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