1 /* 2 * Copyright 2019 Google LLC 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #ifndef SkImageFilterTypes_DEFINED 9 #define SkImageFilterTypes_DEFINED 10 11 #include "include/core/SkColorSpace.h" 12 #include "include/core/SkMatrix.h" 13 #include "include/core/SkPoint.h" 14 #include "include/core/SkRect.h" 15 #include "include/core/SkSamplingOptions.h" 16 #include "include/core/SkTypes.h" 17 #include "src/core/SkSpecialImage.h" 18 #include "src/core/SkSpecialSurface.h" 19 20 class GrRecordingContext; 21 class SkImageFilter; 22 class SkImageFilterCache; 23 class SkSpecialSurface; 24 class SkSurfaceProps; 25 26 // The skif (SKI[mage]F[ilter]) namespace contains types that are used for filter implementations. 27 // The defined types come in two groups: users of internal Skia types, and templates to help with 28 // readability. Image filters cannot be implemented without access to key internal types, such as 29 // SkSpecialImage. It is possible to avoid the use of the readability templates, although they are 30 // strongly encouraged. 31 namespace skif { 32 33 // Rounds in/out but with a tolerance. 34 SkIRect RoundOut(SkRect); 35 SkIRect RoundIn(SkRect); 36 37 // skif::IVector and skif::Vector represent plain-old-data types for storing direction vectors, so 38 // that the coordinate-space templating system defined below can have a separate type id for 39 // directions vs. points, and specialize appropriately. As such, all operations with direction 40 // vectors are defined on the LayerSpace specialization, since that is the intended point of use. 41 struct IVector { 42 int32_t fX; 43 int32_t fY; 44 45 IVector() = default; IVectorIVector46 IVector(int32_t x, int32_t y) : fX(x), fY(y) {} IVectorIVector47 explicit IVector(const SkIVector& v) : fX(v.fX), fY(v.fY) {} 48 }; 49 50 struct Vector { 51 SkScalar fX; 52 SkScalar fY; 53 54 Vector() = default; VectorVector55 Vector(SkScalar x, SkScalar y) : fX(x), fY(y) {} VectorVector56 explicit Vector(const SkVector& v) : fX(v.fX), fY(v.fY) {} 57 }; 58 59 /////////////////////////////////////////////////////////////////////////////////////////////////// 60 // Coordinate Space Tagging 61 // - In order to enforce correct coordinate spaces in image filter implementations and use, 62 // geometry is wrapped by templated structs to declare in the type system what coordinate space 63 // the coordinates are defined in. 64 // - Currently there is ParameterSpace and DeviceSpace that are data-only wrappers around 65 // coordinates, and the primary LayerSpace that provides all operative functionality for image 66 // filters. It is intended that all logic about image bounds and access be conducted in the shared 67 // layer space. 68 // - The LayerSpace struct has type-safe specializations for SkIRect, SkRect, SkIPoint, SkPoint, 69 // skif::IVector (to distinguish SkIVector from SkIPoint), skif::Vector, SkISize, and SkSize. 70 // - A Mapping object provides type safe coordinate conversions between these spaces, and 71 // automatically does the "right thing" for each geometric type. 72 /////////////////////////////////////////////////////////////////////////////////////////////////// 73 74 // ParameterSpace is a data-only wrapper around Skia's geometric types such as SkIPoint, and SkRect. 75 // Parameter space is the same as the local coordinate space of an SkShader, or the coordinates 76 // passed into SkCanvas::drawX calls, but "local" is avoided due to the alliteration with layer 77 // space. SkImageFilters are defined in terms of ParameterSpace<T> geometry and must use the Mapping 78 // on Context to transform the parameters into LayerSpace to evaluate the filter in the shared 79 // coordinate space of the entire filter DAG. 80 // 81 // A value of ParameterSpace<SkIRect> implies that its wrapped SkIRect is defined in the local 82 // parameter space. 83 template<typename T> 84 class ParameterSpace { 85 public: 86 ParameterSpace() = default; ParameterSpace(const T & data)87 explicit ParameterSpace(const T& data) : fData(data) {} ParameterSpace(T && data)88 explicit ParameterSpace(T&& data) : fData(std::move(data)) {} 89 90 explicit operator const T&() const { return fData; } 91 Optional(const T * ptr)92 static const ParameterSpace<T>* Optional(const T* ptr) { 93 return static_cast<const ParameterSpace<T>*>(reinterpret_cast<const void*>(ptr)); 94 } 95 private: 96 T fData; 97 }; 98 99 // DeviceSpace is a data-only wrapper around Skia's geometric types. It is similar to 100 // 'ParameterSpace' except that it is used to represent geometry that has been transformed or 101 // defined in the root device space (i.e. the final pixels of drawn content). Much of what SkCanvas 102 // tracks, such as its clip bounds are defined in this space and DeviceSpace provides a 103 // type-enforced mechanism for the canvas to pass that information into the image filtering system, 104 // using the Mapping of the filtering context. 105 template<typename T> 106 class DeviceSpace { 107 public: 108 DeviceSpace() = default; DeviceSpace(const T & data)109 explicit DeviceSpace(const T& data) : fData(data) {} DeviceSpace(T && data)110 explicit DeviceSpace(T&& data) : fData(std::move(data)) {} 111 112 explicit operator const T&() const { return fData; } 113 114 private: 115 T fData; 116 }; 117 118 // LayerSpace is a geometric wrapper that specifies the geometry is defined in the shared layer 119 // space where image filters are evaluated. For a given Context (and its Mapping), the image filter 120 // DAG operates in the same coordinate space. This space may be different from the local coordinate 121 // space that defined the image filter parameters (such as blur sigma), and it may be different 122 // from the total CTM of the SkCanvas. 123 // 124 // To encourage correct filter use and implementation, the bulk of filter logic should be performed 125 // in layer space (e.g. determining what portion of an input image to read, or what the output 126 // region is). LayerSpace specializations for the six common Skia math types (Sk[I]Rect, Sk[I]Point, 127 // and Sk[I]Size), and skif::[I]Vector (to allow vectors to be specialized separately from points)) 128 // are provided that mimic their APIs but preserve the coordinate space and enforce type semantics. 129 template<typename T> 130 class LayerSpace {}; 131 132 // Layer-space specialization for integerized direction vectors. 133 template<> 134 class LayerSpace<IVector> { 135 public: 136 LayerSpace() = default; LayerSpace(const IVector & geometry)137 explicit LayerSpace(const IVector& geometry) : fData(geometry) {} LayerSpace(IVector && geometry)138 explicit LayerSpace(IVector&& geometry) : fData(std::move(geometry)) {} 139 explicit operator const IVector&() const { return fData; } 140 SkIVector()141 explicit operator SkIVector() const { return SkIVector::Make(fData.fX, fData.fY); } 142 x()143 int32_t x() const { return fData.fX; } y()144 int32_t y() const { return fData.fY; } 145 146 LayerSpace<IVector> operator-() const { return LayerSpace<IVector>({-fData.fX, -fData.fY}); } 147 148 LayerSpace<IVector> operator+(const LayerSpace<IVector>& v) const { 149 LayerSpace<IVector> sum = *this; 150 sum += v; 151 return sum; 152 } 153 LayerSpace<IVector> operator-(const LayerSpace<IVector>& v) const { 154 LayerSpace<IVector> diff = *this; 155 diff -= v; 156 return diff; 157 } 158 159 void operator+=(const LayerSpace<IVector>& v) { 160 fData.fX += v.fData.fX; 161 fData.fY += v.fData.fY; 162 } 163 void operator-=(const LayerSpace<IVector>& v) { 164 fData.fX -= v.fData.fX; 165 fData.fY -= v.fData.fY; 166 } 167 168 private: 169 IVector fData; 170 }; 171 172 // Layer-space specialization for floating point direction vectors. 173 template<> 174 class LayerSpace<Vector> { 175 public: 176 LayerSpace() = default; LayerSpace(const Vector & geometry)177 explicit LayerSpace(const Vector& geometry) : fData(geometry) {} LayerSpace(Vector && geometry)178 explicit LayerSpace(Vector&& geometry) : fData(std::move(geometry)) {} 179 explicit operator const Vector&() const { return fData; } 180 SkVector()181 explicit operator SkVector() const { return SkVector::Make(fData.fX, fData.fY); } 182 x()183 SkScalar x() const { return fData.fX; } y()184 SkScalar y() const { return fData.fY; } 185 length()186 SkScalar length() const { return SkVector::Length(fData.fX, fData.fY); } 187 188 LayerSpace<Vector> operator-() const { return LayerSpace<Vector>({-fData.fX, -fData.fY}); } 189 190 LayerSpace<Vector> operator*(SkScalar s) const { 191 LayerSpace<Vector> scaled = *this; 192 scaled *= s; 193 return scaled; 194 } 195 196 LayerSpace<Vector> operator+(const LayerSpace<Vector>& v) const { 197 LayerSpace<Vector> sum = *this; 198 sum += v; 199 return sum; 200 } 201 LayerSpace<Vector> operator-(const LayerSpace<Vector>& v) const { 202 LayerSpace<Vector> diff = *this; 203 diff -= v; 204 return diff; 205 } 206 207 void operator*=(SkScalar s) { 208 fData.fX *= s; 209 fData.fY *= s; 210 } 211 void operator+=(const LayerSpace<Vector>& v) { 212 fData.fX += v.fData.fX; 213 fData.fY += v.fData.fY; 214 } 215 void operator-=(const LayerSpace<Vector>& v) { 216 fData.fX -= v.fData.fX; 217 fData.fY -= v.fData.fY; 218 } 219 220 friend LayerSpace<Vector> operator*(SkScalar s, const LayerSpace<Vector>& b) { 221 return b * s; 222 } 223 224 private: 225 Vector fData; 226 }; 227 228 // Layer-space specialization for integer 2D coordinates (treated as positions, not directions). 229 template<> 230 class LayerSpace<SkIPoint> { 231 public: 232 LayerSpace() = default; LayerSpace(const SkIPoint & geometry)233 explicit LayerSpace(const SkIPoint& geometry) : fData(geometry) {} LayerSpace(SkIPoint && geometry)234 explicit LayerSpace(SkIPoint&& geometry) : fData(std::move(geometry)) {} 235 explicit operator const SkIPoint&() const { return fData; } 236 237 // Parrot the SkIPoint API while preserving coordinate space. x()238 int32_t x() const { return fData.fX; } y()239 int32_t y() const { return fData.fY; } 240 241 // Offsetting by direction vectors produce more points 242 LayerSpace<SkIPoint> operator+(const LayerSpace<IVector>& v) { 243 return LayerSpace<SkIPoint>(fData + SkIVector(v)); 244 } 245 LayerSpace<SkIPoint> operator-(const LayerSpace<IVector>& v) { 246 return LayerSpace<SkIPoint>(fData - SkIVector(v)); 247 } 248 249 void operator+=(const LayerSpace<IVector>& v) { 250 fData += SkIVector(v); 251 } 252 void operator-=(const LayerSpace<IVector>& v) { 253 fData -= SkIVector(v); 254 } 255 256 // Subtracting another point makes a direction between them 257 LayerSpace<IVector> operator-(const LayerSpace<SkIPoint>& p) { 258 return LayerSpace<IVector>(IVector(fData - p.fData)); 259 } 260 261 LayerSpace<IVector> operator-() const { return LayerSpace<IVector>({-fData.fX, -fData.fY}); } 262 263 private: 264 SkIPoint fData; 265 }; 266 267 // Layer-space specialization for floating point 2D coordinates (treated as positions) 268 template<> 269 class LayerSpace<SkPoint> { 270 public: 271 LayerSpace() = default; LayerSpace(const SkPoint & geometry)272 explicit LayerSpace(const SkPoint& geometry) : fData(geometry) {} LayerSpace(SkPoint && geometry)273 explicit LayerSpace(SkPoint&& geometry) : fData(std::move(geometry)) {} 274 explicit operator const SkPoint&() const { return fData; } 275 276 // Parrot the SkPoint API while preserving coordinate space. x()277 SkScalar x() const { return fData.fX; } y()278 SkScalar y() const { return fData.fY; } 279 distanceToOrigin()280 SkScalar distanceToOrigin() const { return fData.distanceToOrigin(); } 281 282 // Offsetting by direction vectors produce more points 283 LayerSpace<SkPoint> operator+(const LayerSpace<Vector>& v) { 284 return LayerSpace<SkPoint>(fData + SkVector(v)); 285 } 286 LayerSpace<SkPoint> operator-(const LayerSpace<Vector>& v) { 287 return LayerSpace<SkPoint>(fData - SkVector(v)); 288 } 289 290 void operator+=(const LayerSpace<Vector>& v) { 291 fData += SkVector(v); 292 } 293 void operator-=(const LayerSpace<Vector>& v) { 294 fData -= SkVector(v); 295 } 296 297 // Subtracting another point makes a direction between them 298 LayerSpace<Vector> operator-(const LayerSpace<SkPoint>& p) { 299 return LayerSpace<Vector>(Vector(fData - p.fData)); 300 } 301 302 LayerSpace<Vector> operator-() const { return LayerSpace<Vector>({-fData.fX, -fData.fY}); } 303 304 private: 305 SkPoint fData; 306 }; 307 308 // Layer-space specialization for integer dimensions 309 template<> 310 class LayerSpace<SkISize> { 311 public: 312 LayerSpace() = default; LayerSpace(const SkISize & geometry)313 explicit LayerSpace(const SkISize& geometry) : fData(geometry) {} LayerSpace(SkISize && geometry)314 explicit LayerSpace(SkISize&& geometry) : fData(std::move(geometry)) {} 315 explicit operator const SkISize&() const { return fData; } 316 width()317 int32_t width() const { return fData.width(); } height()318 int32_t height() const { return fData.height(); } 319 isEmpty()320 bool isEmpty() const { return fData.isEmpty(); } 321 322 private: 323 SkISize fData; 324 }; 325 326 // Layer-space specialization for floating point dimensions 327 template<> 328 class LayerSpace<SkSize> { 329 public: 330 LayerSpace() = default; LayerSpace(const SkSize & geometry)331 explicit LayerSpace(const SkSize& geometry) : fData(geometry) {} LayerSpace(SkSize && geometry)332 explicit LayerSpace(SkSize&& geometry) : fData(std::move(geometry)) {} 333 explicit operator const SkSize&() const { return fData; } 334 width()335 SkScalar width() const { return fData.width(); } height()336 SkScalar height() const { return fData.height(); } 337 isEmpty()338 bool isEmpty() const { return fData.isEmpty(); } isZero()339 bool isZero() const { return fData.isZero(); } 340 round()341 LayerSpace<SkISize> round() const { return LayerSpace<SkISize>(fData.toRound()); } ceil()342 LayerSpace<SkISize> ceil() const { return LayerSpace<SkISize>(fData.toCeil()); } floor()343 LayerSpace<SkISize> floor() const { return LayerSpace<SkISize>(fData.toFloor()); } 344 345 private: 346 SkSize fData; 347 }; 348 349 // Layer-space specialization for axis-aligned integer bounding boxes. 350 template<> 351 class LayerSpace<SkIRect> { 352 public: 353 LayerSpace() = default; LayerSpace(const SkIRect & geometry)354 explicit LayerSpace(const SkIRect& geometry) : fData(geometry) {} LayerSpace(SkIRect && geometry)355 explicit LayerSpace(SkIRect&& geometry) : fData(std::move(geometry)) {} LayerSpace(const SkISize & size)356 explicit LayerSpace(const SkISize& size) : fData(SkIRect::MakeSize(size)) {} 357 explicit operator const SkIRect&() const { return fData; } 358 Empty()359 static LayerSpace<SkIRect> Empty() { return LayerSpace<SkIRect>(SkIRect::MakeEmpty()); } 360 361 // Parrot the SkIRect API while preserving coord space isEmpty()362 bool isEmpty() const { return fData.isEmpty(); } contains(const LayerSpace<SkIRect> & r)363 bool contains(const LayerSpace<SkIRect>& r) const { return fData.contains(r.fData); } 364 left()365 int32_t left() const { return fData.fLeft; } top()366 int32_t top() const { return fData.fTop; } right()367 int32_t right() const { return fData.fRight; } bottom()368 int32_t bottom() const { return fData.fBottom; } 369 width()370 int32_t width() const { return fData.width(); } height()371 int32_t height() const { return fData.height(); } 372 topLeft()373 LayerSpace<SkIPoint> topLeft() const { return LayerSpace<SkIPoint>(fData.topLeft()); } size()374 LayerSpace<SkISize> size() const { return LayerSpace<SkISize>(fData.size()); } 375 intersect(const LayerSpace<SkIRect> & r)376 bool intersect(const LayerSpace<SkIRect>& r) { return fData.intersect(r.fData); } join(const LayerSpace<SkIRect> & r)377 void join(const LayerSpace<SkIRect>& r) { fData.join(r.fData); } offset(const LayerSpace<IVector> & v)378 void offset(const LayerSpace<IVector>& v) { fData.offset(SkIVector(v)); } outset(const LayerSpace<SkISize> & delta)379 void outset(const LayerSpace<SkISize>& delta) { fData.outset(delta.width(), delta.height()); } 380 381 private: 382 SkIRect fData; 383 }; 384 385 // Layer-space specialization for axis-aligned float bounding boxes. 386 template<> 387 class LayerSpace<SkRect> { 388 public: 389 LayerSpace() = default; LayerSpace(const SkRect & geometry)390 explicit LayerSpace(const SkRect& geometry) : fData(geometry) {} LayerSpace(SkRect && geometry)391 explicit LayerSpace(SkRect&& geometry) : fData(std::move(geometry)) {} LayerSpace(const LayerSpace<SkIRect> & rect)392 explicit LayerSpace(const LayerSpace<SkIRect>& rect) : fData(SkRect::Make(SkIRect(rect))) {} 393 explicit operator const SkRect&() const { return fData; } 394 Empty()395 static LayerSpace<SkRect> Empty() { return LayerSpace<SkRect>(SkRect::MakeEmpty()); } 396 397 // Parrot the SkRect API while preserving coord space and usage isEmpty()398 bool isEmpty() const { return fData.isEmpty(); } contains(const LayerSpace<SkRect> & r)399 bool contains(const LayerSpace<SkRect>& r) const { return fData.contains(r.fData); } 400 left()401 SkScalar left() const { return fData.fLeft; } top()402 SkScalar top() const { return fData.fTop; } right()403 SkScalar right() const { return fData.fRight; } bottom()404 SkScalar bottom() const { return fData.fBottom; } 405 width()406 SkScalar width() const { return fData.width(); } height()407 SkScalar height() const { return fData.height(); } 408 topLeft()409 LayerSpace<SkPoint> topLeft() const { 410 return LayerSpace<SkPoint>(SkPoint::Make(fData.fLeft, fData.fTop)); 411 } size()412 LayerSpace<SkSize> size() const { 413 return LayerSpace<SkSize>(SkSize::Make(fData.width(), fData.height())); 414 } 415 round()416 LayerSpace<SkIRect> round() const { return LayerSpace<SkIRect>(fData.round()); } roundIn()417 LayerSpace<SkIRect> roundIn() const { return LayerSpace<SkIRect>(RoundIn(fData)); } roundOut()418 LayerSpace<SkIRect> roundOut() const { return LayerSpace<SkIRect>(RoundOut(fData)); } 419 intersect(const LayerSpace<SkRect> & r)420 bool intersect(const LayerSpace<SkRect>& r) { return fData.intersect(r.fData); } join(const LayerSpace<SkRect> & r)421 void join(const LayerSpace<SkRect>& r) { fData.join(r.fData); } offset(const LayerSpace<Vector> & v)422 void offset(const LayerSpace<Vector>& v) { fData.offset(SkVector(v)); } outset(const LayerSpace<SkSize> & delta)423 void outset(const LayerSpace<SkSize>& delta) { fData.outset(delta.width(), delta.height()); } 424 425 private: 426 SkRect fData; 427 }; 428 429 // A transformation that manipulates geometry in the layer-space coordinate system. Mathematically 430 // there's little difference from these matrices compared to what's stored in a skif::Mapping, but 431 // the intent differs. skif::Mapping's matrices map geometry from one coordinate space to another 432 // while these transforms move geometry w/o changing the coordinate space semantics. 433 // TODO(michaelludwig): Will be replaced with an SkM44 version when skif::Mapping works with SkM44. 434 template<> 435 class LayerSpace<SkMatrix> { 436 public: 437 LayerSpace() = default; LayerSpace(const SkMatrix & m)438 explicit LayerSpace(const SkMatrix& m) : fData(m) {} LayerSpace(SkMatrix && m)439 explicit LayerSpace(SkMatrix&& m) : fData(std::move(m)) {} 440 explicit operator const SkMatrix&() const { return fData; } 441 442 // Parrot a limited selection of the SkMatrix API while preserving coordinate space. 443 LayerSpace<SkRect> mapRect(const LayerSpace<SkRect>& r) const; 444 445 // Effectively mapRect(SkRect).roundOut() but more accurate when the underlying matrix or 446 // SkIRect has large floating point values. 447 LayerSpace<SkIRect> mapRect(const LayerSpace<SkIRect>& r) const; 448 mapPoint(const LayerSpace<SkPoint> & p)449 LayerSpace<SkPoint> mapPoint(const LayerSpace<SkPoint>& p) const { 450 return LayerSpace<SkPoint>(fData.mapPoint(SkPoint(p))); 451 } 452 mapVector(const LayerSpace<Vector> & v)453 LayerSpace<Vector> mapVector(const LayerSpace<Vector>& v) const { 454 return LayerSpace<Vector>(Vector(fData.mapVector(v.x(), v.y()))); 455 } 456 preConcat(const LayerSpace<SkMatrix> & m)457 LayerSpace<SkMatrix>& preConcat(const LayerSpace<SkMatrix>& m) { 458 fData = SkMatrix::Concat(fData, m.fData); 459 return *this; 460 } 461 postConcat(const LayerSpace<SkMatrix> & m)462 LayerSpace<SkMatrix>& postConcat(const LayerSpace<SkMatrix>& m) { 463 fData = SkMatrix::Concat(m.fData, fData); 464 return *this; 465 } 466 invert(LayerSpace<SkMatrix> * inverse)467 bool invert(LayerSpace<SkMatrix>* inverse) const { 468 return fData.invert(&inverse->fData); 469 } 470 rc(int row,int col)471 float rc(int row, int col) const { return fData.rc(row, col); } get(int i)472 float get(int i) const { return fData.get(i); } 473 474 private: 475 SkMatrix fData; 476 }; 477 478 // Mapping is the primary definition of the shared layer space used when evaluating an image filter 479 // DAG. It encapsulates any needed decomposition of the total CTM into the parameter-to-layer matrix 480 // (that filters use to map their parameters to the layer space), and the layer-to-device matrix 481 // (that canvas uses to map the output layer-space image into its root device space). Mapping 482 // defines functions to transform ParameterSpace and DeviceSpace types to and from their LayerSpace 483 // variants, which can then be used and reasoned about by SkImageFilter implementations. 484 class Mapping { 485 public: 486 Mapping() = default; 487 488 // Helper constructor that equates device and layer space to the same coordinate space. Mapping(const SkMatrix & paramToLayer)489 explicit Mapping(const SkMatrix& paramToLayer) 490 : fLayerToDevMatrix(SkMatrix::I()) 491 , fParamToLayerMatrix(paramToLayer) 492 , fDevToLayerMatrix(SkMatrix::I()) {} 493 494 // This constructor allows the decomposition to be explicitly provided, assumes that 495 // 'layerToDev's inverse has already been calculated in 'devToLayer' Mapping(const SkMatrix & layerToDev,const SkMatrix & devToLayer,const SkMatrix & paramToLayer)496 Mapping(const SkMatrix& layerToDev, const SkMatrix& devToLayer, const SkMatrix& paramToLayer) 497 : fLayerToDevMatrix(layerToDev) 498 , fParamToLayerMatrix(paramToLayer) 499 , fDevToLayerMatrix(devToLayer) {} 500 501 // Sets this Mapping to the default decomposition of the canvas's total transform, given the 502 // requirements of the 'filter'. Returns false if the decomposition failed or would produce an 503 // invalid device matrix. Assumes 'ctm' is invertible. 504 bool SK_WARN_UNUSED_RESULT decomposeCTM(const SkMatrix& ctm, 505 const SkImageFilter* filter, 506 const skif::ParameterSpace<SkPoint>& representativePt); 507 508 // Update the mapping's parameter-to-layer matrix to be pre-concatenated with the specified 509 // local space transformation. This changes the definition of parameter space, any 510 // skif::ParameterSpace<> values are interpreted anew. Layer space and device space are 511 // unchanged. concatLocal(const SkMatrix & local)512 void concatLocal(const SkMatrix& local) { fParamToLayerMatrix.preConcat(local); } 513 514 // Update the mapping's layer space coordinate system by post-concatenating the given matrix 515 // to it's parameter-to-layer transform, and pre-concatenating the inverse of the matrix with 516 // it's layer-to-device transform. The net effect is that neither the parameter nor device 517 // coordinate systems are changed, but skif::LayerSpace is adjusted. 518 // 519 // Returns false if the layer matrix cannot be inverted, and this mapping is left unmodified. 520 bool adjustLayerSpace(const SkMatrix& layer); 521 522 // Update the mapping's layer space so that the point 'origin' in the current layer coordinate 523 // space maps to (0, 0) in the adjusted coordinate space. applyOrigin(const LayerSpace<SkIPoint> & origin)524 void applyOrigin(const LayerSpace<SkIPoint>& origin) { 525 SkAssertResult(this->adjustLayerSpace(SkMatrix::Translate(-origin.x(), -origin.y()))); 526 } 527 layerToDevice()528 const SkMatrix& layerToDevice() const { return fLayerToDevMatrix; } deviceToLayer()529 const SkMatrix& deviceToLayer() const { return fDevToLayerMatrix; } layerMatrix()530 const SkMatrix& layerMatrix() const { return fParamToLayerMatrix; } totalMatrix()531 SkMatrix totalMatrix() const { 532 return SkMatrix::Concat(fLayerToDevMatrix, fParamToLayerMatrix); 533 } 534 535 template<typename T> paramToLayer(const ParameterSpace<T> & paramGeometry)536 LayerSpace<T> paramToLayer(const ParameterSpace<T>& paramGeometry) const { 537 return LayerSpace<T>(map(static_cast<const T&>(paramGeometry), fParamToLayerMatrix)); 538 } 539 540 template<typename T> deviceToLayer(const DeviceSpace<T> & devGeometry)541 LayerSpace<T> deviceToLayer(const DeviceSpace<T>& devGeometry) const { 542 return LayerSpace<T>(map(static_cast<const T&>(devGeometry), fDevToLayerMatrix)); 543 } 544 545 template<typename T> layerToDevice(const LayerSpace<T> & layerGeometry)546 DeviceSpace<T> layerToDevice(const LayerSpace<T>& layerGeometry) const { 547 return DeviceSpace<T>(map(static_cast<const T&>(layerGeometry), fLayerToDevMatrix)); 548 } 549 550 private: 551 // The image filter process decomposes the total CTM into layerToDev * paramToLayer and uses the 552 // param-to-layer matrix to define the layer-space coordinate system. Depending on how it's 553 // decomposed, either the layer matrix or the device matrix could be the identity matrix (but 554 // sometimes neither). 555 SkMatrix fLayerToDevMatrix; 556 SkMatrix fParamToLayerMatrix; 557 558 // Cached inverse of fLayerToDevMatrix 559 SkMatrix fDevToLayerMatrix; 560 561 // Actual geometric mapping operations that work on coordinates and matrices w/o the type 562 // safety of the coordinate space wrappers (hence these are private). 563 template<typename T> 564 static T map(const T& geom, const SkMatrix& matrix); 565 }; 566 567 class Context; // Forward declare for FilterResult 568 569 // Wraps an SkSpecialImage and metadata needed to rasterize it to a shared layer coordinate space. 570 // This includes a transform matrix, sampling options, and clip. Frequently, the transform is an 571 // integer translation that effectively places the origin of the image within the layer space. When 572 // this is the case, the FilterResult's layerBounds have the same width and height as the subset 573 // of the special image and translated to that origin. However, the transform of a FilterResult can 574 // be arbitrary, in which case its layer bounds is the bounding box that would contain the sampled 575 // image's contents. 576 // 577 // In order to collapse image filter nodes dynamically, FilterResult provides utilities to apply 578 // operations (like transform or crop) that attempt to modify the metadata without producing an 579 // intermediate image. Internally it tracks when a new image is needed and rasterizes as needed. 580 // 581 // When filter implementations are processing intermediate FilterResult results, it can be assumed 582 // that all FilterResult' layerBounds are in the same coordinate space defined by the shared 583 // skif::Context. 584 // 585 // NOTE: This is named FilterResult since most instances will represent the output of an image 586 // filter (even if that is then used as an input to the next filter). The main exception is the 587 // source input used when an input filter is null, but from a data-standpoint it is the same since 588 // it is equivalent to the result of an identity filter. 589 class FilterResult { 590 // Bilinear is used as the default because it can be downgraded to nearest-neighbor when the 591 // final transform is pixel-aligned, and chaining multiple bilinear samples and transforms is 592 // assumed to be visually close enough to sampling once at highest quality and final transform. 593 static constexpr SkSamplingOptions kDefaultSampling{SkFilterMode::kLinear}; 594 public: FilterResult()595 FilterResult() : FilterResult(nullptr) {} 596 FilterResult(sk_sp<SkSpecialImage> image)597 explicit FilterResult(sk_sp<SkSpecialImage> image) 598 : FilterResult(std::move(image), LayerSpace<SkIPoint>({0, 0})) {} 599 FilterResult(std::pair<sk_sp<SkSpecialImage>,LayerSpace<SkIPoint>> imageAndOrigin)600 FilterResult(std::pair<sk_sp<SkSpecialImage>, LayerSpace<SkIPoint>> imageAndOrigin) 601 : FilterResult(std::move(std::get<0>(imageAndOrigin)), std::get<1>(imageAndOrigin)) {} 602 FilterResult(sk_sp<SkSpecialImage> image,const LayerSpace<SkIPoint> & origin)603 FilterResult(sk_sp<SkSpecialImage> image, const LayerSpace<SkIPoint>& origin) 604 : fImage(std::move(image)) 605 , fSamplingOptions(kDefaultSampling) 606 , fTransform(SkMatrix::Translate(origin.x(), origin.y())) 607 , fLayerBounds( 608 fTransform.mapRect(LayerSpace<SkIRect>(fImage ? fImage->dimensions() 609 : SkISize{0, 0}))) {} 610 611 explicit operator bool() const { return SkToBool(fImage); } 612 613 // TODO(michaelludwig): Given the planned expansion of FilterResult state, it might be nice to 614 // pull this back and not expose anything other than its bounding box. This will be possible if 615 // all rendering can be handled by functions defined on FilterResult. image()616 const SkSpecialImage* image() const { return fImage.get(); } refImage()617 sk_sp<SkSpecialImage> refImage() const { return fImage; } 618 619 // Get the layer-space bounds of the result. This will incorporate any layer-space transform. layerBounds()620 LayerSpace<SkIRect> layerBounds() const { 621 return fLayerBounds; 622 } 623 624 // Produce a new FilterResult that has been cropped to 'crop', taking into account the context's 625 // desired output. When possible, the returned FilterResult will reuse the underlying image and 626 // adjust its metadata. This will depend on the current transform and tile mode as well as how 627 // the crop rect intersects this result's layer bounds. 628 // TODO (michaelludwig): All FilterResults are decal mode and there are no current usages that 629 // require force-padding a decal FilterResult so these arguments aren't implemented yet. 630 FilterResult applyCrop(const Context& ctx, 631 const LayerSpace<SkIRect>& crop) const; 632 // SkTileMode newTileMode=SkTileMode::kDecal, 633 // bool forcePad=false) const; 634 635 // Produce a new FilterResult that is the transformation of this FilterResult. When this 636 // result's sampling and transform are compatible with the new transformation, the returned 637 // FilterResult can reuse the same image data and adjust just the metadata. 638 FilterResult applyTransform(const Context& ctx, 639 const LayerSpace<SkMatrix>& transform, 640 const SkSamplingOptions& sampling) const; 641 642 // Extract image and origin, safely when the image is null. If there are deferred operations 643 // on FilterResult (such as tiling or transforms) not representable as an image+origin pair, 644 // the returned image will be the resolution resulting from that metadata and not necessarily 645 // equal to the original 'image()'. 646 // TODO (michaelludwig) - This is intended for convenience until all call sites of 647 // SkImageFilter_Base::filterImage() have been updated to work in the new type system 648 // (which comes later as SkDevice, SkCanvas, etc. need to be modified, and coordinate space 649 // tagging needs to be added). 650 sk_sp<SkSpecialImage> imageAndOffset(SkIPoint* offset) const; 651 652 private: 653 // Renders this FilterResult into a new, but visually equivalent, image that fills 'dstBounds', 654 // has nearest-neighbor sampling, and a transform that just translates by 'dstBounds' TL corner. 655 std::pair<sk_sp<SkSpecialImage>, LayerSpace<SkIPoint>> 656 resolve(LayerSpace<SkIRect> dstBounds) const; 657 658 // Update metadata to concat the given transform directly. 659 void concatTransform(const LayerSpace<SkMatrix>& transform, 660 const SkSamplingOptions& newSampling, 661 const LayerSpace<SkIRect>& outputBounds); 662 663 // The effective image of a FilterResult is 'fImage' sampled by 'fSamplingOptions' and 664 // respecting 'fTileMode' (on the SkSpecialImage's subset), transformed by 'fTransform', clipped 665 // to 'fLayerBounds'. 666 sk_sp<SkSpecialImage> fImage; 667 SkSamplingOptions fSamplingOptions; 668 // SkTileMode fTileMode = SkTileMode::kDecal; 669 // Typically this will be an integer translation that encodes the origin of the top left corner, 670 // but can become more complex when combined with applyTransform(). 671 LayerSpace<SkMatrix> fTransform; 672 673 // The layer bounds are initially fImage's dimensions mapped by fTransform. As the filter result 674 // is processed by the image filter DAG, it can be further restricted by crop rects or the 675 // implicit desired output at each node. 676 LayerSpace<SkIRect> fLayerBounds; 677 }; 678 679 // The context contains all necessary information to describe how the image filter should be 680 // computed (i.e. the current layer matrix and clip), and the color information of the output of a 681 // filter DAG. For now, this is just the color space (of the original requesting device). This is 682 // used when constructing intermediate rendering surfaces, so that we ensure we land in a surface 683 // that's similar/compatible to the final consumer of the DAG's output. 684 class Context { 685 public: 686 // Creates a context with the given layer matrix and destination clip, reading from 'source' 687 // with an origin of (0,0). Context(const SkMatrix & layerMatrix,const SkIRect & clipBounds,SkImageFilterCache * cache,SkColorType colorType,SkColorSpace * colorSpace,const SkSpecialImage * source)688 Context(const SkMatrix& layerMatrix, const SkIRect& clipBounds, SkImageFilterCache* cache, 689 SkColorType colorType, SkColorSpace* colorSpace, const SkSpecialImage* source) 690 : fMapping(layerMatrix) 691 , fDesiredOutput(clipBounds) 692 , fCache(cache) 693 , fColorType(colorType) 694 , fColorSpace(colorSpace) 695 , fSource(sk_ref_sp(source), LayerSpace<SkIPoint>({0, 0})) {} 696 Context(const Mapping & mapping,const LayerSpace<SkIRect> & desiredOutput,SkImageFilterCache * cache,SkColorType colorType,SkColorSpace * colorSpace,const FilterResult & source)697 Context(const Mapping& mapping, const LayerSpace<SkIRect>& desiredOutput, 698 SkImageFilterCache* cache, SkColorType colorType, SkColorSpace* colorSpace, 699 const FilterResult& source) 700 : fMapping(mapping) 701 , fDesiredOutput(desiredOutput) 702 , fCache(cache) 703 , fColorType(colorType) 704 , fColorSpace(colorSpace) 705 , fSource(source) {} 706 707 // The mapping that defines the transformation from local parameter space of the filters to the 708 // layer space where the image filters are evaluated, as well as the remaining transformation 709 // from the layer space to the final device space. The layer space defined by the returned 710 // Mapping may be the same as the root device space, or be an intermediate space that is 711 // supported by the image filter DAG (depending on what it returns from getCTMCapability()). 712 // If a node returns something other than kComplex from getCTMCapability(), the layer matrix of 713 // the mapping will respect that return value, and the remaining matrix will be appropriately 714 // set to transform the layer space to the final device space (applied by the SkCanvas when 715 // filtering is finished). mapping()716 const Mapping& mapping() const { return fMapping; } 717 // DEPRECATED: Use mapping() and its coordinate-space types instead ctm()718 const SkMatrix& ctm() const { return fMapping.layerMatrix(); } 719 // The bounds, in the layer space, that the filtered image will be clipped to. The output 720 // from filterImage() must cover these clip bounds, except in areas where it will just be 721 // transparent black, in which case a smaller output image can be returned. desiredOutput()722 const LayerSpace<SkIRect>& desiredOutput() const { return fDesiredOutput; } 723 // DEPRECATED: Use desiredOutput() instead clipBounds()724 const SkIRect& clipBounds() const { return static_cast<const SkIRect&>(fDesiredOutput); } 725 // The cache to use when recursing through the filter DAG, in order to avoid repeated 726 // calculations of the same image. cache()727 SkImageFilterCache* cache() const { return fCache; } 728 // The output device's color type, which can be used for intermediate images to be 729 // compatible with the eventual target of the filtered result. colorType()730 SkColorType colorType() const { return fColorType; } 731 #if defined(SK_GANESH) grColorType()732 GrColorType grColorType() const { return SkColorTypeToGrColorType(fColorType); } 733 #endif 734 // The output device's color space, so intermediate images can match, and so filtering can 735 // be performed in the destination color space. colorSpace()736 SkColorSpace* colorSpace() const { return fColorSpace; } refColorSpace()737 sk_sp<SkColorSpace> refColorSpace() const { return sk_ref_sp(fColorSpace); } 738 // The default surface properties to use when making transient surfaces during filtering. surfaceProps()739 const SkSurfaceProps& surfaceProps() const { return fSource.image()->props(); } 740 741 // This is the image to use whenever an expected input filter has been set to null. In the 742 // majority of cases, this is the original source image for the image filter DAG so it comes 743 // from the SkDevice that holds either the saveLayer or the temporary rendered result. The 744 // exception is composing two image filters (via SkImageFilters::Compose), which must use 745 // the output of the inner DAG as the "source" for the outer DAG. source()746 const FilterResult& source() const { return fSource; } 747 // DEPRECATED: Use source() instead to get both the image and its origin. sourceImage()748 const SkSpecialImage* sourceImage() const { return fSource.image(); } 749 750 // True if image filtering should occur on the GPU if possible. gpuBacked()751 bool gpuBacked() const { return fSource.image()->isTextureBacked(); } 752 // The recording context to use when computing the filter with the GPU. getContext()753 GrRecordingContext* getContext() const { return fSource.image()->getContext(); } 754 755 /** 756 * Since a context can be built directly, its constructor has no chance to "return null" if 757 * it's given invalid or unsupported inputs. Call this to know of the the context can be 758 * used. 759 * 760 * The SkImageFilterCache Key, for example, requires a finite ctm (no infinities or NaN), 761 * so that test is part of isValid. 762 */ isValid()763 bool isValid() const { return fSource.image() != nullptr && fMapping.layerMatrix().isFinite(); } 764 765 // Create a surface of the given size, that matches the context's color type and color space 766 // as closely as possible, and uses the same backend of the device that produced the source 767 // image. 768 sk_sp<SkSpecialSurface> makeSurface(const SkISize& size, 769 const SkSurfaceProps* props = nullptr) const { 770 if (!props) { 771 props = &this->surfaceProps(); 772 } 773 return fSource.image()->makeSurface(fColorType, fColorSpace, size, 774 kPremul_SkAlphaType, *props); 775 } 776 777 // Create a new context that matches this context, but with an overridden layer space. withNewMapping(const Mapping & mapping)778 Context withNewMapping(const Mapping& mapping) const { 779 return Context(mapping, fDesiredOutput, fCache, fColorType, fColorSpace, fSource); 780 } 781 // Create a new context that matches this context, but with an overridden desired output rect. withNewDesiredOutput(const LayerSpace<SkIRect> & desiredOutput)782 Context withNewDesiredOutput(const LayerSpace<SkIRect>& desiredOutput) const { 783 return Context(fMapping, desiredOutput, fCache, fColorType, fColorSpace, fSource); 784 } 785 786 private: 787 Mapping fMapping; 788 LayerSpace<SkIRect> fDesiredOutput; 789 SkImageFilterCache* fCache; 790 SkColorType fColorType; 791 // The pointed-to object is owned by the device controlling the filter process, and our lifetime 792 // is bounded by the device, so this can be a bare pointer. 793 SkColorSpace* fColorSpace; 794 FilterResult fSource; 795 }; 796 797 } // end namespace skif 798 799 #endif // SkImageFilterTypes_DEFINED 800