summaryrefslogtreecommitdiff
path: root/src/helpers/cm/ColorManagement.hpp
blob: c8d1cfcba66bf82a149974e7073b36a0806a857c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
#pragma once

#include "color-management-v1.hpp"
#include <format>
#include <hyprgraphics/color/Color.hpp>
#include "../memory/Memory.hpp"
#include "../math/Math.hpp"
#include "../Color.hpp"
#include "../../debug/log/Logger.hpp"

#include <filesystem>
#include <string>
#include <vector>
#include <expected>

#define SDR_MIN_LUMINANCE 0.2f
#define SDR_MAX_LUMINANCE 80.0f
#define SDR_REF_LUMINANCE 80.0f
#define HDR_MIN_LUMINANCE 0.005f
#define HDR_MAX_LUMINANCE 10000.0f
#define HDR_REF_LUMINANCE 203.0f
#define HLG_MAX_LUMINANCE 1000.0f

namespace Render {
    class ITexture;
}

namespace NColorManagement {
    enum eNoShader : uint8_t {
        CM_NS_DISABLE  = 0,
        CM_NS_ALWAYS   = 1,
        CM_NS_ONDEMAND = 2,
        CM_NS_IGNORE   = 3,
    };

    enum ePrimaries : uint8_t {
        CM_PRIMARIES_SRGB         = 1,
        CM_PRIMARIES_PAL_M        = 2,
        CM_PRIMARIES_PAL          = 3,
        CM_PRIMARIES_NTSC         = 4,
        CM_PRIMARIES_GENERIC_FILM = 5,
        CM_PRIMARIES_BT2020       = 6,
        CM_PRIMARIES_CIE1931_XYZ  = 7,
        CM_PRIMARIES_DCI_P3       = 8,
        CM_PRIMARIES_DISPLAY_P3   = 9,
        CM_PRIMARIES_ADOBE_RGB    = 10,
    };

    enum eTransferFunction : uint8_t {
        CM_TRANSFER_FUNCTION_LINEAR     = 0,
        CM_TRANSFER_FUNCTION_BT1886     = 1,
        CM_TRANSFER_FUNCTION_GAMMA22    = 2,
        CM_TRANSFER_FUNCTION_GAMMA28    = 3,
        CM_TRANSFER_FUNCTION_ST240      = 4,
        CM_TRANSFER_FUNCTION_EXT_LINEAR = 5,
        CM_TRANSFER_FUNCTION_LOG_100    = 6,
        CM_TRANSFER_FUNCTION_LOG_316    = 7,
        CM_TRANSFER_FUNCTION_XVYCC      = 8,
        CM_TRANSFER_FUNCTION_SRGB       = 9,
        CM_TRANSFER_FUNCTION_EXT_SRGB   = 10,
        CM_TRANSFER_FUNCTION_ST2084_PQ  = 11,
        CM_TRANSFER_FUNCTION_ST428      = 12,
        CM_TRANSFER_FUNCTION_HLG        = 13,
    };

    // NOTE should be ok this way. unsupported primaries/tfs must be rejected earlier. supported enum values should be in sync with proto.
    // might need a proper switch-case and additional INVALID enum value.
    inline wpColorManagerV1Primaries convertPrimaries(ePrimaries primaries) {
        return sc<wpColorManagerV1Primaries>(primaries);
    }
    inline ePrimaries convertPrimaries(wpColorManagerV1Primaries primaries) {
        return sc<ePrimaries>(primaries);
    }
    inline wpColorManagerV1TransferFunction convertTransferFunction(eTransferFunction tf, bool useV1SRGB = true) {
        switch (tf) {
            case CM_TRANSFER_FUNCTION_LINEAR:
                Log::logger->log(Log::TRACE,
                                 "CM_TRANSFER_FUNCTION_LINEAR is internal and buffers with this TF shouldn't go outside. Returning "
                                 "WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR for preferred description instead");
                return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR;
            case CM_TRANSFER_FUNCTION_SRGB: return useV1SRGB ? WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB : WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_COMPOUND_POWER_2_4;
            default: return sc<wpColorManagerV1TransferFunction>(tf);
        }
    }
    inline eTransferFunction convertTransferFunction(wpColorManagerV1TransferFunction tf) {
        switch (tf) {
            case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_COMPOUND_POWER_2_4: return CM_TRANSFER_FUNCTION_SRGB;
            default: return sc<eTransferFunction>(tf);
        }
    }
    inline std::string tfToString(eTransferFunction tf) {
        switch (tf) {
            case CM_TRANSFER_FUNCTION_LINEAR: return "TF:INTERNAL LINEAR NOT NORMALISED";
            case CM_TRANSFER_FUNCTION_BT1886: return "TF:BT1886";
            case CM_TRANSFER_FUNCTION_GAMMA22: return "TF:GAMMA22";
            case CM_TRANSFER_FUNCTION_GAMMA28: return "TF:GAMMA28";
            case CM_TRANSFER_FUNCTION_ST240: return "TF:ST240";
            case CM_TRANSFER_FUNCTION_EXT_LINEAR: return "TF:EXT_LINEAR";
            case CM_TRANSFER_FUNCTION_LOG_100: return "TF:LOG_100";
            case CM_TRANSFER_FUNCTION_LOG_316: return "TF:LOG_316";
            case CM_TRANSFER_FUNCTION_XVYCC: return "TF:XVYCC";
            case CM_TRANSFER_FUNCTION_SRGB: return "TF:SRGB";
            case CM_TRANSFER_FUNCTION_EXT_SRGB: return "TF:EXT_SRGB";
            case CM_TRANSFER_FUNCTION_ST2084_PQ: return "TF:ST2084_PQ";
            case CM_TRANSFER_FUNCTION_ST428: return "TF:ST428";
            case CM_TRANSFER_FUNCTION_HLG: return "TF:HLG";
            default: return "TF:ERROR";
        }
    }

    using SPCPRimaries = Hyprgraphics::SPCPRimaries;

    namespace NColorPrimaries {

        static const auto BT709 = SPCPRimaries{
            .red   = {.x = 0.64, .y = 0.33},
            .green = {.x = 0.30, .y = 0.60},
            .blue  = {.x = 0.15, .y = 0.06},
            .white = {.x = 0.3127, .y = 0.3290},
        };

        static const auto DEFAULT_PRIMARIES = BT709;

        static const auto PAL_M = SPCPRimaries{
            .red   = {.x = 0.67, .y = 0.33},
            .green = {.x = 0.21, .y = 0.71},
            .blue  = {.x = 0.14, .y = 0.08},
            .white = {.x = 0.310, .y = 0.316},
        };

        static const auto PAL = SPCPRimaries{
            .red   = {.x = 0.640, .y = 0.330},
            .green = {.x = 0.290, .y = 0.600},
            .blue  = {.x = 0.150, .y = 0.060},
            .white = {.x = 0.3127, .y = 0.3290},
        };

        static const auto NTSC = SPCPRimaries{
            .red   = {.x = 0.630, .y = 0.340},
            .green = {.x = 0.310, .y = 0.595},
            .blue  = {.x = 0.155, .y = 0.070},
            .white = {.x = 0.3127, .y = 0.3290},
        };

        static const auto GENERIC_FILM = SPCPRimaries{
            .red   = {.x = 0.243, .y = 0.692},
            .green = {.x = 0.145, .y = 0.049},
            .blue  = {.x = 0.681, .y = 0.319}, // NOLINT(modernize-use-std-numbers)
            .white = {.x = 0.310, .y = 0.316},
        };

        static const auto BT2020 = SPCPRimaries{
            .red   = {.x = 0.708, .y = 0.292},
            .green = {.x = 0.170, .y = 0.797},
            .blue  = {.x = 0.131, .y = 0.046},
            .white = {.x = 0.3127, .y = 0.3290},
        };

        static const auto CIE1931_XYZ = SPCPRimaries{
            .red   = {.x = 1.0, .y = 0.0},
            .green = {.x = 0.0, .y = 1.0},
            .blue  = {.x = 0.0, .y = 0.0},
            .white = {.x = 1.0 / 3.0, .y = 1.0 / 3.0},
        };

        static const auto DCI_P3 = SPCPRimaries{
            .red   = {.x = 0.680, .y = 0.320},
            .green = {.x = 0.265, .y = 0.690},
            .blue  = {.x = 0.150, .y = 0.060},
            .white = {.x = 0.314, .y = 0.351},
        };

        static const auto DISPLAY_P3 = SPCPRimaries{
            .red   = {.x = 0.680, .y = 0.320},
            .green = {.x = 0.265, .y = 0.690},
            .blue  = {.x = 0.150, .y = 0.060},
            .white = {.x = 0.3127, .y = 0.3290},
        };

        static const auto ADOBE_RGB = SPCPRimaries{
            .red   = {.x = 0.6400, .y = 0.3300},
            .green = {.x = 0.2100, .y = 0.7100},
            .blue  = {.x = 0.1500, .y = 0.0600},
            .white = {.x = 0.3127, .y = 0.3290},
        };
    }

    struct SVCGTTable16 {
        uint16_t                             channels  = 0;
        uint16_t                             entries   = 0;
        uint16_t                             entrySize = 0;
        std::array<std::vector<uint16_t>, 3> ch;
    };

    const SPCPRimaries&   getPrimaries(ePrimaries name);
    std::optional<Mat3x3> rgbToXYZFromPrimaries(SPCPRimaries pr);
    Mat3x3                adaptBradford(Hyprgraphics::CColor::xy srcW, Hyprgraphics::CColor::xy dstW);

    class CPrimaries {
      public:
        static WP<const CPrimaries>   from(const SPCPRimaries& primaries);
        static WP<const CPrimaries>   from(const ePrimaries name);
        static WP<const CPrimaries>   from(const uint primariesId);

        const SPCPRimaries&           value() const;
        uint                          id() const;

        const Hyprgraphics::CMatrix3& toXYZ() const;                                       // toXYZ() * rgb -> xyz
        const Hyprgraphics::CMatrix3& convertMatrix(const WP<const CPrimaries> dst) const; // convertMatrix(dst) * rgb with "this" primaries -> rgb with dst primaries

      private:
        CPrimaries(const SPCPRimaries& primaries, const uint primariesId);
        uint                   m_id;
        SPCPRimaries           m_primaries;

        Hyprgraphics::CMatrix3 m_primaries2XYZ;
    };

    struct SImageDescription {
        static std::expected<SImageDescription, std::string> fromICC(const std::filesystem::path& file);

        //
        std::vector<uint8_t> rawICC;

        eTransferFunction    transferFunction      = CM_TRANSFER_FUNCTION_GAMMA22;
        float                transferFunctionPower = 1.0f;
        bool                 windowsScRGB          = false;

        bool                 primariesNameSet = false;
        ePrimaries           primariesNamed   = CM_PRIMARIES_SRGB;
        // primaries are stored as FP values with the same scale as standard defines (0.0 - 1.0)
        // wayland protocol expects int32_t values multiplied by 1000000
        // drm expects uint16_t values multiplied by 50000
        SPCPRimaries primaries, masteringPrimaries;

        // luminances in cd/m²
        // protos and drm expect min * 10000
        struct SPCLuminances {
            float    min       = 0.2; // 0.2 cd/m²
            uint32_t max       = 80;  // 80 cd/m²
            uint32_t reference = 80;  // 80 cd/m²
            bool     operator==(const SPCLuminances& l2) const {
                return min == l2.min && max == l2.max && reference == l2.reference;
            }
        } luminances;
        struct SPCMasteringLuminances {
            float    min = 0;
            uint32_t max = 0;
            bool     operator==(const SPCMasteringLuminances& l2) const {
                return min == l2.min && max == l2.max;
            }
        } masteringLuminances;

        // Matrix data from ICC
        struct SICCData {
            bool                        present = false;
            size_t                      lutSize = 33;
            std::vector<float>          lutDataPacked;
            SP<Render::ITexture>        lutTexture;
            std::optional<SVCGTTable16> vcgt;
        } icc;

        uint32_t maxCLL  = 0;
        uint32_t maxFALL = 0;

        bool     operator==(const SImageDescription& d2) const {
            if (icc.present || d2.icc.present)
                return false;

            return windowsScRGB == d2.windowsScRGB && transferFunction == d2.transferFunction && transferFunctionPower == d2.transferFunctionPower &&
                (primariesNameSet == d2.primariesNameSet && (primariesNameSet ? primariesNamed == d2.primariesNamed : primaries == d2.primaries)) &&
                masteringPrimaries == d2.masteringPrimaries && luminances == d2.luminances && masteringLuminances == d2.masteringLuminances && maxCLL == d2.maxCLL &&
                maxFALL == d2.maxFALL;
        }

        const SPCPRimaries& getPrimaries() const {
            if (primariesNameSet || primaries == SPCPRimaries{})
                return NColorManagement::getPrimaries(primariesNamed);
            return primaries;
        }

        float getTFMinLuminance(float sdrMinLuminance = -1.0f) const {
            switch (transferFunction) {
                case CM_TRANSFER_FUNCTION_EXT_LINEAR: return 0;
                case CM_TRANSFER_FUNCTION_ST2084_PQ:
                case CM_TRANSFER_FUNCTION_HLG: return HDR_MIN_LUMINANCE;
                case CM_TRANSFER_FUNCTION_BT1886: return 0.01;
                case CM_TRANSFER_FUNCTION_GAMMA22:
                case CM_TRANSFER_FUNCTION_GAMMA28:
                case CM_TRANSFER_FUNCTION_ST240:
                case CM_TRANSFER_FUNCTION_LOG_100:
                case CM_TRANSFER_FUNCTION_LOG_316:
                case CM_TRANSFER_FUNCTION_XVYCC:
                case CM_TRANSFER_FUNCTION_EXT_SRGB:
                case CM_TRANSFER_FUNCTION_ST428:
                case CM_TRANSFER_FUNCTION_SRGB:
                default: return sdrMinLuminance >= 0 ? sdrMinLuminance : SDR_MIN_LUMINANCE;
            }
        };

        float getTFMaxLuminance(int sdrMaxLuminance = -1) const {
            switch (transferFunction) {
                case CM_TRANSFER_FUNCTION_EXT_LINEAR:
                    return SDR_MAX_LUMINANCE; // assume Windows scRGB. white color range 1.0 - 125.0 maps to SDR_MAX_LUMINANCE (80) - HDR_MAX_LUMINANCE (10000)
                case CM_TRANSFER_FUNCTION_ST2084_PQ: return HDR_MAX_LUMINANCE;
                case CM_TRANSFER_FUNCTION_HLG: return HLG_MAX_LUMINANCE;
                case CM_TRANSFER_FUNCTION_BT1886: return 100;
                case CM_TRANSFER_FUNCTION_GAMMA22:
                case CM_TRANSFER_FUNCTION_GAMMA28:
                case CM_TRANSFER_FUNCTION_ST240:
                case CM_TRANSFER_FUNCTION_LOG_100:
                case CM_TRANSFER_FUNCTION_LOG_316:
                case CM_TRANSFER_FUNCTION_XVYCC:
                case CM_TRANSFER_FUNCTION_EXT_SRGB:
                case CM_TRANSFER_FUNCTION_ST428:
                case CM_TRANSFER_FUNCTION_SRGB:
                default: return sdrMaxLuminance >= 0 ? sdrMaxLuminance : SDR_MAX_LUMINANCE;
            }
        };

        float getTFRefLuminance(int sdrRefLuminance = -1) const {
            switch (transferFunction) {
                case CM_TRANSFER_FUNCTION_EXT_LINEAR:
                case CM_TRANSFER_FUNCTION_ST2084_PQ:
                case CM_TRANSFER_FUNCTION_HLG: return HDR_REF_LUMINANCE;
                case CM_TRANSFER_FUNCTION_BT1886: return 100;
                case CM_TRANSFER_FUNCTION_GAMMA22:
                case CM_TRANSFER_FUNCTION_GAMMA28:
                case CM_TRANSFER_FUNCTION_ST240:
                case CM_TRANSFER_FUNCTION_LOG_100:
                case CM_TRANSFER_FUNCTION_LOG_316:
                case CM_TRANSFER_FUNCTION_XVYCC:
                case CM_TRANSFER_FUNCTION_EXT_SRGB:
                case CM_TRANSFER_FUNCTION_ST428:
                case CM_TRANSFER_FUNCTION_SRGB:
                default: return sdrRefLuminance >= 0 ? sdrRefLuminance : SDR_REF_LUMINANCE;
            }
        };
    };

    class CImageDescription {
      public:
        static WP<const CImageDescription> from(const SImageDescription& imageDescription);
        static WP<const CImageDescription> from(const uint64_t imageDescriptionId);

        WP<const CImageDescription>        with(const SImageDescription::SPCLuminances& luminances) const;

        const SImageDescription&           value() const;
        uint64_t                           id() const;

        WP<const CPrimaries>               getPrimaries() const;
        bool                               needsCM(WP<const CImageDescription> target) const;

      private:
        CImageDescription(const SImageDescription& imageDescription, const uint64_t imageDescriptionId);
        uint64_t          m_id          = 0;
        uint              m_primariesId = 0;
        SImageDescription m_imageDescription;
    };

    union RGBAColor {
        struct {
            double r = 0, g = 0, b = 0, a = 0;
        } c;
        double     v[4];

        RGBAColor& operator*=(double value) {
            c.r *= value;
            c.g *= value;
            c.b *= value;
            return *this;
        }

        RGBAColor& operator/=(double value) {
            c.r /= value;
            c.g /= value;
            c.b /= value;
            return *this;
        }
    };

    using PImageDescription = WP<const CImageDescription>;

    RGBAColor         convertColor(RGBAColor color, PImageDescription srcDesc, PImageDescription dstDesc);
    CHyprColor        convertColor(const CHyprColor& color, PImageDescription srcDesc, PImageDescription dstDesc);

    PImageDescription getDefaultImageDescription();

    static const auto DEFAULT_GAMMA22_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{
        .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22,
        .primariesNameSet = true,
        .primariesNamed   = NColorManagement::CM_PRIMARIES_SRGB,
        .primaries        = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_SRGB),
        .luminances       = {.min = SDR_MIN_LUMINANCE, .max = 80, .reference = 80},
    });

    static const auto DEFAULT_SRGB_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{
        .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_SRGB,
        .primariesNameSet = true,
        .primariesNamed   = NColorManagement::CM_PRIMARIES_SRGB,
        .primaries        = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_SRGB),
        .luminances       = {.min = SDR_MIN_LUMINANCE, .max = 80, .reference = 80},
    });

    static const auto DEFAULT_HDR_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{
        .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ,
        .primariesNameSet = true,
        .primariesNamed   = NColorManagement::CM_PRIMARIES_BT2020,
        .primaries        = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020),
        .luminances       = {.min = HDR_MIN_LUMINANCE, .max = 10000, .reference = 203},
    });

    static const auto SCRGB_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{
        .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_EXT_LINEAR,
        .windowsScRGB     = true,
        .primariesNameSet = true,
        .primariesNamed   = NColorManagement::CM_PRIMARIES_SRGB,
        .primaries        = NColorPrimaries::BT709,
        .luminances       = {.reference = 203},
    });

    static const auto LINEAR_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{
        .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_EXT_LINEAR,
        .primariesNameSet = true,
        .primariesNamed   = NColorManagement::CM_PRIMARIES_SRGB,
        .primaries        = NColorPrimaries::BT709,
        .luminances       = {.min = 0, .max = 10000, .reference = 80},
    });

    // For internal use only
    // not normalised to 0.0 - 1.0
    // luminance values should be set to default SDR settings in SDR mode and to output settings in HDR mode
    // keep srgb primaries to avoid conversions for image exports
    static const auto LINEAR_NN_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{
        .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_LINEAR,
        .primariesNameSet = true,
        .primariesNamed   = NColorManagement::CM_PRIMARIES_SRGB,
        .primaries        = NColorPrimaries::BT709,
    });
}

template <typename CharT>
struct std::formatter<Hyprgraphics::SPCPRimaries, CharT> : std::formatter<CharT> {
    template <typename FormatContext>
    auto format(const Hyprgraphics::SPCPRimaries& primaries, FormatContext& ctx) const {
        return std::format_to(ctx.out(), "[r={},{} g={},{} b={},{} w={},{}]", primaries.red.x, primaries.red.y, primaries.green.x, primaries.green.y, primaries.blue.x,
                              primaries.blue.y, primaries.white.x, primaries.white.y);
    }
};

template <typename CharT>
struct std::formatter<NColorManagement::SImageDescription::SPCLuminances, CharT> : std::formatter<CharT> {
    template <typename FormatContext>
    auto format(const NColorManagement::SImageDescription::SPCLuminances& luminances, FormatContext& ctx) const {
        return std::format_to(ctx.out(), "[{}-{}({})]", luminances.min, luminances.max, luminances.reference);
    }
};

template <typename CharT>
struct std::formatter<NColorManagement::SImageDescription, CharT> : std::formatter<CharT> {
    template <typename FormatContext>
    auto format(const NColorManagement::SImageDescription& imageDescription, FormatContext& ctx) const {
        return std::format_to(ctx.out(), "[{}{}, primaries={}, luminances={}]", NColorManagement::tfToString(imageDescription.transferFunction),
                              imageDescription.transferFunctionPower != 1.0f ? std::format("^{}", imageDescription.transferFunctionPower) : "", imageDescription.getPrimaries(),
                              imageDescription.luminances);
    }
};