diff --git a/CHANGELOG.md b/CHANGELOG.md index 86d6123c..365214ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Added Inflate/Deflate modifier (aka Pucker/Bloat) * Added support for rounded polygons / stars * Added rounded corners modifier + * Added support for inverted masks * I/O: * Loading Lottie with `meta` no longer show a warning * Fixed UUIDs when loading lottie animations @@ -17,6 +18,7 @@ * Fixed parsing SVG paths * Added support for loading basic animated SVG paths * Fixed importing solid layers from lottie + * Fixed importing mattes affecting precomps * UI: * Added simplified Chinese translation * Added British English translation diff --git a/src/core/io/glaxnimate/glaxnimate_format.cpp b/src/core/io/glaxnimate/glaxnimate_format.cpp index abc53d70..17e6f82d 100644 --- a/src/core/io/glaxnimate/glaxnimate_format.cpp +++ b/src/core/io/glaxnimate/glaxnimate_format.cpp @@ -11,7 +11,7 @@ using namespace glaxnimate; io::Autoreg io::glaxnimate::GlaxnimateFormat::autoreg; -const int glaxnimate::io::glaxnimate::GlaxnimateFormat::format_version = 5; +const int glaxnimate::io::glaxnimate::GlaxnimateFormat::format_version = 6; diff --git a/src/core/io/glaxnimate/import_state.hpp b/src/core/io/glaxnimate/import_state.hpp index 4ff17182..70e22e94 100644 --- a/src/core/io/glaxnimate/import_state.hpp +++ b/src/core/io/glaxnimate/import_state.hpp @@ -226,6 +226,12 @@ class ImportState object["multiple"] = "Individually"; } } + + if ( document_version < 6 ) + { + if ( object["__type__"].toString() == "MaskSettings" ) + object["mask"] = int(object["mask"].toBool()); + } } void do_load_object ( model::Object* target, QJsonObject object, const UnresolvedPath& path ) diff --git a/src/core/io/lottie/lottie_importer.hpp b/src/core/io/lottie/lottie_importer.hpp index d7b3b240..9f11e499 100644 --- a/src/core/io/lottie/lottie_importer.hpp +++ b/src/core/io/lottie/lottie_importer.hpp @@ -10,6 +10,8 @@ #include "lottie_private_common.hpp" #include "io/svg/svg_parser.hpp" +#include + namespace glaxnimate::io::lottie::detail { struct FontInfo @@ -123,11 +125,6 @@ class LottieImporterState load_layer(pair.second, static_cast(pair.first)); } - bool has_mask(const QJsonObject& json) - { - return mask && json["tt"].toInt(); - } - void load_visibility(model::VisualNode* node, const QJsonObject& json) { if ( json.contains("hd") && json["hd"].toBool() ) @@ -145,39 +142,63 @@ class LottieImporterState } int ty = json["ty"].toInt(); + + std::unique_ptr inner_shape; + bool start_mask = json["td"].toInt(); + if ( ty == 0 ) { - load_precomp_layer(json, referenced); - mask = nullptr; + inner_shape = load_precomp_layer(json); + + auto op = document->main()->animation->last_frame.get(); + if ( json.contains("parent") || referenced.count(index) || json["ip"].toDouble() != 0 || + json["op"].toDouble(op) != op || start_mask + ) + { + auto layer = make_node(document); + layer->name.set(inner_shape->name.get()); + layer->shapes.insert(std::move(inner_shape), 0); + layer_indices[index] = layer.get(); + deferred.emplace_back(layer.get(), json); + inner_shape = std::move(layer); + } } else { auto layer = std::make_unique(document); - if ( json["td"].toInt() ) + layer_indices[index] = layer.get(); + deferred.emplace_back(layer.get(), json); + inner_shape = std::move(layer); + } + + if ( start_mask ) + { + auto layer = std::make_unique(document); + mask = layer.get(); + layer->name.set(json["nm"].toString()); + layer->shapes.insert(std::move(inner_shape), 0); + composition->shapes.insert(std::move(layer), 0); + } + else + { + auto tt = json["tt"].toInt(); + + if ( mask && tt ) { - mask = layer.get(); - layer->mask->mask.set(true); - layer->name.set(json["nm"].toString()); - auto child = std::make_unique(document); - layer_indices[index] = child.get(); - deferred.emplace_back(child.get(), json); - layer->shapes.insert(std::move(child), 0); - composition->shapes.insert(std::move(layer), 0); + mask->shapes.insert(std::move(inner_shape), 1); + auto mode = model::MaskSettings::MaskMode((tt + 1) / 2); + mask->mask->mask.set(mode); + mask->mask->inverted.set(tt > 0 && tt % 2 == 0); } else { - layer_indices[index] = layer.get(); - deferred.emplace_back(layer.get(), json); - if ( has_mask(json) ) - mask->shapes.insert(std::move(layer), 1); - else - composition->shapes.insert(std::move(layer), 0); - mask = nullptr; + composition->shapes.insert(std::move(inner_shape), 0); } + mask = nullptr; } } - void load_precomp_layer(const QJsonObject& json, std::set& referenced) + std::unique_ptr load_precomp_layer(const QJsonObject& json) { auto props = load_basic_setup(json); @@ -211,27 +232,9 @@ class LottieImporterState json["h"].toInt() )); - int index = json["ind"].toInt(); - auto op = document->main()->animation->last_frame.get(); - if ( json.contains("parent") || referenced.count(index) || json["ip"].toDouble() != 0 || - json["op"].toDouble(op) != op || has_mask(json) - ) - { - auto layer = make_node(document); - layer->name.set(precomp->name.get()); - layer->shapes.insert(std::move(precomp), 0); - if ( has_mask(json) ) - layer->mask->mask.set(mask); - layer_indices[index] = layer.get(); - deferred.emplace_back(layer.get(), json); - load_transform(json["ks"].toObject(), layer->transform.get(), &layer->opacity); - composition->shapes.insert(std::move(layer), 0); - } - else - { - load_transform(json["ks"].toObject(), precomp->transform.get(), &precomp->opacity); - composition->shapes.insert(std::move(precomp), 0); - } + load_transform(json["ks"].toObject(), precomp->transform.get(), &precomp->opacity); + + return precomp; } void load_mask(const QJsonObject& json, model::Group* group) @@ -318,7 +321,7 @@ class LottieImporterState auto masks = json["masksProperties"].toArray(); if ( !masks.empty() ) { - layer->mask->mask.set(true); + layer->mask->mask.set(model::MaskSettings::Alpha); auto clip_p = make_node(document); auto clip = clip_p.get(); diff --git a/src/core/io/svg/svg_parser.cpp b/src/core/io/svg/svg_parser.cpp index 93671da7..20d1b3c3 100644 --- a/src/core/io/svg/svg_parser.cpp +++ b/src/core/io/svg/svg_parser.cpp @@ -574,7 +574,7 @@ class glaxnimate::io::svg::SvgParser::Private auto layer = std::make_unique(document); apply_common_style(layer.get(), args.element, style); set_name(layer.get(), args.element); - layer->mask->mask.set(true); + layer->mask->mask.set(model::MaskSettings::Alpha); QDomElement element = args.element; diff --git a/src/core/model/mask_settings.hpp b/src/core/model/mask_settings.hpp index 14b5dd45..17347c25 100644 --- a/src/core/model/mask_settings.hpp +++ b/src/core/model/mask_settings.hpp @@ -11,8 +11,16 @@ class MaskSettings : public Object { GLAXNIMATE_OBJECT(MaskSettings) - GLAXNIMATE_PROPERTY(bool, mask, false, {}, {}, PropertyTraits::Visual) -// GLAXNIMATE_PROPERTY(bool, invert, false, {}, {}, PropertyTraits::Visual) +public: + enum MaskMode + { + NoMask = 0, + Alpha = 1, + }; + Q_ENUM(MaskMode) + + GLAXNIMATE_PROPERTY(MaskMode, mask, NoMask, {}, {}, PropertyTraits::Visual) + GLAXNIMATE_PROPERTY(bool, inverted, false, {}, {}, PropertyTraits::Visual) public: using Object::Object; diff --git a/src/core/model/shapes/layer.cpp b/src/core/model/shapes/layer.cpp index 2fb4389e..e29c91c6 100644 --- a/src/core/model/shapes/layer.cpp +++ b/src/core/model/shapes/layer.cpp @@ -3,6 +3,7 @@ #include #include "model/composition.hpp" +#include "model/document.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::Layer) @@ -124,6 +125,14 @@ void glaxnimate::model::Layer::paint(QPainter* painter, FrameTime time, PaintMod { QPainterPath clip = shapes[0]->to_clip(time); clip.setFillRule(Qt::WindingFill); + if ( mask->inverted.get() ) + { + QPainterPath outer_clip; + outer_clip.addPolygon( + transform.inverted().map(QRectF(QPointF(0, 0), document()->size())) + ); + clip = outer_clip.subtracted(clip); + } painter->setClipPath(clip, Qt::IntersectClip); } diff --git a/src/python/miscdefs.cpp b/src/python/miscdefs.cpp index 809bfdd8..7279a574 100644 --- a/src/python/miscdefs.cpp +++ b/src/python/miscdefs.cpp @@ -103,7 +103,7 @@ static void define_trace(py::module& m) py::class_(trace, "TraceOptions") .def(py::init<>()) .def_property("smoothness", &TraceOptions::smoothness, &TraceOptions::set_smoothness) - .def_property("min_area", &TraceOptions::min_area, &TraceOptions::min_area) + .def_property("min_area", &TraceOptions::min_area, &TraceOptions::set_min_area) ; py::class_(trace, "Tracer") .def(py::init())