/* * 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_lottiemodel.h" #include <cassert> #include <iterator> #include <stack> #include "vector_vimageloader.h" #include "vector_vline.h" using namespace rlottie::internal; /* * We process the iterator objects in the children list * by iterating from back to front. when we find a repeater object * we remove the objects from satrt till repeater object and then place * under a new shape group object which we add it as children to the repeater * object. * Then we visit the childrens of the newly created shape group object to * process the remaining repeater object(when children list contains more than * one repeater). * */ class LottieRepeaterProcesser { public: void visitChildren(model::Group *obj) { for (auto i = obj->mChildren.rbegin(); i != obj->mChildren.rend(); ++i) { auto child = (*i); if (child->type() == model::Object::Type::Repeater) { model::Repeater *repeater = static_cast<model::Repeater *>(child); // check if this repeater is already processed // can happen if the layer is an asset and referenced by // multiple layer. if (repeater->processed()) continue; repeater->markProcessed(); auto content = repeater->content(); // 1. increment the reverse iterator to point to the // object before the repeater ++i; // 2. move all the children till repater to the group std::move(obj->mChildren.begin(), i.base(), back_inserter(content->mChildren)); // 3. erase the objects from the original children list obj->mChildren.erase(obj->mChildren.begin(), i.base()); // 5. visit newly created group to process remaining repeater // object. visitChildren(content); // 6. exit the loop as the current iterators are invalid break; } visit(child); } } void visit(model::Object *obj) { switch (obj->type()) { case model::Object::Type::Group: case model::Object::Type::Layer: { visitChildren(static_cast<model::Group *>(obj)); break; } default: break; } } }; class LottieUpdateStatVisitor { model::Composition::Stats *stat; public: explicit LottieUpdateStatVisitor(model::Composition::Stats *s) : stat(s) {} void visitChildren(model::Group *obj) { for (const auto &child : obj->mChildren) { if (child) visit(child); } } void visitLayer(model::Layer *layer) { switch (layer->mLayerType) { case model::Layer::Type::Precomp: stat->precompLayerCount++; break; case model::Layer::Type::Null: stat->nullLayerCount++; break; case model::Layer::Type::Shape: stat->shapeLayerCount++; break; case model::Layer::Type::Solid: stat->solidLayerCount++; break; case model::Layer::Type::Image: stat->imageLayerCount++; break; default: break; } visitChildren(layer); } void visit(model::Object *obj) { switch (obj->type()) { case model::Object::Type::Layer: { visitLayer(static_cast<model::Layer *>(obj)); break; } case model::Object::Type::Repeater: { visitChildren(static_cast<model::Repeater *>(obj)->content()); break; } case model::Object::Type::Group: { visitChildren(static_cast<model::Group *>(obj)); break; } default: break; } } }; void model::Composition::processRepeaterObjects() { LottieRepeaterProcesser visitor; visitor.visit(mRootLayer); } void model::Composition::updateStats() { LottieUpdateStatVisitor visitor(&mStats); visitor.visit(mRootLayer); } VMatrix model::Repeater::Transform::matrix(int frameNo, float multiplier) const { VPointF scale = mScale.value(frameNo) / 100.f; scale.setX(std::pow(scale.x(), multiplier)); scale.setY(std::pow(scale.y(), multiplier)); VMatrix m; m.translate(mPosition.value(frameNo) * multiplier) .translate(mAnchor.value(frameNo)) .scale(scale) .rotate(mRotation.value(frameNo) * multiplier) .translate(-mAnchor.value(frameNo)); return m; } VMatrix model::Transform::Data::matrix(int frameNo, bool autoOrient) const { VMatrix m; VPointF position; if (mExtra && mExtra->mSeparate) { position.setX(mExtra->mSeparateX.value(frameNo)); position.setY(mExtra->mSeparateY.value(frameNo)); } else { position = mPosition.value(frameNo); } float angle = autoOrient ? mPosition.angle(frameNo) : 0; if (mExtra && mExtra->m3DData) { m.translate(position) .rotate(mExtra->m3DRz.value(frameNo) + angle) .rotate(mExtra->m3DRy.value(frameNo), VMatrix::Axis::Y) .rotate(mExtra->m3DRx.value(frameNo), VMatrix::Axis::X) .scale(mScale.value(frameNo) / 100.f) .translate(-mAnchor.value(frameNo)); } else { m.translate(position) .rotate(mRotation.value(frameNo) + angle) .scale(mScale.value(frameNo) / 100.f) .translate(-mAnchor.value(frameNo)); } return m; } void model::Dash::getDashInfo(int frameNo, std::vector<float> &result) const { result.clear(); if (mData.size() <= 1) return; if (result.capacity() < mData.size()) result.reserve(mData.size() + 1); for (const auto &elm : mData) result.push_back(elm.value(frameNo)); // if the size is even then we are missing last // gap information which is same as the last dash value // copy it from the last dash value. // NOTE: last value is the offset and last-1 is the last dash value. auto size = result.size(); if ((size % 2) == 0) { // copy offset value to end. result.push_back(result.back()); // copy dash value to gap. result[size - 1] = result[size - 2]; } } /** * Both the color stops and opacity stops are in the same array. * There are {@link #colorPoints} colors sequentially as: * [ * ..., * position, * red, * green, * blue, * ... * ] * * The remainder of the array is the opacity stops sequentially as: * [ * ..., * position, * opacity, * ... * ] */ void model::Gradient::populate(VGradientStops &stops, int frameNo) { model::Gradient::Data gradData = mGradient.value(frameNo); auto size = gradData.mGradient.size(); float * ptr = gradData.mGradient.data(); int colorPoints = mColorPoints; if (colorPoints == -1) { // for legacy bodymovin (ref: lottie-android) colorPoints = int(size / 4); } auto opacityArraySize = size - colorPoints * 4; float *opacityPtr = ptr + (colorPoints * 4); stops.clear(); size_t j = 0; for (int i = 0; i < colorPoints; i++) { float colorStop = ptr[0]; model::Color color = model::Color(ptr[1], ptr[2], ptr[3]); if (opacityArraySize) { if (j == opacityArraySize) { // already reached the end float stop1 = opacityPtr[j - 4]; float op1 = opacityPtr[j - 3]; float stop2 = opacityPtr[j - 2]; float op2 = opacityPtr[j - 1]; if (colorStop > stop2) { stops.push_back( std::make_pair(colorStop, color.toColor(op2))); } else { float progress = (colorStop - stop1) / (stop2 - stop1); float opacity = op1 + progress * (op2 - op1); stops.push_back( std::make_pair(colorStop, color.toColor(opacity))); } continue; } for (; j < opacityArraySize; j += 2) { float opacityStop = opacityPtr[j]; if (opacityStop < colorStop) { // add a color using opacity stop stops.push_back(std::make_pair( opacityStop, color.toColor(opacityPtr[j + 1]))); continue; } // add a color using color stop if (j == 0) { stops.push_back(std::make_pair( colorStop, color.toColor(opacityPtr[j + 1]))); } else { float progress = (colorStop - opacityPtr[j - 2]) / (opacityPtr[j] - opacityPtr[j - 2]); float opacity = opacityPtr[j - 1] + progress * (opacityPtr[j + 1] - opacityPtr[j - 1]); stops.push_back( std::make_pair(colorStop, color.toColor(opacity))); } j += 2; break; } } else { stops.push_back(std::make_pair(colorStop, color.toColor())); } ptr += 4; } } void model::Gradient::update(std::unique_ptr<VGradient> &grad, int frameNo) { bool init = false; if (!grad) { if (mGradientType == 1) grad = std::make_unique<VGradient>(VGradient::Type::Linear); else grad = std::make_unique<VGradient>(VGradient::Type::Radial); grad->mSpread = VGradient::Spread::Pad; init = true; } if (!mGradient.isStatic() || init) { populate(grad->mStops, frameNo); } if (mGradientType == 1) { // linear gradient VPointF start = mStartPoint.value(frameNo); VPointF end = mEndPoint.value(frameNo); grad->linear.x1 = start.x(); grad->linear.y1 = start.y(); grad->linear.x2 = end.x(); grad->linear.y2 = end.y(); } else { // radial gradient VPointF start = mStartPoint.value(frameNo); VPointF end = mEndPoint.value(frameNo); grad->radial.cx = start.x(); grad->radial.cy = start.y(); grad->radial.cradius = VLine::length(start.x(), start.y(), end.x(), end.y()); /* * Focal point is the point lives in highlight length distance from * center along the line (start, end) and rotated by highlight angle. * below calculation first finds the quadrant(angle) on which the point * lives by applying inverse slope formula then adds the rotation angle * to find the final angle. then point is retrived using circle equation * of center, angle and distance. */ float progress = mHighlightLength.value(frameNo) / 100.0f; if (vCompare(progress, 1.0f)) progress = 0.99f; float startAngle = VLine(start, end).angle(); float highlightAngle = mHighlightAngle.value(frameNo); static constexpr float K_PI = 3.1415926f; float angle = (startAngle + highlightAngle) * (K_PI / 180.0f); grad->radial.fx = grad->radial.cx + std::cos(angle) * progress * grad->radial.cradius; grad->radial.fy = grad->radial.cy + std::sin(angle) * progress * grad->radial.cradius; // Lottie dosen't have any focal radius concept. grad->radial.fradius = 0; } } void model::Asset::loadImageData(std::string data) { if (!data.empty()) mBitmap = VImageLoader::instance().load(data.c_str(), data.length()); } void model::Asset::loadImagePath(std::string path) { if (!path.empty()) mBitmap = VImageLoader::instance().load(path.c_str()); } std::vector<LayerInfo> model::Composition::layerInfoList() const { if (!mRootLayer || mRootLayer->mChildren.empty()) return {}; std::vector<LayerInfo> result; result.reserve(mRootLayer->mChildren.size()); for (auto it : mRootLayer->mChildren) { auto layer = static_cast<model::Layer *>(it); result.emplace_back(layer->name(), layer->mInFrame, layer->mOutFrame); } return result; }