xref: /aosp_15_r20/external/skia/modules/skottie/src/Transform.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2020 Google Inc.
3*c8dee2aaSAndroid Build Coastguard Worker  *
4*c8dee2aaSAndroid Build Coastguard Worker  * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker  * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker  */
7*c8dee2aaSAndroid Build Coastguard Worker 
8*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/src/Transform.h"
9*c8dee2aaSAndroid Build Coastguard Worker 
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRefCnt.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkScalar.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkAssert.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTPin.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/src/SkottiePriv.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "modules/sksg/include/SkSGTransform.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "src/utils/SkJSON.h"
17*c8dee2aaSAndroid Build Coastguard Worker 
18*c8dee2aaSAndroid Build Coastguard Worker #include <cmath>
19*c8dee2aaSAndroid Build Coastguard Worker 
20*c8dee2aaSAndroid Build Coastguard Worker namespace skottie {
21*c8dee2aaSAndroid Build Coastguard Worker namespace internal {
22*c8dee2aaSAndroid Build Coastguard Worker 
TransformAdapter2D(const AnimationBuilder & abuilder,const skjson::ObjectValue * janchor_point,const skjson::ObjectValue * jposition,const skjson::ObjectValue * jscale,const skjson::ObjectValue * jrotation,const skjson::ObjectValue * jskew,const skjson::ObjectValue * jskew_axis,bool auto_orient)23*c8dee2aaSAndroid Build Coastguard Worker TransformAdapter2D::TransformAdapter2D(const AnimationBuilder& abuilder,
24*c8dee2aaSAndroid Build Coastguard Worker                                        const skjson::ObjectValue* janchor_point,
25*c8dee2aaSAndroid Build Coastguard Worker                                        const skjson::ObjectValue* jposition,
26*c8dee2aaSAndroid Build Coastguard Worker                                        const skjson::ObjectValue* jscale,
27*c8dee2aaSAndroid Build Coastguard Worker                                        const skjson::ObjectValue* jrotation,
28*c8dee2aaSAndroid Build Coastguard Worker                                        const skjson::ObjectValue* jskew,
29*c8dee2aaSAndroid Build Coastguard Worker                                        const skjson::ObjectValue* jskew_axis,
30*c8dee2aaSAndroid Build Coastguard Worker                                        bool auto_orient)
31*c8dee2aaSAndroid Build Coastguard Worker     : INHERITED(sksg::Matrix<SkMatrix>::Make(SkMatrix::I())) {
32*c8dee2aaSAndroid Build Coastguard Worker 
33*c8dee2aaSAndroid Build Coastguard Worker     this->bind(abuilder, janchor_point, fAnchorPoint);
34*c8dee2aaSAndroid Build Coastguard Worker     this->bind(abuilder, jscale       , fScale);
35*c8dee2aaSAndroid Build Coastguard Worker     this->bind(abuilder, jrotation    , fRotation);
36*c8dee2aaSAndroid Build Coastguard Worker     this->bind(abuilder, jskew        , fSkew);
37*c8dee2aaSAndroid Build Coastguard Worker     this->bind(abuilder, jskew_axis   , fSkewAxis);
38*c8dee2aaSAndroid Build Coastguard Worker 
39*c8dee2aaSAndroid Build Coastguard Worker     this->bindAutoOrientable(abuilder, jposition, &fPosition, auto_orient ? &fOrientation
40*c8dee2aaSAndroid Build Coastguard Worker                                                                           : nullptr);
41*c8dee2aaSAndroid Build Coastguard Worker }
42*c8dee2aaSAndroid Build Coastguard Worker 
~TransformAdapter2D()43*c8dee2aaSAndroid Build Coastguard Worker TransformAdapter2D::~TransformAdapter2D() {}
44*c8dee2aaSAndroid Build Coastguard Worker 
onSync()45*c8dee2aaSAndroid Build Coastguard Worker void TransformAdapter2D::onSync() {
46*c8dee2aaSAndroid Build Coastguard Worker     this->node()->setMatrix(this->totalMatrix());
47*c8dee2aaSAndroid Build Coastguard Worker }
48*c8dee2aaSAndroid Build Coastguard Worker 
totalMatrix() const49*c8dee2aaSAndroid Build Coastguard Worker SkMatrix TransformAdapter2D::totalMatrix() const {
50*c8dee2aaSAndroid Build Coastguard Worker     auto skew_matrix = [](float sk, float sa) {
51*c8dee2aaSAndroid Build Coastguard Worker         if (!sk) return SkMatrix::I();
52*c8dee2aaSAndroid Build Coastguard Worker 
53*c8dee2aaSAndroid Build Coastguard Worker         // AE control limit.
54*c8dee2aaSAndroid Build Coastguard Worker         static constexpr float kMaxSkewAngle = 85;
55*c8dee2aaSAndroid Build Coastguard Worker         sk = -SkDegreesToRadians(SkTPin(sk, -kMaxSkewAngle, kMaxSkewAngle));
56*c8dee2aaSAndroid Build Coastguard Worker         sa =  SkDegreesToRadians(sa);
57*c8dee2aaSAndroid Build Coastguard Worker 
58*c8dee2aaSAndroid Build Coastguard Worker         // Similar to CSS/SVG SkewX [1] with an explicit rotation.
59*c8dee2aaSAndroid Build Coastguard Worker         // [1] https://www.w3.org/TR/css-transforms-1/#SkewXDefined
60*c8dee2aaSAndroid Build Coastguard Worker         return SkMatrix::RotateRad(sa)
61*c8dee2aaSAndroid Build Coastguard Worker              * SkMatrix::Skew(std::tan(sk), 0)
62*c8dee2aaSAndroid Build Coastguard Worker              * SkMatrix::RotateRad(-sa);
63*c8dee2aaSAndroid Build Coastguard Worker     };
64*c8dee2aaSAndroid Build Coastguard Worker 
65*c8dee2aaSAndroid Build Coastguard Worker     return SkMatrix::Translate(fPosition.x, fPosition.y)
66*c8dee2aaSAndroid Build Coastguard Worker          * SkMatrix::RotateDeg(fRotation + fOrientation)
67*c8dee2aaSAndroid Build Coastguard Worker          * skew_matrix        (fSkew, fSkewAxis)
68*c8dee2aaSAndroid Build Coastguard Worker          * SkMatrix::Scale    (fScale.x / 100, fScale.y / 100) // 100% based
69*c8dee2aaSAndroid Build Coastguard Worker          * SkMatrix::Translate(-fAnchorPoint.x, -fAnchorPoint.y);
70*c8dee2aaSAndroid Build Coastguard Worker }
71*c8dee2aaSAndroid Build Coastguard Worker 
getAnchorPoint() const72*c8dee2aaSAndroid Build Coastguard Worker SkPoint TransformAdapter2D::getAnchorPoint() const {
73*c8dee2aaSAndroid Build Coastguard Worker     return { fAnchorPoint.x, fAnchorPoint.y };
74*c8dee2aaSAndroid Build Coastguard Worker }
75*c8dee2aaSAndroid Build Coastguard Worker 
setAnchorPoint(const SkPoint & ap)76*c8dee2aaSAndroid Build Coastguard Worker void TransformAdapter2D::setAnchorPoint(const SkPoint& ap) {
77*c8dee2aaSAndroid Build Coastguard Worker     fAnchorPoint = { ap.x(), ap.y() };
78*c8dee2aaSAndroid Build Coastguard Worker     this->onSync();
79*c8dee2aaSAndroid Build Coastguard Worker }
80*c8dee2aaSAndroid Build Coastguard Worker 
getPosition() const81*c8dee2aaSAndroid Build Coastguard Worker SkPoint TransformAdapter2D::getPosition() const {
82*c8dee2aaSAndroid Build Coastguard Worker     return { fPosition.x, fPosition.y };
83*c8dee2aaSAndroid Build Coastguard Worker }
84*c8dee2aaSAndroid Build Coastguard Worker 
setPosition(const SkPoint & p)85*c8dee2aaSAndroid Build Coastguard Worker void TransformAdapter2D::setPosition(const SkPoint& p) {
86*c8dee2aaSAndroid Build Coastguard Worker     fPosition = { p.x(), p.y() };
87*c8dee2aaSAndroid Build Coastguard Worker     this->onSync();
88*c8dee2aaSAndroid Build Coastguard Worker }
89*c8dee2aaSAndroid Build Coastguard Worker 
getScale() const90*c8dee2aaSAndroid Build Coastguard Worker SkVector TransformAdapter2D::getScale() const {
91*c8dee2aaSAndroid Build Coastguard Worker     return { fScale.x, fScale.y };
92*c8dee2aaSAndroid Build Coastguard Worker }
93*c8dee2aaSAndroid Build Coastguard Worker 
setScale(const SkVector & s)94*c8dee2aaSAndroid Build Coastguard Worker void TransformAdapter2D::setScale(const SkVector& s) {
95*c8dee2aaSAndroid Build Coastguard Worker     fScale = { s.x(), s.y() };
96*c8dee2aaSAndroid Build Coastguard Worker     this->onSync();
97*c8dee2aaSAndroid Build Coastguard Worker }
98*c8dee2aaSAndroid Build Coastguard Worker 
setRotation(float r)99*c8dee2aaSAndroid Build Coastguard Worker void TransformAdapter2D::setRotation(float r) {
100*c8dee2aaSAndroid Build Coastguard Worker     fRotation = r;
101*c8dee2aaSAndroid Build Coastguard Worker     this->onSync();
102*c8dee2aaSAndroid Build Coastguard Worker }
103*c8dee2aaSAndroid Build Coastguard Worker 
setSkew(float sk)104*c8dee2aaSAndroid Build Coastguard Worker void TransformAdapter2D::setSkew(float sk) {
105*c8dee2aaSAndroid Build Coastguard Worker     fSkew = sk;
106*c8dee2aaSAndroid Build Coastguard Worker     this->onSync();
107*c8dee2aaSAndroid Build Coastguard Worker }
108*c8dee2aaSAndroid Build Coastguard Worker 
setSkewAxis(float sa)109*c8dee2aaSAndroid Build Coastguard Worker void TransformAdapter2D::setSkewAxis(float sa) {
110*c8dee2aaSAndroid Build Coastguard Worker     fSkewAxis = sa;
111*c8dee2aaSAndroid Build Coastguard Worker     this->onSync();
112*c8dee2aaSAndroid Build Coastguard Worker }
113*c8dee2aaSAndroid Build Coastguard Worker 
attachMatrix2D(const skjson::ObjectValue & jtransform,sk_sp<sksg::Transform> parent,bool auto_orient) const114*c8dee2aaSAndroid Build Coastguard Worker sk_sp<sksg::Transform> AnimationBuilder::attachMatrix2D(const skjson::ObjectValue& jtransform,
115*c8dee2aaSAndroid Build Coastguard Worker                                                         sk_sp<sksg::Transform> parent,
116*c8dee2aaSAndroid Build Coastguard Worker                                                         bool auto_orient) const {
117*c8dee2aaSAndroid Build Coastguard Worker     const auto* jrotation = &jtransform["r"];
118*c8dee2aaSAndroid Build Coastguard Worker     if (jrotation->is<skjson::NullValue>()) {
119*c8dee2aaSAndroid Build Coastguard Worker         // Some 2D rotations are disguised as 3D...
120*c8dee2aaSAndroid Build Coastguard Worker         jrotation = &jtransform["rz"];
121*c8dee2aaSAndroid Build Coastguard Worker     }
122*c8dee2aaSAndroid Build Coastguard Worker 
123*c8dee2aaSAndroid Build Coastguard Worker     auto adapter = TransformAdapter2D::Make(*this,
124*c8dee2aaSAndroid Build Coastguard Worker                                             jtransform["a"],
125*c8dee2aaSAndroid Build Coastguard Worker                                             jtransform["p"],
126*c8dee2aaSAndroid Build Coastguard Worker                                             jtransform["s"],
127*c8dee2aaSAndroid Build Coastguard Worker                                             *jrotation,
128*c8dee2aaSAndroid Build Coastguard Worker                                             jtransform["sk"],
129*c8dee2aaSAndroid Build Coastguard Worker                                             jtransform["sa"],
130*c8dee2aaSAndroid Build Coastguard Worker                                             auto_orient);
131*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(adapter);
132*c8dee2aaSAndroid Build Coastguard Worker 
133*c8dee2aaSAndroid Build Coastguard Worker     const auto dispatched = this->dispatchTransformProperty(adapter);
134*c8dee2aaSAndroid Build Coastguard Worker 
135*c8dee2aaSAndroid Build Coastguard Worker     if (adapter->isStatic()) {
136*c8dee2aaSAndroid Build Coastguard Worker         if (!dispatched && adapter->totalMatrix().isIdentity()) {
137*c8dee2aaSAndroid Build Coastguard Worker             // The transform has no observable effects - we can discard.
138*c8dee2aaSAndroid Build Coastguard Worker             return parent;
139*c8dee2aaSAndroid Build Coastguard Worker         }
140*c8dee2aaSAndroid Build Coastguard Worker         adapter->seek(0);
141*c8dee2aaSAndroid Build Coastguard Worker     } else {
142*c8dee2aaSAndroid Build Coastguard Worker         fCurrentAnimatorScope->push_back(adapter);
143*c8dee2aaSAndroid Build Coastguard Worker     }
144*c8dee2aaSAndroid Build Coastguard Worker 
145*c8dee2aaSAndroid Build Coastguard Worker     return sksg::Transform::MakeConcat(std::move(parent), adapter->node());
146*c8dee2aaSAndroid Build Coastguard Worker }
147*c8dee2aaSAndroid Build Coastguard Worker 
TransformAdapter3D(const skjson::ObjectValue & jtransform,const AnimationBuilder & abuilder)148*c8dee2aaSAndroid Build Coastguard Worker TransformAdapter3D::TransformAdapter3D(const skjson::ObjectValue& jtransform,
149*c8dee2aaSAndroid Build Coastguard Worker                                        const AnimationBuilder& abuilder)
150*c8dee2aaSAndroid Build Coastguard Worker     : INHERITED(sksg::Matrix<SkM44>::Make(SkM44())) {
151*c8dee2aaSAndroid Build Coastguard Worker 
152*c8dee2aaSAndroid Build Coastguard Worker     this->bind(abuilder, jtransform["a"], fAnchorPoint);
153*c8dee2aaSAndroid Build Coastguard Worker     this->bind(abuilder, jtransform["p"], fPosition);
154*c8dee2aaSAndroid Build Coastguard Worker     this->bind(abuilder, jtransform["s"], fScale);
155*c8dee2aaSAndroid Build Coastguard Worker 
156*c8dee2aaSAndroid Build Coastguard Worker     // Axis-wise rotation and orientation are mapped to the same rotation property (3D rotation).
157*c8dee2aaSAndroid Build Coastguard Worker     // The difference is in how they get interpolated (scalar/decomposed vs. vector).
158*c8dee2aaSAndroid Build Coastguard Worker     this->bind(abuilder, jtransform["rx"], fRx);
159*c8dee2aaSAndroid Build Coastguard Worker     this->bind(abuilder, jtransform["ry"], fRy);
160*c8dee2aaSAndroid Build Coastguard Worker     this->bind(abuilder, jtransform["rz"], fRz);
161*c8dee2aaSAndroid Build Coastguard Worker     this->bind(abuilder, jtransform["or"], fOrientation);
162*c8dee2aaSAndroid Build Coastguard Worker }
163*c8dee2aaSAndroid Build Coastguard Worker 
164*c8dee2aaSAndroid Build Coastguard Worker TransformAdapter3D::~TransformAdapter3D() = default;
165*c8dee2aaSAndroid Build Coastguard Worker 
onSync()166*c8dee2aaSAndroid Build Coastguard Worker void TransformAdapter3D::onSync() {
167*c8dee2aaSAndroid Build Coastguard Worker     this->node()->setMatrix(this->totalMatrix());
168*c8dee2aaSAndroid Build Coastguard Worker }
169*c8dee2aaSAndroid Build Coastguard Worker 
anchor_point() const170*c8dee2aaSAndroid Build Coastguard Worker SkV3 TransformAdapter3D::anchor_point() const {
171*c8dee2aaSAndroid Build Coastguard Worker     return fAnchorPoint;
172*c8dee2aaSAndroid Build Coastguard Worker }
173*c8dee2aaSAndroid Build Coastguard Worker 
position() const174*c8dee2aaSAndroid Build Coastguard Worker SkV3 TransformAdapter3D::position() const {
175*c8dee2aaSAndroid Build Coastguard Worker     return fPosition;
176*c8dee2aaSAndroid Build Coastguard Worker }
177*c8dee2aaSAndroid Build Coastguard Worker 
rotation() const178*c8dee2aaSAndroid Build Coastguard Worker SkV3 TransformAdapter3D::rotation() const {
179*c8dee2aaSAndroid Build Coastguard Worker     // orientation and axis-wise rotation map onto the same property.
180*c8dee2aaSAndroid Build Coastguard Worker     return static_cast<SkV3>(fOrientation) + SkV3{ fRx, fRy, fRz };
181*c8dee2aaSAndroid Build Coastguard Worker }
182*c8dee2aaSAndroid Build Coastguard Worker 
totalMatrix() const183*c8dee2aaSAndroid Build Coastguard Worker SkM44 TransformAdapter3D::totalMatrix() const {
184*c8dee2aaSAndroid Build Coastguard Worker     const auto anchor_point = this->anchor_point(),
185*c8dee2aaSAndroid Build Coastguard Worker                position     = this->position(),
186*c8dee2aaSAndroid Build Coastguard Worker                scale        = static_cast<SkV3>(fScale),
187*c8dee2aaSAndroid Build Coastguard Worker                rotation     = this->rotation();
188*c8dee2aaSAndroid Build Coastguard Worker 
189*c8dee2aaSAndroid Build Coastguard Worker     return SkM44::Translate(position.x, position.y, position.z)
190*c8dee2aaSAndroid Build Coastguard Worker          * SkM44::Rotate({ 1, 0, 0 }, SkDegreesToRadians(rotation.x))
191*c8dee2aaSAndroid Build Coastguard Worker          * SkM44::Rotate({ 0, 1, 0 }, SkDegreesToRadians(rotation.y))
192*c8dee2aaSAndroid Build Coastguard Worker          * SkM44::Rotate({ 0, 0, 1 }, SkDegreesToRadians(rotation.z))
193*c8dee2aaSAndroid Build Coastguard Worker          * SkM44::Scale(scale.x / 100, scale.y / 100, scale.z / 100)
194*c8dee2aaSAndroid Build Coastguard Worker          * SkM44::Translate(-anchor_point.x, -anchor_point.y, -anchor_point.z);
195*c8dee2aaSAndroid Build Coastguard Worker }
196*c8dee2aaSAndroid Build Coastguard Worker 
attachMatrix3D(const skjson::ObjectValue & jtransform,sk_sp<sksg::Transform> parent,bool) const197*c8dee2aaSAndroid Build Coastguard Worker sk_sp<sksg::Transform> AnimationBuilder::attachMatrix3D(const skjson::ObjectValue& jtransform,
198*c8dee2aaSAndroid Build Coastguard Worker                                                         sk_sp<sksg::Transform> parent,
199*c8dee2aaSAndroid Build Coastguard Worker                                                         bool /*TODO: auto_orient*/) const {
200*c8dee2aaSAndroid Build Coastguard Worker     auto adapter = TransformAdapter3D::Make(jtransform, *this);
201*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(adapter);
202*c8dee2aaSAndroid Build Coastguard Worker 
203*c8dee2aaSAndroid Build Coastguard Worker     if (adapter->isStatic()) {
204*c8dee2aaSAndroid Build Coastguard Worker         // TODO: SkM44::isIdentity?
205*c8dee2aaSAndroid Build Coastguard Worker         if (adapter->totalMatrix() == SkM44()) {
206*c8dee2aaSAndroid Build Coastguard Worker             // The transform has no observable effects - we can discard.
207*c8dee2aaSAndroid Build Coastguard Worker             return parent;
208*c8dee2aaSAndroid Build Coastguard Worker         }
209*c8dee2aaSAndroid Build Coastguard Worker         adapter->seek(0);
210*c8dee2aaSAndroid Build Coastguard Worker     } else {
211*c8dee2aaSAndroid Build Coastguard Worker         fCurrentAnimatorScope->push_back(adapter);
212*c8dee2aaSAndroid Build Coastguard Worker     }
213*c8dee2aaSAndroid Build Coastguard Worker 
214*c8dee2aaSAndroid Build Coastguard Worker     return sksg::Transform::MakeConcat(std::move(parent), adapter->node());
215*c8dee2aaSAndroid Build Coastguard Worker }
216*c8dee2aaSAndroid Build Coastguard Worker 
217*c8dee2aaSAndroid Build Coastguard Worker } // namespace internal
218*c8dee2aaSAndroid Build Coastguard Worker } // namespace skottie
219