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