#pragma once #include #include #include #include #include #include #include #include #include #include #include // Tiny DNG files: an RGGB mosaic, a linear sRGB camera. namespace DNGFixture { struct Options { int width = 64; int height = 48; // A photosite's value: 0 red, 1 green, 2 blue. std::function value = [](int, int, int) { return quint16(20000); }; std::array asShotNeutral{1, 1, 1}; quint16 orientation = 1; // A header's size past the pixels written, for budgets. std::optional claimed; // Row-major: the camera's channels from sRGB's. std::array mix{1, 0, 0, 0, 1, 0, 0, 0, 1}; // A fourth plane, the second green, as CMYG sensors have. bool fourColours = false; }; // XYZ (D65) to linear sRGB: the camera sees sRGB. inline constexpr std::array colorMatrix{3.2404542, -1.5371385, -0.4985314, -0.9692660, 1.8760108, 0.0415560, 0.0556434, -0.2040259, 1.0572252}; struct Entry { quint16 tag; quint16 type; quint32 count; QByteArray data; }; inline QByteArray le16(quint16 value) { QByteArray bytes(2, '\0'); qToLittleEndian(value, bytes.data()); return bytes; } inline QByteArray le32(quint32 value) { QByteArray bytes(4, '\0'); qToLittleEndian(value, bytes.data()); return bytes; } // Ten-thousandths; a signed numerator keeps its bits. inline QByteArray rational(double value) { return le32(quint32(qint32(std::lround(value * 10000)))) + le32(10000); } inline QByteArray data(const Options &options) { QByteArray pixels; for (int y = 0; y < options.height; ++y) for (int x = 0; x < options.width; ++x) { // RGGB: red on even rows and columns. const int channel = y % 2 == 0 ? (x % 2 == 0 ? 0 : 1) : (x % 2 == 0 ? 1 : 2); pixels += le16(options.value(channel, x, y)); } QByteArray matrix, neutral; const int planes = options.fourColours ? 4 : 3; for (int row = 0; row < planes; ++row) for (size_t column = 0; column < 3; ++column) { const size_t from = size_t(row == 3 ? 1 : row) * 3; matrix += rational(options.mix[from] * colorMatrix[column] + options.mix[from + 1] * colorMatrix[3 + column] + options.mix[from + 2] * colorMatrix[6 + column]); } for (int plane = 0; plane < planes; ++plane) neutral += rational(options.asShotNeutral[size_t(plane == 3 ? 1 : plane)]); const QByteArray model("OmaPhoto Fixture", 17); std::vector entries{ {254, 4, 1, le32(0)}, {256, 4, 1, le32(quint32(options.claimed.value_or(QSize(options.width, options.height)).width()))}, {257, 4, 1, le32(quint32(options.claimed.value_or(QSize(options.width, options.height)).height()))}, {258, 3, 1, le16(16) + le16(0)}, {259, 3, 1, le16(1) + le16(0)}, {262, 3, 1, le16(32803) + le16(0)}, {271, 2, 9, QByteArray("OmaPhoto", 9)}, {272, 2, 8, QByteArray("Fixture", 8)}, {273, 4, 1, QByteArray()}, {274, 3, 1, le16(options.orientation) + le16(0)}, {277, 3, 1, le16(1) + le16(0)}, {278, 4, 1, le32(quint32(options.height))}, {279, 4, 1, le32(quint32(pixels.size()))}, {284, 3, 1, le16(1) + le16(0)}, {33421, 3, 2, le16(2) + le16(2)}, {33422, 1, 4, options.fourColours ? QByteArray("\0\1\3\2", 4) : QByteArray("\0\1\1\2", 4)}, {50706, 1, 4, QByteArray("\1\4\0\0", 4)}, {50707, 1, 4, QByteArray("\1\1\0\0", 4)}, {50708, 2, quint32(model.size()), model}, {50710, 1, quint32(planes), QByteArray("\0\1\2\3", planes)}, {50714, 4, 1, le32(0)}, {50717, 4, 1, le32(65535)}, {50721, 10, quint32(planes * 3), matrix}, {50728, 5, quint32(planes), neutral}, {50778, 3, 1, le16(21) + le16(0)}, }; // Header, then the directory, its outside values, then the pixels. const quint32 directory = 8, outside = directory + 2 + quint32(entries.size()) * 12 + 4; QByteArray values; for (Entry &entry : entries) if (entry.data.size() > 4) { if (values.size() % 2) values += '\0'; const QByteArray stored = entry.data; entry.data = le32(outside + quint32(values.size())); values += stored; } if (values.size() % 2) values += '\0'; const quint32 strip = outside + quint32(values.size()); QByteArray file = QByteArray("II*\0", 4) + le32(directory) + le16(quint16(entries.size())); for (const Entry &entry : entries) { const QByteArray inline_ = entry.tag == 273 ? le32(strip) : entry.data.leftJustified(4, '\0'); file += le16(entry.tag) + le16(entry.type) + le32(entry.count) + inline_; } file += le32(0) + values + pixels; return file; } inline QString write(const QString &path, const Options &options = {}) { QFile file(path); if (!file.open(QIODevice::WriteOnly) || file.write(data(options)) <= 0) throw std::runtime_error("could not write a DNG"); return path; } }