2 * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved.
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 #include "lottieitem.h"
27 #include "lottiekeypath.h"
33 * 1. time stretch is pre calculated and applied to all the properties of the
34 * lottilayer model and all its children
35 * 2. The frame property could be reversed using,time-reverse layer property in
36 * AE. which means (start frame > endFrame) 3.
39 static bool transformProp(rlottie::Property prop)
42 case rlottie::Property::TrAnchor:
43 case rlottie::Property::TrScale:
44 case rlottie::Property::TrOpacity:
45 case rlottie::Property::TrPosition:
46 case rlottie::Property::TrRotation:
52 static bool fillProp(rlottie::Property prop)
55 case rlottie::Property::FillColor:
56 case rlottie::Property::FillOpacity:
63 static bool strokeProp(rlottie::Property prop)
66 case rlottie::Property::StrokeColor:
67 case rlottie::Property::StrokeOpacity:
68 case rlottie::Property::StrokeWidth:
75 static renderer::Layer *createLayerItem(model::Layer *layerData,
76 VArenaAlloc * allocator)
78 switch (layerData->mLayerType) {
79 case model::Layer::Type::Precomp: {
80 return allocator->make<renderer::CompLayer>(layerData, allocator);
82 case model::Layer::Type::Solid: {
83 return allocator->make<renderer::SolidLayer>(layerData);
85 case model::Layer::Type::Shape: {
86 return allocator->make<renderer::ShapeLayer>(layerData, allocator);
88 case model::Layer::Type::Null: {
89 return allocator->make<renderer::NullLayer>(layerData);
91 case model::Layer::Type::Image: {
92 return allocator->make<renderer::ImageLayer>(layerData);
100 renderer::Composition::Composition(std::shared_ptr<model::Composition> model)
103 mModel = std::move(model);
104 mRootLayer = createLayerItem(mModel->mRootLayer, &mAllocator);
105 mRootLayer->setComplexContent(false);
106 mViewSize = mModel->size();
109 void renderer::Composition::setValue(const std::string &keypath,
112 LOTKeyPath key(keypath);
113 mRootLayer->resolveKeyPath(key, 0, value);
116 bool renderer::Composition::update(int frameNo, const VSize &size,
117 bool keepAspectRatio)
119 // check if cached frame is same as requested frame.
120 if ((mViewSize == size) && (mCurFrameNo == frameNo) &&
121 (mKeepAspectRatio == keepAspectRatio))
125 mCurFrameNo = frameNo;
126 mKeepAspectRatio = keepAspectRatio;
129 * if viewbox dosen't scale exactly to the viewport
130 * we scale the viewbox keeping AspectRatioPreserved and then align the
131 * viewbox to the viewport using AlignCenter rule.
134 VSize viewPort = mViewSize;
135 VSize viewBox = mModel->size();
136 float sx = float(viewPort.width()) / viewBox.width();
137 float sy = float(viewPort.height()) / viewBox.height();
138 if (mKeepAspectRatio) {
139 float scale = std::min(sx, sy);
140 float tx = (viewPort.width() - viewBox.width() * scale) * 0.5f;
141 float ty = (viewPort.height() - viewBox.height() * scale) * 0.5f;
142 m.translate(tx, ty).scale(scale, scale);
146 mRootLayer->update(frameNo, m, 1.0);
150 bool renderer::Composition::render(const rlottie::Surface &surface)
152 mSurface.reset(reinterpret_cast<uchar *>(surface.buffer()),
153 uint(surface.width()), uint(surface.height()),
154 uint(surface.bytesPerLine()),
155 VBitmap::Format::ARGB32_Premultiplied);
157 /* schedule all preprocess task for this frame at once.
159 VRect clip(0, 0, int(surface.drawRegionWidth()),
160 int(surface.drawRegionHeight()));
161 mRootLayer->preprocess(clip);
163 VPainter painter(&mSurface);
164 // set sub surface area for drawing.
165 painter.setDrawRegion(
166 VRect(int(surface.drawRegionPosX()), int(surface.drawRegionPosY()),
167 int(surface.drawRegionWidth()), int(surface.drawRegionHeight())));
168 mRootLayer->render(&painter, {}, {}, mSurfaceCache);
173 void renderer::Mask::update(int frameNo, const VMatrix &parentMatrix,
174 float /*parentAlpha*/, const DirtyFlag &flag)
176 bool dirtyPath = false;
178 if (flag.testFlag(DirtyFlagBit::None) && mData->isStatic()) return;
180 if (mData->mShape.isStatic()) {
181 if (mLocalPath.empty()) {
183 mData->mShape.value(frameNo, mLocalPath);
187 mData->mShape.value(frameNo, mLocalPath);
189 /* mask item dosen't inherit opacity */
190 mCombinedAlpha = mData->opacity(frameNo);
192 if ( flag.testFlag(DirtyFlagBit::Matrix) || dirtyPath ) {
193 mFinalPath.clone(mLocalPath);
194 mFinalPath.transform(parentMatrix);
195 mRasterRequest = true;
199 VRle renderer::Mask::rle()
201 if (!vCompare(mCombinedAlpha, 1.0f)) {
202 VRle obj = mRasterizer.rle();
203 obj *= uchar(mCombinedAlpha * 255);
206 return mRasterizer.rle();
210 void renderer::Mask::preprocess(const VRect &clip)
213 mRasterizer.rasterize(mFinalPath, FillRule::Winding, clip);
216 void renderer::Layer::render(VPainter *painter, const VRle &inheritMask,
217 const VRle &matteRle, SurfaceCache &)
219 auto renderlist = renderList();
221 if (renderlist.empty()) return;
225 mask = mLayerMask->maskRle(painter->clipBoundingRect());
226 if (!inheritMask.empty()) mask = mask & inheritMask;
227 // if resulting mask is empty then return.
228 if (mask.empty()) return;
233 for (auto &i : renderlist) {
234 painter->setBrush(i->mBrush);
236 if (matteRle.empty()) {
239 painter->drawRle(VPoint(), rle);
242 painter->drawRle(rle, mask);
246 if (!mask.empty()) rle = rle & mask;
248 if (rle.empty()) continue;
249 if (matteType() == model::MatteType::AlphaInv) {
250 rle = rle - matteRle;
251 painter->drawRle(VPoint(), rle);
253 // render with matteRle as clip.
254 painter->drawRle(rle, matteRle);
260 void renderer::LayerMask::preprocess(const VRect &clip)
262 for (auto &i : mMasks) {
267 renderer::LayerMask::LayerMask(model::Layer *layerData)
269 if (!layerData->mExtra) return;
271 mMasks.reserve(layerData->mExtra->mMasks.size());
273 for (auto &i : layerData->mExtra->mMasks) {
274 mMasks.emplace_back(i);
275 mStatic &= i->isStatic();
279 void renderer::LayerMask::update(int frameNo, const VMatrix &parentMatrix,
280 float parentAlpha, const DirtyFlag &flag)
282 if (flag.testFlag(DirtyFlagBit::None) && isStatic()) return;
284 for (auto &i : mMasks) {
285 i.update(frameNo, parentMatrix, parentAlpha, flag);
290 VRle renderer::LayerMask::maskRle(const VRect &clipRect)
292 if (!mDirty) return mRle;
295 for (auto &e : mMasks) {
296 const auto cur = [&]() {
298 return clipRect - e.rle();
303 switch (e.maskMode()) {
304 case model::Mask::Mode::Add: {
308 case model::Mask::Mode::Substarct: {
309 if (rle.empty() && !clipRect.empty())
310 rle = clipRect - cur;
315 case model::Mask::Mode::Intersect: {
316 if (rle.empty() && !clipRect.empty())
317 rle = clipRect & cur;
322 case model::Mask::Mode::Difference: {
331 if (!rle.empty() && !rle.unique()) {
340 renderer::Layer::Layer(model::Layer *layerData) : mLayerData(layerData)
342 if (mLayerData->mHasMask)
343 mLayerMask = std::make_unique<renderer::LayerMask>(mLayerData);
346 bool renderer::Layer::resolveKeyPath(LOTKeyPath &keyPath, uint depth,
349 if (!keyPath.matches(name(), depth)) {
353 if (!keyPath.skip(name())) {
354 if (keyPath.fullyResolvesTo(name(), depth) &&
355 transformProp(value.property())) {
356 //@TODO handle propery update.
362 bool renderer::ShapeLayer::resolveKeyPath(LOTKeyPath &keyPath, uint depth,
365 if (renderer::Layer::resolveKeyPath(keyPath, depth, value)) {
366 if (keyPath.propagate(name(), depth)) {
367 uint newDepth = keyPath.nextDepth(name(), depth);
368 mRoot->resolveKeyPath(keyPath, newDepth, value);
375 bool renderer::CompLayer::resolveKeyPath(LOTKeyPath &keyPath, uint depth,
378 if (renderer::Layer::resolveKeyPath(keyPath, depth, value)) {
379 if (keyPath.propagate(name(), depth)) {
380 uint newDepth = keyPath.nextDepth(name(), depth);
381 for (const auto &layer : mLayers) {
382 layer->resolveKeyPath(keyPath, newDepth, value);
390 void renderer::Layer::update(int frameNumber, const VMatrix &parentMatrix,
393 mFrameNo = frameNumber;
394 // 1. check if the layer is part of the current frame
395 if (!visible()) return;
397 float alpha = parentAlpha * opacity(frameNo());
398 if (vIsZero(alpha)) {
403 // 2. calculate the parent matrix and alpha
404 VMatrix m = matrix(frameNo());
407 // 3. update the dirty flag based on the change
408 if (mCombinedMatrix != m) {
409 mDirtyFlag |= DirtyFlagBit::Matrix;
413 if (!vCompare(mCombinedAlpha, alpha)) {
414 mDirtyFlag |= DirtyFlagBit::Alpha;
415 mCombinedAlpha = alpha;
418 // 4. update the mask
420 mLayerMask->update(frameNo(), mCombinedMatrix, mCombinedAlpha,
424 // 5. if no parent property change and layer is static then nothing to do.
425 if (!mLayerData->precompLayer() && flag().testFlag(DirtyFlagBit::None) &&
429 // 6. update the content of the layer
432 // 7. reset the dirty flag
433 mDirtyFlag = DirtyFlagBit::None;
436 VMatrix renderer::Layer::matrix(int frameNo) const
439 ? (mLayerData->matrix(frameNo) * mParentLayer->matrix(frameNo))
440 : mLayerData->matrix(frameNo);
443 bool renderer::Layer::visible() const
445 return (frameNo() >= mLayerData->inFrame() &&
446 frameNo() < mLayerData->outFrame());
449 void renderer::Layer::preprocess(const VRect &clip)
451 // layer dosen't contribute to the frame
452 if (skipRendering()) return;
454 // preprocess layer masks
455 if (mLayerMask) mLayerMask->preprocess(clip);
457 preprocessStage(clip);
460 renderer::CompLayer::CompLayer(model::Layer *layerModel, VArenaAlloc *allocator)
461 : renderer::Layer(layerModel)
463 if (!mLayerData->mChildren.empty())
464 mLayers.reserve(mLayerData->mChildren.size());
466 // 1. keep the layer in back-to-front order.
467 // as lottie model keeps the data in front-toback-order.
468 for (auto it = mLayerData->mChildren.crbegin();
469 it != mLayerData->mChildren.rend(); ++it) {
470 auto model = static_cast<model::Layer *>(*it);
471 auto item = createLayerItem(model, allocator);
472 if (item) mLayers.push_back(item);
475 // 2. update parent layer
476 for (const auto &layer : mLayers) {
477 int id = layer->parentId();
480 std::find_if(mLayers.begin(), mLayers.end(),
481 [id](const auto &val) { return val->id() == id; });
482 if (search != mLayers.end()) layer->setParentLayer(*search);
486 // 4. check if its a nested composition
487 if (!layerModel->layerSize().empty()) {
488 mClipper = std::make_unique<renderer::Clipper>(layerModel->layerSize());
491 if (mLayers.size() > 1) setComplexContent(true);
494 void renderer::CompLayer::render(VPainter *painter, const VRle &inheritMask,
495 const VRle &matteRle, SurfaceCache &cache)
497 if (vIsZero(combinedAlpha())) return;
499 if (vCompare(combinedAlpha(), 1.0)) {
500 renderHelper(painter, inheritMask, matteRle, cache);
502 if (complexContent()) {
503 VSize size = painter->clipBoundingRect().size();
505 VBitmap srcBitmap = cache.make_surface(size.width(), size.height());
506 srcPainter.begin(&srcBitmap);
507 renderHelper(&srcPainter, inheritMask, matteRle, cache);
509 painter->drawBitmap(VPoint(), srcBitmap,
510 uchar(combinedAlpha() * 255.0f));
511 cache.release_surface(srcBitmap);
513 renderHelper(painter, inheritMask, matteRle, cache);
518 void renderer::CompLayer::renderHelper(VPainter * painter,
519 const VRle & inheritMask,
520 const VRle & matteRle,
525 mask = mLayerMask->maskRle(painter->clipBoundingRect());
526 if (!inheritMask.empty()) mask = mask & inheritMask;
527 // if resulting mask is empty then return.
528 if (mask.empty()) return;
534 mask = mClipper->rle(mask);
535 if (mask.empty()) return;
538 renderer::Layer *matte = nullptr;
539 for (const auto &layer : mLayers) {
540 if (layer->hasMatte()) {
543 if (layer->visible()) {
545 if (matte->visible())
546 renderMatteLayer(painter, mask, matteRle, matte, layer,
549 layer->render(painter, mask, matteRle, cache);
557 void renderer::CompLayer::renderMatteLayer(VPainter *painter, const VRle &mask,
558 const VRle & matteRle,
559 renderer::Layer *layer,
560 renderer::Layer *src,
561 SurfaceCache & cache)
563 VSize size = painter->clipBoundingRect().size();
564 // Decide if we can use fast matte.
565 // 1. draw src layer to matte buffer
567 VBitmap srcBitmap = cache.make_surface(size.width(), size.height());
568 srcPainter.begin(&srcBitmap);
569 src->render(&srcPainter, mask, matteRle, cache);
572 // 2. draw layer to layer buffer
573 VPainter layerPainter;
574 VBitmap layerBitmap = cache.make_surface(size.width(), size.height());
575 layerPainter.begin(&layerBitmap);
576 layer->render(&layerPainter, mask, matteRle, cache);
578 // 2.1update composition mode
579 switch (layer->matteType()) {
580 case model::MatteType::Alpha:
581 case model::MatteType::Luma: {
582 layerPainter.setBlendMode(BlendMode::DestIn);
585 case model::MatteType::AlphaInv:
586 case model::MatteType::LumaInv: {
587 layerPainter.setBlendMode(BlendMode::DestOut);
594 // 2.2 update srcBuffer if the matte is luma type
595 if (layer->matteType() == model::MatteType::Luma ||
596 layer->matteType() == model::MatteType::LumaInv) {
597 srcBitmap.updateLuma();
600 auto clip = layerPainter.clipBoundingRect();
602 // if the layer has only one renderer then use it as the clip rect
603 // when blending 2 buffer and copy back to final buffer to avoid
604 // unnecessary pixel processing.
605 if (layer->renderList().size() == 1)
607 clip = layer->renderList()[0]->rle().boundingRect();
610 // 2.3 draw src buffer as mask
611 layerPainter.drawBitmap(clip, srcBitmap, clip);
613 // 3. draw the result buffer into painter
614 painter->drawBitmap(clip, layerBitmap, clip);
616 cache.release_surface(srcBitmap);
617 cache.release_surface(layerBitmap);
620 void renderer::Clipper::update(const VMatrix &matrix)
623 mPath.addRect(VRectF(0, 0, mSize.width(), mSize.height()));
624 mPath.transform(matrix);
625 mRasterRequest = true;
628 void renderer::Clipper::preprocess(const VRect &clip)
630 if (mRasterRequest) mRasterizer.rasterize(mPath, FillRule::Winding, clip);
632 mRasterRequest = false;
635 VRle renderer::Clipper::rle(const VRle &mask)
637 if (mask.empty()) return mRasterizer.rle();
639 mMaskedRle.clone(mask);
640 mMaskedRle &= mRasterizer.rle();
644 void renderer::CompLayer::updateContent()
646 if (mClipper && flag().testFlag(DirtyFlagBit::Matrix)) {
647 mClipper->update(combinedMatrix());
649 int mappedFrame = mLayerData->timeRemap(frameNo());
650 float alpha = combinedAlpha();
651 if (complexContent()) alpha = 1;
652 for (const auto &layer : mLayers) {
653 layer->update(mappedFrame, combinedMatrix(), alpha);
657 void renderer::CompLayer::preprocessStage(const VRect &clip)
659 // if layer has clipper
660 if (mClipper) mClipper->preprocess(clip);
662 renderer::Layer *matte = nullptr;
663 for (const auto &layer : mLayers) {
664 if (layer->hasMatte()) {
667 if (layer->visible()) {
669 if (matte->visible()) {
670 layer->preprocess(clip);
671 matte->preprocess(clip);
674 layer->preprocess(clip);
682 renderer::SolidLayer::SolidLayer(model::Layer *layerData)
683 : renderer::Layer(layerData)
685 mDrawableList = &mRenderNode;
688 void renderer::SolidLayer::updateContent()
690 if (flag() & DirtyFlagBit::Matrix) {
692 mPath.addRect(VRectF(0, 0, mLayerData->layerSize().width(),
693 mLayerData->layerSize().height()));
694 mPath.transform(combinedMatrix());
695 mRenderNode.mFlag |= VDrawable::DirtyState::Path;
696 mRenderNode.mPath = mPath;
698 if (flag() & DirtyFlagBit::Alpha) {
699 model::Color color = mLayerData->solidColor();
700 VBrush brush(color.toColor(combinedAlpha()));
701 mRenderNode.setBrush(brush);
702 mRenderNode.mFlag |= VDrawable::DirtyState::Brush;
706 void renderer::SolidLayer::preprocessStage(const VRect &clip)
708 mRenderNode.preprocess(clip);
711 renderer::DrawableList renderer::SolidLayer::renderList()
713 if (skipRendering()) return {};
715 return {&mDrawableList, 1};
718 renderer::ImageLayer::ImageLayer(model::Layer *layerData)
719 : renderer::Layer(layerData)
721 mDrawableList = &mRenderNode;
723 if (!mLayerData->asset()) return;
725 mTexture.mBitmap = mLayerData->asset()->bitmap();
726 VBrush brush(&mTexture);
727 mRenderNode.setBrush(brush);
730 void renderer::ImageLayer::updateContent()
732 if (!mLayerData->asset()) return;
734 if (flag() & DirtyFlagBit::Matrix) {
736 mPath.addRect(VRectF(0, 0, mLayerData->asset()->mWidth,
737 mLayerData->asset()->mHeight));
738 mPath.transform(combinedMatrix());
739 mRenderNode.mFlag |= VDrawable::DirtyState::Path;
740 mRenderNode.mPath = mPath;
741 mTexture.mMatrix = combinedMatrix();
744 if (flag() & DirtyFlagBit::Alpha) {
745 mTexture.mAlpha = int(combinedAlpha() * 255);
749 void renderer::ImageLayer::preprocessStage(const VRect &clip)
751 mRenderNode.preprocess(clip);
754 renderer::DrawableList renderer::ImageLayer::renderList()
756 if (skipRendering()) return {};
758 return {&mDrawableList, 1};
761 renderer::NullLayer::NullLayer(model::Layer *layerData)
762 : renderer::Layer(layerData)
765 void renderer::NullLayer::updateContent() {}
767 static renderer::Object *createContentItem(model::Object *contentData,
768 VArenaAlloc * allocator)
770 switch (contentData->type()) {
771 case model::Object::Type::Group: {
772 return allocator->make<renderer::Group>(
773 static_cast<model::Group *>(contentData), allocator);
775 case model::Object::Type::Rect: {
776 return allocator->make<renderer::Rect>(
777 static_cast<model::Rect *>(contentData));
779 case model::Object::Type::Ellipse: {
780 return allocator->make<renderer::Ellipse>(
781 static_cast<model::Ellipse *>(contentData));
783 case model::Object::Type::Path: {
784 return allocator->make<renderer::Path>(
785 static_cast<model::Path *>(contentData));
787 case model::Object::Type::Polystar: {
788 return allocator->make<renderer::Polystar>(
789 static_cast<model::Polystar *>(contentData));
791 case model::Object::Type::Fill: {
792 return allocator->make<renderer::Fill>(
793 static_cast<model::Fill *>(contentData));
795 case model::Object::Type::GFill: {
796 return allocator->make<renderer::GradientFill>(
797 static_cast<model::GradientFill *>(contentData));
799 case model::Object::Type::Stroke: {
800 return allocator->make<renderer::Stroke>(
801 static_cast<model::Stroke *>(contentData));
803 case model::Object::Type::GStroke: {
804 return allocator->make<renderer::GradientStroke>(
805 static_cast<model::GradientStroke *>(contentData));
807 case model::Object::Type::Repeater: {
808 return allocator->make<renderer::Repeater>(
809 static_cast<model::Repeater *>(contentData), allocator);
811 case model::Object::Type::Trim: {
812 return allocator->make<renderer::Trim>(
813 static_cast<model::Trim *>(contentData));
821 renderer::ShapeLayer::ShapeLayer(model::Layer *layerData,
822 VArenaAlloc * allocator)
823 : renderer::Layer(layerData),
824 mRoot(allocator->make<renderer::Group>(nullptr, allocator))
826 mRoot->addChildren(layerData, allocator);
828 std::vector<renderer::Shape *> list;
829 mRoot->processPaintItems(list);
831 if (layerData->hasPathOperator()) {
833 mRoot->processTrimItems(list);
837 void renderer::ShapeLayer::updateContent()
839 mRoot->update(frameNo(), combinedMatrix(), combinedAlpha(), flag());
841 if (mLayerData->hasPathOperator()) {
846 void renderer::ShapeLayer::preprocessStage(const VRect &clip)
848 mDrawableList.clear();
849 mRoot->renderList(mDrawableList);
851 for (auto &drawable : mDrawableList) drawable->preprocess(clip);
854 renderer::DrawableList renderer::ShapeLayer::renderList()
856 if (skipRendering()) return {};
858 mDrawableList.clear();
859 mRoot->renderList(mDrawableList);
861 if (mDrawableList.empty()) return {};
863 return {mDrawableList.data(), mDrawableList.size()};
866 bool renderer::Group::resolveKeyPath(LOTKeyPath &keyPath, uint depth,
869 if (!keyPath.skip(name())) {
870 if (!keyPath.matches(mModel.name(), depth)) {
874 if (!keyPath.skip(mModel.name())) {
875 if (keyPath.fullyResolvesTo(mModel.name(), depth) &&
876 transformProp(value.property())) {
877 mModel.filter()->addValue(value);
882 if (keyPath.propagate(name(), depth)) {
883 uint newDepth = keyPath.nextDepth(name(), depth);
884 for (auto &child : mContents) {
885 child->resolveKeyPath(keyPath, newDepth, value);
891 bool renderer::Fill::resolveKeyPath(LOTKeyPath &keyPath, uint depth,
894 if (!keyPath.matches(mModel.name(), depth)) {
898 if (keyPath.fullyResolvesTo(mModel.name(), depth) &&
899 fillProp(value.property())) {
900 mModel.filter()->addValue(value);
906 bool renderer::Stroke::resolveKeyPath(LOTKeyPath &keyPath, uint depth,
909 if (!keyPath.matches(mModel.name(), depth)) {
913 if (keyPath.fullyResolvesTo(mModel.name(), depth) &&
914 strokeProp(value.property())) {
915 mModel.filter()->addValue(value);
921 renderer::Group::Group(model::Group *data, VArenaAlloc *allocator)
924 addChildren(data, allocator);
927 void renderer::Group::addChildren(model::Group *data, VArenaAlloc *allocator)
931 if (!data->mChildren.empty()) mContents.reserve(data->mChildren.size());
933 // keep the content in back-to-front order.
934 // as lottie model keeps it in front-to-back order.
935 for (auto it = data->mChildren.crbegin(); it != data->mChildren.rend();
937 auto content = createContentItem(*it, allocator);
939 mContents.push_back(content);
944 void renderer::Group::update(int frameNo, const VMatrix &parentMatrix,
945 float parentAlpha, const DirtyFlag &flag)
947 DirtyFlag newFlag = flag;
950 if (mModel.hasModel() && mModel.transform()) {
951 VMatrix m = mModel.matrix(frameNo);
954 if (!(flag & DirtyFlagBit::Matrix) && !mModel.transform()->isStatic() &&
956 newFlag |= DirtyFlagBit::Matrix;
961 alpha = parentAlpha * mModel.transform()->opacity(frameNo);
962 if (!vCompare(alpha, parentAlpha)) {
963 newFlag |= DirtyFlagBit::Alpha;
966 mMatrix = parentMatrix;
970 for (const auto &content : mContents) {
971 content->update(frameNo, matrix(), alpha, newFlag);
975 void renderer::Group::applyTrim()
977 for (auto i = mContents.rbegin(); i != mContents.rend(); ++i) {
979 switch (content->type()) {
980 case renderer::Object::Type::Trim: {
981 static_cast<renderer::Trim *>(content)->update();
984 case renderer::Object::Type::Group: {
985 static_cast<renderer::Group *>(content)->applyTrim();
994 void renderer::Group::renderList(std::vector<VDrawable *> &list)
996 for (const auto &content : mContents) {
997 content->renderList(list);
1001 void renderer::Group::processPaintItems(std::vector<renderer::Shape *> &list)
1003 size_t curOpCount = list.size();
1004 for (auto i = mContents.rbegin(); i != mContents.rend(); ++i) {
1005 auto content = (*i);
1006 switch (content->type()) {
1007 case renderer::Object::Type::Shape: {
1008 auto pathItem = static_cast<renderer::Shape *>(content);
1009 pathItem->setParent(this);
1010 list.push_back(pathItem);
1013 case renderer::Object::Type::Paint: {
1014 static_cast<renderer::Paint *>(content)->addPathItems(list,
1018 case renderer::Object::Type::Group: {
1019 static_cast<renderer::Group *>(content)->processPaintItems(list);
1028 void renderer::Group::processTrimItems(std::vector<renderer::Shape *> &list)
1030 size_t curOpCount = list.size();
1031 for (auto i = mContents.rbegin(); i != mContents.rend(); ++i) {
1032 auto content = (*i);
1034 switch (content->type()) {
1035 case renderer::Object::Type::Shape: {
1036 list.push_back(static_cast<renderer::Shape *>(content));
1039 case renderer::Object::Type::Trim: {
1040 static_cast<renderer::Trim *>(content)->addPathItems(list,
1044 case renderer::Object::Type::Group: {
1045 static_cast<renderer::Group *>(content)->processTrimItems(list);
1055 * renderer::Shape uses 2 path objects for path object reuse.
1056 * mLocalPath - keeps track of the local path of the item before
1057 * applying path operation and transformation.
1058 * mTemp - keeps a referece to the mLocalPath and can be updated by the
1059 * path operation objects(trim, merge path),
1060 * We update the DirtyPath flag if the path needs to be updated again
1061 * beacuse of local path or matrix or some path operation has changed which
1062 * affects the final path.
1063 * The PaintObject queries the dirty flag to check if it needs to compute the
1064 * final path again and calls finalPath() api to do the same.
1065 * finalPath() api passes a result Object so that we keep only one copy of
1066 * the path object in the paintItem (for memory efficiency).
1067 * NOTE: As path objects are COW objects we have to be
1068 * carefull about the refcount so that we don't generate deep copy while
1069 * modifying the path objects.
1071 void renderer::Shape::update(int frameNo, const VMatrix &, float,
1072 const DirtyFlag &flag)
1076 // 1. update the local path if needed
1077 if (hasChanged(frameNo)) {
1078 // loose the reference to mLocalPath if any
1079 // from the last frame update.
1082 updatePath(mLocalPath, frameNo);
1085 // 2. keep a reference path in temp in case there is some
1086 // path operation like trim which will update the path.
1087 // we don't want to update the local path.
1090 // 3. mark the path dirty if matrix has changed.
1091 if (flag & DirtyFlagBit::Matrix) {
1096 void renderer::Shape::finalPath(VPath &result)
1098 result.addPath(mTemp, static_cast<renderer::Group *>(parent())->matrix());
1101 renderer::Rect::Rect(model::Rect *data)
1102 : renderer::Shape(data->isStatic()), mData(data)
1106 void renderer::Rect::updatePath(VPath &path, int frameNo)
1108 VPointF pos = mData->mPos.value(frameNo);
1109 VPointF size = mData->mSize.value(frameNo);
1110 float roundness = mData->roundness(frameNo);
1111 VRectF r(pos.x() - size.x() / 2, pos.y() - size.y() / 2, size.x(),
1115 path.addRoundRect(r, roundness, mData->direction());
1118 renderer::Ellipse::Ellipse(model::Ellipse *data)
1119 : renderer::Shape(data->isStatic()), mData(data)
1123 void renderer::Ellipse::updatePath(VPath &path, int frameNo)
1125 VPointF pos = mData->mPos.value(frameNo);
1126 VPointF size = mData->mSize.value(frameNo);
1127 VRectF r(pos.x() - size.x() / 2, pos.y() - size.y() / 2, size.x(),
1131 path.addOval(r, mData->direction());
1134 renderer::Path::Path(model::Path *data)
1135 : renderer::Shape(data->isStatic()), mData(data)
1139 void renderer::Path::updatePath(VPath &path, int frameNo)
1141 mData->mShape.value(frameNo, path);
1144 renderer::Polystar::Polystar(model::Polystar *data)
1145 : renderer::Shape(data->isStatic()), mData(data)
1149 void renderer::Polystar::updatePath(VPath &path, int frameNo)
1151 VPointF pos = mData->mPos.value(frameNo);
1152 float points = mData->mPointCount.value(frameNo);
1153 float innerRadius = mData->mInnerRadius.value(frameNo);
1154 float outerRadius = mData->mOuterRadius.value(frameNo);
1155 float innerRoundness = mData->mInnerRoundness.value(frameNo);
1156 float outerRoundness = mData->mOuterRoundness.value(frameNo);
1157 float rotation = mData->mRotation.value(frameNo);
1162 if (mData->mPolyType == model::Polystar::PolyType::Star) {
1163 path.addPolystar(points, innerRadius, outerRadius, innerRoundness,
1164 outerRoundness, 0.0, 0.0, 0.0, mData->direction());
1166 path.addPolygon(points, outerRadius, outerRoundness, 0.0, 0.0, 0.0,
1167 mData->direction());
1170 m.translate(pos.x(), pos.y()).rotate(rotation);
1176 * PaintData Node handling
1179 renderer::Paint::Paint(bool staticContent) : mStaticContent(staticContent) {}
1181 void renderer::Paint::update(int frameNo, const VMatrix &parentMatrix,
1182 float parentAlpha, const DirtyFlag & /*flag*/)
1184 mRenderNodeUpdate = true;
1185 mContentToRender = updateContent(frameNo, parentMatrix, parentAlpha);
1188 void renderer::Paint::updateRenderNode()
1191 for (auto &i : mPathItems) {
1200 for (const auto &i : mPathItems) {
1201 i->finalPath(mPath);
1203 mDrawable.setPath(mPath);
1205 if (mDrawable.mFlag & VDrawable::DirtyState::Path)
1206 mDrawable.mPath = mPath;
1210 void renderer::Paint::renderList(std::vector<VDrawable *> &list)
1212 if (mRenderNodeUpdate) {
1214 mRenderNodeUpdate = false;
1217 // Q: Why we even update the final path if we don't have content
1219 // Ans: We update the render nodes because we will loose the
1220 // dirty path information at end of this frame.
1221 // so if we return early without updating the final path.
1222 // in the subsequent frame when we have content to render but
1223 // we may not able to update our final path properly as we
1224 // don't know what paths got changed in between.
1225 if (mContentToRender) list.push_back(&mDrawable);
1228 void renderer::Paint::addPathItems(std::vector<renderer::Shape *> &list,
1231 std::copy(list.begin() + startOffset, list.end(),
1232 back_inserter(mPathItems));
1235 renderer::Fill::Fill(model::Fill *data)
1236 : renderer::Paint(data->isStatic()), mModel(data)
1238 mDrawable.setName(mModel.name());
1241 bool renderer::Fill::updateContent(int frameNo, const VMatrix &, float alpha)
1243 auto combinedAlpha = alpha * mModel.opacity(frameNo);
1244 auto color = mModel.color(frameNo).toColor(combinedAlpha);
1246 VBrush brush(color);
1247 mDrawable.setBrush(brush);
1248 mDrawable.setFillRule(mModel.fillRule());
1250 return !color.isTransparent();
1253 renderer::GradientFill::GradientFill(model::GradientFill *data)
1254 : renderer::Paint(data->isStatic()), mData(data)
1256 mDrawable.setName(mData->name());
1259 bool renderer::GradientFill::updateContent(int frameNo, const VMatrix &matrix,
1262 float combinedAlpha = alpha * mData->opacity(frameNo);
1264 mData->update(mGradient, frameNo);
1265 mGradient->setAlpha(combinedAlpha);
1266 mGradient->mMatrix = matrix;
1267 mDrawable.setBrush(VBrush(mGradient.get()));
1268 mDrawable.setFillRule(mData->fillRule());
1270 return !vIsZero(combinedAlpha);
1273 renderer::Stroke::Stroke(model::Stroke *data)
1274 : renderer::Paint(data->isStatic()), mModel(data)
1276 mDrawable.setName(mModel.name());
1277 if (mModel.hasDashInfo()) {
1278 mDrawable.setType(VDrawable::Type::StrokeWithDash);
1280 mDrawable.setType(VDrawable::Type::Stroke);
1284 static vthread_local std::vector<float> Dash_Vector;
1286 bool renderer::Stroke::updateContent(int frameNo, const VMatrix &matrix,
1289 auto combinedAlpha = alpha * mModel.opacity(frameNo);
1290 auto color = mModel.color(frameNo).toColor(combinedAlpha);
1292 VBrush brush(color);
1293 mDrawable.setBrush(brush);
1294 float scale = matrix.scale();
1295 mDrawable.setStrokeInfo(mModel.capStyle(), mModel.joinStyle(),
1296 mModel.miterLimit(),
1297 mModel.strokeWidth(frameNo) * scale);
1299 if (mModel.hasDashInfo()) {
1300 Dash_Vector.clear();
1301 mModel.getDashInfo(frameNo, Dash_Vector);
1302 if (!Dash_Vector.empty()) {
1303 for (auto &elm : Dash_Vector) elm *= scale;
1304 mDrawable.setDashInfo(Dash_Vector);
1308 return !color.isTransparent();
1311 renderer::GradientStroke::GradientStroke(model::GradientStroke *data)
1312 : renderer::Paint(data->isStatic()), mData(data)
1314 mDrawable.setName(mData->name());
1315 if (mData->hasDashInfo()) {
1316 mDrawable.setType(VDrawable::Type::StrokeWithDash);
1318 mDrawable.setType(VDrawable::Type::Stroke);
1322 bool renderer::GradientStroke::updateContent(int frameNo, const VMatrix &matrix,
1325 float combinedAlpha = alpha * mData->opacity(frameNo);
1327 mData->update(mGradient, frameNo);
1328 mGradient->setAlpha(combinedAlpha);
1329 mGradient->mMatrix = matrix;
1330 auto scale = mGradient->mMatrix.scale();
1331 mDrawable.setBrush(VBrush(mGradient.get()));
1332 mDrawable.setStrokeInfo(mData->capStyle(), mData->joinStyle(),
1333 mData->miterLimit(), mData->width(frameNo) * scale);
1335 if (mData->hasDashInfo()) {
1336 Dash_Vector.clear();
1337 mData->getDashInfo(frameNo, Dash_Vector);
1338 if (!Dash_Vector.empty()) {
1339 for (auto &elm : Dash_Vector) elm *= scale;
1340 mDrawable.setDashInfo(Dash_Vector);
1344 return !vIsZero(combinedAlpha);
1347 void renderer::Trim::update(int frameNo, const VMatrix & /*parentMatrix*/,
1348 float /*parentAlpha*/, const DirtyFlag & /*flag*/)
1352 if (mCache.mFrameNo == frameNo) return;
1354 model::Trim::Segment segment = mData->segment(frameNo);
1356 if (!(vCompare(mCache.mSegment.start, segment.start) &&
1357 vCompare(mCache.mSegment.end, segment.end))) {
1359 mCache.mSegment = segment;
1361 mCache.mFrameNo = frameNo;
1364 void renderer::Trim::update()
1366 // when both path and trim are not dirty
1367 if (!(mDirty || pathDirty())) return;
1369 if (vCompare(mCache.mSegment.start, mCache.mSegment.end)) {
1370 for (auto &i : mPathItems) {
1371 i->updatePath(VPath());
1376 if (vCompare(std::fabs(mCache.mSegment.start - mCache.mSegment.end), 1)) {
1377 for (auto &i : mPathItems) {
1378 i->updatePath(i->localPath());
1383 if (mData->type() == model::Trim::TrimType::Simultaneously) {
1384 for (auto &i : mPathItems) {
1385 mPathMesure.setRange(mCache.mSegment.start, mCache.mSegment.end);
1386 i->updatePath(mPathMesure.trim(i->localPath()));
1388 } else { // model::Trim::TrimType::Individually
1389 float totalLength = 0.0;
1390 for (auto &i : mPathItems) {
1391 totalLength += i->localPath().length();
1393 float start = totalLength * mCache.mSegment.start;
1394 float end = totalLength * mCache.mSegment.end;
1398 for (auto &i : mPathItems) {
1400 // update with empty path.
1401 i->updatePath(VPath());
1404 float len = i->localPath().length();
1406 if (curLen < start && curLen + len < start) {
1408 // update with empty path.
1409 i->updatePath(VPath());
1411 } else if (start <= curLen && end >= curLen + len) {
1416 float local_start = start > curLen ? start - curLen : 0;
1418 float local_end = curLen + len < end ? len : end - curLen;
1420 mPathMesure.setRange(local_start, local_end);
1421 i->updatePath(mPathMesure.trim(i->localPath()));
1429 void renderer::Trim::addPathItems(std::vector<renderer::Shape *> &list,
1432 std::copy(list.begin() + startOffset, list.end(),
1433 back_inserter(mPathItems));
1436 renderer::Repeater::Repeater(model::Repeater *data, VArenaAlloc *allocator)
1437 : mRepeaterData(data)
1439 assert(mRepeaterData->content());
1441 mCopies = mRepeaterData->maxCopies();
1443 for (int i = 0; i < mCopies; i++) {
1444 auto content = allocator->make<renderer::Group>(
1445 mRepeaterData->content(), allocator);
1446 // content->setParent(this);
1447 mContents.push_back(content);
1451 void renderer::Repeater::update(int frameNo, const VMatrix &parentMatrix,
1452 float parentAlpha, const DirtyFlag &flag)
1454 DirtyFlag newFlag = flag;
1456 float copies = mRepeaterData->copies(frameNo);
1457 int visibleCopies = int(copies);
1459 if (visibleCopies == 0) {
1466 if (!mRepeaterData->isStatic()) newFlag |= DirtyFlagBit::Matrix;
1468 float offset = mRepeaterData->offset(frameNo);
1469 float startOpacity = mRepeaterData->mTransform.startOpacity(frameNo);
1470 float endOpacity = mRepeaterData->mTransform.endOpacity(frameNo);
1472 newFlag |= DirtyFlagBit::Alpha;
1474 for (int i = 0; i < mCopies; ++i) {
1476 parentAlpha * lerp(startOpacity, endOpacity, i / copies);
1478 // hide rest of the copies , @TODO find a better solution.
1479 if (i >= visibleCopies) newAlpha = 0;
1481 VMatrix result = mRepeaterData->mTransform.matrix(frameNo, i + offset) *
1483 mContents[i]->update(frameNo, result, newAlpha, newFlag);
1487 void renderer::Repeater::renderList(std::vector<VDrawable *> &list)
1489 if (mHidden) return;
1490 return renderer::Group::renderList(list);