[skif] Consolidate mapping/layerspace code
Removes the static functions that were used by both Mapping and
LayerSpace<T>, and instead just has LayerSpace<T> use Mapping's
functionality.
Adjusts some of the ceil/floor functions to match how rectangles are
rounded (i.e. include the rounding epsilon).
Improves mapSize to be more correct when the transform is not just a
scale/translate matrix.
Bug: skia:9282
Change-Id: I1116cae9d7d3a486d601f985c2c779259bb2acda
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/706438
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
diff --git a/src/core/SkImageFilterTypes.cpp b/src/core/SkImageFilterTypes.cpp
index 5d0970b..81c37ca 100644
--- a/src/core/SkImageFilterTypes.cpp
+++ b/src/core/SkImageFilterTypes.cpp
@@ -28,87 +28,6 @@
// input image when using a strict roundOut.
static constexpr float kRoundEpsilon = 1e-3f;
-// Both [I]Vectors and Sk[I]Sizes are transformed as non-positioned values, i.e. go through
-// mapVectors() not mapPoints().
-SkIVector map_as_vector(int32_t x, int32_t y, const SkMatrix& matrix) {
- SkVector v = SkVector::Make(SkIntToScalar(x), SkIntToScalar(y));
- matrix.mapVectors(&v, 1);
- return SkIVector::Make(SkScalarRoundToInt(v.fX), SkScalarRoundToInt(v.fY));
-}
-
-SkVector map_as_vector(SkScalar x, SkScalar y, const SkMatrix& matrix) {
- SkVector v = SkVector::Make(x, y);
- matrix.mapVectors(&v, 1);
- return v;
-}
-
-SkRect map_rect(const SkMatrix& matrix, const SkRect& rect) {
- return rect.isEmpty() ? SkRect::MakeEmpty() : matrix.mapRect(rect);
-}
-
-SkIRect map_rect(const SkMatrix& matrix, const SkIRect& rect) {
- if (rect.isEmpty()) {
- return SkIRect::MakeEmpty();
- }
- // Unfortunately, there is a range of integer values such that we have 1px precision as an int,
- // but less precision as a float. This can lead to non-empty SkIRects becoming empty simply
- // because of float casting. If we're already dealing with a float rect or having a float
- // output, that's what we're stuck with; but if we are starting form an irect and desiring an
- // SkIRect output, we go through efforts to preserve the 1px precision for simple transforms.
- if (matrix.isScaleTranslate()) {
- double l = (double)matrix.getScaleX()*rect.fLeft + (double)matrix.getTranslateX();
- double r = (double)matrix.getScaleX()*rect.fRight + (double)matrix.getTranslateX();
- double t = (double)matrix.getScaleY()*rect.fTop + (double)matrix.getTranslateY();
- double b = (double)matrix.getScaleY()*rect.fBottom + (double)matrix.getTranslateY();
- return {sk_double_saturate2int(sk_double_floor(std::min(l, r) + kRoundEpsilon)),
- sk_double_saturate2int(sk_double_floor(std::min(t, b) + kRoundEpsilon)),
- sk_double_saturate2int(sk_double_ceil(std::max(l, r) - kRoundEpsilon)),
- sk_double_saturate2int(sk_double_ceil(std::max(t, b) - kRoundEpsilon))};
- } else {
- return skif::RoundOut(matrix.mapRect(SkRect::Make(rect)));
- }
-}
-
-bool inverse_map_rect(const SkMatrix& matrix, const SkRect& rect, SkRect* out) {
- if (rect.isEmpty()) {
- *out = SkRect::MakeEmpty();
- return true;
- }
- return SkMatrixPriv::InverseMapRect(matrix, out, rect);
-}
-
-bool inverse_map_rect(const SkMatrix& matrix, const SkIRect& rect, SkIRect* out) {
- if (rect.isEmpty()) {
- *out = SkIRect::MakeEmpty();
- return true;
- }
- // This is a specialized inverse equivalent to the 1px precision preserving map_rect above.
- if (matrix.isScaleTranslate()) {
- // A scale-translate matrix with a 0 scale factor is not invertible.
- if (matrix.getScaleX() == 0.f || matrix.getScaleY() == 0.f) {
- return false;
- }
- double l = (rect.fLeft - (double)matrix.getTranslateX()) / (double)matrix.getScaleX();
- double r = (rect.fRight - (double)matrix.getTranslateX()) / (double)matrix.getScaleX();
- double t = (rect.fTop - (double)matrix.getTranslateY()) / (double)matrix.getScaleY();
- double b = (rect.fBottom - (double)matrix.getTranslateY()) / (double)matrix.getScaleY();
-
- *out = {sk_double_saturate2int(sk_double_floor(std::min(l, r) + kRoundEpsilon)),
- sk_double_saturate2int(sk_double_floor(std::min(t, b) + kRoundEpsilon)),
- sk_double_saturate2int(sk_double_ceil(std::max(l, r) - kRoundEpsilon)),
- sk_double_saturate2int(sk_double_ceil(std::max(t, b) - kRoundEpsilon))};
- return true;
- } else {
- SkRect mapped;
- if (inverse_map_rect(matrix, SkRect::Make(rect), &mapped)) {
- *out = skif::RoundOut(mapped);
- return true;
- } else {
- return false;
- }
- }
-}
-
// If m is epsilon within the form [1 0 tx], this returns true and sets out to [tx, ty]
// [0 1 ty]
// [0 0 1 ]
@@ -223,10 +142,6 @@
///////////////////////////////////////////////////////////////////////////////////////////////////
-SkIRect RoundOut(SkRect r) { return r.makeInset(kRoundEpsilon, kRoundEpsilon).roundOut(); }
-
-SkIRect RoundIn(SkRect r) { return r.makeOutset(kRoundEpsilon, kRoundEpsilon).roundIn(); }
-
sk_sp<SkSpecialSurface> Context::makeSurface(const SkISize& size,
const SkSurfaceProps* props) const {
if (!props) {
@@ -260,6 +175,10 @@
///////////////////////////////////////////////////////////////////////////////////////////////////
// Mapping
+SkIRect RoundOut(SkRect r) { return r.makeInset(kRoundEpsilon, kRoundEpsilon).roundOut(); }
+
+SkIRect RoundIn(SkRect r) { return r.makeOutset(kRoundEpsilon, kRoundEpsilon).roundIn(); }
+
bool Mapping::decomposeCTM(const SkMatrix& ctm, const SkImageFilter* filter,
const skif::ParameterSpace<SkPoint>& representativePt) {
SkMatrix remainder, layer;
@@ -328,12 +247,31 @@
// Instantiate map specializations for the 6 geometric types used during filtering
template<>
SkRect Mapping::map<SkRect>(const SkRect& geom, const SkMatrix& matrix) {
- return map_rect(matrix, geom);
+ return geom.isEmpty() ? SkRect::MakeEmpty() : matrix.mapRect(geom);
}
template<>
SkIRect Mapping::map<SkIRect>(const SkIRect& geom, const SkMatrix& matrix) {
- return map_rect(matrix, geom);
+ if (geom.isEmpty()) {
+ return SkIRect::MakeEmpty();
+ }
+ // Unfortunately, there is a range of integer values such that we have 1px precision as an int,
+ // but less precision as a float. This can lead to non-empty SkIRects becoming empty simply
+ // because of float casting. If we're already dealing with a float rect or having a float
+ // output, that's what we're stuck with; but if we are starting form an irect and desiring an
+ // SkIRect output, we go through efforts to preserve the 1px precision for simple transforms.
+ if (matrix.isScaleTranslate()) {
+ double l = (double)matrix.getScaleX()*geom.fLeft + (double)matrix.getTranslateX();
+ double r = (double)matrix.getScaleX()*geom.fRight + (double)matrix.getTranslateX();
+ double t = (double)matrix.getScaleY()*geom.fTop + (double)matrix.getTranslateY();
+ double b = (double)matrix.getScaleY()*geom.fBottom + (double)matrix.getTranslateY();
+ return {sk_double_saturate2int(sk_double_floor(std::min(l, r) + kRoundEpsilon)),
+ sk_double_saturate2int(sk_double_floor(std::min(t, b) + kRoundEpsilon)),
+ sk_double_saturate2int(sk_double_ceil(std::max(l, r) - kRoundEpsilon)),
+ sk_double_saturate2int(sk_double_ceil(std::max(t, b) - kRoundEpsilon))};
+ } else {
+ return RoundOut(matrix.mapRect(SkRect::Make(geom)));
+ }
}
template<>
@@ -351,25 +289,40 @@
}
template<>
-IVector Mapping::map<IVector>(const IVector& geom, const SkMatrix& matrix) {
- return IVector(map_as_vector(geom.fX, geom.fY, matrix));
+Vector Mapping::map<Vector>(const Vector& geom, const SkMatrix& matrix) {
+ SkVector v = SkVector::Make(geom.fX, geom.fY);
+ matrix.mapVectors(&v, 1);
+ return Vector{v};
}
template<>
-Vector Mapping::map<Vector>(const Vector& geom, const SkMatrix& matrix) {
- return Vector(map_as_vector(geom.fX, geom.fY, matrix));
+IVector Mapping::map<IVector>(const IVector& geom, const SkMatrix& matrix) {
+ SkVector v = SkVector::Make(SkIntToScalar(geom.fX), SkIntToScalar(geom.fY));
+ matrix.mapVectors(&v, 1);
+ return IVector(SkScalarRoundToInt(v.fX), SkScalarRoundToInt(v.fY));
+}
+
+// Sizes are also treated as non-positioned values (although this assumption breaks down if there's
+// perspective). Unlike vectors, we treat input sizes as specifying lengths of the local X and Y
+// axes and return the lengths of those mapped axes.
+template<>
+SkSize Mapping::map<SkSize>(const SkSize& geom, const SkMatrix& matrix) {
+ if (matrix.isScaleTranslate()) {
+ // This is equivalent to mapping the two basis vectors and calculating their lengths.
+ SkVector sizes = matrix.mapVector(geom.fWidth, geom.fHeight);
+ return {SkScalarAbs(sizes.fX), SkScalarAbs(sizes.fY)};
+ }
+
+ SkVector xAxis = matrix.mapVector(geom.fWidth, 0.f);
+ SkVector yAxis = matrix.mapVector(0.f, geom.fHeight);
+ return {xAxis.length(), yAxis.length()};
}
template<>
SkISize Mapping::map<SkISize>(const SkISize& geom, const SkMatrix& matrix) {
- SkIVector v = map_as_vector(geom.fWidth, geom.fHeight, matrix);
- return SkISize::Make(v.fX, v.fY);
-}
-
-template<>
-SkSize Mapping::map<SkSize>(const SkSize& geom, const SkMatrix& matrix) {
- SkVector v = map_as_vector(geom.fWidth, geom.fHeight, matrix);
- return SkSize::Make(v.fX, v.fY);
+ SkSize size = map(SkSize::Make(geom), matrix);
+ return SkISize::Make(SkScalarCeilToInt(size.fWidth - kRoundEpsilon),
+ SkScalarCeilToInt(size.fHeight - kRoundEpsilon));
}
template<>
@@ -388,18 +341,49 @@
///////////////////////////////////////////////////////////////////////////////////////////////////
// LayerSpace<T>
-LayerSpace<SkRect> LayerSpace<SkMatrix>::mapRect(const LayerSpace<SkRect>& r) const {
- return LayerSpace<SkRect>(map_rect(fData, SkRect(r)));
+// Match rounding tolerances of SkRects to SkIRects
+LayerSpace<SkISize> LayerSpace<SkSize>::round() const {
+ return LayerSpace<SkISize>(fData.toRound());
+}
+LayerSpace<SkISize> LayerSpace<SkSize>::ceil() const {
+ return LayerSpace<SkISize>({SkScalarCeilToInt(fData.fWidth - kRoundEpsilon),
+ SkScalarCeilToInt(fData.fHeight - kRoundEpsilon)});
+}
+LayerSpace<SkISize> LayerSpace<SkSize>::floor() const {
+ return LayerSpace<SkISize>({SkScalarFloorToInt(fData.fWidth + kRoundEpsilon),
+ SkScalarFloorToInt(fData.fHeight + kRoundEpsilon)});
}
+LayerSpace<SkRect> LayerSpace<SkMatrix>::mapRect(const LayerSpace<SkRect>& r) const {
+ return LayerSpace<SkRect>(Mapping::map(SkRect(r), fData));
+}
+
+// Effectively mapRect(SkRect).roundOut() but more accurate when the underlying matrix or
+// SkIRect has large floating point values.
LayerSpace<SkIRect> LayerSpace<SkMatrix>::mapRect(const LayerSpace<SkIRect>& r) const {
- return LayerSpace<SkIRect>(map_rect(fData, SkIRect(r)));
+ return LayerSpace<SkIRect>(Mapping::map(SkIRect(r), fData));
+}
+
+LayerSpace<SkPoint> LayerSpace<SkMatrix>::mapPoint(const LayerSpace<SkPoint>& p) const {
+ return LayerSpace<SkPoint>(Mapping::map(SkPoint(p), fData));
+}
+
+LayerSpace<Vector> LayerSpace<SkMatrix>::mapVector(const LayerSpace<Vector>& v) const {
+ return LayerSpace<Vector>(Mapping::map(Vector(v), fData));
+}
+
+LayerSpace<SkSize> LayerSpace<SkMatrix>::mapSize(const LayerSpace<SkSize>& s) const {
+ return LayerSpace<SkSize>(Mapping::map(SkSize(s), fData));
}
bool LayerSpace<SkMatrix>::inverseMapRect(const LayerSpace<SkRect>& r,
LayerSpace<SkRect>* out) const {
SkRect mapped;
- if (inverse_map_rect(fData, SkRect(r), &mapped)) {
+ if (r.isEmpty()) {
+ // An empty input always inverse maps to an empty rect "successfully"
+ *out = LayerSpace<SkRect>::Empty();
+ return true;
+ } else if (SkMatrixPriv::InverseMapRect(fData, &mapped, SkRect(r))) {
*out = LayerSpace<SkRect>(mapped);
return true;
} else {
@@ -407,15 +391,37 @@
}
}
-bool LayerSpace<SkMatrix>::inverseMapRect(const LayerSpace<SkIRect>& r,
+bool LayerSpace<SkMatrix>::inverseMapRect(const LayerSpace<SkIRect>& rect,
LayerSpace<SkIRect>* out) const {
- SkIRect mapped;
- if (inverse_map_rect(fData, SkIRect(r), &mapped)) {
+ if (rect.isEmpty()) {
+ // An empty input always inverse maps to an empty rect "successfully"
+ *out = LayerSpace<SkIRect>::Empty();
+ return true;
+ } else if (fData.isScaleTranslate()) { // Specialized inverse of 1px-preserving map<SkIRect>
+ // A scale-translate matrix with a 0 scale factor is not invertible.
+ if (fData.getScaleX() == 0.f || fData.getScaleY() == 0.f) {
+ return false;
+ }
+ double l = (rect.left() - (double)fData.getTranslateX()) / (double)fData.getScaleX();
+ double r = (rect.right() - (double)fData.getTranslateX()) / (double)fData.getScaleX();
+ double t = (rect.top() - (double)fData.getTranslateY()) / (double)fData.getScaleY();
+ double b = (rect.bottom() - (double)fData.getTranslateY()) / (double)fData.getScaleY();
+
+ SkIRect mapped{sk_double_saturate2int(sk_double_floor(std::min(l, r) + kRoundEpsilon)),
+ sk_double_saturate2int(sk_double_floor(std::min(t, b) + kRoundEpsilon)),
+ sk_double_saturate2int(sk_double_ceil(std::max(l, r) - kRoundEpsilon)),
+ sk_double_saturate2int(sk_double_ceil(std::max(t, b) - kRoundEpsilon))};
*out = LayerSpace<SkIRect>(mapped);
return true;
} else {
- return false;
+ SkRect mapped;
+ if (SkMatrixPriv::InverseMapRect(fData, &mapped, SkRect::Make(SkIRect(rect)))) {
+ *out = LayerSpace<SkRect>(mapped).roundOut();
+ return true;
+ }
}
+
+ return false;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/core/SkImageFilterTypes.h b/src/core/SkImageFilterTypes.h
index f0bd759..b343808 100644
--- a/src/core/SkImageFilterTypes.h
+++ b/src/core/SkImageFilterTypes.h
@@ -349,9 +349,9 @@
bool isEmpty() const { return fData.isEmpty(); }
bool isZero() const { return fData.isZero(); }
- LayerSpace<SkISize> round() const { return LayerSpace<SkISize>(fData.toRound()); }
- LayerSpace<SkISize> ceil() const { return LayerSpace<SkISize>(fData.toCeil()); }
- LayerSpace<SkISize> floor() const { return LayerSpace<SkISize>(fData.toFloor()); }
+ LayerSpace<SkISize> round() const;
+ LayerSpace<SkISize> ceil() const;
+ LayerSpace<SkISize> floor() const;
private:
SkSize fData;
@@ -470,13 +470,11 @@
// SkIRect has large floating point values.
LayerSpace<SkIRect> mapRect(const LayerSpace<SkIRect>& r) const;
- LayerSpace<SkPoint> mapPoint(const LayerSpace<SkPoint>& p) const {
- return LayerSpace<SkPoint>(fData.mapPoint(SkPoint(p)));
- }
+ LayerSpace<SkPoint> mapPoint(const LayerSpace<SkPoint>& p) const;
- LayerSpace<Vector> mapVector(const LayerSpace<Vector>& v) const {
- return LayerSpace<Vector>(Vector(fData.mapVector(v.x(), v.y())));
- }
+ LayerSpace<Vector> mapVector(const LayerSpace<Vector>& v) const;
+
+ LayerSpace<SkSize> mapSize(const LayerSpace<SkSize>& s) const;
LayerSpace<SkMatrix>& preConcat(const LayerSpace<SkMatrix>& m) {
fData = SkMatrix::Concat(fData, m.fData);
@@ -577,6 +575,8 @@
}
private:
+ friend class LayerSpace<SkMatrix>; // for map()
+
// The image filter process decomposes the total CTM into layerToDev * paramToLayer and uses the
// param-to-layer matrix to define the layer-space coordinate system. Depending on how it's
// decomposed, either the layer matrix or the device matrix could be the identity matrix (but