| /* |
| * Copyright 2018 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "modules/skottie/src/SkottieAdapter.h" |
| |
| #include "include/core/SkFont.h" |
| #include "include/core/SkMatrix.h" |
| #include "include/core/SkMatrix44.h" |
| #include "include/core/SkPath.h" |
| #include "include/core/SkRRect.h" |
| #include "include/private/SkTo.h" |
| #include "include/utils/Sk3D.h" |
| #include "modules/skottie/src/SkottieValue.h" |
| #include "modules/sksg/include/SkSGDraw.h" |
| #include "modules/sksg/include/SkSGGradient.h" |
| #include "modules/sksg/include/SkSGGroup.h" |
| #include "modules/sksg/include/SkSGPaint.h" |
| #include "modules/sksg/include/SkSGPath.h" |
| #include "modules/sksg/include/SkSGRect.h" |
| #include "modules/sksg/include/SkSGTransform.h" |
| #include "modules/sksg/include/SkSGTrimEffect.h" |
| |
| #include <cmath> |
| #include <utility> |
| |
| namespace skottie { |
| |
| namespace internal { |
| |
| DiscardableAdaptorBase::DiscardableAdaptorBase() = default; |
| |
| void DiscardableAdaptorBase::setAnimators(sksg::AnimatorList&& animators) { |
| fAnimators = std::move(animators); |
| } |
| |
| void DiscardableAdaptorBase::onTick(float t) { |
| for (auto& animator : fAnimators) { |
| animator->tick(t); |
| } |
| |
| this->onSync(); |
| } |
| |
| } // namespace internal |
| |
| RRectAdapter::RRectAdapter(sk_sp<sksg::RRect> wrapped_node) |
| : fRRectNode(std::move(wrapped_node)) {} |
| |
| RRectAdapter::~RRectAdapter() = default; |
| |
| void RRectAdapter::apply() { |
| // BM "position" == "center position" |
| auto rr = SkRRect::MakeRectXY(SkRect::MakeXYWH(fPosition.x() - fSize.width() / 2, |
| fPosition.y() - fSize.height() / 2, |
| fSize.width(), fSize.height()), |
| fRadius.width(), |
| fRadius.height()); |
| fRRectNode->setRRect(rr); |
| } |
| |
| TransformAdapter2D::TransformAdapter2D(sk_sp<sksg::Matrix<SkMatrix>> matrix) |
| : fMatrixNode(std::move(matrix)) {} |
| |
| TransformAdapter2D::~TransformAdapter2D() = default; |
| |
| SkMatrix TransformAdapter2D::totalMatrix() const { |
| SkMatrix t = SkMatrix::MakeTrans(-fAnchorPoint.x(), -fAnchorPoint.y()); |
| |
| t.postScale(fScale.x() / 100, fScale.y() / 100); // 100% based |
| t.postRotate(fRotation); |
| t.postTranslate(fPosition.x(), fPosition.y()); |
| // TODO: skew |
| |
| return t; |
| } |
| |
| void TransformAdapter2D::apply() { |
| fMatrixNode->setMatrix(this->totalMatrix()); |
| } |
| |
| TransformAdapter3D::Vec3::Vec3(const VectorValue& v) { |
| fX = v.size() > 0 ? v[0] : 0; |
| fY = v.size() > 1 ? v[1] : 0; |
| fZ = v.size() > 2 ? v[2] : 0; |
| } |
| |
| TransformAdapter3D::TransformAdapter3D() |
| : fMatrixNode(sksg::Matrix<SkMatrix44>::Make(SkMatrix::I())) {} |
| |
| TransformAdapter3D::~TransformAdapter3D() = default; |
| |
| sk_sp<sksg::Transform> TransformAdapter3D::refTransform() const { |
| return fMatrixNode; |
| } |
| |
| SkMatrix44 TransformAdapter3D::totalMatrix() const { |
| SkMatrix44 t; |
| |
| t.setTranslate(-fAnchorPoint.fX, -fAnchorPoint.fY, -fAnchorPoint.fZ); |
| t.postScale(fScale.fX / 100, fScale.fY / 100, fScale.fZ / 100); |
| |
| SkMatrix44 r; |
| r.setRotateDegreesAbout(0, 0, 1, fRotation.fZ); |
| t.postConcat(r); |
| r.setRotateDegreesAbout(0, 1, 0, fRotation.fY); |
| t.postConcat(r); |
| r.setRotateDegreesAbout(1, 0, 0, fRotation.fX); |
| t.postConcat(r); |
| |
| t.postTranslate(fPosition.fX, fPosition.fY, fPosition.fZ); |
| |
| return t; |
| } |
| |
| void TransformAdapter3D::apply() { |
| fMatrixNode->setMatrix(this->totalMatrix()); |
| } |
| |
| CameraAdapter:: CameraAdapter(const SkSize& viewport_size) |
| : fViewportSize(viewport_size) {} |
| |
| CameraAdapter::~CameraAdapter() = default; |
| |
| SkMatrix44 CameraAdapter::totalMatrix() const { |
| // Camera parameters: |
| // |
| // * location -> position attribute |
| // * point of interest -> anchor point attribute |
| // * orientation -> rotation attribute |
| // |
| SkPoint3 pos = { this->getPosition().fX, |
| this->getPosition().fY, |
| -this->getPosition().fZ }, |
| poi = { this->getAnchorPoint().fX, |
| this->getAnchorPoint().fY, |
| -this->getAnchorPoint().fZ }, |
| up = { 0, 1, 0 }; |
| |
| // Initial camera vector. |
| SkMatrix44 cam_t; |
| Sk3LookAt(&cam_t, pos, poi, up); |
| |
| // Rotation origin is camera position. |
| { |
| SkMatrix44 rot; |
| rot.setRotateDegreesAbout(1, 0, 0, this->getRotation().fX); |
| cam_t.postConcat(rot); |
| rot.setRotateDegreesAbout(0, 1, 0, this->getRotation().fY); |
| cam_t.postConcat(rot); |
| rot.setRotateDegreesAbout(0, 0, 1, -this->getRotation().fZ); |
| cam_t.postConcat(rot); |
| } |
| |
| // Flip world Z, as it is opposite of what Sk3D expects. |
| cam_t.preScale(1, 1, -1); |
| |
| // View parameters: |
| // |
| // * size -> composition size (TODO: AE seems to base it on width only?) |
| // * distance -> "zoom" camera attribute |
| // |
| const auto view_size = SkTMax(fViewportSize.width(), fViewportSize.height()), |
| view_distance = this->getZoom(), |
| view_angle = std::atan(sk_ieee_float_divide(view_size * 0.5f, view_distance)); |
| |
| SkMatrix44 persp_t; |
| Sk3Perspective(&persp_t, 0, view_distance, 2 * view_angle); |
| persp_t.postScale(view_size * 0.5f, view_size * 0.5f, 1); |
| |
| SkMatrix44 t; |
| t.setTranslate(fViewportSize.width() * 0.5f, fViewportSize.height() * 0.5f, 0); |
| t.preConcat(persp_t); |
| t.preConcat(cam_t); |
| |
| return t; |
| } |
| |
| RepeaterAdapter::RepeaterAdapter(sk_sp<sksg::RenderNode> repeater_node, Composite composite) |
| : fRepeaterNode(repeater_node) |
| , fComposite(composite) |
| , fRoot(sksg::Group::Make()) {} |
| |
| RepeaterAdapter::~RepeaterAdapter() = default; |
| |
| void RepeaterAdapter::apply() { |
| static constexpr SkScalar kMaxCount = 512; |
| const auto count = static_cast<size_t>(SkTPin(fCount, 0.0f, kMaxCount) + 0.5f); |
| |
| const auto& compute_transform = [this] (size_t index) { |
| const auto t = fOffset + index; |
| |
| // Position, scale & rotation are "scaled" by index/offset. |
| SkMatrix m = SkMatrix::MakeTrans(-fAnchorPoint.x(), |
| -fAnchorPoint.y()); |
| m.postScale(std::pow(fScale.x() * .01f, fOffset), |
| std::pow(fScale.y() * .01f, fOffset)); |
| m.postRotate(t * fRotation); |
| m.postTranslate(t * fPosition.x() + fAnchorPoint.x(), |
| t * fPosition.y() + fAnchorPoint.y()); |
| |
| return m; |
| }; |
| |
| // TODO: start/end opacity support. |
| |
| // TODO: we can avoid rebuilding all the fragments in most cases. |
| fRoot->clear(); |
| for (size_t i = 0; i < count; ++i) { |
| const auto insert_index = (fComposite == Composite::kAbove) ? i : count - i - 1; |
| fRoot->addChild(sksg::TransformEffect::Make(fRepeaterNode, |
| compute_transform(insert_index))); |
| } |
| } |
| |
| PolyStarAdapter::PolyStarAdapter(sk_sp<sksg::Path> wrapped_node, Type t) |
| : fPathNode(std::move(wrapped_node)) |
| , fType(t) {} |
| |
| PolyStarAdapter::~PolyStarAdapter() = default; |
| |
| void PolyStarAdapter::apply() { |
| static constexpr int kMaxPointCount = 100000; |
| const auto count = SkToUInt(SkTPin(SkScalarRoundToInt(fPointCount), 0, kMaxPointCount)); |
| const auto arc = sk_ieee_float_divide(SK_ScalarPI * 2, count); |
| |
| const auto pt_on_circle = [](const SkPoint& c, SkScalar r, SkScalar a) { |
| return SkPoint::Make(c.x() + r * std::cos(a), |
| c.y() + r * std::sin(a)); |
| }; |
| |
| // TODO: inner/outer "roundness"? |
| |
| SkPath poly; |
| |
| auto angle = SkDegreesToRadians(fRotation - 90); |
| poly.moveTo(pt_on_circle(fPosition, fOuterRadius, angle)); |
| poly.incReserve(fType == Type::kStar ? count * 2 : count); |
| |
| for (unsigned i = 0; i < count; ++i) { |
| if (fType == Type::kStar) { |
| poly.lineTo(pt_on_circle(fPosition, fInnerRadius, angle + arc * 0.5f)); |
| } |
| angle += arc; |
| poly.lineTo(pt_on_circle(fPosition, fOuterRadius, angle)); |
| } |
| |
| poly.close(); |
| fPathNode->setPath(poly); |
| } |
| |
| GradientAdapter::GradientAdapter(sk_sp<sksg::Gradient> grad, size_t colorStopCount) |
| : fGradient(std::move(grad)), fColorStopCount(colorStopCount) {} |
| |
| void GradientAdapter::apply() { |
| this->onApply(); |
| |
| // Gradient color stops are specified as a consolidated float vector holding: |
| // |
| // a) an (optional) array of color/RGB stop records (t, r, g, b) |
| // |
| // followed by |
| // |
| // b) an (optional) array of opacity/alpha stop records (t, a) |
| // |
| struct ColorRec { |
| float t, r, g, b; |
| }; |
| struct OpacityRec { |
| float t, a; |
| }; |
| |
| // The number of color records is explicit (fColorStopCount), |
| // while the number of opacity stops is implicit (based on the size of fStops). |
| // |
| // |fStops| holds ColorRec x |fColorStopCount| + OpacityRec x N |
| const auto c_count = fColorStopCount, c_size = c_count * 4, |
| o_count = (fStops.size() - c_size) / 2; |
| if (fStops.size() < c_size || fStops.size() != (c_count * 4 + o_count * 2)) { |
| // apply() may get called before the stops are set, so only log when we have some stops. |
| if (!fStops.empty()) { |
| SkDebugf("!! Invalid gradient stop array size: %zu\n", fStops.size()); |
| } |
| return; |
| } |
| |
| const auto* current_c = reinterpret_cast<const ColorRec*>(fStops.data()); |
| const auto* end_c = current_c + c_count; |
| const auto* current_o = reinterpret_cast<const OpacityRec*>(end_c); |
| const auto* end_o = current_o + o_count; |
| |
| // TODO: Gradient/ColorStop should use 4f. |
| sksg::Gradient::ColorStop prev_stop = {0.0f, SK_ColorBLACK}; |
| if (current_c < end_c) { |
| prev_stop.fColor = SkColor4f{current_c->r, current_c->g, current_c->b, 1.0}.toSkColor(); |
| } |
| if (current_o < end_o) { |
| prev_stop.fColor = SkColorSetA(prev_stop.fColor, SkScalarRoundToInt(current_o->a * 255)); |
| } |
| |
| auto lerp = [](float a, float b, float t) { return a + t * (b - a); }; |
| |
| auto next_stop = [&]() -> sksg::Gradient::ColorStop { |
| const auto prev_c = SkColor4f::FromColor(prev_stop.fColor); |
| |
| const uint8_t has_color_stop = SkToU8(current_c < end_c), |
| has_opacity_stop = SkToU8(current_o < end_o); |
| |
| switch (has_color_stop | (has_opacity_stop << 1)) { |
| case 0x01: { |
| // Color-only stop. |
| sksg::Gradient::ColorStop cs{ |
| current_c->t, |
| SkColor4f{current_c->r, current_c->g, current_c->b, prev_c.fA}.toSkColor()}; |
| |
| current_c++; |
| return cs; |
| } |
| case 0x02: { |
| // Opacity-only stop. |
| sksg::Gradient::ColorStop cs{ |
| current_o->t, |
| SkColor4f{prev_c.fR, prev_c.fG, prev_c.fB, current_o->a}.toSkColor()}; |
| |
| current_o++; |
| return cs; |
| } |
| case 0x03: { |
| // Separate color and opacity stops. |
| // Merge-sort the two arrays, LERP-ing intermediate channel values as needed. |
| |
| auto c = SkColor4f{current_c->r, current_c->g, current_c->b, current_o->a}; |
| auto t_rgb = current_c->t, t_a = current_o->t; |
| |
| if (SkScalarNearlyEqual(t_rgb, t_a)) { |
| // Coincident color and opacity stops: no LERP needed, consume both. |
| current_c++; |
| current_o++; |
| |
| return {t_rgb, c.toSkColor()}; |
| } |
| |
| if (t_rgb < t_a) { |
| // Color stop followed by opacity stop: LERP alpha, consume the color stop. |
| const auto rel_t = SkTPin(sk_ieee_float_divide(t_rgb - prev_stop.fPosition, |
| t_a - prev_stop.fPosition), |
| 0.0f, 1.0f); |
| c.fA = lerp(prev_c.fA, c.fA, rel_t); |
| |
| current_c++; |
| |
| return {t_rgb, c.toSkColor()}; |
| } else { |
| // Opacity stop followed by color stop: LERP r/g/b, consume the opacity stop. |
| const auto rel_t = SkTPin(sk_ieee_float_divide(t_a - prev_stop.fPosition, |
| t_rgb - prev_stop.fPosition), |
| 0.0f, 1.0f); |
| c.fR = lerp(prev_c.fR, c.fR, rel_t); |
| c.fG = lerp(prev_c.fG, c.fG, rel_t); |
| c.fB = lerp(prev_c.fB, c.fB, rel_t); |
| |
| current_o++; |
| |
| return {t_a, c.toSkColor()}; |
| } |
| } |
| |
| default: |
| SkUNREACHABLE; |
| } |
| }; |
| |
| std::vector<sksg::Gradient::ColorStop> stops; |
| stops.reserve(c_count); |
| |
| while (current_c < end_c || current_o < end_o) { |
| prev_stop = next_stop(); |
| stops.push_back(prev_stop); |
| } |
| |
| stops.shrink_to_fit(); |
| fGradient->setColorStops(std::move(stops)); |
| } |
| |
| LinearGradientAdapter::LinearGradientAdapter(sk_sp<sksg::LinearGradient> grad, size_t stopCount) |
| : INHERITED(std::move(grad), stopCount) {} |
| |
| void LinearGradientAdapter::onApply() { |
| auto* grad = static_cast<sksg::LinearGradient*>(fGradient.get()); |
| grad->setStartPoint(this->startPoint()); |
| grad->setEndPoint(this->endPoint()); |
| } |
| |
| RadialGradientAdapter::RadialGradientAdapter(sk_sp<sksg::RadialGradient> grad, size_t stopCount) |
| : INHERITED(std::move(grad), stopCount) {} |
| |
| void RadialGradientAdapter::onApply() { |
| auto* grad = static_cast<sksg::RadialGradient*>(fGradient.get()); |
| grad->setStartCenter(this->startPoint()); |
| grad->setEndCenter(this->startPoint()); |
| grad->setStartRadius(0); |
| grad->setEndRadius(SkPoint::Distance(this->startPoint(), this->endPoint())); |
| } |
| |
| TrimEffectAdapter::TrimEffectAdapter(sk_sp<sksg::TrimEffect> trimEffect) |
| : fTrimEffect(std::move(trimEffect)) { |
| SkASSERT(fTrimEffect); |
| } |
| |
| TrimEffectAdapter::~TrimEffectAdapter() = default; |
| |
| void TrimEffectAdapter::apply() { |
| // BM semantics: start/end are percentages, offset is "degrees" (?!). |
| const auto start = fStart / 100, |
| end = fEnd / 100, |
| offset = fOffset / 360; |
| |
| auto startT = SkTMin(start, end) + offset, |
| stopT = SkTMax(start, end) + offset; |
| auto mode = SkTrimPathEffect::Mode::kNormal; |
| |
| if (stopT - startT < 1) { |
| startT -= SkScalarFloorToScalar(startT); |
| stopT -= SkScalarFloorToScalar(stopT); |
| |
| if (startT > stopT) { |
| using std::swap; |
| swap(startT, stopT); |
| mode = SkTrimPathEffect::Mode::kInverted; |
| } |
| } else { |
| startT = 0; |
| stopT = 1; |
| } |
| |
| fTrimEffect->setStart(startT); |
| fTrimEffect->setStop(stopT); |
| fTrimEffect->setMode(mode); |
| } |
| |
| } // namespace skottie |