Files
OmaPhoto/src/IO/ProjectStore.h
T

106 lines
3.9 KiB
C++

#pragma once
#include "Document/Guides.h"
#include "Document/LayerAdjustment.h"
#include "Document/LayerAppearance.h"
#include "Document/LayerEffects.h"
#include "Document/LayerTransform.h"
#include "Document/ShapeTool.h"
#include "Document/TypeTool.h"
#include "IO/ImageImporter.h"
#include <QByteArray>
#include <QString>
#include <QUuid>
#include <map>
#include <optional>
#include <stdexcept>
#include <utility>
#include <vector>
struct LayerMask;
// Absent optionals stay out of the manifest.
struct ProjectLayerRecord {
QUuid id;
QString name;
bool isVisible;
LayerTransform transform;
std::optional<QString> imageFile;
std::optional<QUuid> parentID = std::nullopt;
std::optional<bool> isGroup = std::nullopt;
std::optional<double> opacity = std::nullopt;
std::optional<LayerBlendMode> blendMode = std::nullopt;
std::optional<QString> maskFile = std::nullopt;
std::optional<bool> maskEnabled = std::nullopt;
std::optional<QUuid> maskSourceID = std::nullopt;
std::optional<LayerAdjustment> adjustment = std::nullopt;
std::optional<LayerTransform> maskPlacement = std::nullopt;
// Absent in older projects, which means linked.
std::optional<bool> maskLinked = std::nullopt;
// A shape layer's shape; older versions keep the pixels alone.
std::optional<LayerShapeStyle> shape = std::nullopt;
// What the layer draws round itself; its pixels stay.
std::optional<LayerEffects> effects = std::nullopt;
std::optional<LayerTextStyle> text = std::nullopt;
// Drawn opacity, folders included; defined in LayerGroups.cpp.
double effectiveOpacity(const std::map<QUuid, ProjectLayerRecord> &byID) const;
};
// A UUID as Swift writes it: upper case, no braces.
QString uuidString(const QUuid &id);
struct ProjectManifest {
// Swift's current and supported: what saves write, what loads accept.
static constexpr qint64 current = 10;
static constexpr std::pair<qint64, qint64> supported{1, current};
static bool supports(qint64 version) { return version >= supported.first && version <= supported.second; }
QString format = QStringLiteral("com.compositor.project");
// Swift's Int: a wild number must reach validation whole.
qint64 version = current;
QString colorSpace = QStringLiteral("sRGB");
// Version-1 projects carry none: 72 pixels per inch.
std::optional<double> resolution = std::nullopt;
QUuid documentID;
qint64 width;
qint64 height;
std::optional<QUuid> activeLayerID;
std::vector<ProjectLayerRecord> layers;
// Alignment guides; versions 1 to 7 carry none.
std::optional<std::vector<CanvasGuide>> guides = std::nullopt;
// Pretty JSON with sorted keys; absent optionals are left out.
QByteArray encoded() const;
// Throws ProjectError(invalid) on anything Swift's decoder refuses.
static ProjectManifest decoded(const QByteArray &data);
// Format and version alone: newer files may not decode.
static std::pair<QString, qint64> header(const QByteArray &data);
};
struct ProjectSnapshot {
ProjectManifest manifest;
std::map<QUuid, ImportedImage> images;
std::map<QUuid, ImportedImage> masks = {};
// A record's mask, from its mask file and fields.
std::optional<LayerMask> mask(const ProjectLayerRecord &layer) const;
};
class ProjectError : public std::runtime_error {
public:
enum class Kind { invalid, version, missingImage, tooLarge, encode };
explicit ProjectError(Kind kind);
static ProjectError unsupportedVersion(qint64 version);
const Kind kind;
// Set only for Kind::version.
const std::optional<qint64> version;
private:
ProjectError(Kind kind, std::optional<qint64> version, const QString &description);
};
// A .comp project: a directory holding manifest.json and images/.
namespace ProjectStore {
// Replaces the project in one step or leaves it untouched.
void save(const ProjectSnapshot &snapshot, const QString &path);
ProjectSnapshot load(const QString &path);
}