GrMatrix viewM = fTarget->getViewMatrix();
GrScalar tol = GR_Scalar1;
- tol = GrPathUtils::scaleToleranceToSrc(tol, viewM);
+ tol = GrPathUtils::scaleToleranceToSrc(tol, viewM, fPath->getBounds());
// FIXME: It's really dumb that we recreate the verts for a new vertex
// layout. We only do that because the GrDrawTarget API doesn't allow
#include "GrPoint.h"
GrScalar GrPathUtils::scaleToleranceToSrc(GrScalar devTol,
- const GrMatrix& viewM) {
+ const GrMatrix& viewM,
+ const GrRect& pathBounds) {
// In order to tesselate the path we get a bound on how much the matrix can
// stretch when mapping to screen coordinates.
GrScalar stretch = viewM.getMaxStretch();
GrScalar srcTol = devTol;
if (stretch < 0) {
- // TODO: deal with perspective in some better way.
- srcTol /= 5;
- stretch = -stretch;
+ // take worst case mapRadius amoung four corners.
+ // (less than perfect)
+ for (int i = 0; i < 4; ++i) {
+ GrMatrix mat;
+ mat.setTranslate((i % 2) ? pathBounds.fLeft : pathBounds.fRight,
+ (i < 2) ? pathBounds.fTop : pathBounds.fBottom);
+ mat.postConcat(viewM);
+ stretch = SkMaxScalar(stretch, mat.mapRadius(SK_Scalar1));
+ }
}
srcTol = GrScalarDiv(srcTol, stretch);
return srcTol;
*/
namespace GrPathUtils {
GrScalar scaleToleranceToSrc(GrScalar devTol,
- const GrMatrix& viewM);
+ const GrMatrix& viewM,
+ const GrRect& pathBounds);
/// Since we divide by tol if we're computing exact worst-case bounds,
/// very small tolerances will be increased to gMinCurveTol.
GrMatrix viewM = fTarget->getViewMatrix();
GrScalar tol = GR_Scalar1;
- tol = GrPathUtils::scaleToleranceToSrc(tol, viewM);
+ tol = GrPathUtils::scaleToleranceToSrc(tol, viewM, fPath->getBounds());
GrScalar tolSqd = GrMul(tol, tol);
int subpathCnt;
/**
* Calculates the maximum stretching factor of the matrix. If the matrix has
- * perspective the max stretch at the origin (in the pre-matrix space) is
- * computed and returned as a negative.
+ * perspective -1 is returned.
*
- * @return maximum strecthing factor or negative max stretching factor at
- * the origin if matrix has perspective.
+ * @return maximum strecthing factor
*/
SkScalar getMaxStretch() const;
SkScalar SkMatrix::getMaxStretch() const {
TypeMask mask = this->getType();
- SkScalar stretch;
-
+ if (this->hasPerspective()) {
+ return -SK_Scalar1;
+ }
if (this->isIdentity()) {
- stretch = SK_Scalar1;
- } else if (!(mask & kAffine_Mask)) {
- stretch = SkMaxScalar(SkScalarAbs(fMat[kMScaleX]), SkScalarAbs(fMat[kMScaleY]));
-#if 0 // don't have this bit
- } else if (mask & kZeroScale_TypeBit) {
- stretch = SkMaxScalar(SkScalarAbs(fM[kSkewX]), SkScalarAbs(fM[kSkewY]));
-#endif
+ return SK_Scalar1;
+ }
+ if (!(mask & kAffine_Mask)) {
+ return SkMaxScalar(SkScalarAbs(fMat[kMScaleX]),
+ SkScalarAbs(fMat[kMScaleY]));
+ }
+ // ignore the translation part of the matrix, just look at 2x2 portion.
+ // compute singular values, take largest abs value.
+ // [a b; b c] = A^T*A
+ SkScalar a = SkScalarMul(fMat[kMScaleX], fMat[kMScaleX]) +
+ SkScalarMul(fMat[kMSkewY], fMat[kMSkewY]);
+ SkScalar b = SkScalarMul(fMat[kMScaleX], fMat[kMSkewX]) +
+ SkScalarMul(fMat[kMScaleY], fMat[kMSkewY]);
+ SkScalar c = SkScalarMul(fMat[kMSkewX], fMat[kMSkewX]) +
+ SkScalarMul(fMat[kMScaleY], fMat[kMScaleY]);
+ // eigenvalues of A^T*A are the squared singular values of A.
+ // characteristic equation is det((A^T*A) - l*I) = 0
+ // l^2 - (a + c)l + (ac-b^2)
+ // solve using quadratic equation (divisor is non-zero since l^2 has 1 coeff
+ // and roots are guaraunteed to be pos and real).
+ SkScalar largerRoot;
+ SkScalar bSqd = SkScalarMul(b,b);
+ // if upper left 2x2 is orthogonal save some math
+ if (bSqd <= SK_ScalarNearlyZero) {
+ largerRoot = SkMaxScalar(a, c);
} else {
- // ignore the translation part of the matrix, just look at 2x2 portion.
- // compute singular values, take largest abs value.
- // [a b; b c] = A^T*A
- SkScalar a = SkScalarMul(fMat[kMScaleX], fMat[kMScaleX]) + SkScalarMul(fMat[kMSkewY], fMat[kMSkewY]);
- SkScalar b = SkScalarMul(fMat[kMScaleX], fMat[kMSkewX]) + SkScalarMul(fMat[kMScaleY], fMat[kMSkewY]);
- SkScalar c = SkScalarMul(fMat[kMSkewX], fMat[kMSkewX]) + SkScalarMul(fMat[kMScaleY], fMat[kMScaleY]);
- // eigenvalues of A^T*A are the squared singular values of A.
- // characteristic equation is det((A^T*A) - l*I) = 0
- // l^2 - (a + c)l + (ac-b^2)
- // solve using quadratic equation (divisor is non-zero since l^2 has 1 coeff
- // and roots are guaraunteed to be pos and real).
- SkScalar largerRoot;
- SkScalar bSqd = SkScalarMul(b,b);
- // if upper left 2x2 is orthogonal save some math
- if (bSqd <= SK_ScalarNearlyZero) {
- largerRoot = SkMaxScalar(a, c);
- } else {
- SkScalar aminusc = a - c;
- SkScalar apluscdiv2 = (a + c) / 2;
- SkScalar x = SkScalarSqrt(SkScalarMul(aminusc, aminusc) + 4 * bSqd) / 2;
- largerRoot = apluscdiv2 + x;
- }
- stretch = SkScalarSqrt(largerRoot);
- if (mask & kPerspective_Mask) {
- stretch = -stretch;
- if (fMat[kMPersp2] != kMatrix22Elem) {
-#if defined(SK_SCALAR_IS_FLOAT)
- stretch /= fMat[kMPersp2];
-#else
- stretch = SkFractDiv(stretch, fMat[kMPersp2]);
-#endif
- }
- }
+ SkScalar aminusc = a - c;
+ SkScalar apluscdiv2 = SkScalarHalf(a + c);
+ SkScalar x = SkScalarHalf(SkScalarSqrt(SkScalarMul(aminusc, aminusc) + 4 * bSqd));
+ largerRoot = apluscdiv2 + x;
}
-#if defined(SK_DEBUG) && 0
- // test a bunch of vectors. None should be scaled by more than stretch
- // (modulo some error) and we should find a vector that is scaled by almost
- // stretch.
- SkPoint pt;
- SkScalar max = 0;
- for (int i = 0; i < 1000; ++i) {
- SkScalar x = (float)rand() / RAND_MAX;
- SkScalar y = sqrtf(1 - (x*x));
- pt.fX = fMat[kMScaleX]*x + fMat[kMSkewX]*y;
- pt.fY = fMat[kMSkewY]*x + fMat[kMScaleY]*y;
- SkScalar d = pt.distanceToOrigin();
- SkASSERT(d <= (1.0001 * stretch));
- if (max < pt.distanceToOrigin()) {
- max = pt.distanceToOrigin();
- }
- }
- SkASSERT((stretch - max) < .05*stretch);
-#endif
- return stretch;
+ return SkScalarSqrt(largerRoot);
}
const SkMatrix& SkMatrix::I() {
*/
#include "Test.h"
#include "SkMatrix.h"
+#include "SkRandom.h"
static bool nearly_equal_scalar(SkScalar a, SkScalar b) {
// Note that we get more compounded error for multiple operations when
REPORTER_ASSERT(reporter, memcmp(buffer, buffer2, size1) == 0);
}
+void test_matrix_max_stretch(skiatest::Reporter* reporter) {
+ SkMatrix identity;
+ identity.reset();
+ REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMaxStretch());
+
+ SkMatrix scale;
+ scale.setScale(SK_Scalar1 * 2, SK_Scalar1 * 4);
+ REPORTER_ASSERT(reporter, SK_Scalar1 * 4 == scale.getMaxStretch());
+
+ SkMatrix rot90Scale;
+ rot90Scale.setRotate(90 * SK_Scalar1);
+ rot90Scale.postScale(SK_Scalar1 / 4, SK_Scalar1 / 2);
+ REPORTER_ASSERT(reporter, SK_Scalar1 / 2 == rot90Scale.getMaxStretch());
+
+ SkMatrix rotate;
+ rotate.setRotate(128 * SK_Scalar1);
+ REPORTER_ASSERT(reporter, SkScalarAbs(SK_Scalar1 - rotate.getMaxStretch()) <= SK_ScalarNearlyZero);
+
+ SkMatrix translate;
+ translate.setTranslate(10 * SK_Scalar1, -5 * SK_Scalar1);
+ REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMaxStretch());
+
+ SkMatrix perspX;
+ perspX.reset();
+ perspX.setPerspX(SK_Scalar1 / 1000);
+ REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMaxStretch());
+
+ SkMatrix perspY;
+ perspY.reset();
+ perspY.setPerspX(-SK_Scalar1 / 500);
+ REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMaxStretch());
+
+ SkMatrix baseMats[] = {scale, rot90Scale, rotate,
+ translate, perspX, perspY};
+ SkMatrix mats[2*SK_ARRAY_COUNT(baseMats)];
+ for (int i = 0; i < SK_ARRAY_COUNT(baseMats); ++i) {
+ mats[i] = baseMats[i];
+ bool invertable = mats[i].invert(&mats[i + SK_ARRAY_COUNT(baseMats)]);
+ REPORTER_ASSERT(reporter, invertable);
+ }
+ SkRandom rand;
+ for (int m = 0; m < 1000; ++m) {
+ SkMatrix mat;
+ mat.reset();
+ for (int i = 0; i < 4; ++i) {
+ int x = rand.nextU() % SK_ARRAY_COUNT(mats);
+ mat.postConcat(mats[x]);
+ }
+ SkScalar stretch = mat.getMaxStretch();
+
+ if ((stretch < 0) != mat.hasPerspective()) {
+ stretch = mat.getMaxStretch();
+ }
+
+ REPORTER_ASSERT(reporter, (stretch < 0) == mat.hasPerspective());
+
+ if (mat.hasPerspective()) {
+ m -= 1; // try another non-persp matrix
+ continue;
+ }
+
+ // test a bunch of vectors. None should be scaled by more than stretch
+ // (modulo some error) and we should find a vector that is scaled by
+ // almost stretch.
+ static const SkScalar gStretchTol = (105 * SK_Scalar1) / 100;
+ static const SkScalar gMaxStretchTol = (97 * SK_Scalar1) / 100;
+ SkScalar max = 0;
+ SkVector vectors[1000];
+ for (int i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
+ vectors[i].fX = rand.nextSScalar1();
+ vectors[i].fY = rand.nextSScalar1();
+ if (!vectors[i].normalize()) {
+ i -= 1;
+ continue;
+ }
+ }
+ mat.mapVectors(vectors, SK_ARRAY_COUNT(vectors));
+ for (int i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
+ SkScalar d = vectors[i].length();
+ REPORTER_ASSERT(reporter, SkScalarDiv(d, stretch) < gStretchTol);
+ if (max < d) {
+ max = d;
+ }
+ }
+ REPORTER_ASSERT(reporter, SkScalarDiv(max, stretch) >= gMaxStretchTol);
+ }
+}
+
void TestMatrix(skiatest::Reporter* reporter) {
SkMatrix mat, inverse, iden1, iden2;
mat.set(SkMatrix::kMPersp1, SkIntToScalar(1));
REPORTER_ASSERT(reporter, !mat.asAffine(affine));
+
+ test_matrix_max_stretch(reporter);
}
#include "TestClassDef.h"