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