#include "ui/gfx/geometry/transform.h"
#include <array>
#include <ostream>
#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/notreached.h"
#include "base/numerics/angle_conversions.h"
#include "base/strings/stringprintf.h"
#include "ui/gfx/geometry/axis_transform2d.h"
#include "ui/gfx/geometry/box_f.h"
#include "ui/gfx/geometry/clamp_float_geometry.h"
#include "ui/gfx/geometry/decomposed_transform.h"
#include "ui/gfx/geometry/double4.h"
#include "ui/gfx/geometry/point3_f.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/quad_f.h"
#include "ui/gfx/geometry/quaternion.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/sin_cos_degrees.h"
#include "ui/gfx/geometry/transform_util.h"
#include "ui/gfx/geometry/vector3d_f.h"
namespace gfx {
namespace {
const double kEpsilon = std::numeric_limits<float>::epsilon();
double TanDegrees(double degrees) {
return std::tan(base::DegToRad(degrees));
}
inline bool ApproximatelyZero(double x, double tolerance) {
return std::abs(x) <= tolerance;
}
inline bool ApproximatelyOne(double x, double tolerance) {
return std::abs(x - 1) <= tolerance;
}
Matrix44 AxisTransform2dToMatrix44(const AxisTransform2d& axis_2d) {
return Matrix44(axis_2d.scale().x(), 0, 0, 0,
0, axis_2d.scale().y(), 0, 0,
0, 0, 1, 0,
axis_2d.translation().x(), axis_2d.translation().y(), 0, 1);
}
template <typename T>
void AxisTransform2dToColMajor(const AxisTransform2d& axis_2d,
base::span<T, 16> a) {
a[0] = axis_2d.scale().x();
a[5] = axis_2d.scale().y();
a[12] = axis_2d.translation().x();
a[13] = axis_2d.translation().y();
a[1] = a[2] = a[3] = a[4] = a[6] = a[7] = a[8] = a[9] = a[11] = a[14] = 0;
a[10] = a[15] = 1;
}
}
Transform::Transform(const Quaternion& q)
: Transform(
1.0 - 2.0 * (q.y() * q.y() + q.z() * q.z()),
2.0 * (q.x() * q.y() + q.z() * q.w()),
2.0 * (q.x() * q.z() - q.y() * q.w()),
0,
2.0 * (q.x() * q.y() - q.z() * q.w()),
1.0 - 2.0 * (q.x() * q.x() + q.z() * q.z()),
2.0 * (q.y() * q.z() + q.x() * q.w()),
0,
2.0 * (q.x() * q.z() + q.y() * q.w()),
2.0 * (q.y() * q.z() - q.x() * q.w()),
1.0 - 2.0 * (q.x() * q.x() + q.y() * q.y()),
0,
0, 0, 0, 1) {}
Matrix44 Transform::GetFullMatrix() const {
if (!full_matrix_) [[likely]] {
return AxisTransform2dToMatrix44(axis_2d_);
}
return matrix_;
}
Matrix44& Transform::EnsureFullMatrix() {
if (!full_matrix_) [[likely]] {
full_matrix_ = true;
matrix_ = AxisTransform2dToMatrix44(axis_2d_);
}
return matrix_;
}
Transform Transform::ColMajor(base::span<const double, 16> a) {
return Transform(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9],
a[10], a[11], a[12], a[13], a[14], a[15]);
}
Transform Transform::ColMajorF(base::span<const float, 16> a) {
if (AllTrue(Float4{a[1], a[2], a[3], a[4]} == Float4{0, 0, 0, 0} &
Float4{a[6], a[7], a[8], a[9]} == Float4{0, 0, 0, 0} &
Float4{a[10], a[11], a[14], a[15]} == Float4{1, 0, 0, 1})) {
return Transform(a[0], a[5], a[12], a[13]);
}
return Transform(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9],
a[10], a[11], a[12], a[13], a[14], a[15]);
}
void Transform::GetColMajor(base::span<double, 16> a) const {
if (!full_matrix_) [[likely]] {
AxisTransform2dToColMajor(axis_2d_, a);
} else {
matrix_.GetColMajor(a);
}
}
void Transform::GetColMajorF(base::span<float, 16> a) const {
if (!full_matrix_) [[likely]] {
AxisTransform2dToColMajor(axis_2d_, a);
} else {
matrix_.GetColMajorF(a);
}
}
void Transform::RotateAboutXAxis(double degrees) {
SinCos sin_cos = SinCosDegrees(degrees);
if (sin_cos.IsZeroAngle())
return;
EnsureFullMatrix().RotateAboutXAxisSinCos(sin_cos.sin, sin_cos.cos);
}
void Transform::RotateAboutYAxis(double degrees) {
SinCos sin_cos = SinCosDegrees(degrees);
if (sin_cos.IsZeroAngle())
return;
EnsureFullMatrix().RotateAboutYAxisSinCos(sin_cos.sin, sin_cos.cos);
}
void Transform::RotateAboutZAxis(double degrees) {
SinCos sin_cos = SinCosDegrees(degrees);
if (sin_cos.IsZeroAngle())
return;
EnsureFullMatrix().RotateAboutZAxisSinCos(sin_cos.sin, sin_cos.cos);
}
void Transform::RotateAbout(double x, double y, double z, double degrees) {
SinCos sin_cos = SinCosDegrees(degrees);
if (sin_cos.IsZeroAngle())
return;
double square_length = x * x + y * y + z * z;
if (square_length == 0)
return;
if (square_length != 1) {
double scale = 1.0 / sqrt(square_length);
x *= scale;
y *= scale;
z *= scale;
}
EnsureFullMatrix().RotateUnitSinCos(x, y, z, sin_cos.sin, sin_cos.cos);
}
void Transform::RotateAbout(const Vector3dF& axis, double degrees) {
RotateAbout(axis.x(), axis.y(), axis.z(), degrees);
}
double Transform::Determinant() const {
if (!full_matrix_) [[likely]] {
return axis_2d_.Determinant();
}
return matrix_.Determinant();
}
void Transform::Scale(float x, float y) {
if (!full_matrix_) [[likely]] {
axis_2d_.PreScale(Vector2dF(x, y));
} else {
matrix_.PreScale(x, y);
}
}
void Transform::PostScale(float x, float y) {
if (!full_matrix_) [[likely]] {
axis_2d_.PostScale(Vector2dF(x, y));
} else {
matrix_.PostScale(x, y);
}
}
void Transform::Scale3d(float x, float y, float z) {
if (z == 1)
Scale(x, y);
else
EnsureFullMatrix().PreScale3d(x, y, z);
}
void Transform::PostScale3d(float x, float y, float z) {
if (z == 1)
PostScale(x, y);
else
EnsureFullMatrix().PostScale3d(x, y, z);
}
void Transform::Translate(const Vector2dF& offset) {
Translate(offset.x(), offset.y());
}
void Transform::Translate(float x, float y) {
if (!full_matrix_) [[likely]] {
axis_2d_.PreTranslate(Vector2dF(x, y));
} else {
matrix_.PreTranslate(x, y);
}
}
void Transform::PostTranslate(const Vector2dF& offset) {
PostTranslate(offset.x(), offset.y());
}
void Transform::PostTranslate(float x, float y) {
if (!full_matrix_) [[likely]] {
axis_2d_.PostTranslate(Vector2dF(x, y));
} else {
matrix_.PostTranslate(x, y);
}
}
void Transform::PostTranslate3d(const Vector3dF& offset) {
PostTranslate3d(offset.x(), offset.y(), offset.z());
}
void Transform::PostTranslate3d(float x, float y, float z) {
if (z == 0)
PostTranslate(x, y);
else
EnsureFullMatrix().PostTranslate3d(x, y, z);
}
void Transform::Translate3d(const Vector3dF& offset) {
Translate3d(offset.x(), offset.y(), offset.z());
}
void Transform::Translate3d(float x, float y, float z) {
if (z == 0)
Translate(x, y);
else
EnsureFullMatrix().PreTranslate3d(x, y, z);
}
void Transform::Skew(double degrees_x, double degrees_y) {
if (!degrees_x && !degrees_y)
return;
EnsureFullMatrix().Skew(TanDegrees(degrees_x), TanDegrees(degrees_y));
}
void Transform::ApplyPerspectiveDepth(double depth) {
if (depth == 0)
return;
EnsureFullMatrix().ApplyPerspectiveDepth(depth);
}
void Transform::PreConcat(const Transform& transform) {
if (!transform.full_matrix_) [[likely]] {
PreConcat(transform.axis_2d_);
} else if (!full_matrix_) [[likely]] {
AxisTransform2d self = axis_2d_;
*this = transform;
PostConcat(self);
} else {
matrix_.PreConcat(transform.matrix_);
}
}
void Transform::PostConcat(const Transform& transform) {
if (!transform.full_matrix_) [[likely]] {
PostConcat(transform.axis_2d_);
} else if (!full_matrix_) [[likely]] {
AxisTransform2d self = axis_2d_;
*this = transform;
PreConcat(self);
} else {
matrix_.PostConcat(transform.matrix_);
}
}
Transform Transform::operator*(const Transform& transform) const {
if (!transform.full_matrix_) [[likely]] {
Transform result = *this;
result.PreConcat(transform.axis_2d_);
return result;
}
if (!full_matrix_) [[likely]] {
Transform result = transform;
result.PostConcat(axis_2d_);
return result;
}
Transform result(Matrix44::kUninitialized);
result.matrix_.SetConcat(matrix_, transform.matrix_);
return result;
}
void Transform::PreConcat(const AxisTransform2d& transform) {
Translate(transform.translation());
Scale(transform.scale().x(), transform.scale().y());
}
void Transform::PostConcat(const AxisTransform2d& transform) {
PostScale(transform.scale().x(), transform.scale().y());
PostTranslate(transform.translation());
}
bool Transform::IsApproximatelyIdentityOrTranslation(double tolerance) const {
DCHECK_GE(tolerance, 0);
if (!full_matrix_) [[likely]] {
return ApproximatelyOne(axis_2d_.scale().x(), tolerance) &&
ApproximatelyOne(axis_2d_.scale().y(), tolerance);
}
if (!ApproximatelyOne(matrix_.rc(0, 0), tolerance) ||
!ApproximatelyZero(matrix_.rc(1, 0), tolerance) ||
!ApproximatelyZero(matrix_.rc(2, 0), tolerance) ||
!ApproximatelyZero(matrix_.rc(0, 1), tolerance) ||
!ApproximatelyOne(matrix_.rc(1, 1), tolerance) ||
!ApproximatelyZero(matrix_.rc(2, 1), tolerance) ||
!ApproximatelyZero(matrix_.rc(0, 2), tolerance) ||
!ApproximatelyZero(matrix_.rc(1, 2), tolerance) ||
!ApproximatelyOne(matrix_.rc(2, 2), tolerance)) {
return false;
}
const double perspective_tolerance = std::min(kEpsilon, tolerance);
return ApproximatelyZero(matrix_.rc(3, 0), perspective_tolerance) &&
ApproximatelyZero(matrix_.rc(3, 1), perspective_tolerance) &&
ApproximatelyZero(matrix_.rc(3, 2), perspective_tolerance) &&
ApproximatelyOne(matrix_.rc(3, 3), perspective_tolerance);
}
bool Transform::IsApproximatelyIdentityOrIntegerTranslation(
double tolerance) const {
if (!IsApproximatelyIdentityOrTranslation(tolerance))
return false;
if (!full_matrix_) [[likely]] {
for (float t : {axis_2d_.translation().x(), axis_2d_.translation().y()}) {
if (!base::IsValueInRangeForNumericType<int>(t) ||
std::abs(std::round(t) - t) > tolerance)
return false;
}
return true;
}
for (double t : {matrix_.rc(0, 3), matrix_.rc(1, 3), matrix_.rc(2, 3)}) {
if (!base::IsValueInRangeForNumericType<int>(t) ||
std::abs(std::round(t) - t) > tolerance)
return false;
}
return true;
}
bool Transform::Is2dProportionalUpscaleAndOr2dTranslation() const {
if (!full_matrix_) [[likely]] {
return axis_2d_.scale().x() >= 1 &&
axis_2d_.scale().x() == axis_2d_.scale().y();
}
return matrix_.IsScaleOrTranslation() &&
matrix_.rc(0, 0) >= 1 && matrix_.rc(1, 1) == matrix_.rc(0, 0) &&
matrix_.rc(2, 2) == 1 && matrix_.rc(2, 3) == 0;
}
bool Transform::IsIdentityOrIntegerTranslation() const {
if (!IsIdentityOrTranslation())
return false;
if (!full_matrix_) [[likely]] {
for (float t : {axis_2d_.translation().x(), axis_2d_.translation().y()}) {
if (!base::IsValueInRangeForNumericType<int>(t) ||
static_cast<int>(t) != t) {
return false;
}
}
return true;
}
for (double t : {matrix_.rc(0, 3), matrix_.rc(1, 3), matrix_.rc(2, 3)}) {
if (!base::IsValueInRangeForNumericType<int>(t) || static_cast<int>(t) != t)
return false;
}
return true;
}
bool Transform::IsIdentityOrInteger2dTranslation() const {
return IsIdentityOrIntegerTranslation() && rc(2, 3) == 0;
}
bool Transform::Creates3d() const {
if (!full_matrix_) [[likely]] {
return false;
}
return matrix_.rc(2, 0) != 0 || matrix_.rc(2, 1) != 0 ||
matrix_.rc(2, 3) != 0;
}
bool Transform::IsBackFaceVisible() const {
if (!full_matrix_) [[likely]] {
return false;
}
double determinant = matrix_.Determinant();
if (determinant == 0)
return false;
double cofactor_part_1 =
matrix_.rc(0, 0) * matrix_.rc(1, 1) * matrix_.rc(3, 3);
double cofactor_part_2 =
matrix_.rc(0, 1) * matrix_.rc(1, 3) * matrix_.rc(3, 0);
double cofactor_part_3 =
matrix_.rc(0, 3) * matrix_.rc(1, 0) * matrix_.rc(3, 1);
double cofactor_part_4 =
matrix_.rc(0, 0) * matrix_.rc(1, 3) * matrix_.rc(3, 1);
double cofactor_part_5 =
matrix_.rc(0, 1) * matrix_.rc(1, 0) * matrix_.rc(3, 3);
double cofactor_part_6 =
matrix_.rc(0, 3) * matrix_.rc(1, 1) * matrix_.rc(3, 0);
double cofactor33 = cofactor_part_1 + cofactor_part_2 + cofactor_part_3 -
cofactor_part_4 - cofactor_part_5 - cofactor_part_6;
return cofactor33 * determinant < -kEpsilon;
}
bool Transform::GetInverse(Transform* transform) const {
if (!full_matrix_) [[likely]] {
transform->full_matrix_ = false;
if (axis_2d_.IsInvertible()) {
transform->axis_2d_ = axis_2d_;
transform->axis_2d_.Invert();
return true;
}
transform->axis_2d_ = AxisTransform2d();
return false;
}
if (matrix_.GetInverse(transform->matrix_)) {
transform->full_matrix_ = true;
return true;
}
transform->MakeIdentity();
return false;
}
Transform Transform::GetCheckedInverse() const {
Transform inverse;
if (!GetInverse(&inverse))
DUMP_WILL_BE_NOTREACHED() << ToString() << " is not invertible";
return inverse;
}
Transform Transform::InverseOrIdentity() const {
Transform inverse;
bool invertible = GetInverse(&inverse);
DCHECK(invertible || inverse.IsIdentity());
return inverse;
}
bool Transform::Preserves2dAffine() const {
if (!full_matrix_) [[likely]] {
return true;
}
const bool is_flat_ignore_z = gfx::AllTrue(gfx::Double4{
matrix_.rc(2, 0),
matrix_.rc(2, 1),
0,
matrix_.rc(2, 3),
} == gfx::Double4{0, 0, 0, 0});
const bool has_no_perspective_ignore_z =
gfx::AllTrue(gfx::Double4{
matrix_.rc(3, 0),
matrix_.rc(3, 1),
0,
matrix_.rc(3, 3),
} == gfx::Double4{0, 0, 0, 1});
if (is_flat_ignore_z && has_no_perspective_ignore_z) {
return true;
}
return false;
}
bool Transform::Preserves2dAxisAlignment() const {
if (!full_matrix_) [[likely]] {
return true;
}
bool has_x_or_y_perspective = matrix_.rc(3, 0) != 0 || matrix_.rc(3, 1) != 0;
int num_non_zero_in_row_0 = 0;
int num_non_zero_in_row_1 = 0;
int num_non_zero_in_col_0 = 0;
int num_non_zero_in_col_1 = 0;
if (std::abs(matrix_.rc(0, 0)) > kEpsilon) {
num_non_zero_in_row_0++;
num_non_zero_in_col_0++;
}
if (std::abs(matrix_.rc(0, 1)) > kEpsilon) {
num_non_zero_in_row_0++;
num_non_zero_in_col_1++;
}
if (std::abs(matrix_.rc(1, 0)) > kEpsilon) {
num_non_zero_in_row_1++;
num_non_zero_in_col_0++;
}
if (std::abs(matrix_.rc(1, 1)) > kEpsilon) {
num_non_zero_in_row_1++;
num_non_zero_in_col_1++;
}
return num_non_zero_in_row_0 <= 1 && num_non_zero_in_row_1 <= 1 &&
num_non_zero_in_col_0 <= 1 && num_non_zero_in_col_1 <= 1 &&
!has_x_or_y_perspective;
}
bool Transform::NonDegeneratePreserves2dAxisAlignment() const {
if (!full_matrix_) [[likely]] {
return axis_2d_.scale().x() > kEpsilon && axis_2d_.scale().y() > kEpsilon;
}
bool has_x_or_y_perspective = matrix_.rc(3, 0) != 0 || matrix_.rc(3, 1) != 0;
bool positive_w_perspective = matrix_.rc(3, 3) > kEpsilon;
bool have_0_0 = std::abs(matrix_.rc(0, 0)) > kEpsilon;
bool have_0_1 = std::abs(matrix_.rc(0, 1)) > kEpsilon;
bool have_1_0 = std::abs(matrix_.rc(1, 0)) > kEpsilon;
bool have_1_1 = std::abs(matrix_.rc(1, 1)) > kEpsilon;
return have_0_0 == have_1_1 && have_0_1 == have_1_0 && have_0_0 != have_0_1 &&
!has_x_or_y_perspective && positive_w_perspective;
}
void Transform::Transpose() {
if (!IsScale2d())
EnsureFullMatrix().Transpose();
}
void Transform::ApplyTransformOrigin(float x, float y, float z) {
PostTranslate3d(x, y, z);
Translate3d(-x, -y, -z);
}
void Transform::Zoom(float zoom_factor) {
if (!full_matrix_) [[likely]] {
axis_2d_.Zoom(zoom_factor);
} else {
matrix_.Zoom(zoom_factor);
}
}
void Transform::Flatten() {
if (full_matrix_) [[unlikely]] {
matrix_.Flatten();
}
DCHECK(IsFlat());
}
bool Transform::IsFlat() const {
if (!full_matrix_) [[likely]] {
return true;
}
return matrix_.IsFlat();
}
bool Transform::Is2dTransform() const {
if (!full_matrix_) [[likely]] {
return true;
}
return matrix_.Is2dTransform();
}
Vector2dF Transform::To2dTranslation() const {
if (!full_matrix_) [[likely]] {
return Vector2dF(ClampFloatGeometry(axis_2d_.translation().x()),
ClampFloatGeometry(axis_2d_.translation().y()));
}
return Vector2dF(ClampFloatGeometry(matrix_.rc(0, 3)),
ClampFloatGeometry(matrix_.rc(1, 3)));
}
Vector3dF Transform::To3dTranslation() const {
if (!full_matrix_) [[likely]] {
return Vector3dF(ClampFloatGeometry(axis_2d_.translation().x()),
ClampFloatGeometry(axis_2d_.translation().y()), 0);
}
return Vector3dF(ClampFloatGeometry(matrix_.rc(0, 3)),
ClampFloatGeometry(matrix_.rc(1, 3)),
ClampFloatGeometry(matrix_.rc(2, 3)));
}
Vector2dF Transform::To2dScale() const {
if (!full_matrix_) [[likely]] {
return Vector2dF(ClampFloatGeometry(axis_2d_.scale().x()),
ClampFloatGeometry(axis_2d_.scale().y()));
}
return Vector2dF(ClampFloatGeometry(matrix_.rc(0, 0)),
ClampFloatGeometry(matrix_.rc(1, 1)));
}
Point Transform::MapPoint(const Point& point) const {
return gfx::ToRoundedPoint(MapPoint(gfx::PointF(point)));
}
PointF Transform::MapPoint(const PointF& point) const {
if (!full_matrix_) [[likely]] {
return axis_2d_.MapPoint(point);
}
return MapPointInternal(matrix_, point);
}
Point3F Transform::MapPoint(const Point3F& point) const {
if (!full_matrix_) [[likely]] {
PointF result = axis_2d_.MapPoint(point.AsPointF());
return Point3F(result.x(), result.y(), ClampFloatGeometry(point.z()));
}
return MapPointInternal(matrix_, point);
}
Vector3dF Transform::MapVector(const Vector3dF& vector) const {
if (!full_matrix_) [[likely]] {
return Vector3dF(ClampFloatGeometry(vector.x() * axis_2d_.scale().x()),
ClampFloatGeometry(vector.y() * axis_2d_.scale().y()),
ClampFloatGeometry(vector.z()));
}
double p[4] = {vector.x(), vector.y(), vector.z(), 0};
matrix_.MapVector4(p);
return Vector3dF(ClampFloatGeometry(p[0]), ClampFloatGeometry(p[1]),
ClampFloatGeometry(p[2]));
}
void Transform::TransformVector4(base::span<float, 4> vector) const {
DCHECK(!vector.empty());
if (!full_matrix_) [[likely]] {
vector[0] = vector[0] * axis_2d_.scale().x() +
vector[3] * axis_2d_.translation().x();
vector[1] = vector[1] * axis_2d_.scale().y() +
vector[3] * axis_2d_.translation().y();
for (int i = 0; i < 4; i++)
vector[i] = ClampFloatGeometry(vector[i]);
} else {
std::array<double, 4> v = {vector[0], vector[1], vector[2], vector[3]};
matrix_.MapVector4(v.data());
for (int i = 0; i < 4; i++)
vector[i] = ClampFloatGeometry(v[i]);
}
}
std::optional<PointF> Transform::InverseMapPoint(const PointF& point) const {
if (!full_matrix_) [[likely]] {
if (!axis_2d_.IsInvertible())
return std::nullopt;
return axis_2d_.InverseMapPoint(point);
}
Matrix44 inverse(Matrix44::kUninitialized);
if (!matrix_.GetInverse(inverse))
return std::nullopt;
return MapPointInternal(inverse, point);
}
std::optional<Point> Transform::InverseMapPoint(const Point& point) const {
if (std::optional<PointF> point_f = InverseMapPoint(PointF(point))) {
return ToRoundedPoint(*point_f);
}
return std::nullopt;
}
std::optional<Point3F> Transform::InverseMapPoint(const Point3F& point) const {
if (!full_matrix_) [[likely]] {
if (!axis_2d_.IsInvertible())
return std::nullopt;
PointF result = axis_2d_.InverseMapPoint(point.AsPointF());
return Point3F(result.x(), result.y(), ClampFloatGeometry(point.z()));
}
Matrix44 inverse(Matrix44::kUninitialized);
if (!matrix_.GetInverse(inverse))
return std::nullopt;
return std::make_optional(MapPointInternal(inverse, point));
}
RectF Transform::MapRect(const RectF& rect) const {
if (IsIdentity())
return rect;
if (!full_matrix_) [[likely]] {
if (axis_2d_.scale().x() >= 0 && axis_2d_.scale().y() >= 0) {
return axis_2d_.MapRect(rect);
}
}
return MapQuad(QuadF(rect)).BoundingBox();
}
Rect Transform::MapRect(const Rect& rect) const {
if (IsIdentity())
return rect;
return ToEnclosingRect(MapRect(RectF(rect)));
}
std::optional<RectF> Transform::InverseMapRect(const RectF& rect) const {
if (IsIdentity())
return rect;
if (!full_matrix_) [[likely]] {
if (!axis_2d_.IsInvertible())
return std::nullopt;
if (axis_2d_.scale().x() > 0 && axis_2d_.scale().y() > 0)
return axis_2d_.InverseMapRect(rect);
}
Transform inverse;
if (!GetInverse(&inverse))
return std::nullopt;
return inverse.MapQuad(QuadF(rect)).BoundingBox();
}
std::optional<Rect> Transform::InverseMapRect(const Rect& rect) const {
if (IsIdentity())
return rect;
if (std::optional<RectF> mapped = InverseMapRect(RectF(rect))) {
return ToEnclosingRect(mapped.value());
}
return std::nullopt;
}
BoxF Transform::MapBox(const BoxF& box) const {
BoxF bounds;
bool first_point = true;
for (int corner = 0; corner < 8; ++corner) {
gfx::Point3F point = box.origin();
point += gfx::Vector3dF(corner & 1 ? box.width() : 0.f,
corner & 2 ? box.height() : 0.f,
corner & 4 ? box.depth() : 0.f);
point = MapPoint(point);
if (first_point) {
bounds.set_origin(point);
first_point = false;
} else {
bounds.ExpandTo(point);
}
}
return bounds;
}
QuadF Transform::MapQuad(const QuadF& quad) const {
return QuadF(MapPoint(quad.p1()), MapPoint(quad.p2()), MapPoint(quad.p3()),
MapPoint(quad.p4()));
}
PointF Transform::ProjectPoint(const PointF& point, bool* clamped) const {
if (clamped)
*clamped = false;
if (!full_matrix_) [[likely]] {
return axis_2d_.MapPoint(point);
}
if (!std::isnormal(matrix_.rc(2, 2))) {
if (clamped)
*clamped = true;
return gfx::PointF();
}
double x = point.x();
double y = point.y();
double z = -(matrix_.rc(2, 0) * x + matrix_.rc(2, 1) * y + matrix_.rc(2, 3)) /
matrix_.rc(2, 2);
if (!std::isfinite(z)) {
if (clamped)
*clamped = true;
return gfx::PointF();
}
double v[4] = {x, y, z, 1};
matrix_.MapVector4(v);
if (v[3] <= 0) {
constexpr double kBigNumber = 1 << (std::numeric_limits<float>::digits - 1);
if (clamped)
*clamped = true;
return PointF(std::copysign(kBigNumber, v[0]),
std::copysign(kBigNumber, v[1]));
}
if (v[3] != 1) {
v[0] /= v[3];
v[1] /= v[3];
}
return PointF(ClampFloatGeometry(v[0]), ClampFloatGeometry(v[1]));
}
QuadF Transform::ProjectQuad(const QuadF& quad) const {
bool clamped1 = false;
bool clamped2 = false;
bool clamped3 = false;
bool clamped4 = false;
QuadF projected_quad(
ProjectPoint(quad.p1(), &clamped1), ProjectPoint(quad.p2(), &clamped2),
ProjectPoint(quad.p3(), &clamped3), ProjectPoint(quad.p4(), &clamped4));
if (clamped1 && clamped2 && clamped3 && clamped4)
return QuadF();
return projected_quad;
}
std::optional<DecomposedTransform> Transform::Decompose() const {
if (!full_matrix_) [[likely]] {
if (!axis_2d_.IsInvertible())
return std::nullopt;
return axis_2d_.Decompose();
}
return matrix_.Decompose();
}
Transform Transform::Compose(const DecomposedTransform& decomp) {
Transform result;
for (int i = 0; i < 3; i++) {
if (decomp.perspective[i] != 0)
result.set_rc(3, i, decomp.perspective[i]);
}
if (decomp.perspective[3] != 1)
result.set_rc(3, 3, decomp.perspective[3]);
result.Translate3d(decomp.translate[0], decomp.translate[1],
decomp.translate[2]);
result.PreConcat(Transform(decomp.quaternion));
if (decomp.skew[0] || decomp.skew[1] || decomp.skew[2])
result.EnsureFullMatrix().ApplyDecomposedSkews(decomp.skew);
result.Scale3d(decomp.scale[0], decomp.scale[1], decomp.scale[2]);
return result;
}
bool Transform::Blend(const Transform& from, double progress) {
std::optional<DecomposedTransform> to_decomp = Decompose();
if (!to_decomp)
return false;
std::optional<DecomposedTransform> from_decomp = from.Decompose();
if (!from_decomp)
return false;
*to_decomp = BlendDecomposedTransforms(*to_decomp, *from_decomp, progress);
*this = Compose(*to_decomp);
return true;
}
bool Transform::Accumulate(const Transform& other) {
std::optional<DecomposedTransform> this_decomp = Decompose();
if (!this_decomp)
return false;
std::optional<DecomposedTransform> other_decomp = other.Decompose();
if (!other_decomp)
return false;
*this_decomp = AccumulateDecomposedTransforms(*this_decomp, *other_decomp);
*this = Compose(*this_decomp);
return true;
}
void Transform::Round2dTranslationComponents() {
if (!full_matrix_) [[likely]] {
axis_2d_ = AxisTransform2d::FromScaleAndTranslation(
axis_2d_.scale(), Vector2dF(std::round(axis_2d_.translation().x()),
std::round(axis_2d_.translation().y())));
} else {
matrix_.set_rc(0, 3, std::round(matrix_.rc(0, 3)));
matrix_.set_rc(1, 3, std::round(matrix_.rc(1, 3)));
}
}
void Transform::Floor2dTranslationComponents() {
if (!full_matrix_) [[likely]] {
axis_2d_ = AxisTransform2d::FromScaleAndTranslation(
axis_2d_.scale(), Vector2dF(std::floor(axis_2d_.translation().x()),
std::floor(axis_2d_.translation().y())));
} else {
matrix_.set_rc(0, 3, std::floor(matrix_.rc(0, 3)));
matrix_.set_rc(1, 3, std::floor(matrix_.rc(1, 3)));
}
}
void Transform::RoundToIdentityOrIntegerTranslation() {
if (!full_matrix_) [[likely]] {
axis_2d_ = AxisTransform2d::FromScaleAndTranslation(
Vector2dF(1, 1), Vector2dF(std::round(axis_2d_.translation().x()),
std::round(axis_2d_.translation().y())));
} else {
matrix_ =
Matrix44(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
std::round(matrix_.rc(0, 3)),
std::round(matrix_.rc(1, 3)), std::round(matrix_.rc(2, 3)), 1);
}
}
PointF Transform::MapPointInternal(const Matrix44& matrix,
const PointF& point) const {
DCHECK(full_matrix_);
double p[2] = {point.x(), point.y()};
double w = matrix.MapVector2(p);
if (w != 1.0 && std::isnormal(w)) {
double w_inverse = 1.0 / w;
return PointF(ClampFloatGeometry(p[0] * w_inverse),
ClampFloatGeometry(p[1] * w_inverse));
}
return PointF(ClampFloatGeometry(p[0]), ClampFloatGeometry(p[1]));
}
Point3F Transform::MapPointInternal(const Matrix44& matrix,
const Point3F& point) const {
DCHECK(full_matrix_);
double p[4] = {point.x(), point.y(), point.z(), 1};
matrix.MapVector4(p);
if (p[3] != 1.0 && std::isnormal(p[3])) {
double w_inverse = 1.0 / p[3];
return Point3F(ClampFloatGeometry(p[0] * w_inverse),
ClampFloatGeometry(p[1] * w_inverse),
ClampFloatGeometry(p[2] * w_inverse));
}
return Point3F(ClampFloatGeometry(p[0]), ClampFloatGeometry(p[1]),
ClampFloatGeometry(p[2]));
}
bool Transform::ApproximatelyEqual(const gfx::Transform& transform,
float abs_translation_tolerance,
float abs_other_tolerance,
float rel_scale_tolerance) const {
if (*this == transform)
return true;
if (abs_translation_tolerance == 0 && abs_other_tolerance == 0)
return false;
auto approximately_equal = [abs_other_tolerance](float a, float b) {
return std::abs(a - b) <= abs_other_tolerance;
};
auto translation_approximately_equal = [abs_translation_tolerance](float a,
float b) {
return std::abs(a - b) <= abs_translation_tolerance;
};
auto scale_approximately_equal = [abs_other_tolerance, rel_scale_tolerance](
float a, float b) {
float diff = std::abs(a - b);
return diff <= abs_other_tolerance &&
(rel_scale_tolerance == 0 ||
diff <= (std::abs(a) + std::abs(b)) * rel_scale_tolerance);
};
if (!full_matrix_ && !transform.full_matrix_) [[likely]] {
return scale_approximately_equal(axis_2d_.scale().x(),
transform.axis_2d_.scale().x()) &&
scale_approximately_equal(axis_2d_.scale().y(),
transform.axis_2d_.scale().y()) &&
translation_approximately_equal(
axis_2d_.translation().x(),
transform.axis_2d_.translation().x()) &&
translation_approximately_equal(
axis_2d_.translation().y(),
transform.axis_2d_.translation().y());
}
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
float x = rc(row, col);
float y = transform.rc(row, col);
if (row < 3 && col == 3) {
if (!translation_approximately_equal(x, y))
return false;
} else if (row < 3 && col == row) {
if (!scale_approximately_equal(x, y))
return false;
} else if (!approximately_equal(x, y)) {
return false;
}
}
}
return true;
}
std::string Transform::ToString() const {
return base::StringPrintf(
"[ %lg %lg %lg %lg\n"
" %lg %lg %lg %lg\n"
" %lg %lg %lg %lg\n"
" %lg %lg %lg %lg ]\n",
rc(0, 0), rc(0, 1), rc(0, 2), rc(0, 3), rc(1, 0), rc(1, 1), rc(1, 2),
rc(1, 3), rc(2, 0), rc(2, 1), rc(2, 2), rc(2, 3), rc(3, 0), rc(3, 1),
rc(3, 2), rc(3, 3));
}
std::string Transform::ToDecomposedString() const {
std::optional<gfx::DecomposedTransform> decomp = Decompose();
if (!decomp)
return ToString() + "(degenerate)";
if (IsIdentity())
return "identity";
if (IsIdentityOrTranslation()) {
return base::StringPrintf("translate: %lg,%lg,%lg", decomp->translate[0],
decomp->translate[1], decomp->translate[2]);
}
return decomp->ToString();
}
}