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