lottie/loader: support the masking features.

Enhancing the basic masking options by providing additional support.
This commit is contained in:
Hermet Park 2023-08-17 17:33:48 +09:00 committed by Hermet Park
parent 3b2c040f70
commit 994c1b99a5
38 changed files with 278 additions and 95 deletions

View file

@ -149,11 +149,11 @@ int main(int argc, char **argv)
if (threads > 0) --threads; //Allow the designated main thread capacity
//Initialize ThorVG Engine
if (tvg::Initializer::init(tvg::CanvasEngine::Sw, threads) == tvg::Result::Success) {
if (tvg::Initializer::init(tvg::CanvasEngine::Sw, 3) == tvg::Result::Success) {
elm_init(argc, argv);
view = createSwView(1440, 1440);
view = createSwView(1280, 1280);
ecore_animator_add(animatorCb, view);
elm_run();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -31,8 +31,8 @@
/* Internal Class Implementation */
/************************************************************************/
static void _updateChildren(LottieGroup* parent, int32_t frameNo, Shape* baseShape, bool reset);
static void _updateLayer(LottieLayer* root, LottieLayer* layer, int32_t frameNo, bool reset);
static void _updateChildren(LottieGroup* parent, int32_t frameNo, Shape* baseShape);
static void _updateLayer(LottieLayer* root, LottieLayer* layer, int32_t frameNo);
static bool _buildPrecomp(LottieComposition* comp, LottieGroup* parent);
static bool _invisible(LottieGroup* group, int32_t frameNo)
@ -68,17 +68,16 @@ static bool _updateTransform(LottieTransform* transform, int32_t frameNo, bool a
mathTranslate(&matrix, position.x, position.y);
}
auto scale = transform->scale(frameNo);
mathScale(&matrix, scale.x * 0.01f, scale.y * 0.01f);
auto angle = 0.0f;
if (autoOrient) angle = transform->position.angle(frameNo);
mathRotate(&matrix, transform->rotation(frameNo) + angle);
auto scale = transform->scale(frameNo);
mathScaleR(&matrix, scale.x * 0.01f, scale.y * 0.01f);
//Lottie specific anchor transform.
auto anchor = transform->anchor(frameNo);
matrix.e13 -= (anchor.x * matrix.e11 + anchor.y * matrix.e12);
matrix.e23 -= (anchor.x * matrix.e21 + anchor.y * matrix.e22);
mathTranslateR(&matrix, -anchor.x, -anchor.y);
opacity = transform->opacity(frameNo);
@ -97,7 +96,8 @@ static void _updateTransform(LottieLayer* layer, int32_t frameNo)
auto& matrix = layer->cache.matrix;
uint8_t opacity;
_updateTransform(transform, layer->remap(frameNo), layer->autoOrient, matrix, opacity);
_updateTransform(transform, frameNo, layer->autoOrient, matrix, opacity);
if (parent) {
layer->cache.matrix = mathMultiply(&parent->cache.matrix, &matrix);
@ -124,17 +124,12 @@ static Shape* _updateTransform(Paint* paint, LottieTransform* transform, int32_t
}
static Shape* _updateGroup(LottieGroup* parent, LottieGroup* group, int32_t frameNo, Shape* baseShape, bool reset)
static Shape* _updateGroup(LottieGroup* parent, LottieGroup* group, int32_t frameNo, Shape* baseShape)
{
//Prepare render data
if (reset || !group->scene) {
auto scene = Scene::gen();
group->scene = scene.get();
static_cast<Scene*>(parent->scene)->push(std::move(scene));
} else {
static_cast<Scene*>(group->scene)->clear();
reset = true;
}
auto scene = Scene::gen();
group->scene = scene.get();
parent->scene->push(std::move(scene));
if (_invisible(group, frameNo)) return nullptr;
@ -145,7 +140,7 @@ static Shape* _updateGroup(LottieGroup* parent, LottieGroup* group, int32_t fram
group->scene->transform(matrix);
group->scene->opacity(opacity);
}
_updateChildren(group, frameNo, baseShape, reset);
_updateChildren(group, frameNo, baseShape);
return nullptr;
}
@ -214,7 +209,7 @@ static Shape* _updateRect(LottieGroup* parent, LottieRect* rect, int32_t frameNo
if (!mergingShape) {
auto newShape = cast<Shape>(baseShape->duplicate());
mergingShape = newShape.get();
static_cast<Scene*>(parent->scene)->push(std::move(newShape));
parent->scene->push(std::move(newShape));
}
mergingShape->appendRect(position.x - size.x * 0.5f, position.y - size.y * 0.5f, size.x, size.y, roundness, roundness);
return mergingShape;
@ -228,7 +223,7 @@ static Shape* _updateEllipse(LottieGroup* parent, LottieEllipse* ellipse, int32_
if (!mergingShape) {
auto newShape = cast<Shape>(baseShape->duplicate());
mergingShape = newShape.get();
static_cast<Scene*>(parent->scene)->push(std::move(newShape));
parent->scene->push(std::move(newShape));
}
mergingShape->appendCircle(position.x, position.y, size.x * 0.5f, size.y * 0.5f);
return mergingShape;
@ -240,7 +235,7 @@ static Shape* _updatePath(LottieGroup* parent, LottiePath* path, int32_t frameNo
if (!mergingShape) {
auto newShape = cast<Shape>(baseShape->duplicate());
mergingShape = newShape.get();
static_cast<Scene*>(parent->scene)->push(std::move(newShape));
parent->scene->push(std::move(newShape));
}
if (path->pathset(frameNo, P(mergingShape)->rs.path.cmds, P(mergingShape)->rs.path.pts)) {
P(mergingShape)->update(RenderUpdateFlag::Path);
@ -276,7 +271,7 @@ static void _updateImage(LottieGroup* parent, LottieImage* image, int32_t frameN
}
static void _updateChildren(LottieGroup* parent, int32_t frameNo, Shape* baseShape, bool reset)
static void _updateChildren(LottieGroup* parent, int32_t frameNo, Shape* baseShape)
{
if (parent->children.empty()) return;
@ -290,7 +285,7 @@ static void _updateChildren(LottieGroup* parent, int32_t frameNo, Shape* baseSha
for (auto child = parent->children.end() - 1; child >= parent->children.data; --child) {
switch ((*child)->type) {
case LottieObject::Group: {
mergingShape = _updateGroup(parent, static_cast<LottieGroup*>(*child), frameNo, baseShape, reset);
mergingShape = _updateGroup(parent, static_cast<LottieGroup*>(*child), frameNo, baseShape);
break;
}
case LottieObject::Transform: {
@ -341,11 +336,14 @@ static void _updateChildren(LottieGroup* parent, int32_t frameNo, Shape* baseSha
delete(baseShape);
}
static void _updatePrecomp(LottieLayer* precomp, int32_t frameNo, bool reset)
static void _updatePrecomp(LottieLayer* precomp, int32_t frameNo)
{
if (precomp->children.count == 0) return;
//TODO: skip if the layer is static.
for (auto child = precomp->children.end() - 1; child >= precomp->children.data; --child) {
_updateLayer(precomp, static_cast<LottieLayer*>(*child), frameNo, reset);
_updateLayer(precomp, static_cast<LottieLayer*>(*child), frameNo);
}
}
@ -355,26 +353,48 @@ static void _updateSolid(LottieLayer* layer, int32_t frameNo)
auto shape = Shape::gen();
shape->appendRect(0, 0, layer->w, layer->h);
shape->fill(layer->color.rgb[0], layer->color.rgb[1], layer->color.rgb[2], layer->opacity(frameNo));
static_cast<Scene*>(layer->scene)->push(std::move(shape));
layer->scene->push(std::move(shape));
}
static void _updateLayer(LottieLayer* root, LottieLayer* layer, int32_t frameNo, bool reset)
static void _updateMaskings(LottieLayer* layer, int32_t frameNo)
{
//Prepare render data
if (reset || !layer->scene) {
auto scene = Scene::gen();
layer->scene = scene.get();
static_cast<Scene*>(root->scene)->push(std::move(scene));
}
//else if (layer->statical) return; //FIXME: rendering is skipped due to no update?
else {
static_cast<Scene*>(layer->scene)->clear();
reset = true;
if (layer->masks.count == 0) return;
//maskings+clipping
Shape* mergingMask = nullptr;
for (auto m = layer->masks.data; m < layer->masks.end(); ++m) {
auto mask = static_cast<LottieMask*>(*m);
auto shape = Shape::gen().release();
shape->fill(255, 255, 255, mask->opacity(frameNo));
shape->transform(layer->cache.matrix);
if (mask->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts)) {
P(shape)->update(RenderUpdateFlag::Path);
}
if (mergingMask) {
mergingMask->composite(cast<Shape>(shape), mask->method);
}
else {
auto method = mask->method;
if (method == CompositeMethod::AddMask) method = CompositeMethod::AlphaMask;
if (method == CompositeMethod::SubtractMask) method = CompositeMethod::InvAlphaMask;
layer->scene->composite(cast<Shape>(shape), method);
}
mergingMask = shape;
}
}
static void _updateLayer(LottieLayer* root, LottieLayer* layer, int32_t frameNo)
{
layer->scene = nullptr;
if (_invisible(layer, frameNo)) return;
//Prepare render data
layer->scene = Scene::gen().release();
_updateTransform(layer, frameNo);
layer->scene->transform(layer->cache.matrix);
@ -384,22 +404,49 @@ static void _updateLayer(LottieLayer* root, LottieLayer* layer, int32_t frameNo,
layer->scene->opacity(layer->cache.opacity);
}
frameNo = layer->remap(frameNo);
auto rFrameNo = layer->remap(frameNo);
switch (layer->type) {
case LottieLayer::Precomp: {
_updatePrecomp(layer, frameNo, reset);
_updatePrecomp(layer, rFrameNo);
break;
}
case LottieLayer::Solid: {
_updateSolid(layer, frameNo);
_updateSolid(layer, rFrameNo);
break;
}
default: {
_updateChildren(layer, frameNo, nullptr, reset);
_updateChildren(layer, rFrameNo, nullptr);
break;
}
}
if (layer->matte.target && layer->masks.count > 0) {
TVGERR("LOTTIE", "FIXME: Matte + Masking??");
}
//matte masking layer
if (layer->matte.target) {
_updateLayer(root, layer->matte.target, frameNo);
layer->scene->composite(cast<Scene>(layer->matte.target->scene), layer->matte.type);
}
_updateMaskings(layer, rFrameNo);
//clip the layer viewport
if (layer->clipself) {
//TODO: remove the intermediate scene....
auto cscene = Scene::gen();
auto clipper = Shape::gen();
clipper->appendRect(0, 0, layer->w, layer->h);
clipper->transform(layer->cache.matrix);
cscene->composite(move(clipper), CompositeMethod::ClipPath);
cscene->push(cast<Scene>(layer->scene));
layer->scene = cscene.release();
}
//the given matte source was composited by the target earlier.
if (!layer->matteSrc) root->scene->push(cast<Scene>(layer->scene));
}
@ -421,27 +468,53 @@ static void _buildReference(LottieComposition* comp, LottieLayer* layer)
}
static void _bulidHierarchy(LottieGroup* parent, LottieLayer* child)
{
if (child->pid == -1) return;
for (auto p = parent->children.data; p < parent->children.end(); ++p) {
auto parent = static_cast<LottieLayer*>(*p);
if (child == parent) continue;
if (child->pid == parent->id) {
child->parent = parent;
parent->statical &= child->statical;
break;
}
}
}
static void _buildSize(LottieComposition* comp, LottieLayer* layer)
{
// default size is 0x0
if (layer->w == 0 || layer->h == 0) return;
//compact layer size
if (layer->w > comp->w) layer->w = comp->w;
if (layer->h > comp->h) layer->h = comp->h;
if (layer->w < comp->w || layer->h < comp->h) {
layer->clipself = true;
}
}
static bool _buildPrecomp(LottieComposition* comp, LottieGroup* parent)
{
if (parent->children.count == 0) return false;
for (auto c = parent->children.data; c < parent->children.end(); ++c) {
auto child = static_cast<LottieLayer*>(*c);
//compact layer size
_buildSize(comp, child);
//attach the referencing layer.
if (child->refId) _buildReference(comp, child);
if (child->pid == -1) continue;
//parenting
for (auto p = parent->children.data; p < parent->children.end(); ++p) {
if (c == p) continue;
auto parent = static_cast<LottieLayer*>(*p);
if (child->pid == parent->id) {
child->parent = parent;
parent->statical &= child->statical;
break;
}
}
if (child->matte.target) _bulidHierarchy(parent, child->matte.target);
if (child->pid != -1) _bulidHierarchy(parent, child);
}
return true;
}
@ -466,12 +539,14 @@ bool LottieBuilder::update(LottieComposition* comp, int32_t frameNo)
auto scene = Scene::gen();
root->scene = scene.get();
comp->scene->push(std::move(scene));
} else {
root->scene->clear();
}
//update children layers
//TODO: skip if the layer is static.
for (auto child = root->children.end() - 1; child >= root->children.data; --child) {
_updateLayer(root, static_cast<LottieLayer*>(*child), frameNo, false);
_updateLayer(root, static_cast<LottieLayer*>(*child), frameNo);
}
return true;
}

View file

@ -74,6 +74,18 @@ void LottieGroup::prepare(LottieObject::Type type)
}
void LottieLayer::prepare()
{
LottieGroup::prepare(LottieObject::Layer);
/* if layer is hidden, only useulf data is its transform matrix.
so force it to be a Null Layer and release all resource. */
if (!hidden) return;
type = LottieLayer::Null;
children.reset();
}
int32_t LottieLayer::remap(int32_t frameNo)
{
if (timeRemap.frames) {

View file

@ -99,6 +99,21 @@ struct LottieGradient
};
struct LottieMask
{
LottiePathSet pathset = PathSet{nullptr, nullptr, 0, 0};
LottieOpacity opacity = 255;
CompositeMethod method;
bool inverse = false;
bool dynamic()
{
if (opacity.frames || pathset.frames) return true;
return false;
}
};
struct LottieObject
{
enum Type : uint8_t
@ -359,19 +374,6 @@ struct LottieLayer : LottieGroup
}
}
void prepare()
{
LottieGroup::prepare(LottieObject::Layer);
/* if layer is hidden, only useulf data is its transform matrix.
so force it to be a Null Layer and release all resource. */
if (hidden) {
type = LottieLayer::Null;
children.reset();
return;
}
}
uint8_t opacity(int32_t frameNo) override
{
//return zero if the visibility is false.
@ -380,16 +382,22 @@ struct LottieLayer : LottieGroup
return LottieGroup::opacity(frameNo);
}
void prepare();
int32_t remap(int32_t frameNo);
//Optimize: compact data??
RGB24 color = {255, 255, 255};
CompositeMethod matteType = CompositeMethod::None;
struct {
CompositeMethod type = CompositeMethod::None;
LottieLayer* target = nullptr;
} matte;
BlendMethod blendMethod = BlendMethod::Normal;
LottieLayer* parent = nullptr;
LottieFloat timeRemap = 0.0f;
LottieComposition* comp = nullptr;
Array<LottieMask*> masks;
float timeStretch = 1.0f;
uint32_t w, h;
@ -409,8 +417,9 @@ struct LottieLayer : LottieGroup
Type type = Null;
bool autoOrient = false;
bool mask = false;
bool roundedCorner = false;
bool clipself = false; //clip the layer viewport
bool matteSrc = false;
};

View file

@ -58,6 +58,21 @@ static void _updateRoundedCorner(LottieGroup* parent, LottieRoundedCorner* round
}
CompositeMethod LottieParser::getMaskMethod(bool inversed)
{
switch (getString()[0]) {
case 'a': {
if (inversed) return CompositeMethod::InvAlphaMask;
else return CompositeMethod::AddMask;
}
case 's': return CompositeMethod::SubtractMask;
case 'i': return CompositeMethod::IntersectMask;
case 'f': return CompositeMethod::DifferenceMask;
default: return CompositeMethod::None;
}
}
BlendMethod LottieParser::getBlendMethod()
{
switch (getInt()) {
@ -493,7 +508,7 @@ LottieTransform* LottieParser::parseTransform(bool ddd)
auto transform = new LottieTransform;
if (!transform) return nullptr;
if (ddd) TVGLOG("LOTTIE", "3d transform(ddd) is not supported");
if (ddd) TVGERR("LOTTIE", "3d transform(ddd) is not supported");
while (auto key = nextObjectKey()) {
if (!strcmp(key, "p"))
@ -754,7 +769,7 @@ LottieObject* LottieParser::parseObject()
} else if (!strcmp(type, "sh")) {
return parsePath();
} else if (!strcmp(type, "sr")) {
TVGLOG("LOTTIE", "Polystar(sr) is not supported");
TVGERR("LOTTIE", "Polystar(sr) is not supported");
return parsePolyStar();
} else if (!strcmp(type, "rd")) {
return parseRoundedCorner();
@ -763,11 +778,11 @@ LottieObject* LottieParser::parseObject()
} else if (!strcmp(type, "gs")) {
return parseGradientStroke();
} else if (!strcmp(type, "tm")) {
TVGLOG("LOTTIE", "Trimpath(tm) is not supported");
TVGERR("LOTTIE", "Trimpath(tm) is not supported");
} else if (!strcmp(type, "rp")) {
TVGLOG("LOTTIE", "Repeater(rp) is not supported yet");
TVGERR("LOTTIE", "Repeater(rp) is not supported yet");
} else if (!strcmp(type, "mm")) {
TVGLOG("LOTTIE", "MergePath(mm) is not supported yet");
TVGERR("LOTTIE", "MergePath(mm) is not supported yet");
} else {
TVGERR("LOTTIE", "Unkown object type(%s) is given", type);
}
@ -904,7 +919,6 @@ LottieObject* LottieParser::parseGroup()
void LottieParser::parseTimeRemap(LottieLayer* layer)
{
layer->comp = comp;
parseProperty(layer->timeRemap);
}
@ -930,12 +944,39 @@ void LottieParser::getLayerSize(uint32_t& val)
}
}
LottieMask* LottieParser::parseMask()
{
auto mask = new LottieMask;
if (!mask) return nullptr;
enterObject();
while (auto key = nextObjectKey()) {
if (!strcmp(key, "inv")) mask->inverse = getBool();
else if (!strcmp(key, "mode")) mask->method = getMaskMethod(mask->inverse);
else if (!strcmp(key, "pt")) getPathSet(mask->pathset);
else if (!strcmp(key, "o")) parseProperty(mask->opacity);
else skip(key);
}
return mask;
}
void LottieParser::parseMasks(LottieLayer* layer)
{
enterArray();
while (nextArrayValue()) {
layer->masks.push(parseMask());
}
}
LottieLayer* LottieParser::parseLayer()
{
auto layer = new LottieLayer;
if (!layer) return nullptr;
layer->comp = comp;
context->layer = layer;
auto ddd = false;
@ -964,15 +1005,11 @@ LottieLayer* LottieParser::parseLayer()
else if (!strcmp(key, "w") || !strcmp(key, "sw")) getLayerSize(layer->w);
else if (!strcmp(key, "h") || !strcmp(key, "sh")) getLayerSize(layer->h);
else if (!strcmp(key, "sc")) layer->color = getColor(getString());
else if (!strcmp(key, "tt")) layer->matteType = getMatteType();
else if (!strcmp(key, "hasMask")) layer->mask = getBool();
else if (!strcmp(key, "masksProperties"))
{
TVGLOG("LOTTIE", "Masking(maskProperties) is not supported");
skip(key);
}
else if (!strcmp(key, "tt")) layer->matte.type = getMatteType();
else if (!strcmp(key, "masksProperties")) parseMasks(layer);
else if (!strcmp(key, "hd")) layer->hidden = getBool();
else if (!strcmp(key, "refId")) layer->refId = getStringCopy();
else if (!strcmp(key, "td")) layer->matteSrc = getInt(); //used for matte layer
else skip(key);
}
@ -995,15 +1032,22 @@ LottieLayer* LottieParser::parseLayers()
if (!root) return nullptr;
root->type = LottieLayer::Precomp;
root->comp = comp;
enterArray();
while (nextArrayValue()) {
if (auto layer = parseLayer()) {
if (layer->matte.type == CompositeMethod::None) {
root->children.push(layer);
} else {
//matte source must be located in the right previous.
layer->matte.target = static_cast<LottieLayer*>(root->children.last());
layer->statical &= layer->matte.target->statical;
root->children.last() = layer;
}
root->statical &= layer->statical;
root->children.push(layer);
}
}
root->prepare();
return root;
}

View file

@ -46,6 +46,7 @@ private:
FillRule getFillRule();
StrokeCap getStrokeCap();
StrokeJoin getStrokeJoin();
CompositeMethod getMaskMethod(bool inversed);
LottieInterpolator* getInterpolator(const char* key, Point& in, Point& out);
void getInperpolatorPoint(Point& pt);
@ -81,9 +82,11 @@ private:
LottieRoundedCorner* parseRoundedCorner();
LottieGradientFill* parseGradientFill();
LottieLayer* parseLayers();
LottieMask* parseMask();
void parseObject(LottieGroup* parent);
void parseShapes(LottieLayer* layer);
void parseMasks(LottieLayer* layer);
void parseTimeRemap(LottieLayer* layer);
void parseStrokeDash(LottieStroke* stroke);
void parseGradient(LottieGradient* gradient, const char* key);

View file

@ -221,7 +221,7 @@ const char* LookaheadParserHandler::nextObjectKey()
void LookaheadParserHandler::skip(const char* key)
{
if (key) TVGLOG("LOTTIE", "Skipped parsing value = %s", key);
//if (key) TVGLOG("LOTTIE", "Skipped parsing value = %s", key);
if (peekType() == kArrayType) {
enterArray();

View file

@ -97,6 +97,19 @@ static inline void mathScale(Matrix* m, float sx, float sy)
}
static inline void mathScaleR(Matrix* m, float x, float y)
{
if (x != 1.0f) {
m->e11 *= x;
m->e21 *= x;
}
if (y != 1.0f) {
m->e22 *= y;
m->e12 *= y;
}
}
static inline void mathTranslate(Matrix* m, float x, float y)
{
m->e13 += x;
@ -104,6 +117,20 @@ static inline void mathTranslate(Matrix* m, float x, float y)
}
static inline void mathTranslateR(Matrix* m, float x, float y)
{
if (x == 0.0f && y == 0.0f) return;
m->e13 += (x * m->e11 + y * m->e12);
m->e23 += (x * m->e21 + y * m->e22);
}
static inline void mathLog(Matrix* m)
{
TVGLOG("MATH", "Matrix: [%f %f %f] [%f %f %f] [%f %f %f]", m->e11, m->e12, m->e13, m->e21, m->e22, m->e23, m->e31, m->e32, m->e33);
}
static inline Point operator-(const Point& lhs, const Point& rhs)
{
return {lhs.x - rhs.x, lhs.y - rhs.y};