SourcePP
Several modern C++20 libraries for sanely parsing Valve's formats.
Loading...
Searching...
No Matches
ImageConversion.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <bit>
5#include <cmath>
6#include <cstdlib>
7#include <cstring>
8#include <memory>
9#include <span>
10#include <string_view>
11#include <unordered_map>
12
13#ifdef SOURCEPP_BUILD_WITH_TBB
14#include <execution>
15#endif
16
17#ifdef SOURCEPP_BUILD_WITH_THREADS
18#include <future>
19#endif
20
21#include <Compressonator.h>
22
23#define QOI_IMPLEMENTATION
24#define QOI_NO_STDIO
25#include <qoi.h>
26
27#include <sourcepp/Macros.h>
29
30#define STB_IMAGE_IMPLEMENTATION
31#define STB_IMAGE_STATIC
32#define STBI_NO_FAILURE_STRINGS
33#define STBI_NO_STDIO
34#include <stb_image.h>
35
36#define STB_IMAGE_RESIZE_IMPLEMENTATION
37#define STB_IMAGE_RESIZE_STATIC
38#include <stb_image_resize2.h>
39
40#define STB_IMAGE_WRITE_IMPLEMENTATION
41#define STB_IMAGE_WRITE_STATIC
42#define STBI_WRITE_NO_STDIO
43#include <stb_image_write.h>
44
45#define TINYEXR_IMPLEMENTATION 1
46#ifdef SOURCEPP_BUILD_WITH_THREADS
47#define TINYEXR_USE_THREAD 1
48#else
49#define TINYEXR_USE_THREAD 0
50#endif
51#include <tinyexr.h>
52
53#include <webp/decode.h>
54#include <webp/encode.h>
55
56using namespace sourcepp;
57using namespace vtfpp;
58
59namespace {
60
61[[nodiscard]] constexpr CMP_FORMAT imageFormatToCompressonatorFormat(ImageFormat format) {
62 switch (format) {
63 using enum ImageFormat;
64 case RGBA8888:
65 case UVWQ8888:
66 return CMP_FORMAT_RGBA_8888;
67 case ABGR8888:
68 return CMP_FORMAT_ABGR_8888;
69 case RGB888:
70 return CMP_FORMAT_RGB_888;
71 case BGR888:
72 return CMP_FORMAT_BGR_888;
73 case I8:
74 case P8:
75 return CMP_FORMAT_R_8;
76 case ARGB8888:
77 return CMP_FORMAT_ARGB_8888;
78 case BGRA8888:
79 return CMP_FORMAT_BGRA_8888;
80 case DXT1:
82 return CMP_FORMAT_DXT1;
83 case DXT3:
84 return CMP_FORMAT_DXT3;
85 case DXT5:
86 return CMP_FORMAT_DXT5;
87 case UV88:
88 return CMP_FORMAT_RG_8;
89 case R16F:
90 return CMP_FORMAT_R_16F;
91 case RG1616F:
92 return CMP_FORMAT_RG_16F;
93 case RGBA16161616F:
94 return CMP_FORMAT_RGBA_16F;
95 case RGBA16161616:
96 return CMP_FORMAT_RGBA_16;
97 case R32F:
98 return CMP_FORMAT_R_32F;
99 case RG3232F:
100 return CMP_FORMAT_RG_32F;
101 case RGB323232F:
102 return CMP_FORMAT_RGB_32F;
103 case RGBA32323232F:
104 return CMP_FORMAT_RGBA_32F;
105 case ATI2N:
106 return CMP_FORMAT_ATI2N;
107 case ATI1N:
108 return CMP_FORMAT_ATI1N;
109 case RGBA1010102:
110 return CMP_FORMAT_RGBA_1010102;
111 case R8:
112 return CMP_FORMAT_R_8;
113 case BC7:
114 return CMP_FORMAT_BC7;
115 case BC6H:
116 return CMP_FORMAT_BC6H_SF;
117 case RGB565:
118 case IA88:
119 case A8:
122 case BGRX8888:
123 case BGR565:
124 case BGRX5551:
125 case BGRA4444:
126 case BGRA5551:
127 case UVLX8888:
128 case EMPTY:
129 case BGRA1010102:
130 case RGBX8888:
143 return CMP_FORMAT_Unknown;
144 }
145 return CMP_FORMAT_Unknown;
146}
147
148[[nodiscard]] constexpr int imageFormatToSTBIRPixelLayout(ImageFormat format) {
149 switch (format) {
150 using enum ImageFormat;
151 case RGBA8888:
152 case UVWQ8888:
153 case RGBA16161616:
154 case RGBA16161616F:
155 case RGBA32323232F:
156 return STBIR_RGBA;
157 case ABGR8888:
158 return STBIR_ABGR;
159 case RGB888:
160 case RGB323232F:
161 return STBIR_RGB;
162 case BGR888:
163 return STBIR_BGR;
164 case I8:
165 case P8:
166 case R32F:
167 case R16F:
168 case R8:
169 return STBIR_1CHANNEL;
170 case ARGB8888:
171 return STBIR_ARGB;
172 case BGRA8888:
173 return STBIR_BGRA;
174 case UV88:
175 case RG1616F:
176 case RG3232F:
177 return STBIR_2CHANNEL;
178 case IA88:
179 return STBIR_RA;
180 // We want these to get converted to their respective container format before resize
181 case DXT1:
183 case DXT3:
184 case DXT5:
185 case ATI2N:
186 case ATI1N:
187 case BC7:
188 case BC6H:
189 case RGB565:
190 case A8:
193 case RGBX8888:
194 case BGRX8888:
195 case BGR565:
196 case BGRX5551:
197 case BGRA4444:
198 case BGRA5551:
199 case UVLX8888:
200 case EMPTY:
201 case RGBA1010102:
202 case BGRA1010102:
215 break;
216 }
217 return -1;
218}
219
220[[nodiscard]] constexpr int imageFormatToSTBIRDataType(ImageFormat format, bool srgb = false) {
221 switch (format) {
222 using enum ImageFormat;
223 case RGBA8888:
224 case ABGR8888:
225 case RGB888:
226 case BGR888:
227 case I8:
228 case IA88:
229 case P8:
230 case A8:
233 case ARGB8888:
234 case BGRA8888:
235 case BGRX8888:
236 case UV88:
237 case UVWQ8888:
238 case UVLX8888:
239 case RGBX8888:
240 case R8:
241 return srgb ? STBIR_TYPE_UINT8_SRGB : STBIR_TYPE_UINT8;
242 case R16F:
243 case RG1616F:
244 case RGBA16161616F:
245 return STBIR_TYPE_HALF_FLOAT;
246 case RGBA16161616:
247 return STBIR_TYPE_UINT16;
248 case R32F:
249 case RG3232F:
250 case RGB323232F:
251 case RGBA32323232F:
252 return STBIR_TYPE_FLOAT;
253 case RGB565:
254 case BGR565:
255 case BGRX5551:
256 case BGRA4444:
258 case BGRA5551:
259 case DXT1:
260 case DXT3:
261 case DXT5:
262 case EMPTY:
263 case ATI2N:
264 case ATI1N:
265 case RGBA1010102:
266 case BGRA1010102:
279 case BC7:
280 case BC6H:
281 break;
282 }
283 return -1;
284}
285
286[[nodiscard]] std::vector<std::byte> convertImageDataUsingCompressonator(std::span<const std::byte> imageData, ImageFormat oldFormat, ImageFormat newFormat, uint16_t width, uint16_t height, float quality = ImageConversion::DEFAULT_COMPRESSED_QUALITY) {
287 if (imageData.empty()) {
288 return {};
289 }
290
291 CMP_Texture srcTexture{};
292 srcTexture.dwSize = sizeof(srcTexture);
293 srcTexture.dwWidth = width;
294 srcTexture.dwHeight = height;
295 srcTexture.dwPitch = ImageFormatDetails::compressed(newFormat) ? 0 : width * (ImageFormatDetails::bpp(newFormat) / 8);
296 srcTexture.format = ::imageFormatToCompressonatorFormat(oldFormat);
297 srcTexture.dwDataSize = imageData.size();
298
299 srcTexture.pData = const_cast<CMP_BYTE*>(reinterpret_cast<const CMP_BYTE*>(imageData.data()));
300
301 CMP_Texture destTexture{};
302 destTexture.dwSize = sizeof(destTexture);
303 destTexture.dwWidth = width;
304 destTexture.dwHeight = height;
305 destTexture.dwPitch = ImageFormatDetails::compressed(newFormat) ? 0 : width * (ImageFormatDetails::bpp(newFormat) / 8);
306 destTexture.format = ::imageFormatToCompressonatorFormat(newFormat);
307 destTexture.dwDataSize = CMP_CalculateBufferSize(&destTexture);
308
309 std::vector<std::byte> destData;
310 destData.resize(destTexture.dwDataSize);
311 destTexture.pData = reinterpret_cast<CMP_BYTE*>(destData.data());
312
313 CMP_CompressOptions options{};
314 options.dwSize = sizeof(options);
315 options.fquality = std::clamp(quality, 0.f, 1.f);
316 options.bDXT1UseAlpha = oldFormat == ImageFormat::DXT1_ONE_BIT_ALPHA || newFormat == ImageFormat::DXT1_ONE_BIT_ALPHA;
317 if (options.bDXT1UseAlpha) {
318 options.nAlphaThreshold = 128;
319 }
320
321 if (CMP_ConvertTexture(&srcTexture, &destTexture, &options, nullptr) != CMP_OK) {
322 return {};
323 }
324 return destData;
325}
326
327[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA8888(std::span<const std::byte> imageData, ImageFormat format) {
328 using namespace ImageConversion;
329
330 if (imageData.empty()) {
331 return {};
332 }
333
334 if (format == ImageFormat::RGBA8888 || format == ImageFormat::UVWQ8888) {
335 return {imageData.begin(), imageData.end()};
336 }
337
338 std::vector<std::byte> newData;
339 newData.resize(imageData.size() / (ImageFormatDetails::bpp(format) / 8) * sizeof(ImagePixel::RGBA8888));
340 std::span newDataSpan{reinterpret_cast<ImagePixel::RGBA8888*>(newData.data()), newData.size() / sizeof(ImagePixel::RGBA8888)};
341
342 #define VTFPP_REMAP_TO_8(value, shift) math::remap<uint8_t>((value), (1 << (shift)) - 1, (1 << 8) - 1)
343
344 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
345 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
346 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA8888 { \
347 return {(r), (g), (b), (a)}; \
348 })
349#ifdef SOURCEPP_BUILD_WITH_TBB
350 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
351#else
352 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
353#endif
354 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
355 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
356
357 switch (format) {
358 using enum ImageFormat;
359 VTFPP_CASE_CONVERT_AND_BREAK(ABGR8888, pixel.r, pixel.g, pixel.b, pixel.a);
360 VTFPP_CASE_CONVERT_AND_BREAK(RGB888, pixel.r, pixel.g, pixel.b, 0xff);
361 VTFPP_CASE_CONVERT_AND_BREAK(RGB888_BLUESCREEN, pixel.r, pixel.g, pixel.b, static_cast<uint8_t>((pixel.r == 0 && pixel.g == 0 && pixel.b == 0xff) ? 0 : 0xff));
362 VTFPP_CASE_CONVERT_AND_BREAK(BGR888, pixel.r, pixel.g, pixel.b, 0xff);
363 VTFPP_CASE_CONVERT_AND_BREAK(BGR888_BLUESCREEN, pixel.r, pixel.g, pixel.b, static_cast<uint8_t>((pixel.r == 0 && pixel.g == 0 && pixel.b == 0xff) ? 0 : 0xff));
364 VTFPP_CASE_CONVERT_AND_BREAK(RGB565, VTFPP_REMAP_TO_8(pixel.r, 5), VTFPP_REMAP_TO_8(pixel.g, 6), VTFPP_REMAP_TO_8(pixel.b, 5), 0xff);
365 VTFPP_CASE_CONVERT_AND_BREAK(P8, pixel.p, pixel.p, pixel.p, 0xff);
366 VTFPP_CASE_CONVERT_AND_BREAK(I8, pixel.i, pixel.i, pixel.i, 0xff);
367 VTFPP_CASE_CONVERT_AND_BREAK(IA88, pixel.i, pixel.i, pixel.i, pixel.a);
368 VTFPP_CASE_CONVERT_AND_BREAK(A8, 0, 0, 0, pixel.a);
369 VTFPP_CASE_CONVERT_AND_BREAK(ARGB8888, pixel.r, pixel.g, pixel.b, pixel.a);
370 VTFPP_CASE_CONVERT_AND_BREAK(BGRA8888, pixel.r, pixel.g, pixel.b, pixel.a);
371 VTFPP_CASE_CONVERT_AND_BREAK(BGRX8888, pixel.r, pixel.g, pixel.b, 0xff);
372 VTFPP_CASE_CONVERT_AND_BREAK(BGR565, VTFPP_REMAP_TO_8(pixel.r, 5), VTFPP_REMAP_TO_8(pixel.g, 6), VTFPP_REMAP_TO_8(pixel.b, 5), 0xff);
373 VTFPP_CASE_CONVERT_AND_BREAK(BGRA5551, VTFPP_REMAP_TO_8(pixel.r, 5), VTFPP_REMAP_TO_8(pixel.g, 5), VTFPP_REMAP_TO_8(pixel.b, 5), static_cast<uint8_t>(pixel.a * 0xff));
376 VTFPP_CASE_CONVERT_AND_BREAK(UV88, pixel.u, pixel.v, 0, 0xff);
377 VTFPP_CASE_CONVERT_AND_BREAK(UVLX8888, pixel.u, pixel.v, pixel.l, 0xff);
378 VTFPP_CASE_CONVERT_AND_BREAK(RGBX8888, pixel.r, pixel.g, pixel.b, 0xff);
379 VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_BGRX8888_LINEAR, pixel.r, pixel.g, pixel.b, 0xff);
380 VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_RGBA8888_LINEAR, pixel.r, pixel.g, pixel.b, pixel.a);
381 VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_ABGR8888_LINEAR, pixel.r, pixel.g, pixel.b, pixel.a);
382 VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_ARGB8888_LINEAR, pixel.r, pixel.g, pixel.b, pixel.a);
383 VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_BGRA8888_LINEAR, pixel.r, pixel.g, pixel.b, pixel.a);
384 VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_RGB888_LINEAR, pixel.r, pixel.g, pixel.b, 0xff);
385 VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_BGR888_LINEAR, pixel.r, pixel.g, pixel.b, 0xff);
387 VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_I8_LINEAR, pixel.i, pixel.i, pixel.i, 0xff);
388 VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_BGRX8888_LE, pixel.r, pixel.g, pixel.b, 0xff);
389 VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_BGRA8888_LE, pixel.r, pixel.g, pixel.b, pixel.a);
390 VTFPP_CASE_CONVERT_AND_BREAK(R8, pixel.r, 0, 0, 0xff);
391 default: SOURCEPP_DEBUG_BREAK; break;
392 }
393
394 #undef VTFPP_CASE_CONVERT_AND_BREAK
395 #undef VTFPP_CONVERT
396 #undef VTFPP_CONVERT_DETAIL
397 #undef VTFPP_REMAP_TO_8
398
399 return newData;
400}
401
402[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888(std::span<const std::byte> imageData, ImageFormat format) {
403 using namespace ImageConversion;
404
405 if (imageData.empty()) {
406 return {};
407 }
408
409 if (format == ImageFormat::RGBA8888) {
410 return {imageData.begin(), imageData.end()};
411 }
412
413 std::span imageDataSpan{reinterpret_cast<const ImagePixel::RGBA8888*>(imageData.data()), imageData.size() / sizeof(ImagePixel::RGBA8888)};
414 std::vector<std::byte> newData;
415 newData.resize(imageData.size() / sizeof(ImagePixel::RGBA8888) * (ImageFormatDetails::bpp(format) / 8));
416
417 #define VTFPP_REMAP_FROM_8(value, shift) math::remap<uint8_t>((value), (1 << 8) - 1, (1 << (shift)) - 1)
418
419#ifdef SOURCEPP_BUILD_WITH_TBB
420 #define VTFPP_CONVERT(InputType, ...) \
421 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
422 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA8888 pixel) -> ImagePixel::InputType { \
423 return __VA_ARGS__; \
424 })
425#else
426 #define VTFPP_CONVERT(InputType, ...) \
427 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
428 std::transform(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA8888 pixel) -> ImagePixel::InputType { \
429 return __VA_ARGS__; \
430 })
431#endif
432 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
433 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
434
435 switch (format) {
436 using enum ImageFormat;
437 VTFPP_CASE_CONVERT_AND_BREAK(ABGR8888, {pixel.a, pixel.b, pixel.g, pixel.r});
438 VTFPP_CASE_CONVERT_AND_BREAK(RGB888, {pixel.r, pixel.g, pixel.b});
439 VTFPP_CASE_CONVERT_AND_BREAK(RGB888_BLUESCREEN, pixel.a == 0xff ? ImagePixel::RGB888_BLUESCREEN{pixel.r, pixel.g, pixel.b} : ImagePixel::RGB888_BLUESCREEN{0, 0, 0xff});
440 VTFPP_CASE_CONVERT_AND_BREAK(BGR888, {pixel.b, pixel.g, pixel.r});
441 VTFPP_CASE_CONVERT_AND_BREAK(BGR888_BLUESCREEN, pixel.a == 0xff ? ImagePixel::BGR888_BLUESCREEN{pixel.b, pixel.g, pixel.r} : ImagePixel::BGR888_BLUESCREEN{0xff, 0, 0});
445 VTFPP_CASE_CONVERT_AND_BREAK(IA88, {pixel.r, pixel.a});
447 VTFPP_CASE_CONVERT_AND_BREAK(ARGB8888, {pixel.a, pixel.r, pixel.g, pixel.b});
448 VTFPP_CASE_CONVERT_AND_BREAK(BGRA8888, {pixel.b, pixel.g, pixel.r, pixel.a});
449 VTFPP_CASE_CONVERT_AND_BREAK(BGRX8888, {pixel.b, pixel.g, pixel.r, 0xff});
451 VTFPP_CASE_CONVERT_AND_BREAK(BGRA5551, {VTFPP_REMAP_FROM_8(pixel.b, 5), VTFPP_REMAP_FROM_8(pixel.g, 5), VTFPP_REMAP_FROM_8(pixel.r, 5), static_cast<uint8_t>(pixel.a < 0xff ? 1 : 0)});
454 VTFPP_CASE_CONVERT_AND_BREAK(UV88, {pixel.r, pixel.g});
455 VTFPP_CASE_CONVERT_AND_BREAK(UVLX8888, {pixel.r, pixel.g, pixel.b});
456 VTFPP_CASE_CONVERT_AND_BREAK(RGBX8888, {pixel.r, pixel.g, pixel.b, 0xff});
457 VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_BGRX8888_LINEAR, {pixel.b, pixel.g, pixel.r, 0xff});
458 VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_RGBA8888_LINEAR, {pixel.r, pixel.g, pixel.b, pixel.a});
459 VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_ABGR8888_LINEAR, {pixel.a, pixel.b, pixel.g, pixel.r});
460 VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_ARGB8888_LINEAR, {pixel.a, pixel.r, pixel.g, pixel.b});
461 VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_BGRA8888_LINEAR, {pixel.b, pixel.g, pixel.r, pixel.a});
462 VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_RGB888_LINEAR, {pixel.r, pixel.g, pixel.b});
463 VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_BGR888_LINEAR, {pixel.b, pixel.g, pixel.r});
466 VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_BGRX8888_LE, {pixel.b, pixel.g, pixel.r, 0xff});
467 VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_BGRA8888_LE, {pixel.b, pixel.g, pixel.r, pixel.a});
469 default: SOURCEPP_DEBUG_BREAK; break;
470 }
471
472 #undef VTFPP_CASE_CONVERT_AND_BREAK
473 #undef VTFPP_CONVERT
474 #undef VTFPP_REMAP_FROM_8
475
476 return newData;
477}
478
479[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA16161616(std::span<const std::byte> imageData, ImageFormat format) {
480 using namespace ImageConversion;
481
482 if (imageData.empty()) {
483 return {};
484 }
485
486 if (format == ImageFormat::RGBA16161616) {
487 return {imageData.begin(), imageData.end()};
488 }
489
490 std::vector<std::byte> newData;
491 newData.resize(imageData.size() / (ImageFormatDetails::bpp(format) / 8) * sizeof(ImagePixel::RGBA16161616));
492 std::span newDataSpan{reinterpret_cast<ImagePixel::RGBA16161616*>(newData.data()), newData.size() / sizeof(ImagePixel::RGBA16161616)};
493
494 #define VTFPP_REMAP_TO_16(value, shift) math::remap<uint16_t>((value), (1 << (shift)) - 1, (1 << 16) - 1)
495
496 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
497 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
498 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA16161616 { \
499 return { static_cast<uint16_t>(r), static_cast<uint16_t>(g), static_cast<uint16_t>(b), static_cast<uint16_t>(a) }; \
500 })
501#ifdef SOURCEPP_BUILD_WITH_TBB
502 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
503#else
504 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
505#endif
506 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
507 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
508
509 #define VTFPP_CONVERT_REMAP(InputType, r, g, b, a) \
510 do { \
511 if constexpr (ImageFormatDetails::alpha(ImageFormat::InputType) > 1) { \
512 VTFPP_CONVERT(InputType, \
513 VTFPP_REMAP_TO_16((r), ImageFormatDetails::red(ImageFormat::InputType)), \
514 VTFPP_REMAP_TO_16((g), ImageFormatDetails::green(ImageFormat::InputType)), \
515 VTFPP_REMAP_TO_16((b), ImageFormatDetails::blue(ImageFormat::InputType)), \
516 VTFPP_REMAP_TO_16((a), ImageFormatDetails::alpha(ImageFormat::InputType))); \
517 } else if constexpr (ImageFormatDetails::alpha(ImageFormat::InputType) == 1) { \
518 VTFPP_CONVERT(InputType, \
519 VTFPP_REMAP_TO_16((r), ImageFormatDetails::red(ImageFormat::InputType)), \
520 VTFPP_REMAP_TO_16((g), ImageFormatDetails::green(ImageFormat::InputType)), \
521 VTFPP_REMAP_TO_16((b), ImageFormatDetails::blue(ImageFormat::InputType)), \
522 (a) * 0xff); \
523 } else { \
524 VTFPP_CONVERT(InputType, \
525 VTFPP_REMAP_TO_16((r), ImageFormatDetails::red(ImageFormat::InputType)), \
526 VTFPP_REMAP_TO_16((g), ImageFormatDetails::green(ImageFormat::InputType)), \
527 VTFPP_REMAP_TO_16((b), ImageFormatDetails::blue(ImageFormat::InputType)), \
528 (a)); \
529 } \
530 } while (false)
531 #define VTFPP_CASE_CONVERT_REMAP_AND_BREAK(InputType, r, g, b, a) \
532 case InputType: { VTFPP_CONVERT_REMAP(InputType, r, g, b, a); } \
533 break
534
535 switch (format) {
536 using enum ImageFormat;
537 VTFPP_CASE_CONVERT_REMAP_AND_BREAK(RGBA1010102, pixel.r, pixel.g, pixel.b, pixel.a);
538 VTFPP_CASE_CONVERT_REMAP_AND_BREAK(BGRA1010102, pixel.r, pixel.g, pixel.b, pixel.a);
539 VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_RGBA16161616_LINEAR, pixel.r, pixel.g, pixel.b, pixel.a);
540 default: SOURCEPP_DEBUG_BREAK; break;
541 }
542
543 #undef VTFPP_CASE_CONVERT_REMAP_AND_BREAK
544 #undef VTFPP_CONVERT_REMAP
545 #undef VTFPP_CASE_CONVERT_AND_BREAK
546 #undef VTFPP_CONVERT
547 #undef VTFPP_CONVERT_DETAIL
548 #undef VTFPP_REMAP_TO_16
549
550 return newData;
551}
552
553[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616(std::span<const std::byte> imageData, ImageFormat format) {
554 using namespace ImageConversion;
555
556 if (imageData.empty()) {
557 return {};
558 }
559
560 if (format == ImageFormat::RGBA16161616) {
561 return {imageData.begin(), imageData.end()};
562 }
563
564 std::span imageDataSpan{reinterpret_cast<const ImagePixel::RGBA16161616*>(imageData.data()), imageData.size() / sizeof(ImagePixel::RGBA16161616)};
565 std::vector<std::byte> newData;
566 newData.resize(imageData.size() / sizeof(ImagePixel::RGBA16161616) * (ImageFormatDetails::bpp(format) / 8));
567
568 #define VTFPP_REMAP_FROM_16(value, shift) static_cast<uint8_t>(math::remap<uint16_t>((value), (1 << 16) - 1, (1 << (shift)) - 1))
569
570#ifdef SOURCEPP_BUILD_WITH_TBB
571 #define VTFPP_CONVERT(InputType, ...) \
572 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
573 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA16161616 pixel) -> ImagePixel::InputType { \
574 return __VA_ARGS__; \
575 })
576#else
577 #define VTFPP_CONVERT(InputType, ...) \
578 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
579 std::transform(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA16161616 pixel) -> ImagePixel::InputType { \
580 return __VA_ARGS__; \
581 })
582#endif
583 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
584 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
585
586 switch (format) {
587 using enum ImageFormat;
590 VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_RGBA16161616_LINEAR, {pixel.r, pixel.g, pixel.b, pixel.a});
591 default: SOURCEPP_DEBUG_BREAK; break;
592 }
593
594 #undef VTFPP_CASE_CONVERT_AND_BREAK
595 #undef VTFPP_CONVERT
596 #undef VTFPP_REMAP_FROM_16
597
598 return newData;
599}
600
601[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA32323232F(std::span<const std::byte> imageData, ImageFormat format) {
602 if (imageData.empty()) {
603 return {};
604 }
605
606 if (format == ImageFormat::RGBA32323232F) {
607 return {imageData.begin(), imageData.end()};
608 }
609
610 std::vector<std::byte> newData;
611 newData.resize(imageData.size() / (ImageFormatDetails::bpp(format) / 8) * sizeof(ImagePixel::RGBA32323232F));
612 std::span newDataSpan{reinterpret_cast<ImagePixel::RGBA32323232F*>(newData.data()), newData.size() / sizeof(ImagePixel::RGBA32323232F)};
613
614 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
615 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
616 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA32323232F { return {(r), (g), (b), (a)}; })
617#ifdef SOURCEPP_BUILD_WITH_TBB
618 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
619#else
620 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
621#endif
622 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
623 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
624
625 switch (format) {
626 using enum ImageFormat;
627 VTFPP_CASE_CONVERT_AND_BREAK(R32F, pixel.r, 0.f, 0.f, 1.f);
628 VTFPP_CASE_CONVERT_AND_BREAK(RG3232F, pixel.r, pixel.g, 0.f, 1.f);
629 VTFPP_CASE_CONVERT_AND_BREAK(RGB323232F, pixel.r, pixel.g, pixel.b, 1.f);
630 VTFPP_CASE_CONVERT_AND_BREAK(R16F, pixel.r, 0.f, 0.f, 1.f);
631 VTFPP_CASE_CONVERT_AND_BREAK(RG1616F, pixel.r, pixel.g, 0.f, 1.f);
632 VTFPP_CASE_CONVERT_AND_BREAK(RGBA16161616F, pixel.r, pixel.g, pixel.b, pixel.a);
633 default: SOURCEPP_DEBUG_BREAK; break;
634 }
635
636 #undef VTFPP_CASE_CONVERT_AND_BREAK
637 #undef VTFPP_CONVERT
638 #undef VTFPP_CONVERT_DETAIL
639
640 return newData;
641}
642
643[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232F(std::span<const std::byte> imageData, ImageFormat format) {
644 using namespace ImageConversion;
645
646 if (imageData.empty()) {
647 return {};
648 }
649
650 if (format == ImageFormat::RGBA32323232F) {
651 return {imageData.begin(), imageData.end()};
652 }
653
654 std::span imageDataSpan{reinterpret_cast<const ImagePixel::RGBA32323232F*>(imageData.data()), imageData.size() / sizeof(ImagePixel::RGBA32323232F)};
655 std::vector<std::byte> newData;
656 newData.resize(imageData.size() / sizeof(ImagePixel::RGBA32323232F) * (ImageFormatDetails::bpp(format) / 8));
657
658#ifdef SOURCEPP_BUILD_WITH_TBB
659 #define VTFPP_CONVERT(InputType, ...) \
660 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
661 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA32323232F pixel) -> ImagePixel::InputType { \
662 return __VA_ARGS__; \
663 })
664#else
665 #define VTFPP_CONVERT(InputType, ...) \
666 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
667 std::transform(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA32323232F pixel) -> ImagePixel::InputType { \
668 return __VA_ARGS__; \
669 })
670#endif
671 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
672 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
673
674 switch (format) {
675 using enum ImageFormat;
677 VTFPP_CASE_CONVERT_AND_BREAK(RG3232F, {pixel.r, pixel.g});
678 VTFPP_CASE_CONVERT_AND_BREAK(RGB323232F, {pixel.r, pixel.g, pixel.b});
679 VTFPP_CASE_CONVERT_AND_BREAK(R16F, {half{pixel.r}});
680 VTFPP_CASE_CONVERT_AND_BREAK(RG1616F, {half{pixel.r}, half{pixel.g}});
681 VTFPP_CASE_CONVERT_AND_BREAK(RGBA16161616F, {half{pixel.r}, half{pixel.g}, half{pixel.b}, half{pixel.a}});
682 default: SOURCEPP_DEBUG_BREAK; break;
683 }
684
685 #undef VTFPP_CASE_CONVERT_AND_BREAK
686 #undef VTFPP_CONVERT
687
688 return newData;
689}
690
691[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888ToRGBA32323232F(std::span<const std::byte> imageData) {
692 if (imageData.empty()) {
693 return {};
694 }
695
696 std::vector<std::byte> newData;
697 newData.resize(imageData.size() / sizeof(ImagePixel::RGBA8888) * sizeof(ImagePixel::RGBA32323232F));
698 std::span newDataSpan{reinterpret_cast<ImagePixel::RGBA32323232F*>(newData.data()), newData.size() / sizeof(ImagePixel::RGBA32323232F)};
699
700 std::span imageDataSpan{reinterpret_cast<const ImagePixel::RGBA8888*>(imageData.data()), imageData.size() / sizeof(ImagePixel::RGBA8888)};
701 std::transform(
702#ifdef SOURCEPP_BUILD_WITH_TBB
703 std::execution::par_unseq,
704#endif
705 imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA8888 pixel) -> ImagePixel::RGBA32323232F {
706 return {
707 static_cast<float>(pixel.r) / static_cast<float>((1 << 8) - 1),
708 static_cast<float>(pixel.g) / static_cast<float>((1 << 8) - 1),
709 static_cast<float>(pixel.b) / static_cast<float>((1 << 8) - 1),
710 static_cast<float>(pixel.a) / static_cast<float>((1 << 8) - 1),
711 };
712 });
713
714 return newData;
715}
716
717[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232FToRGBA8888(std::span<const std::byte> imageData) {
718 if (imageData.empty()) {
719 return {};
720 }
721
722 std::vector<std::byte> newData;
723 newData.resize(imageData.size() / sizeof(ImagePixel::RGBA32323232F) * sizeof(ImagePixel::RGBA8888));
724 std::span newDataSpan{reinterpret_cast<ImagePixel::RGBA8888*>(newData.data()), newData.size() / sizeof(ImagePixel::RGBA8888)};
725
726 std::span imageDataSpan{reinterpret_cast<const ImagePixel::RGBA32323232F*>(imageData.data()), imageData.size() / sizeof(ImagePixel::RGBA32323232F)};
727 std::transform(
728#ifdef SOURCEPP_BUILD_WITH_TBB
729 std::execution::par_unseq,
730#endif
731 imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA32323232F pixel) -> ImagePixel::RGBA8888 {
732 return {
733 static_cast<uint8_t>(std::clamp(pixel.r, 0.f, 1.f) * ((1 << 8) - 1)),
734 static_cast<uint8_t>(std::clamp(pixel.g, 0.f, 1.f) * ((1 << 8) - 1)),
735 static_cast<uint8_t>(std::clamp(pixel.b, 0.f, 1.f) * ((1 << 8) - 1)),
736 static_cast<uint8_t>(std::clamp(pixel.a, 0.f, 1.f) * ((1 << 8) - 1)),
737 };
738 });
739
740 return newData;
741}
742
743[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888ToRGBA16161616(std::span<const std::byte> imageData) {
744 if (imageData.empty()) {
745 return {};
746 }
747
748 std::vector<std::byte> newData;
749 newData.resize(imageData.size() / sizeof(ImagePixel::RGBA8888) * sizeof(ImagePixel::RGBA16161616));
750 std::span newDataSpan{reinterpret_cast<ImagePixel::RGBA16161616*>(newData.data()), newData.size() / sizeof(ImagePixel::RGBA16161616)};
751
752 std::span imageDataSpan{reinterpret_cast<const ImagePixel::RGBA8888*>(imageData.data()), imageData.size() / sizeof(ImagePixel::RGBA8888)};
753 std::transform(
754#ifdef SOURCEPP_BUILD_WITH_TBB
755 std::execution::par_unseq,
756#endif
757 imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA8888 pixel) -> ImagePixel::RGBA16161616 {
758 return {
759 math::remap<uint16_t>(pixel.r, (1 << 8) - 1, (1 << 16) - 1),
760 math::remap<uint16_t>(pixel.g, (1 << 8) - 1, (1 << 16) - 1),
761 math::remap<uint16_t>(pixel.b, (1 << 8) - 1, (1 << 16) - 1),
762 math::remap<uint16_t>(pixel.a, (1 << 8) - 1, (1 << 16) - 1),
763 };
764 });
765
766 return newData;
767}
768
769[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616ToRGBA8888(std::span<const std::byte> imageData) {
770 if (imageData.empty()) {
771 return {};
772 }
773
774 std::vector<std::byte> newData;
775 newData.resize(imageData.size() / sizeof(ImagePixel::RGBA16161616) * sizeof(ImagePixel::RGBA8888));
776 std::span newDataSpan{reinterpret_cast<ImagePixel::RGBA8888*>(newData.data()), newData.size() / sizeof(ImagePixel::RGBA8888)};
777
778 std::span imageDataSpan{reinterpret_cast<const ImagePixel::RGBA16161616*>(imageData.data()), imageData.size() / sizeof(ImagePixel::RGBA16161616)};
779 std::transform(
780#ifdef SOURCEPP_BUILD_WITH_TBB
781 std::execution::par_unseq,
782#endif
783 imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA16161616 pixel) -> ImagePixel::RGBA8888 {
784 return {
785 static_cast<uint8_t>(math::remap<uint16_t>(pixel.r, (1 << 16) - 1, (1 << 8) - 1)),
786 static_cast<uint8_t>(math::remap<uint16_t>(pixel.g, (1 << 16) - 1, (1 << 8) - 1)),
787 static_cast<uint8_t>(math::remap<uint16_t>(pixel.b, (1 << 16) - 1, (1 << 8) - 1)),
788 static_cast<uint8_t>(math::remap<uint16_t>(pixel.a, (1 << 16) - 1, (1 << 8) - 1)),
789 };
790 });
791
792 return newData;
793}
794
795[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232FToRGBA16161616(std::span<const std::byte> imageData) {
796 if (imageData.empty()) {
797 return {};
798 }
799
800 std::vector<std::byte> newData;
801 newData.resize(imageData.size() / sizeof(ImagePixel::RGBA32323232F) * sizeof(ImagePixel::RGBA16161616));
802 std::span newDataSpan{reinterpret_cast<ImagePixel::RGBA16161616*>(newData.data()), newData.size() / sizeof(ImagePixel::RGBA16161616)};
803
804 std::span imageDataSpan{reinterpret_cast<const ImagePixel::RGBA32323232F*>(imageData.data()), imageData.size() / sizeof(ImagePixel::RGBA32323232F)};
805 std::transform(
806#ifdef SOURCEPP_BUILD_WITH_TBB
807 std::execution::par_unseq,
808#endif
809 imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA32323232F pixel) -> ImagePixel::RGBA16161616 {
810 return {
811 static_cast<uint16_t>(std::clamp(pixel.r, 0.f, 1.f) * ((1 << 16) - 1)),
812 static_cast<uint16_t>(std::clamp(pixel.g, 0.f, 1.f) * ((1 << 16) - 1)),
813 static_cast<uint16_t>(std::clamp(pixel.b, 0.f, 1.f) * ((1 << 16) - 1)),
814 static_cast<uint16_t>(std::clamp(pixel.a, 0.f, 1.f) * ((1 << 16) - 1)),
815 };
816 });
817
818 return newData;
819}
820
821[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616ToRGBA32323232F(std::span<const std::byte> imageData) {
822 if (imageData.empty()) {
823 return {};
824 }
825
826 std::vector<std::byte> newData;
827 newData.resize(imageData.size() / sizeof(ImagePixel::RGBA16161616) * sizeof(ImagePixel::RGBA32323232F));
828 std::span newDataSpan{reinterpret_cast<ImagePixel::RGBA32323232F*>(newData.data()), newData.size() / sizeof(ImagePixel::RGBA32323232F)};
829
830 std::span imageDataSpan{reinterpret_cast<const ImagePixel::RGBA16161616*>(imageData.data()), imageData.size() / sizeof(ImagePixel::RGBA16161616)};
831 std::transform(
832#ifdef SOURCEPP_BUILD_WITH_TBB
833 std::execution::par_unseq,
834#endif
835 imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA16161616 pixel) -> ImagePixel::RGBA32323232F {
836 return {
837 static_cast<float>(pixel.r) / static_cast<float>((1 << 16) - 1),
838 static_cast<float>(pixel.g) / static_cast<float>((1 << 16) - 1),
839 static_cast<float>(pixel.b) / static_cast<float>((1 << 16) - 1),
840 static_cast<float>(pixel.a) / static_cast<float>((1 << 16) - 1),
841 };
842 });
843
844 return newData;
845}
846
847} // namespace
848
849std::vector<std::byte> ImageConversion::convertImageDataToFormat(std::span<const std::byte> imageData, ImageFormat oldFormat, ImageFormat newFormat, uint16_t width, uint16_t height, float quality) {
850 if (imageData.empty() || oldFormat == ImageFormat::EMPTY) {
851 return {};
852 }
853
854 if (oldFormat == newFormat) {
855 return {imageData.begin(), imageData.end()};
856 }
857
858 std::vector<std::byte> newData;
859
860 const ImageFormat intermediaryOldFormat = ImageFormatDetails::containerFormat(oldFormat);
861 if (ImageFormatDetails::compressed(oldFormat)) {
862 newData = ::convertImageDataUsingCompressonator(imageData, oldFormat, intermediaryOldFormat, width, height, quality);
863 } else {
864 switch (intermediaryOldFormat) {
865 case ImageFormat::RGBA8888: newData = ::convertImageDataToRGBA8888(imageData, oldFormat); break;
866 case ImageFormat::RGBA16161616: newData = ::convertImageDataToRGBA16161616(imageData, oldFormat); break;
867 case ImageFormat::RGBA32323232F: newData = ::convertImageDataToRGBA32323232F(imageData, oldFormat); break;
868 default: return {};
869 }
870 }
871
872 if (intermediaryOldFormat == newFormat) {
873 return newData;
874 }
875
876 const ImageFormat intermediaryNewFormat = ImageFormatDetails::containerFormat(newFormat);
877 if (intermediaryOldFormat != intermediaryNewFormat) {
878 if (intermediaryOldFormat == ImageFormat::RGBA8888) {
879 if (intermediaryNewFormat == ImageFormat::RGBA16161616) {
880 newData = ::convertImageDataFromRGBA8888ToRGBA16161616(newData);
881 } else if (intermediaryNewFormat == ImageFormat::RGBA32323232F) {
882 newData = ::convertImageDataFromRGBA8888ToRGBA32323232F(newData);
883 } else {
884 return {};
885 }
886 } else if (intermediaryOldFormat == ImageFormat::RGBA16161616) {
887 if (intermediaryNewFormat == ImageFormat::RGBA8888) {
888 newData = ::convertImageDataFromRGBA16161616ToRGBA8888(newData);
889 } else if (intermediaryNewFormat == ImageFormat::RGBA32323232F) {
890 newData = ::convertImageDataFromRGBA16161616ToRGBA32323232F(newData);
891 } else {
892 return {};
893 }
894 } else if (intermediaryOldFormat == ImageFormat::RGBA32323232F) {
895 if (intermediaryNewFormat == ImageFormat::RGBA8888) {
896 newData = ::convertImageDataFromRGBA32323232FToRGBA8888(newData);
897 } else if (intermediaryNewFormat == ImageFormat::RGBA16161616) {
898 newData = ::convertImageDataFromRGBA32323232FToRGBA16161616(newData);
899 } else {
900 return {};
901 }
902 } else {
903 return {};
904 }
905 }
906
907 if (intermediaryNewFormat == newFormat) {
908 return newData;
909 }
910
911 if (ImageFormatDetails::compressed(newFormat)) {
912 newData = ::convertImageDataUsingCompressonator(newData, intermediaryNewFormat, newFormat, width, height, quality);
913 } else {
914 switch (intermediaryNewFormat) {
915 case ImageFormat::RGBA8888: newData = ::convertImageDataFromRGBA8888(newData, newFormat); break;
916 case ImageFormat::RGBA16161616: newData = ::convertImageDataFromRGBA16161616(newData, newFormat); break;
917 case ImageFormat::RGBA32323232F: newData = ::convertImageDataFromRGBA32323232F(newData, newFormat); break;
918 default: return {};
919 }
920 }
921
922 return newData;
923}
924
925std::vector<std::byte> ImageConversion::convertSeveralImageDataToFormat(std::span<const std::byte> imageData, ImageFormat oldFormat, ImageFormat newFormat, uint8_t mipCount, uint16_t frameCount, uint8_t faceCount, uint16_t width, uint16_t height, uint16_t sliceCount, float quality) {
926 if (imageData.empty() || oldFormat == ImageFormat::EMPTY) {
927 return {};
928 }
929
930 if (oldFormat == newFormat) {
931 return {imageData.begin(), imageData.end()};
932 }
933
934 std::vector<std::byte> out(ImageFormatDetails::getDataLength(newFormat, mipCount, frameCount, faceCount, width, height, sliceCount));
935 for(int mip = mipCount - 1; mip >= 0; mip--) {
936 for (int frame = 0; frame < frameCount; frame++) {
937 for (int face = 0; face < faceCount; face++) {
938 for (int slice = 0; slice < sliceCount; slice++) {
939 if (uint32_t oldOffset, oldLength; ImageFormatDetails::getDataPosition(oldOffset, oldLength, oldFormat, mip, mipCount, frame, frameCount, face, faceCount, width, height, slice, sliceCount)) {
940 const auto convertedImageData = ImageConversion::convertImageDataToFormat({imageData.data() + oldOffset, oldLength}, oldFormat, newFormat, ImageDimensions::getMipDim(mip, width), ImageDimensions::getMipDim(mip, height), quality);
941 if (uint32_t newOffset, newLength; ImageFormatDetails::getDataPosition(newOffset, newLength, newFormat, mip, mipCount, frame, frameCount, face, faceCount, width, height, slice, sliceCount) && newLength == convertedImageData.size()) {
942 std::memcpy(out.data() + newOffset, convertedImageData.data(), newLength);
943 }
944 }
945 }
946 }
947 }
948 }
949 return out;
950}
951
952std::array<std::vector<std::byte>, 6> ImageConversion::convertHDRIToCubeMap(std::span<const std::byte> imageData, ImageFormat format, uint16_t width, uint16_t height, uint16_t resolution, bool bilinear) {
953 if (imageData.empty() || format == ImageFormat::EMPTY) {
954 return {};
955 }
956
957 if (!resolution) {
958 resolution = height;
959 }
960
961 std::span<const float> imageDataRGBA32323232F{reinterpret_cast<const float*>(imageData.data()), reinterpret_cast<const float*>(imageData.data() + imageData.size())};
962
963 std::vector<std::byte> possiblyConvertedDataOrEmptyDontUseMeDirectly;
964 if (format != ImageFormat::RGBA32323232F) {
965 possiblyConvertedDataOrEmptyDontUseMeDirectly = convertImageDataToFormat(imageData, format, ImageFormat::RGBA32323232F, width, height);
966 imageDataRGBA32323232F = {reinterpret_cast<const float*>(possiblyConvertedDataOrEmptyDontUseMeDirectly.data()), reinterpret_cast<const float*>(possiblyConvertedDataOrEmptyDontUseMeDirectly.data() + possiblyConvertedDataOrEmptyDontUseMeDirectly.size())};
967 }
968
969 // For each face, contains the 3d starting point (corresponding to left bottom pixel), right direction,
970 // and up direction in 3d space, corresponding to pixel x,y coordinates of each face
971 static constexpr std::array<std::array<math::Vec3f, 3>, 6> startRightUp = {{
972 {{{ 1.0f, -1.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}}, // front
973 {{{ 1.0f, 1.0f, 1.0f}, { 0.0f,-1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}}, // back
974 {{{ 1.0f, 1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, { 0.0f,-1.0f, 0.0f}}}, // right
975 {{{-1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}}}, // left
976 {{{ 1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}}}, // up
977 {{{ 1.0f, 1.0f, -1.0f}, { 0.0f, 0.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}}, // down
978 }};
979
980 std::array<std::vector<std::byte>, 6> faceData;
981
982#ifdef SOURCEPP_BUILD_WITH_THREADS
983 const auto faceExtraction = [&](int i) {
984#else
985 for (int i = 0; i < faceData.size(); i++) {
986#endif
987 const auto start = startRightUp[i][0];
988 const auto right = startRightUp[i][1];
989 const auto up = startRightUp[i][2];
990
991 faceData[i].resize(resolution * resolution * sizeof(ImagePixel::RGBA32323232F));
992 std::span<float> face{reinterpret_cast<float*>(faceData[i].data()), reinterpret_cast<float*>(faceData[i].data() + faceData[i].size())};
993
994 for (int row = 0; row < resolution; row++) {
995 for (int col = 0; col < resolution; col++) {
996 math::Vec3f pixelDirection3d{
997 start[0] + (static_cast<float>(col) * 2.f + 0.5f) / static_cast<float>(resolution) * right[0] + (static_cast<float>(row) * 2.f + 0.5f) / static_cast<float>(resolution) * up[0],
998 start[1] + (static_cast<float>(col) * 2.f + 0.5f) / static_cast<float>(resolution) * right[1] + (static_cast<float>(row) * 2.f + 0.5f) / static_cast<float>(resolution) * up[1],
999 start[2] + (static_cast<float>(col) * 2.f + 0.5f) / static_cast<float>(resolution) * right[2] + (static_cast<float>(row) * 2.f + 0.5f) / static_cast<float>(resolution) * up[2],
1000 };
1001 float azimuth = std::atan2(pixelDirection3d[0], -pixelDirection3d[2]) + math::pi_f32; // add pi to move range to 0-360 deg
1002 float elevation = std::atan(pixelDirection3d[1] / std::sqrt(pixelDirection3d[0] * pixelDirection3d[0] + pixelDirection3d[2] * pixelDirection3d[2])) + math::pi_f32 / 2.f;
1003 float colHdri = (azimuth / math::pi_f32 / 2.f) * static_cast<float>(width); // add pi to azimuth to move range to 0-360 deg
1004 float rowHdri = (elevation / math::pi_f32) * static_cast<float>(height);
1005 if (!bilinear) {
1006 int colNearest = std::clamp(static_cast<int>(colHdri), 0, width - 1);
1007 int rowNearest = std::clamp(static_cast<int>(rowHdri), 0, height - 1);
1008 face[col * 4 + resolution * row * 4 + 0] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 0];
1009 face[col * 4 + resolution * row * 4 + 1] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 1];
1010 face[col * 4 + resolution * row * 4 + 2] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 2];
1011 face[col * 4 + resolution * row * 4 + 3] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 3];
1012 } else {
1013 float intCol, intRow;
1014 // factor gives the contribution of the next column, while the contribution of intCol is 1 - factor
1015 float factorCol = std::modf(colHdri - 0.5f, &intCol);
1016 float factorRow = std::modf(rowHdri - 0.5f, &intRow);
1017 int low_idx_row = static_cast<int>(intRow);
1018 int low_idx_column = static_cast<int>(intCol);
1019 int high_idx_column;
1020 if (factorCol < 0.f) {
1021 // modf can only give a negative value if the azimuth falls in the first pixel, left of the
1022 // center, so we have to mix with the pixel on the opposite side of the panoramic image
1023 high_idx_column = width - 1;
1024 } else if (low_idx_column == width - 1) {
1025 // if we are in the right-most pixel, and fall right of the center, mix with the left-most pixel
1026 high_idx_column = 0;
1027 } else {
1028 high_idx_column = low_idx_column + 1;
1029 }
1030 int high_idx_row;
1031 if (factorRow < 0.f || low_idx_row == height - 1) {
1032 high_idx_row = low_idx_row;
1033 factorRow = 0.f;
1034 } else {
1035 high_idx_row = low_idx_row + 1;
1036 }
1037 factorCol = std::abs(factorCol);
1038 factorRow = std::abs(factorRow);
1039 float f1 = (1 - factorRow) * (1 - factorCol);
1040 float f2 = factorRow * (1 - factorCol);
1041 float f3 = (1 - factorRow) * factorCol;
1042 float f4 = factorRow * factorCol;
1043 for (int j = 0; j < 4; j++) {
1044 face[col * 4 + resolution * row * 4 + j] =
1045 imageDataRGBA32323232F[low_idx_column * 4 + width * low_idx_row * 4 + j] * f1 +
1046 imageDataRGBA32323232F[low_idx_column * 4 + width * high_idx_row * 4 + j] * f2 +
1047 imageDataRGBA32323232F[high_idx_column * 4 + width * low_idx_row * 4 + j] * f3 +
1048 imageDataRGBA32323232F[high_idx_column * 4 + width * high_idx_row * 4 + j] * f4;
1049 }
1050 }
1051 }
1052 }
1053 if (format != ImageFormat::RGBA32323232F) {
1054 faceData[i] = convertImageDataToFormat(faceData[i], ImageFormat::RGBA32323232F, format, resolution, resolution);
1055 }
1056 }
1057#ifdef SOURCEPP_BUILD_WITH_THREADS
1058 ;
1059 std::array<std::future<void>, 6> faceFutures{
1060 std::async(std::launch::async, faceExtraction, 0),
1061 std::async(std::launch::async, faceExtraction, 1),
1062 std::async(std::launch::async, faceExtraction, 2),
1063 std::async(std::launch::async, faceExtraction, 3),
1064 std::async(std::launch::async, faceExtraction, 4),
1065 std::async(std::launch::async, faceExtraction, 5),
1066 };
1067 for (auto& future : faceFutures) {
1068 future.get();
1069 }
1070#endif
1071
1072 return faceData;
1073}
1074
1076 using enum FileFormat;
1077 return ImageFormatDetails::decimal(format) ? EXR : PNG;
1078}
1079
1080std::vector<std::byte> ImageConversion::convertImageDataToFile(std::span<const std::byte> imageData, ImageFormat format, uint16_t width, uint16_t height, FileFormat fileFormat) {
1081 if (imageData.empty() || format == ImageFormat::EMPTY) {
1082 return {};
1083 }
1084 std::vector<std::byte> out;
1085 auto stbWriteFunc = [](void* out_, void* data, int size) {
1086 std::copy_n(static_cast<std::byte*>(data), size, std::back_inserter(*static_cast<std::vector<std::byte>*>(out_)));
1087 };
1088
1089 if (fileFormat == FileFormat::DEFAULT) {
1090 fileFormat = getDefaultFileFormatForImageFormat(format);
1091 }
1092 switch (fileFormat) {
1093 case FileFormat::PNG: {
1094 if (format == ImageFormat::RGB888) {
1095 stbi_write_png_to_func(stbWriteFunc, &out, width, height, sizeof(ImagePixel::RGB888), imageData.data(), 0);
1096 } else if (format == ImageFormat::RGBA8888) {
1097 stbi_write_png_to_func(stbWriteFunc, &out, width, height, sizeof(ImagePixel::RGBA8888), imageData.data(), 0);
1098 } else if (ImageFormatDetails::opaque(format)) {
1099 const auto rgb = convertImageDataToFormat(imageData, format, ImageFormat::RGB888, width, height);
1100 stbi_write_png_to_func(stbWriteFunc, &out, width, height, sizeof(ImagePixel::RGB888), rgb.data(), 0);
1101 } else {
1102 const auto rgba = convertImageDataToFormat(imageData, format, ImageFormat::RGBA8888, width, height);
1103 stbi_write_png_to_func(stbWriteFunc, &out, width, height, sizeof(ImagePixel::RGBA8888), rgba.data(), 0);
1104 }
1105 break;
1106 }
1107 case FileFormat::JPG: {
1108 if (format == ImageFormat::RGB888) {
1109 stbi_write_jpg_to_func(stbWriteFunc, &out, width, height, sizeof(ImagePixel::RGB888), imageData.data(), 95);
1110 } else {
1111 const auto rgb = convertImageDataToFormat(imageData, format, ImageFormat::RGB888, width, height);
1112 stbi_write_jpg_to_func(stbWriteFunc, &out, width, height, sizeof(ImagePixel::RGB888), rgb.data(), 95);
1113 }
1114 break;
1115 }
1116 case FileFormat::BMP: {
1117 if (format == ImageFormat::RGB888) {
1118 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height, sizeof(ImagePixel::RGB888), imageData.data());
1119 } else if (format == ImageFormat::RGBA8888) {
1120 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height, sizeof(ImagePixel::RGBA8888), imageData.data());
1121 } else if (ImageFormatDetails::opaque(format)) {
1122 const auto rgb = convertImageDataToFormat(imageData, format, ImageFormat::RGB888, width, height);
1123 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height, sizeof(ImagePixel::RGB888), rgb.data());
1124 } else {
1125 const auto rgba = convertImageDataToFormat(imageData, format, ImageFormat::RGBA8888, width, height);
1126 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height, sizeof(ImagePixel::RGBA8888), rgba.data());
1127 }
1128 break;
1129 }
1130 case FileFormat::TGA: {
1131 if (format == ImageFormat::RGB888) {
1132 stbi_write_tga_to_func(stbWriteFunc, &out, width, height, sizeof(ImagePixel::RGB888), imageData.data());
1133 } else if (format == ImageFormat::RGBA8888) {
1134 stbi_write_tga_to_func(stbWriteFunc, &out, width, height, sizeof(ImagePixel::RGBA8888), imageData.data());
1135 } else if (ImageFormatDetails::opaque(format)) {
1136 const auto rgb = convertImageDataToFormat(imageData, format, ImageFormat::RGB888, width, height);
1137 stbi_write_tga_to_func(stbWriteFunc, &out, width, height, sizeof(ImagePixel::RGB888), rgb.data());
1138 } else {
1139 const auto rgba = convertImageDataToFormat(imageData, format, ImageFormat::RGBA8888, width, height);
1140 stbi_write_tga_to_func(stbWriteFunc, &out, width, height, sizeof(ImagePixel::RGBA8888), rgba.data());
1141 }
1142 break;
1143 }
1144 case FileFormat::WEBP: {
1145 WebPConfig config;
1146 WebPConfigInit(&config);
1147 WebPConfigPreset(&config, WEBP_PRESET_DRAWING, 75.f);
1148 WebPConfigLosslessPreset(&config, 6);
1149
1150 WebPPicture pic;
1151 if (!WebPPictureInit(&pic)) {
1152 return {};
1153 }
1154 pic.width = width;
1155 pic.height = height;
1156 if (!WebPPictureAlloc(&pic)) {
1157 return {};
1158 }
1159
1160 if (format == ImageFormat::RGB888) {
1161 WebPPictureImportRGB(&pic, reinterpret_cast<const uint8_t*>(imageData.data()), static_cast<int>(width * sizeof(ImagePixel::RGB888)));
1162 } else if (format == ImageFormat::RGBA8888) {
1163 WebPPictureImportRGBA(&pic, reinterpret_cast<const uint8_t*>(imageData.data()), static_cast<int>(width * sizeof(ImagePixel::RGBA8888)));
1164 } else if (ImageFormatDetails::opaque(format)) {
1165 const auto rgb = convertImageDataToFormat(imageData, format, ImageFormat::RGB888, width, height);
1166 WebPPictureImportRGB(&pic, reinterpret_cast<const uint8_t*>(rgb.data()), static_cast<int>(width * sizeof(ImagePixel::RGB888)));
1167 } else {
1168 const auto rgba = convertImageDataToFormat(imageData, format, ImageFormat::RGBA8888, width, height);
1169 WebPPictureImportRGBA(&pic, reinterpret_cast<const uint8_t*>(rgba.data()), static_cast<int>(width * sizeof(ImagePixel::RGBA8888)));
1170 }
1171
1172 WebPMemoryWriter writer;
1173 WebPMemoryWriterInit(&writer);
1174 pic.writer = &WebPMemoryWrite;
1175 pic.custom_ptr = &writer;
1176
1177 int ok = WebPEncode(&config, &pic);
1178 WebPPictureFree(&pic);
1179 if (!ok) {
1180 WebPMemoryWriterClear(&writer);
1181 return {};
1182 }
1183
1184 if (writer.mem && writer.size) {
1185 out.resize(writer.size);
1186 std::memcpy(out.data(), writer.mem, writer.size);
1187 }
1188 WebPMemoryWriterClear(&writer);
1189 break;
1190 }
1191 case FileFormat::QOI: {
1192 qoi_desc descriptor{
1193 .width = width,
1194 .height = height,
1195 .channels = 0,
1196 .colorspace = QOI_SRGB,
1197 };
1198 void* qoiData = nullptr;
1199 int qoiDataLen = 0;
1200 if (format == ImageFormat::RGB888) {
1201 descriptor.channels = 3;
1202 qoiData = qoi_encode(imageData.data(), &descriptor, &qoiDataLen);
1203 } else if (format == ImageFormat::RGBA8888) {
1204 descriptor.channels = 4;
1205 qoiData = qoi_encode(imageData.data(), &descriptor, &qoiDataLen);
1206 } else if (ImageFormatDetails::opaque(format)) {
1207 const auto rgb = convertImageDataToFormat(imageData, format, ImageFormat::RGB888, width, height);
1208 descriptor.channels = 3;
1209 qoiData = qoi_encode(rgb.data(), &descriptor, &qoiDataLen);
1210 } else {
1211 const auto rgba = convertImageDataToFormat(imageData, format, ImageFormat::RGBA8888, width, height);
1212 descriptor.channels = 4;
1213 qoiData = qoi_encode(rgba.data(), &descriptor, &qoiDataLen);
1214 }
1215 if (qoiData && qoiDataLen) {
1216 out.resize(qoiDataLen);
1217 std::memcpy(out.data(), qoiData, qoiDataLen);
1218 }
1219 std::free(qoiData);
1220 break;
1221 }
1222 case FileFormat::HDR: {
1223 if (format == ImageFormat::RGB323232F) {
1224 stbi_write_hdr_to_func(stbWriteFunc, &out, width, height, ImageFormatDetails::bpp(ImageFormat::RGB323232F) / (8 * sizeof(float)), reinterpret_cast<const float*>(imageData.data()));
1225 } else {
1226 const auto hdr = convertImageDataToFormat(imageData, format, ImageFormat::RGB323232F, width, height);
1227 stbi_write_hdr_to_func(stbWriteFunc, &out, width, height, ImageFormatDetails::bpp(ImageFormat::RGB323232F) / (8 * sizeof(float)), reinterpret_cast<const float*>(hdr.data()));
1228 }
1229 break;
1230 }
1231 case FileFormat::EXR: {
1232 EXRHeader header;
1233 InitEXRHeader(&header);
1234
1235 std::vector<std::byte> rawData;
1237 if (ImageFormatDetails::transparent(format)) {
1238 rawData = convertImageDataToFormat(imageData, format, ImageFormat::RGBA32323232F, width, height);
1239 format = ImageFormat::RGBA32323232F;
1240 } else {
1241 rawData = convertImageDataToFormat(imageData, format, ImageFormat::RGB323232F, width, height);
1242 format = ImageFormat::RGB323232F;
1243 }
1244 } else {
1245 rawData = {imageData.begin(), imageData.end()};
1246 }
1247
1248 header.num_channels = (ImageFormatDetails::red(format) > 0) + (ImageFormatDetails::green(format) > 0) + (ImageFormatDetails::blue(format) > 0) + (ImageFormatDetails::alpha(format) > 0);
1249 header.channels = static_cast<EXRChannelInfo*>(std::malloc(header.num_channels * sizeof(EXRChannelInfo)));
1250 header.pixel_types = static_cast<int*>(malloc(header.num_channels * sizeof(int)));
1251 header.requested_pixel_types = static_cast<int*>(malloc(header.num_channels * sizeof(int)));
1252
1253 switch (header.num_channels) {
1254 case 4:
1255 header.channels[0].name[0] = 'A';
1256 header.channels[1].name[0] = 'B';
1257 header.channels[2].name[0] = 'G';
1258 header.channels[3].name[0] = 'R';
1259 break;
1260 case 3:
1261 header.channels[0].name[0] = 'B';
1262 header.channels[1].name[0] = 'G';
1263 header.channels[2].name[0] = 'R';
1264 break;
1265 case 2:
1266 header.channels[0].name[0] = 'G';
1267 header.channels[1].name[0] = 'R';
1268 break;
1269 case 1:
1270 header.channels[0].name[0] = 'R';
1271 break;
1272 default:
1273 FreeEXRHeader(&header);
1274 return {};
1275 }
1276 for (int i = 0; i < header.num_channels; i++) {
1277 header.channels[i].name[1] = '\0';
1278 }
1279
1280 int pixelType = (ImageFormatDetails::red(format) / 8) == sizeof(half) ? TINYEXR_PIXELTYPE_HALF : TINYEXR_PIXELTYPE_FLOAT;
1281 for (int i = 0; i < header.num_channels; i++) {
1282 header.pixel_types[i] = pixelType;
1283 header.requested_pixel_types[i] = pixelType;
1284 }
1285
1286 std::vector<std::vector<std::byte>> images(header.num_channels);
1287 std::vector<void*> imagePtrs(header.num_channels);
1288 switch (header.num_channels) {
1289 case 4:
1290 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1295 } else {
1300 }
1301 break;
1302 case 3:
1303 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1304 // We should not be here!
1305 FreeEXRHeader(&header);
1306 return {};
1307 }
1311 break;
1312 case 2:
1313 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1314 images[0] = extractChannelFromImageData(imageData, &ImagePixel::RG1616F::g);
1315 images[1] = extractChannelFromImageData(imageData, &ImagePixel::RG1616F::r);
1316 } else {
1317 images[0] = extractChannelFromImageData(imageData, &ImagePixel::RG3232F::g);
1318 images[1] = extractChannelFromImageData(imageData, &ImagePixel::RG3232F::r);
1319 }
1320 break;
1321 case 1:
1322 images[0] = rawData;
1323 break;
1324 default:
1325 FreeEXRHeader(&header);
1326 return {};
1327 }
1328 for (int i = 0; i < header.num_channels; i++) {
1329 imagePtrs[i] = images[i].data();
1330 }
1331
1332 EXRImage image;
1333 InitEXRImage(&image);
1334 image.width = width;
1335 image.height = height;
1336 image.images = reinterpret_cast<unsigned char**>(imagePtrs.data());
1337 image.num_channels = header.num_channels;
1338
1339 unsigned char* data = nullptr;
1340 const char* err = nullptr;
1341
1342 size_t size = SaveEXRImageToMemory(&image, &header, &data, &err);
1343 if (err) {
1344 FreeEXRErrorMessage(err);
1345 FreeEXRHeader(&header);
1346 return {};
1347 }
1348 if (data) {
1349 out = {reinterpret_cast<std::byte*>(data), reinterpret_cast<std::byte*>(data) + size};
1350 std::free(data);
1351 }
1352
1353 FreeEXRHeader(&header);
1354 break;
1355 }
1356 case FileFormat::DEFAULT:
1357 break;
1358 }
1359 return out;
1360}
1361
1362namespace {
1363
1364template<typename T>
1365using stb_ptr = std::unique_ptr<T, void(*)(void*)>;
1366
1367} // namespace
1368
1369std::vector<std::byte> ImageConversion::convertFileToImageData(std::span<const std::byte> fileData, ImageFormat& format, int& width, int& height, int& frameCount) {
1370 stbi_convert_iphone_png_to_rgb(true);
1371
1372 format = ImageFormat::EMPTY;
1373 width = 0;
1374 height = 0;
1375 int channels = 0;
1376 frameCount = 1;
1377
1378 // EXR
1379 if (EXRVersion version; ParseEXRVersionFromMemory(&version, reinterpret_cast<const unsigned char*>(fileData.data()), fileData.size()) == TINYEXR_SUCCESS) {
1380 if (version.multipart || version.non_image) {
1381 return {};
1382 }
1383
1384 EXRHeader header;
1385 InitEXRHeader(&header);
1386 const char* err = nullptr;
1387 if (ParseEXRHeaderFromMemory(&header, &version, reinterpret_cast<const unsigned char*>(fileData.data()), fileData.size(), &err) != TINYEXR_SUCCESS) {
1388 FreeEXRErrorMessage(err);
1389 return {};
1390 }
1391
1392 // Sanity check
1393 if (header.num_channels < 1) {
1394 FreeEXRHeader(&header);
1395 return {};
1396 }
1397
1398 // Define the channel names we support (RGBA, greyscale)
1399 std::unordered_map<std::string_view, int> channelIndices{{"R", -1}, {"G", -1}, {"B", -1}, {"A", -1}, {"Y", -1}};
1400
1401 // Get channel type (EXR supports different types per channel, we do not)
1402 // Rather than bailing we ask EXR to convert the lowest precision data
1403 auto channelType = header.pixel_types[0];
1404 for (int i = 1; i < header.num_channels; i++) {
1405 // UINT -> HALF -> FLOAT
1406 if (header.pixel_types[i] > channelType && channelIndices.contains(header.channels[i].name)) {
1407 channelType = header.pixel_types[i];
1408 }
1409 }
1410 // requested_pixel_types field only supports floats
1411 if (channelType == TINYEXR_PIXELTYPE_UINT) {
1412 channelType = TINYEXR_PIXELTYPE_HALF;
1413 }
1414
1415 // Determine proper format to use
1416 for (int i = 0; i < header.num_channels; i++) {
1417 if (channelIndices.contains(header.channels[i].name)) {
1418 channelIndices[header.channels[i].name] = i;
1419 }
1420 }
1421 if (channelIndices["Y"] >= 0) {
1422 if (channelIndices["A"] >= 0) {
1423 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::RGBA16161616F : ImageFormat::RGBA32323232F;
1424 } else {
1425 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1426 // VTF has no RGB161616F
1427 channelType = TINYEXR_PIXELTYPE_FLOAT;
1428 }
1429 format = ImageFormat::RGB323232F;
1430 }
1431 channelIndices["R"] = channelIndices["Y"];
1432 channelIndices["G"] = channelIndices["Y"];
1433 channelIndices["B"] = channelIndices["Y"];
1434 } else if (channelIndices["A"] >= 0) {
1435 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::RGBA16161616F : ImageFormat::RGBA32323232F;
1436 } else if (channelIndices["B"] >= 0) {
1437 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1438 // VTF has no RGB161616F
1439 channelType = TINYEXR_PIXELTYPE_FLOAT;
1440 }
1441 format = ImageFormat::RGB323232F;
1442 } else if (channelIndices["G"] >= 0) {
1443 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::RG1616F : ImageFormat::RG3232F;
1444 } else if (channelIndices["R"] >= 0) {
1445 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::R16F : ImageFormat::R32F;
1446 } else {
1447 FreeEXRHeader(&header);
1448 return {};
1449 }
1450
1451 // Now that channelType has stopped changing, we can set it properly
1452 for (int i = 0; i < header.num_channels; i++) {
1453 if (header.pixel_types[i] != channelType && channelIndices.contains(header.channels[i].name)) {
1454 header.requested_pixel_types[i] = channelType;
1455 }
1456 }
1457
1458 EXRImage image;
1459 InitEXRImage(&image);
1460 if (LoadEXRImageFromMemory(&image, &header, reinterpret_cast<const unsigned char*>(fileData.data()), fileData.size(), &err) != TINYEXR_SUCCESS) {
1461 FreeEXRErrorMessage(err);
1462 FreeEXRHeader(&header);
1463 return {};
1464 }
1465
1466 width = image.width;
1467 height = image.height;
1468
1469 // Merge channel data into a single buffer
1470 std::vector<std::byte> combinedChannels(width * height * (ImageFormatDetails::bpp(format) / 8));
1471 const auto populateBuffer = [
1472 hasRed=ImageFormatDetails::red(format) > 0,
1473 hasGreen=ImageFormatDetails::green(format) > 0,
1474 hasBlue=ImageFormatDetails::blue(format) > 0,
1475 hasAlpha=ImageFormatDetails::alpha(format) > 0,
1476 width,
1477 height,
1478 &header,
1479 r=channelIndices["R"],
1480 g=channelIndices["G"],
1481 b=channelIndices["B"],
1482 a=channelIndices["A"],
1483 &image,
1484 &combinedChannels
1485 ]<typename C> {
1486 const auto channelCount = hasRed + hasGreen + hasBlue + hasAlpha;
1487 std::span out{reinterpret_cast<C*>(combinedChannels.data()), combinedChannels.size() / sizeof(C)};
1488 if (header.tiled) {
1489 for (int t = 0; t < image.num_tiles; t++) {
1490 auto** src = reinterpret_cast<C**>(image.tiles[t].images);
1491 for (int j = 0; j < header.tile_size_y; j++) {
1492 for (int i = 0; i < header.tile_size_x; i++) {
1493 const auto ii = static_cast<uint64_t>(image.tiles[t].offset_x) * header.tile_size_x + i;
1494 const auto jj = static_cast<uint64_t>(image.tiles[t].offset_y) * header.tile_size_y + j;
1495 const auto idx = ii + jj * image.width;
1496
1497 if (ii >= image.width || jj >= image.height) {
1498 continue;
1499 }
1500
1501 const auto srcIdx = j * static_cast<uint64_t>(header.tile_size_x) + i;
1502 if (r >= 0) out[idx * channelCount + 0] = src[r][srcIdx];
1503 else if (hasRed) out[idx * channelCount + 0] = 0.f;
1504 if (g >= 0) out[idx * channelCount + 1] = src[g][srcIdx];
1505 else if (hasGreen) out[idx * channelCount + 1] = 0.f;
1506 if (b >= 0) out[idx * channelCount + 2] = src[b][srcIdx];
1507 else if (hasBlue) out[idx * channelCount + 2] = 0.f;
1508 if (a >= 0) out[idx * channelCount + 3] = src[a][srcIdx];
1509 else if (hasAlpha) out[idx * channelCount + 3] = 1.f;
1510 }
1511 }
1512 }
1513 } else {
1514 auto** src = reinterpret_cast<C**>(image.images);
1515 for (uint64_t i = 0; i < width * height; i++) {
1516 if (r >= 0) out[i * channelCount + 0] = src[r][i];
1517 else if (hasRed) out[i * channelCount + 0] = 0.f;
1518 if (g >= 0) out[i * channelCount + 1] = src[g][i];
1519 else if (hasGreen) out[i * channelCount + 1] = 0.f;
1520 if (b >= 0) out[i * channelCount + 2] = src[b][i];
1521 else if (hasBlue) out[i * channelCount + 2] = 0.f;
1522 if (a >= 0) out[i * channelCount + 3] = src[a][i];
1523 else if (hasAlpha) out[i * channelCount + 3] = 1.f;
1524 }
1525 }
1526 };
1527 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1528 populateBuffer.operator()<half>();
1529 } else {
1530 populateBuffer.operator()<float>();
1531 }
1532
1533 FreeEXRImage(&image);
1534 FreeEXRHeader(&header);
1535 return combinedChannels;
1536 }
1537
1538 // HDR
1539 if (stbi_is_hdr_from_memory(reinterpret_cast<const stbi_uc*>(fileData.data()), static_cast<int>(fileData.size()))) {
1540 const ::stb_ptr<float> stbImage{
1541 stbi_loadf_from_memory(reinterpret_cast<const stbi_uc*>(fileData.data()), static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1542 &stbi_image_free,
1543 };
1544 if (!stbImage) {
1545 return {};
1546 }
1547 switch (channels) {
1548 case 1: format = ImageFormat::R32F; break;
1549 case 2: format = ImageFormat::RG3232F; break;
1550 case 3: format = ImageFormat::RGB323232F; break;
1551 case 4: format = ImageFormat::RGBA32323232F; break;
1552 default: return {};
1553 }
1554 return {reinterpret_cast<std::byte*>(stbImage.get()), reinterpret_cast<std::byte*>(stbImage.get()) + ImageFormatDetails::getDataLength(format, width, height)};
1555 }
1556
1557 // WebP
1558 if (WebPBitstreamFeatures features; fileData.size() > 12 && static_cast<char>(fileData[8]) == 'W' && static_cast<char>(fileData[9]) == 'E' && static_cast<char>(fileData[10]) == 'B' && static_cast<char>(fileData[11]) == 'P' && WebPGetFeatures(reinterpret_cast<const uint8_t*>(fileData.data()), fileData.size(), &features) == VP8_STATUS_OK) {
1559 width = features.width;
1560 height = features.height;
1561 // We don't process animated WebP right now
1562 frameCount = 1;
1563
1564 std::vector<std::byte> out;
1565 if (features.has_alpha) {
1566 format = ImageFormat::RGBA8888;
1567 out.resize(width * height * (ImageFormatDetails::bpp(format) / 8));
1568 if (!WebPDecodeRGBAInto(reinterpret_cast<const uint8_t*>(fileData.data()), fileData.size(), reinterpret_cast<uint8_t*>(out.data()), out.size(), width * (ImageFormatDetails::bpp(format) / 8))) {
1569 return {};
1570 }
1571 } else {
1572 format = ImageFormat::RGB888;
1573 out.resize(width * height * (ImageFormatDetails::bpp(format) / 8));
1574 if (!WebPDecodeRGBInto(reinterpret_cast<const uint8_t*>(fileData.data()), fileData.size(), reinterpret_cast<uint8_t*>(out.data()), out.size(), width * (ImageFormatDetails::bpp(format) / 8))) {
1575 return {};
1576 }
1577 }
1578 return out;
1579 }
1580
1581 // GIF
1582 if (fileData.size() > 3 && static_cast<char>(fileData[0]) == 'G' && static_cast<char>(fileData[1]) == 'I' && static_cast<char>(fileData[2]) == 'F') {
1583 const ::stb_ptr<stbi_uc> stbImage{
1584 stbi_load_gif_from_memory(reinterpret_cast<const stbi_uc*>(fileData.data()), static_cast<int>(fileData.size()), nullptr, &width, &height, &frameCount, &channels, 0),
1585 &stbi_image_free,
1586 };
1587 if (!stbImage || !frameCount) {
1588 return {};
1589 }
1590 switch (channels) {
1591 case 1: format = ImageFormat::I8; break;
1592 case 2: format = ImageFormat::UV88; break;
1593 case 3: format = ImageFormat::RGB888; break;
1594 case 4: format = ImageFormat::RGBA8888; break;
1595 default: return {};
1596 }
1597 return {reinterpret_cast<std::byte*>(stbImage.get()), reinterpret_cast<std::byte*>(stbImage.get() + (ImageFormatDetails::getDataLength(format, width, height) * frameCount))};
1598 }
1599
1600 // APNG
1601 {
1602 stbi__context s;
1603 stbi__start_mem(&s, reinterpret_cast<const stbi_uc*>(fileData.data()), static_cast<int>(fileData.size()));
1604 if (stbi__png_test(&s)) {
1605 // We know it's a PNG, but is it an APNG? You'll have to scroll past the decoder to find out!
1606 const auto apngDecoder = [&format, &width, &height, &frameCount]<typename P>(const auto& stbImage, std::size_t dirOffset) -> std::vector<std::byte> {
1607 auto* dir = reinterpret_cast<stbi__apng_directory*>(stbImage.get() + dirOffset);
1608 if (dir->type != STBI__STRUCTURE_TYPE_APNG_DIRECTORY) {
1609 return {}; // Malformed
1610 }
1611
1612 format = P::FORMAT;
1613 frameCount = static_cast<int>(dir->num_frames);
1614
1615 static constexpr auto calcPixelOffset = [](uint32_t offsetX, uint32_t offsetY, uint32_t width) {
1616 return ((offsetY * width) + offsetX) * sizeof(P);
1617 };
1618
1619 // Where dst is a full frame and src is a subregion
1620 static constexpr auto copyImageData = [](std::span<std::byte> dst, uint32_t dstWidth, uint32_t dstHeight, std::span<const std::byte> src, uint32_t srcWidth, uint32_t srcHeight, uint32_t srcOffsetX, uint32_t srcOffsetY) {
1621 for (uint32_t y = 0; y < srcHeight; y++) {
1622 std::copy(
1623#ifdef SOURCEPP_BUILD_WITH_TBB
1624 std::execution::unseq,
1625#endif
1626 src.data() + calcPixelOffset( 0, y, srcWidth),
1627 src.data() + calcPixelOffset( srcWidth, y, srcWidth),
1628 dst.data() + calcPixelOffset(srcOffsetX, srcOffsetY + y, dstWidth));
1629 }
1630 };
1631
1632 // Where dst and src are the same size and we are copying a subregion
1633 static constexpr auto copyImageSubRectData = [](std::span<std::byte> dst, std::span<const std::byte> src, uint32_t imgWidth, uint32_t imgHeight, uint32_t subWidth, uint32_t subHeight, uint32_t subOffsetX, uint32_t subOffsetY) {
1634 for (uint32_t y = subOffsetY; y < subOffsetY + subHeight; y++) {
1635 std::copy(
1636#ifdef SOURCEPP_BUILD_WITH_TBB
1637 std::execution::unseq,
1638#endif
1639 src.data() + calcPixelOffset(subOffsetX, y, imgWidth),
1640 src.data() + calcPixelOffset(subOffsetX + subWidth, y, imgWidth),
1641 dst.data() + calcPixelOffset(subOffsetX, y, imgWidth));
1642 }
1643 };
1644
1645 static constexpr auto clearImageData = [](std::span<std::byte> dst, uint32_t dstWidth, uint32_t dstHeight, uint32_t clrWidth, uint32_t clrHeight, uint32_t clrOffsetX, uint32_t clrOffsetY) {
1646 for (uint32_t y = 0; y < clrHeight; y++) {
1647 std::transform(
1648#ifdef SOURCEPP_BUILD_WITH_TBB
1649 std::execution::unseq,
1650#endif
1651 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth),
1652 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth) + (clrWidth * sizeof(P)),
1653 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth),
1654 [](std::byte) { return std::byte{0}; });
1655 }
1656 };
1657
1658 static constexpr auto overlayImageData = [](std::span<std::byte> dst, uint32_t dstWidth, uint32_t dstHeight, std::span<const std::byte> src, uint32_t srcWidth, uint32_t srcHeight, uint32_t srcOffsetX, uint32_t srcOffsetY) {
1659 for (uint32_t y = 0; y < srcHeight; y++) {
1660 const auto* sp = reinterpret_cast<const uint8_t*>(src.data() + calcPixelOffset(0, y, srcWidth));
1661 auto* dp = reinterpret_cast<uint8_t*>(dst.data() + calcPixelOffset(srcOffsetX, srcOffsetY + y, dstWidth));
1662 for (uint32_t x = 0; x < srcWidth; x++, sp += 4, dp += 4) {
1663 if (sp[3] == 0) {
1664 continue;
1665 } else if ((sp[3] == 0xff) || (dp[3] == 0)) {
1666 std::copy(sp, sp + sizeof(P), dp);
1667 } else {
1668 int u = sp[3] * 0xff;
1669 int v = (0xff - sp[3]) * dp[3];
1670 int al = u + v;
1671 dp[0] = (sp[0] * u + dp[0] * v) / al;
1672 dp[1] = (sp[1] * u + dp[1] * v) / al;
1673 dp[2] = (sp[2] * u + dp[2] * v) / al;
1674 dp[3] = al / 0xff;
1675 }
1676 }
1677 }
1678 };
1679
1680 // https://wiki.mozilla.org/APNG_Specification
1681 const uint64_t fullFrameSize = sizeof(P) * width * height;
1682 uint64_t currentFrameSize = 0;
1683 std::vector<std::byte> out(fullFrameSize * frameCount);
1684 uint64_t srcFrameOffset = 0;
1685 uint64_t dstFrameOffset = 0;
1686 for (uint32_t i = 0; i < dir->num_frames; i++) {
1687 const auto& frame = dir->frames[i];
1688 currentFrameSize = sizeof(P) * frame.width * frame.height;
1689
1690 // If the parameters are perfect we can memcpy all the data in
1691 if (frame.width == width && frame.height == height && frame.x_offset == 0 && frame.y_offset == 0 && frame.blend_op == STBI_APNG_blend_op_source) {
1692 std::memcpy(out.data() + dstFrameOffset, stbImage.get() + srcFrameOffset, fullFrameSize);
1693 } else {
1694 // Check the blend op
1695 if (frame.blend_op == STBI_APNG_blend_op_source || (i == 0 && frame.blend_op == STBI_APNG_blend_op_over)) {
1696 copyImageData({out.data() + dstFrameOffset, out.data() + dstFrameOffset + fullFrameSize}, width, height, {reinterpret_cast<const std::byte*>(stbImage.get() + srcFrameOffset), reinterpret_cast<const std::byte*>(stbImage.get() + srcFrameOffset + currentFrameSize)}, frame.width, frame.height, frame.x_offset, frame.y_offset);
1697 } else if (frame.blend_op == STBI_APNG_blend_op_over) {
1698 overlayImageData({out.data() + dstFrameOffset, out.data() + dstFrameOffset + fullFrameSize}, width, height, {reinterpret_cast<const std::byte*>(stbImage.get() + srcFrameOffset), reinterpret_cast<const std::byte*>(stbImage.get() + srcFrameOffset + currentFrameSize)}, frame.width, frame.height, frame.x_offset, frame.y_offset);
1699 } else {
1700 return {}; // Malformed
1701 }
1702 }
1703
1704 dstFrameOffset += fullFrameSize;
1705 srcFrameOffset += currentFrameSize;
1706
1707 // Bail here if this is the last frame
1708 if (i == dir->num_frames - 1) {
1709 continue;
1710 }
1711
1712 // Copy over this frame to the next one
1713 copyImageData({out.data() + dstFrameOffset, out.data() + dstFrameOffset + fullFrameSize}, width, height, {out.data() + dstFrameOffset - fullFrameSize, out.data() + dstFrameOffset}, width, height, 0, 0);
1714
1715 // Check the dispose op to see what to do about the frame's region for the next frame, if there is one
1716 if (frame.dispose_op == STBI_APNG_dispose_op_background || (i == 0 && frame.dispose_op == STBI_APNG_dispose_op_previous)) {
1717 clearImageData({out.data() + dstFrameOffset, out.data() + dstFrameOffset + fullFrameSize}, width, height, frame.width, frame.height, frame.x_offset, frame.y_offset);
1718 } else if (frame.dispose_op == STBI_APNG_dispose_op_previous) {
1719 copyImageSubRectData({out.data() + dstFrameOffset, out.data() + dstFrameOffset + fullFrameSize}, {out.data() + dstFrameOffset - fullFrameSize, out.data() + dstFrameOffset}, width, height, frame.width, frame.height, frame.x_offset, frame.y_offset);
1720 } else if (frame.dispose_op != STBI_APNG_dispose_op_none) {
1721 return {}; // Malformed
1722 }
1723 }
1724#if 0
1725 // Debug code from https://gist.github.com/jcredmond/9ef711b406e42a250daa3797ce96fd26
1726
1727 static const char *dispose_ops[] = {
1728 "STBI_APNG_dispose_op_none", // leave the old frame
1729 "STBI_APNG_dispose_op_background", // clear frame's region to black transparent
1730 "STBI_APNG_dispose_op_previous", // frame's region should be reverted to prior frame before adding new one - if first frame, clear region to black transparent
1731 };
1732
1733 static const char *blend_ops[] = {
1734 "STBI_APNG_blend_op_source", // all color, including alpha, overwrites prior image
1735 "STBI_APNG_blend_op_over", // composited onto the output buffer with algorithm
1736 };
1737
1738 fprintf(stderr, "dir_offset : %zu\n", dirOffset);
1739 fprintf(stderr, "dir.type : %.*s\n", 4, (unsigned char *) &dir->type);
1740 fprintf(stderr, "dir.num_frames : %u\n", dir->num_frames);
1741 fprintf(stderr, "dir.default_image_is_first_frame : %s\n",
1742 dir->default_image_is_first_frame ? "yes" : "no");
1743 fprintf(stderr, "dir.num_plays : %u\n", dir->num_plays);
1744
1745 for (int i = 0; i < dir->num_frames; ++i) {
1746 stbi__apng_frame_directory_entry *frame = &dir->frames[i];
1747
1748 fprintf(stderr, "frame : %u\n", i);
1749 fprintf(stderr, " width : %u\n", frame->width);
1750 fprintf(stderr, " height : %u\n", frame->height);
1751 fprintf(stderr, " x_offset : %u\n", frame->x_offset);
1752 fprintf(stderr, " y_offset : %u\n", frame->y_offset);
1753 fprintf(stderr, " delay_num : %u\n", frame->delay_num);
1754 fprintf(stderr, " delay_den : %u\n", frame->delay_den);
1755 fprintf(stderr, " dispose_op : %s\n", dispose_ops[frame->dispose_op]);
1756 fprintf(stderr, " blend_op : %s\n", blend_ops[frame->blend_op]);
1757 }
1758#endif
1759 return out;
1760 };
1761
1762 std::size_t dirOffset = 0;
1763 if (stbi__png_is16(&s)) {
1764 const ::stb_ptr<stbi_us> stbImage{
1765 stbi__apng_load_16bit(&s, &width, &height, &channels, STBI_rgb_alpha, &dirOffset),
1766 &stbi_image_free,
1767 };
1768 if (stbImage && dirOffset) {
1769 return apngDecoder.template operator()<ImagePixel::RGBA16161616>(stbImage, dirOffset);
1770 }
1771 } else {
1772 const ::stb_ptr<stbi_uc> stbImage{
1773 stbi__apng_load_8bit(&s, &width, &height, &channels, STBI_rgb_alpha, &dirOffset),
1774 &stbi_image_free,
1775 };
1776 if (stbImage && dirOffset) {
1777 return apngDecoder.template operator()<ImagePixel::RGBA8888>(stbImage, dirOffset);
1778 }
1779 }
1780 }
1781 }
1782
1783 // QOI header
1784 if (fileData.size() >= 26 && *reinterpret_cast<const uint32_t*>(fileData.data()) == parser::binary::makeFourCC("fioq")) {
1785 qoi_desc descriptor;
1786 const ::stb_ptr<std::byte> qoiImage{
1787 static_cast<std::byte*>(qoi_decode(fileData.data(), fileData.size(), &descriptor, 0)),
1788 &std::free,
1789 };
1790 if (!qoiImage) {
1791 return {};
1792 }
1793 width = descriptor.width;
1794 height = descriptor.height;
1795 channels = descriptor.channels;
1796 switch (channels) {
1797 case 3: format = ImageFormat::RGB888; break;
1798 case 4: format = ImageFormat::RGBA8888; break;
1799 default: return {};
1800 }
1801 return {qoiImage.get(), qoiImage.get() + ImageFormatDetails::getDataLength(format, width, height)};
1802 }
1803
1804 // 16-bit single-frame image
1805 if (stbi_is_16_bit_from_memory(reinterpret_cast<const stbi_uc*>(fileData.data()), static_cast<int>(fileData.size()))) {
1806 const ::stb_ptr<stbi_us> stbImage{
1807 stbi_load_16_from_memory(reinterpret_cast<const stbi_uc*>(fileData.data()), static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1808 &stbi_image_free,
1809 };
1810 if (!stbImage) {
1811 return {};
1812 }
1813 if (channels == 4) {
1814 format = ImageFormat::RGBA16161616;
1815 } else if (channels >= 1 && channels < 4) {
1816 // There are no other 16-bit integer formats in Source, so we have to do a conversion here
1817 format = ImageFormat::RGBA16161616;
1818 std::vector<std::byte> out(ImageFormatDetails::getDataLength(format, width, height));
1819 std::span<ImagePixel::RGBA16161616> outPixels{reinterpret_cast<ImagePixel::RGBA16161616*>(out.data()), out.size() / sizeof(ImagePixel::RGBA16161616)};
1820 switch (channels) {
1821 case 1: {
1822 std::span<uint16_t> inPixels{reinterpret_cast<uint16_t*>(stbImage.get()), outPixels.size()};
1823 std::transform(
1824#ifdef SOURCEPP_BUILD_WITH_TBB
1825 std::execution::par_unseq,
1826#endif
1827 inPixels.begin(), inPixels.end(), outPixels.begin(), [](uint16_t pixel) -> ImagePixel::RGBA16161616 {
1828 return {pixel, 0, 0, 0xffff};
1829 });
1830 return out;
1831 }
1832 case 2: {
1833 struct RG1616 {
1834 uint16_t r;
1835 uint16_t g;
1836 };
1837 std::span<RG1616> inPixels{reinterpret_cast<RG1616*>(stbImage.get()), outPixels.size()};
1838 std::transform(
1839#ifdef SOURCEPP_BUILD_WITH_TBB
1840 std::execution::par_unseq,
1841#endif
1842 inPixels.begin(), inPixels.end(), outPixels.begin(), [](RG1616 pixel) -> ImagePixel::RGBA16161616 {
1843 return {pixel.r, pixel.g, 0, 0xffff};
1844 });
1845 return out;
1846 }
1847 case 3: {
1848 struct RGB161616 {
1849 uint16_t r;
1850 uint16_t g;
1851 uint16_t b;
1852 };
1853 std::span<RGB161616> inPixels{reinterpret_cast<RGB161616*>(stbImage.get()), outPixels.size()};
1854 std::transform(
1855#ifdef SOURCEPP_BUILD_WITH_TBB
1856 std::execution::par_unseq,
1857#endif
1858 inPixels.begin(), inPixels.end(), outPixels.begin(), [](RGB161616 pixel) -> ImagePixel::RGBA16161616 {
1859 return {pixel.r, pixel.g, pixel.b, 0xffff};
1860 });
1861 return out;
1862 }
1863 default:
1864 return {};
1865 }
1866 } else {
1867 return {};
1868 }
1869 return {reinterpret_cast<std::byte*>(stbImage.get()), reinterpret_cast<std::byte*>(stbImage.get()) + ImageFormatDetails::getDataLength(format, width, height)};
1870 }
1871
1872 // 8-bit or less single frame image
1873 const ::stb_ptr<stbi_uc> stbImage{
1874 stbi_load_from_memory(reinterpret_cast<const stbi_uc*>(fileData.data()), static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1875 &stbi_image_free,
1876 };
1877 if (!stbImage) {
1878 return {};
1879 }
1880 switch (channels) {
1881 case 1: format = ImageFormat::I8; break;
1882 case 2: format = ImageFormat::UV88; break;
1883 case 3: format = ImageFormat::RGB888; break;
1884 case 4: format = ImageFormat::RGBA8888; break;
1885 default: return {};
1886 }
1887 return {reinterpret_cast<std::byte*>(stbImage.get()), reinterpret_cast<std::byte*>(stbImage.get()) + ImageFormatDetails::getDataLength(format, width, height)};
1888}
1889
1890uint16_t ImageConversion::getResizedDim(uint16_t n, ResizeMethod method) {
1891 switch (method) {
1892 case ResizeMethod::NONE: break;
1893 case ResizeMethod::POWER_OF_TWO_BIGGER: return std::bit_ceil(n);
1894 case ResizeMethod::POWER_OF_TWO_SMALLER: return std::bit_floor(n);
1895 case ResizeMethod::POWER_OF_TWO_NEAREST: return math::nearestPowerOf2(n);
1896 }
1897 return n;
1898}
1899
1900void ImageConversion::setResizedDims(uint16_t& width, ResizeMethod widthResize, uint16_t& height, ResizeMethod heightResize) {
1901 width = getResizedDim(width, widthResize);
1902 height = getResizedDim(height, heightResize);
1903}
1904
1905std::vector<std::byte> ImageConversion::resizeImageData(std::span<const std::byte> imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t height, uint16_t newHeight, bool srgb, ResizeFilter filter, ResizeEdge edge) {
1906 if (imageData.empty() || format == ImageFormat::EMPTY) {
1907 return {};
1908 }
1909
1910 STBIR_RESIZE resize;
1911 const auto setEdgeModesAndFiltersAndDoResize = [edge, filter, &resize] {
1912 stbir_set_edgemodes(&resize, static_cast<stbir_edge>(edge), static_cast<stbir_edge>(edge));
1913 switch (filter) {
1914 case ResizeFilter::DEFAULT:
1915 case ResizeFilter::BOX:
1916 case ResizeFilter::BILINEAR:
1917 case ResizeFilter::CUBIC_BSPLINE:
1918 case ResizeFilter::CATMULL_ROM:
1919 case ResizeFilter::MITCHELL:
1920 case ResizeFilter::POINT_SAMPLE: {
1921 stbir_set_filters(&resize, static_cast<stbir_filter>(filter), static_cast<stbir_filter>(filter));
1922 break;
1923 }
1924 case ResizeFilter::KAISER: {
1925 static constexpr auto KAISER_BETA = [](float s) {
1926 if (s >= 1.f) {
1927 return 5.f;
1928 }
1929 if (s >= 0.5f) {
1930 return 6.5f;
1931 }
1932 return 8.f;
1933 };
1934 static constexpr auto KAISER_FILTER = [](float x, float s, void*) -> float {
1935 if (x < -1.f || x > 1.f) {
1936 return 0.f;
1937 }
1938 return static_cast<float>(math::kaiserWindow(x * s, KAISER_BETA(s)));
1939 };
1940 static constexpr auto KAISER_SUPPORT = [](float s, void*) -> float {
1941 float baseSupport = KAISER_BETA(s) / 2.f;
1942 if (s > 1.f) {
1943 return std::max(2.f, baseSupport - 0.5f);
1944 }
1945 return std::max(3.f, baseSupport);
1946 };
1947 stbir_set_filter_callbacks(&resize, KAISER_FILTER, KAISER_SUPPORT, KAISER_FILTER, KAISER_SUPPORT);
1948 break;
1949 }
1950 case ResizeFilter::NICE: {
1951 static constexpr auto SINC = [](float x) -> float {
1952 if (x == 0.f) return 1.f;
1953 const float a = x * math::pi_f32;
1954 return sinf(a) / a;
1955 };
1956 static constexpr auto NICE_FILTER = [](float x, float, void*) -> float {
1957 if (x >= 3.f || x <= -3.f) return 0.f;
1958 return SINC(x) * SINC(x / 3.f);
1959 };
1960 // Normally you would max(1, invScale), but stb is weird and immediately un-scales the result when downsampling.
1961 // See stb_image_resize2.h L2977 (stbir__get_filter_pixel_width)
1962 static constexpr auto NICE_SUPPORT = [](float invScale, void*) -> float {
1963 return invScale * 3.f;
1964 };
1965 stbir_set_filter_callbacks(&resize, NICE_FILTER, NICE_SUPPORT, NICE_FILTER, NICE_SUPPORT);
1966 break;
1967 }
1968 }
1969 stbir_resize_extended(&resize);
1970 };
1971
1972 const auto pixelLayout = ::imageFormatToSTBIRPixelLayout(format);
1973 if (pixelLayout == -1) {
1974 const auto containerFormat = ImageFormatDetails::containerFormat(format);
1975 const auto in = convertImageDataToFormat(imageData, format, containerFormat, width, height);
1976 std::vector<std::byte> intermediary(ImageFormatDetails::getDataLength(containerFormat, newWidth, newHeight));
1977 stbir_resize_init(&resize, in.data(), width, height, ImageFormatDetails::bpp(containerFormat) / 8 * width, intermediary.data(), newWidth, newHeight, ImageFormatDetails::bpp(containerFormat) / 8 * newWidth, static_cast<stbir_pixel_layout>(::imageFormatToSTBIRPixelLayout(containerFormat)), static_cast<stbir_datatype>(::imageFormatToSTBIRDataType(containerFormat, srgb)));
1978 setEdgeModesAndFiltersAndDoResize();
1979 return convertImageDataToFormat(intermediary, containerFormat, format, newWidth, newHeight);
1980 }
1981 std::vector<std::byte> out(ImageFormatDetails::getDataLength(format, newWidth, newHeight));
1982 stbir_resize_init(&resize, imageData.data(), width, height, ImageFormatDetails::bpp(format) / 8 * width, out.data(), newWidth, newHeight, ImageFormatDetails::bpp(format) / 8 * newWidth, static_cast<stbir_pixel_layout>(pixelLayout), static_cast<stbir_datatype>(::imageFormatToSTBIRDataType(format, srgb)));
1983 setEdgeModesAndFiltersAndDoResize();
1984 return out;
1985}
1986
1987std::vector<std::byte> ImageConversion::resizeImageDataStrict(std::span<const std::byte> imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t& widthOut, ResizeMethod widthResize, uint16_t height, uint16_t newHeight, uint16_t& heightOut, ResizeMethod heightResize, bool srgb, ResizeFilter filter, ResizeEdge edge) {
1988 if (imageData.empty() || format == ImageFormat::EMPTY) {
1989 return {};
1990 }
1991 widthOut = getResizedDim(newWidth, widthResize);
1992 heightOut = getResizedDim(newHeight, heightResize);
1993 return resizeImageData(imageData, format, width, widthOut, height, heightOut, srgb, filter, edge);
1994}
1995
1996// NOLINTNEXTLINE(*-no-recursion)
1997std::vector<std::byte> ImageConversion::cropImageData(std::span<const std::byte> imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t xOffset, uint16_t height, uint16_t newHeight, uint16_t yOffset) {
1998 if (imageData.empty() || format == ImageFormat::EMPTY || xOffset + newWidth >= width || yOffset + newHeight >= height) {
1999 return {};
2000 }
2001 if (ImageFormatDetails::compressed(format)) {
2002 // This is horrible but what can you do?
2003 const auto container = ImageFormatDetails::containerFormat(format);
2004 return convertImageDataToFormat(cropImageData(convertImageDataToFormat(imageData, format, container, width, height), container, width, newWidth, xOffset, height, newHeight, yOffset), container, format, newWidth, newHeight);
2005 }
2006
2007 const auto pixelSize = ImageFormatDetails::bpp(format) / 8;
2008 std::vector<std::byte> out(pixelSize * newWidth * newHeight);
2009 for (uint16_t y = yOffset; y < yOffset + newHeight; y++) {
2010 std::memcpy(out.data() + (((y - yOffset) * newWidth) * pixelSize), imageData.data() + (((y * width) + xOffset) * pixelSize), newWidth * pixelSize);
2011 }
2012 return out;
2013}
2014
2015// NOLINTNEXTLINE(*-no-recursion)
2016std::vector<std::byte> ImageConversion::gammaCorrectImageData(std::span<const std::byte> imageData, ImageFormat format, uint16_t width, uint16_t height, float gamma) {
2017 if (imageData.empty() || format == ImageFormat::EMPTY) {
2018 return {};
2019 }
2020 if (gamma == 1.f || ImageFormatDetails::large(format) || ImageFormatDetails::console(format) || format == ImageFormat::P8 || format == ImageFormat::A8 || format == ImageFormat::UV88 || format == ImageFormat::UVLX8888 || format == ImageFormat::UVWQ8888) {
2021 // No gamma correction for you! You are supposed to be linear! Or specialized...
2022 return {imageData.begin(), imageData.end()};
2023 }
2024 if (ImageFormatDetails::compressed(format)) {
2025 // This is horrible but what can you do?
2026 const auto container = ImageFormatDetails::containerFormat(format);
2027 return convertImageDataToFormat(gammaCorrectImageData(convertImageDataToFormat(imageData, format, container, width, height), container, width, height, gamma), container, format, width, height);
2028 }
2029
2030 static constexpr auto calculateGammaLUT = [](float gamma_, uint8_t channelSize) -> std::array<uint8_t, 256> {
2031 const auto maxSize = static_cast<float>((1 << channelSize) - 1);
2032 std::array<uint8_t, 256> gammaLUT{};
2033 for (int i = 0; i < gammaLUT.size(); i++) {
2034 gammaLUT[i] = static_cast<uint8_t>(std::clamp(std::pow((static_cast<float>(i) + 0.5f) / maxSize, gamma_) * maxSize - 0.5f, 0.f, maxSize));
2035 }
2036 return gammaLUT;
2037 };
2038
2039 #define VTFPP_CREATE_GAMMA_LUTS(InputType) \
2040 std::unordered_map<uint8_t, std::array<uint8_t, 256>> gammaLUTs; \
2041 if constexpr (ImageFormatDetails::red(ImageFormat::InputType) > 0) { \
2042 if (!gammaLUTs.contains(ImageFormatDetails::red(ImageFormat::InputType))) { \
2043 gammaLUTs[ImageFormatDetails::red(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::red(ImageFormat::InputType)); \
2044 } \
2045 } \
2046 if constexpr (ImageFormatDetails::green(ImageFormat::InputType) > 0) { \
2047 if (!gammaLUTs.contains(ImageFormatDetails::green(ImageFormat::InputType))) { \
2048 gammaLUTs[ImageFormatDetails::green(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::green(ImageFormat::InputType)); \
2049 } \
2050 } \
2051 if constexpr (ImageFormatDetails::blue(ImageFormat::InputType) > 0) { \
2052 if (!gammaLUTs.contains(ImageFormatDetails::blue(ImageFormat::InputType))) { \
2053 gammaLUTs[ImageFormatDetails::blue(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::blue(ImageFormat::InputType)); \
2054 } \
2055 }
2056
2057 #define VTFPP_APPLY_GAMMA_RED(value) \
2058 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::red(PIXEL_TYPE::FORMAT))[value])
2059
2060 #define VTFPP_APPLY_GAMMA_GREEN(value) \
2061 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::green(PIXEL_TYPE::FORMAT))[value])
2062
2063 #define VTFPP_APPLY_GAMMA_BLUE(value) \
2064 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::blue(PIXEL_TYPE::FORMAT))[value])
2065
2066 std::vector<std::byte> out(imageData.size());
2067
2068#ifdef SOURCEPP_BUILD_WITH_TBB
2069 #define VTFPP_GAMMA_CORRECT(InputType, ...) \
2070 std::span imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
2071 std::span outSpan{reinterpret_cast<ImagePixel::InputType*>(out.data()), out.size() / sizeof(ImagePixel::InputType)}; \
2072 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), outSpan.begin(), [gammaLUTs](ImagePixel::InputType pixel) -> ImagePixel::InputType { \
2073 using PIXEL_TYPE = ImagePixel::InputType; \
2074 return __VA_ARGS__; \
2075 })
2076#else
2077 #define VTFPP_GAMMA_CORRECT(InputType, ...) \
2078 std::span imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
2079 std::span outSpan{reinterpret_cast<ImagePixel::InputType*>(out.data()), out.size() / sizeof(ImagePixel::InputType)}; \
2080 std::transform(imageDataSpan.begin(), imageDataSpan.end(), outSpan.begin(), [gammaLUTs](ImagePixel::InputType pixel) -> ImagePixel::InputType { \
2081 using PIXEL_TYPE = ImagePixel::InputType; \
2082 return __VA_ARGS__; \
2083 })
2084#endif
2085 #define VTFPP_CASE_GAMMA_CORRECT_AND_BREAK(InputType, ...) \
2086 case InputType: { VTFPP_CREATE_GAMMA_LUTS(InputType) VTFPP_GAMMA_CORRECT(InputType, __VA_ARGS__); } break
2087
2088 switch (format) {
2089 using enum ImageFormat;
2092 VTFPP_CASE_GAMMA_CORRECT_AND_BREAK(RGB888_BLUESCREEN, pixel.r == 0 && pixel.g == 0 && pixel.b == 0xff ? ImagePixel::RGB888_BLUESCREEN{0, 0, 0xff} : ImagePixel::RGB888_BLUESCREEN{VTFPP_APPLY_GAMMA_RED(pixel.r), VTFPP_APPLY_GAMMA_GREEN(pixel.g), VTFPP_APPLY_GAMMA_BLUE(pixel.b)});
2094 VTFPP_CASE_GAMMA_CORRECT_AND_BREAK(BGR888_BLUESCREEN, pixel.r == 0 && pixel.g == 0 && pixel.b == 0xff ? ImagePixel::BGR888_BLUESCREEN{0, 0, 0xff} : ImagePixel::BGR888_BLUESCREEN{VTFPP_APPLY_GAMMA_BLUE(pixel.b), VTFPP_APPLY_GAMMA_GREEN(pixel.g), VTFPP_APPLY_GAMMA_RED(pixel.r)});
2107 default: SOURCEPP_DEBUG_BREAK; break;
2108 }
2109
2110 #undef VTFPP_CASE_GAMMA_CORRECT_AND_BREAK
2111 #undef VTFPP_GAMMA_CORRECT
2112 #undef VTFPP_APPLY_GAMMA_BLUE
2113 #undef VTFPP_APPLY_GAMMA_GREEN
2114 #undef VTFPP_APPLY_GAMMA_RED
2115 #undef VTFPP_CREATE_GAMMA_LUTS
2116
2117 return out;
2118}
2119
2120// NOLINTNEXTLINE(*-no-recursion)
2121std::vector<std::byte> ImageConversion::invertGreenChannelForImageData(std::span<const std::byte> imageData, ImageFormat format, uint16_t width, uint16_t height) {
2122 if (imageData.empty() || format == ImageFormat::EMPTY || ImageFormatDetails::decompressedGreen(format) == 0) {
2123 return {};
2124 }
2125 if (ImageFormatDetails::compressed(format)) {
2126 // This is horrible but what can you do?
2127 const auto container = ImageFormatDetails::containerFormat(format);
2128 return convertImageDataToFormat(invertGreenChannelForImageData(convertImageDataToFormat(imageData, format, container, width, height), container, width, height), container, format, width, height);
2129 }
2130
2131 #define VTFPP_INVERT_GREEN(PixelType, ChannelName, ...) \
2132 static constexpr auto channelSize = ImageFormatDetails::green(ImagePixel::PixelType::FORMAT); \
2133 std::span imageDataSpan{reinterpret_cast<const ImagePixel::PixelType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::PixelType)}; \
2134 std::span outSpan{reinterpret_cast<ImagePixel::PixelType*>(out.data()), out.size() / sizeof(ImagePixel::PixelType)}; \
2135 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), outSpan.begin(), [](ImagePixel::PixelType pixel) -> ImagePixel::PixelType { \
2136 if constexpr (std::same_as<decltype(pixel.ChannelName), float> || std::same_as<decltype(pixel.ChannelName), half>) { \
2137 pixel.ChannelName = static_cast<decltype(pixel.ChannelName)>(static_cast<float>(static_cast<uint64_t>(1) << channelSize) - 1.f - static_cast<float>(pixel.ChannelName)); \
2138 } else { \
2139 if constexpr (channelSize >= sizeof(uint32_t) * 8) { \
2140 pixel.ChannelName = static_cast<decltype(pixel.ChannelName)>((static_cast<uint64_t>(1) << channelSize) - 1 - static_cast<uint32_t>(pixel.ChannelName)); \
2141 } else { \
2142 pixel.ChannelName = static_cast<decltype(pixel.ChannelName)>(static_cast<uint32_t>(1 << channelSize) - 1 - static_cast<uint32_t>(pixel.ChannelName)); \
2143 } \
2144 } \
2145 return pixel; \
2146 })
2147#ifdef SOURCEPP_BUILD_WITH_TBB
2148 #define VTFPP_INVERT_GREEN_CASE(PixelType) \
2149 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, g, std::execution::par_unseq); break; }
2150 #define VTFPP_INVERT_GREEN_CASE_CA_OVERRIDE(PixelType, ChannelName) \
2151 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, ChannelName, std::execution::par_unseq); break; }
2152#else
2153 #define VTFPP_INVERT_GREEN_CASE(PixelType) \
2154 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, g); } break
2155 #define VTFPP_INVERT_GREEN_CASE_CA_OVERRIDE(PixelType, ChannelName) \
2156 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, ChannelName); } break
2157#endif
2158
2159 std::vector<std::byte> out(imageData.size());
2160 switch (format) {
2198 default: SOURCEPP_DEBUG_BREAK; break;
2199 }
2200
2201 #undef VTFPP_INVERT_GREEN_CASE_CA_OVERRIDE
2202 #undef VTFPP_INVERT_GREEN_CASE
2203 #undef VTFPP_INVERT_GREEN
2204
2205 return out;
2206}
#define VTFPP_APPLY_GAMMA_BLUE(value)
#define VTFPP_CASE_GAMMA_CORRECT_AND_BREAK(InputType,...)
#define VTFPP_INVERT_GREEN_CASE_CA_OVERRIDE(PixelType, ChannelName)
#define VTFPP_APPLY_GAMMA_RED(value)
#define VTFPP_APPLY_GAMMA_GREEN(value)
#define VTFPP_REMAP_TO_8(value, shift)
#define VTFPP_REMAP_FROM_8(value, shift)
#define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a)
#define VTFPP_CASE_CONVERT_REMAP_AND_BREAK(InputType, r, g, b, a)
#define VTFPP_REMAP_FROM_16(value, shift)
#define VTFPP_INVERT_GREEN_CASE(PixelType)
#define SOURCEPP_DEBUG_BREAK
Create a breakpoint in debug.
Definition: Macros.h:19
constexpr T nearestPowerOf2(T n)
Definition: Math.h:48
constexpr auto pi_f32
Definition: Math.h:27
constexpr double kaiserWindow(double x, double b)
Definition: MathExtended.h:81
consteval uint32_t makeFourCC(const char fourCC[4])
Creates a FourCC identifier from a string of 4 characters.
Definition: Binary.h:18
Definition: LZMA.h:11
std::vector< std::byte > convertFileToImageData(std::span< const std::byte > fileData, ImageFormat &format, int &width, int &height, int &frameCount)
std::vector< std::byte > convertImageDataToFile(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height, FileFormat fileFormat=FileFormat::DEFAULT)
Converts image data to the given file format (PNG or EXR by default).
void setResizedDims(uint16_t &width, ResizeMethod widthResize, uint16_t &height, ResizeMethod heightResize)
Set the new image dimensions given a resize method.
std::array< std::vector< std::byte >, 6 > convertHDRIToCubeMap(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height, uint16_t resolution=0, bool bilinear=true)
Converts an HDRI into a cubemap.
std::vector< std::byte > convertImageDataToFormat(std::span< const std::byte > imageData, ImageFormat oldFormat, ImageFormat newFormat, uint16_t width, uint16_t height, float quality=DEFAULT_COMPRESSED_QUALITY)
Converts an image from one format to another.
uint16_t getResizedDim(uint16_t n, ResizeMethod method)
Get the new image size given a resize method.
constexpr float DEFAULT_COMPRESSED_QUALITY
std::vector< std::byte > gammaCorrectImageData(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height, float gamma)
Perform gamma correction on the given image data. Will not perform gamma correction if the input imag...
FileFormat getDefaultFileFormatForImageFormat(ImageFormat format)
PNG for integer formats, EXR for floating point formats.
std::vector< std::byte > extractChannelFromImageData(std::span< const std::byte > imageData, auto P::*channel)
Extracts a single channel from the given image data.
std::vector< std::byte > convertSeveralImageDataToFormat(std::span< const std::byte > imageData, ImageFormat oldFormat, ImageFormat newFormat, uint8_t mipCount, uint16_t frameCount, uint8_t faceCount, uint16_t width, uint16_t height, uint16_t sliceCount, float quality=DEFAULT_COMPRESSED_QUALITY)
Converts several images from one format to another.
std::vector< std::byte > cropImageData(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t xOffset, uint16_t height, uint16_t newHeight, uint16_t yOffset)
Crops the given image to the new dimensions. If the image format is compressed it will be converted t...
std::vector< std::byte > resizeImageDataStrict(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t &widthOut, ResizeMethod widthResize, uint16_t height, uint16_t newHeight, uint16_t &heightOut, ResizeMethod heightResize, bool srgb, ResizeFilter filter, ResizeEdge edge=ResizeEdge::CLAMP)
Resize given image data to the new dimensions, where the new width and height are governed by the res...
std::vector< std::byte > invertGreenChannelForImageData(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height)
Invert the green channel. Meant for converting normal maps between OpenGL and DirectX formats.
std::vector< std::byte > resizeImageData(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t height, uint16_t newHeight, bool srgb, ResizeFilter filter, ResizeEdge edge=ResizeEdge::CLAMP)
Resize given image data to the new dimensions.
constexpr uint32_t getMipDim(uint8_t mip, uint16_t dim)
Definition: ImageFormats.h:650
constexpr int8_t decompressedGreen(ImageFormat format)
Definition: ImageFormats.h:236
constexpr int8_t alpha(ImageFormat format)
Definition: ImageFormats.h:345
constexpr bool large(ImageFormat format)
Definition: ImageFormats.h:571
constexpr int8_t green(ImageFormat format)
Definition: ImageFormats.h:166
constexpr ImageFormat containerFormat(ImageFormat format)
Definition: ImageFormats.h:506
constexpr uint32_t getDataLength(ImageFormat format, uint16_t width, uint16_t height, uint16_t sliceCount=1)
Definition: ImageFormats.h:711
constexpr bool transparent(ImageFormat format)
Definition: ImageFormats.h:583
constexpr bool getDataPosition(uint32_t &offset, uint32_t &length, ImageFormat format, uint8_t mip, uint8_t mipCount, uint16_t frame, uint16_t frameCount, uint8_t face, uint8_t faceCount, uint16_t width, uint16_t height, uint16_t slice=0, uint16_t sliceCount=1)
Definition: ImageFormats.h:737
constexpr int8_t red(ImageFormat format)
Definition: ImageFormats.h:77
constexpr bool decimal(ImageFormat format)
Definition: ImageFormats.h:575
constexpr uint8_t bpp(ImageFormat format)
Definition: ImageFormats.h:436
constexpr bool console(ImageFormat format)
Definition: ImageFormats.h:624
constexpr bool opaque(ImageFormat format)
Definition: ImageFormats.h:620
constexpr bool compressed(ImageFormat format)
Definition: ImageFormats.h:579
constexpr int8_t blue(ImageFormat format)
Definition: ImageFormats.h:256
Definition: HOT.h:11
ImageFormat
Definition: ImageFormats.h:7