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