| /* |
| * 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 "SkottieAdapter.h" |
| |
| #include "Sk3D.h" |
| #include "SkFont.h" |
| #include "SkMatrix.h" |
| #include "SkMatrix44.h" |
| #include "SkPath.h" |
| #include "SkRRect.h" |
| #include "SkSGColorFilter.h" |
| #include "SkSGDraw.h" |
| #include "SkSGGradient.h" |
| #include "SkSGGroup.h" |
| #include "SkSGPaint.h" |
| #include "SkSGPath.h" |
| #include "SkSGRect.h" |
| #include "SkSGRenderEffect.h" |
| #include "SkSGText.h" |
| #include "SkSGTransform.h" |
| #include "SkSGTrimEffect.h" |
| #include "SkTableColorFilter.h" |
| #include "SkTo.h" |
| #include "SkottieShaper.h" |
| #include "SkottieValue.h" |
| |
| #include <cmath> |
| #include <utility> |
| |
| namespace skottie { |
| |
| 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); |
| |
| // TODO: SkMatrix44:postRotate()? |
| SkMatrix44 r; |
| r.setRotateDegreesAbout(1, 0, 0, fRotation.fX); |
| t.postConcat(r); |
| r.setRotateDegreesAbout(0, 1, 0, fRotation.fY); |
| t.postConcat(r); |
| r.setRotateDegreesAbout(0, 0, 1, fRotation.fZ); |
| 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 |
| // |
| // Note: the orientation is specified post position/POI adjustment. |
| // |
| 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 }; |
| |
| SkMatrix44 cam_t; |
| Sk3LookAt(&cam_t, pos, poi, up); |
| |
| { |
| 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); |
| } |
| |
| // 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(view_size * 0.5f / view_distance); |
| |
| SkMatrix44 view_t; |
| Sk3Perspective(&view_t, 0, view_distance, 2 * view_angle); |
| view_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(view_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 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<sksg::Gradient::ColorStop> 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<VectorValue>::As<SkColor>(rgb) }); |
| } |
| |
| 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())); |
| } |
| |
| GradientRampEffectAdapter::GradientRampEffectAdapter(sk_sp<sksg::RenderNode> child) |
| : fRoot(sksg::ShaderEffect::Make(std::move(child))) {} |
| |
| GradientRampEffectAdapter::~GradientRampEffectAdapter() = default; |
| |
| void GradientRampEffectAdapter::apply() { |
| // This adapter manages a SG fragment with the following structure: |
| // |
| // - ShaderEffect [fRoot] |
| // \ GradientShader [fGradient] |
| // \ child/wrapped fragment |
| // |
| // The gradient shader is updated based on the (animatable) intance type (linear/radial). |
| |
| auto update_gradient = [this] (InstanceType new_type) { |
| if (new_type != fInstanceType) { |
| fGradient = new_type == InstanceType::kLinear |
| ? sk_sp<sksg::Gradient>(sksg::LinearGradient::Make()) |
| : sk_sp<sksg::Gradient>(sksg::RadialGradient::Make()); |
| |
| fRoot->setShader(fGradient); |
| fInstanceType = new_type; |
| } |
| |
| fGradient->setColorStops({ {0, fStartColor}, {1, fEndColor} }); |
| }; |
| |
| static constexpr int kLinearShapeValue = 1; |
| const auto instance_type = (SkScalarRoundToInt(fShape) == kLinearShapeValue) |
| ? InstanceType::kLinear |
| : InstanceType::kRadial; |
| |
| // Sync the gradient shader instance if needed. |
| update_gradient(instance_type); |
| |
| // Sync instance-dependent gradient params. |
| if (instance_type == InstanceType::kLinear) { |
| auto* lg = static_cast<sksg::LinearGradient*>(fGradient.get()); |
| lg->setStartPoint(fStartPoint); |
| lg->setEndPoint(fEndPoint); |
| } else { |
| SkASSERT(instance_type == InstanceType::kRadial); |
| |
| auto* rg = static_cast<sksg::RadialGradient*>(fGradient.get()); |
| rg->setStartCenter(fStartPoint); |
| rg->setEndCenter(fStartPoint); |
| rg->setEndRadius(SkPoint::Distance(fStartPoint, fEndPoint)); |
| } |
| |
| // TODO: blend, scatter |
| } |
| |
| 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); |
| } |
| |
| DropShadowEffectAdapter::DropShadowEffectAdapter(sk_sp<sksg::DropShadowImageFilter> dropShadow) |
| : fDropShadow(std::move(dropShadow)) { |
| SkASSERT(fDropShadow); |
| } |
| |
| DropShadowEffectAdapter::~DropShadowEffectAdapter() = default; |
| |
| void DropShadowEffectAdapter::apply() { |
| // fColor -> RGB, fOpacity -> A |
| fDropShadow->setColor(SkColorSetA(fColor, SkTPin(SkScalarRoundToInt(fOpacity), 0, 255))); |
| |
| // The offset is specified in terms of a bearing angle + distance. |
| SkScalar rad = SkDegreesToRadians(90 - fDirection); |
| fDropShadow->setOffset(SkVector::Make( fDistance * SkScalarCos(rad), |
| -fDistance * SkScalarSin(rad))); |
| |
| // Close enough to AE. |
| static constexpr SkScalar kSoftnessToSigmaFactor = 0.3f; |
| const auto sigma = fSoftness * kSoftnessToSigmaFactor; |
| fDropShadow->setSigma(SkVector::Make(sigma, sigma)); |
| |
| fDropShadow->setMode(fShadowOnly ? sksg::DropShadowImageFilter::Mode::kShadowOnly |
| : sksg::DropShadowImageFilter::Mode::kShadowAndForeground); |
| } |
| |
| GaussianBlurEffectAdapter::GaussianBlurEffectAdapter(sk_sp<sksg::BlurImageFilter> blur) |
| : fBlur(std::move(blur)) { |
| SkASSERT(fBlur); |
| } |
| |
| GaussianBlurEffectAdapter::~GaussianBlurEffectAdapter() = default; |
| |
| void GaussianBlurEffectAdapter::apply() { |
| static constexpr SkVector kDimensionsMap[] = { |
| { 1, 1 }, // 1 -> horizontal and vertical |
| { 1, 0 }, // 2 -> horizontal |
| { 0, 1 }, // 3 -> vertical |
| }; |
| |
| const auto dim_index = SkTPin<size_t>(static_cast<size_t>(fDimensions), |
| 1, SK_ARRAY_COUNT(kDimensionsMap)) - 1; |
| |
| // Close enough to AE. |
| static constexpr SkScalar kBlurrinessToSigmaFactor = 0.3f; |
| const auto sigma = fBlurriness * kBlurrinessToSigmaFactor; |
| |
| fBlur->setSigma({ sigma * kDimensionsMap[dim_index].x(), |
| sigma * kDimensionsMap[dim_index].y() }); |
| |
| static constexpr SkBlurImageFilter::TileMode kRepeatEdgeMap[] = { |
| SkBlurImageFilter::kClampToBlack_TileMode, // 0 -> repeat edge pixels: off |
| SkBlurImageFilter:: kClamp_TileMode, // 1 -> repeat edge pixels: on |
| }; |
| |
| const auto repeat_index = SkTPin<size_t>(static_cast<size_t>(fRepeatEdge), |
| 0, SK_ARRAY_COUNT(kRepeatEdgeMap) - 1); |
| fBlur->setTileMode(kRepeatEdgeMap[repeat_index]); |
| } |
| |
| |
| // Levels color correction effect. |
| // |
| // Maps the selected channels from [inBlack...inWhite] to [outBlack, outWhite], |
| // based on a gamma exponent. |
| // |
| // For [i0..i1] -> [o0..o1]: |
| // |
| // c' = o0 + (o1 - o0) * ((c - i0) / (i1 - i0)) ^ G |
| // |
| // The output is optionally clipped to the output range. |
| // |
| // In/out intervals are clampped to [0..1]. Inversion is allowed. |
| LevelsEffectAdapter::LevelsEffectAdapter(sk_sp<sksg::RenderNode> child) |
| : fEffect(sksg::ExternalColorFilter::Make(std::move(child))) { |
| SkASSERT(fEffect); |
| } |
| |
| LevelsEffectAdapter::~LevelsEffectAdapter() = default; |
| |
| void LevelsEffectAdapter::apply() { |
| enum LottieChannel { |
| kRGB_Channel = 1, |
| kR_Channel = 2, |
| kG_Channel = 3, |
| kB_Channel = 4, |
| kA_Channel = 5, |
| }; |
| |
| const auto channel = SkScalarTruncToInt(fChannel); |
| if (channel < kRGB_Channel || channel > kA_Channel) { |
| fEffect->setColorFilter(nullptr); |
| return; |
| } |
| |
| auto in_0 = SkTPin(fInBlack, 0.0f, 1.0f), |
| in_1 = SkTPin(fInWhite, 0.0f, 1.0f), |
| out_0 = SkTPin(fOutBlack, 0.0f, 1.0f), |
| out_1 = SkTPin(fOutWhite, 0.0f, 1.0f), |
| g = 1 / SkTMax(fGamma, 0.0f); |
| |
| float clip[] = {0, 1}; |
| const auto kLottieDoClip = 1; |
| if (SkScalarTruncToInt(fClipBlack) == kLottieDoClip) { |
| const auto idx = fOutBlack <= fOutWhite ? 0 : 1; |
| clip[idx] = out_0; |
| } |
| if (SkScalarTruncToInt(fClipWhite) == kLottieDoClip) { |
| const auto idx = fOutBlack <= fOutWhite ? 1 : 0; |
| clip[idx] = out_1; |
| } |
| SkASSERT(clip[0] <= clip[1]); |
| |
| auto dIn = in_1 - in_0, |
| dOut = out_1 - out_0; |
| |
| if (SkScalarNearlyZero(dIn)) { |
| // Degenerate dIn == 0 makes the arithmetic below explode. |
| // |
| // We could specialize the builder to deal with that case, or we could just |
| // nudge by epsilon to make it all work. The latter approach is simpler |
| // and doesn't have any noticeable downsides. |
| // |
| // Also nudge in_0 towards 0.5, in case it was sqashed against an extremity. |
| // This allows for some abrupt transition when the output interval is not |
| // collapsed, and produces results closer to AE. |
| static constexpr auto kEpsilon = 2 * SK_ScalarNearlyZero; |
| dIn += std::copysign(kEpsilon, dIn); |
| in_0 += std::copysign(kEpsilon, .5f - in_0); |
| SkASSERT(!SkScalarNearlyZero(dIn)); |
| } |
| |
| uint8_t lut[256]; |
| |
| auto t = -in_0 / dIn, |
| dT = 1 / 255.0f / dIn; |
| |
| // TODO: is linear gamma common-enough to warrant a fast path? |
| for (size_t i = 0; i < 256; ++i) { |
| const auto out = out_0 + dOut * std::pow(std::max(t, 0.0f), g); |
| SkASSERT(!SkScalarIsNaN(out)); |
| |
| lut[i] = static_cast<uint8_t>(std::round(SkTPin(out, clip[0], clip[1]) * 255)); |
| |
| t += dT; |
| } |
| |
| fEffect->setColorFilter(SkTableColorFilter::MakeARGB( |
| channel == kA_Channel ? lut : nullptr, |
| channel == kR_Channel || channel == kRGB_Channel ? lut : nullptr, |
| channel == kG_Channel || channel == kRGB_Channel ? lut : nullptr, |
| channel == kB_Channel || channel == kRGB_Channel ? lut : nullptr |
| )); |
| } |
| |
| TextAdapter::TextAdapter(sk_sp<sksg::Group> root) |
| : fRoot(std::move(root)) |
| , fTextNode(sksg::TextBlob::Make()) |
| , fFillColor(sksg::Color::Make(SK_ColorTRANSPARENT)) |
| , fStrokeColor(sksg::Color::Make(SK_ColorTRANSPARENT)) |
| , fFillNode(sksg::Draw::Make(fTextNode, fFillColor)) |
| , fStrokeNode(sksg::Draw::Make(fTextNode, fStrokeColor)) |
| , fHadFill(false) |
| , fHadStroke(false) { |
| // Build a SG fragment with the following general format: |
| // |
| // [Group] |
| // [Draw] |
| // [FillPaint] |
| // [Text]* |
| // [Draw] |
| // [StrokePaint] |
| // [Text]* |
| // |
| // * where the text node is shared |
| |
| fFillColor->setAntiAlias(true); |
| fStrokeColor->setAntiAlias(true); |
| fStrokeColor->setStyle(SkPaint::kStroke_Style); |
| } |
| |
| TextAdapter::~TextAdapter() = default; |
| |
| void TextAdapter::apply() { |
| const Shaper::TextDesc text_desc = { |
| fText.fTypeface, |
| fText.fTextSize, |
| fText.fHAlign, |
| fText.fVAlign, |
| }; |
| const auto shape_result = Shaper::Shape(fText.fText, text_desc, fText.fBox); |
| |
| #if (0) |
| // Enable for text box debugging/visualization. |
| auto box_color = sksg::Color::Make(0xffff0000); |
| box_color->setStyle(SkPaint::kStroke_Style); |
| box_color->setStrokeWidth(1); |
| box_color->setAntiAlias(true); |
| |
| auto bounds_color = sksg::Color::Make(0xff00ff00); |
| bounds_color->setStyle(SkPaint::kStroke_Style); |
| bounds_color->setStrokeWidth(1); |
| bounds_color->setAntiAlias(true); |
| |
| fRoot->addChild(sksg::Draw::Make(sksg::Rect::Make(fText.fBox), |
| std::move(box_color))); |
| fRoot->addChild(sksg::Draw::Make(sksg::Rect::Make(shape_result.computeBounds()), |
| std::move(bounds_color))); |
| #endif |
| |
| fTextNode->setBlob(shape_result.fBlob); |
| fTextNode->setPosition(shape_result.fPos); |
| |
| fFillColor->setColor(fText.fFillColor); |
| fStrokeColor->setColor(fText.fStrokeColor); |
| fStrokeColor->setStrokeWidth(fText.fStrokeWidth); |
| |
| // Turn the state transition into a tri-state value: |
| // -1: detach node |
| // 0: no change |
| // 1: attach node |
| const auto fill_change = SkToInt(fText.fHasFill) - SkToInt(fHadFill); |
| const auto stroke_change = SkToInt(fText.fHasStroke) - SkToInt(fHadStroke); |
| |
| // Sync SG topology. |
| if (fill_change || stroke_change) { |
| // This is trickier than it should be because sksg::Group only allows adding children |
| // in paint-order. |
| if (stroke_change < 0 || (fHadStroke && fill_change > 0)) { |
| fRoot->removeChild(fStrokeNode); |
| } |
| |
| if (fill_change < 0) { |
| fRoot->removeChild(fFillNode); |
| } else if (fill_change > 0) { |
| fRoot->addChild(fFillNode); |
| } |
| |
| if (stroke_change > 0 || (fHadStroke && fill_change > 0)) { |
| fRoot->addChild(fStrokeNode); |
| } |
| } |
| |
| // Track current state. |
| fHadFill = fText.fHasFill; |
| fHadStroke = fText.fHasStroke; |
| } |
| |
| } // namespace skottie |