File indexing completed on 2025-01-05 04:01:12
0001 /* 0002 * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best> 0003 * 0004 * SPDX-License-Identifier: GPL-3.0-or-later 0005 */ 0006 #include "aep_loader.hpp" 0007 #include "model/shapes/rect.hpp" 0008 #include "model/shapes/ellipse.hpp" 0009 #include "model/shapes/fill.hpp" 0010 #include "model/shapes/stroke.hpp" 0011 #include "model/shapes/image.hpp" 0012 #include "model/shapes/polystar.hpp" 0013 #include "model/shapes/path.hpp" 0014 #include "model/shapes/trim.hpp" 0015 #include "model/shapes/offset_path.hpp" 0016 #include "model/shapes/inflate_deflate.hpp" 0017 #include "model/shapes/zig_zag.hpp" 0018 #include "model/shapes/round_corners.hpp" 0019 #include "model/shapes/repeater.hpp" 0020 #include "model/shapes/precomp_layer.hpp" 0021 #include "model/shapes/text.hpp" 0022 #include "model/animation/join_animatables.hpp" 0023 0024 using namespace glaxnimate::io::aep; 0025 using namespace glaxnimate; 0026 using glaxnimate::io::ImportExport; 0027 0028 static constexpr std::array<QRgb, 17> label_colors = { 0029 0x00000000, // None 0030 0xffb4393b, // Red 0031 0xffe2d759, // Yellow 0032 0xffabcbc8, // Aqua 0033 0xffe5bcca, // Pink 0034 0xffa9aac9, // Lavender 0035 0xffe5c19f, // Peach 0036 0xffb4c7b4, // Sea Foam 0037 0xff687fdd, // Blue 0038 0xff4ea350, // Green 0039 0xff8d3299, // Purple 0040 0xffe79228, // Orange 0041 0xff7e442c, // Brown 0042 0xfff371d5, // Fuchsia 0043 0xff43a2a4, // Cyan 0044 0xffa7967a, // Sandstone 0045 0xff203f1f // Dark Green 0046 }; 0047 0048 void glaxnimate::io::aep::AepLoader::load_project() 0049 { 0050 for ( const auto& comp : project.compositions ) 0051 get_comp(comp->id); 0052 0053 for ( const auto& pair : project.assets ) 0054 load_asset(pair.second); 0055 0056 for ( const auto& comp : project.compositions ) 0057 load_comp(*comp); 0058 } 0059 0060 void glaxnimate::io::aep::AepLoader::load_asset(const glaxnimate::io::aep::FolderItem* item) 0061 { 0062 if ( item->type() == FolderItem::Asset ) 0063 { 0064 auto image = std::make_unique<glaxnimate::model::Bitmap>(document); 0065 auto asset = static_cast<const FileAsset*>(item); 0066 if ( asset->path.exists() ) 0067 { 0068 image->filename.set(asset->path.filePath()); 0069 } 0070 else 0071 { 0072 // Handle collected assets 0073 QFileInfo path(asset_path.filePath(asset->path.fileName())); 0074 if ( !path.exists() ) 0075 warning(i18n("External asset not found: %1", asset->path.filePath())); 0076 else 0077 image->filename.set(path.filePath()); 0078 } 0079 image->name.set(item->name); 0080 images[item->id] = image.get(); 0081 document->assets()->images->values.insert(std::move(image)); 0082 asset_size[item->id] = QPointF(asset->width, asset->height); 0083 } 0084 else if ( item->type() == FolderItem::Solid ) 0085 { 0086 auto color = std::make_unique<glaxnimate::model::NamedColor>(document); 0087 auto solid = static_cast<const Solid*>(item); 0088 color->color.set(solid->color); 0089 color->name.set(solid->name); 0090 colors[item->id] = {color.get(), solid}; 0091 document->assets()->colors->values.insert(std::move(color)); 0092 asset_size[item->id] = QPointF(solid->width, solid->height); 0093 } 0094 else if ( item->type() == FolderItem::Composition ) 0095 { 0096 auto aecomp = static_cast<const Composition*>(item); 0097 asset_size[item->id] = QPointF(aecomp->width, aecomp->height); 0098 auto comp = get_comp(item->id); 0099 comp->width.set(aecomp->width); 0100 comp->height.set(aecomp->height); 0101 comp->name.set(aecomp->name); 0102 } 0103 } 0104 0105 void glaxnimate::io::aep::AepLoader::warning(const QString& msg) 0106 { 0107 io->warning(msg); 0108 } 0109 0110 void glaxnimate::io::aep::AepLoader::info(const QString& msg) 0111 { 0112 io->information(msg); 0113 } 0114 0115 static bool unknown_mn(glaxnimate::io::ImportExport* io, const QString& context, const QString& mn) 0116 { 0117 io->information(i18n("Unknown property \"%1\" of \"%2\"", mn, context)); 0118 return true; 0119 } 0120 0121 0122 model::Composition * glaxnimate::io::aep::AepLoader::get_comp(glaxnimate::io::aep::Id id) 0123 { 0124 if ( !id ) 0125 return nullptr; 0126 0127 auto& comp = comps[id]; 0128 if ( !comp ) 0129 comp = document->assets()->add_comp_no_undo(); 0130 0131 return comp; 0132 } 0133 0134 struct glaxnimate::io::aep::AepLoader::CompData 0135 { 0136 struct PendingLayer 0137 { 0138 model::Layer* layer; 0139 Id parent = 0; 0140 Id track_matte = 0; 0141 }; 0142 0143 void resolve() 0144 { 0145 for ( const auto& p : pending ) 0146 { 0147 if ( p.parent ) 0148 p.layer->parent.set(layers.at(p.parent)); 0149 /// \todo track matte 0150 } 0151 } 0152 0153 model::Composition* comp; 0154 const Composition* ae_comp; 0155 std::unordered_map<Id, model::Layer*> layers = {}; 0156 std::vector<PendingLayer> pending = {}; 0157 0158 }; 0159 0160 void glaxnimate::io::aep::AepLoader::load_comp(const glaxnimate::io::aep::Composition& ae_comp) 0161 { 0162 auto comp = get_comp(ae_comp.id); 0163 comp->name.set(ae_comp.name); 0164 comp->width.set(ae_comp.width); 0165 comp->height.set(ae_comp.height); 0166 comp->fps.set(ae_comp.framerate); 0167 comp->animation->first_frame.set(ae_comp.in_time); 0168 comp->animation->last_frame.set(ae_comp.out_time); 0169 comp->group_color.set(ae_comp.color); 0170 comp->group_color.set(label_colors[int(ae_comp.label_color)]); 0171 0172 CompData data{comp, &ae_comp}; 0173 for ( const auto& layer : ae_comp.layers ) 0174 load_layer(*layer, data); 0175 0176 data.resolve(); 0177 } 0178 0179 namespace { 0180 template<class T> T convert_value(const PropertyValue& v) 0181 { 0182 return std::get<T>(v.value); 0183 } 0184 0185 template<> float convert_value(const PropertyValue& v) 0186 { 0187 return convert_value<qreal>(v); 0188 } 0189 0190 template<> int convert_value(const PropertyValue& v) 0191 { 0192 return convert_value<qreal>(v); 0193 } 0194 0195 template<> QPointF convert_value(const PropertyValue& v) 0196 { 0197 if ( v.type() == PropertyValue::Vector2D ) 0198 return std::get<QPointF>(v.value); 0199 auto p = convert_value<QVector3D>(v.value); 0200 return {p.x(), p.y()}; 0201 } 0202 0203 0204 template<> QVector2D convert_value(const PropertyValue& v) 0205 { 0206 if ( v.type() == PropertyValue::Vector2D ) 0207 { 0208 auto p = std::get<QPointF>(v.value); 0209 return QVector2D(p.x(), p.y()); 0210 } 0211 else 0212 { 0213 auto p = convert_value<QVector3D>(v.value); 0214 return {p.x(), p.y()}; 0215 } 0216 } 0217 0218 template<> QSizeF convert_value(const PropertyValue& v) 0219 { 0220 auto p = convert_value<QPointF>(v.value); 0221 return {p.x(), p.y()}; 0222 } 0223 0224 template<> math::bezier::Bezier convert_value(const PropertyValue& v) 0225 { 0226 const auto& aebez = std::get<BezierData>(v.value); 0227 math::bezier::Bezier bez; 0228 int count = aebez.points.size(); 0229 0230 for ( int i = 0; i < count; i += 3 ) 0231 { 0232 /// \todo smooth etc? 0233 math::bezier::Point p(aebez.convert_point(aebez.points[i])); 0234 if ( i > 0 ) 0235 p.tan_in = aebez.convert_point(aebez.points[i-1]); 0236 else 0237 p.tan_in = aebez.convert_point(aebez.points.back()); 0238 0239 p.tan_out = aebez.convert_point(aebez.points[i+1]); 0240 0241 if ( i == count - 1 && aebez.closed && math::fuzzy_compare(bez[0].pos, p.pos) ) 0242 { 0243 bez[0].tan_in = p.tan_in; 0244 break; 0245 } 0246 0247 bez.push_back(p); 0248 } 0249 bez.set_closed(aebez.closed); 0250 return bez; 0251 } 0252 0253 template<> QGradientStops convert_value(const PropertyValue& v) 0254 { 0255 return convert_value<Gradient>(v).to_qt(); 0256 } 0257 0258 template<class T> struct DefaultConverter 0259 { 0260 T operator()(const PropertyValue& v) const { return convert_value<T>(v); } 0261 }; 0262 0263 template<class T, class Converter=DefaultConverter<T>> 0264 bool load_property(model::Property<T>& prop, const Property& ae_prop, const Converter& conv = {}) 0265 { 0266 if ( ae_prop.value.type() ) 0267 prop.set(conv(ae_prop.value)); 0268 else if ( !ae_prop.keyframes.empty() && ae_prop.keyframes[0].value.type() ) 0269 prop.set(conv(ae_prop.keyframes[0].value)); 0270 else 0271 return false; 0272 0273 return true; 0274 } 0275 0276 template<class T> void kf_extra_data(model::Keyframe<T>* kf, const Keyframe& aekf) 0277 { 0278 (void)kf; 0279 (void)aekf; 0280 } 0281 0282 template<> 0283 void kf_extra_data(model::Keyframe<QPointF>* kf, const Keyframe& aekf) 0284 { 0285 auto p = kf->get(); 0286 kf->set_point(math::bezier::Point( 0287 p, 0288 p + aekf.in_tangent, 0289 p + aekf.out_tangent 0290 )); 0291 } 0292 0293 qreal vector_length(const std::vector<double>& v) 0294 { 0295 qreal len = 0; 0296 for ( double a : v ) 0297 len += a * a; 0298 return math::sqrt(len); 0299 } 0300 0301 model::KeyframeTransition keyframe_transition(const Property& prop, const Keyframe& kf, const Keyframe& next_kf) 0302 { 0303 qreal duration = next_kf.time - kf.time; 0304 if ( qFuzzyIsNull(duration) ) 0305 return model::KeyframeTransition(model::KeyframeTransition::Linear); 0306 0307 qreal average_speed = 0; 0308 if ( prop.type == PropertyType::Position ) 0309 { 0310 math::bezier::BezierSegment bez; 0311 if ( kf.value.type() == PropertyValue::Vector2D ) 0312 { 0313 bez[0] = std::get<QPointF>(kf.value.value); 0314 bez[3] = std::get<QPointF>(next_kf.value.value); 0315 } 0316 else 0317 { 0318 auto p = std::get<QVector3D>(kf.value.value); 0319 bez[0] = {p.x(), p.y()}; 0320 p = std::get<QVector3D>(next_kf.value.value); 0321 bez[3] = {p.x(), p.y()}; 0322 } 0323 0324 bez[1] = kf.out_tangent; 0325 bez[2] = kf.in_tangent; 0326 0327 average_speed = math::bezier::LengthData(math::bezier::CubicBezierSolver(bez), 20).length(); 0328 0329 } 0330 else if ( prop.type == PropertyType::NoValue ) 0331 { 0332 average_speed = 1; 0333 } 0334 else 0335 { 0336 average_speed = math::abs(kf.value.magnitude() - next_kf.value.magnitude()); 0337 } 0338 0339 average_speed /= duration; 0340 qreal out_influence = vector_length(kf.out_influence); 0341 qreal in_influence = vector_length(kf.in_influence); 0342 qreal out_speed = vector_length(kf.out_speed); 0343 qreal in_speed = vector_length(kf.in_speed); 0344 0345 QPointF ease_out; 0346 QPointF ease_in; 0347 ease_out.setX(out_influence); 0348 ease_in.setX(1 - in_influence); 0349 if ( qFuzzyIsNull(average_speed) ) 0350 { 0351 ease_out.setY(out_influence); 0352 ease_in.setY(1 - in_influence); 0353 } 0354 else 0355 { 0356 ease_out.setY(out_influence * out_speed / average_speed); 0357 ease_in.setY(1 - in_influence * in_speed / average_speed); 0358 } 0359 0360 return model::KeyframeTransition(ease_out, ease_in); 0361 } 0362 0363 template<class T, class Converter=DefaultConverter<T>> 0364 bool load_property( 0365 model::AnimatedProperty<T>& prop, const Property& ae_prop, const Converter& conv = {} 0366 ) 0367 { 0368 if ( !ae_prop.animated && ae_prop.value.type() ) 0369 { 0370 prop.set(conv(ae_prop.value)); 0371 return true; 0372 } 0373 0374 for ( std::size_t i = 0; i < ae_prop.keyframes.size(); i++ ) 0375 { 0376 const auto& aekf = ae_prop.keyframes[i]; 0377 auto kf = prop.set_keyframe(aekf.time, conv(aekf.value)); 0378 0379 kf_extra_data(kf, aekf); 0380 0381 /// \todo easing 0382 if ( aekf.transition_type == KeyframeTransitionType::Hold ) 0383 kf->set_transition(model::KeyframeTransition(model::KeyframeTransition::Hold)); 0384 else if ( aekf.transition_type == KeyframeTransitionType::Linear ) 0385 kf->set_transition(model::KeyframeTransition(model::KeyframeTransition::Linear)); 0386 else if ( i + 1 < ae_prop.keyframes.size() ) 0387 kf->set_transition(keyframe_transition(ae_prop, aekf, ae_prop.keyframes[i+1])); 0388 } 0389 0390 return true; 0391 } 0392 0393 template<class PropT, class Converter=DefaultConverter<typename PropT::value_type>> 0394 void load_property_check( 0395 ImportExport* io, 0396 PropT& prop, 0397 const PropertyBase& ae_prop, 0398 const QString& match_name, 0399 const Converter& conv = {} 0400 ) 0401 { 0402 if ( ae_prop.class_type() != PropertyBase::Property ) 0403 { 0404 io->warning(i18n("Expected property for %1", match_name)); 0405 return; 0406 } 0407 0408 try 0409 { 0410 if ( !load_property(prop, static_cast<const Property&>(ae_prop), conv) ) 0411 io->warning(i18n("Could convert %1", match_name)); 0412 } 0413 catch ( const std::bad_variant_access& ) 0414 { 0415 io->error(i18n("Invalid value for %1", match_name)); 0416 } 0417 } 0418 0419 template<class PropT, class Converter=DefaultConverter<typename PropT::value_type>> 0420 bool load_property(ImportExport* io, PropT& prop, 0421 const PropertyPair& ae_prop, const char* match_name, const Converter& conv = {}) 0422 { 0423 if ( ae_prop.match_name != match_name ) 0424 return false; 0425 0426 load_property_check(io, prop, *ae_prop.value, ae_prop.match_name, conv); 0427 return true; 0428 } 0429 0430 bool convert_shape_reverse(const PropertyValue& v) 0431 { 0432 return convert_value<int>(v) == 3; 0433 } 0434 0435 template<int Divisor, class T = qreal> 0436 T convert_divide(const PropertyValue& v) 0437 { 0438 return convert_value<T>(v) / Divisor; 0439 } 0440 0441 template<class T> 0442 T convert_enum(const PropertyValue& v) 0443 { 0444 return T(convert_value<int>(v)); 0445 } 0446 0447 template<> 0448 model::Fill::Rule convert_enum(const PropertyValue& v) 0449 { 0450 if ( convert_value<int>(v) == 2 ) 0451 return model::Fill::Rule::EvenOdd; 0452 return model::Fill::Rule::NonZero; 0453 } 0454 0455 template<> 0456 model::Stroke::Cap convert_enum(const PropertyValue& v) 0457 { 0458 switch ( convert_value<int>(v) ) 0459 { 0460 default: 0461 case 1: return model::Stroke::Cap::ButtCap; 0462 case 2: return model::Stroke::Cap::RoundCap; 0463 case 3: return model::Stroke::Cap::SquareCap; 0464 } 0465 } 0466 0467 template<> 0468 model::Stroke::Join convert_enum(const PropertyValue& v) 0469 { 0470 switch ( convert_value<int>(v) ) 0471 { 0472 default: 0473 case 1: return model::Stroke::Join::MiterJoin; 0474 case 2: return model::Stroke::Join::RoundJoin; 0475 case 3: return model::Stroke::Join::BevelJoin; 0476 } 0477 } 0478 0479 struct AnchorMult 0480 { 0481 QPointF operator()(const PropertyValue& v) const 0482 { 0483 auto a = convert_value<QPointF>(v); 0484 return {a.x() * p.x(), a.y() * p.y()}; 0485 } 0486 QPointF p; 0487 }; 0488 0489 bool load_position_component(io::ImportExport* io, const PropertyGroup& group, int suffix, model::AnimatedProperty<float>& out, bool force) 0490 { 0491 auto pair = group.get_pair(QString("ADBE Position_%1").arg(suffix)); 0492 if ( !pair ) 0493 return false; 0494 0495 if ( pair->value->class_type() != PropertyBase::Property ) 0496 return false; 0497 0498 const Property& prop = static_cast<const Property&>(*pair->value); 0499 if ( !prop.is_component && !force ) 0500 return false; 0501 0502 load_property_check(io, out, prop, pair->match_name); 0503 return true; 0504 } 0505 0506 void load_transform(io::ImportExport* io, model::Transform* tf, const PropertyBase& prop, model::AnimatedProperty<float>* opacity, const QPointF& anchor_mult, bool divide_100) 0507 { 0508 if ( prop.class_type() != PropertyBase::PropertyGroup ) 0509 { 0510 io->warning(i18n("Expected property group for transform")); 0511 return; 0512 } 0513 0514 const PropertyGroup& g = static_cast<const PropertyGroup&>(prop); 0515 0516 bool is_3d = false; 0517 int split_position = 1; 0518 0519 for ( const auto& p : g.properties ) 0520 { 0521 if ( p.match_name.endsWith("Anchor Point") || p.match_name.endsWith("Anchor") ) 0522 load_property_check(io, tf->anchor_point, *p.value, p.match_name, AnchorMult{anchor_mult}); 0523 else if ( p.match_name.endsWith("Position") ) 0524 { 0525 if ( p.value->class_type() == PropertyBase::Property ) 0526 { 0527 const Property& pos_prop = static_cast<const Property&>(*p.value); 0528 if ( pos_prop.split ) 0529 { 0530 split_position = 2; 0531 } 0532 else 0533 { 0534 split_position = 0; 0535 load_property_check(io, tf->position, *p.value, p.match_name); 0536 } 0537 } 0538 } 0539 else if ( p.match_name.endsWith("Scale") ) 0540 load_property_check(io, tf->scale, *p.value, p.match_name, divide_100 ? &convert_divide<100, QVector2D> : &convert_divide<1, QVector2D>); 0541 else if ( p.match_name.endsWith("Rotation") || p.match_name.endsWith("Rotate Z") ) 0542 load_property_check(io, tf->rotation, *p.value, p.match_name); 0543 else if ( opacity && p.match_name.endsWith("Opacity") ) 0544 load_property_check(io, *opacity, *p.value, p.match_name, divide_100 ? &convert_divide<100> : &convert_divide<1>); 0545 else if ( 0546 p.match_name.endsWith("Rotate X") || 0547 p.match_name.endsWith("Rotate Y") || 0548 p.match_name.endsWith("Orientation") || 0549 p.match_name.endsWith("Position_2") 0550 ) 0551 is_3d = true; 0552 else if ( !p.match_name.endsWith("Position_1") && 0553 !p.match_name.endsWith("Position_0") && 0554 !p.match_name.endsWith("Opacity") && 0555 !p.match_name.endsWith("Envir Appear in Reflect") 0556 ) 0557 io->information(i18n("Unknown property \"%1\"", p.match_name)); 0558 } 0559 0560 if ( split_position ) 0561 { 0562 model::Document dummydoc(""); 0563 model::Object dummy(&dummydoc); 0564 model::AnimatedProperty<float> ax(&dummy, {}, 0); 0565 model::AnimatedProperty<float> ay(&dummy, {}, 0); 0566 0567 0568 bool force_split = split_position == 2; 0569 bool xok = load_position_component(io, g, 0, ax, force_split); 0570 bool yok = load_position_component(io, g, 1, ay, force_split); 0571 if ( split_position == 1 ) 0572 force_split = xok || yok; 0573 0574 if ( force_split ) 0575 { 0576 model::JoinAnimatables join({&ax, &ay}); 0577 join.apply_to(&tf->position, [](float x, float y) -> QPointF { 0578 return QPointF(x, y); 0579 }, &ax, &ay); 0580 } 0581 } 0582 0583 if ( is_3d ) 0584 { 0585 /// \todo figure a way of determining whether the transform is actually 3D 0586 /// as layer transfoms seem to often have the 3D properties 0587 (void)is_3d; 0588 // warning(i18n("3D transforms are not supported")); 0589 } 0590 } 0591 0592 template<class Obj> 0593 struct PropertyConverterBase 0594 { 0595 virtual ~PropertyConverterBase() noexcept = default; 0596 0597 virtual void load(ImportExport* io, Obj* object, const PropertyBase& ae_prop) const = 0; 0598 virtual void set_default(Obj* object) const = 0; 0599 }; 0600 0601 template<class Obj, class Base, class PropT, class T = typename PropT::value_type, class Converter=DefaultConverter<T>> 0602 struct PropertyConverter : PropertyConverterBase<Obj> 0603 { 0604 PropertyConverter(PropT (Base::*prop), const char* match_name, const Converter& converter, const std::optional<T>& default_value = {}) 0605 : prop(prop), match_name(match_name), converter(converter), default_value(default_value) 0606 {} 0607 0608 void load(ImportExport* io, Obj* object, const PropertyBase& ae_prop) const override 0609 { 0610 load_property_check(io, object->*prop, ae_prop, match_name, converter); 0611 } 0612 0613 void set_default(Obj* object) const override 0614 { 0615 if ( default_value ) 0616 (object->*prop).set(*default_value); 0617 } 0618 0619 PropT Base::*prop; 0620 QString match_name; 0621 Converter converter = {}; 0622 std::optional<T> default_value ; 0623 }; 0624 0625 template<class Base> 0626 struct ObjectConverterBase 0627 { 0628 virtual ~ObjectConverterBase() noexcept = default; 0629 virtual std::unique_ptr<Base> load(ImportExport* io, model::Document* document, const PropertyPair& prop) const = 0; 0630 }; 0631 0632 template<class Obj, class Base, class FuncT> 0633 struct ObjectConverterFunctor : public ObjectConverterBase<Base> 0634 { 0635 template<class F> 0636 ObjectConverterFunctor(F&& functor) : functor(std::forward<F>(functor)) {} 0637 0638 std::unique_ptr<Base> load(ImportExport* io, model::Document* document, const PropertyPair& prop) const override 0639 { 0640 return functor(io, document, prop); 0641 } 0642 0643 FuncT functor; 0644 }; 0645 0646 struct FallbackConverterBase 0647 { 0648 virtual ~FallbackConverterBase() noexcept = default; 0649 virtual void set_default() const = 0; 0650 virtual void load_property(ImportExport* io, model::Document* document, const PropertyPair& prop_parent, const PropertyPair& prop) const = 0; 0651 }; 0652 0653 template<class Obj, class Base> struct FallbackConverter; 0654 0655 0656 template<class Obj, class Base> 0657 struct ObjectConverter : public ObjectConverterBase<Base> 0658 { 0659 std::unique_ptr<Base> load(ImportExport* io, model::Document* document, const PropertyPair& prop) const override 0660 { 0661 return load_object(io, document, prop); 0662 } 0663 0664 void set_default(Obj* object, FallbackConverterBase* fallback) const 0665 { 0666 for ( const auto& conv : converters ) 0667 if ( conv.second ) 0668 conv.second->set_default(object); 0669 0670 if ( fallback ) 0671 fallback->set_default(); 0672 } 0673 0674 void load_property(Obj* object, ImportExport* io, model::Document* document, const PropertyPair& prop_parent, const PropertyPair& prop, FallbackConverterBase* fallback) const 0675 { 0676 auto it = converters.find(prop.match_name); 0677 if ( it == converters.end() ) 0678 { 0679 if ( fallback ) 0680 fallback->load_property(io, document, prop_parent, prop); 0681 else 0682 unknown_mn(io, prop_parent.match_name, prop.match_name); 0683 } 0684 else if ( it->second ) 0685 { 0686 it->second->load(io, object, *prop.value); 0687 } 0688 } 0689 0690 void load_properties(Obj* object, ImportExport* io, model::Document* document, const PropertyPair& prop, FallbackConverterBase* fallback = nullptr) const 0691 { 0692 set_default(object, fallback); 0693 0694 for ( const auto& p : *prop.value ) 0695 this->load_property(object, io, document, prop, p, fallback); 0696 } 0697 0698 std::unique_ptr<Obj> load_object(ImportExport* io, model::Document* document, const PropertyPair& prop) const 0699 { 0700 auto object = std::make_unique<Obj>(document); 0701 load_properties(object.get(), io, document, prop); 0702 return object; 0703 } 0704 0705 template<class O2, class PropT, class T = typename PropT::value_type, class Converter=DefaultConverter<T>> 0706 ObjectConverter& prop(PropT O2::* property, const char* match_name, const Converter& conv = {}) 0707 { 0708 auto ptr = std::make_unique<PropertyConverter<Obj, O2, PropT, T, Converter>>(property, match_name, conv); 0709 converters.emplace(match_name, std::move(ptr)); 0710 return *this; 0711 } 0712 0713 template<class O2, class PropT, class T = typename PropT::value_type, class Converter=DefaultConverter<T>> 0714 ObjectConverter& prop(PropT O2::*property, const char* match_name, const Converter& conv, const T& default_value) 0715 { 0716 converters.emplace(match_name, std::make_unique<PropertyConverter<Obj, O2, PropT, T, Converter>>(property, match_name, conv, default_value)); 0717 return *this; 0718 } 0719 0720 ObjectConverter& ignore(const char* match_name) 0721 { 0722 converters.emplace(match_name, nullptr); 0723 return *this; 0724 } 0725 0726 FallbackConverter<Obj, Base> fallback(Obj* object, FallbackConverterBase* next) const 0727 { 0728 return {object, this, next}; 0729 } 0730 0731 std::unordered_map<QString, std::unique_ptr<PropertyConverterBase<Obj>>> converters; 0732 }; 0733 0734 0735 template<class Obj, class Base> 0736 struct FallbackConverter : public FallbackConverterBase 0737 { 0738 FallbackConverter(Obj* object, const ObjectConverter<Obj, Base>* converter, FallbackConverterBase* next) 0739 : object(object), converter(converter), next(next) 0740 {} 0741 0742 void set_default() const override 0743 { 0744 converter->set_default(object, next); 0745 } 0746 0747 void load_property(ImportExport* io, model::Document* document, const PropertyPair& prop_parent, const PropertyPair& prop) const override 0748 { 0749 converter->load_property(object, io, document, prop_parent, prop, next); 0750 } 0751 0752 Obj* object; 0753 const ObjectConverter<Obj, Base>* converter; 0754 FallbackConverterBase* next; 0755 }; 0756 0757 template<class Base> 0758 struct ObjectFactory 0759 { 0760 std::unique_ptr<Base> load(ImportExport* io, model::Document* document, const PropertyPair& prop) const 0761 { 0762 auto it = converters.find(prop.match_name); 0763 if ( it == converters.end() ) 0764 return {}; 0765 return it->second->load(io, document, prop); 0766 } 0767 0768 template<class Obj, class FuncT> 0769 void obj(const char* match_name, FuncT&& func) 0770 { 0771 assert(converters.count(match_name) == 0); 0772 auto up = std::make_unique<ObjectConverterFunctor<Obj, Base, std::decay_t<FuncT>>>(std::forward<FuncT>(func)); 0773 converters.emplace(match_name, std::move(up)); 0774 } 0775 0776 template<class Obj> 0777 ObjectConverter<Obj, Base>& obj(const char* match_name) 0778 { 0779 assert(converters.count(match_name) == 0); 0780 auto up = std::make_unique<ObjectConverter<Obj, Base>>(); 0781 auto ptr = up.get(); 0782 converters.emplace(match_name, std::move(up)); 0783 return *ptr; 0784 } 0785 0786 std::unordered_map<QString, std::unique_ptr<ObjectConverterBase<Base>>> converters; 0787 }; 0788 0789 0790 std::unique_ptr<model::ShapeElement> create_shape(ImportExport* io, model::Document* document, const PropertyPair& prop); 0791 0792 std::unique_ptr<model::ShapeElement> load_shape(ImportExport* io, model::Document* document, const PropertyPair& prop) 0793 { 0794 auto shape = create_shape(io, document, prop); 0795 if ( shape && prop.value->class_type() == PropertyBase::PropertyGroup ) 0796 { 0797 const auto& gp = static_cast<const PropertyGroup&>(*prop.value); 0798 shape->visible.set(gp.visible); 0799 } 0800 return shape; 0801 } 0802 0803 const ObjectConverter<model::Gradient, model::Gradient>& gradient_converter() 0804 { 0805 static ObjectConverter<model::Gradient, model::Gradient> gradient; 0806 static bool initialized = false; 0807 if ( !initialized ) 0808 { 0809 initialized = true; 0810 gradient 0811 .prop(&model::Gradient::type, "ADBE Vector Grad Type", &convert_enum<model::Gradient::GradientType>) 0812 .prop(&model::Gradient::start_point, "ADBE Vector Grad Start Pt") 0813 .prop(&model::Gradient::end_point, "ADBE Vector Grad End Pt") 0814 .ignore("ADBE Vector Grad HiLite Length") /// \todo 0815 .ignore("ADBE Vector Grad HiLite Angle") /// \todo 0816 ; 0817 } 0818 0819 return gradient; 0820 } 0821 0822 const ObjectConverter<model::GradientColors, model::GradientColors>& gradient_stop_converter() 0823 { 0824 static ObjectConverter<model::GradientColors, model::GradientColors> gradient; 0825 static bool initialized = false; 0826 if ( !initialized ) 0827 { 0828 initialized = true; 0829 gradient 0830 .prop(&model::GradientColors::colors, "ADBE Vector Grad Colors") 0831 ; 0832 } 0833 0834 return gradient; 0835 } 0836 0837 template<class T> 0838 std::unique_ptr<model::ShapeElement> load_gradient(const ObjectConverter<T, model::ShapeElement>* base_converter, ImportExport* io, model::Document* document, const PropertyPair& prop) 0839 { 0840 auto shape = std::make_unique<T>(document); 0841 auto grad_colors = document->assets()->gradient_colors->values.insert( 0842 std::make_unique<glaxnimate::model::GradientColors>(document) 0843 ); 0844 auto grad = document->assets()->gradients->values.insert( 0845 std::make_unique<glaxnimate::model::Gradient>(document) 0846 ); 0847 grad->end_point.set({100, 0}); // default value 0848 grad->colors.set(grad_colors); 0849 shape->use.set(grad); 0850 0851 auto f1 = gradient_stop_converter().fallback(grad_colors, nullptr); 0852 auto f2 = gradient_converter().fallback(grad, &f1); 0853 base_converter->load_properties(shape.get(), io, document, prop, &f2); 0854 0855 auto* highlight_len = prop.value->get_pair("ADBE Vector Grad HiLite Length"); 0856 auto* highlight_angle = prop.value->get_pair("ADBE Vector Grad HiLite Angle"); 0857 if ( highlight_len || highlight_angle ) 0858 { 0859 model::Document dummydoc(""); 0860 model::Object dummy(&dummydoc); 0861 model::AnimatedProperty<float> length(&dummy, {}, 0); 0862 model::AnimatedProperty<float> angle(&dummy, {}, 0); 0863 if ( highlight_len ) 0864 load_property_check(io, length, *highlight_len->value, highlight_len->match_name); 0865 if ( highlight_angle ) 0866 load_property_check(io, angle, *highlight_angle->value, highlight_angle->match_name); 0867 model::JoinAnimatables join({&grad->start_point, &grad->end_point, &length, &angle}); 0868 join.apply_to(&grad->highlight, [](const QPointF& p, const QPointF& e, float length, float angle) -> QPointF { 0869 angle = math::deg2rad(angle + 90); 0870 length = math::length(e - p) * length / 100; 0871 return p + math::from_polar<QPointF>(length, angle); 0872 }, &grad->start_point, &grad->end_point, &length, &angle); 0873 } 0874 else 0875 { 0876 grad->highlight.set(grad->start_point.get()); 0877 } 0878 0879 return shape; 0880 } 0881 0882 /** 0883 * \brief Checks for the default rectangle created by AE with merge path 0884 * bodymovin has a similar check on export 0885 */ 0886 bool is_merge_rect(const model::ShapeElement& shape) 0887 { 0888 auto path = shape.cast<model::Path>(); 0889 0890 if ( !path ) 0891 return false; 0892 0893 if ( path->shape.animated() ) 0894 return false; 0895 0896 const auto& bez = path->shape.get(); 0897 0898 if ( !bez.closed() || bez.size() != 4 ) 0899 return false; 0900 0901 for ( const auto& p : bez ) 0902 if ( !math::fuzzy_compare(p.pos, p.tan_in) || !math::fuzzy_compare(p.pos, p.tan_out) ) 0903 return false; 0904 0905 std::array<qreal, 4> x; 0906 std::array<qreal, 4> y; 0907 for ( int i = 0; i < 4; i++ ) 0908 { 0909 x[i] = bez[i].pos.x(); 0910 y[i] = bez[i].pos.y(); 0911 } 0912 std::sort(x.begin(), x.end()); 0913 std::sort(y.begin(), y.end()); 0914 0915 return qFuzzyIsNull(x[0]-x[1]) && 0916 qFuzzyIsNull(x[2]-x[3]) && 0917 qFuzzyIsNull(y[0]-y[1]) && 0918 qFuzzyIsNull(y[2]-y[3]); 0919 } 0920 0921 bool skip_merge(const PropertyPair& prop) 0922 { 0923 if ( prop.match_name != "ADBE Vector Filter - Merge" ) 0924 return false; 0925 0926 auto type = prop.value->get("ADBE Vector Merge Type"); 0927 if ( !type || type->class_type() != PropertyBase::Property ) 0928 return false; 0929 0930 auto type_prop = static_cast<const Property*>(type); 0931 if ( type_prop->animated ) 0932 return false; 0933 0934 if ( !qFuzzyCompare(type_prop->value.magnitude(), 4) ) 0935 return false; 0936 0937 return true; 0938 0939 } 0940 0941 void load_shape_list(ImportExport* io, model::Document* document, const PropertyBase& properties, model::ShapeListProperty& shapes) 0942 { 0943 model::ShapeElement* merge_rect = nullptr; 0944 0945 for ( const auto& prop : properties ) 0946 { 0947 if ( merge_rect && skip_merge(prop) ) 0948 { 0949 shapes.remove(shapes.index_of(merge_rect)); 0950 merge_rect = nullptr; 0951 continue; 0952 } 0953 0954 if ( auto shape = load_shape(io, document, prop) ) 0955 { 0956 if ( !merge_rect && is_merge_rect(*shape) ) 0957 merge_rect = shape.get(); 0958 0959 shapes.insert(std::move(shape), 0); 0960 } 0961 } 0962 } 0963 0964 const ObjectFactory<model::ShapeElement>& shape_factory() 0965 { 0966 static ObjectFactory<model::ShapeElement> factory; 0967 static bool initialized = false; 0968 if ( !initialized ) 0969 { 0970 initialized = true; 0971 factory.obj<model::Group>("ADBE Vector Group", [](ImportExport* io, model::Document* document, const PropertyPair& prop) { 0972 auto gp = std::make_unique<model::Group>(document); 0973 load_transform(io, gp->transform.get(), (*prop.value)["ADBE Vector Transform Group"], &gp->opacity, {1, 1}, true); 0974 0975 load_shape_list(io, document, (*prop.value)["ADBE Vectors Group"], gp->shapes); 0976 0977 return gp; 0978 }); 0979 factory.obj<model::Rect>("ADBE Vector Shape - Rect") 0980 .prop(&model::Rect::reversed, "ADBE Vector Shape Direction", &convert_shape_reverse) 0981 .prop(&model::Rect::position, "ADBE Vector Rect Position") 0982 .prop(&model::Rect::size, "ADBE Vector Rect Size") 0983 .prop(&model::Rect::rounded, "ADBE Vector Rect Roundness") 0984 ; 0985 factory.obj<model::Ellipse>("ADBE Vector Shape - Ellipse") 0986 .prop(&model::Ellipse::reversed, "ADBE Vector Shape Direction", &convert_shape_reverse) 0987 .prop(&model::Ellipse::position, "ADBE Vector Ellipse Position") 0988 .prop(&model::Ellipse::size, "ADBE Vector Ellipse Size") 0989 ; 0990 factory.obj<model::PolyStar>("ADBE Vector Shape - Star") 0991 .prop(&model::PolyStar::reversed, "ADBE Vector Shape Direction", &convert_shape_reverse) 0992 .prop(&model::PolyStar::position, "ADBE Vector Star Position") 0993 .prop(&model::PolyStar::type, "ADBE Vector Star Type", &convert_enum<model::PolyStar::StarType>) 0994 .prop(&model::PolyStar::points, "ADBE Vector Star Points") 0995 .prop(&model::PolyStar::angle, "ADBE Vector Star Rotation") 0996 .prop(&model::PolyStar::inner_radius, "ADBE Vector Star Inner Radius") 0997 .prop(&model::PolyStar::outer_radius, "ADBE Vector Star Outer Radius") 0998 .prop(&model::PolyStar::inner_roundness, "ADBE Vector Star Inner Roundess", &convert_divide<100>) 0999 .prop(&model::PolyStar::outer_roundness, "ADBE Vector Star Outer Roundess", &convert_divide<100>) 1000 ; 1001 factory.obj<model::Path>("ADBE Vector Shape - Group") 1002 .prop(&model::Path::reversed, "ADBE Vector Shape Direction", &convert_shape_reverse) 1003 .prop(&model::Path::shape, "ADBE Vector Shape") 1004 ; 1005 const auto* fill = &factory.obj<model::Fill>("ADBE Vector Graphic - Fill") 1006 .ignore("ADBE Vector Blend Mode") 1007 .prop(&model::Fill::color, "ADBE Vector Fill Color", {}, QColor(255, 0, 0)) 1008 .prop(&model::Fill::opacity, "ADBE Vector Fill Opacity", &convert_divide<100>) 1009 .prop(&model::Fill::fill_rule, "ADBE Vector Fill Rule", &convert_enum<model::Fill::Rule>) 1010 .ignore("ADBE Vector Composite Order") /// \todo could be parsed 1011 ; 1012 const auto* stroke = &factory.obj<model::Stroke>("ADBE Vector Graphic - Stroke") 1013 .ignore("ADBE Vector Blend Mode") 1014 .prop(&model::Stroke::color, "ADBE Vector Stroke Color", {}, QColor(255, 255, 255)) 1015 .prop(&model::Stroke::opacity, "ADBE Vector Stroke Opacity", &convert_divide<100>) 1016 .prop(&model::Stroke::width, "ADBE Vector Stroke Width", {}, 2) 1017 .prop(&model::Stroke::cap, "ADBE Vector Stroke Line Cap", &convert_enum<model::Stroke::Cap>, model::Stroke::ButtCap) 1018 .prop(&model::Stroke::join, "ADBE Vector Stroke Line Join", &convert_enum<model::Stroke::Join>, model::Stroke::MiterJoin) 1019 .prop(&model::Stroke::miter_limit, "ADBE Vector Stroke Miter Limit", {}, 4) 1020 .ignore("ADBE Vector Stroke Dashes") 1021 .ignore("ADBE Vector Stroke Taper") 1022 .ignore("ADBE Vector Stroke Wave") 1023 .ignore("ADBE Vector Composite Order") /// \todo could be parsed 1024 ; 1025 factory.obj<model::RoundCorners>("ADBE Vector Filter - RC") 1026 .prop(&model::RoundCorners::radius, "ADBE Vector RoundCorner Radius", {}, 10) 1027 ; 1028 factory.obj<model::Trim>("ADBE Vector Filter - Trim") 1029 .prop(&model::Trim::start, "ADBE Vector Trim Start", &convert_divide<100>) 1030 .prop(&model::Trim::end, "ADBE Vector Trim End", &convert_divide<100>) 1031 .prop(&model::Trim::offset, "ADBE Vector Trim Offset", &convert_divide<360>) 1032 .prop(&model::Trim::multiple, "ADBE Vector Trim Type", &convert_enum<model::Trim::MultipleShapes>) 1033 ; 1034 factory.obj<model::OffsetPath>("ADBE Vector Filter - Offset") 1035 .prop(&model::OffsetPath::amount, "ADBE Vector Offset Amount") 1036 .prop(&model::OffsetPath::join, "ADBE Vector Offset Line Join", &convert_enum<model::Stroke::Join>, model::Stroke::MiterJoin) 1037 .prop(&model::OffsetPath::miter_limit, "ADBE Vector Offset Miter Limit", {}, 4) 1038 ; 1039 factory.obj<model::InflateDeflate>("ADBE Vector Filter - PB") 1040 .prop(&model::InflateDeflate::amount, "ADBE Vector PuckerBloat Amount", &convert_divide<100>) 1041 ; 1042 factory.obj<model::ZigZag>("ADBE Vector Filter - Zigzag") 1043 .prop(&model::ZigZag::amplitude, "ADBE Vector Zigzag Size", {}, 5) 1044 .prop(&model::ZigZag::frequency, "ADBE Vector Zigzag Detail", {}, 10) 1045 .prop(&model::ZigZag::style, "ADBE Vector Zigzag Points", &convert_enum<model::ZigZag::Style>) 1046 ; 1047 factory.obj<model::Fill>("ADBE Vector Graphic - G-Fill", [fill](ImportExport* io, model::Document* document, const PropertyPair& prop) { 1048 return load_gradient(fill, io, document, prop); 1049 }); 1050 factory.obj<model::Stroke>("ADBE Vector Graphic - G-Stroke", [stroke](ImportExport* io, model::Document* document, const PropertyPair& prop) { 1051 return load_gradient(stroke, io, document, prop); 1052 }); 1053 factory.obj<model::Repeater>("ADBE Vector Filter - Repeater", [](ImportExport* io, model::Document* document, const PropertyPair& prop) { 1054 auto shape = std::make_unique<model::Repeater>(document); 1055 if ( auto tf = prop.value->get("ADBE Vector Repeater Transform") ) 1056 { 1057 load_transform(io, shape->transform.get(), *tf, nullptr, {1, 1}, false); 1058 const char* pmn = "ADBE Vector Repeater Start Opacity"; 1059 if ( auto o = tf->get(pmn) ) 1060 load_property_check(io, shape->start_opacity, *o, pmn, &convert_divide<100>); 1061 pmn = "ADBE Vector Repeater End Opacity"; 1062 if ( auto o = tf->get(pmn) ) 1063 load_property_check(io, shape->end_opacity, *o, pmn, &convert_divide<100>); 1064 } 1065 1066 if ( auto copies = prop.value->get("ADBE Vector Repeater Copies") ) 1067 { 1068 load_property_check(io, shape->copies, *copies, "ADBE Vector Repeater Copies"); 1069 } 1070 1071 return shape; 1072 }); 1073 } 1074 1075 return factory; 1076 }; 1077 1078 1079 std::unique_ptr<model::ShapeElement> create_shape(ImportExport* io, model::Document* document, const PropertyPair& prop) 1080 { 1081 if ( auto shape = shape_factory().load(io, document, prop) ) 1082 return shape; 1083 1084 io->information(i18n("Unknown shape %1", prop.match_name)); 1085 return nullptr; 1086 } 1087 1088 } // namespace 1089 1090 1091 void glaxnimate::io::aep::AepLoader::load_layer(const glaxnimate::io::aep::Layer& ae_layer, CompData& data) 1092 { 1093 1094 auto ulayer = std::make_unique<model::Layer>(document); 1095 auto layer = ulayer.get(); 1096 data.comp->shapes.insert(std::move(ulayer), 0); 1097 data.layers[ae_layer.id] = layer; 1098 1099 if ( ae_layer.parent_id || ae_layer.matte_id ) 1100 data.pending.push_back({layer, ae_layer.parent_id, ae_layer.matte_id}); 1101 1102 layer->name.set(ae_layer.name); 1103 layer->render.set(!ae_layer.is_guide); 1104 layer->animation->first_frame.set(ae_layer.in_time); 1105 layer->animation->last_frame.set(ae_layer.out_time); 1106 layer->visible.set(ae_layer.properties.visible); 1107 /// \todo could be nice to toggle visibility based on solo/shy 1108 layer->group_color.set(label_colors[int(ae_layer.label_color)]); 1109 1110 layer->transform->position.set({ 1111 data.comp->width.get() / 2., 1112 data.comp->height.get() / 2., 1113 }); 1114 1115 QPointF anchor{1, 1}; 1116 auto it = asset_size.find(ae_layer.asset_id); 1117 if ( it != asset_size.end() && !ae_layer.is_null ) 1118 { 1119 anchor = it->second; 1120 layer->transform->anchor_point.set(anchor / 2); 1121 } 1122 load_transform(io, layer->transform.get(), ae_layer.properties["ADBE Transform Group"], &layer->opacity, anchor, false); 1123 layer->auto_orient.set(ae_layer.auto_orient); 1124 /// \todo masks "ADBE Mask Parade" 1125 1126 if ( ae_layer.is_null ) 1127 return; 1128 else if ( ae_layer.asset_id ) 1129 asset_layer(layer, ae_layer, data); 1130 else if ( ae_layer.type == LayerType::ShapeLayer ) 1131 shape_layer(layer, ae_layer, data); 1132 else if ( ae_layer.type == LayerType::TextLayer ) 1133 text_layer(layer, ae_layer, data); 1134 1135 auto mask_parade = ae_layer.properties.get("ADBE Mask Parade"); 1136 if ( mask_parade ) 1137 { 1138 layer->mask->mask.set(model::MaskSettings::Alpha); 1139 1140 auto clip_p = std::make_unique<model::Group>(document); 1141 auto clip = clip_p.get(); 1142 layer->shapes.insert(std::move(clip_p), 0); 1143 document->set_best_name(clip, i18n("Clip")); 1144 1145 for ( const auto& mask : *mask_parade ) 1146 { 1147 if ( mask.match_name != "ADBE Mask Atom" ) 1148 continue; 1149 1150 auto group = std::make_unique<model::Group>(document); 1151 1152 auto fill = std::make_unique<model::Fill>(document); 1153 fill->color.set(QColor(255, 255, 255)); 1154 document->set_best_name(fill.get()); 1155 auto mask_opacity = mask.value->get_pair("ADBE Mask Opacity"); 1156 if ( mask_opacity ) 1157 load_property_check(io, fill->opacity, *mask_opacity->value, mask_opacity->match_name, &convert_divide<100>); 1158 group->shapes.insert(std::move(fill)); 1159 1160 if ( auto expansion = mask.value->get_pair("ADBE Mask Offset") ) 1161 { 1162 auto offset = std::make_unique<model::OffsetPath>(document); 1163 document->set_best_name(offset.get()); 1164 load_property_check(io, offset->amount, *expansion->value, expansion->match_name); 1165 group->shapes.insert(std::move(offset)); 1166 } 1167 1168 if ( auto shape = mask.value->get_pair("ADBE Mask Shape") ) 1169 { 1170 auto path = std::make_unique<model::Path>(document); 1171 document->set_best_name(path.get()); 1172 load_property_check(io, path->shape, *shape->value, shape->match_name, {}); 1173 path->closed.set(true); 1174 group->shapes.insert(std::move(path)); 1175 } 1176 1177 clip->shapes.insert(std::move(group)); 1178 } 1179 } 1180 } 1181 1182 void glaxnimate::io::aep::AepLoader::shape_layer( 1183 model::Layer* layer, 1184 const glaxnimate::io::aep::Layer& ae_layer, 1185 glaxnimate::io::aep::AepLoader::CompData& 1186 ) 1187 { 1188 load_shape_list(io, document, ae_layer.properties["ADBE Root Vectors Group"], layer->shapes); 1189 } 1190 1191 void glaxnimate::io::aep::AepLoader::asset_layer( 1192 model::Layer* layer, const Layer& ae_layer, CompData& 1193 ) 1194 { 1195 auto img_it = images.find(ae_layer.asset_id); 1196 if ( img_it != images.end() ) 1197 { 1198 auto image = std::make_unique<model::Image>(document); 1199 image->image.set(img_it->second); 1200 image->name.set(img_it->second->name.get()); 1201 if ( layer->name.get().isEmpty() ) 1202 layer->name.set(image->name.get()); 1203 layer->shapes.insert(std::move(image)); 1204 return; 1205 } 1206 1207 auto comp_it = comps.find(ae_layer.asset_id); 1208 if ( comp_it != comps.end() ) 1209 { 1210 /// \todo ADBE Time Remapping 1211 /// \todo Time stretch / start_time 1212 auto precomp = std::make_unique<model::PreCompLayer>(document); 1213 precomp->timing->start_time.set(ae_layer.start_time); 1214 precomp->timing->stretch.set(ae_layer.time_stretch); 1215 precomp->composition.set(comp_it->second); 1216 precomp->name.set(comp_it->second->name.get()); 1217 precomp->size.set(comp_it->second->size()); 1218 if ( layer->name.get().isEmpty() ) 1219 layer->name.set(precomp->name.get()); 1220 layer->shapes.insert(std::move(precomp)); 1221 return; 1222 } 1223 1224 auto solid_it = colors.find(ae_layer.asset_id); 1225 if ( solid_it != colors.end() ) 1226 { 1227 auto fill = std::make_unique<model::Fill>(document); 1228 fill->color.set(solid_it->second.asset->color.get()); 1229 fill->use.set(solid_it->second.asset); 1230 layer->shapes.insert(std::move(fill)); 1231 1232 auto rect = std::make_unique<model::Rect>(document); 1233 rect->size.set(QSizeF( 1234 solid_it->second.solid->width, 1235 solid_it->second.solid->height 1236 )); 1237 rect->position.set(QPointF( 1238 solid_it->second.solid->width / 2, 1239 solid_it->second.solid->height / 2 1240 )); 1241 layer->shapes.insert(std::move(rect)); 1242 1243 if ( layer->name.get().isEmpty() ) 1244 layer->name.set(solid_it->second.asset->name.get()); 1245 return; 1246 } 1247 1248 warning(i18n("Unknown asset type for %1", ae_layer.name.isEmpty() ? "Layer" : ae_layer.name)); 1249 } 1250 1251 namespace { 1252 1253 std::unique_ptr<model::ShapeElement> text_to_shapes( 1254 const TextDocument& doc, const std::vector<Font>& fonts, model::Document* document 1255 ) 1256 { 1257 auto group = std::make_unique<model::Group>(document); 1258 auto pt = std::make_unique<model::TextShape>(document); 1259 auto text = pt.get(); 1260 group->shapes.insert(std::move(pt)); 1261 text->text.set(doc.text); 1262 if ( !doc.character_styles.empty() ) 1263 { 1264 /// \todo Style text spans 1265 const auto& style = doc.character_styles[0]; 1266 /// \todo figure out weight etc 1267 if ( style.font_index >= 0 && style.font_index < int(fonts.size()) ) 1268 text->font->family.set(fonts[style.font_index].family); 1269 text->font->size.set(style.size); 1270 1271 std::unique_ptr<model::Fill> fill; 1272 std::unique_ptr<model::Stroke> stroke; 1273 1274 if ( style.stroke_enabled ) 1275 { 1276 stroke = std::make_unique<model::Stroke>(document); 1277 stroke->color.set(style.stroke_color); 1278 stroke->width.set(style.stroke_width); 1279 } 1280 1281 /// \todo fill enabled? 1282 fill = std::make_unique<model::Fill>(document); 1283 fill->color.set(style.fill_color); 1284 1285 if ( style.stroke_over_fill ) 1286 { 1287 if ( fill ) 1288 group->shapes.insert(std::move(fill)); 1289 if ( stroke ) 1290 group->shapes.insert(std::move(stroke)); 1291 } 1292 else 1293 { 1294 if ( stroke ) 1295 group->shapes.insert(std::move(stroke)); 1296 if ( fill ) 1297 group->shapes.insert(std::move(fill)); 1298 } 1299 } 1300 1301 return group; 1302 } 1303 1304 } // namespace 1305 1306 void glaxnimate::io::aep::AepLoader::text_layer(model::Layer* layer, const Layer& ae_layer, CompData&) 1307 { 1308 auto prop = ae_layer.properties["ADBE Text Properties"]["ADBE Text Document"]; 1309 if ( prop.class_type() != PropertyBase::TextProperty ) 1310 return; 1311 1312 const auto& tprop = static_cast<const TextProperty&>(prop); 1313 1314 if ( tprop.documents.value.type() == PropertyValue::TextDocument ) 1315 { 1316 layer->shapes.insert(text_to_shapes( 1317 std::get<TextDocument>(tprop.documents.value.value), tprop.fonts, document 1318 )); 1319 return; 1320 } 1321 1322 if ( tprop.documents.keyframes.empty() ) 1323 return; 1324 1325 /// \todo animated text 1326 const auto& kf = tprop.documents.keyframes[0]; 1327 1328 if ( kf.value.type() == PropertyValue::TextDocument ) 1329 { 1330 layer->shapes.insert(text_to_shapes( 1331 std::get<TextDocument>(kf.value.value), tprop.fonts, document 1332 )); 1333 return; 1334 } 1335 } 1336