root/ui/gfx/transform_unittest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. PointsAreNearlyEqual
  2. MatricesAreNearlyEqual
  3. InitializeTestMatrix
  4. InitializeTestMatrix2
  5. InitializeApproxIdentityMatrix
  6. TEST
  7. TEST
  8. TEST
  9. TEST
  10. TEST
  11. TEST
  12. TEST
  13. TEST
  14. TEST
  15. TEST
  16. TEST
  17. TEST
  18. TEST
  19. TEST
  20. TEST
  21. TEST
  22. TEST
  23. TEST
  24. TEST
  25. TEST
  26. TEST
  27. TEST
  28. TEST
  29. TEST
  30. TEST
  31. TEST
  32. TEST
  33. TEST
  34. TEST
  35. TEST
  36. TEST
  37. TEST
  38. TEST
  39. TEST
  40. TEST
  41. TEST
  42. TEST
  43. TEST
  44. TEST
  45. TEST
  46. TEST
  47. TEST
  48. TEST
  49. TEST
  50. TEST
  51. TEST
  52. TEST
  53. TEST
  54. TEST
  55. TEST
  56. TEST
  57. TEST
  58. TEST
  59. TEST
  60. TEST
  61. TEST
  62. TEST
  63. TEST
  64. TEST
  65. TEST
  66. TEST
  67. TEST
  68. TEST
  69. TEST
  70. TEST
  71. TEST
  72. TEST
  73. TEST
  74. TEST
  75. EmpiricallyPreserves2dAxisAlignment
  76. TEST
  77. TEST
  78. TEST
  79. TEST
  80. TEST
  81. TEST

// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// MSVC++ requires this to be set before any other includes to get M_PI.
#define _USE_MATH_DEFINES

#include "ui/gfx/transform.h"

#include <cmath>
#include <ostream>
#include <limits>

#include "base/basictypes.h"
#include "base/logging.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/box_f.h"
#include "ui/gfx/point.h"
#include "ui/gfx/point3_f.h"
#include "ui/gfx/quad_f.h"
#include "ui/gfx/transform_util.h"
#include "ui/gfx/vector3d_f.h"

namespace gfx {

namespace {

#define EXPECT_ROW1_EQ(a, b, c, d, transform)               \
    EXPECT_FLOAT_EQ((a), (transform).matrix().get(0, 0));   \
    EXPECT_FLOAT_EQ((b), (transform).matrix().get(0, 1));   \
    EXPECT_FLOAT_EQ((c), (transform).matrix().get(0, 2));   \
    EXPECT_FLOAT_EQ((d), (transform).matrix().get(0, 3));

#define EXPECT_ROW2_EQ(a, b, c, d, transform)               \
    EXPECT_FLOAT_EQ((a), (transform).matrix().get(1, 0));   \
    EXPECT_FLOAT_EQ((b), (transform).matrix().get(1, 1));   \
    EXPECT_FLOAT_EQ((c), (transform).matrix().get(1, 2));   \
    EXPECT_FLOAT_EQ((d), (transform).matrix().get(1, 3));

#define EXPECT_ROW3_EQ(a, b, c, d, transform)               \
    EXPECT_FLOAT_EQ((a), (transform).matrix().get(2, 0));   \
    EXPECT_FLOAT_EQ((b), (transform).matrix().get(2, 1));   \
    EXPECT_FLOAT_EQ((c), (transform).matrix().get(2, 2));   \
    EXPECT_FLOAT_EQ((d), (transform).matrix().get(2, 3));

#define EXPECT_ROW4_EQ(a, b, c, d, transform)               \
    EXPECT_FLOAT_EQ((a), (transform).matrix().get(3, 0));   \
    EXPECT_FLOAT_EQ((b), (transform).matrix().get(3, 1));   \
    EXPECT_FLOAT_EQ((c), (transform).matrix().get(3, 2));   \
    EXPECT_FLOAT_EQ((d), (transform).matrix().get(3, 3));   \

// Checking float values for equality close to zero is not robust using
// EXPECT_FLOAT_EQ (see gtest documentation). So, to verify rotation matrices,
// we must use a looser absolute error threshold in some places.
#define EXPECT_ROW1_NEAR(a, b, c, d, transform, errorThreshold)         \
    EXPECT_NEAR((a), (transform).matrix().get(0, 0), (errorThreshold)); \
    EXPECT_NEAR((b), (transform).matrix().get(0, 1), (errorThreshold)); \
    EXPECT_NEAR((c), (transform).matrix().get(0, 2), (errorThreshold)); \
    EXPECT_NEAR((d), (transform).matrix().get(0, 3), (errorThreshold));

#define EXPECT_ROW2_NEAR(a, b, c, d, transform, errorThreshold)         \
    EXPECT_NEAR((a), (transform).matrix().get(1, 0), (errorThreshold)); \
    EXPECT_NEAR((b), (transform).matrix().get(1, 1), (errorThreshold)); \
    EXPECT_NEAR((c), (transform).matrix().get(1, 2), (errorThreshold)); \
    EXPECT_NEAR((d), (transform).matrix().get(1, 3), (errorThreshold));

#define EXPECT_ROW3_NEAR(a, b, c, d, transform, errorThreshold)         \
    EXPECT_NEAR((a), (transform).matrix().get(2, 0), (errorThreshold)); \
    EXPECT_NEAR((b), (transform).matrix().get(2, 1), (errorThreshold)); \
    EXPECT_NEAR((c), (transform).matrix().get(2, 2), (errorThreshold)); \
    EXPECT_NEAR((d), (transform).matrix().get(2, 3), (errorThreshold));

bool PointsAreNearlyEqual(const Point3F& lhs,
                          const Point3F& rhs) {
  float epsilon = 0.0001f;
  return lhs.SquaredDistanceTo(rhs) < epsilon;
}

bool MatricesAreNearlyEqual(const Transform& lhs,
                            const Transform& rhs) {
  float epsilon = 0.0001f;
  for (int row = 0; row < 4; ++row) {
    for (int col = 0; col < 4; ++col) {
      if (std::abs(lhs.matrix().get(row, col) -
                   rhs.matrix().get(row, col)) > epsilon)
        return false;
    }
  }
  return true;
}

void InitializeTestMatrix(Transform* transform) {
  SkMatrix44& matrix = transform->matrix();
  matrix.set(0, 0, 10.f);
  matrix.set(1, 0, 11.f);
  matrix.set(2, 0, 12.f);
  matrix.set(3, 0, 13.f);
  matrix.set(0, 1, 14.f);
  matrix.set(1, 1, 15.f);
  matrix.set(2, 1, 16.f);
  matrix.set(3, 1, 17.f);
  matrix.set(0, 2, 18.f);
  matrix.set(1, 2, 19.f);
  matrix.set(2, 2, 20.f);
  matrix.set(3, 2, 21.f);
  matrix.set(0, 3, 22.f);
  matrix.set(1, 3, 23.f);
  matrix.set(2, 3, 24.f);
  matrix.set(3, 3, 25.f);

  // Sanity check
  EXPECT_ROW1_EQ(10.0f, 14.0f, 18.0f, 22.0f, (*transform));
  EXPECT_ROW2_EQ(11.0f, 15.0f, 19.0f, 23.0f, (*transform));
  EXPECT_ROW3_EQ(12.0f, 16.0f, 20.0f, 24.0f, (*transform));
  EXPECT_ROW4_EQ(13.0f, 17.0f, 21.0f, 25.0f, (*transform));
}

void InitializeTestMatrix2(Transform* transform) {
  SkMatrix44& matrix = transform->matrix();
  matrix.set(0, 0, 30.f);
  matrix.set(1, 0, 31.f);
  matrix.set(2, 0, 32.f);
  matrix.set(3, 0, 33.f);
  matrix.set(0, 1, 34.f);
  matrix.set(1, 1, 35.f);
  matrix.set(2, 1, 36.f);
  matrix.set(3, 1, 37.f);
  matrix.set(0, 2, 38.f);
  matrix.set(1, 2, 39.f);
  matrix.set(2, 2, 40.f);
  matrix.set(3, 2, 41.f);
  matrix.set(0, 3, 42.f);
  matrix.set(1, 3, 43.f);
  matrix.set(2, 3, 44.f);
  matrix.set(3, 3, 45.f);

  // Sanity check
  EXPECT_ROW1_EQ(30.0f, 34.0f, 38.0f, 42.0f, (*transform));
  EXPECT_ROW2_EQ(31.0f, 35.0f, 39.0f, 43.0f, (*transform));
  EXPECT_ROW3_EQ(32.0f, 36.0f, 40.0f, 44.0f, (*transform));
  EXPECT_ROW4_EQ(33.0f, 37.0f, 41.0f, 45.0f, (*transform));
}

const SkMScalar kApproxZero =
    SkFloatToMScalar(std::numeric_limits<float>::epsilon());
const SkMScalar kApproxOne = 1 - kApproxZero;

void InitializeApproxIdentityMatrix(Transform* transform) {
  SkMatrix44& matrix = transform->matrix();
  matrix.set(0, 0, kApproxOne);
  matrix.set(0, 1, kApproxZero);
  matrix.set(0, 2, kApproxZero);
  matrix.set(0, 3, kApproxZero);

  matrix.set(1, 0, kApproxZero);
  matrix.set(1, 1, kApproxOne);
  matrix.set(1, 2, kApproxZero);
  matrix.set(1, 3, kApproxZero);

  matrix.set(2, 0, kApproxZero);
  matrix.set(2, 1, kApproxZero);
  matrix.set(2, 2, kApproxOne);
  matrix.set(2, 3, kApproxZero);

  matrix.set(3, 0, kApproxZero);
  matrix.set(3, 1, kApproxZero);
  matrix.set(3, 2, kApproxZero);
  matrix.set(3, 3, kApproxOne);
}

#ifdef SK_MSCALAR_IS_DOUBLE
#define ERROR_THRESHOLD 1e-14
#else
#define ERROR_THRESHOLD 1e-7
#endif
#define LOOSE_ERROR_THRESHOLD 1e-7

TEST(XFormTest, Equality) {
  Transform lhs, rhs, interpolated;
  rhs.matrix().set3x3(1, 2, 3,
                      4, 5, 6,
                      7, 8, 9);
  interpolated = lhs;
  for (int i = 0; i <= 100; ++i) {
    for (int row = 0; row < 4; ++row) {
      for (int col = 0; col < 4; ++col) {
        float a = lhs.matrix().get(row, col);
        float b = rhs.matrix().get(row, col);
        float t = i / 100.0f;
        interpolated.matrix().set(row, col, a + (b - a) * t);
      }
    }
    if (i == 100) {
      EXPECT_TRUE(rhs == interpolated);
    } else {
      EXPECT_TRUE(rhs != interpolated);
    }
  }
  lhs = Transform();
  rhs = Transform();
  for (int i = 1; i < 100; ++i) {
    lhs.MakeIdentity();
    rhs.MakeIdentity();
    lhs.Translate(i, i);
    rhs.Translate(-i, -i);
    EXPECT_TRUE(lhs != rhs);
    rhs.Translate(2*i, 2*i);
    EXPECT_TRUE(lhs == rhs);
  }
}

TEST(XFormTest, ConcatTranslate) {
  static const struct TestCase {
    int x1;
    int y1;
    float tx;
    float ty;
    int x2;
    int y2;
  } test_cases[] = {
    { 0, 0, 10.0f, 20.0f, 10, 20 },
    { 0, 0, -10.0f, -20.0f, 0, 0 },
    { 0, 0, -10.0f, -20.0f, -10, -20 },
    { 0, 0,
      std::numeric_limits<float>::quiet_NaN(),
      std::numeric_limits<float>::quiet_NaN(),
      10, 20 },
  };

  Transform xform;
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
    const TestCase& value = test_cases[i];
    Transform translation;
    translation.Translate(value.tx, value.ty);
    xform = translation * xform;
    Point3F p1(value.x1, value.y1, 0);
    Point3F p2(value.x2, value.y2, 0);
    xform.TransformPoint(&p1);
    if (value.tx == value.tx &&
        value.ty == value.ty) {
      EXPECT_TRUE(PointsAreNearlyEqual(p1, p2));
    }
  }
}

TEST(XFormTest, ConcatScale) {
  static const struct TestCase {
    int before;
    float scale;
    int after;
  } test_cases[] = {
    { 1, 10.0f, 10 },
    { 1, .1f, 1 },
    { 1, 100.0f, 100 },
    { 1, -1.0f, -100 },
    { 1, std::numeric_limits<float>::quiet_NaN(), 1 }
  };

  Transform xform;
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
    const TestCase& value = test_cases[i];
    Transform scale;
    scale.Scale(value.scale, value.scale);
    xform = scale * xform;
    Point3F p1(value.before, value.before, 0);
    Point3F p2(value.after, value.after, 0);
    xform.TransformPoint(&p1);
    if (value.scale == value.scale) {
      EXPECT_TRUE(PointsAreNearlyEqual(p1, p2));
    }
  }
}

TEST(XFormTest, ConcatRotate) {
  static const struct TestCase {
    int x1;
    int y1;
    float degrees;
    int x2;
    int y2;
  } test_cases[] = {
    { 1, 0, 90.0f, 0, 1 },
    { 1, 0, -90.0f, 1, 0 },
    { 1, 0, 90.0f, 0, 1 },
    { 1, 0, 360.0f, 0, 1 },
    { 1, 0, 0.0f, 0, 1 },
    { 1, 0, std::numeric_limits<float>::quiet_NaN(), 1, 0 }
  };

  Transform xform;
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
    const TestCase& value = test_cases[i];
    Transform rotation;
    rotation.Rotate(value.degrees);
    xform = rotation * xform;
    Point3F p1(value.x1, value.y1, 0);
    Point3F p2(value.x2, value.y2, 0);
    xform.TransformPoint(&p1);
    if (value.degrees == value.degrees) {
      EXPECT_TRUE(PointsAreNearlyEqual(p1, p2));
    }
  }
}

TEST(XFormTest, SetTranslate) {
  static const struct TestCase {
    int x1; int y1;
    float tx; float ty;
    int x2; int y2;
  } test_cases[] = {
    { 0, 0, 10.0f, 20.0f, 10, 20 },
    { 10, 20, 10.0f, 20.0f, 20, 40 },
    { 10, 20, 0.0f, 0.0f, 10, 20 },
    { 0, 0,
      std::numeric_limits<float>::quiet_NaN(),
      std::numeric_limits<float>::quiet_NaN(),
      0, 0 }
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
    const TestCase& value = test_cases[i];
    for (int k = 0; k < 3; ++k) {
      Point3F p0, p1, p2;
      Transform xform;
      switch (k) {
      case 0:
        p1.SetPoint(value.x1, 0, 0);
        p2.SetPoint(value.x2, 0, 0);
        xform.Translate(value.tx, 0.0);
        break;
      case 1:
        p1.SetPoint(0, value.y1, 0);
        p2.SetPoint(0, value.y2, 0);
        xform.Translate(0.0, value.ty);
        break;
      case 2:
        p1.SetPoint(value.x1, value.y1, 0);
        p2.SetPoint(value.x2, value.y2, 0);
        xform.Translate(value.tx, value.ty);
        break;
      }
      p0 = p1;
      xform.TransformPoint(&p1);
      if (value.tx == value.tx &&
          value.ty == value.ty) {
        EXPECT_TRUE(PointsAreNearlyEqual(p1, p2));
        xform.TransformPointReverse(&p1);
        EXPECT_TRUE(PointsAreNearlyEqual(p1, p0));
      }
    }
  }
}

TEST(XFormTest, SetScale) {
  static const struct TestCase {
    int before;
    float s;
    int after;
  } test_cases[] = {
    { 1, 10.0f, 10 },
    { 1, 1.0f, 1 },
    { 1, 0.0f, 0 },
    { 0, 10.0f, 0 },
    { 1, std::numeric_limits<float>::quiet_NaN(), 0 },
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
    const TestCase& value = test_cases[i];
    for (int k = 0; k < 3; ++k) {
      Point3F p0, p1, p2;
      Transform xform;
      switch (k) {
      case 0:
        p1.SetPoint(value.before, 0, 0);
        p2.SetPoint(value.after, 0, 0);
        xform.Scale(value.s, 1.0);
        break;
      case 1:
        p1.SetPoint(0, value.before, 0);
        p2.SetPoint(0, value.after, 0);
        xform.Scale(1.0, value.s);
        break;
      case 2:
        p1.SetPoint(value.before, value.before, 0);
        p2.SetPoint(value.after, value.after, 0);
        xform.Scale(value.s, value.s);
        break;
      }
      p0 = p1;
      xform.TransformPoint(&p1);
      if (value.s == value.s) {
        EXPECT_TRUE(PointsAreNearlyEqual(p1, p2));
        if (value.s != 0.0f) {
          xform.TransformPointReverse(&p1);
          EXPECT_TRUE(PointsAreNearlyEqual(p1, p0));
        }
      }
    }
  }
}

TEST(XFormTest, SetRotate) {
  static const struct SetRotateCase {
    int x;
    int y;
    float degree;
    int xprime;
    int yprime;
  } set_rotate_cases[] = {
    { 100, 0, 90.0f, 0, 100 },
    { 0, 0, 90.0f, 0, 0 },
    { 0, 100, 90.0f, -100, 0 },
    { 0, 1, -90.0f, 1, 0 },
    { 100, 0, 0.0f, 100, 0 },
    { 0, 0, 0.0f, 0, 0 },
    { 0, 0, std::numeric_limits<float>::quiet_NaN(), 0, 0 },
    { 100, 0, 360.0f, 100, 0 }
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(set_rotate_cases); ++i) {
    const SetRotateCase& value = set_rotate_cases[i];
    Point3F p0;
    Point3F p1(value.x, value.y, 0);
    Point3F p2(value.xprime, value.yprime, 0);
    p0 = p1;
    Transform xform;
    xform.Rotate(value.degree);
    // just want to make sure that we don't crash in the case of NaN.
    if (value.degree == value.degree) {
      xform.TransformPoint(&p1);
      EXPECT_TRUE(PointsAreNearlyEqual(p1, p2));
      xform.TransformPointReverse(&p1);
      EXPECT_TRUE(PointsAreNearlyEqual(p1, p0));
    }
  }
}

// 2D tests
TEST(XFormTest, ConcatTranslate2D) {
  static const struct TestCase {
    int x1;
    int y1;
    float tx;
    float ty;
    int x2;
    int y2;
  } test_cases[] = {
    { 0, 0, 10.0f, 20.0f, 10, 20},
    { 0, 0, -10.0f, -20.0f, 0, 0},
    { 0, 0, -10.0f, -20.0f, -10, -20},
    { 0, 0,
      std::numeric_limits<float>::quiet_NaN(),
      std::numeric_limits<float>::quiet_NaN(),
      10, 20},
  };

  Transform xform;
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
    const TestCase& value = test_cases[i];
    Transform translation;
    translation.Translate(value.tx, value.ty);
    xform = translation * xform;
    Point p1(value.x1, value.y1);
    Point p2(value.x2, value.y2);
    xform.TransformPoint(&p1);
    if (value.tx == value.tx &&
        value.ty == value.ty) {
      EXPECT_EQ(p1.x(), p2.x());
      EXPECT_EQ(p1.y(), p2.y());
    }
  }
}

TEST(XFormTest, ConcatScale2D) {
  static const struct TestCase {
    int before;
    float scale;
    int after;
  } test_cases[] = {
    { 1, 10.0f, 10},
    { 1, .1f, 1},
    { 1, 100.0f, 100},
    { 1, -1.0f, -100},
    { 1, std::numeric_limits<float>::quiet_NaN(), 1}
  };

  Transform xform;
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
    const TestCase& value = test_cases[i];
    Transform scale;
    scale.Scale(value.scale, value.scale);
    xform = scale * xform;
    Point p1(value.before, value.before);
    Point p2(value.after, value.after);
    xform.TransformPoint(&p1);
    if (value.scale == value.scale) {
      EXPECT_EQ(p1.x(), p2.x());
      EXPECT_EQ(p1.y(), p2.y());
    }
  }
}

TEST(XFormTest, ConcatRotate2D) {
  static const struct TestCase {
    int x1;
    int y1;
    float degrees;
    int x2;
    int y2;
  } test_cases[] = {
    { 1, 0, 90.0f, 0, 1},
    { 1, 0, -90.0f, 1, 0},
    { 1, 0, 90.0f, 0, 1},
    { 1, 0, 360.0f, 0, 1},
    { 1, 0, 0.0f, 0, 1},
    { 1, 0, std::numeric_limits<float>::quiet_NaN(), 1, 0}
  };

  Transform xform;
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
    const TestCase& value = test_cases[i];
    Transform rotation;
    rotation.Rotate(value.degrees);
    xform = rotation * xform;
    Point p1(value.x1, value.y1);
    Point p2(value.x2, value.y2);
    xform.TransformPoint(&p1);
    if (value.degrees == value.degrees) {
      EXPECT_EQ(p1.x(), p2.x());
      EXPECT_EQ(p1.y(), p2.y());
    }
  }
}

TEST(XFormTest, SetTranslate2D) {
  static const struct TestCase {
    int x1; int y1;
    float tx; float ty;
    int x2; int y2;
  } test_cases[] = {
    { 0, 0, 10.0f, 20.0f, 10, 20},
    { 10, 20, 10.0f, 20.0f, 20, 40},
    { 10, 20, 0.0f, 0.0f, 10, 20},
    { 0, 0,
      std::numeric_limits<float>::quiet_NaN(),
      std::numeric_limits<float>::quiet_NaN(),
      0, 0}
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
    const TestCase& value = test_cases[i];
    for (int j = -1; j < 2; ++j) {
      for (int k = 0; k < 3; ++k) {
        float epsilon = 0.0001f;
        Point p0, p1, p2;
        Transform xform;
        switch (k) {
        case 0:
          p1.SetPoint(value.x1, 0);
          p2.SetPoint(value.x2, 0);
          xform.Translate(value.tx + j * epsilon, 0.0);
          break;
        case 1:
          p1.SetPoint(0, value.y1);
          p2.SetPoint(0, value.y2);
          xform.Translate(0.0, value.ty + j * epsilon);
          break;
        case 2:
          p1.SetPoint(value.x1, value.y1);
          p2.SetPoint(value.x2, value.y2);
          xform.Translate(value.tx + j * epsilon,
                          value.ty + j * epsilon);
          break;
        }
        p0 = p1;
        xform.TransformPoint(&p1);
        if (value.tx == value.tx &&
            value.ty == value.ty) {
          EXPECT_EQ(p1.x(), p2.x());
          EXPECT_EQ(p1.y(), p2.y());
          xform.TransformPointReverse(&p1);
          EXPECT_EQ(p1.x(), p0.x());
          EXPECT_EQ(p1.y(), p0.y());
        }
      }
    }
  }
}

TEST(XFormTest, SetScale2D) {
  static const struct TestCase {
    int before;
    float s;
    int after;
  } test_cases[] = {
    { 1, 10.0f, 10},
    { 1, 1.0f, 1},
    { 1, 0.0f, 0},
    { 0, 10.0f, 0},
    { 1, std::numeric_limits<float>::quiet_NaN(), 0},
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
    const TestCase& value = test_cases[i];
    for (int j = -1; j < 2; ++j) {
      for (int k = 0; k < 3; ++k) {
        float epsilon = 0.0001f;
        Point p0, p1, p2;
        Transform xform;
        switch (k) {
        case 0:
          p1.SetPoint(value.before, 0);
          p2.SetPoint(value.after, 0);
          xform.Scale(value.s + j * epsilon, 1.0);
          break;
        case 1:
          p1.SetPoint(0, value.before);
          p2.SetPoint(0, value.after);
          xform.Scale(1.0, value.s + j * epsilon);
          break;
        case 2:
          p1.SetPoint(value.before,
                      value.before);
          p2.SetPoint(value.after,
                      value.after);
          xform.Scale(value.s + j * epsilon,
                      value.s + j * epsilon);
          break;
        }
        p0 = p1;
        xform.TransformPoint(&p1);
        if (value.s == value.s) {
          EXPECT_EQ(p1.x(), p2.x());
          EXPECT_EQ(p1.y(), p2.y());
          if (value.s != 0.0f) {
            xform.TransformPointReverse(&p1);
            EXPECT_EQ(p1.x(), p0.x());
            EXPECT_EQ(p1.y(), p0.y());
          }
        }
      }
    }
  }
}

TEST(XFormTest, SetRotate2D) {
  static const struct SetRotateCase {
    int x;
    int y;
    float degree;
    int xprime;
    int yprime;
  } set_rotate_cases[] = {
    { 100, 0, 90.0f, 0, 100},
    { 0, 0, 90.0f, 0, 0},
    { 0, 100, 90.0f, -100, 0},
    { 0, 1, -90.0f, 1, 0},
    { 100, 0, 0.0f, 100, 0},
    { 0, 0, 0.0f, 0, 0},
    { 0, 0, std::numeric_limits<float>::quiet_NaN(), 0, 0},
    { 100, 0, 360.0f, 100, 0}
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(set_rotate_cases); ++i) {
    const SetRotateCase& value = set_rotate_cases[i];
    for (int j = 1; j >= -1; --j) {
      float epsilon = 0.1f;
      Point pt(value.x, value.y);
      Transform xform;
      // should be invariant to small floating point errors.
      xform.Rotate(value.degree + j * epsilon);
      // just want to make sure that we don't crash in the case of NaN.
      if (value.degree == value.degree) {
        xform.TransformPoint(&pt);
        EXPECT_EQ(value.xprime, pt.x());
        EXPECT_EQ(value.yprime, pt.y());
        xform.TransformPointReverse(&pt);
        EXPECT_EQ(pt.x(), value.x);
        EXPECT_EQ(pt.y(), value.y);
      }
    }
  }
}

TEST(XFormTest, TransformPointWithExtremePerspective) {
  Point3F point(1.f, 1.f, 1.f);
  Transform perspective;
  perspective.ApplyPerspectiveDepth(1.f);
  Point3F transformed = point;
  perspective.TransformPoint(&transformed);
  EXPECT_EQ(point.ToString(), transformed.ToString());

  transformed = point;
  perspective.MakeIdentity();
  perspective.ApplyPerspectiveDepth(1.1f);
  perspective.TransformPoint(&transformed);
  EXPECT_FLOAT_EQ(11.f, transformed.x());
  EXPECT_FLOAT_EQ(11.f, transformed.y());
  EXPECT_FLOAT_EQ(11.f, transformed.z());
}

TEST(XFormTest, BlendTranslate) {
  Transform from;
  for (int i = -5; i < 15; ++i) {
    Transform to;
    to.Translate3d(1, 1, 1);
    double t = i / 9.0;
    EXPECT_TRUE(to.Blend(from, t));
    EXPECT_FLOAT_EQ(t, to.matrix().get(0, 3));
    EXPECT_FLOAT_EQ(t, to.matrix().get(1, 3));
    EXPECT_FLOAT_EQ(t, to.matrix().get(2, 3));
  }
}

TEST(XFormTest, BlendRotate) {
  Vector3dF axes[] = {
    Vector3dF(1, 0, 0),
    Vector3dF(0, 1, 0),
    Vector3dF(0, 0, 1),
    Vector3dF(1, 1, 1)
  };
  Transform from;
  for (size_t index = 0; index < ARRAYSIZE_UNSAFE(axes); ++index) {
    for (int i = -5; i < 15; ++i) {
      Transform to;
      to.RotateAbout(axes[index], 90);
      double t = i / 9.0;
      EXPECT_TRUE(to.Blend(from, t));

      Transform expected;
      expected.RotateAbout(axes[index], 90 * t);

      EXPECT_TRUE(MatricesAreNearlyEqual(expected, to));
    }
  }
}

TEST(XFormTest, BlendRotateFollowsShortestPath) {
  // Verify that we interpolate along the shortest path regardless of whether
  // this path crosses the 180-degree point.
  Vector3dF axes[] = {
    Vector3dF(1, 0, 0),
    Vector3dF(0, 1, 0),
    Vector3dF(0, 0, 1),
    Vector3dF(1, 1, 1)
  };
  for (size_t index = 0; index < ARRAYSIZE_UNSAFE(axes); ++index) {
    for (int i = -5; i < 15; ++i) {
      Transform from1;
      from1.RotateAbout(axes[index], 130.0);
      Transform to1;
      to1.RotateAbout(axes[index], 175.0);

      Transform from2;
      from2.RotateAbout(axes[index], 140.0);
      Transform to2;
      to2.RotateAbout(axes[index], 185.0);

      double t = i / 9.0;
      EXPECT_TRUE(to1.Blend(from1, t));
      EXPECT_TRUE(to2.Blend(from2, t));

      Transform expected1;
      expected1.RotateAbout(axes[index], 130.0 + 45.0 * t);

      Transform expected2;
      expected2.RotateAbout(axes[index], 140.0 + 45.0 * t);

      EXPECT_TRUE(MatricesAreNearlyEqual(expected1, to1));
      EXPECT_TRUE(MatricesAreNearlyEqual(expected2, to2));
    }
  }
}

TEST(XFormTest, CanBlend180DegreeRotation) {
  Vector3dF axes[] = {
    Vector3dF(1, 0, 0),
    Vector3dF(0, 1, 0),
    Vector3dF(0, 0, 1),
    Vector3dF(1, 1, 1)
  };
  Transform from;
  for (size_t index = 0; index < ARRAYSIZE_UNSAFE(axes); ++index) {
    for (int i = -5; i < 15; ++i) {
      Transform to;
      to.RotateAbout(axes[index], 180.0);
      double t = i / 9.0;
      EXPECT_TRUE(to.Blend(from, t));

      // A 180 degree rotation is exactly opposite on the sphere, therefore
      // either great circle arc to it is equivalent (and numerical precision
      // will determine which is closer).  Test both directions.
      Transform expected1;
      expected1.RotateAbout(axes[index], 180.0 * t);
      Transform expected2;
      expected2.RotateAbout(axes[index], -180.0 * t);

      EXPECT_TRUE(MatricesAreNearlyEqual(expected1, to) ||
                  MatricesAreNearlyEqual(expected2, to))
          << "axis: " << index << ", i: " << i;
    }
  }
}

TEST(XFormTest, BlendScale) {
  Transform from;
  for (int i = -5; i < 15; ++i) {
    Transform to;
    to.Scale3d(5, 4, 3);
    double t = i / 9.0;
    EXPECT_TRUE(to.Blend(from, t));
    EXPECT_FLOAT_EQ(t * 4 + 1, to.matrix().get(0, 0)) << "i: " << i;
    EXPECT_FLOAT_EQ(t * 3 + 1, to.matrix().get(1, 1)) << "i: " << i;
    EXPECT_FLOAT_EQ(t * 2 + 1, to.matrix().get(2, 2)) << "i: " << i;
  }
}

TEST(XFormTest, BlendSkew) {
  Transform from;
  for (int i = 0; i < 2; ++i) {
    Transform to;
    to.SkewX(10);
    to.SkewY(5);
    double t = i;
    Transform expected;
    expected.SkewX(t * 10);
    expected.SkewY(t * 5);
    EXPECT_TRUE(to.Blend(from, t));
    EXPECT_TRUE(MatricesAreNearlyEqual(expected, to));
  }
}

TEST(XFormTest, ExtrapolateSkew) {
  Transform from;
  for (int i = -1; i < 2; ++i) {
    Transform to;
    to.SkewX(20);
    double t = i;
    Transform expected;
    expected.SkewX(t * 20);
    EXPECT_TRUE(to.Blend(from, t));
    EXPECT_TRUE(MatricesAreNearlyEqual(expected, to));
  }
}

TEST(XFormTest, BlendPerspective) {
  Transform from;
  from.ApplyPerspectiveDepth(200);
  for (int i = -1; i < 3; ++i) {
    Transform to;
    to.ApplyPerspectiveDepth(800);
    double t = i;
    double depth = 1.0 / ((1.0 / 200) * (1.0 - t) + (1.0 / 800) * t);
    Transform expected;
    expected.ApplyPerspectiveDepth(depth);
    EXPECT_TRUE(to.Blend(from, t));
    EXPECT_TRUE(MatricesAreNearlyEqual(expected, to));
  }
}

TEST(XFormTest, BlendIdentity) {
  Transform from;
  Transform to;
  EXPECT_TRUE(to.Blend(from, 0.5));
  EXPECT_EQ(to, from);
}

TEST(XFormTest, CannotBlendSingularMatrix) {
  Transform from;
  Transform to;
  to.matrix().set(1, 1, SkDoubleToMScalar(0));
  EXPECT_FALSE(to.Blend(from, 0.5));
}

TEST(XFormTest, VerifyBlendForTranslation) {
  Transform from;
  from.Translate3d(100.0, 200.0, 100.0);

  Transform to;

  to.Translate3d(200.0, 100.0, 300.0);
  to.Blend(from, 0.0);
  EXPECT_EQ(from, to);

  to = Transform();
  to.Translate3d(200.0, 100.0, 300.0);
  to.Blend(from, 0.25);
  EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 125.0f, to);
  EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 175.0f, to);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 150.0f, to);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f,  1.0f,  to);

  to = Transform();
  to.Translate3d(200.0, 100.0, 300.0);
  to.Blend(from, 0.5);
  EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 150.0f, to);
  EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 150.0f, to);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 200.0f, to);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f,  1.0f,  to);

  to = Transform();
  to.Translate3d(200.0, 100.0, 300.0);
  to.Blend(from, 1.0);
  EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 200.0f, to);
  EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 100.0f, to);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 300.0f, to);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f,  1.0f,  to);
}

TEST(XFormTest, VerifyBlendForScale) {
  Transform from;
  from.Scale3d(100.0, 200.0, 100.0);

  Transform to;

  to.Scale3d(200.0, 100.0, 300.0);
  to.Blend(from, 0.0);
  EXPECT_EQ(from, to);

  to = Transform();
  to.Scale3d(200.0, 100.0, 300.0);
  to.Blend(from, 0.25);
  EXPECT_ROW1_EQ(125.0f, 0.0f,  0.0f,  0.0f, to);
  EXPECT_ROW2_EQ(0.0f,  175.0f, 0.0f,  0.0f, to);
  EXPECT_ROW3_EQ(0.0f,   0.0f, 150.0f, 0.0f, to);
  EXPECT_ROW4_EQ(0.0f,   0.0f,  0.0f,  1.0f, to);

  to = Transform();
  to.Scale3d(200.0, 100.0, 300.0);
  to.Blend(from, 0.5);
  EXPECT_ROW1_EQ(150.0f, 0.0f,  0.0f,  0.0f, to);
  EXPECT_ROW2_EQ(0.0f,  150.0f, 0.0f,  0.0f, to);
  EXPECT_ROW3_EQ(0.0f,   0.0f, 200.0f, 0.0f, to);
  EXPECT_ROW4_EQ(0.0f,   0.0f,  0.0f,  1.0f, to);

  to = Transform();
  to.Scale3d(200.0, 100.0, 300.0);
  to.Blend(from, 1.0);
  EXPECT_ROW1_EQ(200.0f, 0.0f,  0.0f,  0.0f, to);
  EXPECT_ROW2_EQ(0.0f,  100.0f, 0.0f,  0.0f, to);
  EXPECT_ROW3_EQ(0.0f,   0.0f, 300.0f, 0.0f, to);
  EXPECT_ROW4_EQ(0.0f,   0.0f,  0.0f,  1.0f, to);
}

TEST(XFormTest, VerifyBlendForSkewX) {
  Transform from;
  from.SkewX(0.0);

  Transform to;

  to.SkewX(45.0);
  to.Blend(from, 0.0);
  EXPECT_EQ(from, to);

  to = Transform();
  to.SkewX(45.0);
  to.Blend(from, 0.5);
  EXPECT_ROW1_EQ(1.0f, 0.5f, 0.0f, 0.0f, to);
  EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, to);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, to);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);

  to = Transform();
  to.SkewX(45.0);
  to.Blend(from, 0.25);
  EXPECT_ROW1_EQ(1.0f, 0.25f, 0.0f, 0.0f, to);
  EXPECT_ROW2_EQ(0.0f, 1.0f,  0.0f, 0.0f, to);
  EXPECT_ROW3_EQ(0.0f, 0.0f,  1.0f, 0.0f, to);
  EXPECT_ROW4_EQ(0.0f, 0.0f,  0.0f, 1.0f, to);

  to = Transform();
  to.SkewX(45.0);
  to.Blend(from, 1.0);
  EXPECT_ROW1_EQ(1.0f, 1.0f, 0.0f, 0.0f, to);
  EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, to);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, to);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
}

TEST(XFormTest, VerifyBlendForSkewY) {
  // NOTE CAREFULLY: Decomposition of skew and rotation terms of the matrix
  // is inherently underconstrained, and so it does not always compute the
  // originally intended skew parameters. The current implementation uses QR
  // decomposition, which decomposes the shear into a rotation + non-uniform
  // scale.
  //
  // It is unlikely that the decomposition implementation will need to change
  // very often, so to get any test coverage, the compromise is to verify the
  // exact matrix that the.Blend() operation produces.
  //
  // This problem also potentially exists for skewX, but the current QR
  // decomposition implementation just happens to decompose those test
  // matrices intuitively.
  //
  // Unfortunately, this case suffers from uncomfortably large precision
  // error.

  Transform from;
  from.SkewY(0.0);

  Transform to;

  to.SkewY(45.0);
  to.Blend(from, 0.0);
  EXPECT_EQ(from, to);

  to = Transform();
  to.SkewY(45.0);
  to.Blend(from, 0.25);
  EXPECT_ROW1_NEAR(1.0823489449280947471976333,
                   0.0464370719145053845178239,
                   0.0,
                   0.0,
                   to,
                   LOOSE_ERROR_THRESHOLD);
  EXPECT_ROW2_NEAR(0.2152925909665224513123150,
                   0.9541702441750861130032035,
                   0.0,
                   0.0,
                   to,
                   LOOSE_ERROR_THRESHOLD);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, to);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);

  to = Transform();
  to.SkewY(45.0);
  to.Blend(from, 0.5);
  EXPECT_ROW1_NEAR(1.1152212925809066312865525,
                   0.0676495144007326631996335,
                   0.0,
                   0.0,
                   to,
                   LOOSE_ERROR_THRESHOLD);
  EXPECT_ROW2_NEAR(0.4619397844342648662419037,
                   0.9519009045724774464858342,
                   0.0,
                   0.0,
                   to,
                   LOOSE_ERROR_THRESHOLD);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, to);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);

  to = Transform();
  to.SkewY(45.0);
  to.Blend(from, 1.0);
  EXPECT_ROW1_NEAR(1.0, 0.0, 0.0, 0.0, to, LOOSE_ERROR_THRESHOLD);
  EXPECT_ROW2_NEAR(1.0, 1.0, 0.0, 0.0, to, LOOSE_ERROR_THRESHOLD);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, to);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
}

TEST(XFormTest, VerifyBlendForRotationAboutX) {
  // Even though.Blending uses quaternions, axis-aligned rotations should.
  // Blend the same with quaternions or Euler angles. So we can test
  // rotation.Blending by comparing against manually specified matrices from
  // Euler angles.

  Transform from;
  from.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 0.0);

  Transform to;

  to.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 90.0);
  to.Blend(from, 0.0);
  EXPECT_EQ(from, to);

  double expectedRotationAngle = 22.5 * M_PI / 180.0;
  to = Transform();
  to.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 90.0);
  to.Blend(from, 0.25);
  EXPECT_ROW1_NEAR(1.0, 0.0, 0.0, 0.0, to, ERROR_THRESHOLD);
  EXPECT_ROW2_NEAR(0.0,
                   std::cos(expectedRotationAngle),
                   -std::sin(expectedRotationAngle),
                   0.0,
                   to,
                   ERROR_THRESHOLD);
  EXPECT_ROW3_NEAR(0.0,
                   std::sin(expectedRotationAngle),
                   std::cos(expectedRotationAngle),
                   0.0,
                   to,
                   ERROR_THRESHOLD);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);

  expectedRotationAngle = 45.0 * M_PI / 180.0;
  to = Transform();
  to.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 90.0);
  to.Blend(from, 0.5);
  EXPECT_ROW1_NEAR(1.0, 0.0, 0.0, 0.0, to, ERROR_THRESHOLD);
  EXPECT_ROW2_NEAR(0.0,
                   std::cos(expectedRotationAngle),
                   -std::sin(expectedRotationAngle),
                   0.0,
                   to,
                   ERROR_THRESHOLD);
  EXPECT_ROW3_NEAR(0.0,
                   std::sin(expectedRotationAngle),
                   std::cos(expectedRotationAngle),
                   0.0,
                   to,
                   ERROR_THRESHOLD);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);

  to = Transform();
  to.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 90.0);
  to.Blend(from, 1.0);
  EXPECT_ROW1_NEAR(1.0, 0.0,  0.0, 0.0, to, ERROR_THRESHOLD);
  EXPECT_ROW2_NEAR(0.0, 0.0, -1.0, 0.0, to, ERROR_THRESHOLD);
  EXPECT_ROW3_NEAR(0.0, 1.0,  0.0, 0.0, to, ERROR_THRESHOLD);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
}

TEST(XFormTest, VerifyBlendForRotationAboutY) {
  Transform from;
  from.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 0.0);

  Transform to;

  to.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 90.0);
  to.Blend(from, 0.0);
  EXPECT_EQ(from, to);

  double expectedRotationAngle = 22.5 * M_PI / 180.0;
  to = Transform();
  to.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 90.0);
  to.Blend(from, 0.25);
  EXPECT_ROW1_NEAR(std::cos(expectedRotationAngle),
                   0.0,
                   std::sin(expectedRotationAngle),
                   0.0,
                   to,
                   ERROR_THRESHOLD);
  EXPECT_ROW2_NEAR(0.0, 1.0, 0.0, 0.0, to, ERROR_THRESHOLD);
  EXPECT_ROW3_NEAR(-std::sin(expectedRotationAngle),
                   0.0,
                   std::cos(expectedRotationAngle),
                   0.0,
                   to,
                   ERROR_THRESHOLD);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);

  expectedRotationAngle = 45.0 * M_PI / 180.0;
  to = Transform();
  to.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 90.0);
  to.Blend(from, 0.5);
  EXPECT_ROW1_NEAR(std::cos(expectedRotationAngle),
                   0.0,
                   std::sin(expectedRotationAngle),
                   0.0,
                   to,
                   ERROR_THRESHOLD);
  EXPECT_ROW2_NEAR(0.0, 1.0, 0.0, 0.0, to, ERROR_THRESHOLD);
  EXPECT_ROW3_NEAR(-std::sin(expectedRotationAngle),
                   0.0,
                   std::cos(expectedRotationAngle),
                   0.0,
                   to,
                   ERROR_THRESHOLD);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);

  to = Transform();
  to.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 90.0);
  to.Blend(from, 1.0);
  EXPECT_ROW1_NEAR(0.0,  0.0, 1.0, 0.0, to, ERROR_THRESHOLD);
  EXPECT_ROW2_NEAR(0.0,  1.0, 0.0, 0.0, to, ERROR_THRESHOLD);
  EXPECT_ROW3_NEAR(-1.0, 0.0, 0.0, 0.0, to, ERROR_THRESHOLD);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
}

TEST(XFormTest, VerifyBlendForRotationAboutZ) {
  Transform from;
  from.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 0.0);

  Transform to;

  to.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 90.0);
  to.Blend(from, 0.0);
  EXPECT_EQ(from, to);

  double expectedRotationAngle = 22.5 * M_PI / 180.0;
  to = Transform();
  to.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 90.0);
  to.Blend(from, 0.25);
  EXPECT_ROW1_NEAR(std::cos(expectedRotationAngle),
                   -std::sin(expectedRotationAngle),
                   0.0,
                   0.0,
                   to,
                   ERROR_THRESHOLD);
  EXPECT_ROW2_NEAR(std::sin(expectedRotationAngle),
                   std::cos(expectedRotationAngle),
                   0.0,
                   0.0,
                   to,
                   ERROR_THRESHOLD);
  EXPECT_ROW3_NEAR(0.0, 0.0, 1.0, 0.0, to, ERROR_THRESHOLD);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);

  expectedRotationAngle = 45.0 * M_PI / 180.0;
  to = Transform();
  to.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 90.0);
  to.Blend(from, 0.5);
  EXPECT_ROW1_NEAR(std::cos(expectedRotationAngle),
                   -std::sin(expectedRotationAngle),
                   0.0,
                   0.0,
                   to,
                   ERROR_THRESHOLD);
  EXPECT_ROW2_NEAR(std::sin(expectedRotationAngle),
                   std::cos(expectedRotationAngle),
                   0.0,
                   0.0,
                   to,
                   ERROR_THRESHOLD);
  EXPECT_ROW3_NEAR(0.0, 0.0, 1.0, 0.0, to, ERROR_THRESHOLD);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);

  to = Transform();
  to.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 90.0);
  to.Blend(from, 1.0);
  EXPECT_ROW1_NEAR(0.0, -1.0, 0.0, 0.0, to, ERROR_THRESHOLD);
  EXPECT_ROW2_NEAR(1.0,  0.0, 0.0, 0.0, to, ERROR_THRESHOLD);
  EXPECT_ROW3_NEAR(0.0,  0.0, 1.0, 0.0, to, ERROR_THRESHOLD);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
}

TEST(XFormTest, VerifyBlendForCompositeTransform) {
  // Verify that the.Blending was done with a decomposition in correct order
  // by blending a composite transform. Using matrix x vector notation
  // (Ax = b, where x is column vector), the ordering should be:
  // perspective * translation * rotation * skew * scale
  //
  // It is not as important (or meaningful) to check intermediate
  // interpolations; order of operations will be tested well enough by the
  // end cases that are easier to specify.

  Transform from;
  Transform to;

  Transform expectedEndOfAnimation;
  expectedEndOfAnimation.ApplyPerspectiveDepth(1.0);
  expectedEndOfAnimation.Translate3d(10.0, 20.0, 30.0);
  expectedEndOfAnimation.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 25.0);
  expectedEndOfAnimation.SkewY(45.0);
  expectedEndOfAnimation.Scale3d(6.0, 7.0, 8.0);

  to = expectedEndOfAnimation;
  to.Blend(from, 0.0);
  EXPECT_EQ(from, to);

  to = expectedEndOfAnimation;
  // We short circuit if blend is >= 1, so to check the numerics, we will
  // check that we get close to what we expect when we're nearly done
  // interpolating.
  to.Blend(from, .99999f);

  // Recomposing the matrix results in a normalized matrix, so to verify we
  // need to normalize the expectedEndOfAnimation before comparing elements.
  // Normalizing means dividing everything by expectedEndOfAnimation.m44().
  Transform normalizedExpectedEndOfAnimation = expectedEndOfAnimation;
  Transform normalizationMatrix;
  normalizationMatrix.matrix().set(
      0.0,
      0.0,
      SkDoubleToMScalar(1 / expectedEndOfAnimation.matrix().get(3.0, 3.0)));
  normalizationMatrix.matrix().set(
      1.0,
      1.0,
      SkDoubleToMScalar(1 / expectedEndOfAnimation.matrix().get(3.0, 3.0)));
  normalizationMatrix.matrix().set(
      2.0,
      2.0,
      SkDoubleToMScalar(1 / expectedEndOfAnimation.matrix().get(3.0, 3.0)));
  normalizationMatrix.matrix().set(
      3.0,
      3.0,
      SkDoubleToMScalar(1 / expectedEndOfAnimation.matrix().get(3.0, 3.0)));
  normalizedExpectedEndOfAnimation.PreconcatTransform(normalizationMatrix);

  EXPECT_TRUE(MatricesAreNearlyEqual(normalizedExpectedEndOfAnimation, to));
}

TEST(XFormTest, DecomposedTransformCtor) {
  DecomposedTransform decomp;
  for (int i = 0; i < 3; ++i) {
    EXPECT_EQ(0.0, decomp.translate[i]);
    EXPECT_EQ(1.0, decomp.scale[i]);
    EXPECT_EQ(0.0, decomp.skew[i]);
    EXPECT_EQ(0.0, decomp.quaternion[i]);
    EXPECT_EQ(0.0, decomp.perspective[i]);
  }
  EXPECT_EQ(1.0, decomp.quaternion[3]);
  EXPECT_EQ(1.0, decomp.perspective[3]);
  Transform identity;
  Transform composed = ComposeTransform(decomp);
  EXPECT_TRUE(MatricesAreNearlyEqual(identity, composed));
}

TEST(XFormTest, FactorTRS) {
  for (int degrees = 0; degrees < 180; ++degrees) {
    // build a transformation matrix.
    gfx::Transform transform;
    transform.Translate(degrees * 2, -degrees * 3);
    transform.Rotate(degrees);
    transform.Scale(degrees + 1, 2 * degrees + 1);

    // factor the matrix
    DecomposedTransform decomp;
    bool success = DecomposeTransform(&decomp, transform);
    EXPECT_TRUE(success);
    EXPECT_FLOAT_EQ(decomp.translate[0], degrees * 2);
    EXPECT_FLOAT_EQ(decomp.translate[1], -degrees * 3);
    double rotation =
        std::acos(SkMScalarToDouble(decomp.quaternion[3])) * 360.0 / M_PI;
    while (rotation < 0.0)
      rotation += 360.0;
    while (rotation > 360.0)
      rotation -= 360.0;

    const float epsilon = 0.00015f;
    EXPECT_NEAR(rotation, degrees, epsilon);
    EXPECT_NEAR(decomp.scale[0], degrees + 1, epsilon);
    EXPECT_NEAR(decomp.scale[1], 2 * degrees + 1, epsilon);
  }
}

TEST(XFormTest, IntegerTranslation) {
  gfx::Transform transform;
  EXPECT_TRUE(transform.IsIdentityOrIntegerTranslation());

  transform.Translate3d(1, 2, 3);
  EXPECT_TRUE(transform.IsIdentityOrIntegerTranslation());

  transform.MakeIdentity();
  transform.Translate3d(-1, -2, -3);
  EXPECT_TRUE(transform.IsIdentityOrIntegerTranslation());

  transform.MakeIdentity();
  transform.Translate3d(4.5f, 0, 0);
  EXPECT_FALSE(transform.IsIdentityOrIntegerTranslation());

  transform.MakeIdentity();
  transform.Translate3d(0, -6.7f, 0);
  EXPECT_FALSE(transform.IsIdentityOrIntegerTranslation());

  transform.MakeIdentity();
  transform.Translate3d(0, 0, 8.9f);
  EXPECT_FALSE(transform.IsIdentityOrIntegerTranslation());
}

TEST(XFormTest, verifyMatrixInversion) {
  {
    // Invert a translation
    gfx::Transform translation;
    translation.Translate3d(2.0, 3.0, 4.0);
    EXPECT_TRUE(translation.IsInvertible());

    gfx::Transform inverse_translation;
    bool is_invertible = translation.GetInverse(&inverse_translation);
    EXPECT_TRUE(is_invertible);
    EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, -2.0f, inverse_translation);
    EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, -3.0f, inverse_translation);
    EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, -4.0f, inverse_translation);
    EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f,  1.0f, inverse_translation);
  }

  {
    // Invert a non-uniform scale
    gfx::Transform scale;
    scale.Scale3d(4.0, 10.0, 100.0);
    EXPECT_TRUE(scale.IsInvertible());

    gfx::Transform inverse_scale;
    bool is_invertible = scale.GetInverse(&inverse_scale);
    EXPECT_TRUE(is_invertible);
    EXPECT_ROW1_EQ(0.25f, 0.0f, 0.0f, 0.0f, inverse_scale);
    EXPECT_ROW2_EQ(0.0f,  0.1f, 0.0f, 0.0f, inverse_scale);
    EXPECT_ROW3_EQ(0.0f,  0.0f, 0.01f, 0.0f, inverse_scale);
    EXPECT_ROW4_EQ(0.0f,  0.0f, 0.0f, 1.0f, inverse_scale);
  }

  {
    // Try to invert a matrix that is not invertible.
    // The inverse() function should reset the output matrix to identity.
    gfx::Transform uninvertible;
    uninvertible.matrix().set(0, 0, 0.f);
    uninvertible.matrix().set(1, 1, 0.f);
    uninvertible.matrix().set(2, 2, 0.f);
    uninvertible.matrix().set(3, 3, 0.f);
    EXPECT_FALSE(uninvertible.IsInvertible());

    gfx::Transform inverse_of_uninvertible;

    // Add a scale just to more easily ensure that inverse_of_uninvertible is
    // reset to identity.
    inverse_of_uninvertible.Scale3d(4.0, 10.0, 100.0);

    bool is_invertible = uninvertible.GetInverse(&inverse_of_uninvertible);
    EXPECT_FALSE(is_invertible);
    EXPECT_TRUE(inverse_of_uninvertible.IsIdentity());
    EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, inverse_of_uninvertible);
    EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, inverse_of_uninvertible);
    EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, inverse_of_uninvertible);
    EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, inverse_of_uninvertible);
  }
}

TEST(XFormTest, verifyBackfaceVisibilityBasicCases) {
  Transform transform;

  transform.MakeIdentity();
  EXPECT_FALSE(transform.IsBackFaceVisible());

  transform.MakeIdentity();
  transform.RotateAboutYAxis(80.0);
  EXPECT_FALSE(transform.IsBackFaceVisible());

  transform.MakeIdentity();
  transform.RotateAboutYAxis(100.0);
  EXPECT_TRUE(transform.IsBackFaceVisible());

  // Edge case, 90 degree rotation should return false.
  transform.MakeIdentity();
  transform.RotateAboutYAxis(90.0);
  EXPECT_FALSE(transform.IsBackFaceVisible());
}

TEST(XFormTest, verifyBackfaceVisibilityForPerspective) {
  Transform layer_space_to_projection_plane;

  // This tests if IsBackFaceVisible works properly under perspective
  // transforms.  Specifically, layers that may have their back face visible in
  // orthographic projection, may not actually have back face visible under
  // perspective projection.

  // Case 1: Layer is rotated by slightly more than 90 degrees, at the center
  //         of the prespective projection. In this case, the layer's back-side
  //         is visible to the camera.
  layer_space_to_projection_plane.MakeIdentity();
  layer_space_to_projection_plane.ApplyPerspectiveDepth(1.0);
  layer_space_to_projection_plane.Translate3d(0.0, 0.0, 0.0);
  layer_space_to_projection_plane.RotateAboutYAxis(100.0);
  EXPECT_TRUE(layer_space_to_projection_plane.IsBackFaceVisible());

  // Case 2: Layer is rotated by slightly more than 90 degrees, but shifted off
  //         to the side of the camera. Because of the wide field-of-view, the
  //         layer's front side is still visible.
  //
  //                       |<-- front side of layer is visible to camera
  //                    \  |            /
  //                     \ |           /
  //                      \|          /
  //                       |         /
  //                       |\       /<-- camera field of view
  //                       | \     /
  // back side of layer -->|  \   /
  //                           \./ <-- camera origin
  //
  layer_space_to_projection_plane.MakeIdentity();
  layer_space_to_projection_plane.ApplyPerspectiveDepth(1.0);
  layer_space_to_projection_plane.Translate3d(-10.0, 0.0, 0.0);
  layer_space_to_projection_plane.RotateAboutYAxis(100.0);
  EXPECT_FALSE(layer_space_to_projection_plane.IsBackFaceVisible());

  // Case 3: Additionally rotating the layer by 180 degrees should of course
  //         show the opposite result of case 2.
  layer_space_to_projection_plane.RotateAboutYAxis(180.0);
  EXPECT_TRUE(layer_space_to_projection_plane.IsBackFaceVisible());
}

TEST(XFormTest, verifyDefaultConstructorCreatesIdentityMatrix) {
  Transform A;
  EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A);
  EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
  EXPECT_TRUE(A.IsIdentity());
}

TEST(XFormTest, verifyCopyConstructor) {
  Transform A;
  InitializeTestMatrix(&A);

  // Copy constructor should produce exact same elements as matrix A.
  Transform B(A);
  EXPECT_ROW1_EQ(10.0f, 14.0f, 18.0f, 22.0f, B);
  EXPECT_ROW2_EQ(11.0f, 15.0f, 19.0f, 23.0f, B);
  EXPECT_ROW3_EQ(12.0f, 16.0f, 20.0f, 24.0f, B);
  EXPECT_ROW4_EQ(13.0f, 17.0f, 21.0f, 25.0f, B);
}

TEST(XFormTest, verifyConstructorFor16Elements) {
  Transform transform(1.0, 2.0, 3.0, 4.0,
                      5.0, 6.0, 7.0, 8.0,
                      9.0, 10.0, 11.0, 12.0,
                      13.0, 14.0, 15.0, 16.0);

  EXPECT_ROW1_EQ(1.0f, 2.0f, 3.0f, 4.0f, transform);
  EXPECT_ROW2_EQ(5.0f, 6.0f, 7.0f, 8.0f, transform);
  EXPECT_ROW3_EQ(9.0f, 10.0f, 11.0f, 12.0f, transform);
  EXPECT_ROW4_EQ(13.0f, 14.0f, 15.0f, 16.0f, transform);
}

TEST(XFormTest, verifyConstructorFor2dElements) {
  Transform transform(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);

  EXPECT_ROW1_EQ(1.0f, 2.0f, 0.0f, 5.0f, transform);
  EXPECT_ROW2_EQ(3.0f, 4.0f, 0.0f, 6.0f, transform);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, transform);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, transform);
}


TEST(XFormTest, verifyAssignmentOperator) {
  Transform A;
  InitializeTestMatrix(&A);
  Transform B;
  InitializeTestMatrix2(&B);
  Transform C;
  InitializeTestMatrix2(&C);
  C = B = A;

  // Both B and C should now have been re-assigned to the value of A.
  EXPECT_ROW1_EQ(10.0f, 14.0f, 18.0f, 22.0f, B);
  EXPECT_ROW2_EQ(11.0f, 15.0f, 19.0f, 23.0f, B);
  EXPECT_ROW3_EQ(12.0f, 16.0f, 20.0f, 24.0f, B);
  EXPECT_ROW4_EQ(13.0f, 17.0f, 21.0f, 25.0f, B);

  EXPECT_ROW1_EQ(10.0f, 14.0f, 18.0f, 22.0f, C);
  EXPECT_ROW2_EQ(11.0f, 15.0f, 19.0f, 23.0f, C);
  EXPECT_ROW3_EQ(12.0f, 16.0f, 20.0f, 24.0f, C);
  EXPECT_ROW4_EQ(13.0f, 17.0f, 21.0f, 25.0f, C);
}

TEST(XFormTest, verifyEqualsBooleanOperator) {
  Transform A;
  InitializeTestMatrix(&A);

  Transform B;
  InitializeTestMatrix(&B);
  EXPECT_TRUE(A == B);

  // Modifying multiple elements should cause equals operator to return false.
  Transform C;
  InitializeTestMatrix2(&C);
  EXPECT_FALSE(A == C);

  // Modifying any one individual element should cause equals operator to
  // return false.
  Transform D;
  D = A;
  D.matrix().set(0, 0, 0.f);
  EXPECT_FALSE(A == D);

  D = A;
  D.matrix().set(1, 0, 0.f);
  EXPECT_FALSE(A == D);

  D = A;
  D.matrix().set(2, 0, 0.f);
  EXPECT_FALSE(A == D);

  D = A;
  D.matrix().set(3, 0, 0.f);
  EXPECT_FALSE(A == D);

  D = A;
  D.matrix().set(0, 1, 0.f);
  EXPECT_FALSE(A == D);

  D = A;
  D.matrix().set(1, 1, 0.f);
  EXPECT_FALSE(A == D);

  D = A;
  D.matrix().set(2, 1, 0.f);
  EXPECT_FALSE(A == D);

  D = A;
  D.matrix().set(3, 1, 0.f);
  EXPECT_FALSE(A == D);

  D = A;
  D.matrix().set(0, 2, 0.f);
  EXPECT_FALSE(A == D);

  D = A;
  D.matrix().set(1, 2, 0.f);
  EXPECT_FALSE(A == D);

  D = A;
  D.matrix().set(2, 2, 0.f);
  EXPECT_FALSE(A == D);

  D = A;
  D.matrix().set(3, 2, 0.f);
  EXPECT_FALSE(A == D);

  D = A;
  D.matrix().set(0, 3, 0.f);
  EXPECT_FALSE(A == D);

  D = A;
  D.matrix().set(1, 3, 0.f);
  EXPECT_FALSE(A == D);

  D = A;
  D.matrix().set(2, 3, 0.f);
  EXPECT_FALSE(A == D);

  D = A;
  D.matrix().set(3, 3, 0.f);
  EXPECT_FALSE(A == D);
}

TEST(XFormTest, verifyMultiplyOperator) {
  Transform A;
  InitializeTestMatrix(&A);

  Transform B;
  InitializeTestMatrix2(&B);

  Transform C = A * B;
  EXPECT_ROW1_EQ(2036.0f, 2292.0f, 2548.0f, 2804.0f, C);
  EXPECT_ROW2_EQ(2162.0f, 2434.0f, 2706.0f, 2978.0f, C);
  EXPECT_ROW3_EQ(2288.0f, 2576.0f, 2864.0f, 3152.0f, C);
  EXPECT_ROW4_EQ(2414.0f, 2718.0f, 3022.0f, 3326.0f, C);

  // Just an additional sanity check; matrix multiplication is not commutative.
  EXPECT_FALSE(A * B == B * A);
}

TEST(XFormTest, verifyMultiplyAndAssignOperator) {
  Transform A;
  InitializeTestMatrix(&A);

  Transform B;
  InitializeTestMatrix2(&B);

  A *= B;
  EXPECT_ROW1_EQ(2036.0f, 2292.0f, 2548.0f, 2804.0f, A);
  EXPECT_ROW2_EQ(2162.0f, 2434.0f, 2706.0f, 2978.0f, A);
  EXPECT_ROW3_EQ(2288.0f, 2576.0f, 2864.0f, 3152.0f, A);
  EXPECT_ROW4_EQ(2414.0f, 2718.0f, 3022.0f, 3326.0f, A);

  // Just an additional sanity check; matrix multiplication is not commutative.
  Transform C = A;
  C *= B;
  Transform D = B;
  D *= A;
  EXPECT_FALSE(C == D);
}

TEST(XFormTest, verifyMatrixMultiplication) {
  Transform A;
  InitializeTestMatrix(&A);

  Transform B;
  InitializeTestMatrix2(&B);

  A.PreconcatTransform(B);
  EXPECT_ROW1_EQ(2036.0f, 2292.0f, 2548.0f, 2804.0f, A);
  EXPECT_ROW2_EQ(2162.0f, 2434.0f, 2706.0f, 2978.0f, A);
  EXPECT_ROW3_EQ(2288.0f, 2576.0f, 2864.0f, 3152.0f, A);
  EXPECT_ROW4_EQ(2414.0f, 2718.0f, 3022.0f, 3326.0f, A);
}

TEST(XFormTest, verifyMakeIdentiy) {
  Transform A;
  InitializeTestMatrix(&A);
  A.MakeIdentity();
  EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A);
  EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
  EXPECT_TRUE(A.IsIdentity());
}

TEST(XFormTest, verifyTranslate) {
  Transform A;
  A.Translate(2.0, 3.0);
  EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 2.0f, A);
  EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 3.0f, A);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);

  // Verify that Translate() post-multiplies the existing matrix.
  A.MakeIdentity();
  A.Scale(5.0, 5.0);
  A.Translate(2.0, 3.0);
  EXPECT_ROW1_EQ(5.0f, 0.0f, 0.0f, 10.0f, A);
  EXPECT_ROW2_EQ(0.0f, 5.0f, 0.0f, 15.0f, A);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f,  A);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f,  A);
}

TEST(XFormTest, verifyTranslate3d) {
  Transform A;
  A.Translate3d(2.0, 3.0, 4.0);
  EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 2.0f, A);
  EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 3.0f, A);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 4.0f, A);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);

  // Verify that Translate3d() post-multiplies the existing matrix.
  A.MakeIdentity();
  A.Scale3d(6.0, 7.0, 8.0);
  A.Translate3d(2.0, 3.0, 4.0);
  EXPECT_ROW1_EQ(6.0f, 0.0f, 0.0f, 12.0f, A);
  EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 21.0f, A);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 32.0f, A);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f,  A);
}

TEST(XFormTest, verifyScale) {
  Transform A;
  A.Scale(6.0, 7.0);
  EXPECT_ROW1_EQ(6.0f, 0.0f, 0.0f, 0.0f, A);
  EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 0.0f, A);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);

  // Verify that Scale() post-multiplies the existing matrix.
  A.MakeIdentity();
  A.Translate3d(2.0, 3.0, 4.0);
  A.Scale(6.0, 7.0);
  EXPECT_ROW1_EQ(6.0f, 0.0f, 0.0f, 2.0f, A);
  EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 3.0f, A);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 4.0f, A);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
}

TEST(XFormTest, verifyScale3d) {
  Transform A;
  A.Scale3d(6.0, 7.0, 8.0);
  EXPECT_ROW1_EQ(6.0f, 0.0f, 0.0f, 0.0f, A);
  EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 0.0f, A);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 0.0f, A);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);

  // Verify that scale3d() post-multiplies the existing matrix.
  A.MakeIdentity();
  A.Translate3d(2.0, 3.0, 4.0);
  A.Scale3d(6.0, 7.0, 8.0);
  EXPECT_ROW1_EQ(6.0f, 0.0f, 0.0f, 2.0f, A);
  EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 3.0f, A);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 4.0f, A);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
}

TEST(XFormTest, verifyRotate) {
  Transform A;
  A.Rotate(90.0);
  EXPECT_ROW1_NEAR(0.0, -1.0, 0.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW2_NEAR(1.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);

  // Verify that Rotate() post-multiplies the existing matrix.
  A.MakeIdentity();
  A.Scale3d(6.0, 7.0, 8.0);
  A.Rotate(90.0);
  EXPECT_ROW1_NEAR(0.0, -6.0, 0.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW2_NEAR(7.0, 0.0,  0.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 0.0f, A);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
}

TEST(XFormTest, verifyRotateAboutXAxis) {
  Transform A;
  double sin45 = 0.5 * sqrt(2.0);
  double cos45 = sin45;

  A.MakeIdentity();
  A.RotateAboutXAxis(90.0);
  EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A);
  EXPECT_ROW2_NEAR(0.0, 0.0, -1.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW3_NEAR(0.0, 1.0, 0.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);

  A.MakeIdentity();
  A.RotateAboutXAxis(45.0);
  EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A);
  EXPECT_ROW2_NEAR(0.0, cos45, -sin45, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW3_NEAR(0.0, sin45, cos45, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);

  // Verify that RotateAboutXAxis(angle) post-multiplies the existing matrix.
  A.MakeIdentity();
  A.Scale3d(6.0, 7.0, 8.0);
  A.RotateAboutXAxis(90.0);
  EXPECT_ROW1_NEAR(6.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW2_NEAR(0.0, 0.0, -7.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW3_NEAR(0.0, 8.0, 0.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
}

TEST(XFormTest, verifyRotateAboutYAxis) {
  Transform A;
  double sin45 = 0.5 * sqrt(2.0);
  double cos45 = sin45;

  // Note carefully, the expected pattern is inverted compared to rotating
  // about x axis or z axis.
  A.MakeIdentity();
  A.RotateAboutYAxis(90.0);
  EXPECT_ROW1_NEAR(0.0, 0.0, 1.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A);
  EXPECT_ROW3_NEAR(-1.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);

  A.MakeIdentity();
  A.RotateAboutYAxis(45.0);
  EXPECT_ROW1_NEAR(cos45, 0.0, sin45, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A);
  EXPECT_ROW3_NEAR(-sin45, 0.0, cos45, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);

  // Verify that RotateAboutYAxis(angle) post-multiplies the existing matrix.
  A.MakeIdentity();
  A.Scale3d(6.0, 7.0, 8.0);
  A.RotateAboutYAxis(90.0);
  EXPECT_ROW1_NEAR(0.0, 0.0, 6.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW2_NEAR(0.0, 7.0, 0.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW3_NEAR(-8.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
}

TEST(XFormTest, verifyRotateAboutZAxis) {
  Transform A;
  double sin45 = 0.5 * sqrt(2.0);
  double cos45 = sin45;

  A.MakeIdentity();
  A.RotateAboutZAxis(90.0);
  EXPECT_ROW1_NEAR(0.0, -1.0, 0.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW2_NEAR(1.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);

  A.MakeIdentity();
  A.RotateAboutZAxis(45.0);
  EXPECT_ROW1_NEAR(cos45, -sin45, 0.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW2_NEAR(sin45, cos45, 0.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);

  // Verify that RotateAboutZAxis(angle) post-multiplies the existing matrix.
  A.MakeIdentity();
  A.Scale3d(6.0, 7.0, 8.0);
  A.RotateAboutZAxis(90.0);
  EXPECT_ROW1_NEAR(0.0, -6.0, 0.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW2_NEAR(7.0, 0.0,  0.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 0.0f, A);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
}

TEST(XFormTest, verifyRotateAboutForAlignedAxes) {
  Transform A;

  // Check rotation about z-axis
  A.MakeIdentity();
  A.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 90.0);
  EXPECT_ROW1_NEAR(0.0, -1.0, 0.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW2_NEAR(1.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);

  // Check rotation about x-axis
  A.MakeIdentity();
  A.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 90.0);
  EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A);
  EXPECT_ROW2_NEAR(0.0, 0.0, -1.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW3_NEAR(0.0, 1.0, 0.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);

  // Check rotation about y-axis. Note carefully, the expected pattern is
  // inverted compared to rotating about x axis or z axis.
  A.MakeIdentity();
  A.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 90.0);
  EXPECT_ROW1_NEAR(0.0, 0.0, 1.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A);
  EXPECT_ROW3_NEAR(-1.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);

  // Verify that rotate3d(axis, angle) post-multiplies the existing matrix.
  A.MakeIdentity();
  A.Scale3d(6.0, 7.0, 8.0);
  A.RotateAboutZAxis(90.0);
  EXPECT_ROW1_NEAR(0.0, -6.0, 0.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW2_NEAR(7.0, 0.0,  0.0, 0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 0.0f, A);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
}

TEST(XFormTest, verifyRotateAboutForArbitraryAxis) {
  // Check rotation about an arbitrary non-axis-aligned vector.
  Transform A;
  A.RotateAbout(Vector3dF(1.0, 1.0, 1.0), 90.0);
  EXPECT_ROW1_NEAR(0.3333333333333334258519187,
                   -0.2440169358562924717404030,
                   0.9106836025229592124219380,
                   0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW2_NEAR(0.9106836025229592124219380,
                   0.3333333333333334258519187,
                   -0.2440169358562924717404030,
                   0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW3_NEAR(-0.2440169358562924717404030,
                   0.9106836025229592124219380,
                   0.3333333333333334258519187,
                   0.0, A, ERROR_THRESHOLD);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
}

TEST(XFormTest, verifyRotateAboutForDegenerateAxis) {
  // Check rotation about a degenerate zero vector.
  // It is expected to skip applying the rotation.
  Transform A;

  A.RotateAbout(Vector3dF(0.0, 0.0, 0.0), 45.0);
  // Verify that A remains unchanged.
  EXPECT_TRUE(A.IsIdentity());

  InitializeTestMatrix(&A);
  A.RotateAbout(Vector3dF(0.0, 0.0, 0.0), 35.0);

  // Verify that A remains unchanged.
  EXPECT_ROW1_EQ(10.0f, 14.0f, 18.0f, 22.0f, A);
  EXPECT_ROW2_EQ(11.0f, 15.0f, 19.0f, 23.0f, A);
  EXPECT_ROW3_EQ(12.0f, 16.0f, 20.0f, 24.0f, A);
  EXPECT_ROW4_EQ(13.0f, 17.0f, 21.0f, 25.0f, A);
}

TEST(XFormTest, verifySkewX) {
  Transform A;
  A.SkewX(45.0);
  EXPECT_ROW1_EQ(1.0f, 1.0f, 0.0f, 0.0f, A);
  EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);

  // Verify that skewX() post-multiplies the existing matrix. Row 1, column 2,
  // would incorrectly have value "7" if the matrix is pre-multiplied instead
  // of post-multiplied.
  A.MakeIdentity();
  A.Scale3d(6.0, 7.0, 8.0);
  A.SkewX(45.0);
  EXPECT_ROW1_EQ(6.0f, 6.0f, 0.0f, 0.0f, A);
  EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 0.0f, A);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 0.0f, A);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
}

TEST(XFormTest, verifySkewY) {
  Transform A;
  A.SkewY(45.0);
  EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A);
  EXPECT_ROW2_EQ(1.0f, 1.0f, 0.0f, 0.0f, A);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);

  // Verify that skewY() post-multiplies the existing matrix. Row 2, column 1 ,
  // would incorrectly have value "6" if the matrix is pre-multiplied instead
  // of post-multiplied.
  A.MakeIdentity();
  A.Scale3d(6.0, 7.0, 8.0);
  A.SkewY(45.0);
  EXPECT_ROW1_EQ(6.0f, 0.0f, 0.0f, 0.0f, A);
  EXPECT_ROW2_EQ(7.0f, 7.0f, 0.0f, 0.0f, A);
  EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 0.0f, A);
  EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
}

TEST(XFormTest, verifyPerspectiveDepth) {
  Transform A;
  A.ApplyPerspectiveDepth(1.0);
  EXPECT_ROW1_EQ(1.0f, 0.0f,  0.0f, 0.0f, A);
  EXPECT_ROW2_EQ(0.0f, 1.0f,  0.0f, 0.0f, A);
  EXPECT_ROW3_EQ(0.0f, 0.0f,  1.0f, 0.0f, A);
  EXPECT_ROW4_EQ(0.0f, 0.0f, -1.0f, 1.0f, A);

  // Verify that PerspectiveDepth() post-multiplies the existing matrix.
  A.MakeIdentity();
  A.Translate3d(2.0, 3.0, 4.0);
  A.ApplyPerspectiveDepth(1.0);
  EXPECT_ROW1_EQ(1.0f, 0.0f, -2.0f, 2.0f, A);
  EXPECT_ROW2_EQ(0.0f, 1.0f, -3.0f, 3.0f, A);
  EXPECT_ROW3_EQ(0.0f, 0.0f, -3.0f, 4.0f, A);
  EXPECT_ROW4_EQ(0.0f, 0.0f, -1.0f, 1.0f, A);
}

TEST(XFormTest, verifyHasPerspective) {
  Transform A;
  A.ApplyPerspectiveDepth(1.0);
  EXPECT_TRUE(A.HasPerspective());

  A.MakeIdentity();
  A.ApplyPerspectiveDepth(0.0);
  EXPECT_FALSE(A.HasPerspective());

  A.MakeIdentity();
  A.matrix().set(3, 0, -1.f);
  EXPECT_TRUE(A.HasPerspective());

  A.MakeIdentity();
  A.matrix().set(3, 1, -1.f);
  EXPECT_TRUE(A.HasPerspective());

  A.MakeIdentity();
  A.matrix().set(3, 2, -0.3f);
  EXPECT_TRUE(A.HasPerspective());

  A.MakeIdentity();
  A.matrix().set(3, 3, 0.5f);
  EXPECT_TRUE(A.HasPerspective());

  A.MakeIdentity();
  A.matrix().set(3, 3, 0.f);
  EXPECT_TRUE(A.HasPerspective());
}

TEST(XFormTest, verifyIsInvertible) {
  Transform A;

  // Translations, rotations, scales, skews and arbitrary combinations of them
  // are invertible.
  A.MakeIdentity();
  EXPECT_TRUE(A.IsInvertible());

  A.MakeIdentity();
  A.Translate3d(2.0, 3.0, 4.0);
  EXPECT_TRUE(A.IsInvertible());

  A.MakeIdentity();
  A.Scale3d(6.0, 7.0, 8.0);
  EXPECT_TRUE(A.IsInvertible());

  A.MakeIdentity();
  A.RotateAboutXAxis(10.0);
  A.RotateAboutYAxis(20.0);
  A.RotateAboutZAxis(30.0);
  EXPECT_TRUE(A.IsInvertible());

  A.MakeIdentity();
  A.SkewX(45.0);
  EXPECT_TRUE(A.IsInvertible());

  // A perspective matrix (projection plane at z=0) is invertible. The
  // intuitive explanation is that perspective is eqivalent to a skew of the
  // w-axis; skews are invertible.
  A.MakeIdentity();
  A.ApplyPerspectiveDepth(1.0);
  EXPECT_TRUE(A.IsInvertible());

  // A "pure" perspective matrix derived by similar triangles, with m44() set
  // to zero (i.e. camera positioned at the origin), is not invertible.
  A.MakeIdentity();
  A.ApplyPerspectiveDepth(1.0);
  A.matrix().set(3, 3, 0.f);
  EXPECT_FALSE(A.IsInvertible());

  // Adding more to a non-invertible matrix will not make it invertible in the
  // general case.
  A.MakeIdentity();
  A.ApplyPerspectiveDepth(1.0);
  A.matrix().set(3, 3, 0.f);
  A.Scale3d(6.0, 7.0, 8.0);
  A.RotateAboutXAxis(10.0);
  A.RotateAboutYAxis(20.0);
  A.RotateAboutZAxis(30.0);
  A.Translate3d(6.0, 7.0, 8.0);
  EXPECT_FALSE(A.IsInvertible());

  // A degenerate matrix of all zeros is not invertible.
  A.MakeIdentity();
  A.matrix().set(0, 0, 0.f);
  A.matrix().set(1, 1, 0.f);
  A.matrix().set(2, 2, 0.f);
  A.matrix().set(3, 3, 0.f);
  EXPECT_FALSE(A.IsInvertible());
}

TEST(XFormTest, verifyIsIdentity) {
  Transform A;

  InitializeTestMatrix(&A);
  EXPECT_FALSE(A.IsIdentity());

  A.MakeIdentity();
  EXPECT_TRUE(A.IsIdentity());

  // Modifying any one individual element should cause the matrix to no longer
  // be identity.
  A.MakeIdentity();
  A.matrix().set(0, 0, 2.f);
  EXPECT_FALSE(A.IsIdentity());

  A.MakeIdentity();
  A.matrix().set(1, 0, 2.f);
  EXPECT_FALSE(A.IsIdentity());

  A.MakeIdentity();
  A.matrix().set(2, 0, 2.f);
  EXPECT_FALSE(A.IsIdentity());

  A.MakeIdentity();
  A.matrix().set(3, 0, 2.f);
  EXPECT_FALSE(A.IsIdentity());

  A.MakeIdentity();
  A.matrix().set(0, 1, 2.f);
  EXPECT_FALSE(A.IsIdentity());

  A.MakeIdentity();
  A.matrix().set(1, 1, 2.f);
  EXPECT_FALSE(A.IsIdentity());

  A.MakeIdentity();
  A.matrix().set(2, 1, 2.f);
  EXPECT_FALSE(A.IsIdentity());

  A.MakeIdentity();
  A.matrix().set(3, 1, 2.f);
  EXPECT_FALSE(A.IsIdentity());

  A.MakeIdentity();
  A.matrix().set(0, 2, 2.f);
  EXPECT_FALSE(A.IsIdentity());

  A.MakeIdentity();
  A.matrix().set(1, 2, 2.f);
  EXPECT_FALSE(A.IsIdentity());

  A.MakeIdentity();
  A.matrix().set(2, 2, 2.f);
  EXPECT_FALSE(A.IsIdentity());

  A.MakeIdentity();
  A.matrix().set(3, 2, 2.f);
  EXPECT_FALSE(A.IsIdentity());

  A.MakeIdentity();
  A.matrix().set(0, 3, 2.f);
  EXPECT_FALSE(A.IsIdentity());

  A.MakeIdentity();
  A.matrix().set(1, 3, 2.f);
  EXPECT_FALSE(A.IsIdentity());

  A.MakeIdentity();
  A.matrix().set(2, 3, 2.f);
  EXPECT_FALSE(A.IsIdentity());

  A.MakeIdentity();
  A.matrix().set(3, 3, 2.f);
  EXPECT_FALSE(A.IsIdentity());
}

TEST(XFormTest, verifyIsIdentityOrTranslation) {
  Transform A;

  InitializeTestMatrix(&A);
  EXPECT_FALSE(A.IsIdentityOrTranslation());

  A.MakeIdentity();
  EXPECT_TRUE(A.IsIdentityOrTranslation());

  // Modifying any non-translation components should cause
  // IsIdentityOrTranslation() to return false. NOTE: (0, 3), (1, 3), and
  // (2, 3) are the translation components, so modifying them should still
  // return true.
  A.MakeIdentity();
  A.matrix().set(0, 0, 2.f);
  EXPECT_FALSE(A.IsIdentityOrTranslation());

  A.MakeIdentity();
  A.matrix().set(1, 0, 2.f);
  EXPECT_FALSE(A.IsIdentityOrTranslation());

  A.MakeIdentity();
  A.matrix().set(2, 0, 2.f);
  EXPECT_FALSE(A.IsIdentityOrTranslation());

  A.MakeIdentity();
  A.matrix().set(3, 0, 2.f);
  EXPECT_FALSE(A.IsIdentityOrTranslation());

  A.MakeIdentity();
  A.matrix().set(0, 1, 2.f);
  EXPECT_FALSE(A.IsIdentityOrTranslation());

  A.MakeIdentity();
  A.matrix().set(1, 1, 2.f);
  EXPECT_FALSE(A.IsIdentityOrTranslation());

  A.MakeIdentity();
  A.matrix().set(2, 1, 2.f);
  EXPECT_FALSE(A.IsIdentityOrTranslation());

  A.MakeIdentity();
  A.matrix().set(3, 1, 2.f);
  EXPECT_FALSE(A.IsIdentityOrTranslation());

  A.MakeIdentity();
  A.matrix().set(0, 2, 2.f);
  EXPECT_FALSE(A.IsIdentityOrTranslation());

  A.MakeIdentity();
  A.matrix().set(1, 2, 2.f);
  EXPECT_FALSE(A.IsIdentityOrTranslation());

  A.MakeIdentity();
  A.matrix().set(2, 2, 2.f);
  EXPECT_FALSE(A.IsIdentityOrTranslation());

  A.MakeIdentity();
  A.matrix().set(3, 2, 2.f);
  EXPECT_FALSE(A.IsIdentityOrTranslation());

  // Note carefully - expecting true here.
  A.MakeIdentity();
  A.matrix().set(0, 3, 2.f);
  EXPECT_TRUE(A.IsIdentityOrTranslation());

  // Note carefully - expecting true here.
  A.MakeIdentity();
  A.matrix().set(1, 3, 2.f);
  EXPECT_TRUE(A.IsIdentityOrTranslation());

  // Note carefully - expecting true here.
  A.MakeIdentity();
  A.matrix().set(2, 3, 2.f);
  EXPECT_TRUE(A.IsIdentityOrTranslation());

  A.MakeIdentity();
  A.matrix().set(3, 3, 2.f);
  EXPECT_FALSE(A.IsIdentityOrTranslation());
}

TEST(XFormTest, verifyIsApproximatelyIdentityOrTranslation) {
  Transform A;
  SkMatrix44& matrix = A.matrix();

  // Exact pure translation.
  A.MakeIdentity();

  // Set translate values to values other than 0 or 1.
  matrix.set(0, 3, 3.4f);
  matrix.set(1, 3, 4.4f);
  matrix.set(2, 3, 5.6f);

  EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(0));
  EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(kApproxZero));

  // Approximately pure translation.
  InitializeApproxIdentityMatrix(&A);

  // Some values must be exact.
  matrix.set(3, 0, 0);
  matrix.set(3, 1, 0);
  matrix.set(3, 2, 0);
  matrix.set(3, 3, 1);

  // Set translate values to values other than 0 or 1.
  matrix.set(0, 3, 3.4f);
  matrix.set(1, 3, 4.4f);
  matrix.set(2, 3, 5.6f);

  EXPECT_FALSE(A.IsApproximatelyIdentityOrTranslation(0));
  EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(kApproxZero));

  // Not approximately pure translation.
  InitializeApproxIdentityMatrix(&A);

  // Some values must be exact.
  matrix.set(3, 0, 0);
  matrix.set(3, 1, 0);
  matrix.set(3, 2, 0);
  matrix.set(3, 3, 1);

  // Set some values (not translate values) to values other than 0 or 1.
  matrix.set(0, 1, 3.4f);
  matrix.set(3, 2, 4.4f);
  matrix.set(2, 0, 5.6f);

  EXPECT_FALSE(A.IsApproximatelyIdentityOrTranslation(0));
  EXPECT_FALSE(A.IsApproximatelyIdentityOrTranslation(kApproxZero));
}

TEST(XFormTest, verifyIsScaleOrTranslation) {
  Transform A;

  InitializeTestMatrix(&A);
  EXPECT_FALSE(A.IsScaleOrTranslation());

  A.MakeIdentity();
  EXPECT_TRUE(A.IsScaleOrTranslation());

  // Modifying any non-scale or non-translation components should cause
  // IsScaleOrTranslation() to return false. (0, 0), (1, 1), (2, 2), (0, 3),
  // (1, 3), and (2, 3) are the scale and translation components, so
  // modifying them should still return true.

  // Note carefully - expecting true here.
  A.MakeIdentity();
  A.matrix().set(0, 0, 2.f);
  EXPECT_TRUE(A.IsScaleOrTranslation());

  A.MakeIdentity();
  A.matrix().set(1, 0, 2.f);
  EXPECT_FALSE(A.IsScaleOrTranslation());

  A.MakeIdentity();
  A.matrix().set(2, 0, 2.f);
  EXPECT_FALSE(A.IsScaleOrTranslation());

  A.MakeIdentity();
  A.matrix().set(3, 0, 2.f);
  EXPECT_FALSE(A.IsScaleOrTranslation());

  A.MakeIdentity();
  A.matrix().set(0, 1, 2.f);
  EXPECT_FALSE(A.IsScaleOrTranslation());

  // Note carefully - expecting true here.
  A.MakeIdentity();
  A.matrix().set(1, 1, 2.f);
  EXPECT_TRUE(A.IsScaleOrTranslation());

  A.MakeIdentity();
  A.matrix().set(2, 1, 2.f);
  EXPECT_FALSE(A.IsScaleOrTranslation());

  A.MakeIdentity();
  A.matrix().set(3, 1, 2.f);
  EXPECT_FALSE(A.IsScaleOrTranslation());

  A.MakeIdentity();
  A.matrix().set(0, 2, 2.f);
  EXPECT_FALSE(A.IsScaleOrTranslation());

  A.MakeIdentity();
  A.matrix().set(1, 2, 2.f);
  EXPECT_FALSE(A.IsScaleOrTranslation());

  // Note carefully - expecting true here.
  A.MakeIdentity();
  A.matrix().set(2, 2, 2.f);
  EXPECT_TRUE(A.IsScaleOrTranslation());

  A.MakeIdentity();
  A.matrix().set(3, 2, 2.f);
  EXPECT_FALSE(A.IsScaleOrTranslation());

  // Note carefully - expecting true here.
  A.MakeIdentity();
  A.matrix().set(0, 3, 2.f);
  EXPECT_TRUE(A.IsScaleOrTranslation());

  // Note carefully - expecting true here.
  A.MakeIdentity();
  A.matrix().set(1, 3, 2.f);
  EXPECT_TRUE(A.IsScaleOrTranslation());

  // Note carefully - expecting true here.
  A.MakeIdentity();
  A.matrix().set(2, 3, 2.f);
  EXPECT_TRUE(A.IsScaleOrTranslation());

  A.MakeIdentity();
  A.matrix().set(3, 3, 2.f);
  EXPECT_FALSE(A.IsScaleOrTranslation());
}

TEST(XFormTest, verifyFlattenTo2d) {
  Transform A;
  InitializeTestMatrix(&A);

  A.FlattenTo2d();
  EXPECT_ROW1_EQ(10.0f, 14.0f, 0.0f, 22.0f, A);
  EXPECT_ROW2_EQ(11.0f, 15.0f, 0.0f, 23.0f, A);
  EXPECT_ROW3_EQ(0.0f,  0.0f,  1.0f, 0.0f,  A);
  EXPECT_ROW4_EQ(13.0f, 17.0f, 0.0f, 25.0f, A);
}

// Another implementation of Preserves2dAxisAlignment that isn't as fast,
// good for testing the faster implementation.
static bool EmpiricallyPreserves2dAxisAlignment(const Transform& transform) {
  Point3F p1(5.0f, 5.0f, 0.0f);
  Point3F p2(10.0f, 5.0f, 0.0f);
  Point3F p3(10.0f, 20.0f, 0.0f);
  Point3F p4(5.0f, 20.0f, 0.0f);

  QuadF test_quad(PointF(p1.x(), p1.y()),
                 PointF(p2.x(), p2.y()),
                 PointF(p3.x(), p3.y()),
                 PointF(p4.x(), p4.y()));
  EXPECT_TRUE(test_quad.IsRectilinear());

  transform.TransformPoint(&p1);
  transform.TransformPoint(&p2);
  transform.TransformPoint(&p3);
  transform.TransformPoint(&p4);

  QuadF transformedQuad(PointF(p1.x(), p1.y()),
                        PointF(p2.x(), p2.y()),
                        PointF(p3.x(), p3.y()),
                        PointF(p4.x(), p4.y()));
  return transformedQuad.IsRectilinear();
}

TEST(XFormTest, Preserves2dAxisAlignment) {
  static const struct TestCase {
    SkMScalar a; // row 1, column 1
    SkMScalar b; // row 1, column 2
    SkMScalar c; // row 2, column 1
    SkMScalar d; // row 2, column 2
    bool expected;
  } test_cases[] = {
    { 3.f, 0.f,
      0.f, 4.f, true }, // basic case
    { 0.f, 4.f,
      3.f, 0.f, true }, // rotate by 90
    { 0.f, 0.f,
      0.f, 4.f, true }, // degenerate x
    { 3.f, 0.f,
      0.f, 0.f, true }, // degenerate y
    { 0.f, 0.f,
      3.f, 0.f, true }, // degenerate x + rotate by 90
    { 0.f, 4.f,
      0.f, 0.f, true }, // degenerate y + rotate by 90
    { 3.f, 4.f,
      0.f, 0.f, false },
    { 0.f, 0.f,
      3.f, 4.f, false },
    { 0.f, 3.f,
      0.f, 4.f, false },
    { 3.f, 0.f,
      4.f, 0.f, false },
    { 3.f, 4.f,
      5.f, 0.f, false },
    { 3.f, 4.f,
      0.f, 5.f, false },
    { 3.f, 0.f,
      4.f, 5.f, false },
    { 0.f, 3.f,
      4.f, 5.f, false },
    { 2.f, 3.f,
      4.f, 5.f, false },
  };

  Transform transform;
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
    const TestCase& value = test_cases[i];
    transform.MakeIdentity();
    transform.matrix().set(0, 0, value.a);
    transform.matrix().set(0, 1, value.b);
    transform.matrix().set(1, 0, value.c);
    transform.matrix().set(1, 1, value.d);

    if (value.expected) {
      EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
      EXPECT_TRUE(transform.Preserves2dAxisAlignment());
    } else {
      EXPECT_FALSE(EmpiricallyPreserves2dAxisAlignment(transform));
      EXPECT_FALSE(transform.Preserves2dAxisAlignment());
    }
  }

  // Try the same test cases again, but this time make sure that other matrix
  // elements (except perspective) have entries, to test that they are ignored.
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
    const TestCase& value = test_cases[i];
    transform.MakeIdentity();
    transform.matrix().set(0, 0, value.a);
    transform.matrix().set(0, 1, value.b);
    transform.matrix().set(1, 0, value.c);
    transform.matrix().set(1, 1, value.d);

    transform.matrix().set(0, 2, 1.f);
    transform.matrix().set(0, 3, 2.f);
    transform.matrix().set(1, 2, 3.f);
    transform.matrix().set(1, 3, 4.f);
    transform.matrix().set(2, 0, 5.f);
    transform.matrix().set(2, 1, 6.f);
    transform.matrix().set(2, 2, 7.f);
    transform.matrix().set(2, 3, 8.f);

    if (value.expected) {
      EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
      EXPECT_TRUE(transform.Preserves2dAxisAlignment());
    } else {
      EXPECT_FALSE(EmpiricallyPreserves2dAxisAlignment(transform));
      EXPECT_FALSE(transform.Preserves2dAxisAlignment());
    }
  }

  // Try the same test cases again, but this time add perspective which is
  // always assumed to not-preserve axis alignment.
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
    const TestCase& value = test_cases[i];
    transform.MakeIdentity();
    transform.matrix().set(0, 0, value.a);
    transform.matrix().set(0, 1, value.b);
    transform.matrix().set(1, 0, value.c);
    transform.matrix().set(1, 1, value.d);

    transform.matrix().set(0, 2, 1.f);
    transform.matrix().set(0, 3, 2.f);
    transform.matrix().set(1, 2, 3.f);
    transform.matrix().set(1, 3, 4.f);
    transform.matrix().set(2, 0, 5.f);
    transform.matrix().set(2, 1, 6.f);
    transform.matrix().set(2, 2, 7.f);
    transform.matrix().set(2, 3, 8.f);
    transform.matrix().set(3, 0, 9.f);
    transform.matrix().set(3, 1, 10.f);
    transform.matrix().set(3, 2, 11.f);
    transform.matrix().set(3, 3, 12.f);

    EXPECT_FALSE(EmpiricallyPreserves2dAxisAlignment(transform));
    EXPECT_FALSE(transform.Preserves2dAxisAlignment());
  }

  // Try a few more practical situations to check precision
  transform.MakeIdentity();
  transform.RotateAboutZAxis(90.0);
  EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
  EXPECT_TRUE(transform.Preserves2dAxisAlignment());

  transform.MakeIdentity();
  transform.RotateAboutZAxis(180.0);
  EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
  EXPECT_TRUE(transform.Preserves2dAxisAlignment());

  transform.MakeIdentity();
  transform.RotateAboutZAxis(270.0);
  EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
  EXPECT_TRUE(transform.Preserves2dAxisAlignment());

  transform.MakeIdentity();
  transform.RotateAboutYAxis(90.0);
  EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
  EXPECT_TRUE(transform.Preserves2dAxisAlignment());

  transform.MakeIdentity();
  transform.RotateAboutXAxis(90.0);
  EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
  EXPECT_TRUE(transform.Preserves2dAxisAlignment());

  transform.MakeIdentity();
  transform.RotateAboutZAxis(90.0);
  transform.RotateAboutYAxis(90.0);
  EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
  EXPECT_TRUE(transform.Preserves2dAxisAlignment());

  transform.MakeIdentity();
  transform.RotateAboutZAxis(90.0);
  transform.RotateAboutXAxis(90.0);
  EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
  EXPECT_TRUE(transform.Preserves2dAxisAlignment());

  transform.MakeIdentity();
  transform.RotateAboutYAxis(90.0);
  transform.RotateAboutZAxis(90.0);
  EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
  EXPECT_TRUE(transform.Preserves2dAxisAlignment());

  transform.MakeIdentity();
  transform.RotateAboutZAxis(45.0);
  EXPECT_FALSE(EmpiricallyPreserves2dAxisAlignment(transform));
  EXPECT_FALSE(transform.Preserves2dAxisAlignment());

  // 3-d case; In 2d after an orthographic projection, this case does
  // preserve 2d axis alignment. But in 3d, it does not preserve axis
  // alignment.
  transform.MakeIdentity();
  transform.RotateAboutYAxis(45.0);
  EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
  EXPECT_TRUE(transform.Preserves2dAxisAlignment());

  transform.MakeIdentity();
  transform.RotateAboutXAxis(45.0);
  EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
  EXPECT_TRUE(transform.Preserves2dAxisAlignment());

  // Perspective cases.
  transform.MakeIdentity();
  transform.ApplyPerspectiveDepth(10.0);
  transform.RotateAboutYAxis(45.0);
  EXPECT_FALSE(EmpiricallyPreserves2dAxisAlignment(transform));
  EXPECT_FALSE(transform.Preserves2dAxisAlignment());

  transform.MakeIdentity();
  transform.ApplyPerspectiveDepth(10.0);
  transform.RotateAboutZAxis(90.0);
  EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
  EXPECT_TRUE(transform.Preserves2dAxisAlignment());
}

TEST(XFormTest, To2dTranslation) {
  Vector2dF translation(3.f, 7.f);
  Transform transform;
  transform.Translate(translation.x(), translation.y() + 1);
  EXPECT_NE(translation.ToString(), transform.To2dTranslation().ToString());
  transform.MakeIdentity();
  transform.Translate(translation.x(), translation.y());
  EXPECT_EQ(translation.ToString(), transform.To2dTranslation().ToString());
}

TEST(XFormTest, TransformRect) {
  Transform translation;
  translation.Translate(3.f, 7.f);
  RectF rect(1.f, 2.f, 3.f, 4.f);
  RectF expected(4.f, 9.f, 3.f, 4.f);
  translation.TransformRect(&rect);
  EXPECT_EQ(expected.ToString(), rect.ToString());
}

TEST(XFormTest, TransformRectReverse) {
  Transform translation;
  translation.Translate(3.f, 7.f);
  RectF rect(1.f, 2.f, 3.f, 4.f);
  RectF expected(-2.f, -5.f, 3.f, 4.f);
  EXPECT_TRUE(translation.TransformRectReverse(&rect));
  EXPECT_EQ(expected.ToString(), rect.ToString());

  Transform singular;
  singular.Scale3d(0.f, 0.f, 0.f);
  EXPECT_FALSE(singular.TransformRectReverse(&rect));
}

TEST(XFormTest, TransformBox) {
  Transform translation;
  translation.Translate3d(3.f, 7.f, 6.f);
  BoxF box(1.f, 2.f, 3.f, 4.f, 5.f, 6.f);
  BoxF expected(4.f, 9.f, 9.f, 4.f, 5.f, 6.f);
  translation.TransformBox(&box);
  EXPECT_EQ(expected.ToString(), box.ToString());
}

TEST(XFormTest, TransformBoxReverse) {
  Transform translation;
  translation.Translate3d(3.f, 7.f, 6.f);
  BoxF box(1.f, 2.f, 3.f, 4.f, 5.f, 6.f);
  BoxF expected(-2.f, -5.f, -3.f, 4.f, 5.f, 6.f);
  EXPECT_TRUE(translation.TransformBoxReverse(&box));
  EXPECT_EQ(expected.ToString(), box.ToString());

  Transform singular;
  singular.Scale3d(0.f, 0.f, 0.f);
  EXPECT_FALSE(singular.TransformBoxReverse(&box));
}

}  // namespace

}  // namespace gfx

/* [<][>][^][v][top][bottom][index][help] */