forked from jshiffer/matterbridge
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>
391 lines
13 KiB
C++
391 lines
13 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_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;
|
|
}
|