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