File indexing completed on 2025-01-05 04:01:13
0001 /* 0002 * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best> 0003 * 0004 * SPDX-License-Identifier: GPL-3.0-or-later 0005 */ 0006 #pragma once 0007 0008 #include <QJsonDocument> 0009 #include <QJsonObject> 0010 0011 #include "aep_riff.hpp" 0012 #include "ae_project.hpp" 0013 #include "cos.hpp" 0014 #include "gradient_xml.hpp" 0015 #include "aep_format.hpp" 0016 0017 namespace glaxnimate::io::aep { 0018 0019 class AepError : public std::runtime_error 0020 { 0021 public: 0022 AepError(QString message) : runtime_error(message.toStdString()), message(std::move(message)) {} 0023 0024 QString message; 0025 }; 0026 0027 class AepParser 0028 { 0029 private: 0030 using Chunk = const RiffChunk*; 0031 using ChunkRange = RiffChunk::FindRange; 0032 struct PropertyContext 0033 { 0034 Composition* comp = nullptr; 0035 Layer* layer = nullptr; 0036 0037 model::FrameTime time_to_frames(model::FrameTime time) const 0038 { 0039 return time / comp->time_scale + layer->start_time; 0040 } 0041 }; 0042 0043 public: 0044 AepParser(ImportExport* io) : io(io) {} 0045 0046 Project parse(const RiffChunk& root) 0047 { 0048 if ( root.subheader != "Egg!" ) 0049 throw AepError(i18n("Not an AEP file")); 0050 0051 Project project; 0052 Chunk fold = nullptr, efdg = nullptr; 0053 root.find_multiple({&fold, &efdg}, {"Fold", "EfdG"}); 0054 0055 if ( load_unecessary && efdg ) 0056 parse_effect_definitions(efdg->find_all("EfDf"), project); 0057 0058 parse_folder(fold, project.folder, project); 0059 0060 for ( auto& comp : project.compositions ) 0061 parse_composition(comp_chunks[comp->id], *comp); 0062 0063 return project; 0064 } 0065 0066 private: 0067 void parse_folder(Chunk chunk, Folder& folder, Project& project) 0068 { 0069 FolderItem* current_item = nullptr; 0070 0071 for ( const auto& child : chunk->children ) 0072 { 0073 if ( *child == "fiac" ) 0074 { 0075 if ( current_item && child->data().read_uint8() ) 0076 project.current_item = current_item; 0077 } 0078 else if ( *child == "Item" ) 0079 { 0080 Chunk item = nullptr; 0081 Chunk name_chunk = nullptr; 0082 child->find_multiple({&item, &name_chunk}, {"idta", "Utf8"}); 0083 current_item = nullptr; 0084 0085 if ( !item ) 0086 continue; 0087 0088 auto name = to_string(name_chunk); 0089 auto data = item->data(); 0090 auto type = data.read_uint16(); 0091 data.skip(14); 0092 auto id = data.read_uint32(); 0093 data.skip(38); 0094 auto color = LabelColors(data.read_uint8()); 0095 0096 switch ( type ) 0097 { 0098 case 1: // Folder 0099 { 0100 auto child_item = folder.add<Folder>(); 0101 child_item->id = id; 0102 child_item->name = name; 0103 current_item = child_item; 0104 if ( auto contents = child->child("Sfdr") ) 0105 parse_folder(contents, *child_item, project); 0106 break; 0107 } 0108 case 4: // Composition 0109 { 0110 auto comp = folder.add<Composition>(); 0111 comp->id = id; 0112 comp->name = name; 0113 current_item = comp; 0114 project.compositions.push_back(comp); 0115 project.assets[id] = comp; 0116 comp_chunks[id] = child.get(); 0117 break; 0118 } 0119 case 7: // Asset 0120 current_item = parse_asset(id, child->child("Pin "), folder, project); 0121 break; 0122 default: 0123 warning(i18n("Unknown Item type %s", type)); 0124 } 0125 0126 if ( current_item ) 0127 current_item->label_color = color; 0128 } 0129 } 0130 } 0131 0132 void parse_composition(Chunk chunk, Composition& comp) 0133 { 0134 auto cdta = chunk->child("cdta"); 0135 if ( !cdta ) 0136 { 0137 warning(i18n("Missing composition data")); 0138 return; 0139 } 0140 0141 /// \todo label color? 0142 auto data = cdta->data(); 0143 comp.resolution_x = data.read_uint16(); 0144 comp.resolution_y = data.read_uint16(); 0145 data.skip(1); 0146 // Time stuff 0147 comp.time_scale = data.read_uint16(); 0148 data.skip(14); 0149 comp.playhead_time = comp.time_to_frames(data.read_uint16()); 0150 data.skip(6); 0151 comp.in_time = comp.time_to_frames(data.read_uint16()); 0152 data.skip(6); 0153 auto out_time = data.read_uint16(); 0154 data.skip(6); 0155 comp.duration = comp.time_to_frames(data.read_uint16()); 0156 if ( out_time == 0xffff ) 0157 comp.out_time = comp.duration; 0158 else 0159 comp.out_time = comp.time_to_frames(out_time); 0160 data.skip(5); 0161 0162 // Background 0163 comp.color.setRed(data.read_uint8()); 0164 comp.color.setGreen(data.read_uint8()); 0165 comp.color.setBlue(data.read_uint8()); 0166 0167 // Flags 0168 data.skip(84); 0169 Flags attr(data.read_uint8()); 0170 comp.shy = attr.get(0, 0); 0171 comp.motion_blur = attr.get(0, 3); 0172 comp.frame_blending = attr.get(0, 4); 0173 comp.preserve_framerate = attr.get(0, 5); 0174 comp.preserve_resolution = attr.get(0, 7); 0175 0176 // Lottie 0177 comp.width = data.read_uint16(); 0178 comp.height = data.read_uint16(); 0179 comp.pixel_ratio_width = data.read_uint32(); 0180 comp.pixel_ratio_height = data.read_uint32(); 0181 data.skip(4); 0182 comp.framerate = data.read_uint16(); 0183 0184 // Misc 0185 data.skip(16); 0186 comp.shutter_angle = data.read_uint16(); 0187 comp.shutter_phase = data.read_sint32(); 0188 data.skip(16); 0189 comp.samples_limit = data.read_uint32(); 0190 comp.samples_per_frame = data.read_uint32(); 0191 0192 for ( const auto& child : chunk->children ) 0193 { 0194 if ( *child == "Layr" ) 0195 comp.layers.push_back(parse_layer(child.get(), comp)); 0196 else if ( load_unecessary && *child == "SecL" ) 0197 comp.markers = parse_layer(child.get(), comp); 0198 else if ( load_unecessary && (*child == "CLay" || *child == "DLay" || *child == "SLay") ) 0199 comp.views.push_back(parse_layer(child.get(), comp)); 0200 } 0201 } 0202 0203 QString to_string(Chunk chunk) 0204 { 0205 if ( !chunk ) 0206 return ""; 0207 auto data = chunk->data().read(); 0208 if ( data == placeholder ) 0209 return ""; 0210 0211 if ( chunk->header == "Utf8" ) 0212 return QString::fromUtf8(data); 0213 0214 warning(i18n("Unknown encoding for %1", chunk->header.to_string())); 0215 return ""; 0216 } 0217 0218 void warning(const QString& msg) const 0219 { 0220 io->warning(msg); 0221 } 0222 0223 FolderItem* parse_asset(Id id, Chunk chunk, Folder& folder, Project& project) 0224 { 0225 Chunk sspc, utf8, als2, opti; 0226 sspc = utf8 = als2 = opti = nullptr; 0227 chunk->find_multiple({&sspc, &utf8, &als2, &opti}, {"sspc", "Utf8", "Als2", "opti"}); 0228 if ( !sspc || !opti ) 0229 { 0230 warning(i18n("Missing asset data")); 0231 return nullptr; 0232 } 0233 0234 auto name = to_string(utf8); 0235 auto asset_reader = sspc->data(); 0236 asset_reader.skip(32); 0237 auto width = asset_reader.read_uint16(); 0238 asset_reader.skip(2); 0239 auto height = asset_reader.read_uint16(); 0240 Asset* asset; 0241 0242 auto data = opti->data(); 0243 0244 if ( data.read(4) == "Soli" ) 0245 { 0246 data.skip(6); 0247 auto solid = folder.add<Solid>(); 0248 solid->color.setAlphaF(data.read_float32()); 0249 solid->color.setRedF(data.read_float32()); 0250 solid->color.setGreenF(data.read_float32()); 0251 solid->color.setBlueF(data.read_float32()); 0252 solid->name = data.read_utf8_nul(256); 0253 asset = solid; 0254 } 0255 else 0256 { 0257 auto doc = QJsonDocument::fromJson(als2->child("alas")->data().read()); 0258 QString path = doc.object()["fullpath"].toString(); 0259 // Handle weird windows paths 0260 if ( path.contains('\\') && QDir::separator() == '/' ) 0261 { 0262 path = path.replace('\\', '/'); 0263 if ( path.size() > 1 && path[1] == ':' ) 0264 path = '/' + path; 0265 } 0266 auto file = folder.add<FileAsset>(); 0267 file->path = QFileInfo(path); 0268 file->name = name.isEmpty() ? file->path.fileName() : name; 0269 asset = file; 0270 } 0271 0272 asset->width = width; 0273 asset->height = height; 0274 asset->id = id; 0275 project.assets[id] = asset; 0276 return asset; 0277 } 0278 0279 std::unique_ptr<Layer> parse_layer(Chunk chunk, Composition& comp) 0280 { 0281 auto layer = std::make_unique<Layer>(); 0282 0283 Chunk ldta, utf8, tdgp; 0284 ldta = utf8 = tdgp = nullptr; 0285 chunk->find_multiple({&ldta, &utf8, &tdgp}, {"ldta", "Utf8", "tdgp"}); 0286 if ( !ldta ) 0287 { 0288 warning(i18n("Missing layer data")); 0289 return {}; 0290 } 0291 0292 PropertyContext context{&comp, layer.get()}; 0293 layer->name = to_string(utf8); 0294 0295 auto data = ldta->data(); 0296 layer->id = data.read_uint32(); 0297 layer->quality = LayerQuality(data.read_uint16()); 0298 data.skip(4); 0299 layer->time_stretch = data.read_uint16(); 0300 data.skip(1); 0301 layer->start_time = comp.time_to_frames(data.read_sint16()); 0302 data.skip(6); 0303 layer->in_time = context.time_to_frames(data.read_uint16()); 0304 data.skip(6); 0305 layer->out_time = context.time_to_frames(data.read_uint16()); 0306 data.skip(6); 0307 Flags flags = data.read_uint<3>(); 0308 layer->is_guide = flags.get(2, 1); 0309 layer->bicubic_sampling = flags.get(2, 6); 0310 layer->auto_orient = flags.get(1, 0); 0311 layer->is_adjustment = flags.get(1, 1); 0312 layer->threedimensional = flags.get(1, 2); 0313 layer->solo = flags.get(1, 3); 0314 layer->is_null = flags.get(1, 7); 0315 layer->visible = flags.get(0, 0); 0316 layer->effects_enabled = flags.get(0, 2); 0317 layer->motion_blur = flags.get(0, 3); 0318 layer->locked = flags.get(0, 5); 0319 layer->shy = flags.get(0, 6); 0320 layer->continuously_rasterize = flags.get(0, 7); 0321 layer->asset_id = data.read_uint32(); 0322 data.skip(17); 0323 layer->label_color = LabelColors(data.read_uint8()); 0324 data.skip(2); 0325 data.skip(32); // Name, we get it from Utf8 instead 0326 data.skip(11); 0327 layer->matte_mode = TrackMatteType(data.read_uint8()); 0328 data.skip(2); 0329 layer->time_stretch /= data.read_uint16(); 0330 data.skip(19); 0331 layer->type = LayerType(data.read_uint8()); 0332 layer->parent_id = data.read_uint32(); 0333 data.skip(24); 0334 layer->matte_id = data.read_uint32(); 0335 0336 parse_property_group(tdgp, layer->properties, context); 0337 0338 return layer; 0339 } 0340 0341 void parse_property_group(Chunk chunk, PropertyGroup& group, const PropertyContext& context) 0342 { 0343 QString match_name; 0344 for ( auto it = chunk->children.begin(); it != chunk->children.end(); ++it ) 0345 { 0346 auto child = it->get(); 0347 0348 if ( *child == "tdmn" ) 0349 { 0350 match_name = child->data().read_utf8_nul(); 0351 } 0352 else if ( *child == "tdsb" ) 0353 { 0354 Flags flags = child->data().read_uint32(); 0355 group.visible = flags.get(0, 0); 0356 } 0357 else if ( *child == "tdsn" ) 0358 { 0359 group.name = to_string(child->child("Utf8")); 0360 } 0361 else if ( *child == "mkif" ) 0362 { 0363 auto mask = std::make_unique<Mask>(); 0364 auto data = child->data(); 0365 mask->inverted = data.read_uint8(); 0366 mask->locked = data.read_uint8(); 0367 data.skip(4); 0368 mask->mode = MaskMode(data.read_uint16()); 0369 ++it; 0370 if ( it == chunk->children.end() ) 0371 { 0372 warning(i18n("Missing mask properties")); 0373 return; 0374 } 0375 if ( **it != "tdgp" ) 0376 { 0377 warning(i18n("Missing mask properties")); 0378 continue; 0379 } 0380 0381 parse_property_group(it->get(), mask->properties, context); 0382 group.properties.push_back({match_name, std::move(mask)}); 0383 match_name.clear(); 0384 } 0385 else if ( !match_name.isEmpty() ) 0386 { 0387 auto prop = parse_property(child, context); 0388 if ( prop ) 0389 group.properties.push_back({match_name, std::move(prop)}); 0390 match_name.clear(); 0391 } 0392 } 0393 } 0394 0395 std::unique_ptr<PropertyGroup> parse_property_group(Chunk chunk, const PropertyContext& context) 0396 { 0397 auto group = std::make_unique<PropertyGroup>(); 0398 parse_property_group(chunk, *group, context); 0399 return group; 0400 } 0401 0402 std::unique_ptr<PropertyBase> parse_property(Chunk chunk, const PropertyContext& context) 0403 { 0404 if ( *chunk == "tdgp" ) 0405 return parse_property_group(chunk, context); 0406 else if ( *chunk == "tdbs" ) 0407 return parse_animated_property(chunk, context, {}); 0408 else if ( *chunk == "om-s" ) 0409 return parse_animated_with_values(chunk, context, "omks", "shap", &AepParser::parse_bezier); 0410 else if ( *chunk == "GCst" ) 0411 return parse_animated_with_values(chunk, context, "GCky", "Utf8", &AepParser::parse_gradient); 0412 else if ( *chunk == "btds" ) 0413 return parse_animated_text(chunk, context); 0414 else if ( *chunk == "sspc" ) 0415 return parse_effect_instance(chunk, context); 0416 else if ( *chunk == "otst" ) 0417 return load_unecessary ? parse_animated_with_values(chunk, context, "otky", "otda", &AepParser::parse_orientation) : nullptr; 0418 else if ( *chunk == "mrst" ) 0419 return load_unecessary ? parse_animated_with_values(chunk, context, "mrky", "Nmrd", &AepParser::parse_marker) : nullptr; 0420 // I've seen these in files but I'm not sure how to parse them 0421 else if ( *chunk == "OvG2" || *chunk == "blsi" || *chunk == "blsv" ) 0422 return {}; 0423 0424 warning(i18n("Unknown property type: %1", chunk->name().to_string())); 0425 return {}; 0426 } 0427 0428 std::unique_ptr<Property> parse_animated_property( 0429 Chunk chunk, const PropertyContext& context, std::vector<PropertyValue>&& values 0430 ) 0431 { 0432 auto prop = std::make_unique<Property>(); 0433 parse_animated_property(prop.get(), chunk, context, std::move(values)); 0434 return prop; 0435 } 0436 0437 void parse_animated_property( 0438 Property* prop, Chunk chunk, const PropertyContext& context, std::vector<PropertyValue> values 0439 ) 0440 { 0441 Chunk tdsb, header, value, keyframes, expression, tdpi, tdps, tdli; 0442 tdsb = header = value = keyframes = expression = tdpi = tdps = tdli = nullptr; 0443 chunk->find_multiple( 0444 {&tdsb, &header, &value, &keyframes, &expression, &tdpi, &tdps, &tdli}, 0445 {"tdsb", "tdb4", "cdat", "list", "Utf8", "tdpi", "tdps", "tdli"} 0446 ); 0447 0448 if ( tdsb ) 0449 { 0450 Flags flags(tdsb->data().read_uint32()); 0451 prop->split = flags.get(1, 3); 0452 } 0453 0454 auto data = header->data(); 0455 data.skip(2); 0456 prop->components = data.read_uint16(); 0457 0458 bool position = Flags(data.read_uint16()).get(0, 3); 0459 data.skip(10+8*5); 0460 Flags type = data.read_uint32(); 0461 bool no_value = type.get(2, 0); 0462 bool color = type.get(0, 0); 0463 bool integer = type.get(0, 2); 0464 data.skip(8); 0465 0466 if ( position ) 0467 prop->type = PropertyType::Position; 0468 else if ( color ) 0469 prop->type = PropertyType::Color; 0470 else if ( no_value ) 0471 prop->type = PropertyType::NoValue; 0472 else if ( integer ) 0473 prop->type = PropertyType::Integer; 0474 else 0475 prop->type = PropertyType::MultiDimensional; 0476 0477 prop->animated = data.read_uint8() == 1; 0478 0479 data.skip(6); 0480 0481 prop->is_component = data.read_uint8() == 1; 0482 0483 if ( integer && tdpi ) 0484 { 0485 prop->type = PropertyType::LayerSelection; 0486 LayerSelection val; 0487 val.layer_id = tdpi->data().read_uint32(); 0488 if ( tdps ) 0489 val.layer_source = LayerSource(tdps->data().read_sint32()); 0490 prop->value = val; 0491 } 0492 else if ( integer && tdli ) 0493 { 0494 prop->type = PropertyType::MaskIndex; 0495 prop->value = tdli->data().read_uint32(); 0496 } 0497 else if ( keyframes ) 0498 { 0499 auto raw_keys = list_values(keyframes); 0500 for ( std::size_t i = 0; i < raw_keys.size(); i++ ) { 0501 prop->keyframes.push_back(load_keyframe(i, raw_keys[i], *prop, context, values)); 0502 } 0503 } 0504 else if ( value ) 0505 { 0506 auto vdat = value->data(); 0507 auto raw_value = vdat.read_array(&BinaryReader::read_float64, prop->components); 0508 prop->value = property_value(0, raw_value, values, prop->type); 0509 } 0510 0511 if ( expression ) 0512 prop->expression = to_string(expression); 0513 } 0514 0515 PropertyValue property_value( 0516 int index, 0517 const std::vector<qreal>& raw_value, 0518 std::vector<PropertyValue>& values, 0519 PropertyType type 0520 ) 0521 { 0522 switch ( type ) 0523 { 0524 case PropertyType::NoValue: 0525 if ( index < int(values.size()) ) 0526 return std::move(values[index]); 0527 return nullptr; 0528 case PropertyType::Color: 0529 if ( raw_value.size() < 4 ) 0530 return QColor(); 0531 return QColor(raw_value[1], raw_value[2], raw_value[3], raw_value[0]); 0532 default: 0533 return vector_value(raw_value); 0534 } 0535 } 0536 0537 PropertyValue vector_value(const std::vector<qreal>& raw_value) 0538 { 0539 switch ( raw_value.size() ) 0540 { 0541 case 0: 0542 return nullptr; 0543 case 1: 0544 return raw_value[0]; 0545 case 2: 0546 return QPointF(raw_value[0], raw_value[1]); 0547 case 3: 0548 default: 0549 return QVector3D(raw_value[0], raw_value[1], raw_value[2]); 0550 } 0551 } 0552 0553 Keyframe load_keyframe(int index, BinaryReader& reader, Property& prop, const PropertyContext& context, std::vector<PropertyValue>& values) 0554 { 0555 reader.prepare(); 0556 Keyframe kf; 0557 0558 reader.skip(1); 0559 kf.time = context.time_to_frames(reader.read_uint16()); 0560 reader.skip(2); 0561 0562 kf.transition_type = KeyframeTransitionType(reader.read_uint8()); 0563 0564 kf.label_color = LabelColors(reader.read_uint8()); 0565 0566 Flags flags = reader.read_uint8(); 0567 kf.roving = flags.get(0, 5); 0568 if ( flags.get(0, 3) ) 0569 kf.bezier_mode = KeyframeBezierMode::Continuous; 0570 else if ( flags.get(0, 4) ) 0571 kf.bezier_mode = KeyframeBezierMode::Auto; 0572 else 0573 kf.bezier_mode = KeyframeBezierMode::Normal; 0574 0575 if ( prop.type == PropertyType::NoValue ) 0576 { 0577 reader.skip(16); 0578 kf.in_speed.push_back(reader.read_float64()); 0579 kf.in_influence.push_back(reader.read_float64()); 0580 kf.out_speed.push_back(reader.read_float64()); 0581 kf.out_influence.push_back(reader.read_float64()); 0582 kf.value = std::move(values[index]); 0583 } 0584 else if ( prop.type == PropertyType::MultiDimensional || prop.type == PropertyType::Integer ) 0585 { 0586 kf.value = vector_value(reader.read_array(&BinaryReader::read_float64, prop.components)); 0587 kf.in_speed = reader.read_array(&BinaryReader::read_float64, prop.components); 0588 kf.in_influence = reader.read_array(&BinaryReader::read_float64, prop.components); 0589 kf.out_speed = reader.read_array(&BinaryReader::read_float64, prop.components); 0590 kf.out_influence = reader.read_array(&BinaryReader::read_float64, prop.components); 0591 } 0592 else if ( prop.type == PropertyType::Position ) 0593 { 0594 reader.skip(16); 0595 kf.in_speed.push_back(reader.read_float64()); 0596 kf.in_influence.push_back(reader.read_float64()); 0597 kf.out_speed.push_back(reader.read_float64()); 0598 kf.out_influence.push_back(reader.read_float64()); 0599 kf.value = vector_value(reader.read_array(&BinaryReader::read_float64, prop.components)); 0600 auto it = reader.read_array(&BinaryReader::read_float64, prop.components); 0601 auto ot = reader.read_array(&BinaryReader::read_float64, prop.components); 0602 if ( prop.components >= 2 ) 0603 { 0604 kf.in_tangent = {it[0], it[1]}; 0605 kf.out_tangent = {ot[0], ot[1]}; 0606 } 0607 } 0608 else if ( prop.type == PropertyType::Color ) 0609 { 0610 reader.skip(16); 0611 kf.in_speed.push_back(reader.read_float64()); 0612 kf.in_influence.push_back(reader.read_float64()); 0613 kf.out_speed.push_back(reader.read_float64()); 0614 kf.out_influence.push_back(reader.read_float64()); 0615 auto value = reader.read_array(&BinaryReader::read_float64, prop.components); 0616 kf.value = QColor(value[1], value[2], value[3], value[0]); 0617 } 0618 0619 return kf; 0620 } 0621 0622 template<class T> 0623 std::unique_ptr<Property> parse_animated_with_values( 0624 Chunk chunk, const PropertyContext& context, 0625 const char* container, const char* value_name, 0626 T (AepParser::*parse)(Chunk chunk) 0627 ) 0628 { 0629 Chunk value_container, tdbs; 0630 value_container = tdbs = nullptr; 0631 chunk->find_multiple({&value_container, &tdbs}, {container, "tdbs"}); 0632 std::vector<PropertyValue> values; 0633 for ( const RiffChunk& value_chunk : value_container->find_all(value_name) ) 0634 values.emplace_back((this->*parse)(&value_chunk)); 0635 return parse_animated_property(tdbs, context, std::move(values)); 0636 } 0637 0638 std::vector<BinaryReader> list_values(Chunk list) 0639 { 0640 Chunk head, vals; 0641 head = vals = nullptr; 0642 list->find_multiple({&head, &vals}, {"lhd3", "ldat"}); 0643 if ( !head || !vals ) 0644 { 0645 warning(i18n("Missing list data")); 0646 return {}; 0647 } 0648 0649 auto data = head->data(); 0650 data.skip(10); 0651 std::uint32_t count = data.read_uint16(); 0652 data.skip(6); 0653 std::uint32_t size = data.read_uint16(); 0654 std::uint32_t total_size = count * size; 0655 if ( vals->reader.size() < total_size ) 0656 { 0657 warning(i18n("Not enough data in list")); 0658 return {}; 0659 } 0660 0661 std::vector<BinaryReader> values; 0662 values.reserve(count); 0663 for ( std::uint32_t i = 0; i < count; i++ ) 0664 values.push_back(vals->reader.sub_reader(size, i * size)); 0665 vals->reader.prepare(); 0666 return values; 0667 } 0668 0669 BezierData parse_bezier(Chunk chunk) 0670 { 0671 BezierData data; 0672 auto bounds = chunk->child("shph")->data(); 0673 bounds.skip(3); 0674 data.closed = !Flags(bounds.read_uint8()).get(0, 3); 0675 data.minimum.setX(bounds.read_float32()); 0676 data.minimum.setY(bounds.read_float32()); 0677 data.maximum.setX(bounds.read_float32()); 0678 data.maximum.setY(bounds.read_float32()); 0679 0680 for ( auto& pt : list_values(chunk->child("list")) ) 0681 { 0682 float x = pt.read_float32(); 0683 float y = pt.read_float32(); 0684 data.points.push_back({x, y}); 0685 } 0686 0687 return data; 0688 } 0689 0690 Gradient parse_gradient(Chunk chunk) 0691 { 0692 return parse_gradient_xml(to_string(chunk)); 0693 } 0694 0695 QVector3D parse_orientation(Chunk chunk) 0696 { 0697 auto data = chunk->data(); 0698 QVector3D v; 0699 v.setX(data.read_float64()); 0700 v.setY(data.read_float64()); 0701 v.setZ(data.read_float64()); 0702 return v; 0703 } 0704 0705 Marker parse_marker(Chunk chunk) 0706 { 0707 Marker marker; 0708 marker.name = to_string(chunk->child("Utf8")); 0709 auto data = chunk->child("NmHd")->data(); 0710 data.skip(4); 0711 marker.is_protected = data.read_uint8() & 2; 0712 data.skip(4); 0713 marker.duration = data.read_uint32(); 0714 data.skip(4); 0715 marker.label_color = LabelColors(data.read_uint8()); 0716 return marker; 0717 } 0718 0719 QColor cos_color(const CosValue& cos) 0720 { 0721 const auto& arr = *cos.get<CosValue::Index::Array>(); 0722 if ( arr.size() < 4 ) 0723 throw CosError("Not enough components for color"); 0724 0725 return QColor::fromRgbF( 0726 arr[1].get<CosValue::Index::Number>(), 0727 arr[2].get<CosValue::Index::Number>(), 0728 arr[3].get<CosValue::Index::Number>(), 0729 arr[0].get<CosValue::Index::Number>() 0730 ); 0731 } 0732 0733 TextDocument parse_text_document(const CosValue& cos) 0734 { 0735 TextDocument doc; 0736 doc.text = get_as<CosValue::Index::String>(cos, 0, 0); 0737 0738 for ( const auto& cs : *get_as<CosValue::Index::Array>(cos, 0, 5, 0) ) 0739 { 0740 LineStyle style; 0741 style.character_count = get_as<CosValue::Index::Number>(cs, 1); 0742 const auto& data = get(cs, 0, 0, 5); 0743 style.text_justify = TextJustify(get_as<CosValue::Index::Number>(data, 0)); 0744 doc.line_styles.emplace_back(std::move(style)); 0745 } 0746 0747 for ( const auto& cs : *get_as<CosValue::Index::Array>(cos, 0, 6, 0) ) 0748 { 0749 CharacterStyle style; 0750 style.character_count = get_as<CosValue::Index::Number>(cs, 1); 0751 const auto& data = get(cs, 0, 0, 6); 0752 style.font_index = get_as<CosValue::Index::Number>(data, 0); 0753 style.size = get_as<CosValue::Index::Number>(data, 1); 0754 style.faux_bold = get_as<CosValue::Index::Boolean>(data, 2); 0755 style.faux_italic = get_as<CosValue::Index::Boolean>(data, 3); 0756 style.text_transform = TextTransform(get_as<CosValue::Index::Number>(data, 12)); 0757 style.vertical_align = TextVerticalAlign(get_as<CosValue::Index::Number>(data, 13)); 0758 style.fill_color = cos_color(get(data, 53, 0, 1)); 0759 style.stroke_color = cos_color(get(data, 54, 0, 1)); 0760 style.stroke_enabled = get_as<CosValue::Index::Boolean>(data, 57); 0761 style.stroke_over_fill = get_as<CosValue::Index::Boolean>(data, 58); 0762 style.stroke_width = get_as<CosValue::Index::Number>(data, 63); 0763 doc.character_styles.emplace_back(std::move(style)); 0764 } 0765 0766 return doc; 0767 } 0768 0769 std::unique_ptr<PropertyBase> parse_animated_text(Chunk chunk, const PropertyContext& context) 0770 { 0771 Chunk text_data, tdbs; 0772 text_data = tdbs = nullptr; 0773 chunk->find_multiple({&text_data, &tdbs}, {"btdk", "tdbs"}); 0774 try { 0775 auto val = CosParser(text_data->data().read()).parse(); 0776 if ( val.type() != CosValue::Index::Object ) 0777 throw CosError("Expected Object"); 0778 0779 auto property = std::make_unique<TextProperty>(); 0780 for ( const auto& font : *get_as<CosValue::Index::Array>(val, 0, 1, 0) ) 0781 property->fonts.push_back({get_as<CosValue::Index::String>(font, 0, 0, 0)}); 0782 0783 std::vector<PropertyValue> values; 0784 for ( const auto& doc : *get_as<CosValue::Index::Array>(val, 1, 1) ) 0785 values.push_back(parse_text_document(doc)); 0786 0787 parse_animated_property(&property->documents, tdbs, context, std::move(values)); 0788 return property; 0789 0790 } catch ( const CosError& err ) { 0791 warning(i18n("Invalid text document: %1", err.message)); 0792 return {}; 0793 } 0794 } 0795 0796 void parse_effect_definitions(const ChunkRange& range, Project& project) 0797 { 0798 for ( const auto& chunk : range ) 0799 { 0800 Chunk tdmn, sspc; 0801 tdmn = sspc = nullptr; 0802 chunk.find_multiple({&tdmn, &sspc}, {"tdmn", "sspc"}); 0803 if ( !tdmn || !sspc ) 0804 continue; 0805 0806 auto mn = tdmn->data().read_utf8_nul(); 0807 EffectDefinition& effect = project.effects[mn]; 0808 effect.match_name = mn; 0809 0810 Chunk fnam, part; 0811 fnam = part = nullptr; 0812 chunk.find_multiple({&fnam, &part}, {"fnam", "parT"}); 0813 if ( fnam ) 0814 effect.name = to_string(fnam->child("Utf8")); 0815 0816 QString param_mn; 0817 for ( const auto& param_chunk : part->children ) 0818 { 0819 if ( *param_chunk == "tdmn" ) 0820 { 0821 param_mn = param_chunk->data().read_utf8_nul(); 0822 } 0823 else 0824 { 0825 auto& param = effect.parameter_map[param_mn]; 0826 param.match_name = param_mn; 0827 effect.parameters.push_back(¶m); 0828 parse_effect_parameter(param, param_chunk->data()); 0829 } 0830 } 0831 } 0832 } 0833 0834 void parse_effect_parameter(EffectParameter& param, BinaryReader data) 0835 { 0836 data.skip(15); 0837 param.type = EffectParameterType(data.read_uint8()); 0838 param.name = data.read_utf8_nul(32); 0839 data.skip(8); 0840 0841 switch ( param.type ) 0842 { 0843 case EffectParameterType::Layer: 0844 param.last_value = LayerSelection(); 0845 param.default_value = LayerSelection(); 0846 break; 0847 case EffectParameterType::Scalar: 0848 case EffectParameterType::Angle: 0849 param.last_value = data.read_sint32() / 0x10000; 0850 param.default_value = 0; 0851 break; 0852 case EffectParameterType::Boolean: 0853 param.last_value = data.read_uint32(); 0854 param.default_value = data.read_uint8(); 0855 break; 0856 case EffectParameterType::Color: 0857 { 0858 auto a = data.read_uint8(); 0859 auto r = data.read_uint8(); 0860 auto g = data.read_uint8(); 0861 auto b = data.read_uint8(); 0862 param.last_value = QColor(r, g, b, a); 0863 data.skip(1); 0864 a = 255; 0865 r = data.read_uint8(); 0866 g = data.read_uint8(); 0867 b = data.read_uint8(); 0868 param.default_value = QColor(r, g, b, a); 0869 break; 0870 } 0871 case EffectParameterType::Vector2D: 0872 { 0873 qreal x = data.read_sint32(); 0874 qreal y = data.read_sint32(); 0875 param.last_value = QPointF(x / 0x80, y / 0x80); 0876 param.default_value = QPointF(); 0877 break; 0878 } 0879 case EffectParameterType::Enum: 0880 param.last_value = data.read_uint32(); 0881 data.skip(2); // Number of enum values 0882 param.default_value = data.read_uint16(); 0883 break; 0884 case EffectParameterType::Slider: 0885 param.last_value = data.read_float64(); 0886 param.default_value = 0; 0887 break; 0888 case EffectParameterType::Vector3D: 0889 { 0890 auto x3 = data.read_float64() * 512; 0891 auto y3 = data.read_float64() * 512; 0892 auto z3 = data.read_float64() * 512; 0893 param.last_value = QVector3D(x3, y3, z3); 0894 param.default_value = QVector3D(0, 0, 0); 0895 break; 0896 } 0897 default: 0898 param.last_value = 0; 0899 param.default_value = 0; 0900 break; 0901 } 0902 0903 } 0904 0905 std::unique_ptr<EffectInstance> parse_effect_instance(Chunk chunk, const PropertyContext& context) 0906 { 0907 if ( !load_unecessary ) 0908 return {}; 0909 auto effect = std::make_unique<EffectInstance>(); 0910 Chunk fnam, tdgp; 0911 fnam = tdgp = nullptr; 0912 chunk->find_multiple({&fnam, &tdgp}, {"fnam", "tdgp"}); 0913 if ( fnam ) 0914 effect->name = to_string(fnam->child("Utf8")); 0915 parse_property_group(tdgp, effect->parameters, context); 0916 return effect; 0917 } 0918 0919 static constexpr const char* const placeholder = "-_0_/-"; 0920 std::unordered_map<Id, Chunk> comp_chunks; 0921 // For loading into the object model stuff Glaxnimate doesn't support 0922 // I'm adding the code to load them just in case someone wants to do 0923 // something else with them and so that if Glaxnimate ever supports given 0924 // features, it's easier to add 0925 const bool load_unecessary = false; 0926 ImportExport* io; 0927 }; 0928 0929 } // namespace glaxnimate::io::aep 0930 0931