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