File indexing completed on 2025-01-05 04:01:15
0001 /* 0002 * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best> 0003 * 0004 * SPDX-License-Identifier: GPL-3.0-or-later 0005 */ 0006 0007 #pragma once 0008 0009 0010 #include <QCborValue> 0011 #include <QCborArray> 0012 0013 #include "cbor_write_json.hpp" 0014 #include "lottie_private_common.hpp" 0015 #include "model/animation/join_animatables.hpp" 0016 #include "app_info.hpp" 0017 #include "io/utils.hpp" 0018 0019 namespace glaxnimate::io::lottie::detail { 0020 0021 inline QLatin1String operator "" _l(const char* c, std::size_t sz) 0022 { 0023 return QLatin1String(c, sz); 0024 } 0025 0026 // inline QString debug_value(QCborValue v) 0027 // { 0028 // QCborMap map; 0029 // map["v"_l] = v; 0030 // return QString(cbor_write_json(map, false)); 0031 // } 0032 0033 class LottieExporterState 0034 { 0035 static constexpr const char* version = "5.7.1"; 0036 0037 public: 0038 explicit LottieExporterState(ImportExport* format, model::Composition* comp, bool strip, bool strip_raster, const QVariantMap& settings ) 0039 : format(format), 0040 main(comp), 0041 document(comp->document()), 0042 strip(strip), 0043 strip_raster( strip_raster ), 0044 auto_embed(settings["auto_embed"].toBool()), 0045 old_kf(settings["old_kf"].toBool()) 0046 {} 0047 0048 QCborMap to_json() 0049 { 0050 return convert_main(main); 0051 } 0052 0053 void convert_animation_container(model::AnimationContainer* animation, QCborMap& json) 0054 { 0055 json["ip"_l] = animation->first_frame.get(); 0056 json["op"_l] = animation->last_frame.get(); 0057 } 0058 0059 void convert_composition(model::Composition* composition, QCborMap& json) 0060 { 0061 QCborArray layers; 0062 for ( const auto& layer : composition->shapes ) 0063 if ( !strip || layer->visible.get() ) 0064 convert_layer(layer_type(layer.get()), layer.get(), layers); 0065 0066 json["layers"_l] = layers; 0067 } 0068 0069 QCborMap convert_main(model::Composition* animation) 0070 { 0071 layer_indices.clear(); 0072 QCborMap json; 0073 json["v"_l] = version; 0074 convert_animation_container(animation->animation.get(), json); 0075 convert_object_basic(animation, json); 0076 json["assets"_l] = convert_assets(animation); 0077 convert_composition(animation, json); 0078 if ( !strip ) 0079 convert_meta(json); 0080 return json; 0081 } 0082 0083 void convert_meta(QCborMap& json) 0084 { 0085 QCborMap meta; 0086 meta["g"_l] = QString("%1 %2").arg(AppInfo::instance().name(), AppInfo::instance().version()); 0087 0088 if ( !document->info().description.isEmpty() ) 0089 meta["d"_l] = document->info().description; 0090 0091 if ( !document->info().author.isEmpty() ) 0092 meta["a"_l] = document->info().author; 0093 0094 if ( !document->info().keywords.isEmpty() ) 0095 { 0096 QCborArray k; 0097 for ( const auto& kw : document->info().keywords ) 0098 k.push_back(kw); 0099 meta["k"_l] = k; 0100 } 0101 0102 json["meta"_l] = meta; 0103 } 0104 0105 int layer_index(model::DocumentNode* layer) 0106 { 0107 if ( !layer ) 0108 return -1; 0109 if ( !layer_indices.contains(layer->uuid.get()) ) 0110 layer_indices[layer->uuid.get()] = layer_indices.size(); 0111 return layer_indices[layer->uuid.get()]; 0112 } 0113 0114 QCborMap wrap_layer_shape(model::ShapeElement* shape, model::Layer* forced_parent) 0115 { 0116 QCborMap json; 0117 json["ddd"_l] = 0; 0118 json["ty"_l] = 4; 0119 convert_fake_layer_parent(forced_parent, json); 0120 json["ind"_l] = layer_index(shape); 0121 json["st"_l] = 0; 0122 if ( !shape->visible.get() ) 0123 json["hd"_l] = true; 0124 0125 if ( auto grp = shape->cast<model::Group>() ) 0126 { 0127 QCborMap transform; 0128 convert_transform(grp->transform.get(), &grp->opacity, transform); 0129 json["ks"_l] = transform; 0130 json["ao"_l] = int(grp->auto_orient.get()); 0131 0132 json["shapes"_l] = convert_shapes(grp->shapes, false); 0133 } 0134 else 0135 { 0136 QCborMap transform; 0137 model::Transform tf(document); 0138 convert_transform(&tf, nullptr, transform); 0139 json["ks"_l] = transform; 0140 0141 QCborArray shapes; 0142 shapes.push_back(convert_shape(shape, false)); 0143 json["shapes"_l] = shapes; 0144 } 0145 0146 return json; 0147 } 0148 0149 enum class LayerType { Shape, Layer, Image, PreComp }; 0150 0151 LayerType layer_type(model::ShapeElement* shape) 0152 { 0153 auto meta = shape->metaObject(); 0154 if ( meta->inherits(&model::Layer::staticMetaObject) ) 0155 return LayerType::Layer; 0156 if ( meta->inherits(&model::Image::staticMetaObject) ) 0157 return LayerType::Image; 0158 if ( meta->inherits(&model::PreCompLayer::staticMetaObject) ) 0159 return LayerType::PreComp; 0160 return LayerType::Shape; 0161 } 0162 0163 QCborMap convert_single_layer(LayerType type, model::ShapeElement* shape, QCborArray& output, model::Layer* forced_parent, bool force_all_shapes) 0164 { 0165 switch ( type ) 0166 { 0167 case LayerType::Shape: 0168 return wrap_layer_shape(shape, forced_parent); 0169 case LayerType::Image: 0170 return convert_image_layer(static_cast<model::Image*>(shape), forced_parent); 0171 case LayerType::PreComp: 0172 return convert_precomp_layer(static_cast<model::PreCompLayer*>(shape), forced_parent); 0173 case LayerType::Layer: 0174 break; 0175 } 0176 0177 auto layer = static_cast<model::Layer*>(shape); 0178 0179 int parent_index = layer_index(forced_parent ? forced_parent : layer->parent.get()); 0180 0181 QCborMap json; 0182 json["ddd"_l] = 0; 0183 json["ty"_l] = 3; 0184 int index = layer_index(layer); 0185 json["ind"_l] = index; 0186 json["st"_l] = 0; 0187 if ( !shape->visible.get() ) 0188 json["hd"_l] = true; 0189 0190 convert_animation_container(layer->animation.get(), json); 0191 convert_object_properties(layer, fields["DocumentNode"], json); 0192 convert_object_properties(layer, fields["__Layer__"], json); 0193 0194 QCborMap transform; 0195 convert_transform(layer->transform.get(), &layer->opacity, transform); 0196 json["ks"_l] = transform; 0197 if ( parent_index != -1 ) 0198 json["parent"_l] = parent_index; 0199 0200 if ( !layer->shapes.empty() ) 0201 { 0202 std::vector<LayerType> children_types; 0203 children_types.reserve(layer->shapes.size()); 0204 0205 bool all_shapes = true; 0206 if ( !force_all_shapes ) 0207 { 0208 for ( const auto& shape : layer->shapes ) 0209 { 0210 children_types.push_back(layer_type(shape.get())); 0211 if ( children_types.back() != LayerType::Shape ) 0212 all_shapes = false; 0213 } 0214 } 0215 0216 if ( all_shapes && !layer->mask->has_mask() ) 0217 { 0218 json["ty"_l] = 4; 0219 json["shapes"_l] = convert_shapes(layer->shapes, false); 0220 } 0221 else 0222 { 0223 int i = 0; 0224 QCborMap mask; 0225 if ( layer->mask->has_mask() && !layer->shapes.empty() ) 0226 { 0227 if ( layer->shapes[0]->visible.get() ) 0228 { 0229 mask = convert_single_layer(children_types[0], layer->shapes[0], output, layer, true); 0230 if ( !mask.isEmpty() ) 0231 mask["td"_l] = 1; 0232 } 0233 i = 1; 0234 } 0235 0236 for ( ; i < layer->shapes.size(); i++ ) 0237 { 0238 if ( !strip || layer->shapes[i]->visible.get() ) 0239 convert_layer(children_types[i], layer->shapes[i], output, layer, mask); 0240 } 0241 } 0242 } 0243 0244 return json; 0245 } 0246 0247 QCborMap convert_layer(LayerType type, model::ShapeElement* shape, QCborArray& output, 0248 model::Layer* forced_parent = nullptr, const QCborMap& mask = {}) 0249 { 0250 if ( !shape->visible.get() ) 0251 return {}; 0252 0253 model::Layer* layer = nullptr; 0254 if ( type == LayerType::Layer ) 0255 { 0256 layer = static_cast<model::Layer*>(shape); 0257 0258 if ( !layer->render.get() ) 0259 return {}; 0260 } 0261 0262 auto json = convert_single_layer(type, shape, output, forced_parent, false); 0263 0264 if ( !mask.isEmpty() ) 0265 { 0266 json["tt"_l] = 1; 0267 output.push_front(json); 0268 output.push_front(mask); 0269 } 0270 else 0271 { 0272 output.push_front(json); 0273 } 0274 0275 return json; 0276 } 0277 0278 void convert_transform(model::Transform* tf, model::AnimatableBase* opacity, QCborMap& json) 0279 { 0280 convert_object_basic(tf, json); 0281 if ( opacity ) 0282 json["o"_l] = convert_animated(opacity, FloatMult(100)); 0283 else 0284 json["o"_l] = fake_animated(100); 0285 } 0286 0287 QCborArray point_to_lottie(const QPointF& vv) 0288 { 0289 return QCborArray{vv.x(), vv.y()}; 0290 } 0291 0292 QCborValue value_from_variant(const QVariant& v) 0293 { 0294 switch ( v.userType() ) 0295 { 0296 case QMetaType::QPointF: 0297 return point_to_lottie(v.toPointF()); 0298 case QMetaType::QVector2D: 0299 { 0300 auto vv = v.value<QVector2D>() * 100; 0301 return QCborArray{vv.x(), vv.y()}; 0302 } 0303 case QMetaType::QSizeF: 0304 { 0305 auto vv = v.toSizeF(); 0306 return QCborArray{vv.width(), vv.height()}; 0307 } 0308 case QMetaType::QColor: 0309 { 0310 auto vv = v.value<QColor>().toRgb(); 0311 return QCborArray{vv.redF(), vv.greenF(), vv.blueF()}; 0312 } 0313 case QMetaType::QUuid: 0314 return v.toString(); 0315 } 0316 0317 if ( v.userType() == qMetaTypeId<math::bezier::Bezier>() ) 0318 { 0319 math::bezier::Bezier bezier = v.value<math::bezier::Bezier>(); 0320 QCborMap jsbez; 0321 jsbez["c"_l] = bezier.closed(); 0322 QCborArray pos, tan_in, tan_out; 0323 for ( const auto& p : bezier ) 0324 { 0325 pos.push_back(point_to_lottie(p.pos)); 0326 tan_in.push_back(point_to_lottie(p.tan_in - p.pos)); 0327 tan_out.push_back(point_to_lottie(p.tan_out - p.pos)); 0328 } 0329 jsbez["v"_l] = pos; 0330 jsbez["i"_l] = tan_in; 0331 jsbez["o"_l] = tan_out; 0332 return jsbez; 0333 } 0334 else if ( v.userType() == qMetaTypeId<math::bezier::Point>() ) 0335 { 0336 return point_to_lottie(v.value<math::bezier::Point>().pos); 0337 } 0338 else if ( v.userType() == qMetaTypeId<QGradientStops>() ) 0339 { 0340 QCborArray weird_ass_representation; 0341 auto gradient = v.value<QGradientStops>(); 0342 bool alpha = false; 0343 for ( const auto& stop : gradient ) 0344 { 0345 weird_ass_representation.push_back(stop.first); 0346 weird_ass_representation.push_back(stop.second.redF()); 0347 weird_ass_representation.push_back(stop.second.greenF()); 0348 weird_ass_representation.push_back(stop.second.blueF()); 0349 alpha = alpha || stop.second.alpha() != 0; 0350 } 0351 if ( alpha ) 0352 { 0353 for ( const auto& stop : gradient ) 0354 { 0355 weird_ass_representation.push_back(stop.first); 0356 weird_ass_representation.push_back(stop.second.alphaF()); 0357 } 0358 } 0359 return weird_ass_representation; 0360 } 0361 else if ( v.userType() >= QMetaType::User && v.canConvert<int>() ) 0362 { 0363 return v.toInt(); 0364 } 0365 return QCborValue::fromVariant(v); 0366 } 0367 0368 void convert_object_from_meta(model::Object* obj, const QMetaObject* mo, QCborMap& json_obj) 0369 { 0370 if ( auto super = mo->superClass() ) 0371 convert_object_from_meta(obj, super, json_obj); 0372 0373 auto it = fields.find(model::detail::naked_type_name(mo)); 0374 if ( it != fields.end() ) 0375 convert_object_properties(obj, *it, json_obj); 0376 } 0377 0378 void convert_object_basic(model::Object* obj, QCborMap& json_obj) 0379 { 0380 convert_object_from_meta(obj, obj->metaObject(), json_obj); 0381 } 0382 0383 void convert_object_properties(model::Object* obj, const QVector<FieldInfo>& fields, QCborMap& json_obj) 0384 { 0385 for ( const auto& field : fields ) 0386 { 0387 if ( field.mode != Auto || (strip && !field.essential) ) 0388 continue; 0389 0390 model::BaseProperty * prop = obj->get_property(field.name); 0391 if ( !prop ) 0392 { 0393 logger.stream() << field.name << "is not a property"; 0394 continue; 0395 } 0396 0397 if ( prop->traits().flags & model::PropertyTraits::Animated ) 0398 { 0399 json_obj[field.lottie] = convert_animated(static_cast<model::AnimatableBase*>(prop), field.transform); 0400 } 0401 else 0402 { 0403 json_obj[field.lottie] = value_from_variant(field.transform.to_lottie(prop->value(), 0)); 0404 } 0405 } 0406 } 0407 0408 QCborValue keyframe_value_from_variant(const QVariant& v) 0409 { 0410 auto cb = value_from_variant(v); 0411 if ( cb.isArray() ) 0412 return cb; 0413 0414 return QCborArray{cb}; 0415 } 0416 0417 QCborMap convert_animated( 0418 model::AnimatableBase* prop, 0419 const TransformFunc& transform_values 0420 ) 0421 { 0422 bool position = prop->traits().type == model::PropertyTraits::Point; 0423 0424 QCborMap jobj; 0425 if ( prop->keyframe_count() > 1 ) 0426 { 0427 jobj["a"_l] = 1; 0428 std::vector<std::unique_ptr<model::KeyframeBase>> split_kfs = split_keyframes(prop); 0429 0430 QCborArray keyframes; 0431 QCborMap jkf; 0432 for ( int i = 0, e = split_kfs.size(); i < e; i++ ) 0433 { 0434 auto kf = split_kfs[i].get(); 0435 QVariant v = transform_values.to_lottie(kf->value(), kf->time()); 0436 QCborValue kf_value = keyframe_value_from_variant(v); 0437 0438 if ( i != 0 ) 0439 { 0440 if ( old_kf ) 0441 jkf["e"_l] = kf_value; 0442 0443 if ( position ) 0444 { 0445 auto pkf = static_cast<model::Keyframe<QPointF>*>(kf); 0446 jkf["ti"_l] = point_to_lottie(pkf->point().tan_in - pkf->get()); 0447 } 0448 0449 keyframes.push_back(jkf); 0450 } 0451 0452 jkf.clear(); 0453 jkf["t"_l] = kf->time(); 0454 jkf["s"_l] = kf_value; 0455 0456 if ( i != e - 1 ) 0457 { 0458 if ( kf->transition().hold() ) 0459 { 0460 jkf["h"_l] = 1; 0461 } 0462 else 0463 { 0464 jkf["h"_l] = 0; 0465 jkf["o"_l] = keyframe_bezier_handle(kf->transition().before()); 0466 jkf["i"_l] = keyframe_bezier_handle(kf->transition().after()); 0467 } 0468 } 0469 0470 if ( position ) 0471 { 0472 auto pkf = static_cast<model::Keyframe<QPointF>*>(kf); 0473 jkf["to"_l] = point_to_lottie(pkf->point().tan_out - pkf->get()); 0474 } 0475 } 0476 if ( position ) 0477 jkf.remove("to"_l); 0478 keyframes.push_back(jkf); 0479 jobj["k"_l] = keyframes; 0480 } 0481 else 0482 { 0483 jobj["a"_l] = 0; 0484 QVariant v = transform_values.to_lottie(prop->value(), 0); 0485 jobj["k"_l] = value_from_variant(v); 0486 } 0487 return jobj; 0488 } 0489 0490 QCborMap keyframe_bezier_handle(const QPointF& p) 0491 { 0492 QCborMap jobj; 0493 QCborArray x; 0494 x.push_back(p.x()); 0495 QCborArray y; 0496 y.push_back(p.y()); 0497 jobj["x"_l] = x; 0498 jobj["y"_l] = y; 0499 return jobj; 0500 } 0501 0502 void convert_styler(model::Styler* shape, QCborMap& jsh) 0503 { 0504 auto used = shape->use.get(); 0505 0506 auto gradient = qobject_cast<model::Gradient*>(used); 0507 if ( !gradient || !gradient->colors.get() ) 0508 { 0509 auto color_prop = &shape->color; 0510 if ( auto color = qobject_cast<model::NamedColor*>(used) ) 0511 color_prop = &color->color; 0512 jsh["c"_l] = convert_animated(color_prop, {}); 0513 0514 auto join_func = [](const std::vector<QVariant>& args) -> QVariant { 0515 return args[0].value<QColor>().alphaF() * args[1].toFloat() * 100; 0516 }; 0517 model::JoinedAnimatable join({color_prop, &shape->opacity}, join_func); 0518 jsh["o"_l] = convert_animated(&join, {}); 0519 return; 0520 } 0521 0522 convert_object_basic(gradient, jsh); 0523 0524 if ( shape->type_name() == "Fill" ) 0525 jsh["ty"_l] = "gf"; 0526 else 0527 jsh["ty"_l] = "gs"; 0528 0529 /// \todo highlight 0530 jsh["h"_l] = fake_animated(0); 0531 jsh["a"_l] = fake_animated(0); 0532 0533 auto colors = gradient->colors.get(); 0534 QCborMap jcolors; 0535 jcolors["p"_l] = colors->colors.get().size(); 0536 jcolors["k"_l] = convert_animated(&colors->colors, {}); 0537 jsh["g"_l] = jcolors; 0538 } 0539 0540 QCborMap convert_shape(model::ShapeElement* shape, bool force_hidden) 0541 { 0542 if ( auto text = shape->cast<model::TextShape>() ) 0543 { 0544 auto conv = text->to_path(); 0545 return convert_shape(conv.get(), force_hidden || !shape->visible.get()); 0546 } 0547 0548 QCborMap jsh; 0549 jsh["ty"_l] = shape_types[shape->type_name()]; 0550 // jsh["d"] = 0; 0551 if ( force_hidden || !shape->visible.get() ) 0552 jsh["hd"_l] = true; 0553 0554 convert_object_basic(shape, jsh); 0555 0556 if ( auto gr = qobject_cast<model::Group*>(shape) ) 0557 { 0558 if ( qobject_cast<model::Layer*>(gr) ) 0559 format->information(i18n("Lottie only supports layers in the top level")); 0560 else if ( gr->auto_orient.get() ) 0561 format->information(i18n("Lottie only supports auto-orient layers in the top level")); 0562 auto shapes = convert_shapes(gr->shapes, force_hidden || !gr->visible.get()); 0563 QCborMap transform; 0564 transform["ty"_l] = "tr"; 0565 convert_transform(gr->transform.get(), &gr->opacity, transform); 0566 shapes.push_back(transform); 0567 jsh["it"_l] = shapes; 0568 } 0569 else if ( auto styler = shape->cast<model::Styler>() ) 0570 { 0571 convert_styler(styler, jsh); 0572 } 0573 else if ( auto polystar = shape->cast<model::PolyStar>() ) 0574 { 0575 if ( polystar->type.get() == model::PolyStar::Polygon ) 0576 { 0577 jsh.remove("is"_l); 0578 jsh.remove("ir"_l); 0579 } 0580 } 0581 else if ( auto styler = shape->cast<model::Repeater>() ) 0582 { 0583 QCborMap transform; 0584 convert_transform(styler->transform.get(), nullptr, transform); 0585 transform.remove("o"_l); 0586 transform["so"_l] = convert_animated(&styler->start_opacity, FloatMult(100)); 0587 transform["eo"_l] = convert_animated(&styler->end_opacity, FloatMult(100)); 0588 jsh["o"_l] = fake_animated(0); 0589 jsh["m"_l] = 1; 0590 jsh["tr"_l] = transform; 0591 } 0592 0593 return jsh; 0594 } 0595 0596 QCborMap fake_animated(const QCborValue& val) 0597 { 0598 QCborMap fake; 0599 fake["a"_l] = 0; 0600 fake["k"_l] = val; 0601 return fake; 0602 } 0603 0604 QCborArray convert_shapes(const model::ShapeListProperty& shapes, bool force_hidden) 0605 { 0606 QCborArray jshapes; 0607 for ( const auto& shape : shapes ) 0608 { 0609 if ( shape->is_instance<model::Image>() ) 0610 format->warning(i18n("Images cannot be grouped with other shapes, they must be inside a layer")); 0611 else if ( shape->is_instance<model::PreCompLayer>() ) 0612 format->warning(i18n("Composition layers cannot be grouped with other shapes, they must be inside a layer")); 0613 else if ( !strip || shape->visible.get() ) 0614 jshapes.push_front(convert_shape(shape.get(), force_hidden)); 0615 } 0616 return jshapes; 0617 } 0618 0619 QCborArray convert_assets(model::Composition* animation) 0620 { 0621 QCborArray assets; 0622 0623 if ( !strip_raster ) 0624 { 0625 for ( const auto& bmp : document->assets()->images->values ) 0626 { 0627 if ( auto_embed && !bmp->embedded() ) 0628 { 0629 auto clone = bmp->clone_covariant(); 0630 clone->embed(true); 0631 assets.push_back(convert_bitmat(clone.get())); 0632 } 0633 else 0634 { 0635 assets.push_back(convert_bitmat(bmp.get())); 0636 } 0637 } 0638 } 0639 0640 for ( const auto& comp : document->assets()->compositions->values ) 0641 { 0642 if ( comp.get() != animation ) 0643 assets.push_back(convert_precomp(comp.get())); 0644 } 0645 0646 return assets; 0647 } 0648 0649 QCborMap convert_bitmat(model::Bitmap* bmp) 0650 { 0651 QCborMap out; 0652 convert_object_basic(bmp, out); 0653 out["id"_l] = bmp->uuid.get().toString(); 0654 out["e"_l] = int(bmp->embedded()); 0655 if ( bmp->embedded() ) 0656 { 0657 out["u"_l] = ""; 0658 out["p"_l] = bmp->to_url().toString(); 0659 } 0660 else 0661 { 0662 auto finfo = bmp->file_info(); 0663 out["u"_l] = finfo.absolutePath(); 0664 out["p"_l] = finfo.fileName(); 0665 } 0666 return out; 0667 } 0668 0669 void convert_fake_layer_parent(model::Layer* parent, QCborMap& json) 0670 { 0671 if ( parent ) 0672 { 0673 convert_animation_container(parent->animation.get(), json); 0674 json["parent"_l] = layer_index(parent); 0675 } 0676 else 0677 { 0678 convert_animation_container(main->animation.get(), json); 0679 } 0680 } 0681 0682 void convert_fake_layer(model::DocumentNode* node, model::Layer* parent, QCborMap& json) 0683 { 0684 json["ddd"_l] = 0; 0685 if ( !strip ) 0686 { 0687 json["nm"_l] = node->name.get(); 0688 json["mn"_l] = node->uuid.get().toString(); 0689 } 0690 convert_fake_layer_parent(parent, json); 0691 json["ind"_l] = layer_index(node); 0692 } 0693 0694 QCborMap convert_image_layer(model::Image* image, model::Layer* parent) 0695 { 0696 QCborMap json; 0697 convert_fake_layer(image, parent, json); 0698 if ( !strip_raster ) 0699 json["ty"_l] = 2; 0700 json["ind"_l] = layer_index(image); 0701 json["st"_l] = 0; 0702 QCborMap transform; 0703 convert_object_basic(image->transform.get(), transform); 0704 transform["o"_l] = QCborMap{ 0705 {"a"_l, 0}, 0706 {"k"_l, 100}, 0707 }; 0708 json["ks"_l] = transform; 0709 if ( !strip_raster && image->image.get() ) 0710 json["refId"_l] = image->image->uuid.get().toString(); 0711 return json; 0712 } 0713 0714 QCborMap convert_precomp(model::Composition* comp) 0715 { 0716 QCborMap out; 0717 convert_object_basic(comp, out); 0718 out["id"_l] = comp->uuid.get().toString(); 0719 convert_composition(comp, out); 0720 return out; 0721 } 0722 0723 QCborMap convert_precomp_layer(model::PreCompLayer* layer, model::Layer* parent) 0724 { 0725 QCborMap json; 0726 json["ty"_l] = 0; 0727 convert_fake_layer(layer, parent, json); 0728 json["ind"_l] = layer_index(layer); 0729 json["st"_l] = layer->timing->start_time.get(); 0730 json["sr"_l] = layer->timing->stretch.get(); 0731 QCborMap transform; 0732 convert_transform(layer->transform.get(), &layer->opacity, transform); 0733 json["ks"_l] = transform; 0734 if ( layer->composition.get() ) 0735 json["refId"_l] = layer->composition->uuid.get().toString(); 0736 json["w"_l] = layer->size.get().width(); 0737 json["h"_l] = layer->size.get().height(); 0738 return json; 0739 } 0740 0741 ImportExport* format; 0742 model::Composition* main; 0743 model::Document* document; 0744 bool strip; 0745 QMap<QUuid, int> layer_indices; 0746 app::log::Log logger{"Lottie Export"}; 0747 model::Layer* mask = nullptr; 0748 bool strip_raster; 0749 bool auto_embed; 0750 bool old_kf; 0751 }; 0752 0753 0754 0755 } // namespace glaxnimate::io::lottie::detail