Home | History | Annotate | Download | only in gfx
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "ui/gfx/transform_util.h"
      6 
      7 #include <algorithm>
      8 #include <cmath>
      9 
     10 #include "base/logging.h"
     11 #include "base/strings/stringprintf.h"
     12 #include "ui/gfx/point.h"
     13 #include "ui/gfx/point3_f.h"
     14 #include "ui/gfx/rect.h"
     15 
     16 namespace gfx {
     17 
     18 namespace {
     19 
     20 SkMScalar Length3(SkMScalar v[3]) {
     21   double vd[3] = {SkMScalarToDouble(v[0]), SkMScalarToDouble(v[1]),
     22                   SkMScalarToDouble(v[2])};
     23   return SkDoubleToMScalar(
     24       std::sqrt(vd[0] * vd[0] + vd[1] * vd[1] + vd[2] * vd[2]));
     25 }
     26 
     27 void Scale3(SkMScalar v[3], SkMScalar scale) {
     28   for (int i = 0; i < 3; ++i)
     29     v[i] *= scale;
     30 }
     31 
     32 template <int n>
     33 SkMScalar Dot(const SkMScalar* a, const SkMScalar* b) {
     34   double total = 0.0;
     35   for (int i = 0; i < n; ++i)
     36     total += a[i] * b[i];
     37   return SkDoubleToMScalar(total);
     38 }
     39 
     40 template <int n>
     41 void Combine(SkMScalar* out,
     42              const SkMScalar* a,
     43              const SkMScalar* b,
     44              double scale_a,
     45              double scale_b) {
     46   for (int i = 0; i < n; ++i)
     47     out[i] = SkDoubleToMScalar(a[i] * scale_a + b[i] * scale_b);
     48 }
     49 
     50 void Cross3(SkMScalar out[3], SkMScalar a[3], SkMScalar b[3]) {
     51   SkMScalar x = a[1] * b[2] - a[2] * b[1];
     52   SkMScalar y = a[2] * b[0] - a[0] * b[2];
     53   SkMScalar z = a[0] * b[1] - a[1] * b[0];
     54   out[0] = x;
     55   out[1] = y;
     56   out[2] = z;
     57 }
     58 
     59 SkMScalar Round(SkMScalar n) {
     60   return SkDoubleToMScalar(std::floor(SkMScalarToDouble(n) + 0.5));
     61 }
     62 
     63 // Taken from http://www.w3.org/TR/css3-transforms/.
     64 bool Slerp(SkMScalar out[4],
     65            const SkMScalar q1[4],
     66            const SkMScalar q2[4],
     67            double progress) {
     68   double product = Dot<4>(q1, q2);
     69 
     70   // Clamp product to -1.0 <= product <= 1.0.
     71   product = std::min(std::max(product, -1.0), 1.0);
     72 
     73   // Interpolate angles along the shortest path. For example, to interpolate
     74   // between a 175 degree angle and a 185 degree angle, interpolate along the
     75   // 10 degree path from 175 to 185, rather than along the 350 degree path in
     76   // the opposite direction. This matches WebKit's implementation but not
     77   // the current W3C spec. Fixing the spec to match this approach is discussed
     78   // at:
     79   // http://lists.w3.org/Archives/Public/www-style/2013May/0131.html
     80   double scale1 = 1.0;
     81   if (product < 0) {
     82     product = -product;
     83     scale1 = -1.0;
     84   }
     85 
     86   const double epsilon = 1e-5;
     87   if (std::abs(product - 1.0) < epsilon) {
     88     for (int i = 0; i < 4; ++i)
     89       out[i] = q1[i];
     90     return true;
     91   }
     92 
     93   double denom = std::sqrt(1.0 - product * product);
     94   double theta = std::acos(product);
     95   double w = std::sin(progress * theta) * (1.0 / denom);
     96 
     97   scale1 *= std::cos(progress * theta) - product * w;
     98   double scale2 = w;
     99   Combine<4>(out, q1, q2, scale1, scale2);
    100 
    101   return true;
    102 }
    103 
    104 // Returns false if the matrix cannot be normalized.
    105 bool Normalize(SkMatrix44& m) {
    106   if (m.get(3, 3) == 0.0)
    107     // Cannot normalize.
    108     return false;
    109 
    110   SkMScalar scale = 1.0 / m.get(3, 3);
    111   for (int i = 0; i < 4; i++)
    112     for (int j = 0; j < 4; j++)
    113       m.set(i, j, m.get(i, j) * scale);
    114 
    115   return true;
    116 }
    117 
    118 SkMatrix44 BuildPerspectiveMatrix(const DecomposedTransform& decomp) {
    119   SkMatrix44 matrix(SkMatrix44::kIdentity_Constructor);
    120 
    121   for (int i = 0; i < 4; i++)
    122     matrix.setDouble(3, i, decomp.perspective[i]);
    123   return matrix;
    124 }
    125 
    126 SkMatrix44 BuildTranslationMatrix(const DecomposedTransform& decomp) {
    127   SkMatrix44 matrix(SkMatrix44::kUninitialized_Constructor);
    128   // Implicitly calls matrix.setIdentity()
    129   matrix.setTranslate(SkDoubleToMScalar(decomp.translate[0]),
    130                       SkDoubleToMScalar(decomp.translate[1]),
    131                       SkDoubleToMScalar(decomp.translate[2]));
    132   return matrix;
    133 }
    134 
    135 SkMatrix44 BuildSnappedTranslationMatrix(DecomposedTransform decomp) {
    136   decomp.translate[0] = Round(decomp.translate[0]);
    137   decomp.translate[1] = Round(decomp.translate[1]);
    138   decomp.translate[2] = Round(decomp.translate[2]);
    139   return BuildTranslationMatrix(decomp);
    140 }
    141 
    142 SkMatrix44 BuildRotationMatrix(const DecomposedTransform& decomp) {
    143   double x = decomp.quaternion[0];
    144   double y = decomp.quaternion[1];
    145   double z = decomp.quaternion[2];
    146   double w = decomp.quaternion[3];
    147 
    148   SkMatrix44 matrix(SkMatrix44::kUninitialized_Constructor);
    149 
    150   // Implicitly calls matrix.setIdentity()
    151   matrix.set3x3(1.0 - 2.0 * (y * y + z * z),
    152                 2.0 * (x * y + z * w),
    153                 2.0 * (x * z - y * w),
    154                 2.0 * (x * y - z * w),
    155                 1.0 - 2.0 * (x * x + z * z),
    156                 2.0 * (y * z + x * w),
    157                 2.0 * (x * z + y * w),
    158                 2.0 * (y * z - x * w),
    159                 1.0 - 2.0 * (x * x + y * y));
    160   return matrix;
    161 }
    162 
    163 SkMatrix44 BuildSnappedRotationMatrix(const DecomposedTransform& decomp) {
    164   // Create snapped rotation.
    165   SkMatrix44 rotation_matrix = BuildRotationMatrix(decomp);
    166   for (int i = 0; i < 3; ++i) {
    167     for (int j = 0; j < 3; ++j) {
    168       SkMScalar value = rotation_matrix.get(i, j);
    169       // Snap values to -1, 0 or 1.
    170       if (value < -0.5f) {
    171         value = -1.0f;
    172       } else if (value > 0.5f) {
    173         value = 1.0f;
    174       } else {
    175         value = 0.0f;
    176       }
    177       rotation_matrix.set(i, j, value);
    178     }
    179   }
    180   return rotation_matrix;
    181 }
    182 
    183 SkMatrix44 BuildSkewMatrix(const DecomposedTransform& decomp) {
    184   SkMatrix44 matrix(SkMatrix44::kIdentity_Constructor);
    185 
    186   SkMatrix44 temp(SkMatrix44::kIdentity_Constructor);
    187   if (decomp.skew[2]) {
    188     temp.setDouble(1, 2, decomp.skew[2]);
    189     matrix.preConcat(temp);
    190   }
    191 
    192   if (decomp.skew[1]) {
    193     temp.setDouble(1, 2, 0);
    194     temp.setDouble(0, 2, decomp.skew[1]);
    195     matrix.preConcat(temp);
    196   }
    197 
    198   if (decomp.skew[0]) {
    199     temp.setDouble(0, 2, 0);
    200     temp.setDouble(0, 1, decomp.skew[0]);
    201     matrix.preConcat(temp);
    202   }
    203   return matrix;
    204 }
    205 
    206 SkMatrix44 BuildScaleMatrix(const DecomposedTransform& decomp) {
    207   SkMatrix44 matrix(SkMatrix44::kUninitialized_Constructor);
    208   matrix.setScale(SkDoubleToMScalar(decomp.scale[0]),
    209                   SkDoubleToMScalar(decomp.scale[1]),
    210                   SkDoubleToMScalar(decomp.scale[2]));
    211   return matrix;
    212 }
    213 
    214 SkMatrix44 BuildSnappedScaleMatrix(DecomposedTransform decomp) {
    215   decomp.scale[0] = Round(decomp.scale[0]);
    216   decomp.scale[1] = Round(decomp.scale[1]);
    217   decomp.scale[2] = Round(decomp.scale[2]);
    218   return BuildScaleMatrix(decomp);
    219 }
    220 
    221 Transform ComposeTransform(const SkMatrix44& perspective,
    222                            const SkMatrix44& translation,
    223                            const SkMatrix44& rotation,
    224                            const SkMatrix44& skew,
    225                            const SkMatrix44& scale) {
    226   SkMatrix44 matrix(SkMatrix44::kIdentity_Constructor);
    227 
    228   matrix.preConcat(perspective);
    229   matrix.preConcat(translation);
    230   matrix.preConcat(rotation);
    231   matrix.preConcat(skew);
    232   matrix.preConcat(scale);
    233 
    234   Transform to_return;
    235   to_return.matrix() = matrix;
    236   return to_return;
    237 }
    238 
    239 bool CheckViewportPointMapsWithinOnePixel(const Point& point,
    240                                           const Transform& transform) {
    241   Point3F point_original(point);
    242   Point3F point_transformed(point);
    243 
    244   // Can't use TransformRect here since it would give us the axis-aligned
    245   // bounding rect of the 4 points in the initial rectable which is not what we
    246   // want.
    247   transform.TransformPoint(&point_transformed);
    248 
    249   if ((point_transformed - point_original).Length() > 1.f) {
    250     // The changed distance should not be more than 1 pixel.
    251     return false;
    252   }
    253   return true;
    254 }
    255 
    256 bool CheckTransformsMapsIntViewportWithinOnePixel(const Rect& viewport,
    257                                                   const Transform& original,
    258                                                   const Transform& snapped) {
    259 
    260   Transform original_inv(Transform::kSkipInitialization);
    261   bool invertible = true;
    262   invertible &= original.GetInverse(&original_inv);
    263   DCHECK(invertible) << "Non-invertible transform, cannot snap.";
    264 
    265   Transform combined = snapped * original_inv;
    266 
    267   return CheckViewportPointMapsWithinOnePixel(viewport.origin(), combined) &&
    268          CheckViewportPointMapsWithinOnePixel(viewport.top_right(), combined) &&
    269          CheckViewportPointMapsWithinOnePixel(viewport.bottom_left(),
    270                                               combined) &&
    271          CheckViewportPointMapsWithinOnePixel(viewport.bottom_right(),
    272                                               combined);
    273 }
    274 
    275 }  // namespace
    276 
    277 Transform GetScaleTransform(const Point& anchor, float scale) {
    278   Transform transform;
    279   transform.Translate(anchor.x() * (1 - scale),
    280                       anchor.y() * (1 - scale));
    281   transform.Scale(scale, scale);
    282   return transform;
    283 }
    284 
    285 DecomposedTransform::DecomposedTransform() {
    286   translate[0] = translate[1] = translate[2] = 0.0;
    287   scale[0] = scale[1] = scale[2] = 1.0;
    288   skew[0] = skew[1] = skew[2] = 0.0;
    289   perspective[0] = perspective[1] = perspective[2] = 0.0;
    290   quaternion[0] = quaternion[1] = quaternion[2] = 0.0;
    291   perspective[3] = quaternion[3] = 1.0;
    292 }
    293 
    294 bool BlendDecomposedTransforms(DecomposedTransform* out,
    295                                const DecomposedTransform& to,
    296                                const DecomposedTransform& from,
    297                                double progress) {
    298   double scalea = progress;
    299   double scaleb = 1.0 - progress;
    300   Combine<3>(out->translate, to.translate, from.translate, scalea, scaleb);
    301   Combine<3>(out->scale, to.scale, from.scale, scalea, scaleb);
    302   Combine<3>(out->skew, to.skew, from.skew, scalea, scaleb);
    303   Combine<4>(
    304       out->perspective, to.perspective, from.perspective, scalea, scaleb);
    305   return Slerp(out->quaternion, from.quaternion, to.quaternion, progress);
    306 }
    307 
    308 // Taken from http://www.w3.org/TR/css3-transforms/.
    309 bool DecomposeTransform(DecomposedTransform* decomp,
    310                         const Transform& transform) {
    311   if (!decomp)
    312     return false;
    313 
    314   // We'll operate on a copy of the matrix.
    315   SkMatrix44 matrix = transform.matrix();
    316 
    317   // If we cannot normalize the matrix, then bail early as we cannot decompose.
    318   if (!Normalize(matrix))
    319     return false;
    320 
    321   SkMatrix44 perspectiveMatrix = matrix;
    322 
    323   for (int i = 0; i < 3; ++i)
    324     perspectiveMatrix.set(3, i, 0.0);
    325 
    326   perspectiveMatrix.set(3, 3, 1.0);
    327 
    328   // If the perspective matrix is not invertible, we are also unable to
    329   // decompose, so we'll bail early. Constant taken from SkMatrix44::invert.
    330   if (std::abs(perspectiveMatrix.determinant()) < 1e-8)
    331     return false;
    332 
    333   if (matrix.get(3, 0) != 0.0 || matrix.get(3, 1) != 0.0 ||
    334       matrix.get(3, 2) != 0.0) {
    335     // rhs is the right hand side of the equation.
    336     SkMScalar rhs[4] = {
    337       matrix.get(3, 0),
    338       matrix.get(3, 1),
    339       matrix.get(3, 2),
    340       matrix.get(3, 3)
    341     };
    342 
    343     // Solve the equation by inverting perspectiveMatrix and multiplying
    344     // rhs by the inverse.
    345     SkMatrix44 inversePerspectiveMatrix(SkMatrix44::kUninitialized_Constructor);
    346     if (!perspectiveMatrix.invert(&inversePerspectiveMatrix))
    347       return false;
    348 
    349     SkMatrix44 transposedInversePerspectiveMatrix =
    350         inversePerspectiveMatrix;
    351 
    352     transposedInversePerspectiveMatrix.transpose();
    353     transposedInversePerspectiveMatrix.mapMScalars(rhs);
    354 
    355     for (int i = 0; i < 4; ++i)
    356       decomp->perspective[i] = rhs[i];
    357 
    358   } else {
    359     // No perspective.
    360     for (int i = 0; i < 3; ++i)
    361       decomp->perspective[i] = 0.0;
    362     decomp->perspective[3] = 1.0;
    363   }
    364 
    365   for (int i = 0; i < 3; i++)
    366     decomp->translate[i] = matrix.get(i, 3);
    367 
    368   SkMScalar row[3][3];
    369   for (int i = 0; i < 3; i++)
    370     for (int j = 0; j < 3; ++j)
    371       row[i][j] = matrix.get(j, i);
    372 
    373   // Compute X scale factor and normalize first row.
    374   decomp->scale[0] = Length3(row[0]);
    375   if (decomp->scale[0] != 0.0)
    376     Scale3(row[0], 1.0 / decomp->scale[0]);
    377 
    378   // Compute XY shear factor and make 2nd row orthogonal to 1st.
    379   decomp->skew[0] = Dot<3>(row[0], row[1]);
    380   Combine<3>(row[1], row[1], row[0], 1.0, -decomp->skew[0]);
    381 
    382   // Now, compute Y scale and normalize 2nd row.
    383   decomp->scale[1] = Length3(row[1]);
    384   if (decomp->scale[1] != 0.0)
    385     Scale3(row[1], 1.0 / decomp->scale[1]);
    386 
    387   decomp->skew[0] /= decomp->scale[1];
    388 
    389   // Compute XZ and YZ shears, orthogonalize 3rd row
    390   decomp->skew[1] = Dot<3>(row[0], row[2]);
    391   Combine<3>(row[2], row[2], row[0], 1.0, -decomp->skew[1]);
    392   decomp->skew[2] = Dot<3>(row[1], row[2]);
    393   Combine<3>(row[2], row[2], row[1], 1.0, -decomp->skew[2]);
    394 
    395   // Next, get Z scale and normalize 3rd row.
    396   decomp->scale[2] = Length3(row[2]);
    397   if (decomp->scale[2] != 0.0)
    398     Scale3(row[2], 1.0 / decomp->scale[2]);
    399 
    400   decomp->skew[1] /= decomp->scale[2];
    401   decomp->skew[2] /= decomp->scale[2];
    402 
    403   // At this point, the matrix (in rows) is orthonormal.
    404   // Check for a coordinate system flip.  If the determinant
    405   // is -1, then negate the matrix and the scaling factors.
    406   SkMScalar pdum3[3];
    407   Cross3(pdum3, row[1], row[2]);
    408   if (Dot<3>(row[0], pdum3) < 0) {
    409     for (int i = 0; i < 3; i++) {
    410       decomp->scale[i] *= -1.0;
    411       for (int j = 0; j < 3; ++j)
    412         row[i][j] *= -1.0;
    413     }
    414   }
    415 
    416   decomp->quaternion[0] =
    417       0.5 * std::sqrt(std::max(1.0 + row[0][0] - row[1][1] - row[2][2], 0.0));
    418   decomp->quaternion[1] =
    419       0.5 * std::sqrt(std::max(1.0 - row[0][0] + row[1][1] - row[2][2], 0.0));
    420   decomp->quaternion[2] =
    421       0.5 * std::sqrt(std::max(1.0 - row[0][0] - row[1][1] + row[2][2], 0.0));
    422   decomp->quaternion[3] =
    423       0.5 * std::sqrt(std::max(1.0 + row[0][0] + row[1][1] + row[2][2], 0.0));
    424 
    425   if (row[2][1] > row[1][2])
    426       decomp->quaternion[0] = -decomp->quaternion[0];
    427   if (row[0][2] > row[2][0])
    428       decomp->quaternion[1] = -decomp->quaternion[1];
    429   if (row[1][0] > row[0][1])
    430       decomp->quaternion[2] = -decomp->quaternion[2];
    431 
    432   return true;
    433 }
    434 
    435 // Taken from http://www.w3.org/TR/css3-transforms/.
    436 Transform ComposeTransform(const DecomposedTransform& decomp) {
    437   SkMatrix44 perspective = BuildPerspectiveMatrix(decomp);
    438   SkMatrix44 translation = BuildTranslationMatrix(decomp);
    439   SkMatrix44 rotation = BuildRotationMatrix(decomp);
    440   SkMatrix44 skew = BuildSkewMatrix(decomp);
    441   SkMatrix44 scale = BuildScaleMatrix(decomp);
    442 
    443   return ComposeTransform(perspective, translation, rotation, skew, scale);
    444 }
    445 
    446 bool SnapTransform(Transform* out,
    447                    const Transform& transform,
    448                    const Rect& viewport) {
    449   DecomposedTransform decomp;
    450   DecomposeTransform(&decomp, transform);
    451 
    452   SkMatrix44 rotation_matrix = BuildSnappedRotationMatrix(decomp);
    453   SkMatrix44 translation = BuildSnappedTranslationMatrix(decomp);
    454   SkMatrix44 scale = BuildSnappedScaleMatrix(decomp);
    455 
    456   // Rebuild matrices for other unchanged components.
    457   SkMatrix44 perspective = BuildPerspectiveMatrix(decomp);
    458 
    459   // Completely ignore the skew.
    460   SkMatrix44 skew(SkMatrix44::kIdentity_Constructor);
    461 
    462   // Get full tranform
    463   Transform snapped =
    464       ComposeTransform(perspective, translation, rotation_matrix, skew, scale);
    465 
    466   // Verify that viewport is not moved unnaturally.
    467   bool snappable =
    468     CheckTransformsMapsIntViewportWithinOnePixel(viewport, transform, snapped);
    469   if (snappable) {
    470     *out = snapped;
    471   }
    472   return snappable;
    473 }
    474 
    475 std::string DecomposedTransform::ToString() const {
    476   return base::StringPrintf(
    477       "translate: %+0.4f %+0.4f %+0.4f\n"
    478       "scale: %+0.4f %+0.4f %+0.4f\n"
    479       "skew: %+0.4f %+0.4f %+0.4f\n"
    480       "perspective: %+0.4f %+0.4f %+0.4f %+0.4f\n"
    481       "quaternion: %+0.4f %+0.4f %+0.4f %+0.4f\n",
    482       translate[0],
    483       translate[1],
    484       translate[2],
    485       scale[0],
    486       scale[1],
    487       scale[2],
    488       skew[0],
    489       skew[1],
    490       skew[2],
    491       perspective[0],
    492       perspective[1],
    493       perspective[2],
    494       perspective[3],
    495       quaternion[0],
    496       quaternion[1],
    497       quaternion[2],
    498       quaternion[3]);
    499 }
    500 
    501 }  // namespace ui
    502