lottie/trim: fix regression, always modify the path in trim::update() call.
[platform/core/uifw/lottie-player.git] / src / lottie / lottieitem.cpp
index 84945b6..a882a0a 100644 (file)
@@ -80,10 +80,9 @@ bool LOTCompItem::update(int frameNo)
     float ty = (viewPort.height() - viewBox.height() * scale) * 0.5;
 
     VMatrix m;
-    m.scale(scale, scale).translate(tx, ty);
+    m.translate(tx, ty).scale(scale, scale);
     mRootLayer->update(frameNo, m, 1.0);
 
-    buildRenderList();
     mCurFrameNo = frameNo;
     mUpdateViewBox = false;
     return true;
@@ -98,7 +97,7 @@ void LOTCompItem::buildRenderList()
     for (auto &i : mDrawableList) {
         LOTDrawable *lotDrawable = static_cast<LOTDrawable *>(i);
         lotDrawable->sync();
-        mRenderList.push_back(&lotDrawable->mCNode);
+        mRenderList.push_back(lotDrawable->mCNode.get());
     }
 }
 
@@ -107,16 +106,19 @@ const std::vector<LOTNode *> &LOTCompItem::renderList() const
     return mRenderList;
 }
 
-bool LOTCompItem::render(const LOTBuffer &buffer)
+bool LOTCompItem::render(const lottie::Surface &surface)
 {
-    VBitmap bitmap((uchar *)buffer.buffer, buffer.width, buffer.height,
-                   buffer.bytesPerLine, VBitmap::Format::ARGB32_Premultiplied,
+    VBitmap bitmap((uchar *)surface.buffer(), surface.width(), surface.height(),
+                   surface.bytesPerLine(), VBitmap::Format::ARGB32_Premultiplied,
                    nullptr, nullptr);
 
     /* schedule all preprocess task for this frame at once.
      */
+    mDrawableList.clear();
+    mRootLayer->renderList(mDrawableList);
+    VRect clip(0, 0, surface.width(), surface.height());
     for (auto &e : mDrawableList) {
-        e->preprocess();
+        e->preprocess(clip);
     }
 
     VPainter painter(&bitmap);
@@ -129,7 +131,7 @@ void LOTMaskItem::update(int frameNo, const VMatrix &parentMatrix,
                          float parentAlpha, const DirtyFlag &/*flag*/)
 {
     if (mData->mShape.isStatic()) {
-        if (mLocalPath.isEmpty()) {
+        if (mLocalPath.empty()) {
             mData->mShape.value(frameNo).toPath(mLocalPath);
         }
     } else {
@@ -167,7 +169,7 @@ void LOTLayerItem::render(VPainter *painter, const VRle &inheritMask, const VRle
             matteRle = matteRle + i->rle();
         }
 
-        if (!inheritMatte.isEmpty())
+        if (!inheritMatte.empty())
             matteRle = matteRle & inheritMatte;
     } else {
         matteRle = inheritMatte;
@@ -178,10 +180,10 @@ void LOTLayerItem::render(VPainter *painter, const VRle &inheritMask, const VRle
     VRle mask;
     if (hasMask()) {
         mask = maskRle(painter->clipBoundingRect());
-        if (!inheritMask.isEmpty())
+        if (!inheritMask.empty())
             mask = mask & inheritMask;
         // if resulting mask is empty then return.
-        if (mask.isEmpty())
+        if (mask.empty())
             return;
     } else {
         mask = inheritMask;
@@ -190,11 +192,11 @@ void LOTLayerItem::render(VPainter *painter, const VRle &inheritMask, const VRle
     for (auto &i : mDrawableList) {
         painter->setBrush(i->mBrush);
         VRle rle = i->rle();
-        if (!mask.isEmpty()) rle = rle & mask;
+        if (!mask.empty()) rle = rle & mask;
 
-        if (rle.isEmpty()) continue;
+        if (rle.empty()) continue;
 
-        if (!matteRle.isEmpty()) {
+        if (!matteRle.empty()) {
             if (mLayerData->mMatteType == MatteType::AlphaInv) {
                 rle = rle - matteRle;
             } else {
@@ -215,7 +217,7 @@ VRle LOTLayerItem::maskRle(const VRect &clipRect)
             break;
         }
         case LOTMaskData::Mode::Substarct: {
-            if (rle.isEmpty() && !clipRect.isEmpty())
+            if (rle.empty() && !clipRect.empty())
                 rle = VRle::toRle(clipRect);
             rle = rle - i->rle();
             break;
@@ -253,21 +255,21 @@ void LOTLayerItem::updateStaticProperty()
     mStatic = mPrecompLayer ? (mStatic & mPrecompLayer->isStatic()) : mStatic;
 }
 
-void LOTLayerItem::update(int frameNo, const VMatrix &parentMatrix,
+void LOTLayerItem::update(int frameNumber, const VMatrix &parentMatrix,
                           float parentAlpha)
 {
-    mFrameNo = frameNo;
+    mFrameNo = frameNumber;
     // 1. check if the layer is part of the current frame
     if (!visible()) return;
 
     // 2. calculate the parent matrix and alpha
-    VMatrix m = matrix(frameNo);
+    VMatrix m = matrix(frameNo());
     m *= parentMatrix;
-    float alpha = parentAlpha * opacity(frameNo);
+    float alpha = parentAlpha * opacity(frameNo());
 
     // 6. update the mask
     if (hasMask()) {
-        for (auto &i : mMasks) i->update(frameNo, m, alpha, mDirtyFlag);
+        for (auto &i : mMasks) i->update(frameNo(), m, alpha, mDirtyFlag);
     }
 
     // 3. update the dirty flag based on the change
@@ -298,10 +300,10 @@ float LOTLayerItem::opacity(int frameNo) const
 VMatrix LOTLayerItem::matrix(int frameNo) const
 {
     if (mParentLayer)
-        return mLayerData->mTransform->matrix(frameNo) *
+        return mLayerData->mTransform->matrix(frameNo, mLayerData->autoOrient()) *
                mParentLayer->matrix(frameNo);
     else
-        return mLayerData->mTransform->matrix(frameNo);
+        return mLayerData->mTransform->matrix(frameNo, mLayerData->autoOrient());
 }
 
 bool LOTLayerItem::visible() const
@@ -316,33 +318,36 @@ bool LOTLayerItem::visible() const
 LOTCompLayerItem::LOTCompLayerItem(LOTLayerData *layerModel)
     : LOTLayerItem(layerModel)
 {
+    // 1. create layer item
     for (auto &i : mLayerData->mChildren) {
-        LOTLayerData *layerModel = dynamic_cast<LOTLayerData *>(i.get());
-        if (layerModel) {
-            auto layerItem = LOTCompItem::createLayerItem(layerModel);
-            if (layerItem) mLayers.push_back(std::move(layerItem));
-        }
+        LOTLayerData *layerModel = static_cast<LOTLayerData *>(i.get());
+        auto layerItem = LOTCompItem::createLayerItem(layerModel);
+        if (layerItem) mLayers.push_back(std::move(layerItem));
     }
 
     // 2. update parent layer
-    for (auto &i : mLayers) {
-        int id = i->parentId();
+    for (const auto &layer : mLayers) {
+        int id = layer->parentId();
         if (id >= 0) {
             auto search = std::find_if(mLayers.begin(), mLayers.end(),
                             [id](const auto& val){ return val->id() == id;});
-            if (search != mLayers.end()) i->setParentLayer((*search).get());
+            if (search != mLayers.end()) layer->setParentLayer((*search).get());
         }
         // update the precomp layer if its not the root layer.
-        if (!layerModel->root()) i->setPrecompLayer(this);
+        if (!layerModel->root()) layer->setPrecompLayer(this);
     }
+
+    // 3. keep the layer in back-to-front order.
+    // as lottie model keeps the data in front-toback-order.
+    std::reverse(mLayers.begin(), mLayers.end());
 }
 
 void LOTCompLayerItem::updateStaticProperty()
 {
     LOTLayerItem::updateStaticProperty();
 
-    for (auto &i : mLayers) {
-        i->updateStaticProperty();
+    for (const auto &layer : mLayers) {
+        layer->updateStaticProperty();
     }
 }
 
@@ -356,7 +361,7 @@ void LOTCompLayerItem::render(VPainter *painter, const VRle &inheritMask, const
             matteRle = matteRle + i->rle();
         }
 
-        if (!inheritMatte.isEmpty())
+        if (!inheritMatte.empty())
             matteRle = matteRle & inheritMatte;
     } else {
         matteRle = inheritMatte;
@@ -365,38 +370,38 @@ void LOTCompLayerItem::render(VPainter *painter, const VRle &inheritMask, const
     VRle mask;
     if (hasMask()) {
         mask = maskRle(painter->clipBoundingRect());
-        if (!inheritMask.isEmpty())
+        if (!inheritMask.empty())
             mask = mask & inheritMask;
         // if resulting mask is empty then return.
-        if (mask.isEmpty())
+        if (mask.empty())
             return;
     } else {
         mask = inheritMask;
     }
 
     LOTLayerItem *matteLayer = nullptr;
-    for (auto i = mLayers.rbegin(); i != mLayers.rend(); ++i) {
-        LOTLayerItem *layer = (*i).get();
-
+    for (const auto &layer : mLayers) {
         if (!matteLayer && layer->hasMatte()) {
-            matteLayer = layer;
+            matteLayer = layer.get();
             continue;
         }
 
         if (matteLayer) {
-            matteLayer->render(painter, mask, matteRle, layer);
+            if (matteLayer->visible() && layer->visible())
+                matteLayer->render(painter, mask, matteRle, layer.get());
             matteLayer = nullptr;
         } else {
-            layer->render(painter, mask, matteRle, nullptr);
+            if (layer->visible())
+                layer->render(painter, mask, matteRle, nullptr);
         }
     }
 }
 
 void LOTCompLayerItem::updateContent()
 {
-    // update the layer from back to front
-    for (auto i = mLayers.rbegin(); i != mLayers.rend(); ++i) {
-        (*i)->update(frameNo(), combinedMatrix(), combinedAlpha());
+    for (const auto &layer : mLayers) {
+        layer->update( mLayerData->timeRemap(frameNo()) - mLayerData->startFrame(),
+                       combinedMatrix(), combinedAlpha());
     }
 }
 
@@ -404,9 +409,8 @@ void LOTCompLayerItem::renderList(std::vector<VDrawable *> &list)
 {
     if (!visible()) return;
 
-    // update the layer from back to front
-    for (auto i = mLayers.rbegin(); i != mLayers.rend(); ++i) {
-        (*i)->renderList(list);
+    for (const auto &layer : mLayers) {
+        layer->renderList(list);
     }
 }
 
@@ -459,9 +463,12 @@ LOTShapeLayerItem::LOTShapeLayerItem(LOTLayerData *layerData)
     mRoot->addChildren(layerData);
 
     std::vector<LOTPathDataItem *> list;
-    mRoot->processPathItems(list);
+    mRoot->processPaintItems(list);
 
-    if (layerData->hasPathOperator()) mRoot->processTrimOperation();
+    if (layerData->hasPathOperator()) {
+        list.clear();
+        mRoot->processTrimItems(list);
+    }
 }
 
 std::unique_ptr<LOTContentItem>
@@ -522,6 +529,10 @@ LOTShapeLayerItem::createContentItem(LOTData *contentData)
 void LOTShapeLayerItem::updateContent()
 {
     mRoot->update(frameNo(), combinedMatrix(), combinedAlpha(), flag());
+
+    if (mLayerData->hasPathOperator()) {
+        mRoot->applyTrim();
+    }
 }
 
 void LOTShapeLayerItem::renderList(std::vector<VDrawable *> &list)
@@ -541,8 +552,14 @@ void LOTContentGroupItem::addChildren(LOTGroupData *data)
 
     for (auto &i : data->mChildren) {
         auto content = LOTShapeLayerItem::createContentItem(i.get());
-        if (content) mContents.push_back(std::move(content));
+        if (content) {
+            content->setParent(this);
+            mContents.push_back(std::move(content));
+        }
     }
+
+    // keep the content in back-to-front order.
+    std::reverse(mContents.begin(), mContents.end());
 }
 
 void LOTContentGroupItem::update(int frameNo, const VMatrix &parentMatrix,
@@ -567,107 +584,101 @@ void LOTContentGroupItem::update(int frameNo, const VMatrix &parentMatrix,
         }
     }
 
+    mMatrix = m;
+
+    for (const auto &content : mContents) {
+        content->update(frameNo, m, alpha, newFlag);
+    }
+}
+
+void LOTContentGroupItem::applyTrim()
+{
     for (auto i = mContents.rbegin(); i != mContents.rend(); ++i) {
-        (*i)->update(frameNo, m, alpha, newFlag);
+        auto content = (*i).get();
+        if (auto trim = dynamic_cast<LOTTrimItem *>(content)) {
+            trim->update();
+        } else if (auto group = dynamic_cast<LOTContentGroupItem *>(content)) {
+            group->applyTrim();
+        }
     }
 }
 
 void LOTContentGroupItem::renderList(std::vector<VDrawable *> &list)
 {
-    for (auto i = mContents.rbegin(); i != mContents.rend(); ++i) {
-        (*i)->renderList(list);
+    for (const auto &content : mContents) {
+        content->renderList(list);
     }
 }
 
-void LOTContentGroupItem::processPathItems(
+void LOTContentGroupItem::processPaintItems(
     std::vector<LOTPathDataItem *> &list)
 {
     int curOpCount = list.size();
-    for (auto &i : mContents) {
-        if (auto pathNode = dynamic_cast<LOTPathDataItem *>(i.get())) {
+    for (auto i = mContents.rbegin(); i != mContents.rend(); ++i) {
+        auto content = (*i).get();
+        if (auto pathNode = dynamic_cast<LOTPathDataItem *>(content)) {
             // add it to the list
             list.push_back(pathNode);
-        } else if (auto paintNode = dynamic_cast<LOTPaintDataItem *>(i.get())) {
+        } else if (auto paintNode = dynamic_cast<LOTPaintDataItem *>(content)) {
             // the node is a paint data node update the path list of the paint item.
             paintNode->addPathItems(list, curOpCount);
         } else if (auto groupNode =
-                       dynamic_cast<LOTContentGroupItem *>(i.get())) {
+                       dynamic_cast<LOTContentGroupItem *>(content)) {
             // update the groups node with current list
-            groupNode->processPathItems(list);
+            groupNode->processPaintItems(list);
         }
     }
 }
 
-void LOTContentGroupItem::processTrimOperation()
-{
-    std::vector<LOTTrimItem *> list;
-    trimOperationHelper(list);
-}
-
-void LOTContentGroupItem::trimOperationHelper(std::vector<LOTTrimItem *> &list)
+void LOTContentGroupItem::processTrimItems(
+    std::vector<LOTPathDataItem *> &list)
 {
     int curOpCount = list.size();
     for (auto i = mContents.rbegin(); i != mContents.rend(); ++i) {
-        auto child = (*i).get();
-        if (auto pathNode = dynamic_cast<LOTPathDataItem *>(child)) {
-            // the node is a path data node add the trim operation list to it.
-            pathNode->addTrimOperation(list);
-        } else if (auto trimNode = dynamic_cast<LOTTrimItem *>(child)) {
-            // add it to the trim operation list
-            list.push_back(trimNode);
+        auto content = (*i).get();
+        if (auto pathNode = dynamic_cast<LOTPathDataItem *>(content)) {
+            // add it to the list
+            list.push_back(pathNode);
+        } else if (auto trimNode = dynamic_cast<LOTTrimItem *>(content)) {
+            // the node is a paint data node update the path list of the paint item.
+            trimNode->addPathItems(list, curOpCount);
         } else if (auto groupNode =
-                       dynamic_cast<LOTContentGroupItem *>(child)) {
+                       dynamic_cast<LOTContentGroupItem *>(content)) {
             // update the groups node with current list
-            groupNode->trimOperationHelper(list);
+            groupNode->processTrimItems(list);
         }
     }
-    list.erase(list.begin() + curOpCount, list.end());
 }
 
-void LOTPathDataItem::addTrimOperation(std::vector<LOTTrimItem *> &list)
-{
-    for (auto trimItem : list) {
-        mTrimNodeRefs.push_back(trimItem);
-    }
-}
-
-void LOTPathDataItem::update(int frameNo, const VMatrix &parentMatrix,
+void LOTPathDataItem::update(int frameNo, const VMatrix &,
                              float, const DirtyFlag &flag)
 {
-    VPath tempPath;
-
     mPathChanged = false;
 
     // 1. update the local path if needed
-    if (!(mInit && mStaticPath) && hasChanged(frameNo)) {
+    if (hasChanged(frameNo)) {
         updatePath(mLocalPath, frameNo);
-        mInit = true;
         mPathChanged = true;
+        mNeedUpdate = true;
     }
 
-    tempPath = mLocalPath;
-
-    // 2. apply path operation if needed
-    if (mTrimNodeRefs.size() > 0) {
-        // TODO apply more than one trim path if necessary
-        VPathMesure pm;
-        float       s = mTrimNodeRefs.front()->getStart(frameNo) / 100.0f;
-        float       e = mTrimNodeRefs.front()->getEnd(frameNo) / 100.0f;
-
-        pm.setOffset(s, e);
-        tempPath = pm.trim(tempPath);
-        mPathChanged = true;
-    }
+    mTemp = mLocalPath;
 
     // 3. compute the final path with parentMatrix
-
     if ((flag & DirtyFlagBit::Matrix) || mPathChanged) {
-        mFinalPath.clone(tempPath);
-        mFinalPath.transform(parentMatrix);
         mPathChanged = true;
     }
 }
 
+const VPath & LOTPathDataItem::finalPath()
+{
+    if (mPathChanged || mNeedUpdate) {
+        mFinalPath.clone(mTemp);
+        mFinalPath.transform(static_cast<LOTContentGroupItem *>(parent())->matrix());
+        mNeedUpdate = false;
+    }
+    return mFinalPath;
+}
 LOTRectItem::LOTRectItem(LOTRectData *data)
     : LOTPathDataItem(data->isStatic()), mData(data)
 {
@@ -682,7 +693,7 @@ void LOTRectItem::updatePath(VPath& path, int frameNo)
              size.y());
 
     path.reset();
-    path.addRoundRect(r, roundness, roundness, mData->direction());
+    path.addRoundRect(r, roundness, mData->direction());
     updateCache(frameNo, pos, size, roundness);
 }
 
@@ -758,7 +769,6 @@ void LOTPaintDataItem::update(int frameNo, const VMatrix &parentMatrix,
 {
     mRenderNodeUpdate = true;
     mParentAlpha = parentAlpha;
-    mParentMatrix = parentMatrix;
     mFlag = flag;
     mFrameNo = frameNo;
 
@@ -779,7 +789,7 @@ void LOTPaintDataItem::updateRenderNode()
         mPath.reset();
 
         for (auto &i : mPathItems) {
-            mPath.addPath(i->path());
+            mPath.addPath(i->finalPath());
         }
         mDrawable->setPath(mPath);
     } else {
@@ -836,12 +846,13 @@ LOTGFillItem::LOTGFillItem(LOTGFillData *data)
 void LOTGFillItem::updateContent(int frameNo)
 {
     mData->update(mGradient, frameNo);
-    mGradient->mMatrix = mParentMatrix;
+    mGradient->mMatrix = static_cast<LOTContentGroupItem *>(parent())->matrix();
     mFillRule = mData->fillRule();
 }
 
 void LOTGFillItem::updateRenderNode()
 {
+    mGradient->setAlpha(parentAlpha());
     mDrawable->setBrush(VBrush(mGradient.get()));
     mDrawable->setFillRule(mFillRule);
 }
@@ -875,7 +886,7 @@ static float getScale(const VMatrix &matrix)
     p2 = matrix.map(p2);
     VPointF final = p2 - p1;
 
-    return std::sqrt(final.x() * final.x() + final.y() * final.y());
+    return std::sqrt(final.x() * final.x() + final.y() * final.y()) / 2.0;
 }
 
 void LOTStrokeItem::updateRenderNode()
@@ -885,7 +896,7 @@ void LOTStrokeItem::updateRenderNode()
     color.setAlpha(color.a * parentAlpha());
     VBrush brush(color);
     mDrawable->setBrush(brush);
-    float scale = getScale(mParentMatrix);
+    float scale = getScale(static_cast<LOTContentGroupItem *>(parent())->matrix());
     mDrawable->setStrokeInfo(mCap, mJoin, mMiterLimit,
                             mWidth * scale);
     if (mDashArraySize) {
@@ -904,7 +915,7 @@ LOTGStrokeItem::LOTGStrokeItem(LOTGStrokeData *data)
 void LOTGStrokeItem::updateContent(int frameNo)
 {
     mData->update(mGradient, frameNo);
-    mGradient->mMatrix = mParentMatrix;
+    mGradient->mMatrix = static_cast<LOTContentGroupItem *>(parent())->matrix();
     mCap = mData->capStyle();
     mJoin = mData->joinStyle();
     mMiterLimit = mData->meterLimit();
@@ -916,7 +927,8 @@ void LOTGStrokeItem::updateContent(int frameNo)
 
 void LOTGStrokeItem::updateRenderNode()
 {
-    float scale = getScale(mParentMatrix);
+    float scale = getScale(mGradient->mMatrix);
+    mGradient->setAlpha(parentAlpha());
     mDrawable->setBrush(VBrush(mGradient.get()));
     mDrawable->setStrokeInfo(mCap, mJoin, mMiterLimit,
                             mWidth * scale);
@@ -929,11 +941,120 @@ void LOTGStrokeItem::updateRenderNode()
 
 LOTTrimItem::LOTTrimItem(LOTTrimData *data) : mData(data) {}
 
-void LOTTrimItem::update(int /*frameNo*/, const VMatrix &/*parentMatrix*/,
+void LOTTrimItem::update(int frameNo, const VMatrix &/*parentMatrix*/,
                          float /*parentAlpha*/, const DirtyFlag &/*flag*/)
 {
+    mDirty = false;
+
+    if (mCache.mFrameNo == frameNo) return;
+
+    float   start = mData->start(frameNo);
+    float   end = mData->end(frameNo);
+    float   offset = mData->offset(frameNo);
+
+    if (!(vCompare(mCache.mStart, start) && vCompare(mCache.mEnd, end) &&
+          vCompare(mCache.mOffset, offset))) {
+        mDirty = true;
+        mCache.mStart = start;
+        mCache.mEnd = end;
+        mCache.mOffset = offset;
+    }
+    mCache.mFrameNo = frameNo;
+}
+
+void LOTTrimItem::update()
+{
+    // when both path and trim are not dirty
+    if (!(mDirty || pathDirty())) return;
+
+    if (vCompare(mCache.mStart, mCache.mEnd)) {
+        for (auto &i : mPathItems) {
+            i->updatePath(VPath());
+        }
+        return;
+    }
+
+
+    if (mData->type() == LOTTrimData::TrimType::Simultaneously) {
+        for (auto &i : mPathItems) {
+            VPathMesure pm;
+            pm.setStart(mCache.mStart);
+            pm.setEnd(mCache.mEnd);
+            pm.setOffset(mCache.mOffset);
+            i->updatePath(pm.trim(i->localPath()));
+        }
+    } else { // LOTTrimData::TrimType::Individually
+        float totalLength = 0.0;
+        for (auto &i : mPathItems) {
+            totalLength += i->localPath().length();
+        }
+        float offset = totalLength * mCache.mOffset;
+        float start = totalLength * mCache.mStart;
+        float end  = totalLength * mCache.mEnd;
+        start += offset;
+        end +=offset;
+        // normalize start, end value to 0 - totalLength
+        if (fabs(start) > totalLength) start = fmod(start, totalLength);
+        if (fabs(end) > totalLength) end = fmod(end, totalLength);
+
+        if (start >= 0 && end >= 0) {
+            if (start > end) std::swap(start, end);
+        } else if ( start < 0 && end < 0) {
+            start += totalLength;
+            end += totalLength;
+            if (start > end) std::swap(start, end);
+        } else {
+            // one is +ve and one is -ve so the
+            // segment will be wrapped from end segment to start segment.
+            // we need to split it in two segment.
+            //@TODO
+            return;
+        }
+
+        if (start < end ) {
+            float curLen = 0.0;
+            for (auto &i : mPathItems) {
+                if (curLen > end) {
+                    // update with empty path.
+                    i->updatePath(VPath());
+                    continue;
+                }
+                float len = i->localPath().length();
+
+                if (curLen < start  && curLen + len < start) {
+                    curLen += len;
+                    // update with empty path.
+                    i->updatePath(VPath());
+                    continue;
+                } else if (start <= curLen && end >= curLen + len) {
+                    // inside segment
+                    curLen += len;
+                    continue;
+                } else {
+                    float local_start = start > curLen ? start - curLen : 0;
+                    local_start /= len;
+                    float local_end = curLen + len < end ? len : end - curLen;
+                    local_end /= len;
+                    VPathMesure pm;
+                    pm.setStart(local_start);
+                    pm.setEnd(local_end);
+                    VPath p = pm.trim(i->localPath());
+                    i->updatePath(p);
+                    curLen += len;
+                }
+            }
+        }
+    }
+
+}
+
+
+void LOTTrimItem::addPathItems(std::vector<LOTPathDataItem *> &list, int startOffset)
+{
+    std::copy(list.begin() + startOffset, list.end(), back_inserter(mPathItems));
 }
 
+
 LOTRepeaterItem::LOTRepeaterItem(LOTRepeaterData *data) : mData(data) {}
 
 void LOTRepeaterItem::update(int /*frameNo*/, const VMatrix &/*parentMatrix*/,
@@ -943,9 +1064,36 @@ void LOTRepeaterItem::update(int /*frameNo*/, const VMatrix &/*parentMatrix*/,
 
 void LOTRepeaterItem::renderList(std::vector<VDrawable *> &/*list*/) {}
 
+static void updateGStops(LOTNode *n, const VGradient *grad)
+{
+    if (grad->mStops.size() != n->mGradient.stopCount) {
+        if (n->mGradient.stopCount)
+            free(n->mGradient.stopPtr);
+        n->mGradient.stopCount = grad->mStops.size();
+        n->mGradient.stopPtr = (LOTGradientStop *) malloc(n->mGradient.stopCount * sizeof(LOTGradientStop));
+    }
+
+    LOTGradientStop *ptr = n->mGradient.stopPtr;
+    for (const auto &i : grad->mStops) {
+        ptr->pos = i.first;
+        ptr->a = i.second.alpha() * grad->alpha();
+        ptr->r = i.second.red();
+        ptr->g = i.second.green();
+        ptr->b = i.second.blue();
+        ptr++;
+    }
+
+}
+
 void LOTDrawable::sync()
 {
-    mCNode.mFlag = ChangeFlagNone;
+    if (!mCNode) {
+        mCNode = std::make_unique<LOTNode>();
+        mCNode->mGradient.stopPtr = nullptr;
+        mCNode->mGradient.stopCount = 0;
+    }
+
+    mCNode->mFlag = ChangeFlagNone;
     if (mFlag & DirtyState::None) return;
 
     if (mFlag & DirtyState::Path) {
@@ -953,90 +1101,104 @@ void LOTDrawable::sync()
         const std::vector<VPointF> &       pts = mPath.points();
         const float *ptPtr = reinterpret_cast<const float *>(pts.data());
         const char * elmPtr = reinterpret_cast<const char *>(elm.data());
-        mCNode.mPath.elmPtr = elmPtr;
-        mCNode.mPath.elmCount = elm.size();
-        mCNode.mPath.ptPtr = ptPtr;
-        mCNode.mPath.ptCount = 2 * pts.size();
-        mCNode.mFlag |= ChangeFlagPath;
+        mCNode->mPath.elmPtr = elmPtr;
+        mCNode->mPath.elmCount = elm.size();
+        mCNode->mPath.ptPtr = ptPtr;
+        mCNode->mPath.ptCount = 2 * pts.size();
+        mCNode->mFlag |= ChangeFlagPath;
     }
 
     if (mStroke.enable) {
-        mCNode.mStroke.width = mStroke.width;
-        mCNode.mStroke.meterLimit = mStroke.meterLimit;
-        mCNode.mStroke.enable = 1;
+        mCNode->mStroke.width = mStroke.width;
+        mCNode->mStroke.meterLimit = mStroke.meterLimit;
+        mCNode->mStroke.enable = 1;
 
         switch (mFillRule) {
         case FillRule::EvenOdd:
-            mCNode.mFillRule = LOTFillRule::FillEvenOdd;
+            mCNode->mFillRule = LOTFillRule::FillEvenOdd;
             break;
         default:
-            mCNode.mFillRule = LOTFillRule::FillWinding;
+            mCNode->mFillRule = LOTFillRule::FillWinding;
             break;
         }
 
         switch (mStroke.cap) {
         case CapStyle::Flat:
-            mCNode.mStroke.cap = LOTCapStyle::CapFlat;
+            mCNode->mStroke.cap = LOTCapStyle::CapFlat;
             break;
         case CapStyle::Square:
-            mCNode.mStroke.cap = LOTCapStyle::CapSquare;
+            mCNode->mStroke.cap = LOTCapStyle::CapSquare;
             break;
         case CapStyle::Round:
-            mCNode.mStroke.cap = LOTCapStyle::CapRound;
+            mCNode->mStroke.cap = LOTCapStyle::CapRound;
             break;
         default:
-            mCNode.mStroke.cap = LOTCapStyle::CapFlat;
+            mCNode->mStroke.cap = LOTCapStyle::CapFlat;
             break;
         }
 
         switch (mStroke.join) {
         case JoinStyle::Miter:
-            mCNode.mStroke.join = LOTJoinStyle::JoinMiter;
+            mCNode->mStroke.join = LOTJoinStyle::JoinMiter;
             break;
         case JoinStyle::Bevel:
-            mCNode.mStroke.join = LOTJoinStyle::JoinBevel;
+            mCNode->mStroke.join = LOTJoinStyle::JoinBevel;
             break;
         case JoinStyle::Round:
-            mCNode.mStroke.join = LOTJoinStyle::JoinRound;
+            mCNode->mStroke.join = LOTJoinStyle::JoinRound;
             break;
         default:
-            mCNode.mStroke.join = LOTJoinStyle::JoinMiter;
+            mCNode->mStroke.join = LOTJoinStyle::JoinMiter;
             break;
         }
 
-        mCNode.mStroke.dashArray = mStroke.mDash.data();
-        mCNode.mStroke.dashArraySize = mStroke.mDash.size();
+        mCNode->mStroke.dashArray = mStroke.mDash.data();
+        mCNode->mStroke.dashArraySize = mStroke.mDash.size();
 
     } else {
-        mCNode.mStroke.enable = 0;
+        mCNode->mStroke.enable = 0;
     }
 
     switch (mBrush.type()) {
     case VBrush::Type::Solid:
-        mCNode.mType = LOTBrushType::BrushSolid;
-        mCNode.mColor.r = mBrush.mColor.r;
-        mCNode.mColor.g = mBrush.mColor.g;
-        mCNode.mColor.b = mBrush.mColor.b;
-        mCNode.mColor.a = mBrush.mColor.a;
+        mCNode->mBrushType = LOTBrushType::BrushSolid;
+        mCNode->mColor.r = mBrush.mColor.r;
+        mCNode->mColor.g = mBrush.mColor.g;
+        mCNode->mColor.b = mBrush.mColor.b;
+        mCNode->mColor.a = mBrush.mColor.a;
         break;
-    case VBrush::Type::LinearGradient:
-        mCNode.mType = LOTBrushType::BrushGradient;
-        mCNode.mGradient.type = LOTGradientType::GradientLinear;
-        mCNode.mGradient.start.x = mBrush.mGradient->linear.x1;
-        mCNode.mGradient.start.y = mBrush.mGradient->linear.y1;
-        mCNode.mGradient.end.x = mBrush.mGradient->linear.x2;
-        mCNode.mGradient.end.y = mBrush.mGradient->linear.y2;
+    case VBrush::Type::LinearGradient: {
+        mCNode->mBrushType = LOTBrushType::BrushGradient;
+        mCNode->mGradient.type = LOTGradientType::GradientLinear;
+        VPointF s = mBrush.mGradient->mMatrix.map({mBrush.mGradient->linear.x1,
+                                                   mBrush.mGradient->linear.y1});
+        VPointF e = mBrush.mGradient->mMatrix.map({mBrush.mGradient->linear.x2,
+                                                   mBrush.mGradient->linear.y2});
+        mCNode->mGradient.start.x = s.x();
+        mCNode->mGradient.start.y = s.y();
+        mCNode->mGradient.end.x = e.x();
+        mCNode->mGradient.end.y = e.y();
+        updateGStops(mCNode.get(), mBrush.mGradient);
         break;
-    case VBrush::Type::RadialGradient:
-        mCNode.mType = LOTBrushType::BrushGradient;
-        mCNode.mGradient.type = LOTGradientType::GradientRadial;
-        mCNode.mGradient.center.x = mBrush.mGradient->radial.cx;
-        mCNode.mGradient.center.y = mBrush.mGradient->radial.cy;
-        mCNode.mGradient.focal.x = mBrush.mGradient->radial.fx;
-        mCNode.mGradient.focal.y = mBrush.mGradient->radial.fy;
-        mCNode.mGradient.cradius = mBrush.mGradient->radial.cradius;
-        mCNode.mGradient.fradius = mBrush.mGradient->radial.fradius;
+    }
+    case VBrush::Type::RadialGradient: {
+        mCNode->mBrushType = LOTBrushType::BrushGradient;
+        mCNode->mGradient.type = LOTGradientType::GradientRadial;
+        VPointF c = mBrush.mGradient->mMatrix.map({mBrush.mGradient->radial.cx,
+                                                   mBrush.mGradient->radial.cy});
+        VPointF f = mBrush.mGradient->mMatrix.map({mBrush.mGradient->radial.fx,
+                                                   mBrush.mGradient->radial.fy});
+        mCNode->mGradient.center.x = c.x();
+        mCNode->mGradient.center.y = c.y();
+        mCNode->mGradient.focal.x = f.x();
+        mCNode->mGradient.focal.y = f.y();
+
+        float scale = getScale(mBrush.mGradient->mMatrix);
+        mCNode->mGradient.cradius = mBrush.mGradient->radial.cradius * scale;
+        mCNode->mGradient.fradius = mBrush.mGradient->radial.fradius * scale;
+        updateGStops(mCNode.get(), mBrush.mGradient);
         break;
+    }
     default:
         break;
     }