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 if (quality >= 0.f) {
322 options.fquality = std::min(quality, 1.f);
323 }
else if (oldFormat == ImageFormat::BC7 || newFormat == ImageFormat::BC7 || oldFormat == ImageFormat::BC6H || newFormat == ImageFormat::BC6H) {
324 options.fquality = 0.1f;
326 options.fquality = 1.f;
328 options.bDXT1UseAlpha = oldFormat == ImageFormat::DXT1_ONE_BIT_ALPHA || newFormat == ImageFormat::DXT1_ONE_BIT_ALPHA;
329 if (options.bDXT1UseAlpha) {
330 options.nAlphaThreshold = 128;
333 if (CMP_ConvertTexture(&srcTexture, &destTexture, &options,
nullptr) != CMP_OK) {
339[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA8888(std::span<const std::byte> imageData,
ImageFormat format) {
340 using namespace ImageConversion;
342 if (imageData.empty()) {
346 if (format == ImageFormat::RGBA8888 || format == ImageFormat::UVWQ8888) {
347 return {imageData.begin(), imageData.end()};
350 std::vector<std::byte> newData;
354 #define VTFPP_REMAP_TO_8(value, shift) math::remap<uint8_t>((value), (1 << (shift)) - 1, (1 << 8) - 1)
356 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
357 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
358 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA8888 { \
359 return {(r), (g), (b), (a)}; \
361#ifdef SOURCEPP_BUILD_WITH_TBB
362 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
364 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
366 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
367 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
406 #undef VTFPP_CASE_CONVERT_AND_BREAK
408 #undef VTFPP_CONVERT_DETAIL
409 #undef VTFPP_REMAP_TO_8
414[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888(std::span<const std::byte> imageData,
ImageFormat format) {
415 using namespace ImageConversion;
417 if (imageData.empty()) {
421 if (format == ImageFormat::RGBA8888) {
422 return {imageData.begin(), imageData.end()};
426 std::vector<std::byte> newData;
429 #define VTFPP_REMAP_FROM_8(value, shift) math::remap<uint8_t>((value), (1 << 8) - 1, (1 << (shift)) - 1)
431#ifdef SOURCEPP_BUILD_WITH_TBB
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(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA8888 pixel) -> ImagePixel::InputType { \
435 return __VA_ARGS__; \
438 #define VTFPP_CONVERT(InputType, ...) \
439 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
440 std::transform(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA8888 pixel) -> ImagePixel::InputType { \
441 return __VA_ARGS__; \
444 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
445 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
484 #undef VTFPP_CASE_CONVERT_AND_BREAK
486 #undef VTFPP_REMAP_FROM_8
491[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA16161616(std::span<const std::byte> imageData,
ImageFormat format) {
492 using namespace ImageConversion;
494 if (imageData.empty()) {
498 if (format == ImageFormat::RGBA16161616) {
499 return {imageData.begin(), imageData.end()};
502 std::vector<std::byte> newData;
506 #define VTFPP_REMAP_TO_16(value, shift) math::remap<uint16_t>((value), (1 << (shift)) - 1, (1 << 16) - 1)
508 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
509 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
510 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA16161616 { \
511 return { static_cast<uint16_t>(r), static_cast<uint16_t>(g), static_cast<uint16_t>(b), static_cast<uint16_t>(a) }; \
513#ifdef SOURCEPP_BUILD_WITH_TBB
514 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
516 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
518 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
519 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
521 #define VTFPP_CONVERT_REMAP(InputType, r, g, b, a) \
523 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)), \
528 VTFPP_REMAP_TO_16((a), ImageFormatDetails::alpha(ImageFormat::InputType))); \
529 } else if constexpr (ImageFormatDetails::alpha(ImageFormat::InputType) == 1) { \
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)), \
536 VTFPP_CONVERT(InputType, \
537 VTFPP_REMAP_TO_16((r), ImageFormatDetails::red(ImageFormat::InputType)), \
538 VTFPP_REMAP_TO_16((g), ImageFormatDetails::green(ImageFormat::InputType)), \
539 VTFPP_REMAP_TO_16((b), ImageFormatDetails::blue(ImageFormat::InputType)), \
543 #define VTFPP_CASE_CONVERT_REMAP_AND_BREAK(InputType, r, g, b, a) \
544 case InputType: { VTFPP_CONVERT_REMAP(InputType, r, g, b, a); } \
555 #undef VTFPP_CASE_CONVERT_REMAP_AND_BREAK
556 #undef VTFPP_CONVERT_REMAP
557 #undef VTFPP_CASE_CONVERT_AND_BREAK
559 #undef VTFPP_CONVERT_DETAIL
560 #undef VTFPP_REMAP_TO_16
565[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616(std::span<const std::byte> imageData,
ImageFormat format) {
566 using namespace ImageConversion;
568 if (imageData.empty()) {
572 if (format == ImageFormat::RGBA16161616) {
573 return {imageData.begin(), imageData.end()};
577 std::vector<std::byte> newData;
580 #define VTFPP_REMAP_FROM_16(value, shift) static_cast<uint8_t>(math::remap<uint16_t>((value), (1 << 16) - 1, (1 << (shift)) - 1))
582#ifdef SOURCEPP_BUILD_WITH_TBB
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(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA16161616 pixel) -> ImagePixel::InputType { \
586 return __VA_ARGS__; \
589 #define VTFPP_CONVERT(InputType, ...) \
590 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
591 std::transform(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA16161616 pixel) -> ImagePixel::InputType { \
592 return __VA_ARGS__; \
595 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
596 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
606 #undef VTFPP_CASE_CONVERT_AND_BREAK
608 #undef VTFPP_REMAP_FROM_16
613[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA32323232F(std::span<const std::byte> imageData,
ImageFormat format) {
614 if (imageData.empty()) {
618 if (format == ImageFormat::RGBA32323232F) {
619 return {imageData.begin(), imageData.end()};
622 std::vector<std::byte> newData;
626 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
627 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
628 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA32323232F { return {(r), (g), (b), (a)}; })
629#ifdef SOURCEPP_BUILD_WITH_TBB
630 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
632 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
634 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
635 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
648 #undef VTFPP_CASE_CONVERT_AND_BREAK
650 #undef VTFPP_CONVERT_DETAIL
655[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232F(std::span<const std::byte> imageData,
ImageFormat format) {
656 using namespace ImageConversion;
658 if (imageData.empty()) {
662 if (format == ImageFormat::RGBA32323232F) {
663 return {imageData.begin(), imageData.end()};
667 std::vector<std::byte> newData;
670#ifdef SOURCEPP_BUILD_WITH_TBB
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(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA32323232F pixel) -> ImagePixel::InputType { \
674 return __VA_ARGS__; \
677 #define VTFPP_CONVERT(InputType, ...) \
678 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
679 std::transform(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA32323232F pixel) -> ImagePixel::InputType { \
680 return __VA_ARGS__; \
683 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
684 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
697 #undef VTFPP_CASE_CONVERT_AND_BREAK
703[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888ToRGBA32323232F(std::span<const std::byte> imageData) {
704 if (imageData.empty()) {
708 std::vector<std::byte> newData;
714#ifdef SOURCEPP_BUILD_WITH_TBB
715 std::execution::par_unseq,
719 static_cast<float>(pixel.r) / static_cast<float>((1 << 8) - 1),
720 static_cast<float>(pixel.g) / static_cast<float>((1 << 8) - 1),
721 static_cast<float>(pixel.b) / static_cast<float>((1 << 8) - 1),
722 static_cast<float>(pixel.a) / static_cast<float>((1 << 8) - 1),
729[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232FToRGBA8888(std::span<const std::byte> imageData) {
730 if (imageData.empty()) {
734 std::vector<std::byte> newData;
740#ifdef SOURCEPP_BUILD_WITH_TBB
741 std::execution::par_unseq,
745 static_cast<uint8_t>(std::clamp(pixel.r, 0.f, 1.f) * ((1 << 8) - 1)),
746 static_cast<uint8_t>(std::clamp(pixel.g, 0.f, 1.f) * ((1 << 8) - 1)),
747 static_cast<uint8_t>(std::clamp(pixel.b, 0.f, 1.f) * ((1 << 8) - 1)),
748 static_cast<uint8_t>(std::clamp(pixel.a, 0.f, 1.f) * ((1 << 8) - 1)),
755[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888ToRGBA16161616(std::span<const std::byte> imageData) {
756 if (imageData.empty()) {
760 std::vector<std::byte> newData;
766#ifdef SOURCEPP_BUILD_WITH_TBB
767 std::execution::par_unseq,
771 math::remap<uint16_t>(pixel.r, (1 << 8) - 1, (1 << 16) - 1),
772 math::remap<uint16_t>(pixel.g, (1 << 8) - 1, (1 << 16) - 1),
773 math::remap<uint16_t>(pixel.b, (1 << 8) - 1, (1 << 16) - 1),
774 math::remap<uint16_t>(pixel.a, (1 << 8) - 1, (1 << 16) - 1),
781[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616ToRGBA8888(std::span<const std::byte> imageData) {
782 if (imageData.empty()) {
786 std::vector<std::byte> newData;
792#ifdef SOURCEPP_BUILD_WITH_TBB
793 std::execution::par_unseq,
797 static_cast<uint8_t>(math::remap<uint16_t>(pixel.r, (1 << 16) - 1, (1 << 8) - 1)),
798 static_cast<uint8_t>(math::remap<uint16_t>(pixel.g, (1 << 16) - 1, (1 << 8) - 1)),
799 static_cast<uint8_t>(math::remap<uint16_t>(pixel.b, (1 << 16) - 1, (1 << 8) - 1)),
800 static_cast<uint8_t>(math::remap<uint16_t>(pixel.a, (1 << 16) - 1, (1 << 8) - 1)),
807[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232FToRGBA16161616(std::span<const std::byte> imageData) {
808 if (imageData.empty()) {
812 std::vector<std::byte> newData;
818#ifdef SOURCEPP_BUILD_WITH_TBB
819 std::execution::par_unseq,
823 static_cast<uint16_t>(std::clamp(pixel.r, 0.f, 1.f) * ((1 << 16) - 1)),
824 static_cast<uint16_t>(std::clamp(pixel.g, 0.f, 1.f) * ((1 << 16) - 1)),
825 static_cast<uint16_t>(std::clamp(pixel.b, 0.f, 1.f) * ((1 << 16) - 1)),
826 static_cast<uint16_t>(std::clamp(pixel.a, 0.f, 1.f) * ((1 << 16) - 1)),
833[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616ToRGBA32323232F(std::span<const std::byte> imageData) {
834 if (imageData.empty()) {
838 std::vector<std::byte> newData;
844#ifdef SOURCEPP_BUILD_WITH_TBB
845 std::execution::par_unseq,
849 static_cast<float>(pixel.r) / static_cast<float>((1 << 16) - 1),
850 static_cast<float>(pixel.g) / static_cast<float>((1 << 16) - 1),
851 static_cast<float>(pixel.b) / static_cast<float>((1 << 16) - 1),
852 static_cast<float>(pixel.a) / static_cast<float>((1 << 16) - 1),
862 if (imageData.empty() || oldFormat == ImageFormat::EMPTY) {
866 if (oldFormat == newFormat) {
867 return {imageData.begin(), imageData.end()};
870 std::vector<std::byte> newData;
874 newData = ::convertImageDataUsingCompressonator(imageData, oldFormat, intermediaryOldFormat, width, height, quality);
876 switch (intermediaryOldFormat) {
877 case ImageFormat::RGBA8888: newData = ::convertImageDataToRGBA8888(imageData, oldFormat);
break;
878 case ImageFormat::RGBA16161616: newData = ::convertImageDataToRGBA16161616(imageData, oldFormat);
break;
879 case ImageFormat::RGBA32323232F: newData = ::convertImageDataToRGBA32323232F(imageData, oldFormat);
break;
884 if (intermediaryOldFormat == newFormat) {
889 if (intermediaryOldFormat != intermediaryNewFormat) {
890 if (intermediaryOldFormat == ImageFormat::RGBA8888) {
891 if (intermediaryNewFormat == ImageFormat::RGBA16161616) {
892 newData = ::convertImageDataFromRGBA8888ToRGBA16161616(newData);
893 }
else if (intermediaryNewFormat == ImageFormat::RGBA32323232F) {
894 newData = ::convertImageDataFromRGBA8888ToRGBA32323232F(newData);
898 }
else if (intermediaryOldFormat == ImageFormat::RGBA16161616) {
899 if (intermediaryNewFormat == ImageFormat::RGBA8888) {
900 newData = ::convertImageDataFromRGBA16161616ToRGBA8888(newData);
901 }
else if (intermediaryNewFormat == ImageFormat::RGBA32323232F) {
902 newData = ::convertImageDataFromRGBA16161616ToRGBA32323232F(newData);
906 }
else if (intermediaryOldFormat == ImageFormat::RGBA32323232F) {
907 if (intermediaryNewFormat == ImageFormat::RGBA8888) {
908 newData = ::convertImageDataFromRGBA32323232FToRGBA8888(newData);
909 }
else if (intermediaryNewFormat == ImageFormat::RGBA16161616) {
910 newData = ::convertImageDataFromRGBA32323232FToRGBA16161616(newData);
919 if (intermediaryNewFormat == newFormat) {
924 newData = ::convertImageDataUsingCompressonator(newData, intermediaryNewFormat, newFormat, width, height, quality);
926 switch (intermediaryNewFormat) {
927 case ImageFormat::RGBA8888: newData = ::convertImageDataFromRGBA8888(newData, newFormat);
break;
928 case ImageFormat::RGBA16161616: newData = ::convertImageDataFromRGBA16161616(newData, newFormat);
break;
929 case ImageFormat::RGBA32323232F: newData = ::convertImageDataFromRGBA32323232F(newData, newFormat);
break;
938 if (imageData.empty() || oldFormat == ImageFormat::EMPTY) {
942 if (oldFormat == newFormat) {
943 return {imageData.begin(), imageData.end()};
947 for(
int mip = mipCount - 1; mip >= 0; mip--) {
948 for (
int frame = 0; frame < frameCount; frame++) {
949 for (
int face = 0; face < faceCount; face++) {
950 for (
int slice = 0; slice < depth; slice++) {
951 if (uint32_t oldOffset, oldLength;
ImageFormatDetails::getDataPosition(oldOffset, oldLength, oldFormat, mip, mipCount, frame, frameCount, face, faceCount, width, height, slice, depth)) {
953 if (uint32_t newOffset, newLength;
ImageFormatDetails::getDataPosition(newOffset, newLength, newFormat, mip, mipCount, frame, frameCount, face, faceCount, width, height, slice, depth) && newLength == convertedImageData.size()) {
954 std::memcpy(out.data() + newOffset, convertedImageData.data(), newLength);
965 if (imageData.empty() || format == ImageFormat::EMPTY) {
973 std::span<const float> imageDataRGBA32323232F{
reinterpret_cast<const float*
>(imageData.data()),
reinterpret_cast<const float*
>(imageData.data() + imageData.size())};
975 std::vector<std::byte> possiblyConvertedDataOrEmptyDontUseMeDirectly;
976 if (format != ImageFormat::RGBA32323232F) {
977 possiblyConvertedDataOrEmptyDontUseMeDirectly =
convertImageDataToFormat(imageData, format, ImageFormat::RGBA32323232F, width, height);
978 imageDataRGBA32323232F = {
reinterpret_cast<const float*
>(possiblyConvertedDataOrEmptyDontUseMeDirectly.data()),
reinterpret_cast<const float*
>(possiblyConvertedDataOrEmptyDontUseMeDirectly.data() + possiblyConvertedDataOrEmptyDontUseMeDirectly.size())};
983 static constexpr std::array<std::array<math::Vec3f, 3>, 6> startRightUp = {{
984 {{{ 1.0f, -1.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}},
985 {{{ 1.0f, 1.0f, 1.0f}, { 0.0f,-1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}},
986 {{{ 1.0f, 1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, { 0.0f,-1.0f, 0.0f}}},
987 {{{-1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}}},
988 {{{ 1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}}},
989 {{{ 1.0f, 1.0f, -1.0f}, { 0.0f, 0.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}},
992 std::array<std::vector<std::byte>, 6> faceData;
994#ifdef SOURCEPP_BUILD_WITH_THREADS
995 const auto faceExtraction = [&](
int i) {
997 for (
int i = 0; i < faceData.size(); i++) {
999 const auto start = startRightUp[i][0];
1000 const auto right = startRightUp[i][1];
1001 const auto up = startRightUp[i][2];
1004 std::span<float> face{
reinterpret_cast<float*
>(faceData[i].data()),
reinterpret_cast<float*
>(faceData[i].data() + faceData[i].size())};
1006 for (
int row = 0; row < resolution; row++) {
1007 for (
int col = 0; col < resolution; col++) {
1008 math::Vec3f pixelDirection3d{
1009 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],
1010 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],
1011 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],
1013 float azimuth = std::atan2(pixelDirection3d[0], -pixelDirection3d[2]) +
math::pi_f32;
1014 float elevation = std::atan(pixelDirection3d[1] / std::sqrt(pixelDirection3d[0] * pixelDirection3d[0] + pixelDirection3d[2] * pixelDirection3d[2])) +
math::pi_f32 / 2.f;
1015 float colHdri = (azimuth /
math::pi_f32 / 2.f) *
static_cast<float>(width);
1016 float rowHdri = (elevation /
math::pi_f32) *
static_cast<float>(height);
1018 int colNearest = std::clamp(
static_cast<int>(colHdri), 0, width - 1);
1019 int rowNearest = std::clamp(
static_cast<int>(rowHdri), 0, height - 1);
1020 face[col * 4 + resolution * row * 4 + 0] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 0];
1021 face[col * 4 + resolution * row * 4 + 1] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 1];
1022 face[col * 4 + resolution * row * 4 + 2] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 2];
1023 face[col * 4 + resolution * row * 4 + 3] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 3];
1025 float intCol, intRow;
1027 float factorCol = std::modf(colHdri - 0.5f, &intCol);
1028 float factorRow = std::modf(rowHdri - 0.5f, &intRow);
1029 int low_idx_row =
static_cast<int>(intRow);
1030 int low_idx_column =
static_cast<int>(intCol);
1031 int high_idx_column;
1032 if (factorCol < 0.f) {
1035 high_idx_column = width - 1;
1036 }
else if (low_idx_column == width - 1) {
1038 high_idx_column = 0;
1040 high_idx_column = low_idx_column + 1;
1043 if (factorRow < 0.f || low_idx_row == height - 1) {
1044 high_idx_row = low_idx_row;
1047 high_idx_row = low_idx_row + 1;
1049 factorCol = std::abs(factorCol);
1050 factorRow = std::abs(factorRow);
1051 float f1 = (1 - factorRow) * (1 - factorCol);
1052 float f2 = factorRow * (1 - factorCol);
1053 float f3 = (1 - factorRow) * factorCol;
1054 float f4 = factorRow * factorCol;
1055 for (
int j = 0; j < 4; j++) {
1056 face[col * 4 + resolution * row * 4 + j] =
1057 imageDataRGBA32323232F[low_idx_column * 4 + width * low_idx_row * 4 + j] * f1 +
1058 imageDataRGBA32323232F[low_idx_column * 4 + width * high_idx_row * 4 + j] * f2 +
1059 imageDataRGBA32323232F[high_idx_column * 4 + width * low_idx_row * 4 + j] * f3 +
1060 imageDataRGBA32323232F[high_idx_column * 4 + width * high_idx_row * 4 + j] * f4;
1065 if (format != ImageFormat::RGBA32323232F) {
1069#ifdef SOURCEPP_BUILD_WITH_THREADS
1071 std::array<std::future<void>, 6> faceFutures{
1072 std::async(std::launch::async, faceExtraction, 0),
1073 std::async(std::launch::async, faceExtraction, 1),
1074 std::async(std::launch::async, faceExtraction, 2),
1075 std::async(std::launch::async, faceExtraction, 3),
1076 std::async(std::launch::async, faceExtraction, 4),
1077 std::async(std::launch::async, faceExtraction, 5),
1079 for (
auto& future : faceFutures) {
1089#ifdef VTFPP_SUPPORT_EXR
1097 if (imageData.empty() || format == ImageFormat::EMPTY) {
1100 std::vector<std::byte> out;
1101 auto stbWriteFunc = [](
void* out_,
void* data,
int size) {
1102 std::copy_n(
static_cast<std::byte*
>(data), size, std::back_inserter(*
static_cast<std::vector<std::byte>*
>(out_)));
1105 if (fileFormat == FileFormat::DEFAULT) {
1108 switch (fileFormat) {
1109 case FileFormat::PNG: {
1110 if (format == ImageFormat::RGB888) {
1111 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data(), 0);
1112 }
else if (format == ImageFormat::RGBA8888) {
1113 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), imageData.data(), 0);
1116 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data(), 0);
1119 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), rgba.data(), 0);
1123 case FileFormat::JPG: {
1124 if (format == ImageFormat::RGB888) {
1125 stbi_write_jpg_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data(), 95);
1128 stbi_write_jpg_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data(), 95);
1132 case FileFormat::BMP: {
1133 if (format == ImageFormat::RGB888) {
1134 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data());
1135 }
else if (format == ImageFormat::RGBA8888) {
1136 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), imageData.data());
1139 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data());
1142 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), rgba.data());
1146 case FileFormat::TGA: {
1147 if (format == ImageFormat::RGB888) {
1148 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data());
1149 }
else if (format == ImageFormat::RGBA8888) {
1150 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), imageData.data());
1153 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data());
1156 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), rgba.data());
1160#ifdef VTFPP_SUPPORT_WEBP
1161 case FileFormat::WEBP: {
1163 WebPConfigInit(&config);
1164 WebPConfigPreset(&config, WEBP_PRESET_DRAWING, 75.f);
1165 WebPConfigLosslessPreset(&config, 6);
1168 if (!WebPPictureInit(&pic)) {
1172 pic.height = height;
1173 if (!WebPPictureAlloc(&pic)) {
1177 if (format == ImageFormat::RGB888) {
1178 WebPPictureImportRGB(&pic,
reinterpret_cast<const uint8_t*
>(imageData.data()),
static_cast<int>(width *
sizeof(
ImagePixel::RGB888)));
1179 }
else if (format == ImageFormat::RGBA8888) {
1180 WebPPictureImportRGBA(&pic,
reinterpret_cast<const uint8_t*
>(imageData.data()),
static_cast<int>(width *
sizeof(
ImagePixel::RGBA8888)));
1183 WebPPictureImportRGB(&pic,
reinterpret_cast<const uint8_t*
>(rgb.data()),
static_cast<int>(width *
sizeof(
ImagePixel::RGB888)));
1186 WebPPictureImportRGBA(&pic,
reinterpret_cast<const uint8_t*
>(rgba.data()),
static_cast<int>(width *
sizeof(
ImagePixel::RGBA8888)));
1189 WebPMemoryWriter writer;
1190 WebPMemoryWriterInit(&writer);
1191 pic.writer = &WebPMemoryWrite;
1192 pic.custom_ptr = &writer;
1194 int ok = WebPEncode(&config, &pic);
1195 WebPPictureFree(&pic);
1197 WebPMemoryWriterClear(&writer);
1201 if (writer.mem && writer.size) {
1202 out.resize(writer.size);
1203 std::memcpy(out.data(), writer.mem, writer.size);
1205 WebPMemoryWriterClear(&writer);
1209#ifdef VTFPP_SUPPORT_QOI
1210 case FileFormat::QOI: {
1211 qoi_desc descriptor{
1215 .colorspace = QOI_SRGB,
1217 void* qoiData =
nullptr;
1219 if (format == ImageFormat::RGB888) {
1220 descriptor.channels = 3;
1221 qoiData = qoi_encode(imageData.data(), &descriptor, &qoiDataLen);
1222 }
else if (format == ImageFormat::RGBA8888) {
1223 descriptor.channels = 4;
1224 qoiData = qoi_encode(imageData.data(), &descriptor, &qoiDataLen);
1227 descriptor.channels = 3;
1228 qoiData = qoi_encode(rgb.data(), &descriptor, &qoiDataLen);
1231 descriptor.channels = 4;
1232 qoiData = qoi_encode(rgba.data(), &descriptor, &qoiDataLen);
1234 if (qoiData && qoiDataLen) {
1235 out.resize(qoiDataLen);
1236 std::memcpy(out.data(), qoiData, qoiDataLen);
1242 case FileFormat::HDR: {
1243 if (format == ImageFormat::RGB323232F) {
1244 stbi_write_hdr_to_func(stbWriteFunc, &out, width, height,
ImageFormatDetails::bpp(ImageFormat::RGB323232F) / (8 *
sizeof(
float)),
reinterpret_cast<const float*
>(imageData.data()));
1247 stbi_write_hdr_to_func(stbWriteFunc, &out, width, height,
ImageFormatDetails::bpp(ImageFormat::RGB323232F) / (8 *
sizeof(
float)),
reinterpret_cast<const float*
>(hdr.data()));
1251#ifdef VTFPP_SUPPORT_EXR
1252 case FileFormat::EXR: {
1254 InitEXRHeader(&header);
1256 std::vector<std::byte> rawData;
1260 format = ImageFormat::RGBA32323232F;
1263 format = ImageFormat::RGB323232F;
1266 rawData = {imageData.begin(), imageData.end()};
1270 header.channels =
static_cast<EXRChannelInfo*
>(std::malloc(header.num_channels *
sizeof(EXRChannelInfo)));
1271 header.pixel_types =
static_cast<int*
>(malloc(header.num_channels *
sizeof(
int)));
1272 header.requested_pixel_types =
static_cast<int*
>(malloc(header.num_channels *
sizeof(
int)));
1274 switch (header.num_channels) {
1276 header.channels[0].name[0] =
'A';
1277 header.channels[1].name[0] =
'B';
1278 header.channels[2].name[0] =
'G';
1279 header.channels[3].name[0] =
'R';
1282 header.channels[0].name[0] =
'B';
1283 header.channels[1].name[0] =
'G';
1284 header.channels[2].name[0] =
'R';
1287 header.channels[0].name[0] =
'G';
1288 header.channels[1].name[0] =
'R';
1291 header.channels[0].name[0] =
'R';
1294 FreeEXRHeader(&header);
1297 for (
int i = 0; i < header.num_channels; i++) {
1298 header.channels[i].name[1] =
'\0';
1301 int pixelType = (
ImageFormatDetails::red(format) / 8) ==
sizeof(half) ? TINYEXR_PIXELTYPE_HALF : TINYEXR_PIXELTYPE_FLOAT;
1302 for (
int i = 0; i < header.num_channels; i++) {
1303 header.pixel_types[i] = pixelType;
1304 header.requested_pixel_types[i] = pixelType;
1307 std::vector<std::vector<std::byte>> images(header.num_channels);
1308 std::vector<void*> imagePtrs(header.num_channels);
1309 switch (header.num_channels) {
1311 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1324 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1326 FreeEXRHeader(&header);
1334 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1343 images[0] = rawData;
1346 FreeEXRHeader(&header);
1349 for (
int i = 0; i < header.num_channels; i++) {
1350 imagePtrs[i] = images[i].data();
1354 InitEXRImage(&image);
1355 image.width = width;
1356 image.height = height;
1357 image.images =
reinterpret_cast<unsigned char**
>(imagePtrs.data());
1358 image.num_channels = header.num_channels;
1360 unsigned char* data =
nullptr;
1361 const char* err =
nullptr;
1363 size_t size = SaveEXRImageToMemory(&image, &header, &data, &err);
1365 FreeEXRErrorMessage(err);
1366 FreeEXRHeader(&header);
1370 out = {
reinterpret_cast<std::byte*
>(data),
reinterpret_cast<std::byte*
>(data) + size};
1374 FreeEXRHeader(&header);
1378 case FileFormat::DEFAULT:
1387using stb_ptr = std::unique_ptr<T, void(*)(
void*)>;
1392 stbi_convert_iphone_png_to_rgb(
true);
1394 format = ImageFormat::EMPTY;
1400#ifdef VTFPP_SUPPORT_EXR
1402 if (EXRVersion version; ParseEXRVersionFromMemory(&version,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size()) == TINYEXR_SUCCESS) {
1403 if (version.multipart || version.non_image) {
1408 InitEXRHeader(&header);
1409 const char* err =
nullptr;
1410 if (ParseEXRHeaderFromMemory(&header, &version,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size(), &err) != TINYEXR_SUCCESS) {
1411 FreeEXRErrorMessage(err);
1416 if (header.num_channels < 1) {
1417 FreeEXRHeader(&header);
1422 std::unordered_map<std::string_view, int> channelIndices{{
"R", -1}, {
"G", -1}, {
"B", -1}, {
"A", -1}, {
"Y", -1}};
1426 auto channelType = header.pixel_types[0];
1427 for (
int i = 1; i < header.num_channels; i++) {
1429 if (header.pixel_types[i] > channelType && channelIndices.contains(header.channels[i].name)) {
1430 channelType = header.pixel_types[i];
1434 if (channelType == TINYEXR_PIXELTYPE_UINT) {
1435 channelType = TINYEXR_PIXELTYPE_HALF;
1439 for (
int i = 0; i < header.num_channels; i++) {
1440 if (channelIndices.contains(header.channels[i].name)) {
1441 channelIndices[header.channels[i].name] = i;
1444 if (channelIndices[
"Y"] >= 0) {
1445 if (channelIndices[
"A"] >= 0) {
1446 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::RGBA16161616F : ImageFormat::RGBA32323232F;
1448 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1450 channelType = TINYEXR_PIXELTYPE_FLOAT;
1452 format = ImageFormat::RGB323232F;
1454 channelIndices[
"R"] = channelIndices[
"Y"];
1455 channelIndices[
"G"] = channelIndices[
"Y"];
1456 channelIndices[
"B"] = channelIndices[
"Y"];
1457 }
else if (channelIndices[
"A"] >= 0) {
1458 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::RGBA16161616F : ImageFormat::RGBA32323232F;
1459 }
else if (channelIndices[
"B"] >= 0) {
1460 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1462 channelType = TINYEXR_PIXELTYPE_FLOAT;
1464 format = ImageFormat::RGB323232F;
1465 }
else if (channelIndices[
"G"] >= 0) {
1466 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::RG1616F : ImageFormat::RG3232F;
1467 }
else if (channelIndices[
"R"] >= 0) {
1468 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::R16F : ImageFormat::R32F;
1470 FreeEXRHeader(&header);
1475 for (
int i = 0; i < header.num_channels; i++) {
1476 if (header.pixel_types[i] != channelType && channelIndices.contains(header.channels[i].name)) {
1477 header.requested_pixel_types[i] = channelType;
1482 InitEXRImage(&image);
1483 if (LoadEXRImageFromMemory(&image, &header,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size(), &err) != TINYEXR_SUCCESS) {
1484 FreeEXRErrorMessage(err);
1485 FreeEXRHeader(&header);
1489 width = image.width;
1490 height = image.height;
1494 const auto populateBuffer = [
1502 r=channelIndices[
"R"],
1503 g=channelIndices[
"G"],
1504 b=channelIndices[
"B"],
1505 a=channelIndices[
"A"],
1509 const auto channelCount = hasRed + hasGreen + hasBlue + hasAlpha;
1510 std::span out{
reinterpret_cast<C*
>(combinedChannels.data()), combinedChannels.size() /
sizeof(C)};
1512 for (
int t = 0; t < image.num_tiles; t++) {
1513 auto** src =
reinterpret_cast<C**
>(image.tiles[t].images);
1514 for (
int j = 0; j < header.tile_size_y; j++) {
1515 for (
int i = 0; i < header.tile_size_x; i++) {
1516 const auto ii =
static_cast<uint64_t
>(image.tiles[t].offset_x) * header.tile_size_x + i;
1517 const auto jj =
static_cast<uint64_t
>(image.tiles[t].offset_y) * header.tile_size_y + j;
1518 const auto idx = ii + jj * image.width;
1520 if (ii >= image.width || jj >= image.height) {
1524 const auto srcIdx = j *
static_cast<uint64_t
>(header.tile_size_x) + i;
1525 if (r >= 0) out[idx * channelCount + 0] = src[r][srcIdx];
1526 else if (hasRed) out[idx * channelCount + 0] = 0.f;
1527 if (g >= 0) out[idx * channelCount + 1] = src[g][srcIdx];
1528 else if (hasGreen) out[idx * channelCount + 1] = 0.f;
1529 if (b >= 0) out[idx * channelCount + 2] = src[b][srcIdx];
1530 else if (hasBlue) out[idx * channelCount + 2] = 0.f;
1531 if (a >= 0) out[idx * channelCount + 3] = src[a][srcIdx];
1532 else if (hasAlpha) out[idx * channelCount + 3] = 1.f;
1537 auto** src =
reinterpret_cast<C**
>(image.images);
1538 for (uint64_t i = 0; i < width * height; i++) {
1539 if (r >= 0) out[i * channelCount + 0] = src[r][i];
1540 else if (hasRed) out[i * channelCount + 0] = 0.f;
1541 if (g >= 0) out[i * channelCount + 1] = src[g][i];
1542 else if (hasGreen) out[i * channelCount + 1] = 0.f;
1543 if (b >= 0) out[i * channelCount + 2] = src[b][i];
1544 else if (hasBlue) out[i * channelCount + 2] = 0.f;
1545 if (a >= 0) out[i * channelCount + 3] = src[a][i];
1546 else if (hasAlpha) out[i * channelCount + 3] = 1.f;
1550 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1551 populateBuffer.operator()<half>();
1553 populateBuffer.operator()<
float>();
1556 FreeEXRImage(&image);
1557 FreeEXRHeader(&header);
1558 return combinedChannels;
1563 if (stbi_is_hdr_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()))) {
1564 const ::stb_ptr<float> stbImage{
1565 stbi_loadf_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1572 case 1: format = ImageFormat::R32F;
break;
1573 case 2: format = ImageFormat::RG3232F;
break;
1574 case 3: format = ImageFormat::RGB323232F;
break;
1575 case 4: format = ImageFormat::RGBA32323232F;
break;
1581#ifdef VTFPP_SUPPORT_WEBP
1583 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) {
1584 width = features.width;
1585 height = features.height;
1589 std::vector<std::byte> out;
1590 if (features.has_alpha) {
1591 format = ImageFormat::RGBA8888;
1593 if (!WebPDecodeRGBAInto(
reinterpret_cast<const uint8_t*
>(fileData.data()), fileData.size(),
reinterpret_cast<uint8_t*
>(out.data()), out.size(), width * (
ImageFormatDetails::bpp(format) / 8))) {
1597 format = ImageFormat::RGB888;
1599 if (!WebPDecodeRGBInto(
reinterpret_cast<const uint8_t*
>(fileData.data()), fileData.size(),
reinterpret_cast<uint8_t*
>(out.data()), out.size(), width * (
ImageFormatDetails::bpp(format) / 8))) {
1608 if (fileData.size() > 3 &&
static_cast<char>(fileData[0]) ==
'G' &&
static_cast<char>(fileData[1]) ==
'I' &&
static_cast<char>(fileData[2]) ==
'F') {
1609 const ::stb_ptr<stbi_uc> stbImage{
1610 stbi_load_gif_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()),
nullptr, &width, &height, &frameCount, &channels, 0),
1613 if (!stbImage || !frameCount) {
1617 case 1: format = ImageFormat::I8;
break;
1618 case 2: format = ImageFormat::UV88;
break;
1619 case 3: format = ImageFormat::RGB888;
break;
1620 case 4: format = ImageFormat::RGBA8888;
break;
1623 return {
reinterpret_cast<std::byte*
>(stbImage.get()),
reinterpret_cast<std::byte*
>(stbImage.get() + (
ImageFormatDetails::getDataLength(format, width, height) * frameCount))};
1629 stbi__start_mem(&s,
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()));
1630 if (stbi__png_test(&s)) {
1632 const auto apngDecoder = [&format, &width, &height, &frameCount]<
typename P>(
const auto& stbImage, std::size_t dirOffset) -> std::vector<std::byte> {
1633 auto* dir =
reinterpret_cast<stbi__apng_directory*
>(stbImage.get() + dirOffset);
1634 if (dir->type != STBI__STRUCTURE_TYPE_APNG_DIRECTORY) {
1639 frameCount =
static_cast<int>(dir->num_frames);
1641 static constexpr auto calcPixelOffset = [](uint32_t offsetX, uint32_t offsetY, uint32_t width) {
1642 return ((offsetY * width) + offsetX) *
sizeof(P);
1646 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) {
1647 for (uint32_t y = 0; y < srcHeight; y++) {
1649#ifdef SOURCEPP_BUILD_WITH_TBB
1650 std::execution::unseq,
1652 src.data() + calcPixelOffset( 0, y, srcWidth),
1653 src.data() + calcPixelOffset( srcWidth, y, srcWidth),
1654 dst.data() + calcPixelOffset(srcOffsetX, srcOffsetY + y, dstWidth));
1659 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) {
1660 for (uint32_t y = subOffsetY; y < subOffsetY + subHeight; y++) {
1662#ifdef SOURCEPP_BUILD_WITH_TBB
1663 std::execution::unseq,
1665 src.data() + calcPixelOffset(subOffsetX, y, imgWidth),
1666 src.data() + calcPixelOffset(subOffsetX + subWidth, y, imgWidth),
1667 dst.data() + calcPixelOffset(subOffsetX, y, imgWidth));
1671 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) {
1672 for (uint32_t y = 0; y < clrHeight; y++) {
1674#ifdef SOURCEPP_BUILD_WITH_TBB
1675 std::execution::unseq,
1677 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth),
1678 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth) + (clrWidth *
sizeof(P)),
1679 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth),
1680 [](std::byte) {
return std::byte{0}; });
1684 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) {
1685 for (uint32_t y = 0; y < srcHeight; y++) {
1686 const auto* sp =
reinterpret_cast<const uint8_t*
>(src.data() + calcPixelOffset(0, y, srcWidth));
1687 auto* dp =
reinterpret_cast<uint8_t*
>(dst.data() + calcPixelOffset(srcOffsetX, srcOffsetY + y, dstWidth));
1688 for (uint32_t x = 0; x < srcWidth; x++, sp += 4, dp += 4) {
1691 }
else if ((sp[3] == 0xff) || (dp[3] == 0)) {
1692 std::copy(sp, sp +
sizeof(P), dp);
1694 int u = sp[3] * 0xff;
1695 int v = (0xff - sp[3]) * dp[3];
1697 dp[0] = (sp[0] * u + dp[0] * v) / al;
1698 dp[1] = (sp[1] * u + dp[1] * v) / al;
1699 dp[2] = (sp[2] * u + dp[2] * v) / al;
1707 const uint64_t fullFrameSize =
sizeof(P) * width * height;
1708 uint64_t currentFrameSize = 0;
1709 std::vector<std::byte> out(fullFrameSize * frameCount);
1710 uint64_t srcFrameOffset = 0;
1711 uint64_t dstFrameOffset = 0;
1712 for (uint32_t i = 0; i < dir->num_frames; i++) {
1713 const auto& frame = dir->frames[i];
1714 currentFrameSize =
sizeof(P) * frame.width * frame.height;
1717 if (frame.width == width && frame.height == height && frame.x_offset == 0 && frame.y_offset == 0 && frame.blend_op == STBI_APNG_blend_op_source) {
1718 std::memcpy(out.data() + dstFrameOffset, stbImage.get() + srcFrameOffset, fullFrameSize);
1721 if (frame.blend_op == STBI_APNG_blend_op_source || (i == 0 && frame.blend_op == STBI_APNG_blend_op_over)) {
1722 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);
1723 }
else if (frame.blend_op == STBI_APNG_blend_op_over) {
1724 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);
1730 dstFrameOffset += fullFrameSize;
1731 srcFrameOffset += currentFrameSize;
1734 if (i == dir->num_frames - 1) {
1739 copyImageData({out.data() + dstFrameOffset, out.data() + dstFrameOffset + fullFrameSize}, width, height, {out.data() + dstFrameOffset - fullFrameSize, out.data() + dstFrameOffset}, width, height, 0, 0);
1742 if (frame.dispose_op == STBI_APNG_dispose_op_background || (i == 0 && frame.dispose_op == STBI_APNG_dispose_op_previous)) {
1743 clearImageData({out.data() + dstFrameOffset, out.data() + dstFrameOffset + fullFrameSize}, width, height, frame.width, frame.height, frame.x_offset, frame.y_offset);
1744 }
else if (frame.dispose_op == STBI_APNG_dispose_op_previous) {
1745 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);
1746 }
else if (frame.dispose_op != STBI_APNG_dispose_op_none) {
1753 static const char *dispose_ops[] = {
1754 "STBI_APNG_dispose_op_none",
1755 "STBI_APNG_dispose_op_background",
1756 "STBI_APNG_dispose_op_previous",
1759 static const char *blend_ops[] = {
1760 "STBI_APNG_blend_op_source",
1761 "STBI_APNG_blend_op_over",
1764 fprintf(stderr,
"dir_offset : %zu\n", dirOffset);
1765 fprintf(stderr,
"dir.type : %.*s\n", 4, (
unsigned char *) &dir->type);
1766 fprintf(stderr,
"dir.num_frames : %u\n", dir->num_frames);
1767 fprintf(stderr,
"dir.default_image_is_first_frame : %s\n",
1768 dir->default_image_is_first_frame ?
"yes" :
"no");
1769 fprintf(stderr,
"dir.num_plays : %u\n", dir->num_plays);
1771 for (
int i = 0; i < dir->num_frames; ++i) {
1772 stbi__apng_frame_directory_entry *frame = &dir->frames[i];
1774 fprintf(stderr,
"frame : %u\n", i);
1775 fprintf(stderr,
" width : %u\n", frame->width);
1776 fprintf(stderr,
" height : %u\n", frame->height);
1777 fprintf(stderr,
" x_offset : %u\n", frame->x_offset);
1778 fprintf(stderr,
" y_offset : %u\n", frame->y_offset);
1779 fprintf(stderr,
" delay_num : %u\n", frame->delay_num);
1780 fprintf(stderr,
" delay_den : %u\n", frame->delay_den);
1781 fprintf(stderr,
" dispose_op : %s\n", dispose_ops[frame->dispose_op]);
1782 fprintf(stderr,
" blend_op : %s\n", blend_ops[frame->blend_op]);
1788 std::size_t dirOffset = 0;
1789 if (stbi__png_is16(&s)) {
1790 const ::stb_ptr<stbi_us> stbImage{
1791 stbi__apng_load_16bit(&s, &width, &height, &channels, STBI_rgb_alpha, &dirOffset),
1794 if (stbImage && dirOffset) {
1798 const ::stb_ptr<stbi_uc> stbImage{
1799 stbi__apng_load_8bit(&s, &width, &height, &channels, STBI_rgb_alpha, &dirOffset),
1802 if (stbImage && dirOffset) {
1809#ifdef VTFPP_SUPPORT_QOI
1812 qoi_desc descriptor;
1813 const ::stb_ptr<std::byte> qoiImage{
1814 static_cast<std::byte*
>(qoi_decode(fileData.data(),
static_cast<int>(fileData.size()), &descriptor, 0)),
1820 width =
static_cast<int>(descriptor.width);
1821 height =
static_cast<int>(descriptor.height);
1822 channels = descriptor.channels;
1824 case 3: format = ImageFormat::RGB888;
break;
1825 case 4: format = ImageFormat::RGBA8888;
break;
1833 if (stbi_is_16_bit_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()))) {
1834 const ::stb_ptr<stbi_us> stbImage{
1835 stbi_load_16_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1841 if (channels == 4) {
1842 format = ImageFormat::RGBA16161616;
1843 }
else if (channels >= 1 && channels < 4) {
1845 format = ImageFormat::RGBA16161616;
1850 std::span<uint16_t> inPixels{
reinterpret_cast<uint16_t*
>(stbImage.get()), outPixels.size()};
1852#ifdef SOURCEPP_BUILD_WITH_TBB
1853 std::execution::par_unseq,
1856 return {pixel, 0, 0, 0xffff};
1865 std::span<RG1616> inPixels{
reinterpret_cast<RG1616*
>(stbImage.get()), outPixels.size()};
1867#ifdef SOURCEPP_BUILD_WITH_TBB
1868 std::execution::par_unseq,
1871 return {pixel.r, pixel.g, 0, 0xffff};
1881 std::span<RGB161616> inPixels{
reinterpret_cast<RGB161616*
>(stbImage.get()), outPixels.size()};
1883#ifdef SOURCEPP_BUILD_WITH_TBB
1884 std::execution::par_unseq,
1887 return {pixel.r, pixel.g, pixel.b, 0xffff};
1901 const ::stb_ptr<stbi_uc> stbImage{
1902 stbi_load_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1909 case 1: format = ImageFormat::I8;
break;
1910 case 2: format = ImageFormat::UV88;
break;
1911 case 3: format = ImageFormat::RGB888;
break;
1912 case 4: format = ImageFormat::RGBA8888;
break;
1920 case ResizeMethod::NONE:
break;
1921 case ResizeMethod::POWER_OF_TWO_BIGGER:
return std::bit_ceil(n);
1922 case ResizeMethod::POWER_OF_TWO_SMALLER:
return std::bit_floor(n);
1934 if (imageData.empty() || format == ImageFormat::EMPTY) {
1938 STBIR_RESIZE resize;
1939 const auto setEdgeModesAndFiltersAndDoResize = [edge, filter, &resize] {
1940 stbir_set_edgemodes(&resize,
static_cast<stbir_edge
>(edge),
static_cast<stbir_edge
>(edge));
1942 case ResizeFilter::DEFAULT:
1943 case ResizeFilter::BOX:
1944 case ResizeFilter::BILINEAR:
1945 case ResizeFilter::CUBIC_BSPLINE:
1946 case ResizeFilter::CATMULL_ROM:
1947 case ResizeFilter::MITCHELL:
1948 case ResizeFilter::POINT_SAMPLE: {
1949 stbir_set_filters(&resize,
static_cast<stbir_filter
>(filter),
static_cast<stbir_filter
>(filter));
1952 case ResizeFilter::KAISER: {
1953 static constexpr auto KAISER_BETA = [](
float s) {
1962 static constexpr auto KAISER_FILTER = [](
float x,
float s,
void*) ->
float {
1963 if (x < -1.f || x > 1.f) {
1968 static constexpr auto KAISER_SUPPORT = [](
float s,
void*) ->
float {
1969 float baseSupport = KAISER_BETA(s) / 2.f;
1971 return std::max(2.f, baseSupport - 0.5f);
1973 return std::max(3.f, baseSupport);
1975 stbir_set_filter_callbacks(&resize, KAISER_FILTER, KAISER_SUPPORT, KAISER_FILTER, KAISER_SUPPORT);
1978 case ResizeFilter::NICE: {
1979 static constexpr auto SINC = [](
float x) ->
float {
1980 if (x == 0.f)
return 1.f;
1984 static constexpr auto NICE_FILTER = [](
float x, float,
void*) ->
float {
1985 if (x >= 3.f || x <= -3.f)
return 0.f;
1986 return SINC(x) * SINC(x / 3.f);
1990 static constexpr auto NICE_SUPPORT = [](
float invScale,
void*) ->
float {
1991 return invScale * 3.f;
1993 stbir_set_filter_callbacks(&resize, NICE_FILTER, NICE_SUPPORT, NICE_FILTER, NICE_SUPPORT);
1997 stbir_resize_extended(&resize);
2000 const auto pixelLayout = ::imageFormatToSTBIRPixelLayout(format);
2001 if (pixelLayout == -1) {
2005 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)));
2006 setEdgeModesAndFiltersAndDoResize();
2010 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)));
2011 setEdgeModesAndFiltersAndDoResize();
2015std::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) {
2016 if (imageData.empty() || format == ImageFormat::EMPTY) {
2021 return resizeImageData(imageData, format, width, widthOut, height, heightOut, srgb, filter, edge);
2025std::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) {
2026 if (imageData.empty() || format == ImageFormat::EMPTY || xOffset + newWidth >= width || yOffset + newHeight >= height) {
2032 return convertImageDataToFormat(
cropImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, newWidth, xOffset, height, newHeight, yOffset), container, format, newWidth, newHeight);
2036 std::vector<std::byte> out(pixelSize * newWidth * newHeight);
2037 for (uint16_t y = yOffset; y < yOffset + newHeight; y++) {
2038 std::memcpy(out.data() + (((y - yOffset) * newWidth) * pixelSize), imageData.data() + (((y * width) + xOffset) * pixelSize), newWidth * pixelSize);
2045 if (imageData.empty() || format == ImageFormat::EMPTY) {
2050 return {imageData.begin(), imageData.end()};
2055 return convertImageDataToFormat(
gammaCorrectImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, height, gamma), container, format, width, height);
2058 static constexpr auto calculateGammaLUT = [](
float gamma_, uint8_t channelSize) -> std::array<uint8_t, 256> {
2059 const auto maxSize =
static_cast<float>((1 << channelSize) - 1);
2060 std::array<uint8_t, 256> gammaLUT{};
2061 for (
int i = 0; i < gammaLUT.size(); i++) {
2062 gammaLUT[i] =
static_cast<uint8_t
>(std::clamp(std::pow((
static_cast<float>(i) + 0.5f) / maxSize, gamma_) * maxSize - 0.5f, 0.f, maxSize));
2067 #define VTFPP_CREATE_GAMMA_LUTS(InputType) \
2068 std::unordered_map<uint8_t, std::array<uint8_t, 256>> gammaLUTs; \
2069 if constexpr (ImageFormatDetails::red(ImageFormat::InputType) > 0) { \
2070 if (!gammaLUTs.contains(ImageFormatDetails::red(ImageFormat::InputType))) { \
2071 gammaLUTs[ImageFormatDetails::red(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::red(ImageFormat::InputType)); \
2074 if constexpr (ImageFormatDetails::green(ImageFormat::InputType) > 0) { \
2075 if (!gammaLUTs.contains(ImageFormatDetails::green(ImageFormat::InputType))) { \
2076 gammaLUTs[ImageFormatDetails::green(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::green(ImageFormat::InputType)); \
2079 if constexpr (ImageFormatDetails::blue(ImageFormat::InputType) > 0) { \
2080 if (!gammaLUTs.contains(ImageFormatDetails::blue(ImageFormat::InputType))) { \
2081 gammaLUTs[ImageFormatDetails::blue(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::blue(ImageFormat::InputType)); \
2085 #define VTFPP_APPLY_GAMMA_RED(value) \
2086 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::red(PIXEL_TYPE::FORMAT))[value])
2088 #define VTFPP_APPLY_GAMMA_GREEN(value) \
2089 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::green(PIXEL_TYPE::FORMAT))[value])
2091 #define VTFPP_APPLY_GAMMA_BLUE(value) \
2092 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::blue(PIXEL_TYPE::FORMAT))[value])
2094 std::vector<std::byte> out(imageData.size());
2096#ifdef SOURCEPP_BUILD_WITH_TBB
2097 #define VTFPP_GAMMA_CORRECT(InputType, ...) \
2098 std::span imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
2099 std::span outSpan{reinterpret_cast<ImagePixel::InputType*>(out.data()), out.size() / sizeof(ImagePixel::InputType)}; \
2100 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), outSpan.begin(), [gammaLUTs](ImagePixel::InputType pixel) -> ImagePixel::InputType { \
2101 using PIXEL_TYPE = ImagePixel::InputType; \
2102 return __VA_ARGS__; \
2105 #define VTFPP_GAMMA_CORRECT(InputType, ...) \
2106 std::span imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
2107 std::span outSpan{reinterpret_cast<ImagePixel::InputType*>(out.data()), out.size() / sizeof(ImagePixel::InputType)}; \
2108 std::transform(imageDataSpan.begin(), imageDataSpan.end(), outSpan.begin(), [gammaLUTs](ImagePixel::InputType pixel) -> ImagePixel::InputType { \
2109 using PIXEL_TYPE = ImagePixel::InputType; \
2110 return __VA_ARGS__; \
2113 #define VTFPP_CASE_GAMMA_CORRECT_AND_BREAK(InputType, ...) \
2114 case InputType: { VTFPP_CREATE_GAMMA_LUTS(InputType) VTFPP_GAMMA_CORRECT(InputType, __VA_ARGS__); } break
2121 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)});
2123 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)});
2139 #undef VTFPP_CASE_GAMMA_CORRECT_AND_BREAK
2140 #undef VTFPP_GAMMA_CORRECT
2141 #undef VTFPP_APPLY_GAMMA_BLUE
2142 #undef VTFPP_APPLY_GAMMA_GREEN
2143 #undef VTFPP_APPLY_GAMMA_RED
2144 #undef VTFPP_CREATE_GAMMA_LUTS
2157 return convertImageDataToFormat(
invertGreenChannelForImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, height), container, format, width, height);
2160 #define VTFPP_INVERT_GREEN(PixelType, ChannelName, ...) \
2161 static constexpr auto channelSize = ImageFormatDetails::green(ImagePixel::PixelType::FORMAT); \
2162 std::span imageDataSpan{reinterpret_cast<const ImagePixel::PixelType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::PixelType)}; \
2163 std::span outSpan{reinterpret_cast<ImagePixel::PixelType*>(out.data()), out.size() / sizeof(ImagePixel::PixelType)}; \
2164 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), outSpan.begin(), [](ImagePixel::PixelType pixel) -> ImagePixel::PixelType { \
2165 if constexpr (std::same_as<decltype(pixel.ChannelName), float> || std::same_as<decltype(pixel.ChannelName), half>) { \
2166 pixel.ChannelName = static_cast<decltype(pixel.ChannelName)>(static_cast<float>(static_cast<uint64_t>(1) << channelSize) - 1.f - static_cast<float>(pixel.ChannelName)); \
2168 if constexpr (channelSize >= sizeof(uint32_t) * 8) { \
2169 pixel.ChannelName = static_cast<decltype(pixel.ChannelName)>((static_cast<uint64_t>(1) << channelSize) - 1 - static_cast<uint32_t>(pixel.ChannelName)); \
2171 pixel.ChannelName = static_cast<decltype(pixel.ChannelName)>(static_cast<uint32_t>(1 << channelSize) - 1 - static_cast<uint32_t>(pixel.ChannelName)); \
2176#ifdef SOURCEPP_BUILD_WITH_TBB
2177 #define VTFPP_INVERT_GREEN_CASE(PixelType) \
2178 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, g, std::execution::par_unseq); break; }
2179 #define VTFPP_INVERT_GREEN_CASE_CA_OVERRIDE(PixelType, ChannelName) \
2180 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, ChannelName, std::execution::par_unseq); break; }
2182 #define VTFPP_INVERT_GREEN_CASE(PixelType) \
2183 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, g); } break
2184 #define VTFPP_INVERT_GREEN_CASE_CA_OVERRIDE(PixelType, ChannelName) \
2185 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, ChannelName); } break
2188 std::vector<std::byte> out(imageData.size());
2230 #undef VTFPP_INVERT_GREEN_CASE_CA_OVERRIDE
2231 #undef VTFPP_INVERT_GREEN_CASE
2232 #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