Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_jpegxl_export_tools.h
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2021 the JPEG XL Project Authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *
5 * SPDX-FileCopyrightText: 2022 L. E. Segovia <amy@amyspark.me>
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 *
8 * SPDX-FileCopyrightText: 2024 Rasyuqa A. H. <qampidh@gmail.com>
9 * SPDX-License-Identifier: GPL-2.0-or-later
10 */
11
12#ifndef KIS_JPEGXL_EXPORT_TOOLS_H
13#define KIS_JPEGXL_EXPORT_TOOLS_H
14
15#include <QByteArray>
16
17#include <KisDocument.h>
19#include <KoColorProfile.h>
21#include <kis_iterator_ng.h>
22#include <kis_types.h>
23
24#include <jxl/encode_cxx.h>
25
26namespace JXLExpTool
27{
28template<typename CSTrait>
29inline QByteArray
30writeCMYKPixels(bool isTrichromatic, int chPos, const int width, const int height, KisHLineConstIteratorSP it)
31{
32 const int channels = isTrichromatic ? 3 : 1;
33 const int chSize = static_cast<int>(CSTrait::pixelSize / 5);
34 const int pxSize = chSize * channels;
35 const int chOffset = chPos * chSize;
36
37 QByteArray res;
38 res.resize(width * height * pxSize);
39
40 quint8 *ptr = reinterpret_cast<quint8 *>(res.data());
41
42 for (int y = 0; y < height; y++) {
43 for (int x = 0; x < width; x++) {
44 const quint8 *src = it->rawDataConst();
45
46 if (isTrichromatic) {
47 for (int i = 0; i < channels; i++) {
48 std::memcpy(ptr, src + (i * chSize), chSize);
49 ptr += chSize;
50 }
51 } else {
52 std::memcpy(ptr, src + chOffset, chSize);
53 ptr += chSize;
54 }
55
56 it->nextPixel();
57 }
58
59 it->nextRow();
60 }
61 return res;
62}
63
64template<typename... Args>
65inline QByteArray writeCMYKLayer(const KoID &id, Args &&...args)
66{
67 if (id == Integer8BitsColorDepthID) {
68 return writeCMYKPixels<KoCmykU8Traits>(std::forward<Args>(args)...);
69 } else if (id == Integer16BitsColorDepthID) {
70 return writeCMYKPixels<KoCmykU16Traits>(std::forward<Args>(args)...);
71#ifdef HAVE_OPENEXR
72 } else if (id == Float16BitsColorDepthID) {
73 return writeCMYKPixels<KoCmykF16Traits>(std::forward<Args>(args)...);
74#endif
75 } else if (id == Float32BitsColorDepthID) {
76 return writeCMYKPixels<KoCmykF32Traits>(std::forward<Args>(args)...);
77 } else {
78 KIS_ASSERT_X(false, "JPEGXLExport::writeLayer", "unsupported bit depth!");
79 return QByteArray();
80 }
81}
82
84 JxlOutputProcessor(QIODevice *io)
85 : outDevice(io)
86 {
87 }
88
89#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 10, 1)
90 JxlEncoderOutputProcessor getOutputProcessor()
91 {
92 return JxlEncoderOutputProcessor{this, getBuffer, releaseBuffer, seek, setFinalizedPosition};
93 }
94#endif
95
96 static void *getBuffer(void *opaque, size_t *size)
97 {
98 JxlOutputProcessor *self = reinterpret_cast<JxlOutputProcessor *>(opaque);
99 *size = std::min<size_t>(*size, 1u << 16);
100 if (static_cast<size_t>(self->output.size()) < *size) {
101 self->output.resize(*size);
102 }
103 return self->output.data();
104 }
105
106 static void releaseBuffer(void *opaque, size_t written_bytes)
107 {
108 JxlOutputProcessor *self = reinterpret_cast<JxlOutputProcessor *>(opaque);
109 if (self->outDevice->isOpen()) {
110 if (static_cast<size_t>(
111 self->outDevice->write(reinterpret_cast<const char *>(self->output.data()), written_bytes))
112 != written_bytes) {
113 warnFile << "Failed to write" << written_bytes << "bytes to output";
114 }
115 } else {
116 warnFile << "ReleaseBuffer failed, file not open";
117 }
118 self->output.clear();
119 }
120
121 static void seek(void *opaque, uint64_t position)
122 {
123 JxlOutputProcessor *self = reinterpret_cast<JxlOutputProcessor *>(opaque);
124 if (self->outDevice->isOpen()) {
125 self->outDevice->seek(position);
126 } else {
127 warnFile << "Seek failed, file not open";
128 }
129 }
130
131 static void setFinalizedPosition(void *opaque, uint64_t finalized_position)
132 {
133 JxlOutputProcessor *self = reinterpret_cast<JxlOutputProcessor *>(opaque);
135 }
136
137 QIODevice *outDevice{nullptr};
138 QByteArray output;
140};
141} // namespace JXLExpTool
142
143namespace HDR
144{
145template<ConversionPolicy policy>
147{
148 if (policy == ConversionPolicy::ApplyPQ) {
150 } else if (policy == ConversionPolicy::ApplyHLG) {
151 return applyHLGCurve(value);
152 } else if (policy == ConversionPolicy::ApplySMPTE428) {
154 }
155 return value;
156}
157
158template<typename CSTrait,
159 bool swap,
160 bool convertToRec2020,
161 bool isLinear,
162 ConversionPolicy conversionPolicy,
163 typename DestTrait,
164 bool removeOOTF>
165inline QByteArray writeLayer(const int width,
166 const int height,
168 float hlgGamma,
169 float hlgNominalPeak,
170 const KoColorSpace *cs)
171{
172 const int channels = static_cast<int>(CSTrait::channels_nb);
173 QVector<float> pixelValues(channels);
174 QVector<qreal> pixelValuesLinear(channels);
175 const KoColorProfile *profile = cs->profile();
176 const QVector<qreal> lCoef = cs->lumaCoefficients();
177 double *src = pixelValuesLinear.data();
178 float *dst = pixelValues.data();
179
180 QByteArray res;
181 res.resize(width * height * static_cast<int>(DestTrait::pixelSize));
182
183 quint8 *ptr = reinterpret_cast<quint8 *>(res.data());
184
185 for (int y = 0; y < height; y++) {
186 for (int x = 0; x < width; x++) {
187 CSTrait::normalisedChannelsValue(it->rawDataConst(), pixelValues);
188 if (!convertToRec2020 && !isLinear) {
189 for (int i = 0; i < channels; i++) {
190 src[i] = static_cast<double>(dst[i]);
191 }
192 profile->linearizeFloatValue(pixelValuesLinear);
193 for (int i = 0; i < channels; i++) {
194 dst[i] = static_cast<float>(src[i]);
195 }
196 }
197
198 if (conversionPolicy == ConversionPolicy::ApplyHLG && removeOOTF) {
199 removeHLGOOTF(dst, lCoef.constData(), hlgGamma, hlgNominalPeak);
200 }
201
202 for (int ch = 0; ch < channels; ch++) {
203 if (ch == CSTrait::alpha_pos) {
204 dst[ch] = applyCurveAsNeeded<ConversionPolicy::KeepTheSame>(
205 dst[ch]);
206 } else {
207 dst[ch] = applyCurveAsNeeded<conversionPolicy>(dst[ch]);
208 }
209 }
210
211 if (swap) {
212 std::swap(dst[0], dst[2]);
213 }
214
215 DestTrait::fromNormalisedChannelsValue(ptr, pixelValues);
216
217 ptr += DestTrait::pixelSize;
218
219 it->nextPixel();
220 }
221
222 it->nextRow();
223 }
224
225 return res;
226}
227
228template<typename CSTrait, bool swap>
229inline QByteArray writeLayerNoConversion(const int width,
230 const int height,
232 float hlgGamma,
233 float hlgNominalPeak,
234 const KoColorSpace *cs)
235{
236 Q_UNUSED(hlgGamma);
237 Q_UNUSED(hlgNominalPeak);
238 Q_UNUSED(cs);
239
240 const int channels = static_cast<int>(CSTrait::channels_nb);
241 QVector<float> pixelValues(channels);
242 QVector<qreal> pixelValuesLinear(channels);
243
244 QByteArray res;
245 res.resize(width * height * static_cast<int>(CSTrait::pixelSize));
246
247 quint8 *ptr = reinterpret_cast<quint8 *>(res.data());
248
249 for (int y = 0; y < height; y++) {
250 for (int x = 0; x < width; x++) {
251 auto *dst = reinterpret_cast<typename CSTrait::channels_type *>(ptr);
252
253 std::memcpy(dst, it->rawDataConst(), CSTrait::pixelSize);
254
255 if (swap) {
256 std::swap(dst[0], dst[2]);
257 }
258
259 ptr += CSTrait::pixelSize;
260
261 it->nextPixel();
262 }
263
264 it->nextRow();
265 }
266
267 return res;
268}
269
270template<typename CSTrait,
271 bool swap,
272 bool convertToRec2020,
273 bool isLinear,
274 ConversionPolicy linearizePolicy,
275 typename DestTrait,
276 bool removeOOTF,
277 typename... Args>
279{
280 if (linearizePolicy != ConversionPolicy::KeepTheSame) {
281 return writeLayer<CSTrait, swap, convertToRec2020, isLinear, linearizePolicy, DestTrait, removeOOTF>(
282 std::forward<Args>(args)...);
283 } else {
284 return writeLayerNoConversion<CSTrait, swap>(std::forward<Args>(args)...);
285 }
286}
287
288template<typename CSTrait,
289 bool swap,
290 bool convertToRec2020,
291 bool isLinear,
292 ConversionPolicy linearizePolicy,
293 typename DestTrait,
294 typename... Args>
295ALWAYS_INLINE auto writeLayerWithPolicy(bool removeOOTF, Args &&...args)
296{
297 if (removeOOTF) {
298 return writeLayerSimplify<CSTrait, swap, convertToRec2020, isLinear, linearizePolicy, DestTrait, true>(
299 std::forward<Args>(args)...);
300 } else {
301 return writeLayerSimplify<CSTrait, swap, convertToRec2020, isLinear, linearizePolicy, DestTrait, false>(
302 std::forward<Args>(args)...);
303 }
304}
305
306template<typename CSTrait, bool swap, bool convertToRec2020, bool isLinear, typename... Args>
307ALWAYS_INLINE auto writeLayerWithLinear(ConversionPolicy linearizePolicy, Args &&...args)
308{
309 if (linearizePolicy == ConversionPolicy::ApplyHLG) {
310 return writeLayerWithPolicy<CSTrait,
311 swap,
312 convertToRec2020,
313 isLinear,
315 KoBgrU16Traits>(std::forward<Args>(args)...);
316 } else if (linearizePolicy == ConversionPolicy::ApplyPQ) {
317 return writeLayerWithPolicy<CSTrait,
318 swap,
319 convertToRec2020,
320 isLinear,
322 KoBgrU16Traits>(std::forward<Args>(args)...);
323 } else if (linearizePolicy == ConversionPolicy::ApplySMPTE428) {
324 return writeLayerWithPolicy<CSTrait,
325 swap,
326 convertToRec2020,
327 isLinear,
329 KoBgrU16Traits>(std::forward<Args>(args)...);
330 } else {
331 return writeLayerWithPolicy<CSTrait, swap, convertToRec2020, isLinear, ConversionPolicy::KeepTheSame, CSTrait>(
332 std::forward<Args>(args)...);
333 }
334}
335
336template<typename CSTrait, bool swap, bool convertToRec2020, typename... Args>
337ALWAYS_INLINE auto writeLayerWithRec2020(bool isLinear, Args &&...args)
338{
339 if (isLinear) {
340 return writeLayerWithLinear<CSTrait, swap, convertToRec2020, true>(std::forward<Args>(args)...);
341 } else {
342 return writeLayerWithLinear<CSTrait, swap, convertToRec2020, false>(std::forward<Args>(args)...);
343 }
344}
345
346template<typename CSTrait, bool swap, typename... Args>
347ALWAYS_INLINE auto writeLayerWithSwap(bool convertToRec2020, Args &&...args)
348{
349 if (convertToRec2020) {
350 return writeLayerWithRec2020<CSTrait, swap, true>(std::forward<Args>(args)...);
351 } else {
352 return writeLayerWithRec2020<CSTrait, swap, false>(std::forward<Args>(args)...);
353 }
354}
355
356template<typename... Args>
357inline auto writeLayer(const KoID &id, Args &&...args)
358{
359 if (id == Integer8BitsColorDepthID) {
360 return writeLayerWithSwap<KoBgrU8Traits, true>(std::forward<Args>(args)...);
361 } else if (id == Integer16BitsColorDepthID) {
362 return writeLayerWithSwap<KoBgrU16Traits, true>(std::forward<Args>(args)...);
363#ifdef HAVE_OPENEXR
364 } else if (id == Float16BitsColorDepthID) {
365 return writeLayerWithSwap<KoBgrF16Traits, false>(std::forward<Args>(args)...);
366#endif
367 } else if (id == Float32BitsColorDepthID) {
368 return writeLayerWithSwap<KoBgrF32Traits, false>(std::forward<Args>(args)...);
369 } else {
370 KIS_ASSERT_X(false, "JPEGXLExport::writeLayer", "unsupported bit depth!");
371 return QByteArray();
372 }
373}
374} // namespace HDR
375
376#endif // KIS_JPEGXL_EXPORT_TOOLS_H
float value(const T *src, size_t ch)
#define ALWAYS_INLINE
const KoID Float32BitsColorDepthID("F32", ki18n("32-bit float/channel"))
const KoID Float16BitsColorDepthID("F16", ki18n("16-bit float/channel"))
const KoID Integer8BitsColorDepthID("U8", ki18n("8-bit integer/channel"))
const KoID Integer16BitsColorDepthID("U16", ki18n("16-bit integer/channel"))
ALWAYS_INLINE void removeHLGOOTF(float *rgb, const double *lumaCoefficients, float gamma=1.2f, float nominalPeak=1000.0f) noexcept
ALWAYS_INLINE float applySmpte2048Curve(float x) noexcept
ALWAYS_INLINE float applySMPTE_ST_428Curve(float x) noexcept
ALWAYS_INLINE float applyHLGCurve(float x) noexcept
virtual const quint8 * rawDataConst() const =0
virtual bool nextPixel()=0
virtual void nextRow()=0
QVector< qreal > lumaCoefficients
virtual const KoColorProfile * profile() const =0
Definition KoID.h:30
#define KIS_ASSERT_X(cond, where, what)
Definition kis_assert.h:40
#define warnFile
Definition kis_debug.h:95
ALWAYS_INLINE auto writeLayerWithPolicy(bool removeOOTF, Args &&...args)
ALWAYS_INLINE auto writeLayerSimplify(Args &&...args)
ALWAYS_INLINE auto writeLayerWithRec2020(bool isLinear, Args &&...args)
QByteArray writeLayerNoConversion(const int width, const int height, KisHLineConstIteratorSP it, float hlgGamma, float hlgNominalPeak, const KoColorSpace *cs)
ALWAYS_INLINE auto writeLayerWithLinear(ConversionPolicy linearizePolicy, Args &&...args)
ALWAYS_INLINE auto writeLayerWithSwap(bool convertToRec2020, Args &&...args)
ALWAYS_INLINE float applyCurveAsNeeded(float value)
QByteArray writeLayer(const int width, const int height, KisHLineConstIteratorSP it, float hlgGamma, float hlgNominalPeak, const KoColorSpace *cs)
QByteArray writeCMYKPixels(bool isTrichromatic, int chPos, const int width, const int height, KisHLineConstIteratorSP it)
QByteArray writeCMYKLayer(const KoID &id, Args &&...args)
static void setFinalizedPosition(void *opaque, uint64_t finalized_position)
static void releaseBuffer(void *opaque, size_t written_bytes)
static void * getBuffer(void *opaque, size_t *size)
JxlEncoderOutputProcessor getOutputProcessor()
static void seek(void *opaque, uint64_t position)
virtual void linearizeFloatValue(QVector< qreal > &Value) const =0