mirror of
https://github.com/cwinfo/matterbridge.git
synced 2024-12-27 22:35:43 +00:00
53cafa9f3d
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>
1492 lines
44 KiB
C++
1492 lines
44 KiB
C++
/*
|
|
* 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);
|
|
}
|