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