Skip to content

Commit

Permalink
Use gfx::Transform::Decompose/Blend/Compose/Accumulate for blink 2d/3d
Browse files Browse the repository at this point in the history
Add gfx::AccumulateDecomposedTransforms() which extracts the original
blink::Matrix3DTransformOperation::Accumulate().

Let blink::MatrixTransformOperation use the unified 2d/3d version
of interpolation methods.

There is a difference between gfx::Transform::Blend() and the original
blink::TransformationMatrix::Blend() when |this| or |from| can't be
decomposed:
- gfx::Transform::Blend() returns false and doesn't change |this|.
- The original blink::TransformationMatrix::Blend() automatically sets
  |this| to |from| if |progress| < 0.5.
Modified callers to use the gfx::Transform::Blend() convention and to
perform discrete interpolation as necessary.

Bug: 1359528
Change-Id: I320b3a469a8271140dc02ef82f6571642b989c44
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3979782
Owners-Override: Xianzhu Wang <wangxianzhu@chromium.org>
Commit-Queue: Xianzhu Wang <wangxianzhu@chromium.org>
Reviewed-by: Kevin Ellis <kevers@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1068969}
  • Loading branch information
wangxianzhu authored and Chromium LUCI CQ committed Nov 9, 2022
1 parent ea91f9e commit 91ca54c
Show file tree
Hide file tree
Showing 14 changed files with 129 additions and 311 deletions.
18 changes: 10 additions & 8 deletions third_party/blink/renderer/platform/transforms/affine_transform.cc
Original file line number Diff line number Diff line change
Expand Up @@ -241,19 +241,21 @@ String AffineTransform::ToString(bool as_matrix) const {
if (IsIdentity())
return "identity";

TransformationMatrix::Decomposed2dType decomposition;
if (!ToTransformationMatrix().Decompose2D(decomposition))
absl::optional<gfx::DecomposedTransform> decomp =
ToTransformationMatrix().Decompose();
if (!decomp)
return ToString(true) + " (degenerate)";

if (IsIdentityOrTranslation())
return String::Format("translation(%lg,%lg)", decomposition.translate_x,
decomposition.translate_y);
if (IsIdentityOrTranslation()) {
return String::Format("translation(%lg,%lg)", decomp->translate[0],
decomp->translate[1]);
}

double angle = Rad2deg(std::asin(decomp->quaternion.z())) * 2;
return String::Format(
"translation(%lg,%lg), scale(%lg,%lg), angle(%lgdeg), skewxy(%lg)",
decomposition.translate_x, decomposition.translate_y,
decomposition.scale_x, decomposition.scale_y,
Rad2deg(decomposition.angle), decomposition.skew_xy);
decomp->translate[0], decomp->translate[1], decomp->scale[0],
decomp->scale[1], angle, decomp->skew[0]);
}

std::ostream& operator<<(std::ostream& ostream,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ void InterpolatedTransformOperation::Apply(
from_.ApplyRemaining(border_box_size, starting_index_, from_transform);
to_.ApplyRemaining(border_box_size, starting_index_, to_transform);

to_transform.Blend(from_transform, progress_);
if (!to_transform.Blend(from_transform, progress_) && progress_ < 0.5)
to_transform = from_transform;
transform.PreConcat(to_transform);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,44 +38,10 @@ scoped_refptr<TransformOperation> Matrix3DTransformOperation::Accumulate(
DCHECK(other_op.IsSameType(*this));
const auto& other = To<Matrix3DTransformOperation>(other_op);

// If either matrix is non-invertible, fail and fallback to replace.
if (!matrix_.IsInvertible() || !other.matrix_.IsInvertible())
return nullptr;

// Similar to interpolation, accumulating 3D matrices is done by decomposing
// them, accumulating the individual functions, and then recomposing.

absl::optional<gfx::DecomposedTransform> from_decomp = matrix_.Decompose();
if (!from_decomp)
return nullptr;

absl::optional<gfx::DecomposedTransform> to_decomp =
other.matrix_.Decompose();
if (!to_decomp)
TransformationMatrix result = matrix_;
if (!result.Accumulate(other.matrix_))
return nullptr;

// Scale is accumulated using 1-based addition.
for (size_t i = 0; i < std::size(from_decomp->scale); i++)
from_decomp->scale[i] += to_decomp->scale[i] - 1;

// Skew can be added.
for (size_t i = 0; i < std::size(from_decomp->skew); i++)
from_decomp->skew[i] += to_decomp->skew[i];

// To accumulate quaternions, we multiply them. This is equivalent to 'adding'
// the rotations that they represent.
from_decomp->quaternion = from_decomp->quaternion * to_decomp->quaternion;

// Translate is a simple addition.
for (size_t i = 0; i < std::size(from_decomp->translate); i++)
from_decomp->translate[i] += to_decomp->translate[i];

// We sum the perspective components; note that w is 1-based.
for (size_t i = 0; i < std::size(from_decomp->perspective) - 1; i++)
from_decomp->perspective[i] += to_decomp->perspective[i];
from_decomp->perspective[3] += to_decomp->perspective[3] - 1;

TransformationMatrix result = gfx::Transform::Compose(*from_decomp);
return Matrix3DTransformOperation::Create(result);
}

Expand All @@ -85,25 +51,17 @@ scoped_refptr<TransformOperation> Matrix3DTransformOperation::Blend(
bool blend_to_identity) {
DCHECK(!from || CanBlendWith(*from));

// Convert the TransformOperations into matrices. Fail the blend operation
// if either of the matrices is non-invertible.
gfx::SizeF size;
TransformationMatrix from_t;
TransformationMatrix to_t;
if (from) {
from->Apply(from_t, size);
if (!from_t.IsInvertible())
return nullptr;
}

Apply(to_t, size);
if (!to_t.IsInvertible())
return nullptr;
if (from)
from_t = To<Matrix3DTransformOperation>(from)->matrix_;

TransformationMatrix to_t = matrix_;
if (blend_to_identity)
std::swap(from_t, to_t);

to_t.Blend(from_t, progress);
if (!to_t.Blend(from_t, progress))
return nullptr;

return Matrix3DTransformOperation::Create(to_t);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,10 @@ scoped_refptr<TransformOperation> MatrixTransformOperation::Accumulate(
DCHECK(other_op.IsSameType(*this));
const auto& other = To<MatrixTransformOperation>(other_op);

// Similar to interpolation, accumulating matrices is done by decomposing
// them, accumulating the individual functions, and then recomposing.

TransformationMatrix::Decomposed2dType from_decomp;
TransformationMatrix::Decomposed2dType to_decomp;
if (!other.matrix_.Decompose2D(from_decomp) ||
!matrix_.Decompose2D(to_decomp)) {
TransformationMatrix result = matrix_;
if (!result.Accumulate(other.matrix_))
return nullptr;
}

// For a 2D matrix, the components can just be naively summed, noting that
// scale uses 1-based addition.
from_decomp.scale_x += to_decomp.scale_x - 1;
from_decomp.scale_y += to_decomp.scale_y - 1;
from_decomp.skew_xy += to_decomp.skew_xy;
from_decomp.translate_x += to_decomp.translate_x;
from_decomp.translate_y += to_decomp.translate_y;
from_decomp.angle += to_decomp.angle;

TransformationMatrix result;
result.Recompose2D(from_decomp);
return MatrixTransformOperation::Create(result);
}

Expand All @@ -60,24 +43,17 @@ scoped_refptr<TransformOperation> MatrixTransformOperation::Blend(
bool blend_to_identity) {
DCHECK(!from || CanBlendWith(*from));

// convert the TransformOperations into matrices
if (!matrix_.IsInvertible())
return nullptr;

TransformationMatrix from_t;
if (from) {
const MatrixTransformOperation* m =
static_cast<const MatrixTransformOperation*>(from);
from_t = m->matrix_;
if (!from_t.IsInvertible())
return nullptr;
}
if (from)
from_t = To<MatrixTransformOperation>(from)->matrix_;

TransformationMatrix to_t = matrix_;
if (blend_to_identity)
std::swap(from_t, to_t);

to_t.Blend(from_t, progress);
if (!to_t.Blend(from_t, progress))
return nullptr;

return MatrixTransformOperation::Create(to_t);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,9 @@ TransformOperations::BlendRemainingByUsingMatrixInterpolation(
return nullptr;
}

to_transform.Blend(from_transform, progress);
if (!to_transform.Blend(from_transform, progress) && progress < 0.5)
to_transform = from_transform;

return Matrix3DTransformOperation::Create(to_transform);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,20 @@ TEST(TransformOperationsTest, NonCommutativeRotations) {
EXPECT_BOXF_EQ(bounds, expanded_bounds);
}

TEST(TransformOperationsTest, NonInvertibleBlendTest) {
TransformOperations from_ops;
TransformOperations to_ops;

from_ops.Operations().push_back(TranslateTransformOperation::Create(
Length::Fixed(5), Length::Fixed(-5), TransformOperation::kTranslate));
to_ops.Operations().push_back(
MatrixTransformOperation::Create(0, 0, 0, 0, 0, 0));

EXPECT_EQ(from_ops, to_ops.Blend(from_ops, 0.25));
EXPECT_EQ(to_ops, to_ops.Blend(from_ops, 0.5));
EXPECT_EQ(to_ops, to_ops.Blend(from_ops, 0.75));
}

TEST(TransformOperationsTest, AbsoluteSequenceBoundsTest) {
TransformOperations from_ops;
TransformOperations to_ops;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,177 +58,6 @@ AffineTransform TransformationMatrix::ToAffineTransform() const {
rc(1, 3));
}

static inline void BlendFloat(double& from, double to, double progress) {
if (from != to)
from = from + (to - from) * progress;
}

void TransformationMatrix::Blend(const TransformationMatrix& from,
double progress) {
if (gfx::Transform::Blend(from, progress))
return;
if (progress < 0.5)
*this = from;
}

void TransformationMatrix::Blend2D(const TransformationMatrix& from,
double progress) {
// Decompose into scale, rotate, translate and skew transforms.
Decomposed2dType from_decomp;
Decomposed2dType to_decomp;
if (!from.Decompose2D(from_decomp) || !Decompose2D(to_decomp)) {
if (progress < 0.5)
*this = from;
return;
}

// Take the shorter of the clockwise or counter-clockwise paths.
double rotation = abs(from_decomp.angle - to_decomp.angle);
DCHECK(rotation < 2 * M_PI);
if (rotation > M_PI) {
if (from_decomp.angle > to_decomp.angle) {
from_decomp.angle -= 2 * M_PI;
} else {
to_decomp.angle -= 2 * M_PI;
}
}

// Interpolate.
BlendFloat(from_decomp.scale_x, to_decomp.scale_x, progress);
BlendFloat(from_decomp.scale_y, to_decomp.scale_y, progress);
BlendFloat(from_decomp.skew_xy, to_decomp.skew_xy, progress);
BlendFloat(from_decomp.translate_x, to_decomp.translate_x, progress);
BlendFloat(from_decomp.translate_y, to_decomp.translate_y, progress);
BlendFloat(from_decomp.angle, to_decomp.angle, progress);

// Recompose.
Recompose2D(from_decomp);
}

// Decompose a 2D transformation matrix of the form:
// [m11 m21 0 m41]
// [m12 m22 0 m42]
// [ 0 0 1 0 ]
// [ 0 0 0 1 ]
//
// The decomposition is of the form:
// M = translate * rotate * skew * scale
// [1 0 0 Tx] [cos(R) -sin(R) 0 0] [1 K 0 0] [Sx 0 0 0]
// = [0 1 0 Ty] [sin(R) cos(R) 0 0] [0 1 0 0] [0 Sy 0 0]
// [0 0 1 0 ] [ 0 0 1 0] [0 0 1 0] [0 0 1 0]
// [0 0 0 1 ] [ 0 0 0 1] [0 0 0 1] [0 0 0 1]
//
bool TransformationMatrix::Decompose2D(Decomposed2dType& decomp) const {
if (!Is2dTransform()) {
LOG(ERROR) << "2-D decomposition cannot be performed on a 3-D transform.";
return false;
}

double m11 = rc(0, 0);
double m21 = rc(0, 1);
double m12 = rc(1, 0);
double m22 = rc(1, 1);

double determinant = m11 * m22 - m12 * m21;
// Test for matrix being singular.
if (determinant == 0) {
return false;
}

// Translation transform.
// [m11 m21 0 m41] [1 0 0 Tx] [m11 m21 0 0]
// [m12 m22 0 m42] = [0 1 0 Ty] [m12 m22 0 0]
// [ 0 0 1 0 ] [0 0 1 0 ] [ 0 0 1 0]
// [ 0 0 0 1 ] [0 0 0 1 ] [ 0 0 0 1]
decomp.translate_x = rc(0, 3);
decomp.translate_y = rc(1, 3);

// For the remainder of the decomposition process, we can focus on the upper
// 2x2 submatrix
// [m11 m21] = [cos(R) -sin(R)] [1 K] [Sx 0 ]
// [m12 m22] [sin(R) cos(R)] [0 1] [0 Sy]
// = [Sx*cos(R) Sy*(K*cos(R) - sin(R))]
// [Sx*sin(R) Sy*(K*sin(R) + cos(R))]

// Determine sign of the x and y scale.
decomp.scale_x = 1;
decomp.scale_y = 1;
if (determinant < 0) {
// If the determinant is negative, we need to flip either the x or y scale.
// Flipping both is equivalent to rotating by 180 degrees.
// Flip the axis with the minimum unit vector dot product.
if (m11 < m22) {
decomp.scale_x = -decomp.scale_x;
} else {
decomp.scale_y = -decomp.scale_y;
}
}

// X Scale.
// m11^2 + m12^2 = Sx^2*(cos^2(R) + sin^2(R)) = Sx^2.
// Sx = +/-sqrt(m11^2 + m22^2)
decomp.scale_x *= sqrt(m11 * m11 + m12 * m12);
m11 /= decomp.scale_x;
m12 /= decomp.scale_x;

// Post normalization, the submatrix is now of the form:
// [m11 m21] = [cos(R) Sy*(K*cos(R) - sin(R))]
// [m12 m22] [sin(R) Sy*(K*sin(R) + cos(R))]

// XY Shear.
// m11 * m21 + m12 * m22 = Sy*K*cos^2(R) - Sy*sin(R)*cos(R) +
// Sy*K*sin^2(R) + Sy*cos(R)*sin(R)
// = Sy*K
double scaledShear = m11 * m21 + m12 * m22;
m21 -= m11 * scaledShear;
m22 -= m12 * scaledShear;

// Post normalization, the submatrix is now of the form:
// [m11 m21] = [cos(R) -Sy*sin(R)]
// [m12 m22] [sin(R) Sy*cos(R)]

// Y Scale.
// Similar process to determining x-scale.
decomp.scale_y *= sqrt(m21 * m21 + m22 * m22);
m21 /= decomp.scale_y;
m22 /= decomp.scale_y;
decomp.skew_xy = scaledShear / decomp.scale_y;

// Rotation transform.
decomp.angle = atan2(m12, m11);
return true;
}

void TransformationMatrix::Recompose(const gfx::DecomposedTransform& decomp) {
static_cast<gfx::Transform&>(*this) = Compose(decomp);
}

void TransformationMatrix::Recompose2D(const Decomposed2dType& decomp) {
MakeIdentity();

// Translate transform.
set_rc(0, 3, decomp.translate_x);
set_rc(1, 3, decomp.translate_y);

// Rotate transform.
double cos_angle = cos(decomp.angle);
double sin_angle = sin(decomp.angle);
set_rc(0, 0, cos_angle);
set_rc(0, 1, -sin_angle);
set_rc(1, 0, sin_angle);
set_rc(1, 1, cos_angle);

// skew transform.
if (decomp.skew_xy) {
TransformationMatrix skew_transform;
skew_transform.set_rc(0, 1, decomp.skew_xy);
PreConcat(skew_transform);
}

// Scale transform.
Scale3d(decomp.scale_x, decomp.scale_y, 1);
}

SkM44 TransformationMatrix::ToSkM44() const {
return SkM44(
ClampToFloat(rc(0, 0)), ClampToFloat(rc(0, 1)), ClampToFloat(rc(0, 2)),
Expand Down

0 comments on commit 91ca54c

Please sign in to comment.