diff options
author | Benau <Benau@users.noreply.github.com> | 2021-08-25 04:32:50 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-24 22:32:50 +0200 |
commit | 53cafa9f3d0c8be33821fc7338b1da97e91d9cc6 (patch) | |
tree | 964a225219099a1a1c282e27913767da588191b4 /vendor/github.com/Benau/go_rlottie/lottie_lottieitem.cpp | |
parent | d4195deb3a6305c49c50ff30e8af978c7f1bdd92 (diff) | |
download | matterbridge-msglm-53cafa9f3d0c8be33821fc7338b1da97e91d9cc6.tar.gz matterbridge-msglm-53cafa9f3d0c8be33821fc7338b1da97e91d9cc6.tar.bz2 matterbridge-msglm-53cafa9f3d0c8be33821fc7338b1da97e91d9cc6.zip |
Convert .tgs with go libraries (and cgo) (telegram) (#1569)
This commit adds support for go/cgo tgs conversion when building with the -tags `cgo`
The default binaries are still "pure" go and uses the old way of converting.
* Move lottie_convert.py conversion code to its own file
* Add optional libtgsconverter
* Update vendor
* Apply suggestions from code review
* Update bridge/helper/libtgsconverter.go
Co-authored-by: Wim <wim@42.be>
Diffstat (limited to 'vendor/github.com/Benau/go_rlottie/lottie_lottieitem.cpp')
-rw-r--r-- | vendor/github.com/Benau/go_rlottie/lottie_lottieitem.cpp | 1491 |
1 files changed, 1491 insertions, 0 deletions
diff --git a/vendor/github.com/Benau/go_rlottie/lottie_lottieitem.cpp b/vendor/github.com/Benau/go_rlottie/lottie_lottieitem.cpp new file mode 100644 index 00000000..99f80044 --- /dev/null +++ b/vendor/github.com/Benau/go_rlottie/lottie_lottieitem.cpp @@ -0,0 +1,1491 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "lottie_lottieitem.h" +#include <algorithm> +#include <cmath> +#include <iterator> +#include "lottie_lottiekeypath.h" +#include "vector_vbitmap.h" +#include "vector_vpainter.h" +#include "vector_vraster.h" + +/* Lottie Layer Rules + * 1. time stretch is pre calculated and applied to all the properties of the + * lottilayer model and all its children + * 2. The frame property could be reversed using,time-reverse layer property in + * AE. which means (start frame > endFrame) 3. + */ + +static bool transformProp(rlottie::Property prop) +{ + switch (prop) { + case rlottie::Property::TrAnchor: + case rlottie::Property::TrScale: + case rlottie::Property::TrOpacity: + case rlottie::Property::TrPosition: + case rlottie::Property::TrRotation: + return true; + default: + return false; + } +} +static bool fillProp(rlottie::Property prop) +{ + switch (prop) { + case rlottie::Property::FillColor: + case rlottie::Property::FillOpacity: + return true; + default: + return false; + } +} + +static bool strokeProp(rlottie::Property prop) +{ + switch (prop) { + case rlottie::Property::StrokeColor: + case rlottie::Property::StrokeOpacity: + case rlottie::Property::StrokeWidth: + return true; + default: + return false; + } +} + +static renderer::Layer *createLayerItem(model::Layer *layerData, + VArenaAlloc * allocator) +{ + switch (layerData->mLayerType) { + case model::Layer::Type::Precomp: { + return allocator->make<renderer::CompLayer>(layerData, allocator); + } + case model::Layer::Type::Solid: { + return allocator->make<renderer::SolidLayer>(layerData); + } + case model::Layer::Type::Shape: { + return allocator->make<renderer::ShapeLayer>(layerData, allocator); + } + case model::Layer::Type::Null: { + return allocator->make<renderer::NullLayer>(layerData); + } + case model::Layer::Type::Image: { + return allocator->make<renderer::ImageLayer>(layerData); + } + default: + return nullptr; + break; + } +} + +renderer::Composition::Composition(std::shared_ptr<model::Composition> model) + : mCurFrameNo(-1) +{ + mModel = std::move(model); + mRootLayer = createLayerItem(mModel->mRootLayer, &mAllocator); + mRootLayer->setComplexContent(false); + mViewSize = mModel->size(); +} + +void renderer::Composition::setValue(const std::string &keypath, + LOTVariant & value) +{ + LOTKeyPath key(keypath); + mRootLayer->resolveKeyPath(key, 0, value); +} + +bool renderer::Composition::update(int frameNo, const VSize &size, + bool keepAspectRatio) +{ + // check if cached frame is same as requested frame. + if ((mViewSize == size) && (mCurFrameNo == frameNo) && + (mKeepAspectRatio == keepAspectRatio)) + return false; + + mViewSize = size; + mCurFrameNo = frameNo; + mKeepAspectRatio = keepAspectRatio; + + /* + * if viewbox dosen't scale exactly to the viewport + * we scale the viewbox keeping AspectRatioPreserved and then align the + * viewbox to the viewport using AlignCenter rule. + */ + VMatrix m; + VSize viewPort = mViewSize; + VSize viewBox = mModel->size(); + float sx = float(viewPort.width()) / viewBox.width(); + float sy = float(viewPort.height()) / viewBox.height(); + if (mKeepAspectRatio) { + float scale = std::min(sx, sy); + float tx = (viewPort.width() - viewBox.width() * scale) * 0.5f; + float ty = (viewPort.height() - viewBox.height() * scale) * 0.5f; + m.translate(tx, ty).scale(scale, scale); + } else { + m.scale(sx, sy); + } + mRootLayer->update(frameNo, m, 1.0); + return true; +} + +bool renderer::Composition::render(const rlottie::Surface &surface) +{ + mSurface.reset(reinterpret_cast<uchar *>(surface.buffer()), + uint(surface.width()), uint(surface.height()), + uint(surface.bytesPerLine()), + VBitmap::Format::ARGB32_Premultiplied); + + /* schedule all preprocess task for this frame at once. + */ + VRect clip(0, 0, int(surface.drawRegionWidth()), + int(surface.drawRegionHeight())); + mRootLayer->preprocess(clip); + + VPainter painter(&mSurface); + // set sub surface area for drawing. + painter.setDrawRegion( + VRect(int(surface.drawRegionPosX()), int(surface.drawRegionPosY()), + int(surface.drawRegionWidth()), int(surface.drawRegionHeight()))); + mRootLayer->render(&painter, {}, {}, mSurfaceCache); + painter.end(); + return true; +} + +void renderer::Mask::update(int frameNo, const VMatrix &parentMatrix, + float /*parentAlpha*/, const DirtyFlag &flag) +{ + bool dirtyPath = false; + + if (flag.testFlag(DirtyFlagBit::None) && mData->isStatic()) return; + + if (mData->mShape.isStatic()) { + if (mLocalPath.empty()) { + dirtyPath = true; + mData->mShape.value(frameNo, mLocalPath); + } + } else { + dirtyPath = true; + mData->mShape.value(frameNo, mLocalPath); + } + /* mask item dosen't inherit opacity */ + mCombinedAlpha = mData->opacity(frameNo); + + if ( flag.testFlag(DirtyFlagBit::Matrix) || dirtyPath ) { + mFinalPath.clone(mLocalPath); + mFinalPath.transform(parentMatrix); + mRasterRequest = true; + } +} + +VRle renderer::Mask::rle() +{ + if (!vCompare(mCombinedAlpha, 1.0f)) { + VRle obj = mRasterizer.rle(); + obj *= uchar(mCombinedAlpha * 255); + return obj; + } else { + return mRasterizer.rle(); + } +} + +void renderer::Mask::preprocess(const VRect &clip) +{ + if (mRasterRequest) + mRasterizer.rasterize(mFinalPath, FillRule::Winding, clip); +} + +void renderer::Layer::render(VPainter *painter, const VRle &inheritMask, + const VRle &matteRle, SurfaceCache &) +{ + auto renderlist = renderList(); + + if (renderlist.empty()) return; + + VRle mask; + if (mLayerMask) { + mask = mLayerMask->maskRle(painter->clipBoundingRect()); + if (!inheritMask.empty()) mask = mask & inheritMask; + // if resulting mask is empty then return. + if (mask.empty()) return; + } else { + mask = inheritMask; + } + + for (auto &i : renderlist) { + painter->setBrush(i->mBrush); + VRle rle = i->rle(); + if (matteRle.empty()) { + if (mask.empty()) { + // no mask no matte + painter->drawRle(VPoint(), rle); + } else { + // only mask + painter->drawRle(rle, mask); + } + + } else { + if (!mask.empty()) rle = rle & mask; + + if (rle.empty()) continue; + if (matteType() == model::MatteType::AlphaInv) { + rle = rle - matteRle; + painter->drawRle(VPoint(), rle); + } else { + // render with matteRle as clip. + painter->drawRle(rle, matteRle); + } + } + } +} + +void renderer::LayerMask::preprocess(const VRect &clip) +{ + for (auto &i : mMasks) { + i.preprocess(clip); + } +} + +renderer::LayerMask::LayerMask(model::Layer *layerData) +{ + if (!layerData->mExtra) return; + + mMasks.reserve(layerData->mExtra->mMasks.size()); + + for (auto &i : layerData->mExtra->mMasks) { + mMasks.emplace_back(i); + mStatic &= i->isStatic(); + } +} + +void renderer::LayerMask::update(int frameNo, const VMatrix &parentMatrix, + float parentAlpha, const DirtyFlag &flag) +{ + if (flag.testFlag(DirtyFlagBit::None) && isStatic()) return; + + for (auto &i : mMasks) { + i.update(frameNo, parentMatrix, parentAlpha, flag); + } + mDirty = true; +} + +VRle renderer::LayerMask::maskRle(const VRect &clipRect) +{ + if (!mDirty) return mRle; + + VRle rle; + for (auto &e : mMasks) { + const auto cur = [&]() { + if (e.inverted()) + return clipRect - e.rle(); + else + return e.rle(); + }(); + + switch (e.maskMode()) { + case model::Mask::Mode::Add: { + rle = rle + cur; + break; + } + case model::Mask::Mode::Substarct: { + if (rle.empty() && !clipRect.empty()) + rle = clipRect - cur; + else + rle = rle - cur; + break; + } + case model::Mask::Mode::Intersect: { + if (rle.empty() && !clipRect.empty()) + rle = clipRect & cur; + else + rle = rle & cur; + break; + } + case model::Mask::Mode::Difference: { + rle = rle ^ cur; + break; + } + default: + break; + } + } + + if (!rle.empty() && !rle.unique()) { + mRle.clone(rle); + } else { + mRle = rle; + } + mDirty = false; + return mRle; +} + +renderer::Layer::Layer(model::Layer *layerData) : mLayerData(layerData) +{ + if (mLayerData->mHasMask) + mLayerMask = std::make_unique<renderer::LayerMask>(mLayerData); +} + +bool renderer::Layer::resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value) +{ + if (!keyPath.matches(name(), depth)) { + return false; + } + + if (!keyPath.skip(name())) { + if (keyPath.fullyResolvesTo(name(), depth) && + transformProp(value.property())) { + //@TODO handle propery update. + } + } + return true; +} + +bool renderer::ShapeLayer::resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value) +{ + if (renderer::Layer::resolveKeyPath(keyPath, depth, value)) { + if (keyPath.propagate(name(), depth)) { + uint newDepth = keyPath.nextDepth(name(), depth); + mRoot->resolveKeyPath(keyPath, newDepth, value); + } + return true; + } + return false; +} + +bool renderer::CompLayer::resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value) +{ + if (renderer::Layer::resolveKeyPath(keyPath, depth, value)) { + if (keyPath.propagate(name(), depth)) { + uint newDepth = keyPath.nextDepth(name(), depth); + for (const auto &layer : mLayers) { + layer->resolveKeyPath(keyPath, newDepth, value); + } + } + return true; + } + return false; +} + +void renderer::Layer::update(int frameNumber, const VMatrix &parentMatrix, + float parentAlpha) +{ + mFrameNo = frameNumber; + // 1. check if the layer is part of the current frame + if (!visible()) return; + + float alpha = parentAlpha * opacity(frameNo()); + if (vIsZero(alpha)) { + mCombinedAlpha = 0; + return; + } + + // 2. calculate the parent matrix and alpha + VMatrix m = matrix(frameNo()); + m *= parentMatrix; + + // 3. update the dirty flag based on the change + if (mCombinedMatrix != m) { + mDirtyFlag |= DirtyFlagBit::Matrix; + mCombinedMatrix = m; + } + + if (!vCompare(mCombinedAlpha, alpha)) { + mDirtyFlag |= DirtyFlagBit::Alpha; + mCombinedAlpha = alpha; + } + + // 4. update the mask + if (mLayerMask) { + mLayerMask->update(frameNo(), mCombinedMatrix, mCombinedAlpha, + mDirtyFlag); + } + + // 5. if no parent property change and layer is static then nothing to do. + if (!mLayerData->precompLayer() && flag().testFlag(DirtyFlagBit::None) && + isStatic()) + return; + + // 6. update the content of the layer + updateContent(); + + // 7. reset the dirty flag + mDirtyFlag = DirtyFlagBit::None; +} + +VMatrix renderer::Layer::matrix(int frameNo) const +{ + return mParentLayer + ? (mLayerData->matrix(frameNo) * mParentLayer->matrix(frameNo)) + : mLayerData->matrix(frameNo); +} + +bool renderer::Layer::visible() const +{ + return (frameNo() >= mLayerData->inFrame() && + frameNo() < mLayerData->outFrame()); +} + +void renderer::Layer::preprocess(const VRect &clip) +{ + // layer dosen't contribute to the frame + if (skipRendering()) return; + + // preprocess layer masks + if (mLayerMask) mLayerMask->preprocess(clip); + + preprocessStage(clip); +} + +renderer::CompLayer::CompLayer(model::Layer *layerModel, VArenaAlloc *allocator) + : renderer::Layer(layerModel) +{ + if (!mLayerData->mChildren.empty()) + mLayers.reserve(mLayerData->mChildren.size()); + + // 1. keep the layer in back-to-front order. + // as lottie model keeps the data in front-toback-order. + for (auto it = mLayerData->mChildren.crbegin(); + it != mLayerData->mChildren.rend(); ++it) { + auto model = static_cast<model::Layer *>(*it); + auto item = createLayerItem(model, allocator); + if (item) mLayers.push_back(item); + } + + // 2. update parent layer + 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()) layer->setParentLayer(*search); + } + } + + // 4. check if its a nested composition + if (!layerModel->layerSize().empty()) { + mClipper = std::make_unique<renderer::Clipper>(layerModel->layerSize()); + } + + if (mLayers.size() > 1) setComplexContent(true); +} + +void renderer::CompLayer::render(VPainter *painter, const VRle &inheritMask, + const VRle &matteRle, SurfaceCache &cache) +{ + if (vIsZero(combinedAlpha())) return; + + if (vCompare(combinedAlpha(), 1.0)) { + renderHelper(painter, inheritMask, matteRle, cache); + } else { + if (complexContent()) { + VSize size = painter->clipBoundingRect().size(); + VPainter srcPainter; + VBitmap srcBitmap = cache.make_surface(size.width(), size.height()); + srcPainter.begin(&srcBitmap); + renderHelper(&srcPainter, inheritMask, matteRle, cache); + srcPainter.end(); + painter->drawBitmap(VPoint(), srcBitmap, + uchar(combinedAlpha() * 255.0f)); + cache.release_surface(srcBitmap); + } else { + renderHelper(painter, inheritMask, matteRle, cache); + } + } +} + +void renderer::CompLayer::renderHelper(VPainter * painter, + const VRle & inheritMask, + const VRle & matteRle, + SurfaceCache &cache) +{ + VRle mask; + if (mLayerMask) { + mask = mLayerMask->maskRle(painter->clipBoundingRect()); + if (!inheritMask.empty()) mask = mask & inheritMask; + // if resulting mask is empty then return. + if (mask.empty()) return; + } else { + mask = inheritMask; + } + + if (mClipper) { + mask = mClipper->rle(mask); + if (mask.empty()) return; + } + + renderer::Layer *matte = nullptr; + for (const auto &layer : mLayers) { + if (layer->hasMatte()) { + matte = layer; + } else { + if (layer->visible()) { + if (matte) { + if (matte->visible()) + renderMatteLayer(painter, mask, matteRle, matte, layer, + cache); + } else { + layer->render(painter, mask, matteRle, cache); + } + } + matte = nullptr; + } + } +} + +void renderer::CompLayer::renderMatteLayer(VPainter *painter, const VRle &mask, + const VRle & matteRle, + renderer::Layer *layer, + renderer::Layer *src, + SurfaceCache & cache) +{ + VSize size = painter->clipBoundingRect().size(); + // Decide if we can use fast matte. + // 1. draw src layer to matte buffer + VPainter srcPainter; + VBitmap srcBitmap = cache.make_surface(size.width(), size.height()); + srcPainter.begin(&srcBitmap); + src->render(&srcPainter, mask, matteRle, cache); + srcPainter.end(); + + // 2. draw layer to layer buffer + VPainter layerPainter; + VBitmap layerBitmap = cache.make_surface(size.width(), size.height()); + layerPainter.begin(&layerBitmap); + layer->render(&layerPainter, mask, matteRle, cache); + + // 2.1update composition mode + switch (layer->matteType()) { + case model::MatteType::Alpha: + case model::MatteType::Luma: { + layerPainter.setBlendMode(BlendMode::DestIn); + break; + } + case model::MatteType::AlphaInv: + case model::MatteType::LumaInv: { + layerPainter.setBlendMode(BlendMode::DestOut); + break; + } + default: + break; + } + + // 2.2 update srcBuffer if the matte is luma type + if (layer->matteType() == model::MatteType::Luma || + layer->matteType() == model::MatteType::LumaInv) { + srcBitmap.updateLuma(); + } + + auto clip = layerPainter.clipBoundingRect(); + + // if the layer has only one renderer then use it as the clip rect + // when blending 2 buffer and copy back to final buffer to avoid + // unnecessary pixel processing. + if (layer->renderList().size() == 1) + { + clip = layer->renderList()[0]->rle().boundingRect(); + } + + // 2.3 draw src buffer as mask + layerPainter.drawBitmap(clip, srcBitmap, clip); + layerPainter.end(); + // 3. draw the result buffer into painter + painter->drawBitmap(clip, layerBitmap, clip); + + cache.release_surface(srcBitmap); + cache.release_surface(layerBitmap); +} + +void renderer::Clipper::update(const VMatrix &matrix) +{ + mPath.reset(); + mPath.addRect(VRectF(0, 0, mSize.width(), mSize.height())); + mPath.transform(matrix); + mRasterRequest = true; +} + +void renderer::Clipper::preprocess(const VRect &clip) +{ + if (mRasterRequest) mRasterizer.rasterize(mPath, FillRule::Winding, clip); + + mRasterRequest = false; +} + +VRle renderer::Clipper::rle(const VRle &mask) +{ + if (mask.empty()) return mRasterizer.rle(); + + mMaskedRle.clone(mask); + mMaskedRle &= mRasterizer.rle(); + return mMaskedRle; +} + +void renderer::CompLayer::updateContent() +{ + if (mClipper && flag().testFlag(DirtyFlagBit::Matrix)) { + mClipper->update(combinedMatrix()); + } + int mappedFrame = mLayerData->timeRemap(frameNo()); + float alpha = combinedAlpha(); + if (complexContent()) alpha = 1; + for (const auto &layer : mLayers) { + layer->update(mappedFrame, combinedMatrix(), alpha); + } +} + +void renderer::CompLayer::preprocessStage(const VRect &clip) +{ + // if layer has clipper + if (mClipper) mClipper->preprocess(clip); + + renderer::Layer *matte = nullptr; + for (const auto &layer : mLayers) { + if (layer->hasMatte()) { + matte = layer; + } else { + if (layer->visible()) { + if (matte) { + if (matte->visible()) { + layer->preprocess(clip); + matte->preprocess(clip); + } + } else { + layer->preprocess(clip); + } + } + matte = nullptr; + } + } +} + +renderer::SolidLayer::SolidLayer(model::Layer *layerData) + : renderer::Layer(layerData) +{ + mDrawableList = &mRenderNode; +} + +void renderer::SolidLayer::updateContent() +{ + if (flag() & DirtyFlagBit::Matrix) { + mPath.reset(); + mPath.addRect(VRectF(0, 0, mLayerData->layerSize().width(), + mLayerData->layerSize().height())); + mPath.transform(combinedMatrix()); + mRenderNode.mFlag |= VDrawable::DirtyState::Path; + mRenderNode.mPath = mPath; + } + if (flag() & DirtyFlagBit::Alpha) { + model::Color color = mLayerData->solidColor(); + VBrush brush(color.toColor(combinedAlpha())); + mRenderNode.setBrush(brush); + mRenderNode.mFlag |= VDrawable::DirtyState::Brush; + } +} + +void renderer::SolidLayer::preprocessStage(const VRect &clip) +{ + mRenderNode.preprocess(clip); +} + +renderer::DrawableList renderer::SolidLayer::renderList() +{ + if (skipRendering()) return {}; + + return {&mDrawableList, 1}; +} + +renderer::ImageLayer::ImageLayer(model::Layer *layerData) + : renderer::Layer(layerData) +{ + mDrawableList = &mRenderNode; + + if (!mLayerData->asset()) return; + + mTexture.mBitmap = mLayerData->asset()->bitmap(); + VBrush brush(&mTexture); + mRenderNode.setBrush(brush); +} + +void renderer::ImageLayer::updateContent() +{ + if (!mLayerData->asset()) return; + + if (flag() & DirtyFlagBit::Matrix) { + mPath.reset(); + mPath.addRect(VRectF(0, 0, mLayerData->asset()->mWidth, + mLayerData->asset()->mHeight)); + mPath.transform(combinedMatrix()); + mRenderNode.mFlag |= VDrawable::DirtyState::Path; + mRenderNode.mPath = mPath; + mTexture.mMatrix = combinedMatrix(); + } + + if (flag() & DirtyFlagBit::Alpha) { + mTexture.mAlpha = int(combinedAlpha() * 255); + } +} + +void renderer::ImageLayer::preprocessStage(const VRect &clip) +{ + mRenderNode.preprocess(clip); +} + +renderer::DrawableList renderer::ImageLayer::renderList() +{ + if (skipRendering()) return {}; + + return {&mDrawableList, 1}; +} + +renderer::NullLayer::NullLayer(model::Layer *layerData) + : renderer::Layer(layerData) +{ +} +void renderer::NullLayer::updateContent() {} + +static renderer::Object *createContentItem(model::Object *contentData, + VArenaAlloc * allocator) +{ + switch (contentData->type()) { + case model::Object::Type::Group: { + return allocator->make<renderer::Group>( + static_cast<model::Group *>(contentData), allocator); + } + case model::Object::Type::Rect: { + return allocator->make<renderer::Rect>( + static_cast<model::Rect *>(contentData)); + } + case model::Object::Type::Ellipse: { + return allocator->make<renderer::Ellipse>( + static_cast<model::Ellipse *>(contentData)); + } + case model::Object::Type::Path: { + return allocator->make<renderer::Path>( + static_cast<model::Path *>(contentData)); + } + case model::Object::Type::Polystar: { + return allocator->make<renderer::Polystar>( + static_cast<model::Polystar *>(contentData)); + } + case model::Object::Type::Fill: { + return allocator->make<renderer::Fill>( + static_cast<model::Fill *>(contentData)); + } + case model::Object::Type::GFill: { + return allocator->make<renderer::GradientFill>( + static_cast<model::GradientFill *>(contentData)); + } + case model::Object::Type::Stroke: { + return allocator->make<renderer::Stroke>( + static_cast<model::Stroke *>(contentData)); + } + case model::Object::Type::GStroke: { + return allocator->make<renderer::GradientStroke>( + static_cast<model::GradientStroke *>(contentData)); + } + case model::Object::Type::Repeater: { + return allocator->make<renderer::Repeater>( + static_cast<model::Repeater *>(contentData), allocator); + } + case model::Object::Type::Trim: { + return allocator->make<renderer::Trim>( + static_cast<model::Trim *>(contentData)); + } + default: + return nullptr; + break; + } +} + +renderer::ShapeLayer::ShapeLayer(model::Layer *layerData, + VArenaAlloc * allocator) + : renderer::Layer(layerData), + mRoot(allocator->make<renderer::Group>(nullptr, allocator)) +{ + mRoot->addChildren(layerData, allocator); + + std::vector<renderer::Shape *> list; + mRoot->processPaintItems(list); + + if (layerData->hasPathOperator()) { + list.clear(); + mRoot->processTrimItems(list); + } +} + +void renderer::ShapeLayer::updateContent() +{ + mRoot->update(frameNo(), combinedMatrix(), combinedAlpha(), flag()); + + if (mLayerData->hasPathOperator()) { + mRoot->applyTrim(); + } +} + +void renderer::ShapeLayer::preprocessStage(const VRect &clip) +{ + mDrawableList.clear(); + mRoot->renderList(mDrawableList); + + for (auto &drawable : mDrawableList) drawable->preprocess(clip); +} + +renderer::DrawableList renderer::ShapeLayer::renderList() +{ + if (skipRendering()) return {}; + + mDrawableList.clear(); + mRoot->renderList(mDrawableList); + + if (mDrawableList.empty()) return {}; + + return {mDrawableList.data(), mDrawableList.size()}; +} + +bool renderer::Group::resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value) +{ + if (!keyPath.skip(name())) { + if (!keyPath.matches(mModel.name(), depth)) { + return false; + } + + if (!keyPath.skip(mModel.name())) { + if (keyPath.fullyResolvesTo(mModel.name(), depth) && + transformProp(value.property())) { + mModel.filter()->addValue(value); + } + } + } + + if (keyPath.propagate(name(), depth)) { + uint newDepth = keyPath.nextDepth(name(), depth); + for (auto &child : mContents) { + child->resolveKeyPath(keyPath, newDepth, value); + } + } + return true; +} + +bool renderer::Fill::resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value) +{ + if (!keyPath.matches(mModel.name(), depth)) { + return false; + } + + if (keyPath.fullyResolvesTo(mModel.name(), depth) && + fillProp(value.property())) { + mModel.filter()->addValue(value); + return true; + } + return false; +} + +bool renderer::Stroke::resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value) +{ + if (!keyPath.matches(mModel.name(), depth)) { + return false; + } + + if (keyPath.fullyResolvesTo(mModel.name(), depth) && + strokeProp(value.property())) { + mModel.filter()->addValue(value); + return true; + } + return false; +} + +renderer::Group::Group(model::Group *data, VArenaAlloc *allocator) + : mModel(data) +{ + addChildren(data, allocator); +} + +void renderer::Group::addChildren(model::Group *data, VArenaAlloc *allocator) +{ + if (!data) return; + + if (!data->mChildren.empty()) mContents.reserve(data->mChildren.size()); + + // keep the content in back-to-front order. + // as lottie model keeps it in front-to-back order. + for (auto it = data->mChildren.crbegin(); it != data->mChildren.rend(); + ++it) { + auto content = createContentItem(*it, allocator); + if (content) { + mContents.push_back(content); + } + } +} + +void renderer::Group::update(int frameNo, const VMatrix &parentMatrix, + float parentAlpha, const DirtyFlag &flag) +{ + DirtyFlag newFlag = flag; + float alpha; + + if (mModel.hasModel() && mModel.transform()) { + VMatrix m = mModel.matrix(frameNo); + + m *= parentMatrix; + if (!(flag & DirtyFlagBit::Matrix) && !mModel.transform()->isStatic() && + (m != mMatrix)) { + newFlag |= DirtyFlagBit::Matrix; + } + + mMatrix = m; + + alpha = parentAlpha * mModel.transform()->opacity(frameNo); + if (!vCompare(alpha, parentAlpha)) { + newFlag |= DirtyFlagBit::Alpha; + } + } else { + mMatrix = parentMatrix; + alpha = parentAlpha; + } + + for (const auto &content : mContents) { + content->update(frameNo, matrix(), alpha, newFlag); + } +} + +void renderer::Group::applyTrim() +{ + for (auto i = mContents.rbegin(); i != mContents.rend(); ++i) { + auto content = (*i); + switch (content->type()) { + case renderer::Object::Type::Trim: { + static_cast<renderer::Trim *>(content)->update(); + break; + } + case renderer::Object::Type::Group: { + static_cast<renderer::Group *>(content)->applyTrim(); + break; + } + default: + break; + } + } +} + +void renderer::Group::renderList(std::vector<VDrawable *> &list) +{ + for (const auto &content : mContents) { + content->renderList(list); + } +} + +void renderer::Group::processPaintItems(std::vector<renderer::Shape *> &list) +{ + size_t curOpCount = list.size(); + for (auto i = mContents.rbegin(); i != mContents.rend(); ++i) { + auto content = (*i); + switch (content->type()) { + case renderer::Object::Type::Shape: { + auto pathItem = static_cast<renderer::Shape *>(content); + pathItem->setParent(this); + list.push_back(pathItem); + break; + } + case renderer::Object::Type::Paint: { + static_cast<renderer::Paint *>(content)->addPathItems(list, + curOpCount); + break; + } + case renderer::Object::Type::Group: { + static_cast<renderer::Group *>(content)->processPaintItems(list); + break; + } + default: + break; + } + } +} + +void renderer::Group::processTrimItems(std::vector<renderer::Shape *> &list) +{ + size_t curOpCount = list.size(); + for (auto i = mContents.rbegin(); i != mContents.rend(); ++i) { + auto content = (*i); + + switch (content->type()) { + case renderer::Object::Type::Shape: { + list.push_back(static_cast<renderer::Shape *>(content)); + break; + } + case renderer::Object::Type::Trim: { + static_cast<renderer::Trim *>(content)->addPathItems(list, + curOpCount); + break; + } + case renderer::Object::Type::Group: { + static_cast<renderer::Group *>(content)->processTrimItems(list); + break; + } + default: + break; + } + } +} + +/* + * renderer::Shape uses 2 path objects for path object reuse. + * mLocalPath - keeps track of the local path of the item before + * applying path operation and transformation. + * mTemp - keeps a referece to the mLocalPath and can be updated by the + * path operation objects(trim, merge path), + * We update the DirtyPath flag if the path needs to be updated again + * beacuse of local path or matrix or some path operation has changed which + * affects the final path. + * The PaintObject queries the dirty flag to check if it needs to compute the + * final path again and calls finalPath() api to do the same. + * finalPath() api passes a result Object so that we keep only one copy of + * the path object in the paintItem (for memory efficiency). + * NOTE: As path objects are COW objects we have to be + * carefull about the refcount so that we don't generate deep copy while + * modifying the path objects. + */ +void renderer::Shape::update(int frameNo, const VMatrix &, float, + const DirtyFlag &flag) +{ + mDirtyPath = false; + + // 1. update the local path if needed + if (hasChanged(frameNo)) { + // loose the reference to mLocalPath if any + // from the last frame update. + mTemp = VPath(); + + updatePath(mLocalPath, frameNo); + mDirtyPath = true; + } + // 2. keep a reference path in temp in case there is some + // path operation like trim which will update the path. + // we don't want to update the local path. + mTemp = mLocalPath; + + // 3. mark the path dirty if matrix has changed. + if (flag & DirtyFlagBit::Matrix) { + mDirtyPath = true; + } +} + +void renderer::Shape::finalPath(VPath &result) +{ + result.addPath(mTemp, static_cast<renderer::Group *>(parent())->matrix()); +} + +renderer::Rect::Rect(model::Rect *data) + : renderer::Shape(data->isStatic()), mData(data) +{ +} + +void renderer::Rect::updatePath(VPath &path, int frameNo) +{ + VPointF pos = mData->mPos.value(frameNo); + VPointF size = mData->mSize.value(frameNo); + float roundness = mData->roundness(frameNo); + VRectF r(pos.x() - size.x() / 2, pos.y() - size.y() / 2, size.x(), + size.y()); + + path.reset(); + path.addRoundRect(r, roundness, mData->direction()); +} + +renderer::Ellipse::Ellipse(model::Ellipse *data) + : renderer::Shape(data->isStatic()), mData(data) +{ +} + +void renderer::Ellipse::updatePath(VPath &path, int frameNo) +{ + VPointF pos = mData->mPos.value(frameNo); + VPointF size = mData->mSize.value(frameNo); + VRectF r(pos.x() - size.x() / 2, pos.y() - size.y() / 2, size.x(), + size.y()); + + path.reset(); + path.addOval(r, mData->direction()); +} + +renderer::Path::Path(model::Path *data) + : renderer::Shape(data->isStatic()), mData(data) +{ +} + +void renderer::Path::updatePath(VPath &path, int frameNo) +{ + mData->mShape.value(frameNo, path); +} + +renderer::Polystar::Polystar(model::Polystar *data) + : renderer::Shape(data->isStatic()), mData(data) +{ +} + +void renderer::Polystar::updatePath(VPath &path, int frameNo) +{ + VPointF pos = mData->mPos.value(frameNo); + float points = mData->mPointCount.value(frameNo); + float innerRadius = mData->mInnerRadius.value(frameNo); + float outerRadius = mData->mOuterRadius.value(frameNo); + float innerRoundness = mData->mInnerRoundness.value(frameNo); + float outerRoundness = mData->mOuterRoundness.value(frameNo); + float rotation = mData->mRotation.value(frameNo); + + path.reset(); + VMatrix m; + + if (mData->mPolyType == model::Polystar::PolyType::Star) { + path.addPolystar(points, innerRadius, outerRadius, innerRoundness, + outerRoundness, 0.0, 0.0, 0.0, mData->direction()); + } else { + path.addPolygon(points, outerRadius, outerRoundness, 0.0, 0.0, 0.0, + mData->direction()); + } + + m.translate(pos.x(), pos.y()).rotate(rotation); + m.rotate(rotation); + path.transform(m); +} + +/* + * PaintData Node handling + * + */ +renderer::Paint::Paint(bool staticContent) : mStaticContent(staticContent) {} + +void renderer::Paint::update(int frameNo, const VMatrix &parentMatrix, + float parentAlpha, const DirtyFlag & /*flag*/) +{ + mRenderNodeUpdate = true; + mContentToRender = updateContent(frameNo, parentMatrix, parentAlpha); +} + +void renderer::Paint::updateRenderNode() +{ + bool dirty = false; + for (auto &i : mPathItems) { + if (i->dirty()) { + dirty = true; + break; + } + } + + if (dirty) { + mPath.reset(); + for (const auto &i : mPathItems) { + i->finalPath(mPath); + } + mDrawable.setPath(mPath); + } else { + if (mDrawable.mFlag & VDrawable::DirtyState::Path) + mDrawable.mPath = mPath; + } +} + +void renderer::Paint::renderList(std::vector<VDrawable *> &list) +{ + if (mRenderNodeUpdate) { + updateRenderNode(); + mRenderNodeUpdate = false; + } + + // Q: Why we even update the final path if we don't have content + // to render ? + // Ans: We update the render nodes because we will loose the + // dirty path information at end of this frame. + // so if we return early without updating the final path. + // in the subsequent frame when we have content to render but + // we may not able to update our final path properly as we + // don't know what paths got changed in between. + if (mContentToRender) list.push_back(&mDrawable); +} + +void renderer::Paint::addPathItems(std::vector<renderer::Shape *> &list, + size_t startOffset) +{ + std::copy(list.begin() + startOffset, list.end(), + back_inserter(mPathItems)); +} + +renderer::Fill::Fill(model::Fill *data) + : renderer::Paint(data->isStatic()), mModel(data) +{ + mDrawable.setName(mModel.name()); +} + +bool renderer::Fill::updateContent(int frameNo, const VMatrix &, float alpha) +{ + auto combinedAlpha = alpha * mModel.opacity(frameNo); + auto color = mModel.color(frameNo).toColor(combinedAlpha); + + VBrush brush(color); + mDrawable.setBrush(brush); + mDrawable.setFillRule(mModel.fillRule()); + + return !color.isTransparent(); +} + +renderer::GradientFill::GradientFill(model::GradientFill *data) + : renderer::Paint(data->isStatic()), mData(data) +{ + mDrawable.setName(mData->name()); +} + +bool renderer::GradientFill::updateContent(int frameNo, const VMatrix &matrix, + float alpha) +{ + float combinedAlpha = alpha * mData->opacity(frameNo); + + mData->update(mGradient, frameNo); + mGradient->setAlpha(combinedAlpha); + mGradient->mMatrix = matrix; + mDrawable.setBrush(VBrush(mGradient.get())); + mDrawable.setFillRule(mData->fillRule()); + + return !vIsZero(combinedAlpha); +} + +renderer::Stroke::Stroke(model::Stroke *data) + : renderer::Paint(data->isStatic()), mModel(data) +{ + mDrawable.setName(mModel.name()); + if (mModel.hasDashInfo()) { + mDrawable.setType(VDrawable::Type::StrokeWithDash); + } else { + mDrawable.setType(VDrawable::Type::Stroke); + } +} + +static vthread_local std::vector<float> Dash_Vector; + +bool renderer::Stroke::updateContent(int frameNo, const VMatrix &matrix, + float alpha) +{ + auto combinedAlpha = alpha * mModel.opacity(frameNo); + auto color = mModel.color(frameNo).toColor(combinedAlpha); + + VBrush brush(color); + mDrawable.setBrush(brush); + float scale = matrix.scale(); + mDrawable.setStrokeInfo(mModel.capStyle(), mModel.joinStyle(), + mModel.miterLimit(), + mModel.strokeWidth(frameNo) * scale); + + if (mModel.hasDashInfo()) { + Dash_Vector.clear(); + mModel.getDashInfo(frameNo, Dash_Vector); + if (!Dash_Vector.empty()) { + for (auto &elm : Dash_Vector) elm *= scale; + mDrawable.setDashInfo(Dash_Vector); + } + } + + return !color.isTransparent(); +} + +renderer::GradientStroke::GradientStroke(model::GradientStroke *data) + : renderer::Paint(data->isStatic()), mData(data) +{ + mDrawable.setName(mData->name()); + if (mData->hasDashInfo()) { + mDrawable.setType(VDrawable::Type::StrokeWithDash); + } else { + mDrawable.setType(VDrawable::Type::Stroke); + } +} + +bool renderer::GradientStroke::updateContent(int frameNo, const VMatrix &matrix, + float alpha) +{ + float combinedAlpha = alpha * mData->opacity(frameNo); + + mData->update(mGradient, frameNo); + mGradient->setAlpha(combinedAlpha); + mGradient->mMatrix = matrix; + auto scale = mGradient->mMatrix.scale(); + mDrawable.setBrush(VBrush(mGradient.get())); + mDrawable.setStrokeInfo(mData->capStyle(), mData->joinStyle(), + mData->miterLimit(), mData->width(frameNo) * scale); + + if (mData->hasDashInfo()) { + Dash_Vector.clear(); + mData->getDashInfo(frameNo, Dash_Vector); + if (!Dash_Vector.empty()) { + for (auto &elm : Dash_Vector) elm *= scale; + mDrawable.setDashInfo(Dash_Vector); + } + } + + return !vIsZero(combinedAlpha); +} + +void renderer::Trim::update(int frameNo, const VMatrix & /*parentMatrix*/, + float /*parentAlpha*/, const DirtyFlag & /*flag*/) +{ + mDirty = false; + + if (mCache.mFrameNo == frameNo) return; + + model::Trim::Segment segment = mData->segment(frameNo); + + if (!(vCompare(mCache.mSegment.start, segment.start) && + vCompare(mCache.mSegment.end, segment.end))) { + mDirty = true; + mCache.mSegment = segment; + } + mCache.mFrameNo = frameNo; +} + +void renderer::Trim::update() +{ + // when both path and trim are not dirty + if (!(mDirty || pathDirty())) return; + + if (vCompare(mCache.mSegment.start, mCache.mSegment.end)) { + for (auto &i : mPathItems) { + i->updatePath(VPath()); + } + return; + } + + if (vCompare(std::fabs(mCache.mSegment.start - mCache.mSegment.end), 1)) { + for (auto &i : mPathItems) { + i->updatePath(i->localPath()); + } + return; + } + + if (mData->type() == model::Trim::TrimType::Simultaneously) { + for (auto &i : mPathItems) { + mPathMesure.setRange(mCache.mSegment.start, mCache.mSegment.end); + i->updatePath(mPathMesure.trim(i->localPath())); + } + } else { // model::Trim::TrimType::Individually + float totalLength = 0.0; + for (auto &i : mPathItems) { + totalLength += i->localPath().length(); + } + float start = totalLength * mCache.mSegment.start; + float end = totalLength * mCache.mSegment.end; + + 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; + mPathMesure.setRange(local_start, local_end); + i->updatePath(mPathMesure.trim(i->localPath())); + curLen += len; + } + } + } + } +} + +void renderer::Trim::addPathItems(std::vector<renderer::Shape *> &list, + size_t startOffset) +{ + std::copy(list.begin() + startOffset, list.end(), + back_inserter(mPathItems)); +} + +renderer::Repeater::Repeater(model::Repeater *data, VArenaAlloc *allocator) + : mRepeaterData(data) +{ + assert(mRepeaterData->content()); + + mCopies = mRepeaterData->maxCopies(); + + for (int i = 0; i < mCopies; i++) { + auto content = allocator->make<renderer::Group>( + mRepeaterData->content(), allocator); + // content->setParent(this); + mContents.push_back(content); + } +} + +void renderer::Repeater::update(int frameNo, const VMatrix &parentMatrix, + float parentAlpha, const DirtyFlag &flag) +{ + DirtyFlag newFlag = flag; + + float copies = mRepeaterData->copies(frameNo); + int visibleCopies = int(copies); + + if (visibleCopies == 0) { + mHidden = true; + return; + } + + mHidden = false; + + if (!mRepeaterData->isStatic()) newFlag |= DirtyFlagBit::Matrix; + + float offset = mRepeaterData->offset(frameNo); + float startOpacity = mRepeaterData->mTransform.startOpacity(frameNo); + float endOpacity = mRepeaterData->mTransform.endOpacity(frameNo); + + newFlag |= DirtyFlagBit::Alpha; + + for (int i = 0; i < mCopies; ++i) { + float newAlpha = + parentAlpha * lerp(startOpacity, endOpacity, i / copies); + + // hide rest of the copies , @TODO find a better solution. + if (i >= visibleCopies) newAlpha = 0; + + VMatrix result = mRepeaterData->mTransform.matrix(frameNo, i + offset) * + parentMatrix; + mContents[i]->update(frameNo, result, newAlpha, newFlag); + } +} + +void renderer::Repeater::renderList(std::vector<VDrawable *> &list) +{ + if (mHidden) return; + return renderer::Group::renderList(list); +} |