Files
OmaPhoto/tests/PSDVectorBuilders.h

111 lines
3.4 KiB
C++

#pragma once
#include "PSDFixture.h"
#include <bit>
// Photoshop's vector extras built byte by byte, as Swift's.
namespace PSDVectorBuilders {
inline void appendDouble(QByteArray &data, double value)
{
PSDFixture::Buffer bits;
const quint64 raw = std::bit_cast<quint64>(value);
bits.u32(quint32(raw >> 32));
bits.u32(quint32(raw));
data.append(bits.data);
}
inline void appendUnit(QByteArray &data, const char *key, double value)
{
data.append(key);
data.append("UntF#Pxl");
appendDouble(data, value);
}
inline QByteArray originationData(quint32 type, const QRectF &rect, const std::vector<double> &radii = {})
{
QByteArray data("keyOriginTypelong");
PSDFixture::Buffer kind;
kind.u32(type);
data.append(kind.data);
data.append("keyOriginShapeBBox");
appendUnit(data, "Left", rect.left());
appendUnit(data, "Top ", rect.top());
appendUnit(data, "Rght", rect.left() + rect.width());
appendUnit(data, "Btom", rect.top() + rect.height());
if (radii.size() == 4) {
data.append("keyOriginRRectRadii");
const char *keys[] = {"topLeft", "topRight", "bottomRight", "bottomLeft"};
for (size_t index = 0; index < 4; ++index)
appendUnit(data, keys[index], radii[index]);
}
return data;
}
struct Subpath {
std::vector<QPointF> corners;
bool closed = true;
// The knot count the record declares, else the corners'.
std::optional<int> count = std::nullopt;
// Handles this far either side of each anchor.
QPointF handle = QPointF();
};
// Subpaths of knots, in Photoshop's fractions of the canvas.
inline QByteArray vectorMask(QSizeF canvas, const std::vector<Subpath> &subpaths)
{
PSDFixture::Buffer data;
data.u32(3);
data.u32(0);
data.i16(6);
data.bytes(QByteArray(24, '\0'));
data.i16(8);
data.bytes(QByteArray(24, '\0'));
const auto point = [&](QPointF at) {
data.u32(quint32(qint32(at.y() / canvas.height() * 0x1000000)));
data.u32(quint32(qint32(at.x() / canvas.width() * 0x1000000)));
};
for (const Subpath &subpath : subpaths) {
data.i16(subpath.closed ? 0 : 3);
data.i16(qint16(subpath.count.value_or(int(subpath.corners.size()))));
data.bytes(QByteArray(22, '\0'));
for (const QPointF corner : subpath.corners) {
data.i16(1);
point(corner - subpath.handle);
point(corner);
point(corner + subpath.handle);
}
}
return data.data;
}
inline QByteArray vectorMask(QSizeF canvas, const std::vector<QPointF> &corners)
{
return vectorMask(canvas, {Subpath{corners}});
}
inline QByteArray colorDescriptor(double red, double green, double blue)
{
QByteArray data("RGBC");
for (const auto &[key, value] : {std::pair("Rd ", red), {"Grn ", green}, {"Bl ", blue}}) {
data.append(key);
data.append("doub");
appendDouble(data, value);
}
return data;
}
inline QByteArray strokeStyle(bool fill, bool stroke, double width, double red, double green, double blue)
{
QByteArray data;
for (const auto &[key, value] : {std::pair("strokeEnabled", stroke), {"fillEnabled", fill}}) {
data.append(key);
data.append("bool");
data.append(char(value ? 1 : 0));
}
data.append("strokeStyleLineWidthUntF#Pxl");
appendDouble(data, width);
data.append(colorDescriptor(red, green, blue));
return data;
}
}