File indexing completed on 2025-01-05 04:01:17

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 #include "rive_serializer.hpp"
0010 
0011 #include "model/document.hpp"
0012 #include "model/assets/assets.hpp"
0013 #include "model/shapes/precomp_layer.hpp"
0014 #include "model/shapes/rect.hpp"
0015 #include "model/shapes/ellipse.hpp"
0016 #include "model/shapes/polystar.hpp"
0017 #include "model/shapes/path.hpp"
0018 #include "model/shapes/fill.hpp"
0019 #include "model/shapes/stroke.hpp"
0020 #include "model/shapes/image.hpp"
0021 #include "rive_format.hpp"
0022 
0023 namespace glaxnimate::io::rive {
0024 
0025 namespace detail {
0026 
0027 inline const QVariant& noop(const QVariant& v, model::FrameTime) { return v; }
0028 
0029 } // namespace name
0030 
0031 class RiveExporter
0032 {
0033 
0034 public:
0035     explicit RiveExporter(QIODevice* file, ImportExport* format)
0036         : serializer(file), format(format)
0037     {
0038         serializer.write_header(7, 0, 0);
0039         serializer.write_property_table({});
0040         write_object(TypeId::Backboard);
0041     }
0042 
0043     void write_document(model::Document* document)
0044     {
0045         write_assets(document->assets()->images.get());
0046 
0047 
0048         for ( const auto& comp : document->assets()->compositions->values )
0049             write_composition(comp.get(), comp->size());
0050     }
0051 
0052 private:
0053     void write_assets(const model::BitmapList* assets)
0054     {
0055         for ( const auto& image : assets->values )
0056         {
0057             write_bitmap(image.get());
0058         }
0059     }
0060 
0061     void write_bitmap(model::Bitmap* image)
0062     {
0063         auto name = image->name.get();
0064         if ( name.isEmpty() )
0065             name = image->filename.get();
0066 
0067         // idk what this is used for, let's just set it to a unique value the lazy way
0068         Identifier asset_id = reinterpret_cast<Identifier>(image);
0069 
0070         auto obj = types.object(TypeId::ImageAsset);
0071         if ( !obj )
0072             return;
0073         object_ids[image] = next_asset++;
0074 
0075         obj.set("name", name);
0076         obj.set("width", image->width.get());
0077         obj.set("height", image->height.get());
0078         obj.set("assetId", asset_id);
0079 
0080         serializer.write_object(obj);
0081 
0082         auto data = image->image_data();
0083         if ( !data.isEmpty() )
0084         {
0085             auto contents = types.object(TypeId::FileAssetContents);
0086             if ( !contents )
0087                 return;
0088             obj.set("bytes", data);
0089         }
0090     }
0091 
0092     bool write_object(TypeId type, const QVariantMap& props = {})
0093     {
0094         auto obj = types.object(type);
0095         if ( !obj )
0096             return false;
0097 
0098         for ( auto it = props.begin(); it != props.end(); ++it )
0099             obj.set(it.key(), *it);
0100 
0101         serializer.write_object(obj);
0102 
0103         return true;
0104     }
0105 
0106     void write_composition(model::Composition* comp, QSizeF size)
0107     {
0108         object_ids[comp] = next_artboard++;
0109         next_artboard_child = 1;
0110         animations.clear();
0111 
0112         if ( !write_object(TypeId::Artboard, {
0113             {"name", comp->name.get()},
0114             {"width", size.width()},
0115             {"height", size.height()},
0116             {"x", (24 + size.width()) * (next_artboard - 1)}
0117         }) ) return;
0118 
0119         for ( const auto& shape : comp->shapes )
0120             write_shape(shape.get(), 0);
0121 
0122         write_object(TypeId::LinearAnimation, {{"loopValue", 1}});
0123         for ( const auto& anim : animations )
0124         {
0125             write_object(TypeId::KeyedObject, {{"objectId", QVariant::fromValue(anim.first)}});
0126             for ( const auto& obj : anim.second )
0127                 serializer.write_object(obj);
0128         }
0129         write_object(TypeId::StateMachine, {});
0130         write_object(TypeId::StateMachineLayer, {});
0131         write_object(TypeId::AnimationState, {{"animationId", 0}});
0132         write_object(TypeId::EntryState, {});
0133         write_object(TypeId::StateTransition, {{"stateToId", 0}});
0134         write_object(TypeId::AnyState, {});
0135         write_object(TypeId::ExitState, {});
0136     }
0137 
0138     void write_shape(model::ShapeElement* element, Identifier parent_id)
0139     {
0140         auto id = next_artboard_child++;
0141         object_ids[element] = id;
0142 
0143 
0144         if ( auto layer = element->cast<model::Layer>() )
0145         {
0146             auto object = shape_object(TypeId::Node, element, parent_id);
0147             write_group(object, layer, id);
0148         }
0149         else if ( auto group = element->cast<model::Group>() )
0150         {
0151             auto object = shape_object(TypeId::Shape, element, parent_id);
0152             write_group(object, group, id);
0153         }
0154         else if ( auto shape = element->cast<model::Rect>() )
0155         {
0156             write_rect(shape, id, parent_id);
0157         }
0158         else if ( auto shape = element->cast<model::Ellipse>() )
0159         {
0160             write_ellipse(shape, id, parent_id);
0161         }
0162         else if ( auto shape = element->cast<model::PolyStar>() )
0163         {
0164             write_polystar(shape, id, parent_id);
0165         }
0166         else if ( auto shape = element->cast<model::Fill>() )
0167         {
0168             auto object = shape_object(TypeId::Fill, element, parent_id);
0169             object.set("isVisible", shape->visible.get());
0170             /// \todo fillRule
0171             serializer.write_object(object);
0172             write_styler(shape, id);
0173         }
0174         else if ( auto shape = element->cast<model::Stroke>() )
0175         {
0176             auto object = shape_object(TypeId::Stroke, element, parent_id);
0177             write_property(object, "thickness", shape->width, id, &detail::noop);
0178             object.set("isVisible", shape->visible.get());
0179             /// \todo cap + join
0180             serializer.write_object(object);
0181             write_styler(shape, id);
0182         }
0183         else if ( auto shape = element->cast<model::Image>() )
0184         {
0185             auto object = shape_object(TypeId::Image, element, parent_id);
0186             write_transform(object, shape->transform.get(), id, shape->local_bounding_rect(0));
0187             auto asset_id = object_ids.find(shape->image.get());
0188             if ( asset_id != object_ids.end() )
0189                 object.set("assetId", asset_id->second);
0190             serializer.write_object(object);
0191         }
0192         else if ( auto shape = element->cast<model::PreCompLayer>() )
0193         {
0194             write_precomp_layer(shape, id, parent_id);
0195         }
0196         else if ( auto shape = element->cast<model::Path>() )
0197         {
0198             write_path(shape, id, parent_id);
0199         }
0200         else
0201         {
0202             serializer.write_object(shape_object(TypeId::Shape, element, parent_id));
0203         }
0204     }
0205 
0206     void write_rect(model::Rect* shape, Identifier id, Identifier parent_id)
0207     {
0208         auto object = shape_object(TypeId::Rectangle, shape, parent_id);
0209         write_position(object, shape->position, id);
0210         write_property(object, "width", shape->size, id,
0211             [](const QVariant& v, model::FrameTime) { return QVariant::fromValue(v.toSizeF().width()); }
0212         );
0213         write_property(object, "height", shape->size, id,
0214             [](const QVariant& v, model::FrameTime) { return QVariant::fromValue(v.toSizeF().height()); }
0215         );
0216         write_property(object, "cornerRadiusTL", shape->rounded, id, &detail::noop);
0217         write_property(object, "cornerRadiusTR", shape->rounded, id, &detail::noop);
0218         write_property(object, "cornerRadiusBL", shape->rounded, id, &detail::noop);
0219         write_property(object, "cornerRadiusBR", shape->rounded, id, &detail::noop);
0220         serializer.write_object(object);
0221     }
0222 
0223     void write_ellipse(model::Ellipse* shape, Identifier id, Identifier parent_id)
0224     {
0225         auto object = shape_object(TypeId::Ellipse, shape, parent_id);
0226         write_position(object, shape->position, id);
0227         write_property(object, "width", shape->size, id,
0228             [](const QVariant& v, model::FrameTime) { return QVariant::fromValue(v.toSizeF().width()); }
0229         );
0230         write_property(object, "height", shape->size, id,
0231             [](const QVariant& v, model::FrameTime) { return QVariant::fromValue(v.toSizeF().height()); }
0232         );
0233         serializer.write_object(object);
0234     }
0235 
0236     void write_polystar(model::PolyStar* shape, Identifier id, Identifier parent_id)
0237     {
0238         auto type = shape->type.get() == model::PolyStar::Star ? TypeId::Star : TypeId::Polygon;
0239         auto object = shape_object(type, shape, parent_id);
0240         /// \todo cornerRadius
0241         write_position(object, shape->position, id);
0242 
0243         write_property(object, "points", shape->points, id, &detail::noop);
0244         write_property(object, "width", shape->outer_radius, id, &detail::noop);
0245         write_property(object, "height", shape->outer_radius, id, &detail::noop);
0246 
0247         if ( type == TypeId::Star )
0248         {
0249             write_property(object, "innerRadius", shape->inner_radius, id,
0250                 [shape](const QVariant& v, model::FrameTime t) {
0251                     auto outer = shape->outer_radius.get_at(t);
0252                     return QVariant::fromValue(qFuzzyIsNull(outer) ? 0 : v.toDouble() / outer);
0253                 }
0254             );
0255         }
0256 
0257         serializer.write_object(object);
0258     }
0259 
0260     void write_precomp_layer(model::PreCompLayer* shape, Identifier id, Identifier parent_id)
0261     {
0262         auto object = shape_object(TypeId::Rectangle, shape, parent_id);
0263         write_transform(object, shape->transform.get(), id, shape->local_bounding_rect(0));
0264         write_property(object, "opacity", shape->opacity, id, &detail::noop);
0265         if ( auto comp = shape->composition.get() )
0266         {
0267             Identifier comp_index = 1;
0268             for ( const auto& declared_comp : shape->document()->assets()->compositions->values )
0269             {
0270                 if ( declared_comp.get() == comp )
0271                     break;
0272                 comp_index++;
0273             }
0274             object.set("artboardId", comp_index);
0275         }
0276 
0277         serializer.write_object(object);
0278     }
0279 
0280     void write_path(model::Path* shape, Identifier id, Identifier parent_id)
0281     {
0282         auto object = shape_object(TypeId::PointsPath, shape, parent_id);
0283         object.set("isClosed", shape->closed.get());
0284         serializer.write_object(object);
0285 
0286         auto first_point_id = next_artboard_child;
0287         auto animated = shape->shape.keyframe_count() > 1;
0288 
0289         for ( const auto& point: shape->shape.get() )
0290         {
0291             Object pobj;
0292             auto pto = point.polar_tan_out();
0293             auto pti = point.polar_tan_in();
0294 
0295             if ( animated || (
0296                 point.type == math::bezier::PointType::Corner && (!qFuzzyIsNull(pto.length) || !qFuzzyIsNull(pti.length))
0297             ) )
0298             {
0299                 pobj = types.object(TypeId::CubicDetachedVertex);
0300                 pobj.set("outRotation", pto.angle);
0301                 pobj.set("outDistance", pto.length);
0302                 pobj.set("inRotation", pti.angle);
0303                 pobj.set("inDistance", pti.length);
0304             }
0305             else if ( point.type == math::bezier::PointType::Symmetrical)
0306             {
0307                 pobj = types.object(TypeId::CubicMirroredVertex);
0308                 pobj.set("rotation", pto.angle);
0309                 pobj.set("distance", pto.length);
0310             }
0311             else if ( point.type == math::bezier::PointType::Smooth )
0312             {
0313                 pobj = types.object(TypeId::CubicAsymmetricVertex);
0314                 pobj.set("rotation", pto.angle);
0315                 pobj.set("outDistance", pto.length);
0316                 pobj.set("inDistance", pti.length);
0317             }
0318             else
0319             {
0320                 pobj = types.object(TypeId::StraightVertex);
0321             }
0322 
0323             pobj.set("parentId", id);
0324             pobj.set("x", point.pos.x());
0325             pobj.set("y", point.pos.y());
0326             serializer.write_object(pobj);
0327             next_artboard_child++;
0328         }
0329 
0330         if ( animated )
0331         {
0332             auto type = types.get_type(TypeId::CubicDetachedVertex);
0333             const Identifier prop_x = 0;
0334             const Identifier prop_y = 1;
0335             const Identifier prop_in_rot = 2;
0336             const Identifier prop_in_len = 3;
0337             const Identifier prop_out_rot = 4;
0338             const Identifier prop_out_len = 5;
0339 
0340             auto kf_type = types.get_type(TypeId::KeyFrameDouble);
0341             std::array<std::pair<Identifier, std::vector<Object>>, 6> props_template{{
0342                 {type->property("x")->id, {}},
0343                 {type->property("y")->id, {}},
0344                 {type->property("inRotation")->id, {}},
0345                 {type->property("inDistance")->id, {}},
0346                 {type->property("outRotation")->id, {}},
0347                 {type->property("outDistance")->id, {}}
0348             }};
0349 
0350             int point_count = next_artboard_child - first_point_id;
0351             for ( auto pt_id = 0; pt_id < point_count; pt_id++ )
0352             {
0353                 auto props = props_template;
0354 
0355                 for ( const auto& kf : shape->shape )
0356                 {
0357                     if ( int(pt_id) >= kf.get().size() )
0358                     {
0359                         format->error(i18n("Bezier has mismatching number of points"));
0360                         continue;
0361                     }
0362 
0363                     for ( auto& prop: props )
0364                     {
0365                         Object rive_kf(kf_type);
0366                         /// \todo interpolations
0367                         rive_kf.set("interpolationType", 1);
0368                         rive_kf.set("frame", kf.time());
0369                         prop.second.push_back(std::move(rive_kf));
0370                     }
0371 
0372                     auto point = kf.get()[pt_id];
0373                     auto pto = point.polar_tan_out();
0374                     auto pti = point.polar_tan_in();
0375                     props[prop_x].second.back().set("value", point.pos.x());
0376                     props[prop_y].second.back().set("value", point.pos.y());
0377                     props[prop_in_rot].second.back().set("value", pti.angle);
0378                     props[prop_in_len].second.back().set("value", pti.length);
0379                     props[prop_out_rot].second.back().set("value", pto.angle + math::pi);
0380                     props[prop_out_len].second.back().set("value", pto.length);
0381                 }
0382 
0383                 auto& keyed_point = animations[first_point_id + pt_id];
0384                 for ( const auto& prop : props )
0385                 {
0386                     keyed_point.emplace_back(types.get_type(TypeId::KeyedProperty));
0387                     keyed_point.back().set("propertyKey", prop.first);
0388                     for ( auto& rkf : prop.second )
0389                         keyed_point.emplace_back(std::move(rkf));
0390                 }
0391             }
0392         }
0393     }
0394 
0395     Object shape_object(TypeId type_id, model::DocumentNode* shape, Identifier parent_id)
0396     {
0397         auto object = types.object(type_id);
0398         object.set("name", shape->name.get());
0399         object.set("parentId", parent_id);
0400         return object;
0401     }
0402 
0403     void write_group(Object& object, model::Group* group, Identifier id)
0404     {
0405         write_property(object, "opacity", group->opacity, id, &detail::noop);
0406         write_transform(object, group->transform.get(), id, group->local_bounding_rect(0));
0407         serializer.write_object(object);
0408 
0409         for ( const auto& shape : group->shapes )
0410             write_shape(shape.get(), id);
0411     }
0412 
0413     template<class T, class FuncT>
0414     void write_property(
0415         Object& object, const QString& name, const model::AnimatedProperty<T>& prop,
0416         Identifier object_id, const FuncT& transform)
0417     {
0418         auto rive_prop = object.type().property(name);
0419         if ( !rive_prop )
0420         {
0421             format->warning(i18n("Unknown property %1 of %2 (%3, %4)")
0422                 .arg(name)
0423                 .arg(int(object.type().id))
0424                 .arg(types.type_name(object.type().id))
0425                 .arg(prop.object()->type_name_human())
0426             );
0427             return;
0428         }
0429 
0430         object.set(rive_prop, transform(prop.value(), 0));
0431 
0432         if ( !prop.animated() )
0433             return;
0434 
0435         const ObjectType* kf_type = nullptr;
0436         QString attr;
0437         switch ( rive_prop->type )
0438         {
0439             case PropertyType::Float:
0440             case PropertyType::VarUint:
0441                 attr = "value";
0442                 kf_type = types.get_type(TypeId::KeyFrameDouble);
0443                 break;
0444             case PropertyType::Color:
0445                 attr = "colorValue";
0446                 kf_type = types.get_type(TypeId::KeyFrameColor);
0447                 break;
0448             default:
0449                 break;
0450 
0451         }
0452 
0453         if ( !kf_type )
0454         {
0455             format->warning(i18n("Unknown keyframe type for property %1 of %2 (%3, %4)")
0456                 .arg(name)
0457                 .arg(int(object.type().id))
0458                 .arg(types.type_name(object.type().id))
0459                 .arg(prop.object()->type_name_human())
0460             );
0461             return;
0462         }
0463 
0464         auto& keyed_object = animations[object_id];
0465 
0466         auto keyed_prop = types.object(TypeId::KeyedProperty);
0467         keyed_prop.set("propertyKey", rive_prop->id);
0468         keyed_object.emplace_back(std::move(keyed_prop));
0469 
0470         for ( const auto& kf : prop )
0471         {
0472             Object rive_kf(kf_type);
0473             /// \todo interpolations
0474             rive_kf.set("interpolationType", 1);
0475             rive_kf.set(attr, transform(kf.value(), kf.time()));
0476             rive_kf.set("frame", kf.time());
0477             keyed_object.emplace_back(std::move(rive_kf));
0478         }
0479     }
0480 
0481     void write_position(Object& object, const model::AnimatedProperty<QPointF>& prop, Identifier object_id)
0482     {
0483         write_property(object, "x", prop, object_id,
0484             [](const QVariant& v, model::FrameTime) { return QVariant::fromValue(v.toPointF().x()); }
0485         );
0486         write_property(object, "y", prop, object_id,
0487             [](const QVariant& v, model::FrameTime) { return QVariant::fromValue(v.toPointF().y()); }
0488         );
0489     }
0490 
0491     void write_transform(Object& object, model::Transform* trans, Identifier object_id, const QRectF& box)
0492     {
0493         if ( object.type().property("originX") )
0494         {
0495             write_position(object, trans->position, object_id);
0496 
0497             if ( box.width() > 0 )
0498             {
0499                 write_property(object, "originX", trans->anchor_point, object_id,
0500                     [&box](const QVariant& v, model::FrameTime) {
0501                         return QVariant::fromValue(
0502                             (v.toPointF().x() - box.left()) / box.width()
0503                         );
0504                     }
0505                 );
0506             }
0507 
0508             if ( box.height() > 0 )
0509             {
0510                 write_property(object, "originY", trans->anchor_point, object_id,
0511                     [&box](const QVariant& v, model::FrameTime) {
0512                         return QVariant::fromValue(
0513                             (v.toPointF().y() - box.top()) / box.height()
0514                         );
0515                     }
0516                 );
0517             }
0518         }
0519         else
0520         {
0521             /// \todo Handle animated anchor point
0522             auto anchor = trans->anchor_point.get();
0523             write_property(object, "x", trans->position, object_id,
0524                 [anchor](const QVariant& v, model::FrameTime) { return QVariant::fromValue(v.toPointF().x() - anchor.x()); }
0525             );
0526             write_property(object, "y", trans->position, object_id,
0527                 [anchor](const QVariant& v, model::FrameTime) { return QVariant::fromValue(v.toPointF().y() - anchor.y()); }
0528             );
0529         }
0530 
0531         write_property(object, "rotation", trans->rotation, object_id, &detail::noop);
0532 
0533         write_property(object, "scaleX", trans->scale, object_id,
0534             [](const QVariant& v, model::FrameTime) { return QVariant::fromValue(v.value<QVector2D>().x()); }
0535         );
0536         write_property(object, "scaleY", trans->scale, object_id,
0537             [](const QVariant& v, model::FrameTime) { return QVariant::fromValue(v.value<QVector2D>().x()); }
0538         );
0539     }
0540 
0541     void write_styler(model::Styler* shape, Identifier object_id)
0542     {
0543         auto use = shape->use.get();
0544         auto id = next_artboard_child++;
0545 
0546         if ( auto grad = use->cast<model::Gradient>() )
0547         {
0548             auto object = shape_object(
0549                 grad->type.get() == model::Gradient::Radial ? TypeId::RadialGradient : TypeId::LinearGradient,
0550                 grad, object_id
0551             );
0552             write_property(object, "opacity", shape->color, id, &detail::noop);
0553 
0554             serializer.write_object(object);
0555             /// \todo finish
0556         }
0557         else if ( auto col = use->cast<model::NamedColor>() )
0558         {
0559             auto object = shape_object(TypeId::SolidColor, col, object_id);
0560             write_property(object, "colorValue", col->color, id, &detail::noop);
0561             serializer.write_object(object);
0562         }
0563         else
0564         {
0565             auto object = shape_object(TypeId::SolidColor, shape, object_id);
0566             write_property(object, "colorValue", shape->color, id, &detail::noop);
0567             serializer.write_object(object);
0568         }
0569     }
0570 
0571     Identifier next_asset = 0;
0572     Identifier next_artboard = 0;
0573     Identifier next_artboard_child = 0;
0574     std::unordered_map<model::DocumentNode*, Identifier> object_ids;
0575     RiveSerializer serializer;
0576     ImportExport* format;
0577     std::unordered_map<Identifier, std::vector<Object>> animations;
0578     TypeSystem types;
0579 };
0580 
0581 } // namespace glaxnimate::io::rive