11#include <unordered_map>
13#ifdef SOURCEPP_BUILD_WITH_TBB
17#ifdef SOURCEPP_BUILD_WITH_THREADS
21#include <compressonator.h>
23#ifdef VTFPP_SUPPORT_QOI
24#define QOI_IMPLEMENTATION
32#define STB_IMAGE_IMPLEMENTATION
33#define STB_IMAGE_STATIC
34#define STBI_NO_FAILURE_STRINGS
38#define STB_IMAGE_RESIZE_IMPLEMENTATION
39#define STB_IMAGE_RESIZE_STATIC
40#include <stb_image_resize2.h>
42#define STB_IMAGE_WRITE_IMPLEMENTATION
43#define STB_IMAGE_WRITE_STATIC
44#define STBI_WRITE_NO_STDIO
45#include <stb_image_write.h>
47#ifdef VTFPP_SUPPORT_EXR
48#define TINYEXR_IMPLEMENTATION 1
49#ifdef SOURCEPP_BUILD_WITH_THREADS
50#define TINYEXR_USE_THREAD 1
52#define TINYEXR_USE_THREAD 0
57#ifdef VTFPP_SUPPORT_WEBP
58#include <webp/decode.h>
59#include <webp/encode.h>
67[[nodiscard]]
constexpr CMP_FORMAT imageFormatToCompressonatorFormat(
ImageFormat format) {
72 return CMP_FORMAT_RGBA_8888;
74 return CMP_FORMAT_ABGR_8888;
76 return CMP_FORMAT_RGB_888;
78 return CMP_FORMAT_BGR_888;
81 return CMP_FORMAT_R_8;
83 return CMP_FORMAT_ARGB_8888;
85 return CMP_FORMAT_BGRA_8888;
88 return CMP_FORMAT_DXT1;
90 return CMP_FORMAT_DXT3;
92 return CMP_FORMAT_DXT5;
94 return CMP_FORMAT_RG_8;
96 return CMP_FORMAT_R_16F;
98 return CMP_FORMAT_RG_16F;
100 return CMP_FORMAT_RGBA_16F;
102 return CMP_FORMAT_RGBA_16;
104 return CMP_FORMAT_R_32F;
106 return CMP_FORMAT_RG_32F;
108 return CMP_FORMAT_RGB_32F;
110 return CMP_FORMAT_RGBA_32F;
112 return CMP_FORMAT_ATI2N;
114 return CMP_FORMAT_ATI1N;
116 return CMP_FORMAT_RGBA_1010102;
118 return CMP_FORMAT_R_8;
120 return CMP_FORMAT_BC7;
122 return CMP_FORMAT_BC6H_SF;
149 return CMP_FORMAT_Unknown;
151 return CMP_FORMAT_Unknown;
154[[nodiscard]]
constexpr int imageFormatToSTBIRPixelLayout(
ImageFormat format) {
175 return STBIR_1CHANNEL;
183 return STBIR_2CHANNEL;
226[[nodiscard]]
constexpr int imageFormatToSTBIRDataType(
ImageFormat format,
bool srgb =
false) {
247 return srgb ? STBIR_TYPE_UINT8_SRGB : STBIR_TYPE_UINT8;
251 return STBIR_TYPE_HALF_FLOAT;
253 return STBIR_TYPE_UINT16;
258 return STBIR_TYPE_FLOAT;
293 if (imageData.empty()) {
297 CMP_Texture srcTexture{};
298 srcTexture.dwSize =
sizeof(srcTexture);
299 srcTexture.dwWidth = width;
300 srcTexture.dwHeight = height;
302 srcTexture.format = ::imageFormatToCompressonatorFormat(oldFormat);
303 srcTexture.dwDataSize = imageData.size();
305 srcTexture.pData =
const_cast<CMP_BYTE*
>(
reinterpret_cast<const CMP_BYTE*
>(imageData.data()));
307 CMP_Texture destTexture{};
308 destTexture.dwSize =
sizeof(destTexture);
309 destTexture.dwWidth = width;
310 destTexture.dwHeight = height;
312 destTexture.format = ::imageFormatToCompressonatorFormat(newFormat);
313 destTexture.dwDataSize = CMP_CalculateBufferSize(&destTexture);
315 std::vector<std::byte> destData;
316 destData.resize(destTexture.dwDataSize);
317 destTexture.pData =
reinterpret_cast<CMP_BYTE*
>(destData.data());
319 CMP_CompressOptions options{};
320 options.dwSize =
sizeof(options);
321 options.fquality = std::clamp(quality, 0.f, 1.f);
322 options.bDXT1UseAlpha = oldFormat == ImageFormat::DXT1_ONE_BIT_ALPHA || newFormat == ImageFormat::DXT1_ONE_BIT_ALPHA;
323 if (options.bDXT1UseAlpha) {
324 options.nAlphaThreshold = 128;
327 if (CMP_ConvertTexture(&srcTexture, &destTexture, &options,
nullptr) != CMP_OK) {
333[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA8888(std::span<const std::byte> imageData,
ImageFormat format) {
334 using namespace ImageConversion;
336 if (imageData.empty()) {
340 if (format == ImageFormat::RGBA8888 || format == ImageFormat::UVWQ8888) {
341 return {imageData.begin(), imageData.end()};
344 std::vector<std::byte> newData;
348 #define VTFPP_REMAP_TO_8(value, shift) math::remap<uint8_t>((value), (1 << (shift)) - 1, (1 << 8) - 1)
350 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
351 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
352 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA8888 { \
353 return {(r), (g), (b), (a)}; \
355#ifdef SOURCEPP_BUILD_WITH_TBB
356 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
358 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
360 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
361 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
400 #undef VTFPP_CASE_CONVERT_AND_BREAK
402 #undef VTFPP_CONVERT_DETAIL
403 #undef VTFPP_REMAP_TO_8
408[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888(std::span<const std::byte> imageData,
ImageFormat format) {
409 using namespace ImageConversion;
411 if (imageData.empty()) {
415 if (format == ImageFormat::RGBA8888) {
416 return {imageData.begin(), imageData.end()};
420 std::vector<std::byte> newData;
423 #define VTFPP_REMAP_FROM_8(value, shift) math::remap<uint8_t>((value), (1 << 8) - 1, (1 << (shift)) - 1)
425#ifdef SOURCEPP_BUILD_WITH_TBB
426 #define VTFPP_CONVERT(InputType, ...) \
427 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
428 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA8888 pixel) -> ImagePixel::InputType { \
429 return __VA_ARGS__; \
432 #define VTFPP_CONVERT(InputType, ...) \
433 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
434 std::transform(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA8888 pixel) -> ImagePixel::InputType { \
435 return __VA_ARGS__; \
438 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
439 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
478 #undef VTFPP_CASE_CONVERT_AND_BREAK
480 #undef VTFPP_REMAP_FROM_8
485[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA16161616(std::span<const std::byte> imageData,
ImageFormat format) {
486 using namespace ImageConversion;
488 if (imageData.empty()) {
492 if (format == ImageFormat::RGBA16161616) {
493 return {imageData.begin(), imageData.end()};
496 std::vector<std::byte> newData;
500 #define VTFPP_REMAP_TO_16(value, shift) math::remap<uint16_t>((value), (1 << (shift)) - 1, (1 << 16) - 1)
502 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
503 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
504 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA16161616 { \
505 return { static_cast<uint16_t>(r), static_cast<uint16_t>(g), static_cast<uint16_t>(b), static_cast<uint16_t>(a) }; \
507#ifdef SOURCEPP_BUILD_WITH_TBB
508 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
510 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
512 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
513 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
515 #define VTFPP_CONVERT_REMAP(InputType, r, g, b, a) \
517 if constexpr (ImageFormatDetails::alpha(ImageFormat::InputType) > 1) { \
518 VTFPP_CONVERT(InputType, \
519 VTFPP_REMAP_TO_16((r), ImageFormatDetails::red(ImageFormat::InputType)), \
520 VTFPP_REMAP_TO_16((g), ImageFormatDetails::green(ImageFormat::InputType)), \
521 VTFPP_REMAP_TO_16((b), ImageFormatDetails::blue(ImageFormat::InputType)), \
522 VTFPP_REMAP_TO_16((a), ImageFormatDetails::alpha(ImageFormat::InputType))); \
523 } else if constexpr (ImageFormatDetails::alpha(ImageFormat::InputType) == 1) { \
524 VTFPP_CONVERT(InputType, \
525 VTFPP_REMAP_TO_16((r), ImageFormatDetails::red(ImageFormat::InputType)), \
526 VTFPP_REMAP_TO_16((g), ImageFormatDetails::green(ImageFormat::InputType)), \
527 VTFPP_REMAP_TO_16((b), ImageFormatDetails::blue(ImageFormat::InputType)), \
530 VTFPP_CONVERT(InputType, \
531 VTFPP_REMAP_TO_16((r), ImageFormatDetails::red(ImageFormat::InputType)), \
532 VTFPP_REMAP_TO_16((g), ImageFormatDetails::green(ImageFormat::InputType)), \
533 VTFPP_REMAP_TO_16((b), ImageFormatDetails::blue(ImageFormat::InputType)), \
537 #define VTFPP_CASE_CONVERT_REMAP_AND_BREAK(InputType, r, g, b, a) \
538 case InputType: { VTFPP_CONVERT_REMAP(InputType, r, g, b, a); } \
549 #undef VTFPP_CASE_CONVERT_REMAP_AND_BREAK
550 #undef VTFPP_CONVERT_REMAP
551 #undef VTFPP_CASE_CONVERT_AND_BREAK
553 #undef VTFPP_CONVERT_DETAIL
554 #undef VTFPP_REMAP_TO_16
559[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616(std::span<const std::byte> imageData,
ImageFormat format) {
560 using namespace ImageConversion;
562 if (imageData.empty()) {
566 if (format == ImageFormat::RGBA16161616) {
567 return {imageData.begin(), imageData.end()};
571 std::vector<std::byte> newData;
574 #define VTFPP_REMAP_FROM_16(value, shift) static_cast<uint8_t>(math::remap<uint16_t>((value), (1 << 16) - 1, (1 << (shift)) - 1))
576#ifdef SOURCEPP_BUILD_WITH_TBB
577 #define VTFPP_CONVERT(InputType, ...) \
578 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
579 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA16161616 pixel) -> ImagePixel::InputType { \
580 return __VA_ARGS__; \
583 #define VTFPP_CONVERT(InputType, ...) \
584 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
585 std::transform(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA16161616 pixel) -> ImagePixel::InputType { \
586 return __VA_ARGS__; \
589 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
590 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
600 #undef VTFPP_CASE_CONVERT_AND_BREAK
602 #undef VTFPP_REMAP_FROM_16
607[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA32323232F(std::span<const std::byte> imageData,
ImageFormat format) {
608 if (imageData.empty()) {
612 if (format == ImageFormat::RGBA32323232F) {
613 return {imageData.begin(), imageData.end()};
616 std::vector<std::byte> newData;
620 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
621 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
622 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA32323232F { return {(r), (g), (b), (a)}; })
623#ifdef SOURCEPP_BUILD_WITH_TBB
624 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
626 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
628 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
629 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
642 #undef VTFPP_CASE_CONVERT_AND_BREAK
644 #undef VTFPP_CONVERT_DETAIL
649[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232F(std::span<const std::byte> imageData,
ImageFormat format) {
650 using namespace ImageConversion;
652 if (imageData.empty()) {
656 if (format == ImageFormat::RGBA32323232F) {
657 return {imageData.begin(), imageData.end()};
661 std::vector<std::byte> newData;
664#ifdef SOURCEPP_BUILD_WITH_TBB
665 #define VTFPP_CONVERT(InputType, ...) \
666 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
667 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA32323232F pixel) -> ImagePixel::InputType { \
668 return __VA_ARGS__; \
671 #define VTFPP_CONVERT(InputType, ...) \
672 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
673 std::transform(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA32323232F pixel) -> ImagePixel::InputType { \
674 return __VA_ARGS__; \
677 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
678 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
691 #undef VTFPP_CASE_CONVERT_AND_BREAK
697[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888ToRGBA32323232F(std::span<const std::byte> imageData) {
698 if (imageData.empty()) {
702 std::vector<std::byte> newData;
708#ifdef SOURCEPP_BUILD_WITH_TBB
709 std::execution::par_unseq,
713 static_cast<float>(pixel.r) / static_cast<float>((1 << 8) - 1),
714 static_cast<float>(pixel.g) / static_cast<float>((1 << 8) - 1),
715 static_cast<float>(pixel.b) / static_cast<float>((1 << 8) - 1),
716 static_cast<float>(pixel.a) / static_cast<float>((1 << 8) - 1),
723[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232FToRGBA8888(std::span<const std::byte> imageData) {
724 if (imageData.empty()) {
728 std::vector<std::byte> newData;
734#ifdef SOURCEPP_BUILD_WITH_TBB
735 std::execution::par_unseq,
739 static_cast<uint8_t>(std::clamp(pixel.r, 0.f, 1.f) * ((1 << 8) - 1)),
740 static_cast<uint8_t>(std::clamp(pixel.g, 0.f, 1.f) * ((1 << 8) - 1)),
741 static_cast<uint8_t>(std::clamp(pixel.b, 0.f, 1.f) * ((1 << 8) - 1)),
742 static_cast<uint8_t>(std::clamp(pixel.a, 0.f, 1.f) * ((1 << 8) - 1)),
749[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888ToRGBA16161616(std::span<const std::byte> imageData) {
750 if (imageData.empty()) {
754 std::vector<std::byte> newData;
760#ifdef SOURCEPP_BUILD_WITH_TBB
761 std::execution::par_unseq,
765 math::remap<uint16_t>(pixel.r, (1 << 8) - 1, (1 << 16) - 1),
766 math::remap<uint16_t>(pixel.g, (1 << 8) - 1, (1 << 16) - 1),
767 math::remap<uint16_t>(pixel.b, (1 << 8) - 1, (1 << 16) - 1),
768 math::remap<uint16_t>(pixel.a, (1 << 8) - 1, (1 << 16) - 1),
775[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616ToRGBA8888(std::span<const std::byte> imageData) {
776 if (imageData.empty()) {
780 std::vector<std::byte> newData;
786#ifdef SOURCEPP_BUILD_WITH_TBB
787 std::execution::par_unseq,
791 static_cast<uint8_t>(math::remap<uint16_t>(pixel.r, (1 << 16) - 1, (1 << 8) - 1)),
792 static_cast<uint8_t>(math::remap<uint16_t>(pixel.g, (1 << 16) - 1, (1 << 8) - 1)),
793 static_cast<uint8_t>(math::remap<uint16_t>(pixel.b, (1 << 16) - 1, (1 << 8) - 1)),
794 static_cast<uint8_t>(math::remap<uint16_t>(pixel.a, (1 << 16) - 1, (1 << 8) - 1)),
801[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232FToRGBA16161616(std::span<const std::byte> imageData) {
802 if (imageData.empty()) {
806 std::vector<std::byte> newData;
812#ifdef SOURCEPP_BUILD_WITH_TBB
813 std::execution::par_unseq,
817 static_cast<uint16_t>(std::clamp(pixel.r, 0.f, 1.f) * ((1 << 16) - 1)),
818 static_cast<uint16_t>(std::clamp(pixel.g, 0.f, 1.f) * ((1 << 16) - 1)),
819 static_cast<uint16_t>(std::clamp(pixel.b, 0.f, 1.f) * ((1 << 16) - 1)),
820 static_cast<uint16_t>(std::clamp(pixel.a, 0.f, 1.f) * ((1 << 16) - 1)),
827[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616ToRGBA32323232F(std::span<const std::byte> imageData) {
828 if (imageData.empty()) {
832 std::vector<std::byte> newData;
838#ifdef SOURCEPP_BUILD_WITH_TBB
839 std::execution::par_unseq,
843 static_cast<float>(pixel.r) / static_cast<float>((1 << 16) - 1),
844 static_cast<float>(pixel.g) / static_cast<float>((1 << 16) - 1),
845 static_cast<float>(pixel.b) / static_cast<float>((1 << 16) - 1),
846 static_cast<float>(pixel.a) / static_cast<float>((1 << 16) - 1),
856 if (imageData.empty() || oldFormat == ImageFormat::EMPTY) {
860 if (oldFormat == newFormat) {
861 return {imageData.begin(), imageData.end()};
864 std::vector<std::byte> newData;
868 newData = ::convertImageDataUsingCompressonator(imageData, oldFormat, intermediaryOldFormat, width, height, quality);
870 switch (intermediaryOldFormat) {
871 case ImageFormat::RGBA8888: newData = ::convertImageDataToRGBA8888(imageData, oldFormat);
break;
872 case ImageFormat::RGBA16161616: newData = ::convertImageDataToRGBA16161616(imageData, oldFormat);
break;
873 case ImageFormat::RGBA32323232F: newData = ::convertImageDataToRGBA32323232F(imageData, oldFormat);
break;
878 if (intermediaryOldFormat == newFormat) {
883 if (intermediaryOldFormat != intermediaryNewFormat) {
884 if (intermediaryOldFormat == ImageFormat::RGBA8888) {
885 if (intermediaryNewFormat == ImageFormat::RGBA16161616) {
886 newData = ::convertImageDataFromRGBA8888ToRGBA16161616(newData);
887 }
else if (intermediaryNewFormat == ImageFormat::RGBA32323232F) {
888 newData = ::convertImageDataFromRGBA8888ToRGBA32323232F(newData);
892 }
else if (intermediaryOldFormat == ImageFormat::RGBA16161616) {
893 if (intermediaryNewFormat == ImageFormat::RGBA8888) {
894 newData = ::convertImageDataFromRGBA16161616ToRGBA8888(newData);
895 }
else if (intermediaryNewFormat == ImageFormat::RGBA32323232F) {
896 newData = ::convertImageDataFromRGBA16161616ToRGBA32323232F(newData);
900 }
else if (intermediaryOldFormat == ImageFormat::RGBA32323232F) {
901 if (intermediaryNewFormat == ImageFormat::RGBA8888) {
902 newData = ::convertImageDataFromRGBA32323232FToRGBA8888(newData);
903 }
else if (intermediaryNewFormat == ImageFormat::RGBA16161616) {
904 newData = ::convertImageDataFromRGBA32323232FToRGBA16161616(newData);
913 if (intermediaryNewFormat == newFormat) {
918 newData = ::convertImageDataUsingCompressonator(newData, intermediaryNewFormat, newFormat, width, height, quality);
920 switch (intermediaryNewFormat) {
921 case ImageFormat::RGBA8888: newData = ::convertImageDataFromRGBA8888(newData, newFormat);
break;
922 case ImageFormat::RGBA16161616: newData = ::convertImageDataFromRGBA16161616(newData, newFormat);
break;
923 case ImageFormat::RGBA32323232F: newData = ::convertImageDataFromRGBA32323232F(newData, newFormat);
break;
932 if (imageData.empty() || oldFormat == ImageFormat::EMPTY) {
936 if (oldFormat == newFormat) {
937 return {imageData.begin(), imageData.end()};
941 for(
int mip = mipCount - 1; mip >= 0; mip--) {
942 for (
int frame = 0; frame < frameCount; frame++) {
943 for (
int face = 0; face < faceCount; face++) {
944 for (
int slice = 0; slice < depth; slice++) {
945 if (uint32_t oldOffset, oldLength;
ImageFormatDetails::getDataPosition(oldOffset, oldLength, oldFormat, mip, mipCount, frame, frameCount, face, faceCount, width, height, slice, depth)) {
947 if (uint32_t newOffset, newLength;
ImageFormatDetails::getDataPosition(newOffset, newLength, newFormat, mip, mipCount, frame, frameCount, face, faceCount, width, height, slice, depth) && newLength == convertedImageData.size()) {
948 std::memcpy(out.data() + newOffset, convertedImageData.data(), newLength);
959 if (imageData.empty() || format == ImageFormat::EMPTY) {
967 std::span<const float> imageDataRGBA32323232F{
reinterpret_cast<const float*
>(imageData.data()),
reinterpret_cast<const float*
>(imageData.data() + imageData.size())};
969 std::vector<std::byte> possiblyConvertedDataOrEmptyDontUseMeDirectly;
970 if (format != ImageFormat::RGBA32323232F) {
971 possiblyConvertedDataOrEmptyDontUseMeDirectly =
convertImageDataToFormat(imageData, format, ImageFormat::RGBA32323232F, width, height);
972 imageDataRGBA32323232F = {
reinterpret_cast<const float*
>(possiblyConvertedDataOrEmptyDontUseMeDirectly.data()),
reinterpret_cast<const float*
>(possiblyConvertedDataOrEmptyDontUseMeDirectly.data() + possiblyConvertedDataOrEmptyDontUseMeDirectly.size())};
977 static constexpr std::array<std::array<math::Vec3f, 3>, 6> startRightUp = {{
978 {{{ 1.0f, -1.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}},
979 {{{ 1.0f, 1.0f, 1.0f}, { 0.0f,-1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}},
980 {{{ 1.0f, 1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, { 0.0f,-1.0f, 0.0f}}},
981 {{{-1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}}},
982 {{{ 1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}}},
983 {{{ 1.0f, 1.0f, -1.0f}, { 0.0f, 0.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}},
986 std::array<std::vector<std::byte>, 6> faceData;
988#ifdef SOURCEPP_BUILD_WITH_THREADS
989 const auto faceExtraction = [&](
int i) {
991 for (
int i = 0; i < faceData.size(); i++) {
993 const auto start = startRightUp[i][0];
994 const auto right = startRightUp[i][1];
995 const auto up = startRightUp[i][2];
998 std::span<float> face{
reinterpret_cast<float*
>(faceData[i].data()),
reinterpret_cast<float*
>(faceData[i].data() + faceData[i].size())};
1000 for (
int row = 0; row < resolution; row++) {
1001 for (
int col = 0; col < resolution; col++) {
1002 math::Vec3f pixelDirection3d{
1003 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],
1004 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],
1005 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],
1007 float azimuth = std::atan2(pixelDirection3d[0], -pixelDirection3d[2]) +
math::pi_f32;
1008 float elevation = std::atan(pixelDirection3d[1] / std::sqrt(pixelDirection3d[0] * pixelDirection3d[0] + pixelDirection3d[2] * pixelDirection3d[2])) +
math::pi_f32 / 2.f;
1009 float colHdri = (azimuth /
math::pi_f32 / 2.f) *
static_cast<float>(width);
1010 float rowHdri = (elevation /
math::pi_f32) *
static_cast<float>(height);
1012 int colNearest = std::clamp(
static_cast<int>(colHdri), 0, width - 1);
1013 int rowNearest = std::clamp(
static_cast<int>(rowHdri), 0, height - 1);
1014 face[col * 4 + resolution * row * 4 + 0] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 0];
1015 face[col * 4 + resolution * row * 4 + 1] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 1];
1016 face[col * 4 + resolution * row * 4 + 2] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 2];
1017 face[col * 4 + resolution * row * 4 + 3] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 3];
1019 float intCol, intRow;
1021 float factorCol = std::modf(colHdri - 0.5f, &intCol);
1022 float factorRow = std::modf(rowHdri - 0.5f, &intRow);
1023 int low_idx_row =
static_cast<int>(intRow);
1024 int low_idx_column =
static_cast<int>(intCol);
1025 int high_idx_column;
1026 if (factorCol < 0.f) {
1029 high_idx_column = width - 1;
1030 }
else if (low_idx_column == width - 1) {
1032 high_idx_column = 0;
1034 high_idx_column = low_idx_column + 1;
1037 if (factorRow < 0.f || low_idx_row == height - 1) {
1038 high_idx_row = low_idx_row;
1041 high_idx_row = low_idx_row + 1;
1043 factorCol = std::abs(factorCol);
1044 factorRow = std::abs(factorRow);
1045 float f1 = (1 - factorRow) * (1 - factorCol);
1046 float f2 = factorRow * (1 - factorCol);
1047 float f3 = (1 - factorRow) * factorCol;
1048 float f4 = factorRow * factorCol;
1049 for (
int j = 0; j < 4; j++) {
1050 face[col * 4 + resolution * row * 4 + j] =
1051 imageDataRGBA32323232F[low_idx_column * 4 + width * low_idx_row * 4 + j] * f1 +
1052 imageDataRGBA32323232F[low_idx_column * 4 + width * high_idx_row * 4 + j] * f2 +
1053 imageDataRGBA32323232F[high_idx_column * 4 + width * low_idx_row * 4 + j] * f3 +
1054 imageDataRGBA32323232F[high_idx_column * 4 + width * high_idx_row * 4 + j] * f4;
1059 if (format != ImageFormat::RGBA32323232F) {
1063#ifdef SOURCEPP_BUILD_WITH_THREADS
1065 std::array<std::future<void>, 6> faceFutures{
1066 std::async(std::launch::async, faceExtraction, 0),
1067 std::async(std::launch::async, faceExtraction, 1),
1068 std::async(std::launch::async, faceExtraction, 2),
1069 std::async(std::launch::async, faceExtraction, 3),
1070 std::async(std::launch::async, faceExtraction, 4),
1071 std::async(std::launch::async, faceExtraction, 5),
1073 for (
auto& future : faceFutures) {
1083#ifdef VTFPP_SUPPORT_EXR
1091 if (imageData.empty() || format == ImageFormat::EMPTY) {
1094 std::vector<std::byte> out;
1095 auto stbWriteFunc = [](
void* out_,
void* data,
int size) {
1096 std::copy_n(
static_cast<std::byte*
>(data), size, std::back_inserter(*
static_cast<std::vector<std::byte>*
>(out_)));
1099 if (fileFormat == FileFormat::DEFAULT) {
1102 switch (fileFormat) {
1103 case FileFormat::PNG: {
1104 if (format == ImageFormat::RGB888) {
1105 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data(), 0);
1106 }
else if (format == ImageFormat::RGBA8888) {
1107 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), imageData.data(), 0);
1110 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data(), 0);
1113 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), rgba.data(), 0);
1117 case FileFormat::JPG: {
1118 if (format == ImageFormat::RGB888) {
1119 stbi_write_jpg_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data(), 95);
1122 stbi_write_jpg_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data(), 95);
1126 case FileFormat::BMP: {
1127 if (format == ImageFormat::RGB888) {
1128 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data());
1129 }
else if (format == ImageFormat::RGBA8888) {
1130 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), imageData.data());
1133 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data());
1136 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), rgba.data());
1140 case FileFormat::TGA: {
1141 if (format == ImageFormat::RGB888) {
1142 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data());
1143 }
else if (format == ImageFormat::RGBA8888) {
1144 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), imageData.data());
1147 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data());
1150 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), rgba.data());
1154#ifdef VTFPP_SUPPORT_WEBP
1155 case FileFormat::WEBP: {
1157 WebPConfigInit(&config);
1158 WebPConfigPreset(&config, WEBP_PRESET_DRAWING, 75.f);
1159 WebPConfigLosslessPreset(&config, 6);
1162 if (!WebPPictureInit(&pic)) {
1166 pic.height = height;
1167 if (!WebPPictureAlloc(&pic)) {
1171 if (format == ImageFormat::RGB888) {
1172 WebPPictureImportRGB(&pic,
reinterpret_cast<const uint8_t*
>(imageData.data()),
static_cast<int>(width *
sizeof(
ImagePixel::RGB888)));
1173 }
else if (format == ImageFormat::RGBA8888) {
1174 WebPPictureImportRGBA(&pic,
reinterpret_cast<const uint8_t*
>(imageData.data()),
static_cast<int>(width *
sizeof(
ImagePixel::RGBA8888)));
1177 WebPPictureImportRGB(&pic,
reinterpret_cast<const uint8_t*
>(rgb.data()),
static_cast<int>(width *
sizeof(
ImagePixel::RGB888)));
1180 WebPPictureImportRGBA(&pic,
reinterpret_cast<const uint8_t*
>(rgba.data()),
static_cast<int>(width *
sizeof(
ImagePixel::RGBA8888)));
1183 WebPMemoryWriter writer;
1184 WebPMemoryWriterInit(&writer);
1185 pic.writer = &WebPMemoryWrite;
1186 pic.custom_ptr = &writer;
1188 int ok = WebPEncode(&config, &pic);
1189 WebPPictureFree(&pic);
1191 WebPMemoryWriterClear(&writer);
1195 if (writer.mem && writer.size) {
1196 out.resize(writer.size);
1197 std::memcpy(out.data(), writer.mem, writer.size);
1199 WebPMemoryWriterClear(&writer);
1203#ifdef VTFPP_SUPPORT_QOI
1204 case FileFormat::QOI: {
1205 qoi_desc descriptor{
1209 .colorspace = QOI_SRGB,
1211 void* qoiData =
nullptr;
1213 if (format == ImageFormat::RGB888) {
1214 descriptor.channels = 3;
1215 qoiData = qoi_encode(imageData.data(), &descriptor, &qoiDataLen);
1216 }
else if (format == ImageFormat::RGBA8888) {
1217 descriptor.channels = 4;
1218 qoiData = qoi_encode(imageData.data(), &descriptor, &qoiDataLen);
1221 descriptor.channels = 3;
1222 qoiData = qoi_encode(rgb.data(), &descriptor, &qoiDataLen);
1225 descriptor.channels = 4;
1226 qoiData = qoi_encode(rgba.data(), &descriptor, &qoiDataLen);
1228 if (qoiData && qoiDataLen) {
1229 out.resize(qoiDataLen);
1230 std::memcpy(out.data(), qoiData, qoiDataLen);
1236 case FileFormat::HDR: {
1237 if (format == ImageFormat::RGB323232F) {
1238 stbi_write_hdr_to_func(stbWriteFunc, &out, width, height,
ImageFormatDetails::bpp(ImageFormat::RGB323232F) / (8 *
sizeof(
float)),
reinterpret_cast<const float*
>(imageData.data()));
1241 stbi_write_hdr_to_func(stbWriteFunc, &out, width, height,
ImageFormatDetails::bpp(ImageFormat::RGB323232F) / (8 *
sizeof(
float)),
reinterpret_cast<const float*
>(hdr.data()));
1245#ifdef VTFPP_SUPPORT_EXR
1246 case FileFormat::EXR: {
1248 InitEXRHeader(&header);
1250 std::vector<std::byte> rawData;
1254 format = ImageFormat::RGBA32323232F;
1257 format = ImageFormat::RGB323232F;
1260 rawData = {imageData.begin(), imageData.end()};
1264 header.channels =
static_cast<EXRChannelInfo*
>(std::malloc(header.num_channels *
sizeof(EXRChannelInfo)));
1265 header.pixel_types =
static_cast<int*
>(malloc(header.num_channels *
sizeof(
int)));
1266 header.requested_pixel_types =
static_cast<int*
>(malloc(header.num_channels *
sizeof(
int)));
1268 switch (header.num_channels) {
1270 header.channels[0].name[0] =
'A';
1271 header.channels[1].name[0] =
'B';
1272 header.channels[2].name[0] =
'G';
1273 header.channels[3].name[0] =
'R';
1276 header.channels[0].name[0] =
'B';
1277 header.channels[1].name[0] =
'G';
1278 header.channels[2].name[0] =
'R';
1281 header.channels[0].name[0] =
'G';
1282 header.channels[1].name[0] =
'R';
1285 header.channels[0].name[0] =
'R';
1288 FreeEXRHeader(&header);
1291 for (
int i = 0; i < header.num_channels; i++) {
1292 header.channels[i].name[1] =
'\0';
1295 int pixelType = (
ImageFormatDetails::red(format) / 8) ==
sizeof(half) ? TINYEXR_PIXELTYPE_HALF : TINYEXR_PIXELTYPE_FLOAT;
1296 for (
int i = 0; i < header.num_channels; i++) {
1297 header.pixel_types[i] = pixelType;
1298 header.requested_pixel_types[i] = pixelType;
1301 std::vector<std::vector<std::byte>> images(header.num_channels);
1302 std::vector<void*> imagePtrs(header.num_channels);
1303 switch (header.num_channels) {
1305 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1318 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1320 FreeEXRHeader(&header);
1328 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1337 images[0] = rawData;
1340 FreeEXRHeader(&header);
1343 for (
int i = 0; i < header.num_channels; i++) {
1344 imagePtrs[i] = images[i].data();
1348 InitEXRImage(&image);
1349 image.width = width;
1350 image.height = height;
1351 image.images =
reinterpret_cast<unsigned char**
>(imagePtrs.data());
1352 image.num_channels = header.num_channels;
1354 unsigned char* data =
nullptr;
1355 const char* err =
nullptr;
1357 size_t size = SaveEXRImageToMemory(&image, &header, &data, &err);
1359 FreeEXRErrorMessage(err);
1360 FreeEXRHeader(&header);
1364 out = {
reinterpret_cast<std::byte*
>(data),
reinterpret_cast<std::byte*
>(data) + size};
1368 FreeEXRHeader(&header);
1372 case FileFormat::DEFAULT:
1381using stb_ptr = std::unique_ptr<T, void(*)(
void*)>;
1386 stbi_convert_iphone_png_to_rgb(
true);
1388 format = ImageFormat::EMPTY;
1394#ifdef VTFPP_SUPPORT_EXR
1396 if (EXRVersion version; ParseEXRVersionFromMemory(&version,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size()) == TINYEXR_SUCCESS) {
1397 if (version.multipart || version.non_image) {
1402 InitEXRHeader(&header);
1403 const char* err =
nullptr;
1404 if (ParseEXRHeaderFromMemory(&header, &version,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size(), &err) != TINYEXR_SUCCESS) {
1405 FreeEXRErrorMessage(err);
1410 if (header.num_channels < 1) {
1411 FreeEXRHeader(&header);
1416 std::unordered_map<std::string_view, int> channelIndices{{
"R", -1}, {
"G", -1}, {
"B", -1}, {
"A", -1}, {
"Y", -1}};
1420 auto channelType = header.pixel_types[0];
1421 for (
int i = 1; i < header.num_channels; i++) {
1423 if (header.pixel_types[i] > channelType && channelIndices.contains(header.channels[i].name)) {
1424 channelType = header.pixel_types[i];
1428 if (channelType == TINYEXR_PIXELTYPE_UINT) {
1429 channelType = TINYEXR_PIXELTYPE_HALF;
1433 for (
int i = 0; i < header.num_channels; i++) {
1434 if (channelIndices.contains(header.channels[i].name)) {
1435 channelIndices[header.channels[i].name] = i;
1438 if (channelIndices[
"Y"] >= 0) {
1439 if (channelIndices[
"A"] >= 0) {
1440 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::RGBA16161616F : ImageFormat::RGBA32323232F;
1442 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1444 channelType = TINYEXR_PIXELTYPE_FLOAT;
1446 format = ImageFormat::RGB323232F;
1448 channelIndices[
"R"] = channelIndices[
"Y"];
1449 channelIndices[
"G"] = channelIndices[
"Y"];
1450 channelIndices[
"B"] = channelIndices[
"Y"];
1451 }
else if (channelIndices[
"A"] >= 0) {
1452 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::RGBA16161616F : ImageFormat::RGBA32323232F;
1453 }
else if (channelIndices[
"B"] >= 0) {
1454 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1456 channelType = TINYEXR_PIXELTYPE_FLOAT;
1458 format = ImageFormat::RGB323232F;
1459 }
else if (channelIndices[
"G"] >= 0) {
1460 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::RG1616F : ImageFormat::RG3232F;
1461 }
else if (channelIndices[
"R"] >= 0) {
1462 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::R16F : ImageFormat::R32F;
1464 FreeEXRHeader(&header);
1469 for (
int i = 0; i < header.num_channels; i++) {
1470 if (header.pixel_types[i] != channelType && channelIndices.contains(header.channels[i].name)) {
1471 header.requested_pixel_types[i] = channelType;
1476 InitEXRImage(&image);
1477 if (LoadEXRImageFromMemory(&image, &header,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size(), &err) != TINYEXR_SUCCESS) {
1478 FreeEXRErrorMessage(err);
1479 FreeEXRHeader(&header);
1483 width = image.width;
1484 height = image.height;
1488 const auto populateBuffer = [
1496 r=channelIndices[
"R"],
1497 g=channelIndices[
"G"],
1498 b=channelIndices[
"B"],
1499 a=channelIndices[
"A"],
1503 const auto channelCount = hasRed + hasGreen + hasBlue + hasAlpha;
1504 std::span out{
reinterpret_cast<C*
>(combinedChannels.data()), combinedChannels.size() /
sizeof(C)};
1506 for (
int t = 0; t < image.num_tiles; t++) {
1507 auto** src =
reinterpret_cast<C**
>(image.tiles[t].images);
1508 for (
int j = 0; j < header.tile_size_y; j++) {
1509 for (
int i = 0; i < header.tile_size_x; i++) {
1510 const auto ii =
static_cast<uint64_t
>(image.tiles[t].offset_x) * header.tile_size_x + i;
1511 const auto jj =
static_cast<uint64_t
>(image.tiles[t].offset_y) * header.tile_size_y + j;
1512 const auto idx = ii + jj * image.width;
1514 if (ii >= image.width || jj >= image.height) {
1518 const auto srcIdx = j *
static_cast<uint64_t
>(header.tile_size_x) + i;
1519 if (r >= 0) out[idx * channelCount + 0] = src[r][srcIdx];
1520 else if (hasRed) out[idx * channelCount + 0] = 0.f;
1521 if (g >= 0) out[idx * channelCount + 1] = src[g][srcIdx];
1522 else if (hasGreen) out[idx * channelCount + 1] = 0.f;
1523 if (b >= 0) out[idx * channelCount + 2] = src[b][srcIdx];
1524 else if (hasBlue) out[idx * channelCount + 2] = 0.f;
1525 if (a >= 0) out[idx * channelCount + 3] = src[a][srcIdx];
1526 else if (hasAlpha) out[idx * channelCount + 3] = 1.f;
1531 auto** src =
reinterpret_cast<C**
>(image.images);
1532 for (uint64_t i = 0; i < width * height; i++) {
1533 if (r >= 0) out[i * channelCount + 0] = src[r][i];
1534 else if (hasRed) out[i * channelCount + 0] = 0.f;
1535 if (g >= 0) out[i * channelCount + 1] = src[g][i];
1536 else if (hasGreen) out[i * channelCount + 1] = 0.f;
1537 if (b >= 0) out[i * channelCount + 2] = src[b][i];
1538 else if (hasBlue) out[i * channelCount + 2] = 0.f;
1539 if (a >= 0) out[i * channelCount + 3] = src[a][i];
1540 else if (hasAlpha) out[i * channelCount + 3] = 1.f;
1544 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1545 populateBuffer.operator()<half>();
1547 populateBuffer.operator()<
float>();
1550 FreeEXRImage(&image);
1551 FreeEXRHeader(&header);
1552 return combinedChannels;
1557 if (stbi_is_hdr_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()))) {
1558 const ::stb_ptr<float> stbImage{
1559 stbi_loadf_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1566 case 1: format = ImageFormat::R32F;
break;
1567 case 2: format = ImageFormat::RG3232F;
break;
1568 case 3: format = ImageFormat::RGB323232F;
break;
1569 case 4: format = ImageFormat::RGBA32323232F;
break;
1575#ifdef VTFPP_SUPPORT_WEBP
1577 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) {
1578 width = features.width;
1579 height = features.height;
1583 std::vector<std::byte> out;
1584 if (features.has_alpha) {
1585 format = ImageFormat::RGBA8888;
1587 if (!WebPDecodeRGBAInto(
reinterpret_cast<const uint8_t*
>(fileData.data()), fileData.size(),
reinterpret_cast<uint8_t*
>(out.data()), out.size(), width * (
ImageFormatDetails::bpp(format) / 8))) {
1591 format = ImageFormat::RGB888;
1593 if (!WebPDecodeRGBInto(
reinterpret_cast<const uint8_t*
>(fileData.data()), fileData.size(),
reinterpret_cast<uint8_t*
>(out.data()), out.size(), width * (
ImageFormatDetails::bpp(format) / 8))) {
1602 if (fileData.size() > 3 &&
static_cast<char>(fileData[0]) ==
'G' &&
static_cast<char>(fileData[1]) ==
'I' &&
static_cast<char>(fileData[2]) ==
'F') {
1603 const ::stb_ptr<stbi_uc> stbImage{
1604 stbi_load_gif_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()),
nullptr, &width, &height, &frameCount, &channels, 0),
1607 if (!stbImage || !frameCount) {
1611 case 1: format = ImageFormat::I8;
break;
1612 case 2: format = ImageFormat::UV88;
break;
1613 case 3: format = ImageFormat::RGB888;
break;
1614 case 4: format = ImageFormat::RGBA8888;
break;
1617 return {
reinterpret_cast<std::byte*
>(stbImage.get()),
reinterpret_cast<std::byte*
>(stbImage.get() + (
ImageFormatDetails::getDataLength(format, width, height) * frameCount))};
1623 stbi__start_mem(&s,
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()));
1624 if (stbi__png_test(&s)) {
1626 const auto apngDecoder = [&format, &width, &height, &frameCount]<
typename P>(
const auto& stbImage, std::size_t dirOffset) -> std::vector<std::byte> {
1627 auto* dir =
reinterpret_cast<stbi__apng_directory*
>(stbImage.get() + dirOffset);
1628 if (dir->type != STBI__STRUCTURE_TYPE_APNG_DIRECTORY) {
1633 frameCount =
static_cast<int>(dir->num_frames);
1635 static constexpr auto calcPixelOffset = [](uint32_t offsetX, uint32_t offsetY, uint32_t width) {
1636 return ((offsetY * width) + offsetX) *
sizeof(P);
1640 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) {
1641 for (uint32_t y = 0; y < srcHeight; y++) {
1643#ifdef SOURCEPP_BUILD_WITH_TBB
1644 std::execution::unseq,
1646 src.data() + calcPixelOffset( 0, y, srcWidth),
1647 src.data() + calcPixelOffset( srcWidth, y, srcWidth),
1648 dst.data() + calcPixelOffset(srcOffsetX, srcOffsetY + y, dstWidth));
1653 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) {
1654 for (uint32_t y = subOffsetY; y < subOffsetY + subHeight; y++) {
1656#ifdef SOURCEPP_BUILD_WITH_TBB
1657 std::execution::unseq,
1659 src.data() + calcPixelOffset(subOffsetX, y, imgWidth),
1660 src.data() + calcPixelOffset(subOffsetX + subWidth, y, imgWidth),
1661 dst.data() + calcPixelOffset(subOffsetX, y, imgWidth));
1665 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) {
1666 for (uint32_t y = 0; y < clrHeight; y++) {
1668#ifdef SOURCEPP_BUILD_WITH_TBB
1669 std::execution::unseq,
1671 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth),
1672 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth) + (clrWidth *
sizeof(P)),
1673 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth),
1674 [](std::byte) {
return std::byte{0}; });
1678 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) {
1679 for (uint32_t y = 0; y < srcHeight; y++) {
1680 const auto* sp =
reinterpret_cast<const uint8_t*
>(src.data() + calcPixelOffset(0, y, srcWidth));
1681 auto* dp =
reinterpret_cast<uint8_t*
>(dst.data() + calcPixelOffset(srcOffsetX, srcOffsetY + y, dstWidth));
1682 for (uint32_t x = 0; x < srcWidth; x++, sp += 4, dp += 4) {
1685 }
else if ((sp[3] == 0xff) || (dp[3] == 0)) {
1686 std::copy(sp, sp +
sizeof(P), dp);
1688 int u = sp[3] * 0xff;
1689 int v = (0xff - sp[3]) * dp[3];
1691 dp[0] = (sp[0] * u + dp[0] * v) / al;
1692 dp[1] = (sp[1] * u + dp[1] * v) / al;
1693 dp[2] = (sp[2] * u + dp[2] * v) / al;
1701 const uint64_t fullFrameSize =
sizeof(P) * width * height;
1702 uint64_t currentFrameSize = 0;
1703 std::vector<std::byte> out(fullFrameSize * frameCount);
1704 uint64_t srcFrameOffset = 0;
1705 uint64_t dstFrameOffset = 0;
1706 for (uint32_t i = 0; i < dir->num_frames; i++) {
1707 const auto& frame = dir->frames[i];
1708 currentFrameSize =
sizeof(P) * frame.width * frame.height;
1711 if (frame.width == width && frame.height == height && frame.x_offset == 0 && frame.y_offset == 0 && frame.blend_op == STBI_APNG_blend_op_source) {
1712 std::memcpy(out.data() + dstFrameOffset, stbImage.get() + srcFrameOffset, fullFrameSize);
1715 if (frame.blend_op == STBI_APNG_blend_op_source || (i == 0 && frame.blend_op == STBI_APNG_blend_op_over)) {
1716 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);
1717 }
else if (frame.blend_op == STBI_APNG_blend_op_over) {
1718 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);
1724 dstFrameOffset += fullFrameSize;
1725 srcFrameOffset += currentFrameSize;
1728 if (i == dir->num_frames - 1) {
1733 copyImageData({out.data() + dstFrameOffset, out.data() + dstFrameOffset + fullFrameSize}, width, height, {out.data() + dstFrameOffset - fullFrameSize, out.data() + dstFrameOffset}, width, height, 0, 0);
1736 if (frame.dispose_op == STBI_APNG_dispose_op_background || (i == 0 && frame.dispose_op == STBI_APNG_dispose_op_previous)) {
1737 clearImageData({out.data() + dstFrameOffset, out.data() + dstFrameOffset + fullFrameSize}, width, height, frame.width, frame.height, frame.x_offset, frame.y_offset);
1738 }
else if (frame.dispose_op == STBI_APNG_dispose_op_previous) {
1739 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);
1740 }
else if (frame.dispose_op != STBI_APNG_dispose_op_none) {
1747 static const char *dispose_ops[] = {
1748 "STBI_APNG_dispose_op_none",
1749 "STBI_APNG_dispose_op_background",
1750 "STBI_APNG_dispose_op_previous",
1753 static const char *blend_ops[] = {
1754 "STBI_APNG_blend_op_source",
1755 "STBI_APNG_blend_op_over",
1758 fprintf(stderr,
"dir_offset : %zu\n", dirOffset);
1759 fprintf(stderr,
"dir.type : %.*s\n", 4, (
unsigned char *) &dir->type);
1760 fprintf(stderr,
"dir.num_frames : %u\n", dir->num_frames);
1761 fprintf(stderr,
"dir.default_image_is_first_frame : %s\n",
1762 dir->default_image_is_first_frame ?
"yes" :
"no");
1763 fprintf(stderr,
"dir.num_plays : %u\n", dir->num_plays);
1765 for (
int i = 0; i < dir->num_frames; ++i) {
1766 stbi__apng_frame_directory_entry *frame = &dir->frames[i];
1768 fprintf(stderr,
"frame : %u\n", i);
1769 fprintf(stderr,
" width : %u\n", frame->width);
1770 fprintf(stderr,
" height : %u\n", frame->height);
1771 fprintf(stderr,
" x_offset : %u\n", frame->x_offset);
1772 fprintf(stderr,
" y_offset : %u\n", frame->y_offset);
1773 fprintf(stderr,
" delay_num : %u\n", frame->delay_num);
1774 fprintf(stderr,
" delay_den : %u\n", frame->delay_den);
1775 fprintf(stderr,
" dispose_op : %s\n", dispose_ops[frame->dispose_op]);
1776 fprintf(stderr,
" blend_op : %s\n", blend_ops[frame->blend_op]);
1782 std::size_t dirOffset = 0;
1783 if (stbi__png_is16(&s)) {
1784 const ::stb_ptr<stbi_us> stbImage{
1785 stbi__apng_load_16bit(&s, &width, &height, &channels, STBI_rgb_alpha, &dirOffset),
1788 if (stbImage && dirOffset) {
1792 const ::stb_ptr<stbi_uc> stbImage{
1793 stbi__apng_load_8bit(&s, &width, &height, &channels, STBI_rgb_alpha, &dirOffset),
1796 if (stbImage && dirOffset) {
1803#ifdef VTFPP_SUPPORT_QOI
1806 qoi_desc descriptor;
1807 const ::stb_ptr<std::byte> qoiImage{
1808 static_cast<std::byte*
>(qoi_decode(fileData.data(),
static_cast<int>(fileData.size()), &descriptor, 0)),
1814 width =
static_cast<int>(descriptor.width);
1815 height =
static_cast<int>(descriptor.height);
1816 channels = descriptor.channels;
1818 case 3: format = ImageFormat::RGB888;
break;
1819 case 4: format = ImageFormat::RGBA8888;
break;
1827 if (stbi_is_16_bit_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()))) {
1828 const ::stb_ptr<stbi_us> stbImage{
1829 stbi_load_16_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1835 if (channels == 4) {
1836 format = ImageFormat::RGBA16161616;
1837 }
else if (channels >= 1 && channels < 4) {
1839 format = ImageFormat::RGBA16161616;
1844 std::span<uint16_t> inPixels{
reinterpret_cast<uint16_t*
>(stbImage.get()), outPixels.size()};
1846#ifdef SOURCEPP_BUILD_WITH_TBB
1847 std::execution::par_unseq,
1850 return {pixel, 0, 0, 0xffff};
1859 std::span<RG1616> inPixels{
reinterpret_cast<RG1616*
>(stbImage.get()), outPixels.size()};
1861#ifdef SOURCEPP_BUILD_WITH_TBB
1862 std::execution::par_unseq,
1865 return {pixel.r, pixel.g, 0, 0xffff};
1875 std::span<RGB161616> inPixels{
reinterpret_cast<RGB161616*
>(stbImage.get()), outPixels.size()};
1877#ifdef SOURCEPP_BUILD_WITH_TBB
1878 std::execution::par_unseq,
1881 return {pixel.r, pixel.g, pixel.b, 0xffff};
1895 const ::stb_ptr<stbi_uc> stbImage{
1896 stbi_load_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1903 case 1: format = ImageFormat::I8;
break;
1904 case 2: format = ImageFormat::UV88;
break;
1905 case 3: format = ImageFormat::RGB888;
break;
1906 case 4: format = ImageFormat::RGBA8888;
break;
1914 case ResizeMethod::NONE:
break;
1915 case ResizeMethod::POWER_OF_TWO_BIGGER:
return std::bit_ceil(n);
1916 case ResizeMethod::POWER_OF_TWO_SMALLER:
return std::bit_floor(n);
1928 if (imageData.empty() || format == ImageFormat::EMPTY) {
1932 STBIR_RESIZE resize;
1933 const auto setEdgeModesAndFiltersAndDoResize = [edge, filter, &resize] {
1934 stbir_set_edgemodes(&resize,
static_cast<stbir_edge
>(edge),
static_cast<stbir_edge
>(edge));
1936 case ResizeFilter::DEFAULT:
1937 case ResizeFilter::BOX:
1938 case ResizeFilter::BILINEAR:
1939 case ResizeFilter::CUBIC_BSPLINE:
1940 case ResizeFilter::CATMULL_ROM:
1941 case ResizeFilter::MITCHELL:
1942 case ResizeFilter::POINT_SAMPLE: {
1943 stbir_set_filters(&resize,
static_cast<stbir_filter
>(filter),
static_cast<stbir_filter
>(filter));
1946 case ResizeFilter::KAISER: {
1947 static constexpr auto KAISER_BETA = [](
float s) {
1956 static constexpr auto KAISER_FILTER = [](
float x,
float s,
void*) ->
float {
1957 if (x < -1.f || x > 1.f) {
1962 static constexpr auto KAISER_SUPPORT = [](
float s,
void*) ->
float {
1963 float baseSupport = KAISER_BETA(s) / 2.f;
1965 return std::max(2.f, baseSupport - 0.5f);
1967 return std::max(3.f, baseSupport);
1969 stbir_set_filter_callbacks(&resize, KAISER_FILTER, KAISER_SUPPORT, KAISER_FILTER, KAISER_SUPPORT);
1972 case ResizeFilter::NICE: {
1973 static constexpr auto SINC = [](
float x) ->
float {
1974 if (x == 0.f)
return 1.f;
1978 static constexpr auto NICE_FILTER = [](
float x, float,
void*) ->
float {
1979 if (x >= 3.f || x <= -3.f)
return 0.f;
1980 return SINC(x) * SINC(x / 3.f);
1984 static constexpr auto NICE_SUPPORT = [](
float invScale,
void*) ->
float {
1985 return invScale * 3.f;
1987 stbir_set_filter_callbacks(&resize, NICE_FILTER, NICE_SUPPORT, NICE_FILTER, NICE_SUPPORT);
1991 stbir_resize_extended(&resize);
1994 const auto pixelLayout = ::imageFormatToSTBIRPixelLayout(format);
1995 if (pixelLayout == -1) {
1999 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)));
2000 setEdgeModesAndFiltersAndDoResize();
2004 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)));
2005 setEdgeModesAndFiltersAndDoResize();
2009std::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) {
2010 if (imageData.empty() || format == ImageFormat::EMPTY) {
2015 return resizeImageData(imageData, format, width, widthOut, height, heightOut, srgb, filter, edge);
2019std::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) {
2020 if (imageData.empty() || format == ImageFormat::EMPTY || xOffset + newWidth >= width || yOffset + newHeight >= height) {
2026 return convertImageDataToFormat(
cropImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, newWidth, xOffset, height, newHeight, yOffset), container, format, newWidth, newHeight);
2030 std::vector<std::byte> out(pixelSize * newWidth * newHeight);
2031 for (uint16_t y = yOffset; y < yOffset + newHeight; y++) {
2032 std::memcpy(out.data() + (((y - yOffset) * newWidth) * pixelSize), imageData.data() + (((y * width) + xOffset) * pixelSize), newWidth * pixelSize);
2039 if (imageData.empty() || format == ImageFormat::EMPTY) {
2044 return {imageData.begin(), imageData.end()};
2049 return convertImageDataToFormat(
gammaCorrectImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, height, gamma), container, format, width, height);
2052 static constexpr auto calculateGammaLUT = [](
float gamma_, uint8_t channelSize) -> std::array<uint8_t, 256> {
2053 const auto maxSize =
static_cast<float>((1 << channelSize) - 1);
2054 std::array<uint8_t, 256> gammaLUT{};
2055 for (
int i = 0; i < gammaLUT.size(); i++) {
2056 gammaLUT[i] =
static_cast<uint8_t
>(std::clamp(std::pow((
static_cast<float>(i) + 0.5f) / maxSize, gamma_) * maxSize - 0.5f, 0.f, maxSize));
2061 #define VTFPP_CREATE_GAMMA_LUTS(InputType) \
2062 std::unordered_map<uint8_t, std::array<uint8_t, 256>> gammaLUTs; \
2063 if constexpr (ImageFormatDetails::red(ImageFormat::InputType) > 0) { \
2064 if (!gammaLUTs.contains(ImageFormatDetails::red(ImageFormat::InputType))) { \
2065 gammaLUTs[ImageFormatDetails::red(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::red(ImageFormat::InputType)); \
2068 if constexpr (ImageFormatDetails::green(ImageFormat::InputType) > 0) { \
2069 if (!gammaLUTs.contains(ImageFormatDetails::green(ImageFormat::InputType))) { \
2070 gammaLUTs[ImageFormatDetails::green(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::green(ImageFormat::InputType)); \
2073 if constexpr (ImageFormatDetails::blue(ImageFormat::InputType) > 0) { \
2074 if (!gammaLUTs.contains(ImageFormatDetails::blue(ImageFormat::InputType))) { \
2075 gammaLUTs[ImageFormatDetails::blue(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::blue(ImageFormat::InputType)); \
2079 #define VTFPP_APPLY_GAMMA_RED(value) \
2080 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::red(PIXEL_TYPE::FORMAT))[value])
2082 #define VTFPP_APPLY_GAMMA_GREEN(value) \
2083 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::green(PIXEL_TYPE::FORMAT))[value])
2085 #define VTFPP_APPLY_GAMMA_BLUE(value) \
2086 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::blue(PIXEL_TYPE::FORMAT))[value])
2088 std::vector<std::byte> out(imageData.size());
2090#ifdef SOURCEPP_BUILD_WITH_TBB
2091 #define VTFPP_GAMMA_CORRECT(InputType, ...) \
2092 std::span imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
2093 std::span outSpan{reinterpret_cast<ImagePixel::InputType*>(out.data()), out.size() / sizeof(ImagePixel::InputType)}; \
2094 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), outSpan.begin(), [gammaLUTs](ImagePixel::InputType pixel) -> ImagePixel::InputType { \
2095 using PIXEL_TYPE = ImagePixel::InputType; \
2096 return __VA_ARGS__; \
2099 #define VTFPP_GAMMA_CORRECT(InputType, ...) \
2100 std::span imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
2101 std::span outSpan{reinterpret_cast<ImagePixel::InputType*>(out.data()), out.size() / sizeof(ImagePixel::InputType)}; \
2102 std::transform(imageDataSpan.begin(), imageDataSpan.end(), outSpan.begin(), [gammaLUTs](ImagePixel::InputType pixel) -> ImagePixel::InputType { \
2103 using PIXEL_TYPE = ImagePixel::InputType; \
2104 return __VA_ARGS__; \
2107 #define VTFPP_CASE_GAMMA_CORRECT_AND_BREAK(InputType, ...) \
2108 case InputType: { VTFPP_CREATE_GAMMA_LUTS(InputType) VTFPP_GAMMA_CORRECT(InputType, __VA_ARGS__); } break
2114 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)});
2116 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)});
2132 #undef VTFPP_CASE_GAMMA_CORRECT_AND_BREAK
2133 #undef VTFPP_GAMMA_CORRECT
2134 #undef VTFPP_APPLY_GAMMA_BLUE
2135 #undef VTFPP_APPLY_GAMMA_GREEN
2136 #undef VTFPP_APPLY_GAMMA_RED
2137 #undef VTFPP_CREATE_GAMMA_LUTS
2150 return convertImageDataToFormat(
invertGreenChannelForImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, height), container, format, width, height);
2153 #define VTFPP_INVERT_GREEN(PixelType, ChannelName, ...) \
2154 static constexpr auto channelSize = ImageFormatDetails::green(ImagePixel::PixelType::FORMAT); \
2155 std::span imageDataSpan{reinterpret_cast<const ImagePixel::PixelType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::PixelType)}; \
2156 std::span outSpan{reinterpret_cast<ImagePixel::PixelType*>(out.data()), out.size() / sizeof(ImagePixel::PixelType)}; \
2157 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), outSpan.begin(), [](ImagePixel::PixelType pixel) -> ImagePixel::PixelType { \
2158 if constexpr (std::same_as<decltype(pixel.ChannelName), float> || std::same_as<decltype(pixel.ChannelName), half>) { \
2159 pixel.ChannelName = static_cast<decltype(pixel.ChannelName)>(static_cast<float>(static_cast<uint64_t>(1) << channelSize) - 1.f - static_cast<float>(pixel.ChannelName)); \
2161 if constexpr (channelSize >= sizeof(uint32_t) * 8) { \
2162 pixel.ChannelName = static_cast<decltype(pixel.ChannelName)>((static_cast<uint64_t>(1) << channelSize) - 1 - static_cast<uint32_t>(pixel.ChannelName)); \
2164 pixel.ChannelName = static_cast<decltype(pixel.ChannelName)>(static_cast<uint32_t>(1 << channelSize) - 1 - static_cast<uint32_t>(pixel.ChannelName)); \
2169#ifdef SOURCEPP_BUILD_WITH_TBB
2170 #define VTFPP_INVERT_GREEN_CASE(PixelType) \
2171 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, g, std::execution::par_unseq); break; }
2172 #define VTFPP_INVERT_GREEN_CASE_CA_OVERRIDE(PixelType, ChannelName) \
2173 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, ChannelName, std::execution::par_unseq); break; }
2175 #define VTFPP_INVERT_GREEN_CASE(PixelType) \
2176 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, g); } break
2177 #define VTFPP_INVERT_GREEN_CASE_CA_OVERRIDE(PixelType, ChannelName) \
2178 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, ChannelName); } break
2181 std::vector<std::byte> out(imageData.size());
2223 #undef VTFPP_INVERT_GREEN_CASE_CA_OVERRIDE
2224 #undef VTFPP_INVERT_GREEN_CASE
2225 #undef VTFPP_INVERT_GREEN
#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.
constexpr T nearestPowerOf2(T n)
constexpr double kaiserWindow(double x, double b)
consteval uint32_t makeFourCC(const char fourCC[4])
Creates a FourCC identifier from a string of 4 characters.
std::vector< std::byte > convertFileToImageData(std::span< const std::byte > fileData, ImageFormat &format, int &width, int &height, int &frameCount)
std::vector< std::byte > convertImageDataToFile(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height, FileFormat fileFormat=FileFormat::DEFAULT)
Converts image data to the given file format (PNG or EXR by default).
void setResizedDims(uint16_t &width, ResizeMethod widthResize, uint16_t &height, ResizeMethod heightResize)
Set the new image dimensions given a resize method.
std::array< std::vector< std::byte >, 6 > convertHDRIToCubeMap(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height, uint16_t resolution=0, bool bilinear=true)
Converts an HDRI into a cubemap.
std::vector< std::byte > convertImageDataToFormat(std::span< const std::byte > imageData, ImageFormat oldFormat, ImageFormat newFormat, uint16_t width, uint16_t height, float quality=DEFAULT_COMPRESSED_QUALITY)
Converts an image from one format to another.
uint16_t getResizedDim(uint16_t n, ResizeMethod method)
Get the new image size given a resize method.
constexpr float DEFAULT_COMPRESSED_QUALITY
std::vector< std::byte > convertSeveralImageDataToFormat(std::span< const std::byte > imageData, ImageFormat oldFormat, ImageFormat newFormat, uint8_t mipCount, uint16_t frameCount, uint8_t faceCount, uint16_t width, uint16_t height, uint16_t depth, float quality=DEFAULT_COMPRESSED_QUALITY)
Converts several images from one format to another.
std::vector< std::byte > gammaCorrectImageData(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height, float gamma)
Perform gamma correction on the given image data. Will not perform gamma correction if the input imag...
FileFormat getDefaultFileFormatForImageFormat(ImageFormat format)
PNG for integer formats, EXR for floating point formats.
std::vector< std::byte > extractChannelFromImageData(std::span< const std::byte > imageData, auto P::*channel)
Extracts a single channel from the given image data.
std::vector< std::byte > cropImageData(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t xOffset, uint16_t height, uint16_t newHeight, uint16_t yOffset)
Crops the given image to the new dimensions. If the image format is compressed it will be converted t...
std::vector< std::byte > resizeImageDataStrict(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t &widthOut, ResizeMethod widthResize, uint16_t height, uint16_t newHeight, uint16_t &heightOut, ResizeMethod heightResize, bool srgb, ResizeFilter filter, ResizeEdge edge=ResizeEdge::CLAMP)
Resize given image data to the new dimensions, where the new width and height are governed by the res...
std::vector< std::byte > invertGreenChannelForImageData(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height)
Invert the green channel. Meant for converting normal maps between OpenGL and DirectX formats.
std::vector< std::byte > resizeImageData(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t height, uint16_t newHeight, bool srgb, ResizeFilter filter, ResizeEdge edge=ResizeEdge::CLAMP)
Resize given image data to the new dimensions.
constexpr uint32_t getMipDim(uint8_t mip, uint16_t dim)
@ CONSOLE_ARGB8888_LINEAR
@ CONSOLE_BGRX8888_LINEAR
@ CONSOLE_RGBA8888_LINEAR
@ CONSOLE_ABGR8888_LINEAR
@ CONSOLE_BGRX5551_LINEAR
@ CONSOLE_BGRA8888_LINEAR
@ CONSOLE_RGBA16161616_LINEAR