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