/* * 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 #include 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 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> 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::Make(SkMatrix::I())) {} TransformAdapter3D::~TransformAdapter3D() = default; sk_sp 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 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(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 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 grad, size_t stopCount) : fGradient(std::move(grad)) , fStopCount(stopCount) {} void GradientAdapter::apply() { this->onApply(); // |fColorStops| holds |fStopCount| x [ pos, r, g, g ] + ? x [ pos, alpha ] if (fColorStops.size() < fStopCount * 4 || ((fColorStops.size() - fStopCount * 4) % 2)) { // apply() may get called before the stops are set, so only log when we have some stops. if (!fColorStops.empty()) { SkDebugf("!! Invalid gradient stop array size: %zu\n", fColorStops.size()); } return; } std::vector stops; // TODO: merge/lerp opacity stops const auto csEnd = fColorStops.cbegin() + fStopCount * 4; for (auto cs = fColorStops.cbegin(); cs != csEnd; cs += 4) { const auto pos = cs[0]; const VectorValue rgb({ cs[1], cs[2], cs[3] }); stops.push_back({ pos, ValueTraits::As(rgb) }); } fGradient->setColorStops(std::move(stops)); } LinearGradientAdapter::LinearGradientAdapter(sk_sp grad, size_t stopCount) : INHERITED(std::move(grad), stopCount) {} void LinearGradientAdapter::onApply() { auto* grad = static_cast(fGradient.get()); grad->setStartPoint(this->startPoint()); grad->setEndPoint(this->endPoint()); } RadialGradientAdapter::RadialGradientAdapter(sk_sp grad, size_t stopCount) : INHERITED(std::move(grad), stopCount) {} void RadialGradientAdapter::onApply() { auto* grad = static_cast(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 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