#include "persistence/EntityPayloadBinary.h" #include "persistence/PersistentBinaryObject.h" #include "domain/Project.h" #include #include #include #include namespace core { namespace { void sortByFrame(QVector& v) { std::sort(v.begin(), v.end(), [](const auto& a, const auto& b) { return a.frame < b.frame; }); } void sortByFrame(QVector& v) { std::sort(v.begin(), v.end(), [](const auto& a, const auto& b) { return a.frame < b.frame; }); } void sortByFrame(QVector& v) { std::sort(v.begin(), v.end(), [](const auto& a, const auto& b) { return a.frame < b.frame; }); } void sortByFrame(QVector& v) { std::sort(v.begin(), v.end(), [](const auto& a, const auto& b) { return a.frame < b.frame; }); } bool readAnimationBlock(QDataStream& ds, Project::Entity& out, bool hasUserScaleKeys) { out.locationKeys.clear(); out.depthScaleKeys.clear(); out.userScaleKeys.clear(); out.imageFrames.clear(); qint32 nLoc = 0; ds >> nLoc; if (ds.status() != QDataStream::Ok || nLoc < 0 || nLoc > 1000000) { return false; } out.locationKeys.reserve(nLoc); for (qint32 i = 0; i < nLoc; ++i) { qint32 frame = 0; double x = 0.0; double y = 0.0; ds >> frame >> x >> y; if (ds.status() != QDataStream::Ok) { return false; } out.locationKeys.push_back(Project::Entity::KeyframeVec2{frame, QPointF(x, y)}); } qint32 nDepth = 0; ds >> nDepth; if (ds.status() != QDataStream::Ok || nDepth < 0 || nDepth > 1000000) { return false; } out.depthScaleKeys.reserve(nDepth); for (qint32 i = 0; i < nDepth; ++i) { qint32 frame = 0; double v = 0.5; ds >> frame >> v; if (ds.status() != QDataStream::Ok) { return false; } out.depthScaleKeys.push_back(Project::Entity::KeyframeFloat01{frame, v}); } if (hasUserScaleKeys) { qint32 nUser = 0; ds >> nUser; if (ds.status() != QDataStream::Ok || nUser < 0 || nUser > 1000000) { return false; } out.userScaleKeys.reserve(nUser); for (qint32 i = 0; i < nUser; ++i) { qint32 frame = 0; double v = 1.0; ds >> frame >> v; if (ds.status() != QDataStream::Ok) { return false; } out.userScaleKeys.push_back(Project::Entity::KeyframeDouble{frame, v}); } } qint32 nImg = 0; ds >> nImg; if (ds.status() != QDataStream::Ok || nImg < 0 || nImg > 1000000) { return false; } out.imageFrames.reserve(nImg); for (qint32 i = 0; i < nImg; ++i) { qint32 frame = 0; QString path; ds >> frame >> path; if (ds.status() != QDataStream::Ok) { return false; } if (!path.isEmpty()) { out.imageFrames.push_back(Project::Entity::ImageFrame{frame, path}); } } sortByFrame(out.locationKeys); sortByFrame(out.depthScaleKeys); sortByFrame(out.userScaleKeys); sortByFrame(out.imageFrames); return true; } void writeAnimationBlock(QDataStream& ds, const Project::Entity& entity, bool writeUserScaleKeys) { ds << qint32(entity.locationKeys.size()); for (const auto& k : entity.locationKeys) { ds << qint32(k.frame) << double(k.value.x()) << double(k.value.y()); } ds << qint32(entity.depthScaleKeys.size()); for (const auto& k : entity.depthScaleKeys) { ds << qint32(k.frame) << double(k.value); } if (writeUserScaleKeys) { ds << qint32(entity.userScaleKeys.size()); for (const auto& k : entity.userScaleKeys) { ds << qint32(k.frame) << double(k.value); } } ds << qint32(entity.imageFrames.size()); for (const auto& k : entity.imageFrames) { ds << qint32(k.frame) << k.imagePath; } } bool readEntityPayloadV1(QDataStream& ds, Project::Entity& tmp, bool hasUserScaleKeys) { ds >> tmp.id; qint32 depth = 0; ds >> depth; tmp.depth = static_cast(depth); ds >> tmp.imagePath; double ox = 0.0; double oy = 0.0; double itlx = 0.0; double itly = 0.0; ds >> ox >> oy >> itlx >> itly; tmp.originWorld = QPointF(ox, oy); tmp.imageTopLeftWorld = QPointF(itlx, itly); qint32 nLocal = 0; ds >> nLocal; if (ds.status() != QDataStream::Ok || nLocal < 0 || nLocal > 1000000) { return false; } tmp.polygonLocal.reserve(nLocal); for (qint32 i = 0; i < nLocal; ++i) { double x = 0.0; double y = 0.0; ds >> x >> y; if (ds.status() != QDataStream::Ok) { return false; } tmp.polygonLocal.push_back(QPointF(x, y)); } qint32 nCut = 0; ds >> nCut; if (ds.status() != QDataStream::Ok || nCut < 0 || nCut > 1000000) { return false; } tmp.cutoutPolygonWorld.reserve(nCut); for (qint32 i = 0; i < nCut; ++i) { double x = 0.0; double y = 0.0; ds >> x >> y; if (ds.status() != QDataStream::Ok) { return false; } tmp.cutoutPolygonWorld.push_back(QPointF(x, y)); } if (!readAnimationBlock(ds, tmp, hasUserScaleKeys)) { return false; } if (tmp.id.isEmpty() || tmp.polygonLocal.isEmpty()) { return false; } return true; } class EntityBinaryRecord final : public PersistentBinaryObject { public: explicit EntityBinaryRecord(const Project::Entity& e) : m_src(&e), m_dst(nullptr) {} explicit EntityBinaryRecord(Project::Entity& e) : m_src(nullptr), m_dst(&e) {} quint32 recordMagic() const override { return EntityPayloadBinary::kMagicPayload; } quint32 recordFormatVersion() const override { return EntityPayloadBinary::kPayloadVersion; } void writeBody(QDataStream& ds) const override { Q_ASSERT(m_src != nullptr); const Project::Entity& entity = *m_src; ds << entity.id; ds << qint32(entity.depth); ds << entity.imagePath; ds << double(entity.originWorld.x()) << double(entity.originWorld.y()); ds << double(entity.imageTopLeftWorld.x()) << double(entity.imageTopLeftWorld.y()); ds << qint32(entity.polygonLocal.size()); for (const auto& pt : entity.polygonLocal) { ds << double(pt.x()) << double(pt.y()); } ds << qint32(entity.cutoutPolygonWorld.size()); for (const auto& pt : entity.cutoutPolygonWorld) { ds << double(pt.x()) << double(pt.y()); } writeAnimationBlock(ds, entity, true); ds << entity.displayName << double(entity.userScale); } bool readBody(QDataStream& ds) override { Q_ASSERT(m_dst != nullptr); Project::Entity tmp; if (!readEntityPayloadV1(ds, tmp, true)) { return false; } QString dn; double us = 1.0; ds >> dn >> us; if (ds.status() != QDataStream::Ok) { return false; } tmp.displayName = dn; tmp.userScale = std::clamp(us, 1e-3, 1e3); *m_dst = std::move(tmp); return true; } private: const Project::Entity* m_src; Project::Entity* m_dst; }; class LegacyAnimSidecarRecord final : public PersistentBinaryObject { public: explicit LegacyAnimSidecarRecord(Project::Entity& e) : m_entity(&e) {} quint32 recordMagic() const override { return EntityPayloadBinary::kMagicLegacyAnim; } quint32 recordFormatVersion() const override { return EntityPayloadBinary::kLegacyAnimVersion; } void writeBody(QDataStream& ds) const override { Q_UNUSED(ds); } bool readBody(QDataStream& ds) override { Project::Entity tmp = *m_entity; if (!readAnimationBlock(ds, tmp, false)) { return false; } m_entity->locationKeys = std::move(tmp.locationKeys); m_entity->depthScaleKeys = std::move(tmp.depthScaleKeys); m_entity->userScaleKeys = std::move(tmp.userScaleKeys); m_entity->imageFrames = std::move(tmp.imageFrames); return true; } private: Project::Entity* m_entity; }; } // namespace bool EntityPayloadBinary::save(const QString& absolutePath, const Project::Entity& entity) { if (absolutePath.isEmpty() || entity.id.isEmpty()) { return false; } return EntityBinaryRecord(entity).saveToFile(absolutePath); } bool EntityPayloadBinary::load(const QString& absolutePath, Project::Entity& entity) { QFile f(absolutePath); if (!f.open(QIODevice::ReadOnly)) { return false; } QDataStream ds(&f); ds.setVersion(QDataStream::Qt_5_15); quint32 magic = 0; quint32 ver = 0; ds >> magic >> ver; if (ds.status() != QDataStream::Ok || magic != kMagicPayload) { return false; } if (ver != 1 && ver != 2 && ver != 3) { return false; } Project::Entity tmp; if (!readEntityPayloadV1(ds, tmp, ver >= 3)) { return false; } if (ver >= 2) { QString dn; double us = 1.0; ds >> dn >> us; if (ds.status() != QDataStream::Ok) { return false; } tmp.displayName = dn; tmp.userScale = std::clamp(us, 1e-3, 1e3); } else { tmp.displayName.clear(); tmp.userScale = 1.0; } entity = std::move(tmp); return true; } bool EntityPayloadBinary::loadLegacyAnimFile(const QString& absolutePath, Project::Entity& entity) { return LegacyAnimSidecarRecord(entity).loadFromFile(absolutePath); } } // namespace core