Add function to get both min and max scale factors from matrix

R=reed@google.com, jvanverth@google.com

Author: bsalomon@google.com

Review URL: https://codereview.chromium.org/298473002

git-svn-id: http://skia.googlecode.com/svn/trunk@14804 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
commit-bot@chromium.org 2014-05-20 17:02:03 +00:00
parent 6379129229
commit 311a3cda94
3 changed files with 104 additions and 28 deletions

View File

@ -563,21 +563,28 @@ public:
SK_TO_STRING_NONVIRT() SK_TO_STRING_NONVIRT()
/** /**
* Calculates the minimum scaling factor of the matrix. If the matrix has * Calculates the minimum scaling factor of the matrix as computed from the SVD of the upper
* perspective -1 is returned. * left 2x2. If the matrix has perspective -1 is returned.
* *
* @return minumum scale factor * @return minumum scale factor
*/ */
SkScalar getMinScale() const; SkScalar getMinScale() const;
/** /**
* Calculates the maximum scale factor of the matrix. If the matrix has * Calculates the maximum scaling factor of the matrix as computed from the SVD of the upper
* perspective -1 is returned. * left 2x2. If the matrix has perspective -1 is returned.
* *
* @return maximum scale factor * @return maximum scale factor
*/ */
SkScalar getMaxScale() const; SkScalar getMaxScale() const;
/**
* Gets both the min and max scale factors. The min scale factor is scaleFactors[0] and the max
* is scaleFactors[1]. If the matrix has perspective false will be returned and scaleFactors
* will be unchanged.
*/
bool getMinMaxScales(SkScalar scaleFactors[2]) const;
/** /**
* Return a reference to a const identity matrix * Return a reference to a const identity matrix
*/ */

View File

@ -1451,27 +1451,40 @@ bool SkMatrix::setPolyToPoly(const SkPoint src[], const SkPoint dst[],
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
enum MinOrMax { enum MinMaxOrBoth {
kMin_MinOrMax, kMin_MinMaxOrBoth,
kMax_MinOrMax kMax_MinMaxOrBoth,
kBoth_MinMaxOrBoth
}; };
template <MinOrMax MIN_OR_MAX> SkScalar get_scale_factor(SkMatrix::TypeMask typeMask, template <MinMaxOrBoth MIN_MAX_OR_BOTH> bool get_scale_factor(SkMatrix::TypeMask typeMask,
const SkScalar m[9]) { const SkScalar m[9],
SkScalar results[/*1 or 2*/]) {
if (typeMask & SkMatrix::kPerspective_Mask) { if (typeMask & SkMatrix::kPerspective_Mask) {
return -1; return false;
} }
if (SkMatrix::kIdentity_Mask == typeMask) { if (SkMatrix::kIdentity_Mask == typeMask) {
return 1; results[0] = SK_Scalar1;
if (kBoth_MinMaxOrBoth == MIN_MAX_OR_BOTH) {
results[1] = SK_Scalar1;
}
return true;
} }
if (!(typeMask & SkMatrix::kAffine_Mask)) { if (!(typeMask & SkMatrix::kAffine_Mask)) {
if (kMin_MinOrMax == MIN_OR_MAX) { if (kMin_MinMaxOrBoth == MIN_MAX_OR_BOTH) {
return SkMinScalar(SkScalarAbs(m[SkMatrix::kMScaleX]), results[0] = SkMinScalar(SkScalarAbs(m[SkMatrix::kMScaleX]),
SkScalarAbs(m[SkMatrix::kMScaleY])); SkScalarAbs(m[SkMatrix::kMScaleY]));
} else if (kMax_MinMaxOrBoth == MIN_MAX_OR_BOTH) {
results[0] = SkMaxScalar(SkScalarAbs(m[SkMatrix::kMScaleX]),
SkScalarAbs(m[SkMatrix::kMScaleY]));
} else { } else {
return SkMaxScalar(SkScalarAbs(m[SkMatrix::kMScaleX]), results[0] = SkScalarAbs(m[SkMatrix::kMScaleX]);
SkScalarAbs(m[SkMatrix::kMScaleY])); results[1] = SkScalarAbs(m[SkMatrix::kMScaleY]);
if (results[0] > results[1]) {
SkTSwap(results[0], results[1]);
}
} }
return true;
} }
// ignore the translation part of the matrix, just look at 2x2 portion. // ignore the translation part of the matrix, just look at 2x2 portion.
// compute singular values, take largest or smallest abs value. // compute singular values, take largest or smallest abs value.
@ -1487,35 +1500,62 @@ template <MinOrMax MIN_OR_MAX> SkScalar get_scale_factor(SkMatrix::TypeMask type
// l^2 - (a + c)l + (ac-b^2) // l^2 - (a + c)l + (ac-b^2)
// solve using quadratic equation (divisor is non-zero since l^2 has 1 coeff // solve using quadratic equation (divisor is non-zero since l^2 has 1 coeff
// and roots are guaranteed to be pos and real). // and roots are guaranteed to be pos and real).
SkScalar chosenRoot;
SkScalar bSqd = b * b; SkScalar bSqd = b * b;
// if upper left 2x2 is orthogonal save some math // if upper left 2x2 is orthogonal save some math
if (bSqd <= SK_ScalarNearlyZero*SK_ScalarNearlyZero) { if (bSqd <= SK_ScalarNearlyZero*SK_ScalarNearlyZero) {
if (kMin_MinOrMax == MIN_OR_MAX) { if (kMin_MinMaxOrBoth == MIN_MAX_OR_BOTH) {
chosenRoot = SkMinScalar(a, c); results[0] = SkMinScalar(a, c);
} else if (kMax_MinMaxOrBoth == MIN_MAX_OR_BOTH) {
results[0] = SkMaxScalar(a, c);
} else { } else {
chosenRoot = SkMaxScalar(a, c); results[0] = a;
results[1] = c;
if (results[0] > results[1]) {
SkTSwap(results[0], results[1]);
}
} }
} else { } else {
SkScalar aminusc = a - c; SkScalar aminusc = a - c;
SkScalar apluscdiv2 = SkScalarHalf(a + c); SkScalar apluscdiv2 = SkScalarHalf(a + c);
SkScalar x = SkScalarHalf(SkScalarSqrt(aminusc * aminusc + 4 * bSqd)); SkScalar x = SkScalarHalf(SkScalarSqrt(aminusc * aminusc + 4 * bSqd));
if (kMin_MinOrMax == MIN_OR_MAX) { if (kMin_MinMaxOrBoth == MIN_MAX_OR_BOTH) {
chosenRoot = apluscdiv2 - x; results[0] = apluscdiv2 - x;
} else if (kMax_MinMaxOrBoth == MIN_MAX_OR_BOTH) {
results[0] = apluscdiv2 + x;
} else { } else {
chosenRoot = apluscdiv2 + x; results[0] = apluscdiv2 - x;
results[1] = apluscdiv2 + x;
} }
} }
SkASSERT(chosenRoot >= 0); SkASSERT(results[0] >= 0);
return SkScalarSqrt(chosenRoot); results[0] = SkScalarSqrt(results[0]);
if (kBoth_MinMaxOrBoth == MIN_MAX_OR_BOTH) {
SkASSERT(results[1] >= 0);
results[1] = SkScalarSqrt(results[1]);
}
return true;
} }
SkScalar SkMatrix::getMinScale() const { SkScalar SkMatrix::getMinScale() const {
return get_scale_factor<kMin_MinOrMax>(this->getType(), fMat); SkScalar factor;
if (get_scale_factor<kMin_MinMaxOrBoth>(this->getType(), fMat, &factor)) {
return factor;
} else {
return -1;
}
} }
SkScalar SkMatrix::getMaxScale() const { SkScalar SkMatrix::getMaxScale() const {
return get_scale_factor<kMax_MinOrMax>(this->getType(), fMat); SkScalar factor;
if (get_scale_factor<kMax_MinMaxOrBoth>(this->getType(), fMat, &factor)) {
return factor;
} else {
return -1;
}
}
bool SkMatrix::getMinMaxScales(SkScalar scaleFactors[2]) const {
return get_scale_factor<kBoth_MinMaxOrBoth>(this->getType(), fMat, scaleFactors);
} }
static void reset_identity_matrix(SkMatrix* identity) { static void reset_identity_matrix(SkMatrix* identity) {

View File

@ -120,43 +120,67 @@ static void test_flatten(skiatest::Reporter* reporter, const SkMatrix& m) {
} }
static void test_matrix_min_max_scale(skiatest::Reporter* reporter) { static void test_matrix_min_max_scale(skiatest::Reporter* reporter) {
SkScalar scales[2];
bool success;
SkMatrix identity; SkMatrix identity;
identity.reset(); identity.reset();
REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMinScale()); REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMinScale());
REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMaxScale()); REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMaxScale());
success = identity.getMinMaxScales(scales);
REPORTER_ASSERT(reporter, success && SK_Scalar1 == scales[0] && SK_Scalar1 == scales[1]);
SkMatrix scale; SkMatrix scale;
scale.setScale(SK_Scalar1 * 2, SK_Scalar1 * 4); scale.setScale(SK_Scalar1 * 2, SK_Scalar1 * 4);
REPORTER_ASSERT(reporter, SK_Scalar1 * 2 == scale.getMinScale()); REPORTER_ASSERT(reporter, SK_Scalar1 * 2 == scale.getMinScale());
REPORTER_ASSERT(reporter, SK_Scalar1 * 4 == scale.getMaxScale()); REPORTER_ASSERT(reporter, SK_Scalar1 * 4 == scale.getMaxScale());
success = scale.getMinMaxScales(scales);
REPORTER_ASSERT(reporter, success && SK_Scalar1 * 2 == scales[0] && SK_Scalar1 * 4 == scales[1]);
SkMatrix rot90Scale; SkMatrix rot90Scale;
rot90Scale.setRotate(90 * SK_Scalar1); rot90Scale.setRotate(90 * SK_Scalar1);
rot90Scale.postScale(SK_Scalar1 / 4, SK_Scalar1 / 2); rot90Scale.postScale(SK_Scalar1 / 4, SK_Scalar1 / 2);
REPORTER_ASSERT(reporter, SK_Scalar1 / 4 == rot90Scale.getMinScale()); REPORTER_ASSERT(reporter, SK_Scalar1 / 4 == rot90Scale.getMinScale());
REPORTER_ASSERT(reporter, SK_Scalar1 / 2 == rot90Scale.getMaxScale()); REPORTER_ASSERT(reporter, SK_Scalar1 / 2 == rot90Scale.getMaxScale());
success = rot90Scale.getMinMaxScales(scales);
REPORTER_ASSERT(reporter, success && SK_Scalar1 / 4 == scales[0] && SK_Scalar1 / 2 == scales[1]);
SkMatrix rotate; SkMatrix rotate;
rotate.setRotate(128 * SK_Scalar1); rotate.setRotate(128 * SK_Scalar1);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SK_Scalar1, rotate.getMinScale() ,SK_ScalarNearlyZero)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SK_Scalar1, rotate.getMinScale(), SK_ScalarNearlyZero));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SK_Scalar1, rotate.getMaxScale(), SK_ScalarNearlyZero)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SK_Scalar1, rotate.getMaxScale(), SK_ScalarNearlyZero));
success = rotate.getMinMaxScales(scales);
REPORTER_ASSERT(reporter, success);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SK_Scalar1, scales[0], SK_ScalarNearlyZero));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SK_Scalar1, scales[1], SK_ScalarNearlyZero));
SkMatrix translate; SkMatrix translate;
translate.setTranslate(10 * SK_Scalar1, -5 * SK_Scalar1); translate.setTranslate(10 * SK_Scalar1, -5 * SK_Scalar1);
REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMinScale()); REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMinScale());
REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMaxScale()); REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMaxScale());
success = translate.getMinMaxScales(scales);
REPORTER_ASSERT(reporter, success && SK_Scalar1 == scales[0] && SK_Scalar1 == scales[1]);
SkMatrix perspX; SkMatrix perspX;
perspX.reset(); perspX.reset();
perspX.setPerspX(SkScalarToPersp(SK_Scalar1 / 1000)); perspX.setPerspX(SkScalarToPersp(SK_Scalar1 / 1000));
REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMinScale()); REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMinScale());
REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMaxScale()); REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMaxScale());
// Verify that getMinMaxScales() doesn't update the scales array on failure.
scales[0] = -5;
scales[1] = -5;
success = perspX.getMinMaxScales(scales);
REPORTER_ASSERT(reporter, !success && -5 * SK_Scalar1 == scales[0] && -5 * SK_Scalar1 == scales[1]);
SkMatrix perspY; SkMatrix perspY;
perspY.reset(); perspY.reset();
perspY.setPerspY(SkScalarToPersp(-SK_Scalar1 / 500)); perspY.setPerspY(SkScalarToPersp(-SK_Scalar1 / 500));
REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMinScale()); REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMinScale());
REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMaxScale()); REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMaxScale());
scales[0] = -5;
scales[1] = -5;
success = perspY.getMinMaxScales(scales);
REPORTER_ASSERT(reporter, !success && -5 * SK_Scalar1 == scales[0] && -5 * SK_Scalar1 == scales[1]);
SkMatrix baseMats[] = {scale, rot90Scale, rotate, SkMatrix baseMats[] = {scale, rot90Scale, rotate,
translate, perspX, perspY}; translate, perspX, perspY};
@ -180,6 +204,11 @@ static void test_matrix_min_max_scale(skiatest::Reporter* reporter) {
REPORTER_ASSERT(reporter, (minScale < 0) == (maxScale < 0)); REPORTER_ASSERT(reporter, (minScale < 0) == (maxScale < 0));
REPORTER_ASSERT(reporter, (maxScale < 0) == mat.hasPerspective()); REPORTER_ASSERT(reporter, (maxScale < 0) == mat.hasPerspective());
SkScalar scales[2];
bool success = mat.getMinMaxScales(scales);
REPORTER_ASSERT(reporter, success == !mat.hasPerspective());
REPORTER_ASSERT(reporter, !success || (scales[0] == minScale && scales[1] == maxScale));
if (mat.hasPerspective()) { if (mat.hasPerspective()) {
m -= 1; // try another non-persp matrix m -= 1; // try another non-persp matrix
continue; continue;