NekoX/TMessagesProj/jni/rlottie/src/lottie/lottieitem.cpp

1733 lines
51 KiB
C++
Executable File

/*
* Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "lottieitem.h"
#include <algorithm>
#include <cmath>
#include <iterator>
#include "lottiekeypath.h"
#include "vbitmap.h"
#include "vdasher.h"
#include "vpainter.h"
#include "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::Color:
case rlottie::Property::FillOpacity:
return true;
default:
return false;
}
}
static bool strokeProp(rlottie::Property prop)
{
switch (prop) {
case rlottie::Property::Color:
case rlottie::Property::StrokeOpacity:
case rlottie::Property::StrokeWidth:
return true;
default:
return false;
}
}
LOTCompItem::LOTCompItem(LOTModel *model)
: mUpdateViewBox(false), mCurFrameNo(-1)
{
mCompData = model->mRoot.get();
mRootLayer = createLayerItem(mCompData->mRootLayer.get());
mRootLayer->setComplexContent(false);
mViewSize = mCompData->size();
}
void LOTCompItem::setValue(const std::string &keypath, LOTVariant &value)
{
LOTKeyPath key(keypath);
mRootLayer->resolveKeyPath(key, 0, value);
mCurFrameNo = -1;
}
std::unique_ptr<LOTLayerItem> LOTCompItem::createLayerItem(
LOTLayerData *layerData)
{
switch (layerData->mLayerType) {
case LayerType::Precomp: {
return std::make_unique<LOTCompLayerItem>(layerData);
}
case LayerType::Solid: {
return std::make_unique<LOTSolidLayerItem>(layerData);
}
case LayerType::Shape: {
return std::make_unique<LOTShapeLayerItem>(layerData);
}
case LayerType::Null: {
return std::make_unique<LOTNullLayerItem>(layerData);
}
case LayerType::Image: {
return std::make_unique<LOTImageLayerItem>(layerData);
}
default:
return nullptr;
break;
}
}
void LOTCompItem::resize(const VSize &size)
{
if (mViewSize == size) return;
mViewSize = size;
mUpdateViewBox = true;
}
VSize LOTCompItem::size() const
{
return mViewSize;
}
bool LOTCompItem::update(int frameNo)
{
// check if cached frame is same as requested frame.
if (!mUpdateViewBox && (mCurFrameNo == frameNo)) return false;
/*
* 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.
*/
VSize viewPort = mViewSize;
VSize viewBox = mCompData->size();
float sx = float(viewPort.width()) / viewBox.width();
float sy = float(viewPort.height()) / viewBox.height();
float scale = fmin(sx, sy);
float tx = (viewPort.width() - viewBox.width() * scale) * 0.5;
float ty = (viewPort.height() - viewBox.height() * scale) * 0.5;
VMatrix m;
m.translate(tx, ty).scale(scale, scale);
mRootLayer->update(frameNo, m, 1.0);
mCurFrameNo = frameNo;
mUpdateViewBox = false;
return true;
}
void LOTCompItem::buildRenderTree()
{
mRootLayer->buildLayerNode();
}
const LOTLayerNode *LOTCompItem::renderTree() const
{
return mRootLayer->layerNode();
}
bool LOTCompItem::render(const rlottie::Surface &surface)
{
VBitmap bitmap(reinterpret_cast<uchar *>(surface.buffer()), surface.width(),
surface.height(), surface.bytesPerLine(),
VBitmap::Format::ARGB32);
/* schedule all preprocess task for this frame at once.
*/
mDrawableList.clear();
mRootLayer->renderList(mDrawableList);
VRect clip(0, 0, surface.drawRegionWidth(), surface.drawRegionHeight());
for (auto &e : mDrawableList) {
e->preprocess(clip);
}
VPainter painter(&bitmap);
// set sub surface area for drawing.
painter.setDrawRegion(
VRect(surface.drawRegionPosX(), surface.drawRegionPosY(),
surface.drawRegionWidth(), surface.drawRegionHeight()));
mRootLayer->render(&painter, {}, {});
return true;
}
void LOTMaskItem::update(int frameNo, const VMatrix & parentMatrix,
float /*parentAlpha*/, const DirtyFlag &flag)
{
if (flag.testFlag(DirtyFlagBit::None) && mData->isStatic()) return;
if (mData->mShape.isStatic()) {
if (mLocalPath.empty()) {
mData->mShape.value(frameNo).toPath(mLocalPath);
}
} else {
mData->mShape.value(frameNo).toPath(mLocalPath);
}
/* mask item dosen't inherit opacity */
mCombinedAlpha = mData->opacity(frameNo);
mFinalPath.clone(mLocalPath);
mFinalPath.transform(parentMatrix);
mRasterizer.rasterize(mFinalPath);
mRasterRequest = true;
}
VRle LOTMaskItem::rle()
{
if (mRasterRequest) {
mRasterRequest = false;
if (!vCompare(mCombinedAlpha, 1.0f))
mRasterizer.rle() *= (mCombinedAlpha * 255);
if (mData->mInv) mRasterizer.rle().invert();
}
return mRasterizer.rle();
}
void LOTLayerItem::buildLayerNode()
{
if (!mLayerCNode) {
mLayerCNode = std::make_unique<LOTLayerNode>();
mLayerCNode->mMaskList.ptr = nullptr;
mLayerCNode->mMaskList.size = 0;
mLayerCNode->mLayerList.ptr = nullptr;
mLayerCNode->mLayerList.size = 0;
mLayerCNode->mNodeList.ptr = nullptr;
mLayerCNode->mNodeList.size = 0;
mLayerCNode->mMatte = MatteNone;
mLayerCNode->mVisible = 0;
mLayerCNode->mAlpha = 255;
mLayerCNode->mClipPath.ptPtr = nullptr;
mLayerCNode->mClipPath.elmPtr = nullptr;
mLayerCNode->mClipPath.ptCount = 0;
mLayerCNode->mClipPath.elmCount = 0;
mLayerCNode->name = name().c_str();
}
if (complexContent()) mLayerCNode->mAlpha = combinedAlpha() * 255;
mLayerCNode->mVisible = visible();
// update matte
if (hasMatte()) {
switch (mLayerData->mMatteType) {
case MatteType::Alpha:
mLayerCNode->mMatte = MatteAlpha;
break;
case MatteType::AlphaInv:
mLayerCNode->mMatte = MatteAlphaInv;
break;
case MatteType::Luma:
mLayerCNode->mMatte = MatteLuma;
break;
case MatteType::LumaInv:
mLayerCNode->mMatte = MatteLumaInv;
break;
default:
mLayerCNode->mMatte = MatteNone;
break;
}
}
if (mLayerMask) {
mMasksCNode.clear();
mMasksCNode.resize(mLayerMask->mMasks.size());
size_t i = 0;
for (const auto &mask : mLayerMask->mMasks) {
LOTMask * cNode = &mMasksCNode[i++];
const std::vector<VPath::Element> &elm = mask.mFinalPath.elements();
const std::vector<VPointF> & pts = mask.mFinalPath.points();
const float *ptPtr = reinterpret_cast<const float *>(pts.data());
const char * elmPtr = reinterpret_cast<const char *>(elm.data());
cNode->mPath.ptPtr = ptPtr;
cNode->mPath.ptCount = pts.size();
cNode->mPath.elmPtr = elmPtr;
cNode->mPath.elmCount = elm.size();
cNode->mAlpha = mask.mCombinedAlpha * 255;
switch (mask.maskMode()) {
case LOTMaskData::Mode::Add:
cNode->mMode = MaskAdd;
break;
case LOTMaskData::Mode::Substarct:
cNode->mMode = MaskSubstract;
break;
case LOTMaskData::Mode::Intersect:
cNode->mMode = MaskIntersect;
break;
case LOTMaskData::Mode::Difference:
cNode->mMode = MaskDifference;
break;
default:
cNode->mMode = MaskAdd;
break;
}
}
mLayerCNode->mMaskList.ptr = mMasksCNode.data();
mLayerCNode->mMaskList.size = mMasksCNode.size();
}
}
void LOTLayerItem::render(VPainter *painter, const VRle &inheritMask,
const VRle &matteRle)
{
mDrawableList.clear();
renderList(mDrawableList);
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 : mDrawableList) {
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() == MatteType::AlphaInv) {
rle = rle - matteRle;
painter->drawRle(VPoint(), rle);
} else {
// render with matteRle as clip.
painter->drawRle(rle, matteRle);
}
}
}
}
LOTLayerMaskItem::LOTLayerMaskItem(LOTLayerData *layerData)
{
mMasks.reserve(layerData->mMasks.size());
for (auto &i : layerData->mMasks) {
mMasks.emplace_back(i.get());
mStatic &= i->isStatic();
}
}
void LOTLayerMaskItem::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 LOTLayerMaskItem::maskRle(const VRect &clipRect)
{
if (!mDirty) return mRle;
VRle rle;
for (auto &i : mMasks) {
switch (i.maskMode()) {
case LOTMaskData::Mode::Add: {
rle = rle + i.rle();
break;
}
case LOTMaskData::Mode::Substarct: {
if (rle.empty() && !clipRect.empty()) rle = VRle::toRle(clipRect);
rle = rle - i.rle();
break;
}
case LOTMaskData::Mode::Intersect: {
if (rle.empty() && !clipRect.empty()) rle = VRle::toRle(clipRect);
rle = rle & i.rle();
break;
}
case LOTMaskData::Mode::Difference: {
rle = rle ^ i.rle();
break;
}
default:
break;
}
}
if (!rle.empty() && !rle.unique()) {
mRle.clone(rle);
} else {
mRle = rle;
}
mDirty = false;
return mRle;
}
LOTLayerItem::LOTLayerItem(LOTLayerData *layerData) : mLayerData(layerData)
{
if (mLayerData->mHasMask)
mLayerMask = std::make_unique<LOTLayerMaskItem>(mLayerData);
}
bool LOTLayerItem::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 LOTShapeLayerItem::resolveKeyPath(LOTKeyPath &keyPath, uint depth,
LOTVariant &value)
{
if (LOTLayerItem::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 LOTCompLayerItem::resolveKeyPath(LOTKeyPath &keyPath, uint depth,
LOTVariant &value)
{
if (LOTLayerItem::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 LOTLayerItem::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.fuzzyCompare(m)) {
mDirtyFlag |= DirtyFlagBit::Matrix;
}
if (!vCompare(mCombinedAlpha, alpha)) {
mDirtyFlag |= DirtyFlagBit::Alpha;
}
mCombinedMatrix = m;
mCombinedAlpha = alpha;
// 4. update the mask
if (mLayerMask) {
mLayerMask->update(frameNo(), m, alpha, 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 LOTLayerItem::matrix(int frameNo) const
{
return mParentLayer
? (mLayerData->matrix(frameNo) * mParentLayer->matrix(frameNo))
: mLayerData->matrix(frameNo);
}
bool LOTLayerItem::visible() const
{
if (frameNo() >= mLayerData->inFrame() &&
frameNo() < mLayerData->outFrame())
return true;
else
return false;
}
LOTCompLayerItem::LOTCompLayerItem(LOTLayerData *layerModel)
: LOTLayerItem(layerModel)
{
// 1. create layer item
for (auto &i : mLayerData->mChildren) {
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 (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).get());
}
}
// 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());
// 4. check if its a nested composition
if (!layerModel->layerSize().empty()) {
mClipper = std::make_unique<LOTClipperItem>(layerModel->layerSize());
}
if (mLayers.size() > 1) setComplexContent(true);
}
void LOTCompLayerItem::buildLayerNode()
{
LOTLayerItem::buildLayerNode();
if (mClipper) {
const std::vector<VPath::Element> &elm = mClipper->mPath.elements();
const std::vector<VPointF> & pts = mClipper->mPath.points();
const float *ptPtr = reinterpret_cast<const float *>(pts.data());
const char * elmPtr = reinterpret_cast<const char *>(elm.data());
layerNode()->mClipPath.ptPtr = ptPtr;
layerNode()->mClipPath.elmPtr = elmPtr;
layerNode()->mClipPath.ptCount = 2 * pts.size();
layerNode()->mClipPath.elmCount = elm.size();
}
if (mLayers.size() != mLayersCNode.size()) {
for (const auto &layer : mLayers) {
layer->buildLayerNode();
mLayersCNode.push_back(layer->layerNode());
}
layerNode()->mLayerList.ptr = mLayersCNode.data();
layerNode()->mLayerList.size = mLayersCNode.size();
} else {
for (const auto &layer : mLayers) {
layer->buildLayerNode();
}
}
}
void LOTCompLayerItem::render(VPainter *painter, const VRle &inheritMask,
const VRle &matteRle)
{
if (vIsZero(combinedAlpha())) return;
if (vCompare(combinedAlpha(), 1.0)) {
renderHelper(painter, inheritMask, matteRle);
} else {
if (complexContent()) {
VSize size = painter->clipBoundingRect().size();
VPainter srcPainter;
VBitmap srcBitmap(size.width(), size.height(),
VBitmap::Format::ARGB32);
srcPainter.begin(&srcBitmap);
renderHelper(&srcPainter, inheritMask, matteRle);
srcPainter.end();
painter->drawBitmap(VPoint(), srcBitmap, combinedAlpha() * 255);
} else {
renderHelper(painter, inheritMask, matteRle);
}
}
}
void LOTCompLayerItem::renderHelper(VPainter *painter, const VRle &inheritMask,
const VRle &matteRle)
{
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) {
if (mask.empty()) {
mask = mClipper->rle();
} else {
mask = mClipper->rle() & mask;
}
}
LOTLayerItem *matte = nullptr;
for (const auto &layer : mLayers) {
if (layer->hasMatte()) {
matte = layer.get();
} else {
if (layer->visible()) {
if (matte) {
if (matte->visible())
renderMatteLayer(painter, mask, matteRle, matte,
layer.get());
} else {
layer->render(painter, mask, matteRle);
}
}
matte = nullptr;
}
}
}
void LOTCompLayerItem::renderMatteLayer(VPainter *painter, const VRle &mask,
const VRle & matteRle,
LOTLayerItem *layer, LOTLayerItem *src)
{
VSize size = painter->clipBoundingRect().size();
// Decide if we can use fast matte.
// 1. draw src layer to matte buffer
VPainter srcPainter;
src->bitmap().reset(size.width(), size.height(),
VBitmap::Format::ARGB32);
srcPainter.begin(&src->bitmap());
src->render(&srcPainter, mask, matteRle);
srcPainter.end();
// 2. draw layer to layer buffer
VPainter layerPainter;
layer->bitmap().reset(size.width(), size.height(),
VBitmap::Format::ARGB32);
layerPainter.begin(&layer->bitmap());
layer->render(&layerPainter, mask, matteRle);
// 2.1update composition mode
switch (layer->matteType()) {
case MatteType::Alpha:
case MatteType::Luma: {
layerPainter.setCompositionMode(
VPainter::CompositionMode::CompModeDestIn);
break;
}
case MatteType::AlphaInv:
case MatteType::LumaInv: {
layerPainter.setCompositionMode(
VPainter::CompositionMode::CompModeDestOut);
break;
}
default:
break;
}
// 2.2 update srcBuffer if the matte is luma type
if (layer->matteType() == MatteType::Luma ||
layer->matteType() == MatteType::LumaInv) {
src->bitmap().updateLuma();
}
// 2.3 draw src buffer as mask
layerPainter.drawBitmap(VPoint(), src->bitmap());
layerPainter.end();
// 3. draw the result buffer into painter
painter->drawBitmap(VPoint(), layer->bitmap());
}
void LOTClipperItem::update(const VMatrix &matrix)
{
mPath.reset();
mPath.addRect(VRectF(0, 0, mSize.width(), mSize.height()));
mPath.transform(matrix);
mRasterizer.rasterize(mPath);
}
VRle LOTClipperItem::rle()
{
return mRasterizer.rle();
}
void LOTCompLayerItem::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 LOTCompLayerItem::renderList(std::vector<VDrawable *> &list)
{
if (!visible() || vIsZero(combinedAlpha())) return;
LOTLayerItem *matte = nullptr;
for (const auto &layer : mLayers) {
if (layer->hasMatte()) {
matte = layer.get();
} else {
if (layer->visible()) {
if (matte) {
if (matte->visible()) {
layer->renderList(list);
matte->renderList(list);
}
} else {
layer->renderList(list);
}
}
matte = nullptr;
}
}
}
LOTSolidLayerItem::LOTSolidLayerItem(LOTLayerData *layerData)
: LOTLayerItem(layerData)
{
}
void LOTSolidLayerItem::updateContent()
{
if (flag() & DirtyFlagBit::Matrix) {
VPath path;
path.addRect(
VRectF(0, 0, mLayerData->solidWidth(), mLayerData->solidHeight()));
path.transform(combinedMatrix());
mRenderNode.mFlag |= VDrawable::DirtyState::Path;
mRenderNode.mPath = path;
}
if (flag() & DirtyFlagBit::Alpha) {
LottieColor color = mLayerData->solidColor();
VBrush brush(color.toColor(combinedAlpha()));
mRenderNode.setBrush(brush);
mRenderNode.mFlag |= VDrawable::DirtyState::Brush;
}
}
void LOTSolidLayerItem::buildLayerNode()
{
LOTLayerItem::buildLayerNode();
mDrawableList.clear();
renderList(mDrawableList);
mCNodeList.clear();
for (auto &i : mDrawableList) {
LOTDrawable *lotDrawable = static_cast<LOTDrawable *>(i);
lotDrawable->sync();
mCNodeList.push_back(lotDrawable->mCNode.get());
}
layerNode()->mNodeList.ptr = mCNodeList.data();
layerNode()->mNodeList.size = mCNodeList.size();
}
void LOTSolidLayerItem::renderList(std::vector<VDrawable *> &list)
{
if (!visible() || vIsZero(combinedAlpha())) return;
list.push_back(&mRenderNode);
}
LOTImageLayerItem::LOTImageLayerItem(LOTLayerData *layerData)
: LOTLayerItem(layerData)
{
VBrush brush(mLayerData->mAsset->bitmap());
mRenderNode.setBrush(brush);
}
void LOTImageLayerItem::updateContent()
{
if (flag() & DirtyFlagBit::Matrix) {
VPath path;
path.addRect(VRectF(0, 0, mLayerData->mAsset->mWidth,
mLayerData->mAsset->mHeight));
path.transform(combinedMatrix());
mRenderNode.mFlag |= VDrawable::DirtyState::Path;
mRenderNode.mPath = path;
mRenderNode.mBrush.setMatrix(combinedMatrix());
}
if (flag() & DirtyFlagBit::Alpha) {
//@TODO handle alpha with the image.
}
}
void LOTImageLayerItem::renderList(std::vector<VDrawable *> &list)
{
if (!visible() || vIsZero(combinedAlpha())) return;
list.push_back(&mRenderNode);
}
void LOTImageLayerItem::buildLayerNode()
{
LOTLayerItem::buildLayerNode();
mDrawableList.clear();
renderList(mDrawableList);
mCNodeList.clear();
for (auto &i : mDrawableList) {
LOTDrawable *lotDrawable = static_cast<LOTDrawable *>(i);
lotDrawable->sync();
lotDrawable->mCNode->mImageInfo.data =
lotDrawable->mBrush.mTexture.data();
lotDrawable->mCNode->mImageInfo.width =
lotDrawable->mBrush.mTexture.width();
lotDrawable->mCNode->mImageInfo.height =
lotDrawable->mBrush.mTexture.height();
lotDrawable->mCNode->mImageInfo.mMatrix.m11 = combinedMatrix().m_11();
lotDrawable->mCNode->mImageInfo.mMatrix.m12 = combinedMatrix().m_12();
lotDrawable->mCNode->mImageInfo.mMatrix.m13 = combinedMatrix().m_13();
lotDrawable->mCNode->mImageInfo.mMatrix.m21 = combinedMatrix().m_21();
lotDrawable->mCNode->mImageInfo.mMatrix.m22 = combinedMatrix().m_22();
lotDrawable->mCNode->mImageInfo.mMatrix.m23 = combinedMatrix().m_23();
lotDrawable->mCNode->mImageInfo.mMatrix.m31 = combinedMatrix().m_tx();
lotDrawable->mCNode->mImageInfo.mMatrix.m32 = combinedMatrix().m_ty();
lotDrawable->mCNode->mImageInfo.mMatrix.m33 = combinedMatrix().m_33();
mCNodeList.push_back(lotDrawable->mCNode.get());
}
layerNode()->mNodeList.ptr = mCNodeList.data();
layerNode()->mNodeList.size = mCNodeList.size();
}
LOTNullLayerItem::LOTNullLayerItem(LOTLayerData *layerData)
: LOTLayerItem(layerData)
{
}
void LOTNullLayerItem::updateContent() {}
LOTShapeLayerItem::LOTShapeLayerItem(LOTLayerData *layerData)
: LOTLayerItem(layerData),
mRoot(std::make_unique<LOTContentGroupItem>(nullptr))
{
mRoot->addChildren(layerData);
std::vector<LOTPathDataItem *> list;
mRoot->processPaintItems(list);
if (layerData->hasPathOperator()) {
list.clear();
mRoot->processTrimItems(list);
}
}
std::unique_ptr<LOTContentItem> LOTShapeLayerItem::createContentItem(
LOTData *contentData)
{
switch (contentData->type()) {
case LOTData::Type::ShapeGroup: {
return std::make_unique<LOTContentGroupItem>(
static_cast<LOTGroupData *>(contentData));
}
case LOTData::Type::Rect: {
return std::make_unique<LOTRectItem>(
static_cast<LOTRectData *>(contentData));
}
case LOTData::Type::Ellipse: {
return std::make_unique<LOTEllipseItem>(
static_cast<LOTEllipseData *>(contentData));
}
case LOTData::Type::Shape: {
return std::make_unique<LOTShapeItem>(
static_cast<LOTShapeData *>(contentData));
}
case LOTData::Type::Polystar: {
return std::make_unique<LOTPolystarItem>(
static_cast<LOTPolystarData *>(contentData));
}
case LOTData::Type::Fill: {
return std::make_unique<LOTFillItem>(
static_cast<LOTFillData *>(contentData));
}
case LOTData::Type::GFill: {
return std::make_unique<LOTGFillItem>(
static_cast<LOTGFillData *>(contentData));
}
case LOTData::Type::Stroke: {
return std::make_unique<LOTStrokeItem>(
static_cast<LOTStrokeData *>(contentData));
}
case LOTData::Type::GStroke: {
return std::make_unique<LOTGStrokeItem>(
static_cast<LOTGStrokeData *>(contentData));
}
case LOTData::Type::Repeater: {
return std::make_unique<LOTRepeaterItem>(
static_cast<LOTRepeaterData *>(contentData));
}
case LOTData::Type::Trim: {
return std::make_unique<LOTTrimItem>(
static_cast<LOTTrimData *>(contentData));
}
default:
return nullptr;
break;
}
}
void LOTShapeLayerItem::updateContent()
{
mRoot->update(frameNo(), combinedMatrix(), combinedAlpha(), flag());
if (mLayerData->hasPathOperator()) {
mRoot->applyTrim();
}
}
void LOTShapeLayerItem::buildLayerNode()
{
LOTLayerItem::buildLayerNode();
mDrawableList.clear();
renderList(mDrawableList);
mCNodeList.clear();
for (auto &i : mDrawableList) {
LOTDrawable *lotDrawable = static_cast<LOTDrawable *>(i);
lotDrawable->sync();
mCNodeList.push_back(lotDrawable->mCNode.get());
}
layerNode()->mNodeList.ptr = mCNodeList.data();
layerNode()->mNodeList.size = mCNodeList.size();
}
void LOTShapeLayerItem::renderList(std::vector<VDrawable *> &list)
{
if (!visible() || vIsZero(combinedAlpha())) return;
mRoot->renderList(list);
}
bool LOTContentGroupItem::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 property update
}
}
if (keyPath.propagate(name(), depth)) {
uint newDepth = keyPath.nextDepth(name(), depth);
for (auto &child : mContents) {
child->resolveKeyPath(keyPath, newDepth, value);
}
}
return true;
}
bool LOTFillItem::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 LOTStrokeItem::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;
}
LOTContentGroupItem::LOTContentGroupItem(LOTGroupData *data)
: LOTContentItem(ContentType::Group), mData(data)
{
addChildren(mData);
}
void LOTContentGroupItem::addChildren(LOTGroupData *data)
{
if (!data) return;
for (auto &i : data->mChildren) {
auto content = LOTShapeLayerItem::createContentItem(i.get());
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,
float parentAlpha, const DirtyFlag &flag)
{
VMatrix m = parentMatrix;
float alpha = parentAlpha;
DirtyFlag newFlag = flag;
if (mData && mData->mTransform) {
// update the matrix and the flag
if ((flag & DirtyFlagBit::Matrix) || !mData->mTransform->isStatic()) {
newFlag |= DirtyFlagBit::Matrix;
}
m = mData->mTransform->matrix(frameNo);
m *= parentMatrix;
alpha *= mData->mTransform->opacity(frameNo);
if (!vCompare(alpha, parentAlpha)) {
newFlag |= DirtyFlagBit::Alpha;
}
}
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) {
auto content = (*i).get();
switch (content->type()) {
case ContentType::Trim: {
static_cast<LOTTrimItem *>(content)->update();
break;
}
case ContentType::Group: {
static_cast<LOTContentGroupItem *>(content)->applyTrim();
break;
}
default:
break;
}
}
}
void LOTContentGroupItem::renderList(std::vector<VDrawable *> &list)
{
for (const auto &content : mContents) {
content->renderList(list);
}
}
void LOTContentGroupItem::processPaintItems(
std::vector<LOTPathDataItem *> &list)
{
int curOpCount = list.size();
for (auto i = mContents.rbegin(); i != mContents.rend(); ++i) {
auto content = (*i).get();
switch (content->type()) {
case ContentType::Path: {
list.push_back(static_cast<LOTPathDataItem *>(content));
break;
}
case ContentType::Paint: {
static_cast<LOTPaintDataItem *>(content)->addPathItems(list,
curOpCount);
break;
}
case ContentType::Group: {
static_cast<LOTContentGroupItem *>(content)->processPaintItems(
list);
break;
}
default:
break;
}
}
}
void LOTContentGroupItem::processTrimItems(std::vector<LOTPathDataItem *> &list)
{
int curOpCount = list.size();
for (auto i = mContents.rbegin(); i != mContents.rend(); ++i) {
auto content = (*i).get();
switch (content->type()) {
case ContentType::Path: {
list.push_back(static_cast<LOTPathDataItem *>(content));
break;
}
case ContentType::Trim: {
static_cast<LOTTrimItem *>(content)->addPathItems(list, curOpCount);
break;
}
case ContentType::Group: {
static_cast<LOTContentGroupItem *>(content)->processTrimItems(list);
break;
}
default:
break;
}
}
}
/*
* LOTPathDataItem uses 3 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),
* mFinalPath - it takes a deep copy of the intermediate path(mTemp) each time
* when the path is dirty(so if path changes every frame we don't realloc just
* copy to the final path). 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 LOTPathDataItem::update(int frameNo, const VMatrix &, float,
const DirtyFlag &flag)
{
mPathChanged = 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);
mPathChanged = true;
mNeedUpdate = 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. compute the final path with parentMatrix
if ((flag & DirtyFlagBit::Matrix) || mPathChanged) {
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)
{
}
void LOTRectItem::updatePath(VPath &path, int frameNo)
{
VPointF pos = mData->mPos.value(frameNo);
VPointF size = mData->mSize.value(frameNo);
float roundness = mData->mRound.value(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());
}
LOTEllipseItem::LOTEllipseItem(LOTEllipseData *data)
: LOTPathDataItem(data->isStatic()), mData(data)
{
}
void LOTEllipseItem::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());
}
LOTShapeItem::LOTShapeItem(LOTShapeData *data)
: LOTPathDataItem(data->isStatic()), mData(data)
{
}
void LOTShapeItem::updatePath(VPath &path, int frameNo)
{
mData->mShape.value(frameNo).toPath(path);
}
LOTPolystarItem::LOTPolystarItem(LOTPolystarData *data)
: LOTPathDataItem(data->isStatic()), mData(data)
{
}
void LOTPolystarItem::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->mType == LOTPolystarData::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
*
*/
LOTPaintDataItem::LOTPaintDataItem(bool staticContent)
: LOTContentItem(ContentType::Paint), mStaticContent(staticContent)
{
}
void LOTPaintDataItem::update(int frameNo, const VMatrix & /*parentMatrix*/,
float parentAlpha, const DirtyFlag &flag)
{
mRenderNodeUpdate = true;
mParentAlpha = parentAlpha;
mFlag = flag;
mFrameNo = frameNo;
updateContent(frameNo);
}
void LOTPaintDataItem::updateRenderNode()
{
bool dirty = false;
for (auto &i : mPathItems) {
if (i->dirty()) {
dirty = true;
break;
}
}
if (dirty) {
mPath.reset();
for (auto &i : mPathItems) {
mPath.addPath(i->finalPath());
}
mDrawable.setPath(mPath);
} else {
if (mDrawable.mFlag & VDrawable::DirtyState::Path)
mDrawable.mPath = mPath;
}
}
void LOTPaintDataItem::renderList(std::vector<VDrawable *> &list)
{
if (mRenderNodeUpdate) {
updateRenderNode();
LOTPaintDataItem::updateRenderNode();
mRenderNodeUpdate = false;
}
list.push_back(&mDrawable);
}
void LOTPaintDataItem::addPathItems(std::vector<LOTPathDataItem *> &list,
int startOffset)
{
std::copy(list.begin() + startOffset, list.end(),
back_inserter(mPathItems));
}
LOTFillItem::LOTFillItem(LOTFillData *data)
: LOTPaintDataItem(data->isStatic()), mModel(data)
{
}
void LOTFillItem::updateContent(int frameNo)
{
mColor = mModel.color(frameNo).toColor(mModel.opacity(frameNo));
}
void LOTFillItem::updateRenderNode()
{
VColor color = mColor;
color.setAlpha(color.a * parentAlpha());
VBrush brush(color);
mDrawable.setBrush(brush);
mDrawable.setFillRule(mModel.fillRule());
}
LOTGFillItem::LOTGFillItem(LOTGFillData *data)
: LOTPaintDataItem(data->isStatic()), mData(data)
{
}
void LOTGFillItem::updateContent(int frameNo)
{
mAlpha = mData->opacity(frameNo);
mData->update(mGradient, frameNo);
mGradient->mMatrix = static_cast<LOTContentGroupItem *>(parent())->matrix();
mFillRule = mData->fillRule();
}
void LOTGFillItem::updateRenderNode()
{
mGradient->setAlpha(mAlpha * parentAlpha());
mDrawable.setBrush(VBrush(mGradient.get()));
mDrawable.setFillRule(mFillRule);
}
LOTStrokeItem::LOTStrokeItem(LOTStrokeData *data)
: LOTPaintDataItem(data->isStatic()), mModel(data)
{
mDashArraySize = 0;
}
void LOTStrokeItem::updateContent(int frameNo)
{
mColor = mModel.color(frameNo).toColor(mModel.opacity(frameNo));
mWidth = mModel.strokeWidth(frameNo);
if (mModel.hasDashInfo()) {
mDashArraySize = mModel.getDashInfo(frameNo, mDashArray);
}
}
static float getScale(const VMatrix &matrix)
{
constexpr float SQRT_2 = 1.41421;
VPointF p1(0, 0);
VPointF p2(SQRT_2, SQRT_2);
p1 = matrix.map(p1);
p2 = matrix.map(p2);
VPointF final = p2 - p1;
return std::sqrt(final.x() * final.x() + final.y() * final.y()) / 2.0;
}
void LOTStrokeItem::updateRenderNode()
{
VColor color = mColor;
color.setAlpha(color.a * parentAlpha());
VBrush brush(color);
mDrawable.setBrush(brush);
float scale =
getScale(static_cast<LOTContentGroupItem *>(parent())->matrix());
mDrawable.setStrokeInfo(mModel.capStyle(), mModel.joinStyle(),
mModel.meterLimit(), mWidth * scale);
if (mDashArraySize) {
for (int i = 0; i < mDashArraySize; i++) mDashArray[i] *= scale;
/* AE draw the dash even if dash value is 0 */
if (vCompare(mDashArray[0], 0.0f)) mDashArray[0] = 0.1;
mDrawable.setDashInfo(mDashArray, mDashArraySize);
}
}
LOTGStrokeItem::LOTGStrokeItem(LOTGStrokeData *data)
: LOTPaintDataItem(data->isStatic()), mData(data)
{
mDashArraySize = 0;
}
void LOTGStrokeItem::updateContent(int frameNo)
{
mAlpha = mData->opacity(frameNo);
mData->update(mGradient, frameNo);
mGradient->mMatrix = static_cast<LOTContentGroupItem *>(parent())->matrix();
mCap = mData->capStyle();
mJoin = mData->joinStyle();
mMiterLimit = mData->meterLimit();
mWidth = mData->width(frameNo);
if (mData->hasDashInfo()) {
mDashArraySize = mData->getDashInfo(frameNo, mDashArray);
}
}
void LOTGStrokeItem::updateRenderNode()
{
float scale = getScale(mGradient->mMatrix);
mGradient->setAlpha(mAlpha * parentAlpha());
mDrawable.setBrush(VBrush(mGradient.get()));
mDrawable.setStrokeInfo(mCap, mJoin, mMiterLimit, mWidth * scale);
if (mDashArraySize) {
for (int i = 0; i < mDashArraySize; i++) mDashArray[i] *= scale;
mDrawable.setDashInfo(mDashArray, mDashArraySize);
}
}
LOTTrimItem::LOTTrimItem(LOTTrimData *data)
: LOTContentItem(ContentType::Trim), mData(data)
{
}
void LOTTrimItem::update(int frameNo, const VMatrix & /*parentMatrix*/,
float /*parentAlpha*/, const DirtyFlag & /*flag*/)
{
mDirty = false;
if (mCache.mFrameNo == frameNo) return;
LOTTrimData::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 LOTTrimItem::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() == LOTTrimData::TrimType::Simultaneously) {
for (auto &i : mPathItems) {
VPathMesure pm;
pm.setStart(mCache.mSegment.start);
pm.setEnd(mCache.mSegment.end);
i->updatePath(pm.trim(i->localPath()));
}
} else { // LOTTrimData::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;
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) : mRepeaterData(data)
{
assert(mRepeaterData->content());
mCopies = mRepeaterData->maxCopies();
for (int i = 0; i < mCopies; i++) {
auto content =
std::make_unique<LOTContentGroupItem>(mRepeaterData->content());
content->setParent(this);
mContents.push_back(std::move(content));
}
}
void LOTRepeaterItem::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;
} else {
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 LOTRepeaterItem::renderList(std::vector<VDrawable *> &list)
{
if (mHidden) return;
return LOTContentGroupItem::renderList(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()
{
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) {
if (mStroke.mDash.size()) {
VDasher dasher(mStroke.mDash.data(), mStroke.mDash.size());
mPath = dasher.dashed(mPath);
}
const std::vector<VPath::Element> &elm = mPath.elements();
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;
}
if (mStroke.enable) {
mCNode->mStroke.width = mStroke.width;
mCNode->mStroke.meterLimit = mStroke.meterLimit;
mCNode->mStroke.enable = 1;
switch (mStroke.cap) {
case CapStyle::Flat:
mCNode->mStroke.cap = LOTCapStyle::CapFlat;
break;
case CapStyle::Square:
mCNode->mStroke.cap = LOTCapStyle::CapSquare;
break;
case CapStyle::Round:
mCNode->mStroke.cap = LOTCapStyle::CapRound;
break;
}
switch (mStroke.join) {
case JoinStyle::Miter:
mCNode->mStroke.join = LOTJoinStyle::JoinMiter;
break;
case JoinStyle::Bevel:
mCNode->mStroke.join = LOTJoinStyle::JoinBevel;
break;
case JoinStyle::Round:
mCNode->mStroke.join = LOTJoinStyle::JoinRound;
break;
default:
mCNode->mStroke.join = LOTJoinStyle::JoinMiter;
break;
}
} else {
mCNode->mStroke.enable = 0;
}
switch (mFillRule) {
case FillRule::EvenOdd:
mCNode->mFillRule = LOTFillRule::FillEvenOdd;
break;
default:
mCNode->mFillRule = LOTFillRule::FillWinding;
break;
}
switch (mBrush.type()) {
case VBrush::Type::Solid:
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->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->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;
}
}