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