• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include "src/core/SkImageFilterTypes.h"
9 
10 #include "src/core/SkImageFilter_Base.h"
11 #include "src/core/SkMatrixPriv.h"
12 
13 // This exists to cover up issues where infinite precision would produce integers but float
14 // math produces values just larger/smaller than an int and roundOut/In on bounds would produce
15 // nearly a full pixel error. One such case is crbug.com/1313579 where the caller has produced
16 // near integer CTM and uses integer crop rects that would grab an extra row/column of the
17 // input image when using a strict roundOut.
18 static constexpr float kRoundEpsilon = 1e-3f;
19 
20 // Both [I]Vectors and Sk[I]Sizes are transformed as non-positioned values, i.e. go through
21 // mapVectors() not mapPoints().
map_as_vector(int32_t x,int32_t y,const SkMatrix & matrix)22 static SkIVector map_as_vector(int32_t x, int32_t y, const SkMatrix& matrix) {
23     SkVector v = SkVector::Make(SkIntToScalar(x), SkIntToScalar(y));
24     matrix.mapVectors(&v, 1);
25     return SkIVector::Make(SkScalarRoundToInt(v.fX), SkScalarRoundToInt(v.fY));
26 }
27 
map_as_vector(SkScalar x,SkScalar y,const SkMatrix & matrix)28 static SkVector map_as_vector(SkScalar x, SkScalar y, const SkMatrix& matrix) {
29     SkVector v = SkVector::Make(x, y);
30     matrix.mapVectors(&v, 1);
31     return v;
32 }
33 
34 // If m is epsilon within the form [1 0 tx], this returns true and sets out to [tx, ty]
35 //                                 [0 1 ty]
36 //                                 [0 0 1 ]
37 // TODO: Use this in decomposeCTM() (and possibly extend it to support is_nearly_scale_translate)
38 // to be a little more forgiving on matrix types during layer configuration.
is_nearly_integer_translation(const skif::LayerSpace<SkMatrix> & m,skif::LayerSpace<SkIPoint> * out=nullptr)39 static bool is_nearly_integer_translation(const skif::LayerSpace<SkMatrix>& m,
40                                           skif::LayerSpace<SkIPoint>* out=nullptr) {
41     float tx = SkScalarRoundToScalar(sk_ieee_float_divide(m.rc(0,2), m.rc(2,2)));
42     float ty = SkScalarRoundToScalar(sk_ieee_float_divide(m.rc(1,2), m.rc(2,2)));
43     SkMatrix expected = SkMatrix::MakeAll(1.f, 0.f, tx,
44                                           0.f, 1.f, ty,
45                                           0.f, 0.f, 1.f);
46     for (int i = 0; i < 9; ++i) {
47         if (!SkScalarNearlyEqual(expected.get(i), m.get(i), kRoundEpsilon)) {
48             return false;
49         }
50     }
51 
52     if (out) {
53         *out = skif::LayerSpace<SkIPoint>({(int) tx, (int) ty});
54     }
55     return true;
56 }
57 
map_rect(const SkMatrix & matrix,const SkRect & rect)58 static SkRect map_rect(const SkMatrix& matrix, const SkRect& rect) {
59     if (rect.isEmpty()) {
60         return SkRect::MakeEmpty();
61     }
62     return matrix.mapRect(rect);
63 }
64 
map_rect(const SkMatrix & matrix,const SkIRect & rect)65 static SkIRect map_rect(const SkMatrix& matrix, const SkIRect& rect) {
66     if (rect.isEmpty()) {
67         return SkIRect::MakeEmpty();
68     }
69     // Unfortunately, there is a range of integer values such that we have 1px precision as an int,
70     // but less precision as a float. This can lead to non-empty SkIRects becoming empty simply
71     // because of float casting. If we're already dealing with a float rect or having a float
72     // output, that's what we're stuck with; but if we are starting form an irect and desiring an
73     // SkIRect output, we go through efforts to preserve the 1px precision for simple transforms.
74     if (matrix.isScaleTranslate()) {
75         double l = (double)matrix.getScaleX()*rect.fLeft   + (double)matrix.getTranslateX();
76         double r = (double)matrix.getScaleX()*rect.fRight  + (double)matrix.getTranslateX();
77         double t = (double)matrix.getScaleY()*rect.fTop    + (double)matrix.getTranslateY();
78         double b = (double)matrix.getScaleY()*rect.fBottom + (double)matrix.getTranslateY();
79 
80         return {sk_double_saturate2int(sk_double_floor(std::min(l, r) + kRoundEpsilon)),
81                 sk_double_saturate2int(sk_double_floor(std::min(t, b) + kRoundEpsilon)),
82                 sk_double_saturate2int(sk_double_ceil(std::max(l, r)  - kRoundEpsilon)),
83                 sk_double_saturate2int(sk_double_ceil(std::max(t, b)  - kRoundEpsilon))};
84     } else {
85         return skif::RoundOut(matrix.mapRect(SkRect::Make(rect)));
86     }
87 }
88 
89 namespace skif {
90 
RoundOut(SkRect r)91 SkIRect RoundOut(SkRect r) { return r.makeInset(kRoundEpsilon, kRoundEpsilon).roundOut(); }
92 
RoundIn(SkRect r)93 SkIRect RoundIn(SkRect r) { return r.makeOutset(kRoundEpsilon, kRoundEpsilon).roundIn(); }
94 
decomposeCTM(const SkMatrix & ctm,const SkImageFilter * filter,const skif::ParameterSpace<SkPoint> & representativePt)95 bool Mapping::decomposeCTM(const SkMatrix& ctm, const SkImageFilter* filter,
96                            const skif::ParameterSpace<SkPoint>& representativePt) {
97     SkMatrix remainder, layer;
98     SkSize decomposed;
99     using MatrixCapability = SkImageFilter_Base::MatrixCapability;
100     MatrixCapability capability =
101             filter ? as_IFB(filter)->getCTMCapability() : MatrixCapability::kComplex;
102     if (capability == MatrixCapability::kTranslate) {
103         // Apply the entire CTM post-filtering
104         remainder = ctm;
105         layer = SkMatrix::I();
106     } else if (ctm.isScaleTranslate() || capability == MatrixCapability::kComplex) {
107         // Either layer space can be anything (kComplex) - or - it can be scale+translate, and the
108         // ctm is. In both cases, the layer space can be equivalent to device space.
109         remainder = SkMatrix::I();
110         layer = ctm;
111     } else if (ctm.decomposeScale(&decomposed, &remainder)) {
112         // This case implies some amount of sampling post-filtering, either due to skew or rotation
113         // in the original matrix. As such, keep the layer matrix as simple as possible.
114         layer = SkMatrix::Scale(decomposed.fWidth, decomposed.fHeight);
115     } else {
116         // Perspective, which has a non-uniform scaling effect on the filter. Pick a single scale
117         // factor that best matches where the filter will be evaluated.
118         SkScalar scale = SkMatrixPriv::DifferentialAreaScale(ctm, SkPoint(representativePt));
119         if (SkScalarIsFinite(scale) && !SkScalarNearlyZero(scale)) {
120             // Now take the sqrt to go from an area scale factor to a scaling per X and Y
121             // FIXME: It would be nice to be able to choose a non-uniform scale.
122             scale = SkScalarSqrt(scale);
123         } else {
124             // The representative point was behind the W = 0 plane, so don't factor out any scale.
125             // NOTE: This makes remainder and layer the same as the MatrixCapability::Translate case
126             scale = 1.f;
127         }
128 
129         remainder = ctm;
130         remainder.preScale(SkScalarInvert(scale), SkScalarInvert(scale));
131         layer = SkMatrix::Scale(scale, scale);
132     }
133 
134     SkMatrix invRemainder;
135     if (!remainder.invert(&invRemainder)) {
136         // Under floating point arithmetic, it's possible to decompose an invertible matrix into
137         // a scaling matrix and a remainder and have the remainder be non-invertible. Generally
138         // when this happens the scale factors are so large and the matrix so ill-conditioned that
139         // it's unlikely that any drawing would be reasonable, so failing to make a layer is okay.
140         return false;
141     } else {
142         fParamToLayerMatrix = layer;
143         fLayerToDevMatrix = remainder;
144         fDevToLayerMatrix = invRemainder;
145         return true;
146     }
147 }
148 
adjustLayerSpace(const SkMatrix & layer)149 bool Mapping::adjustLayerSpace(const SkMatrix& layer) {
150     SkMatrix invLayer;
151     if (!layer.invert(&invLayer)) {
152         return false;
153     }
154     fParamToLayerMatrix.postConcat(layer);
155     fDevToLayerMatrix.postConcat(layer);
156     fLayerToDevMatrix.preConcat(invLayer);
157     return true;
158 }
159 
160 // Instantiate map specializations for the 6 geometric types used during filtering
161 template<>
map(const SkRect & geom,const SkMatrix & matrix)162 SkRect Mapping::map<SkRect>(const SkRect& geom, const SkMatrix& matrix) {
163     return map_rect(matrix, geom);
164 }
165 
166 template<>
map(const SkIRect & geom,const SkMatrix & matrix)167 SkIRect Mapping::map<SkIRect>(const SkIRect& geom, const SkMatrix& matrix) {
168     return map_rect(matrix, geom);
169 }
170 
171 template<>
map(const SkIPoint & geom,const SkMatrix & matrix)172 SkIPoint Mapping::map<SkIPoint>(const SkIPoint& geom, const SkMatrix& matrix) {
173     SkPoint p = SkPoint::Make(SkIntToScalar(geom.fX), SkIntToScalar(geom.fY));
174     matrix.mapPoints(&p, 1);
175     return SkIPoint::Make(SkScalarRoundToInt(p.fX), SkScalarRoundToInt(p.fY));
176 }
177 
178 template<>
map(const SkPoint & geom,const SkMatrix & matrix)179 SkPoint Mapping::map<SkPoint>(const SkPoint& geom, const SkMatrix& matrix) {
180     SkPoint p;
181     matrix.mapPoints(&p, &geom, 1);
182     return p;
183 }
184 
185 template<>
map(const IVector & geom,const SkMatrix & matrix)186 IVector Mapping::map<IVector>(const IVector& geom, const SkMatrix& matrix) {
187     return IVector(map_as_vector(geom.fX, geom.fY, matrix));
188 }
189 
190 template<>
map(const Vector & geom,const SkMatrix & matrix)191 Vector Mapping::map<Vector>(const Vector& geom, const SkMatrix& matrix) {
192     return Vector(map_as_vector(geom.fX, geom.fY, matrix));
193 }
194 
195 template<>
map(const SkISize & geom,const SkMatrix & matrix)196 SkISize Mapping::map<SkISize>(const SkISize& geom, const SkMatrix& matrix) {
197     SkIVector v = map_as_vector(geom.fWidth, geom.fHeight, matrix);
198     return SkISize::Make(v.fX, v.fY);
199 }
200 
201 template<>
map(const SkSize & geom,const SkMatrix & matrix)202 SkSize Mapping::map<SkSize>(const SkSize& geom, const SkMatrix& matrix) {
203     SkVector v = map_as_vector(geom.fWidth, geom.fHeight, matrix);
204     return SkSize::Make(v.fX, v.fY);
205 }
206 
207 template<>
map(const SkMatrix & m,const SkMatrix & matrix)208 SkMatrix Mapping::map<SkMatrix>(const SkMatrix& m, const SkMatrix& matrix) {
209     // If 'matrix' maps from the C1 coord space to the C2 coord space, and 'm' is a transform that
210     // operates on, and outputs to, the C1 coord space, we want to return a new matrix that is
211     // equivalent to 'm' that operates on and outputs to C2. This is the same as mapping the input
212     // from C2 to C1 (matrix^-1), then transforming by 'm', and then mapping from C1 to C2 (matrix).
213     SkMatrix inv;
214     SkAssertResult(matrix.invert(&inv));
215     inv.postConcat(m);
216     inv.postConcat(matrix);
217     return inv;
218 }
219 
mapRect(const LayerSpace<SkRect> & r) const220 LayerSpace<SkRect> LayerSpace<SkMatrix>::mapRect(const LayerSpace<SkRect>& r) const {
221     return LayerSpace<SkRect>(map_rect(fData, SkRect(r)));
222 }
223 
mapRect(const LayerSpace<SkIRect> & r) const224 LayerSpace<SkIRect> LayerSpace<SkMatrix>::mapRect(const LayerSpace<SkIRect>& r) const {
225     return LayerSpace<SkIRect>(map_rect(fData, SkIRect(r)));
226 }
227 
imageAndOffset(SkIPoint * offset) const228 sk_sp<SkSpecialImage> FilterResult::imageAndOffset(SkIPoint* offset) const {
229     auto [image, origin] = this->resolve(fLayerBounds);
230     *offset = SkIPoint(origin);
231     return image;
232 }
233 
applyCrop(const Context & ctx,const LayerSpace<SkIRect> & crop) const234 FilterResult FilterResult::applyCrop(const Context& ctx,
235                                      const LayerSpace<SkIRect>& crop) const {
236     LayerSpace<SkIRect> tightBounds = crop;
237     // TODO(michaelludwig): Intersecting to the target output is only valid when the crop has
238     // decal tiling (the only current option).
239     if (!fImage || !tightBounds.intersect(ctx.desiredOutput())) {
240         // The desired output would be filled with transparent black.
241         return {};
242     }
243 
244     if (crop.contains(fLayerBounds)) {
245         // The original crop does not affect the image (although the context's desired output might)
246         // We can tighten fLayerBounds to the desired output without resolving the image, regardless
247         // of the transform type.
248         // TODO(michaelludwig): If the crop would use mirror or repeat, the above isn't true.
249         FilterResult restrictedOutput = *this;
250         SkAssertResult(restrictedOutput.fLayerBounds.intersect(ctx.desiredOutput()));
251         return restrictedOutput;
252     } else {
253         return this->resolve(tightBounds);
254     }
255 }
256 
compatible_sampling(const SkSamplingOptions & currentSampling,bool currentXformWontAffectNearest,SkSamplingOptions * nextSampling,bool nextXformWontAffectNearest)257 static bool compatible_sampling(const SkSamplingOptions& currentSampling,
258                                 bool currentXformWontAffectNearest,
259                                 SkSamplingOptions* nextSampling,
260                                 bool nextXformWontAffectNearest) {
261     // Both transforms could perform non-trivial sampling, but if they are similar enough we
262     // assume performing one non-trivial sampling operation with the concatenated transform will
263     // not be visually distinguishable from sampling twice.
264     // TODO(michaelludwig): For now ignore mipmap policy, SkSpecialImages are not supposed to be
265     // drawn with mipmapping, and the majority of filter steps produce images that are at the
266     // proper scale and do not define mip levels. The main exception is the ::Image() filter
267     // leaf but that doesn't use this system yet.
268     if (currentSampling.isAniso() && nextSampling->isAniso()) {
269         // Assume we can get away with one sampling at the highest anisotropy level
270         *nextSampling =  SkSamplingOptions::Aniso(std::max(currentSampling.maxAniso,
271                                                            nextSampling->maxAniso));
272         return true;
273     } else if (currentSampling.useCubic && (nextSampling->filter == SkFilterMode::kLinear ||
274                                             (nextSampling->useCubic &&
275                                              currentSampling.cubic.B == nextSampling->cubic.B &&
276                                              currentSampling.cubic.C == nextSampling->cubic.C))) {
277         // Assume we can get away with the current bicubic filter, since the next is the same
278         // or a bilerp that can be upgraded.
279         *nextSampling = currentSampling;
280         return true;
281     } else if (nextSampling->useCubic && currentSampling.filter == SkFilterMode::kLinear) {
282         // Mirror of the above, assume we can just get away with next's cubic resampler
283         return true;
284     } else if (currentSampling.filter == SkFilterMode::kLinear &&
285                nextSampling->filter == SkFilterMode::kLinear) {
286         // Assume we can get away with a single bilerp vs. the two
287         return true;
288     } else if (nextSampling->filter == SkFilterMode::kNearest && currentXformWontAffectNearest) {
289         // The next transform and nearest-neighbor filtering isn't impacted by the current transform
290         SkASSERT(currentSampling.filter == SkFilterMode::kLinear);
291         return true;
292     } else if (currentSampling.filter == SkFilterMode::kNearest && nextXformWontAffectNearest) {
293         // The next transform doesn't change the nearest-neighbor filtering of the current transform
294         SkASSERT(nextSampling->filter == SkFilterMode::kLinear);
295         *nextSampling = currentSampling;
296         return true;
297     } else {
298         // The current or next sampling is nearest neighbor, and will produce visible texels
299         // oriented with the current transform; assume this is a desired effect and preserve it.
300         return false;
301     }
302 }
303 
applyTransform(const Context & ctx,const LayerSpace<SkMatrix> & transform,const SkSamplingOptions & sampling) const304 FilterResult FilterResult::applyTransform(const Context& ctx,
305                                           const LayerSpace<SkMatrix> &transform,
306                                           const SkSamplingOptions &sampling) const {
307     if (!fImage) {
308         // Transformed transparent black remains transparent black.
309         return {};
310     }
311 
312     // Extract the sampling options that matter based on the current and next transforms.
313     // We make sure the new sampling is bilerp (default) if the new transform doesn't matter
314     // (and assert that the current is bilerp if its transform didn't matter). Bilerp can be
315     // maximally combined, so simplifies the logic in compatible_sampling().
316     const bool currentXformIsInteger = is_nearly_integer_translation(fTransform);
317     const bool nextXformIsInteger = is_nearly_integer_translation(transform);
318 
319     SkASSERT(!currentXformIsInteger || fSamplingOptions == kDefaultSampling);
320     SkSamplingOptions nextSampling = nextXformIsInteger ? kDefaultSampling : sampling;
321 
322     FilterResult transformed;
323     if (compatible_sampling(fSamplingOptions, currentXformIsInteger,
324                             &nextSampling, nextXformIsInteger)) {
325         // We can concat transforms and 'nextSampling' will be either fSamplingOptions,
326         // sampling, or a merged combination depending on the two transforms in play.
327         transformed = *this;
328     } else {
329         // We'll have to resolve this FilterResult first before 'transform' and 'sampling' can be
330         // correctly evaluated. 'nextSampling' will always be 'sampling'.
331         transformed = this->resolve(fLayerBounds);
332     }
333 
334     transformed.concatTransform(transform, nextSampling, ctx.desiredOutput());
335     if (transformed.layerBounds().isEmpty()) {
336         return {};
337     } else {
338         return transformed;
339     }
340 }
341 
concatTransform(const LayerSpace<SkMatrix> & transform,const SkSamplingOptions & newSampling,const LayerSpace<SkIRect> & desiredOutput)342 void FilterResult::concatTransform(const LayerSpace<SkMatrix>& transform,
343                                    const SkSamplingOptions& newSampling,
344                                    const LayerSpace<SkIRect>& desiredOutput) {
345     if (!fImage) {
346         // Under normal circumstances, concatTransform() will only be called when we have an image,
347         // but if resolve() fails to make a special surface, we may end up here at which point
348         // doing nothing further is appropriate.
349         return;
350     }
351     fSamplingOptions = newSampling;
352     fTransform.postConcat(transform);
353     // Rebuild the layer bounds and then restrict to the current desired output. The original value
354     // of fLayerBounds includes the image mapped by the original fTransform as well as any
355     // accumulated soft crops from desired outputs of prior stages. To prevent discarding that info,
356     // we map fLayerBounds by the additional transform, instead of re-mapping the image bounds.
357     fLayerBounds = transform.mapRect(fLayerBounds);
358     if (!fLayerBounds.intersect(desiredOutput)) {
359         // The transformed output doesn't touch the desired, so it would just be transparent black.
360         // TODO: This intersection only applies when the tile mode is kDecal.
361         fLayerBounds = LayerSpace<SkIRect>::Empty();
362     }
363 }
364 
resolve(LayerSpace<SkIRect> dstBounds) const365 std::pair<sk_sp<SkSpecialImage>, LayerSpace<SkIPoint>> FilterResult::resolve(
366         LayerSpace<SkIRect> dstBounds) const {
367     // TODO(michaelludwig): Only valid for kDecal, although kClamp would only need 1 extra
368     // pixel of padding so some restriction could happen. We also should skip the intersection if
369     // we need to include transparent black pixels.
370     if (!fImage || !dstBounds.intersect(fLayerBounds)) {
371         return {nullptr, {}};
372     }
373 
374     // TODO: This logic to skip a draw will also need to account for the tile mode, but we can
375     // always restrict to the intersection of dstBounds and the image's subset since we are
376     // currently always decal sampling.
377     // TODO(michaelludwig): If we get to the point where all filter results track bounds in
378     // floating point, then we can extend this case to any S+T transform.
379     LayerSpace<SkIPoint> origin;
380     if (is_nearly_integer_translation(fTransform, &origin)) {
381         LayerSpace<SkIRect> imageBounds(SkIRect::MakeXYWH(origin.x(), origin.y(),
382                                                           fImage->width(), fImage->height()));
383         if (!imageBounds.intersect(dstBounds)) {
384             return {nullptr, {}};
385         }
386 
387         // Offset the image subset directly to avoid issues negating (origin). With the prior
388         // intersection (bounds - origin) will be >= 0, but (bounds + (-origin)) may not, (e.g.
389         // origin is INT_MIN).
390         SkIRect subset = { imageBounds.left() - origin.x(),
391                            imageBounds.top() - origin.y(),
392                            imageBounds.right() - origin.x(),
393                            imageBounds.bottom() - origin.y() };
394         SkASSERT(subset.fLeft >= 0 && subset.fTop >= 0 &&
395                  subset.fRight <= fImage->width() && subset.fBottom <= fImage->height());
396 
397         return {fImage->makeSubset(subset), imageBounds.topLeft()};
398     } // else fall through and attempt a draw
399 
400     sk_sp<SkSpecialSurface> surface = fImage->makeSurface(fImage->colorType(),
401                                                           fImage->getColorSpace(),
402                                                           SkISize(dstBounds.size()),
403                                                           kPremul_SkAlphaType, {});
404     if (!surface) {
405         return {nullptr, {}};
406     }
407     SkCanvas* canvas = surface->getCanvas();
408     // skbug.com/5075: GPU-backed special surfaces don't reset their contents.
409     canvas->clear(SK_ColorTRANSPARENT);
410     canvas->translate(-dstBounds.left(), -dstBounds.top()); // dst's origin adjustment
411 
412     SkPaint paint;
413     paint.setAntiAlias(true);
414     paint.setBlendMode(SkBlendMode::kSrc);
415 
416     // TODO: When using a tile mode other than kDecal, we'll need to use SkSpecialImage::asShader()
417     // and use drawRect(fLayerBounds).
418     if (!fLayerBounds.contains(dstBounds)) {
419         // We're resolving to a larger than necessary image, so make sure transparency outside of
420         // fLayerBounds is preserved.
421         // NOTE: This should only happen when the next layer requires processing transparent black.
422         canvas->clipIRect(SkIRect(fLayerBounds));
423     }
424     canvas->concat(SkMatrix(fTransform)); // src's origin is embedded in fTransform
425     fImage->draw(canvas, 0.f, 0.f, fSamplingOptions, &paint);
426 
427     return {surface->makeImageSnapshot(), dstBounds.topLeft()};
428 }
429 
430 } // end namespace skif
431