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()
/**
* Calculates the minimum scaling factor of the matrix. If the matrix has
* perspective -1 is returned.
* Calculates the minimum scaling factor of the matrix as computed from the SVD of the upper
* left 2x2. If the matrix has perspective -1 is returned.
*
* @return minumum scale factor
*/
SkScalar getMinScale() const;
/**
* Calculates the maximum scale factor of the matrix. If the matrix has
* perspective -1 is returned.
* Calculates the maximum scaling factor of the matrix as computed from the SVD of the upper
* left 2x2. If the matrix has perspective -1 is returned.
*
* @return maximum scale factor
*/
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
*/

View File

@ -1451,27 +1451,40 @@ bool SkMatrix::setPolyToPoly(const SkPoint src[], const SkPoint dst[],
///////////////////////////////////////////////////////////////////////////////
enum MinOrMax {
kMin_MinOrMax,
kMax_MinOrMax
enum MinMaxOrBoth {
kMin_MinMaxOrBoth,
kMax_MinMaxOrBoth,
kBoth_MinMaxOrBoth
};
template <MinOrMax MIN_OR_MAX> SkScalar get_scale_factor(SkMatrix::TypeMask typeMask,
const SkScalar m[9]) {
template <MinMaxOrBoth MIN_MAX_OR_BOTH> bool get_scale_factor(SkMatrix::TypeMask typeMask,
const SkScalar m[9],
SkScalar results[/*1 or 2*/]) {
if (typeMask & SkMatrix::kPerspective_Mask) {
return -1;
return false;
}
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 (kMin_MinOrMax == MIN_OR_MAX) {
return SkMinScalar(SkScalarAbs(m[SkMatrix::kMScaleX]),
SkScalarAbs(m[SkMatrix::kMScaleY]));
if (kMin_MinMaxOrBoth == MIN_MAX_OR_BOTH) {
results[0] = SkMinScalar(SkScalarAbs(m[SkMatrix::kMScaleX]),
SkScalarAbs(m[SkMatrix::kMScaleY]));
} else if (kMax_MinMaxOrBoth == MIN_MAX_OR_BOTH) {
results[0] = SkMaxScalar(SkScalarAbs(m[SkMatrix::kMScaleX]),
SkScalarAbs(m[SkMatrix::kMScaleY]));
} else {
return SkMaxScalar(SkScalarAbs(m[SkMatrix::kMScaleX]),
SkScalarAbs(m[SkMatrix::kMScaleY]));
results[0] = SkScalarAbs(m[SkMatrix::kMScaleX]);
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.
// 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)
// solve using quadratic equation (divisor is non-zero since l^2 has 1 coeff
// and roots are guaranteed to be pos and real).
SkScalar chosenRoot;
SkScalar bSqd = b * b;
// if upper left 2x2 is orthogonal save some math
if (bSqd <= SK_ScalarNearlyZero*SK_ScalarNearlyZero) {
if (kMin_MinOrMax == MIN_OR_MAX) {
chosenRoot = SkMinScalar(a, c);
if (kMin_MinMaxOrBoth == MIN_MAX_OR_BOTH) {
results[0] = SkMinScalar(a, c);
} else if (kMax_MinMaxOrBoth == MIN_MAX_OR_BOTH) {
results[0] = SkMaxScalar(a, c);
} else {
chosenRoot = SkMaxScalar(a, c);
results[0] = a;
results[1] = c;
if (results[0] > results[1]) {
SkTSwap(results[0], results[1]);
}
}
} else {
SkScalar aminusc = a - c;
SkScalar apluscdiv2 = SkScalarHalf(a + c);
SkScalar x = SkScalarHalf(SkScalarSqrt(aminusc * aminusc + 4 * bSqd));
if (kMin_MinOrMax == MIN_OR_MAX) {
chosenRoot = apluscdiv2 - x;
if (kMin_MinMaxOrBoth == MIN_MAX_OR_BOTH) {
results[0] = apluscdiv2 - x;
} else if (kMax_MinMaxOrBoth == MIN_MAX_OR_BOTH) {
results[0] = apluscdiv2 + x;
} else {
chosenRoot = apluscdiv2 + x;
results[0] = apluscdiv2 - x;
results[1] = apluscdiv2 + x;
}
}
SkASSERT(chosenRoot >= 0);
return SkScalarSqrt(chosenRoot);
SkASSERT(results[0] >= 0);
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 {
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 {
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) {

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) {
SkScalar scales[2];
bool success;
SkMatrix identity;
identity.reset();
REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMinScale());
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;
scale.setScale(SK_Scalar1 * 2, SK_Scalar1 * 4);
REPORTER_ASSERT(reporter, SK_Scalar1 * 2 == scale.getMinScale());
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;
rot90Scale.setRotate(90 * SK_Scalar1);
rot90Scale.postScale(SK_Scalar1 / 4, SK_Scalar1 / 2);
REPORTER_ASSERT(reporter, SK_Scalar1 / 4 == rot90Scale.getMinScale());
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;
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));
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;
translate.setTranslate(10 * SK_Scalar1, -5 * SK_Scalar1);
REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMinScale());
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;
perspX.reset();
perspX.setPerspX(SkScalarToPersp(SK_Scalar1 / 1000));
REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMinScale());
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;
perspY.reset();
perspY.setPerspY(SkScalarToPersp(-SK_Scalar1 / 500));
REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMinScale());
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,
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, (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()) {
m -= 1; // try another non-persp matrix
continue;