11#include <unordered_map>
13#ifdef SOURCEPP_BUILD_WITH_TBB
17#ifdef SOURCEPP_BUILD_WITH_THREADS
21#include <Compressonator.h>
23#define QOI_IMPLEMENTATION
30#define STB_IMAGE_IMPLEMENTATION
31#define STB_IMAGE_STATIC
32#define STBI_NO_FAILURE_STRINGS
36#define STB_IMAGE_RESIZE_IMPLEMENTATION
37#define STB_IMAGE_RESIZE_STATIC
38#include <stb_image_resize2.h>
40#define STB_IMAGE_WRITE_IMPLEMENTATION
41#define STB_IMAGE_WRITE_STATIC
42#define STBI_WRITE_NO_STDIO
43#include <stb_image_write.h>
45#define TINYEXR_IMPLEMENTATION 1
46#ifdef SOURCEPP_BUILD_WITH_THREADS
47#define TINYEXR_USE_THREAD 1
49#define TINYEXR_USE_THREAD 0
53#include <webp/decode.h>
54#include <webp/encode.h>
61[[nodiscard]]
constexpr CMP_FORMAT imageFormatToCompressonatorFormat(
ImageFormat format) {
66 return CMP_FORMAT_RGBA_8888;
68 return CMP_FORMAT_ABGR_8888;
70 return CMP_FORMAT_RGB_888;
72 return CMP_FORMAT_BGR_888;
75 return CMP_FORMAT_R_8;
77 return CMP_FORMAT_ARGB_8888;
79 return CMP_FORMAT_BGRA_8888;
82 return CMP_FORMAT_DXT1;
84 return CMP_FORMAT_DXT3;
86 return CMP_FORMAT_DXT5;
88 return CMP_FORMAT_RG_8;
90 return CMP_FORMAT_R_16F;
92 return CMP_FORMAT_RG_16F;
94 return CMP_FORMAT_RGBA_16F;
96 return CMP_FORMAT_RGBA_16;
98 return CMP_FORMAT_R_32F;
100 return CMP_FORMAT_RG_32F;
102 return CMP_FORMAT_RGB_32F;
104 return CMP_FORMAT_RGBA_32F;
106 return CMP_FORMAT_ATI2N;
108 return CMP_FORMAT_ATI1N;
110 return CMP_FORMAT_RGBA_1010102;
112 return CMP_FORMAT_R_8;
114 return CMP_FORMAT_BC7;
116 return CMP_FORMAT_BC6H_SF;
143 return CMP_FORMAT_Unknown;
145 return CMP_FORMAT_Unknown;
148[[nodiscard]]
constexpr int imageFormatToSTBIRPixelLayout(
ImageFormat format) {
169 return STBIR_1CHANNEL;
177 return STBIR_2CHANNEL;
220[[nodiscard]]
constexpr int imageFormatToSTBIRDataType(
ImageFormat format,
bool srgb =
false) {
241 return srgb ? STBIR_TYPE_UINT8_SRGB : STBIR_TYPE_UINT8;
245 return STBIR_TYPE_HALF_FLOAT;
247 return STBIR_TYPE_UINT16;
252 return STBIR_TYPE_FLOAT;
287 if (imageData.empty()) {
291 CMP_Texture srcTexture{};
292 srcTexture.dwSize =
sizeof(srcTexture);
293 srcTexture.dwWidth = width;
294 srcTexture.dwHeight = height;
296 srcTexture.format = ::imageFormatToCompressonatorFormat(oldFormat);
297 srcTexture.dwDataSize = imageData.size();
299 srcTexture.pData =
const_cast<CMP_BYTE*
>(
reinterpret_cast<const CMP_BYTE*
>(imageData.data()));
301 CMP_Texture destTexture{};
302 destTexture.dwSize =
sizeof(destTexture);
303 destTexture.dwWidth = width;
304 destTexture.dwHeight = height;
306 destTexture.format = ::imageFormatToCompressonatorFormat(newFormat);
307 destTexture.dwDataSize = CMP_CalculateBufferSize(&destTexture);
309 std::vector<std::byte> destData;
310 destData.resize(destTexture.dwDataSize);
311 destTexture.pData =
reinterpret_cast<CMP_BYTE*
>(destData.data());
313 CMP_CompressOptions options{};
314 options.dwSize =
sizeof(options);
315 options.fquality = std::clamp(quality, 0.f, 1.f);
316 options.bDXT1UseAlpha = oldFormat == ImageFormat::DXT1_ONE_BIT_ALPHA || newFormat == ImageFormat::DXT1_ONE_BIT_ALPHA;
317 if (options.bDXT1UseAlpha) {
318 options.nAlphaThreshold = 128;
321 if (CMP_ConvertTexture(&srcTexture, &destTexture, &options,
nullptr) != CMP_OK) {
327[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA8888(std::span<const std::byte> imageData,
ImageFormat format) {
328 using namespace ImageConversion;
330 if (imageData.empty()) {
334 if (format == ImageFormat::RGBA8888 || format == ImageFormat::UVWQ8888) {
335 return {imageData.begin(), imageData.end()};
338 std::vector<std::byte> newData;
342 #define VTFPP_REMAP_TO_8(value, shift) math::remap<uint8_t>((value), (1 << (shift)) - 1, (1 << 8) - 1)
344 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
345 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
346 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA8888 { \
347 return {(r), (g), (b), (a)}; \
349#ifdef SOURCEPP_BUILD_WITH_TBB
350 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
352 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
354 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
355 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
394 #undef VTFPP_CASE_CONVERT_AND_BREAK
396 #undef VTFPP_CONVERT_DETAIL
397 #undef VTFPP_REMAP_TO_8
402[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888(std::span<const std::byte> imageData,
ImageFormat format) {
403 using namespace ImageConversion;
405 if (imageData.empty()) {
409 if (format == ImageFormat::RGBA8888) {
410 return {imageData.begin(), imageData.end()};
414 std::vector<std::byte> newData;
417 #define VTFPP_REMAP_FROM_8(value, shift) math::remap<uint8_t>((value), (1 << 8) - 1, (1 << (shift)) - 1)
419#ifdef SOURCEPP_BUILD_WITH_TBB
420 #define VTFPP_CONVERT(InputType, ...) \
421 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
422 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA8888 pixel) -> ImagePixel::InputType { \
423 return __VA_ARGS__; \
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(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA8888 pixel) -> ImagePixel::InputType { \
429 return __VA_ARGS__; \
432 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
433 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
472 #undef VTFPP_CASE_CONVERT_AND_BREAK
474 #undef VTFPP_REMAP_FROM_8
479[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA16161616(std::span<const std::byte> imageData,
ImageFormat format) {
480 using namespace ImageConversion;
482 if (imageData.empty()) {
486 if (format == ImageFormat::RGBA16161616) {
487 return {imageData.begin(), imageData.end()};
490 std::vector<std::byte> newData;
494 #define VTFPP_REMAP_TO_16(value, shift) math::remap<uint16_t>((value), (1 << (shift)) - 1, (1 << 16) - 1)
496 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
497 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
498 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA16161616 { \
499 return { static_cast<uint16_t>(r), static_cast<uint16_t>(g), static_cast<uint16_t>(b), static_cast<uint16_t>(a) }; \
501#ifdef SOURCEPP_BUILD_WITH_TBB
502 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
504 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
506 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
507 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
509 #define VTFPP_CONVERT_REMAP(InputType, r, g, b, a) \
511 if constexpr (ImageFormatDetails::alpha(ImageFormat::InputType) > 1) { \
512 VTFPP_CONVERT(InputType, \
513 VTFPP_REMAP_TO_16((r), ImageFormatDetails::red(ImageFormat::InputType)), \
514 VTFPP_REMAP_TO_16((g), ImageFormatDetails::green(ImageFormat::InputType)), \
515 VTFPP_REMAP_TO_16((b), ImageFormatDetails::blue(ImageFormat::InputType)), \
516 VTFPP_REMAP_TO_16((a), ImageFormatDetails::alpha(ImageFormat::InputType))); \
517 } else 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)), \
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)), \
531 #define VTFPP_CASE_CONVERT_REMAP_AND_BREAK(InputType, r, g, b, a) \
532 case InputType: { VTFPP_CONVERT_REMAP(InputType, r, g, b, a); } \
543 #undef VTFPP_CASE_CONVERT_REMAP_AND_BREAK
544 #undef VTFPP_CONVERT_REMAP
545 #undef VTFPP_CASE_CONVERT_AND_BREAK
547 #undef VTFPP_CONVERT_DETAIL
548 #undef VTFPP_REMAP_TO_16
553[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616(std::span<const std::byte> imageData,
ImageFormat format) {
554 using namespace ImageConversion;
556 if (imageData.empty()) {
560 if (format == ImageFormat::RGBA16161616) {
561 return {imageData.begin(), imageData.end()};
565 std::vector<std::byte> newData;
568 #define VTFPP_REMAP_FROM_16(value, shift) static_cast<uint8_t>(math::remap<uint16_t>((value), (1 << 16) - 1, (1 << (shift)) - 1))
570#ifdef SOURCEPP_BUILD_WITH_TBB
571 #define VTFPP_CONVERT(InputType, ...) \
572 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
573 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA16161616 pixel) -> ImagePixel::InputType { \
574 return __VA_ARGS__; \
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(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA16161616 pixel) -> ImagePixel::InputType { \
580 return __VA_ARGS__; \
583 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
584 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
594 #undef VTFPP_CASE_CONVERT_AND_BREAK
596 #undef VTFPP_REMAP_FROM_16
601[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA32323232F(std::span<const std::byte> imageData,
ImageFormat format) {
602 if (imageData.empty()) {
606 if (format == ImageFormat::RGBA32323232F) {
607 return {imageData.begin(), imageData.end()};
610 std::vector<std::byte> newData;
614 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
615 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
616 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA32323232F { return {(r), (g), (b), (a)}; })
617#ifdef SOURCEPP_BUILD_WITH_TBB
618 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
620 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
622 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
623 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
636 #undef VTFPP_CASE_CONVERT_AND_BREAK
638 #undef VTFPP_CONVERT_DETAIL
643[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232F(std::span<const std::byte> imageData,
ImageFormat format) {
644 using namespace ImageConversion;
646 if (imageData.empty()) {
650 if (format == ImageFormat::RGBA32323232F) {
651 return {imageData.begin(), imageData.end()};
655 std::vector<std::byte> newData;
658#ifdef SOURCEPP_BUILD_WITH_TBB
659 #define VTFPP_CONVERT(InputType, ...) \
660 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
661 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA32323232F pixel) -> ImagePixel::InputType { \
662 return __VA_ARGS__; \
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(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA32323232F pixel) -> ImagePixel::InputType { \
668 return __VA_ARGS__; \
671 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
672 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
685 #undef VTFPP_CASE_CONVERT_AND_BREAK
691[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888ToRGBA32323232F(std::span<const std::byte> imageData) {
692 if (imageData.empty()) {
696 std::vector<std::byte> newData;
702#ifdef SOURCEPP_BUILD_WITH_TBB
703 std::execution::par_unseq,
707 static_cast<float>(pixel.r) / static_cast<float>((1 << 8) - 1),
708 static_cast<float>(pixel.g) / static_cast<float>((1 << 8) - 1),
709 static_cast<float>(pixel.b) / static_cast<float>((1 << 8) - 1),
710 static_cast<float>(pixel.a) / static_cast<float>((1 << 8) - 1),
717[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232FToRGBA8888(std::span<const std::byte> imageData) {
718 if (imageData.empty()) {
722 std::vector<std::byte> newData;
728#ifdef SOURCEPP_BUILD_WITH_TBB
729 std::execution::par_unseq,
733 static_cast<uint8_t>(std::clamp(pixel.r, 0.f, 1.f) * ((1 << 8) - 1)),
734 static_cast<uint8_t>(std::clamp(pixel.g, 0.f, 1.f) * ((1 << 8) - 1)),
735 static_cast<uint8_t>(std::clamp(pixel.b, 0.f, 1.f) * ((1 << 8) - 1)),
736 static_cast<uint8_t>(std::clamp(pixel.a, 0.f, 1.f) * ((1 << 8) - 1)),
743[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888ToRGBA16161616(std::span<const std::byte> imageData) {
744 if (imageData.empty()) {
748 std::vector<std::byte> newData;
754#ifdef SOURCEPP_BUILD_WITH_TBB
755 std::execution::par_unseq,
759 math::remap<uint16_t>(pixel.r, (1 << 8) - 1, (1 << 16) - 1),
760 math::remap<uint16_t>(pixel.g, (1 << 8) - 1, (1 << 16) - 1),
761 math::remap<uint16_t>(pixel.b, (1 << 8) - 1, (1 << 16) - 1),
762 math::remap<uint16_t>(pixel.a, (1 << 8) - 1, (1 << 16) - 1),
769[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616ToRGBA8888(std::span<const std::byte> imageData) {
770 if (imageData.empty()) {
774 std::vector<std::byte> newData;
780#ifdef SOURCEPP_BUILD_WITH_TBB
781 std::execution::par_unseq,
785 static_cast<uint8_t>(math::remap<uint16_t>(pixel.r, (1 << 16) - 1, (1 << 8) - 1)),
786 static_cast<uint8_t>(math::remap<uint16_t>(pixel.g, (1 << 16) - 1, (1 << 8) - 1)),
787 static_cast<uint8_t>(math::remap<uint16_t>(pixel.b, (1 << 16) - 1, (1 << 8) - 1)),
788 static_cast<uint8_t>(math::remap<uint16_t>(pixel.a, (1 << 16) - 1, (1 << 8) - 1)),
795[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232FToRGBA16161616(std::span<const std::byte> imageData) {
796 if (imageData.empty()) {
800 std::vector<std::byte> newData;
806#ifdef SOURCEPP_BUILD_WITH_TBB
807 std::execution::par_unseq,
811 static_cast<uint16_t>(std::clamp(pixel.r, 0.f, 1.f) * ((1 << 16) - 1)),
812 static_cast<uint16_t>(std::clamp(pixel.g, 0.f, 1.f) * ((1 << 16) - 1)),
813 static_cast<uint16_t>(std::clamp(pixel.b, 0.f, 1.f) * ((1 << 16) - 1)),
814 static_cast<uint16_t>(std::clamp(pixel.a, 0.f, 1.f) * ((1 << 16) - 1)),
821[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616ToRGBA32323232F(std::span<const std::byte> imageData) {
822 if (imageData.empty()) {
826 std::vector<std::byte> newData;
832#ifdef SOURCEPP_BUILD_WITH_TBB
833 std::execution::par_unseq,
837 static_cast<float>(pixel.r) / static_cast<float>((1 << 16) - 1),
838 static_cast<float>(pixel.g) / static_cast<float>((1 << 16) - 1),
839 static_cast<float>(pixel.b) / static_cast<float>((1 << 16) - 1),
840 static_cast<float>(pixel.a) / static_cast<float>((1 << 16) - 1),
850 if (imageData.empty() || oldFormat == ImageFormat::EMPTY) {
854 if (oldFormat == newFormat) {
855 return {imageData.begin(), imageData.end()};
858 std::vector<std::byte> newData;
862 newData = ::convertImageDataUsingCompressonator(imageData, oldFormat, intermediaryOldFormat, width, height, quality);
864 switch (intermediaryOldFormat) {
865 case ImageFormat::RGBA8888: newData = ::convertImageDataToRGBA8888(imageData, oldFormat);
break;
866 case ImageFormat::RGBA16161616: newData = ::convertImageDataToRGBA16161616(imageData, oldFormat);
break;
867 case ImageFormat::RGBA32323232F: newData = ::convertImageDataToRGBA32323232F(imageData, oldFormat);
break;
872 if (intermediaryOldFormat == newFormat) {
877 if (intermediaryOldFormat != intermediaryNewFormat) {
878 if (intermediaryOldFormat == ImageFormat::RGBA8888) {
879 if (intermediaryNewFormat == ImageFormat::RGBA16161616) {
880 newData = ::convertImageDataFromRGBA8888ToRGBA16161616(newData);
881 }
else if (intermediaryNewFormat == ImageFormat::RGBA32323232F) {
882 newData = ::convertImageDataFromRGBA8888ToRGBA32323232F(newData);
886 }
else if (intermediaryOldFormat == ImageFormat::RGBA16161616) {
887 if (intermediaryNewFormat == ImageFormat::RGBA8888) {
888 newData = ::convertImageDataFromRGBA16161616ToRGBA8888(newData);
889 }
else if (intermediaryNewFormat == ImageFormat::RGBA32323232F) {
890 newData = ::convertImageDataFromRGBA16161616ToRGBA32323232F(newData);
894 }
else if (intermediaryOldFormat == ImageFormat::RGBA32323232F) {
895 if (intermediaryNewFormat == ImageFormat::RGBA8888) {
896 newData = ::convertImageDataFromRGBA32323232FToRGBA8888(newData);
897 }
else if (intermediaryNewFormat == ImageFormat::RGBA16161616) {
898 newData = ::convertImageDataFromRGBA32323232FToRGBA16161616(newData);
907 if (intermediaryNewFormat == newFormat) {
912 newData = ::convertImageDataUsingCompressonator(newData, intermediaryNewFormat, newFormat, width, height, quality);
914 switch (intermediaryNewFormat) {
915 case ImageFormat::RGBA8888: newData = ::convertImageDataFromRGBA8888(newData, newFormat);
break;
916 case ImageFormat::RGBA16161616: newData = ::convertImageDataFromRGBA16161616(newData, newFormat);
break;
917 case ImageFormat::RGBA32323232F: newData = ::convertImageDataFromRGBA32323232F(newData, newFormat);
break;
926 if (imageData.empty() || oldFormat == ImageFormat::EMPTY) {
930 if (oldFormat == newFormat) {
931 return {imageData.begin(), imageData.end()};
935 for(
int mip = mipCount - 1; mip >= 0; mip--) {
936 for (
int frame = 0; frame < frameCount; frame++) {
937 for (
int face = 0; face < faceCount; face++) {
938 for (
int slice = 0; slice < sliceCount; slice++) {
939 if (uint32_t oldOffset, oldLength;
ImageFormatDetails::getDataPosition(oldOffset, oldLength, oldFormat, mip, mipCount, frame, frameCount, face, faceCount, width, height, slice, sliceCount)) {
941 if (uint32_t newOffset, newLength;
ImageFormatDetails::getDataPosition(newOffset, newLength, newFormat, mip, mipCount, frame, frameCount, face, faceCount, width, height, slice, sliceCount) && newLength == convertedImageData.size()) {
942 std::memcpy(out.data() + newOffset, convertedImageData.data(), newLength);
953 if (imageData.empty() || format == ImageFormat::EMPTY) {
961 std::span<const float> imageDataRGBA32323232F{
reinterpret_cast<const float*
>(imageData.data()),
reinterpret_cast<const float*
>(imageData.data() + imageData.size())};
963 std::vector<std::byte> possiblyConvertedDataOrEmptyDontUseMeDirectly;
964 if (format != ImageFormat::RGBA32323232F) {
965 possiblyConvertedDataOrEmptyDontUseMeDirectly =
convertImageDataToFormat(imageData, format, ImageFormat::RGBA32323232F, width, height);
966 imageDataRGBA32323232F = {
reinterpret_cast<const float*
>(possiblyConvertedDataOrEmptyDontUseMeDirectly.data()),
reinterpret_cast<const float*
>(possiblyConvertedDataOrEmptyDontUseMeDirectly.data() + possiblyConvertedDataOrEmptyDontUseMeDirectly.size())};
971 static constexpr std::array<std::array<math::Vec3f, 3>, 6> startRightUp = {{
972 {{{ 1.0f, -1.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}},
973 {{{ 1.0f, 1.0f, 1.0f}, { 0.0f,-1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}},
974 {{{ 1.0f, 1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, { 0.0f,-1.0f, 0.0f}}},
975 {{{-1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}}},
976 {{{ 1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}}},
977 {{{ 1.0f, 1.0f, -1.0f}, { 0.0f, 0.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}},
980 std::array<std::vector<std::byte>, 6> faceData;
982#ifdef SOURCEPP_BUILD_WITH_THREADS
983 const auto faceExtraction = [&](
int i) {
985 for (
int i = 0; i < faceData.size(); i++) {
987 const auto start = startRightUp[i][0];
988 const auto right = startRightUp[i][1];
989 const auto up = startRightUp[i][2];
992 std::span<float> face{
reinterpret_cast<float*
>(faceData[i].data()),
reinterpret_cast<float*
>(faceData[i].data() + faceData[i].size())};
994 for (
int row = 0; row < resolution; row++) {
995 for (
int col = 0; col < resolution; col++) {
996 math::Vec3f pixelDirection3d{
997 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],
998 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],
999 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],
1001 float azimuth = std::atan2(pixelDirection3d[0], -pixelDirection3d[2]) +
math::pi_f32;
1002 float elevation = std::atan(pixelDirection3d[1] / std::sqrt(pixelDirection3d[0] * pixelDirection3d[0] + pixelDirection3d[2] * pixelDirection3d[2])) +
math::pi_f32 / 2.f;
1003 float colHdri = (azimuth /
math::pi_f32 / 2.f) *
static_cast<float>(width);
1004 float rowHdri = (elevation /
math::pi_f32) *
static_cast<float>(height);
1006 int colNearest = std::clamp(
static_cast<int>(colHdri), 0, width - 1);
1007 int rowNearest = std::clamp(
static_cast<int>(rowHdri), 0, height - 1);
1008 face[col * 4 + resolution * row * 4 + 0] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 0];
1009 face[col * 4 + resolution * row * 4 + 1] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 1];
1010 face[col * 4 + resolution * row * 4 + 2] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 2];
1011 face[col * 4 + resolution * row * 4 + 3] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 3];
1013 float intCol, intRow;
1015 float factorCol = std::modf(colHdri - 0.5f, &intCol);
1016 float factorRow = std::modf(rowHdri - 0.5f, &intRow);
1017 int low_idx_row =
static_cast<int>(intRow);
1018 int low_idx_column =
static_cast<int>(intCol);
1019 int high_idx_column;
1020 if (factorCol < 0.f) {
1023 high_idx_column = width - 1;
1024 }
else if (low_idx_column == width - 1) {
1026 high_idx_column = 0;
1028 high_idx_column = low_idx_column + 1;
1031 if (factorRow < 0.f || low_idx_row == height - 1) {
1032 high_idx_row = low_idx_row;
1035 high_idx_row = low_idx_row + 1;
1037 factorCol = std::abs(factorCol);
1038 factorRow = std::abs(factorRow);
1039 float f1 = (1 - factorRow) * (1 - factorCol);
1040 float f2 = factorRow * (1 - factorCol);
1041 float f3 = (1 - factorRow) * factorCol;
1042 float f4 = factorRow * factorCol;
1043 for (
int j = 0; j < 4; j++) {
1044 face[col * 4 + resolution * row * 4 + j] =
1045 imageDataRGBA32323232F[low_idx_column * 4 + width * low_idx_row * 4 + j] * f1 +
1046 imageDataRGBA32323232F[low_idx_column * 4 + width * high_idx_row * 4 + j] * f2 +
1047 imageDataRGBA32323232F[high_idx_column * 4 + width * low_idx_row * 4 + j] * f3 +
1048 imageDataRGBA32323232F[high_idx_column * 4 + width * high_idx_row * 4 + j] * f4;
1053 if (format != ImageFormat::RGBA32323232F) {
1057#ifdef SOURCEPP_BUILD_WITH_THREADS
1059 std::array<std::future<void>, 6> faceFutures{
1060 std::async(std::launch::async, faceExtraction, 0),
1061 std::async(std::launch::async, faceExtraction, 1),
1062 std::async(std::launch::async, faceExtraction, 2),
1063 std::async(std::launch::async, faceExtraction, 3),
1064 std::async(std::launch::async, faceExtraction, 4),
1065 std::async(std::launch::async, faceExtraction, 5),
1067 for (
auto& future : faceFutures) {
1081 if (imageData.empty() || format == ImageFormat::EMPTY) {
1084 std::vector<std::byte> out;
1085 auto stbWriteFunc = [](
void* out_,
void* data,
int size) {
1086 std::copy_n(
static_cast<std::byte*
>(data), size, std::back_inserter(*
static_cast<std::vector<std::byte>*
>(out_)));
1089 if (fileFormat == FileFormat::DEFAULT) {
1092 switch (fileFormat) {
1093 case FileFormat::PNG: {
1094 if (format == ImageFormat::RGB888) {
1095 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data(), 0);
1096 }
else if (format == ImageFormat::RGBA8888) {
1097 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), imageData.data(), 0);
1100 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data(), 0);
1103 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), rgba.data(), 0);
1107 case FileFormat::JPG: {
1108 if (format == ImageFormat::RGB888) {
1109 stbi_write_jpg_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data(), 95);
1112 stbi_write_jpg_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data(), 95);
1116 case FileFormat::BMP: {
1117 if (format == ImageFormat::RGB888) {
1118 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data());
1119 }
else if (format == ImageFormat::RGBA8888) {
1120 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), imageData.data());
1123 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data());
1126 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), rgba.data());
1130 case FileFormat::TGA: {
1131 if (format == ImageFormat::RGB888) {
1132 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data());
1133 }
else if (format == ImageFormat::RGBA8888) {
1134 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), imageData.data());
1137 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data());
1140 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), rgba.data());
1144 case FileFormat::WEBP: {
1146 WebPConfigInit(&config);
1147 WebPConfigPreset(&config, WEBP_PRESET_DRAWING, 75.f);
1148 WebPConfigLosslessPreset(&config, 6);
1151 if (!WebPPictureInit(&pic)) {
1155 pic.height = height;
1156 if (!WebPPictureAlloc(&pic)) {
1160 if (format == ImageFormat::RGB888) {
1161 WebPPictureImportRGB(&pic,
reinterpret_cast<const uint8_t*
>(imageData.data()),
static_cast<int>(width *
sizeof(
ImagePixel::RGB888)));
1162 }
else if (format == ImageFormat::RGBA8888) {
1163 WebPPictureImportRGBA(&pic,
reinterpret_cast<const uint8_t*
>(imageData.data()),
static_cast<int>(width *
sizeof(
ImagePixel::RGBA8888)));
1166 WebPPictureImportRGB(&pic,
reinterpret_cast<const uint8_t*
>(rgb.data()),
static_cast<int>(width *
sizeof(
ImagePixel::RGB888)));
1169 WebPPictureImportRGBA(&pic,
reinterpret_cast<const uint8_t*
>(rgba.data()),
static_cast<int>(width *
sizeof(
ImagePixel::RGBA8888)));
1172 WebPMemoryWriter writer;
1173 WebPMemoryWriterInit(&writer);
1174 pic.writer = &WebPMemoryWrite;
1175 pic.custom_ptr = &writer;
1177 int ok = WebPEncode(&config, &pic);
1178 WebPPictureFree(&pic);
1180 WebPMemoryWriterClear(&writer);
1184 if (writer.mem && writer.size) {
1185 out.resize(writer.size);
1186 std::memcpy(out.data(), writer.mem, writer.size);
1188 WebPMemoryWriterClear(&writer);
1191 case FileFormat::QOI: {
1192 qoi_desc descriptor{
1196 .colorspace = QOI_SRGB,
1198 void* qoiData =
nullptr;
1200 if (format == ImageFormat::RGB888) {
1201 descriptor.channels = 3;
1202 qoiData = qoi_encode(imageData.data(), &descriptor, &qoiDataLen);
1203 }
else if (format == ImageFormat::RGBA8888) {
1204 descriptor.channels = 4;
1205 qoiData = qoi_encode(imageData.data(), &descriptor, &qoiDataLen);
1208 descriptor.channels = 3;
1209 qoiData = qoi_encode(rgb.data(), &descriptor, &qoiDataLen);
1212 descriptor.channels = 4;
1213 qoiData = qoi_encode(rgba.data(), &descriptor, &qoiDataLen);
1215 if (qoiData && qoiDataLen) {
1216 out.resize(qoiDataLen);
1217 std::memcpy(out.data(), qoiData, qoiDataLen);
1222 case FileFormat::HDR: {
1223 if (format == ImageFormat::RGB323232F) {
1224 stbi_write_hdr_to_func(stbWriteFunc, &out, width, height,
ImageFormatDetails::bpp(ImageFormat::RGB323232F) / (8 *
sizeof(
float)),
reinterpret_cast<const float*
>(imageData.data()));
1227 stbi_write_hdr_to_func(stbWriteFunc, &out, width, height,
ImageFormatDetails::bpp(ImageFormat::RGB323232F) / (8 *
sizeof(
float)),
reinterpret_cast<const float*
>(hdr.data()));
1231 case FileFormat::EXR: {
1233 InitEXRHeader(&header);
1235 std::vector<std::byte> rawData;
1239 format = ImageFormat::RGBA32323232F;
1242 format = ImageFormat::RGB323232F;
1245 rawData = {imageData.begin(), imageData.end()};
1249 header.channels =
static_cast<EXRChannelInfo*
>(std::malloc(header.num_channels *
sizeof(EXRChannelInfo)));
1250 header.pixel_types =
static_cast<int*
>(malloc(header.num_channels *
sizeof(
int)));
1251 header.requested_pixel_types =
static_cast<int*
>(malloc(header.num_channels *
sizeof(
int)));
1253 switch (header.num_channels) {
1255 header.channels[0].name[0] =
'A';
1256 header.channels[1].name[0] =
'B';
1257 header.channels[2].name[0] =
'G';
1258 header.channels[3].name[0] =
'R';
1261 header.channels[0].name[0] =
'B';
1262 header.channels[1].name[0] =
'G';
1263 header.channels[2].name[0] =
'R';
1266 header.channels[0].name[0] =
'G';
1267 header.channels[1].name[0] =
'R';
1270 header.channels[0].name[0] =
'R';
1273 FreeEXRHeader(&header);
1276 for (
int i = 0; i < header.num_channels; i++) {
1277 header.channels[i].name[1] =
'\0';
1280 int pixelType = (
ImageFormatDetails::red(format) / 8) ==
sizeof(half) ? TINYEXR_PIXELTYPE_HALF : TINYEXR_PIXELTYPE_FLOAT;
1281 for (
int i = 0; i < header.num_channels; i++) {
1282 header.pixel_types[i] = pixelType;
1283 header.requested_pixel_types[i] = pixelType;
1286 std::vector<std::vector<std::byte>> images(header.num_channels);
1287 std::vector<void*> imagePtrs(header.num_channels);
1288 switch (header.num_channels) {
1290 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1303 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1305 FreeEXRHeader(&header);
1313 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1322 images[0] = rawData;
1325 FreeEXRHeader(&header);
1328 for (
int i = 0; i < header.num_channels; i++) {
1329 imagePtrs[i] = images[i].data();
1333 InitEXRImage(&image);
1334 image.width = width;
1335 image.height = height;
1336 image.images =
reinterpret_cast<unsigned char**
>(imagePtrs.data());
1337 image.num_channels = header.num_channels;
1339 unsigned char* data =
nullptr;
1340 const char* err =
nullptr;
1342 size_t size = SaveEXRImageToMemory(&image, &header, &data, &err);
1344 FreeEXRErrorMessage(err);
1345 FreeEXRHeader(&header);
1349 out = {
reinterpret_cast<std::byte*
>(data),
reinterpret_cast<std::byte*
>(data) + size};
1353 FreeEXRHeader(&header);
1356 case FileFormat::DEFAULT:
1365using stb_ptr = std::unique_ptr<T, void(*)(
void*)>;
1370 stbi_convert_iphone_png_to_rgb(
true);
1372 format = ImageFormat::EMPTY;
1379 if (EXRVersion version; ParseEXRVersionFromMemory(&version,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size()) == TINYEXR_SUCCESS) {
1380 if (version.multipart || version.non_image) {
1385 InitEXRHeader(&header);
1386 const char* err =
nullptr;
1387 if (ParseEXRHeaderFromMemory(&header, &version,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size(), &err) != TINYEXR_SUCCESS) {
1388 FreeEXRErrorMessage(err);
1393 if (header.num_channels < 1) {
1394 FreeEXRHeader(&header);
1399 std::unordered_map<std::string_view, int> channelIndices{{
"R", -1}, {
"G", -1}, {
"B", -1}, {
"A", -1}, {
"Y", -1}};
1403 auto channelType = header.pixel_types[0];
1404 for (
int i = 1; i < header.num_channels; i++) {
1406 if (header.pixel_types[i] > channelType && channelIndices.contains(header.channels[i].name)) {
1407 channelType = header.pixel_types[i];
1411 if (channelType == TINYEXR_PIXELTYPE_UINT) {
1412 channelType = TINYEXR_PIXELTYPE_HALF;
1416 for (
int i = 0; i < header.num_channels; i++) {
1417 if (channelIndices.contains(header.channels[i].name)) {
1418 channelIndices[header.channels[i].name] = i;
1421 if (channelIndices[
"Y"] >= 0) {
1422 if (channelIndices[
"A"] >= 0) {
1423 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::RGBA16161616F : ImageFormat::RGBA32323232F;
1425 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1427 channelType = TINYEXR_PIXELTYPE_FLOAT;
1429 format = ImageFormat::RGB323232F;
1431 channelIndices[
"R"] = channelIndices[
"Y"];
1432 channelIndices[
"G"] = channelIndices[
"Y"];
1433 channelIndices[
"B"] = channelIndices[
"Y"];
1434 }
else if (channelIndices[
"A"] >= 0) {
1435 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::RGBA16161616F : ImageFormat::RGBA32323232F;
1436 }
else if (channelIndices[
"B"] >= 0) {
1437 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1439 channelType = TINYEXR_PIXELTYPE_FLOAT;
1441 format = ImageFormat::RGB323232F;
1442 }
else if (channelIndices[
"G"] >= 0) {
1443 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::RG1616F : ImageFormat::RG3232F;
1444 }
else if (channelIndices[
"R"] >= 0) {
1445 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::R16F : ImageFormat::R32F;
1447 FreeEXRHeader(&header);
1452 for (
int i = 0; i < header.num_channels; i++) {
1453 if (header.pixel_types[i] != channelType && channelIndices.contains(header.channels[i].name)) {
1454 header.requested_pixel_types[i] = channelType;
1459 InitEXRImage(&image);
1460 if (LoadEXRImageFromMemory(&image, &header,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size(), &err) != TINYEXR_SUCCESS) {
1461 FreeEXRErrorMessage(err);
1462 FreeEXRHeader(&header);
1466 width = image.width;
1467 height = image.height;
1471 const auto populateBuffer = [
1479 r=channelIndices[
"R"],
1480 g=channelIndices[
"G"],
1481 b=channelIndices[
"B"],
1482 a=channelIndices[
"A"],
1486 const auto channelCount = hasRed + hasGreen + hasBlue + hasAlpha;
1487 std::span out{
reinterpret_cast<C*
>(combinedChannels.data()), combinedChannels.size() /
sizeof(C)};
1489 for (
int t = 0; t < image.num_tiles; t++) {
1490 auto** src =
reinterpret_cast<C**
>(image.tiles[t].images);
1491 for (
int j = 0; j < header.tile_size_y; j++) {
1492 for (
int i = 0; i < header.tile_size_x; i++) {
1493 const auto ii =
static_cast<uint64_t
>(image.tiles[t].offset_x) * header.tile_size_x + i;
1494 const auto jj =
static_cast<uint64_t
>(image.tiles[t].offset_y) * header.tile_size_y + j;
1495 const auto idx = ii + jj * image.width;
1497 if (ii >= image.width || jj >= image.height) {
1501 const auto srcIdx = j *
static_cast<uint64_t
>(header.tile_size_x) + i;
1502 if (r >= 0) out[idx * channelCount + 0] = src[r][srcIdx];
1503 else if (hasRed) out[idx * channelCount + 0] = 0.f;
1504 if (g >= 0) out[idx * channelCount + 1] = src[g][srcIdx];
1505 else if (hasGreen) out[idx * channelCount + 1] = 0.f;
1506 if (b >= 0) out[idx * channelCount + 2] = src[b][srcIdx];
1507 else if (hasBlue) out[idx * channelCount + 2] = 0.f;
1508 if (a >= 0) out[idx * channelCount + 3] = src[a][srcIdx];
1509 else if (hasAlpha) out[idx * channelCount + 3] = 1.f;
1514 auto** src =
reinterpret_cast<C**
>(image.images);
1515 for (uint64_t i = 0; i < width * height; i++) {
1516 if (r >= 0) out[i * channelCount + 0] = src[r][i];
1517 else if (hasRed) out[i * channelCount + 0] = 0.f;
1518 if (g >= 0) out[i * channelCount + 1] = src[g][i];
1519 else if (hasGreen) out[i * channelCount + 1] = 0.f;
1520 if (b >= 0) out[i * channelCount + 2] = src[b][i];
1521 else if (hasBlue) out[i * channelCount + 2] = 0.f;
1522 if (a >= 0) out[i * channelCount + 3] = src[a][i];
1523 else if (hasAlpha) out[i * channelCount + 3] = 1.f;
1527 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1528 populateBuffer.operator()<half>();
1530 populateBuffer.operator()<
float>();
1533 FreeEXRImage(&image);
1534 FreeEXRHeader(&header);
1535 return combinedChannels;
1539 if (stbi_is_hdr_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()))) {
1540 const ::stb_ptr<float> stbImage{
1541 stbi_loadf_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1548 case 1: format = ImageFormat::R32F;
break;
1549 case 2: format = ImageFormat::RG3232F;
break;
1550 case 3: format = ImageFormat::RGB323232F;
break;
1551 case 4: format = ImageFormat::RGBA32323232F;
break;
1558 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) {
1559 width = features.width;
1560 height = features.height;
1564 std::vector<std::byte> out;
1565 if (features.has_alpha) {
1566 format = ImageFormat::RGBA8888;
1568 if (!WebPDecodeRGBAInto(
reinterpret_cast<const uint8_t*
>(fileData.data()), fileData.size(),
reinterpret_cast<uint8_t*
>(out.data()), out.size(), width * (
ImageFormatDetails::bpp(format) / 8))) {
1572 format = ImageFormat::RGB888;
1574 if (!WebPDecodeRGBInto(
reinterpret_cast<const uint8_t*
>(fileData.data()), fileData.size(),
reinterpret_cast<uint8_t*
>(out.data()), out.size(), width * (
ImageFormatDetails::bpp(format) / 8))) {
1582 if (fileData.size() > 3 &&
static_cast<char>(fileData[0]) ==
'G' &&
static_cast<char>(fileData[1]) ==
'I' &&
static_cast<char>(fileData[2]) ==
'F') {
1583 const ::stb_ptr<stbi_uc> stbImage{
1584 stbi_load_gif_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()),
nullptr, &width, &height, &frameCount, &channels, 0),
1587 if (!stbImage || !frameCount) {
1591 case 1: format = ImageFormat::I8;
break;
1592 case 2: format = ImageFormat::UV88;
break;
1593 case 3: format = ImageFormat::RGB888;
break;
1594 case 4: format = ImageFormat::RGBA8888;
break;
1597 return {
reinterpret_cast<std::byte*
>(stbImage.get()),
reinterpret_cast<std::byte*
>(stbImage.get() + (
ImageFormatDetails::getDataLength(format, width, height) * frameCount))};
1603 stbi__start_mem(&s,
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()));
1604 if (stbi__png_test(&s)) {
1606 const auto apngDecoder = [&format, &width, &height, &frameCount]<
typename P>(
const auto& stbImage, std::size_t dirOffset) -> std::vector<std::byte> {
1607 auto* dir =
reinterpret_cast<stbi__apng_directory*
>(stbImage.get() + dirOffset);
1608 if (dir->type != STBI__STRUCTURE_TYPE_APNG_DIRECTORY) {
1613 frameCount =
static_cast<int>(dir->num_frames);
1615 static constexpr auto calcPixelOffset = [](uint32_t offsetX, uint32_t offsetY, uint32_t width) {
1616 return ((offsetY * width) + offsetX) *
sizeof(P);
1620 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) {
1621 for (uint32_t y = 0; y < srcHeight; y++) {
1623#ifdef SOURCEPP_BUILD_WITH_TBB
1624 std::execution::unseq,
1626 src.data() + calcPixelOffset( 0, y, srcWidth),
1627 src.data() + calcPixelOffset( srcWidth, y, srcWidth),
1628 dst.data() + calcPixelOffset(srcOffsetX, srcOffsetY + y, dstWidth));
1633 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) {
1634 for (uint32_t y = subOffsetY; y < subOffsetY + subHeight; y++) {
1636#ifdef SOURCEPP_BUILD_WITH_TBB
1637 std::execution::unseq,
1639 src.data() + calcPixelOffset(subOffsetX, y, imgWidth),
1640 src.data() + calcPixelOffset(subOffsetX + subWidth, y, imgWidth),
1641 dst.data() + calcPixelOffset(subOffsetX, y, imgWidth));
1645 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) {
1646 for (uint32_t y = 0; y < clrHeight; y++) {
1648#ifdef SOURCEPP_BUILD_WITH_TBB
1649 std::execution::unseq,
1651 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth),
1652 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth) + (clrWidth *
sizeof(P)),
1653 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth),
1654 [](std::byte) {
return std::byte{0}; });
1658 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) {
1659 for (uint32_t y = 0; y < srcHeight; y++) {
1660 const auto* sp =
reinterpret_cast<const uint8_t*
>(src.data() + calcPixelOffset(0, y, srcWidth));
1661 auto* dp =
reinterpret_cast<uint8_t*
>(dst.data() + calcPixelOffset(srcOffsetX, srcOffsetY + y, dstWidth));
1662 for (uint32_t x = 0; x < srcWidth; x++, sp += 4, dp += 4) {
1665 }
else if ((sp[3] == 0xff) || (dp[3] == 0)) {
1666 std::copy(sp, sp +
sizeof(P), dp);
1668 int u = sp[3] * 0xff;
1669 int v = (0xff - sp[3]) * dp[3];
1671 dp[0] = (sp[0] * u + dp[0] * v) / al;
1672 dp[1] = (sp[1] * u + dp[1] * v) / al;
1673 dp[2] = (sp[2] * u + dp[2] * v) / al;
1681 const uint64_t fullFrameSize =
sizeof(P) * width * height;
1682 uint64_t currentFrameSize = 0;
1683 std::vector<std::byte> out(fullFrameSize * frameCount);
1684 uint64_t srcFrameOffset = 0;
1685 uint64_t dstFrameOffset = 0;
1686 for (uint32_t i = 0; i < dir->num_frames; i++) {
1687 const auto& frame = dir->frames[i];
1688 currentFrameSize =
sizeof(P) * frame.width * frame.height;
1691 if (frame.width == width && frame.height == height && frame.x_offset == 0 && frame.y_offset == 0 && frame.blend_op == STBI_APNG_blend_op_source) {
1692 std::memcpy(out.data() + dstFrameOffset, stbImage.get() + srcFrameOffset, fullFrameSize);
1695 if (frame.blend_op == STBI_APNG_blend_op_source || (i == 0 && frame.blend_op == STBI_APNG_blend_op_over)) {
1696 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);
1697 }
else if (frame.blend_op == STBI_APNG_blend_op_over) {
1698 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);
1704 dstFrameOffset += fullFrameSize;
1705 srcFrameOffset += currentFrameSize;
1708 if (i == dir->num_frames - 1) {
1713 copyImageData({out.data() + dstFrameOffset, out.data() + dstFrameOffset + fullFrameSize}, width, height, {out.data() + dstFrameOffset - fullFrameSize, out.data() + dstFrameOffset}, width, height, 0, 0);
1716 if (frame.dispose_op == STBI_APNG_dispose_op_background || (i == 0 && frame.dispose_op == STBI_APNG_dispose_op_previous)) {
1717 clearImageData({out.data() + dstFrameOffset, out.data() + dstFrameOffset + fullFrameSize}, width, height, frame.width, frame.height, frame.x_offset, frame.y_offset);
1718 }
else if (frame.dispose_op == STBI_APNG_dispose_op_previous) {
1719 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);
1720 }
else if (frame.dispose_op != STBI_APNG_dispose_op_none) {
1727 static const char *dispose_ops[] = {
1728 "STBI_APNG_dispose_op_none",
1729 "STBI_APNG_dispose_op_background",
1730 "STBI_APNG_dispose_op_previous",
1733 static const char *blend_ops[] = {
1734 "STBI_APNG_blend_op_source",
1735 "STBI_APNG_blend_op_over",
1738 fprintf(stderr,
"dir_offset : %zu\n", dirOffset);
1739 fprintf(stderr,
"dir.type : %.*s\n", 4, (
unsigned char *) &dir->type);
1740 fprintf(stderr,
"dir.num_frames : %u\n", dir->num_frames);
1741 fprintf(stderr,
"dir.default_image_is_first_frame : %s\n",
1742 dir->default_image_is_first_frame ?
"yes" :
"no");
1743 fprintf(stderr,
"dir.num_plays : %u\n", dir->num_plays);
1745 for (
int i = 0; i < dir->num_frames; ++i) {
1746 stbi__apng_frame_directory_entry *frame = &dir->frames[i];
1748 fprintf(stderr,
"frame : %u\n", i);
1749 fprintf(stderr,
" width : %u\n", frame->width);
1750 fprintf(stderr,
" height : %u\n", frame->height);
1751 fprintf(stderr,
" x_offset : %u\n", frame->x_offset);
1752 fprintf(stderr,
" y_offset : %u\n", frame->y_offset);
1753 fprintf(stderr,
" delay_num : %u\n", frame->delay_num);
1754 fprintf(stderr,
" delay_den : %u\n", frame->delay_den);
1755 fprintf(stderr,
" dispose_op : %s\n", dispose_ops[frame->dispose_op]);
1756 fprintf(stderr,
" blend_op : %s\n", blend_ops[frame->blend_op]);
1762 std::size_t dirOffset = 0;
1763 if (stbi__png_is16(&s)) {
1764 const ::stb_ptr<stbi_us> stbImage{
1765 stbi__apng_load_16bit(&s, &width, &height, &channels, STBI_rgb_alpha, &dirOffset),
1768 if (stbImage && dirOffset) {
1772 const ::stb_ptr<stbi_uc> stbImage{
1773 stbi__apng_load_8bit(&s, &width, &height, &channels, STBI_rgb_alpha, &dirOffset),
1776 if (stbImage && dirOffset) {
1785 qoi_desc descriptor;
1786 const ::stb_ptr<std::byte> qoiImage{
1787 static_cast<std::byte*
>(qoi_decode(fileData.data(), fileData.size(), &descriptor, 0)),
1793 width = descriptor.width;
1794 height = descriptor.height;
1795 channels = descriptor.channels;
1797 case 3: format = ImageFormat::RGB888;
break;
1798 case 4: format = ImageFormat::RGBA8888;
break;
1805 if (stbi_is_16_bit_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()))) {
1806 const ::stb_ptr<stbi_us> stbImage{
1807 stbi_load_16_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1813 if (channels == 4) {
1814 format = ImageFormat::RGBA16161616;
1815 }
else if (channels >= 1 && channels < 4) {
1817 format = ImageFormat::RGBA16161616;
1822 std::span<uint16_t> inPixels{
reinterpret_cast<uint16_t*
>(stbImage.get()), outPixels.size()};
1824#ifdef SOURCEPP_BUILD_WITH_TBB
1825 std::execution::par_unseq,
1828 return {pixel, 0, 0, 0xffff};
1837 std::span<RG1616> inPixels{
reinterpret_cast<RG1616*
>(stbImage.get()), outPixels.size()};
1839#ifdef SOURCEPP_BUILD_WITH_TBB
1840 std::execution::par_unseq,
1843 return {pixel.r, pixel.g, 0, 0xffff};
1853 std::span<RGB161616> inPixels{
reinterpret_cast<RGB161616*
>(stbImage.get()), outPixels.size()};
1855#ifdef SOURCEPP_BUILD_WITH_TBB
1856 std::execution::par_unseq,
1859 return {pixel.r, pixel.g, pixel.b, 0xffff};
1873 const ::stb_ptr<stbi_uc> stbImage{
1874 stbi_load_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1881 case 1: format = ImageFormat::I8;
break;
1882 case 2: format = ImageFormat::UV88;
break;
1883 case 3: format = ImageFormat::RGB888;
break;
1884 case 4: format = ImageFormat::RGBA8888;
break;
1892 case ResizeMethod::NONE:
break;
1893 case ResizeMethod::POWER_OF_TWO_BIGGER:
return std::bit_ceil(n);
1894 case ResizeMethod::POWER_OF_TWO_SMALLER:
return std::bit_floor(n);
1906 if (imageData.empty() || format == ImageFormat::EMPTY) {
1910 STBIR_RESIZE resize;
1911 const auto setEdgeModesAndFiltersAndDoResize = [edge, filter, &resize] {
1912 stbir_set_edgemodes(&resize,
static_cast<stbir_edge
>(edge),
static_cast<stbir_edge
>(edge));
1914 case ResizeFilter::DEFAULT:
1915 case ResizeFilter::BOX:
1916 case ResizeFilter::BILINEAR:
1917 case ResizeFilter::CUBIC_BSPLINE:
1918 case ResizeFilter::CATMULL_ROM:
1919 case ResizeFilter::MITCHELL:
1920 case ResizeFilter::POINT_SAMPLE: {
1921 stbir_set_filters(&resize,
static_cast<stbir_filter
>(filter),
static_cast<stbir_filter
>(filter));
1924 case ResizeFilter::KAISER: {
1925 static constexpr auto KAISER_BETA = [](
float s) {
1934 static constexpr auto KAISER_FILTER = [](
float x,
float s,
void*) ->
float {
1935 if (x < -1.f || x > 1.f) {
1940 static constexpr auto KAISER_SUPPORT = [](
float s,
void*) ->
float {
1941 float baseSupport = KAISER_BETA(s) / 2.f;
1943 return std::max(2.f, baseSupport - 0.5f);
1945 return std::max(3.f, baseSupport);
1947 stbir_set_filter_callbacks(&resize, KAISER_FILTER, KAISER_SUPPORT, KAISER_FILTER, KAISER_SUPPORT);
1950 case ResizeFilter::NICE: {
1951 static constexpr auto SINC = [](
float x) ->
float {
1952 if (x == 0.f)
return 1.f;
1956 static constexpr auto NICE_FILTER = [](
float x, float,
void*) ->
float {
1957 if (x >= 3.f || x <= -3.f)
return 0.f;
1958 return SINC(x) * SINC(x / 3.f);
1962 static constexpr auto NICE_SUPPORT = [](
float invScale,
void*) ->
float {
1963 return invScale * 3.f;
1965 stbir_set_filter_callbacks(&resize, NICE_FILTER, NICE_SUPPORT, NICE_FILTER, NICE_SUPPORT);
1969 stbir_resize_extended(&resize);
1972 const auto pixelLayout = ::imageFormatToSTBIRPixelLayout(format);
1973 if (pixelLayout == -1) {
1977 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)));
1978 setEdgeModesAndFiltersAndDoResize();
1982 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)));
1983 setEdgeModesAndFiltersAndDoResize();
1987std::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) {
1988 if (imageData.empty() || format == ImageFormat::EMPTY) {
1993 return resizeImageData(imageData, format, width, widthOut, height, heightOut, srgb, filter, edge);
1997std::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) {
1998 if (imageData.empty() || format == ImageFormat::EMPTY || xOffset + newWidth >= width || yOffset + newHeight >= height) {
2004 return convertImageDataToFormat(
cropImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, newWidth, xOffset, height, newHeight, yOffset), container, format, newWidth, newHeight);
2008 std::vector<std::byte> out(pixelSize * newWidth * newHeight);
2009 for (uint16_t y = yOffset; y < yOffset + newHeight; y++) {
2010 std::memcpy(out.data() + (((y - yOffset) * newWidth) * pixelSize), imageData.data() + (((y * width) + xOffset) * pixelSize), newWidth * pixelSize);
2017 if (imageData.empty() || format == ImageFormat::EMPTY) {
2022 return {imageData.begin(), imageData.end()};
2027 return convertImageDataToFormat(
gammaCorrectImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, height, gamma), container, format, width, height);
2030 static constexpr auto calculateGammaLUT = [](
float gamma_, uint8_t channelSize) -> std::array<uint8_t, 256> {
2031 const auto maxSize =
static_cast<float>((1 << channelSize) - 1);
2032 std::array<uint8_t, 256> gammaLUT{};
2033 for (
int i = 0; i < gammaLUT.size(); i++) {
2034 gammaLUT[i] =
static_cast<uint8_t
>(std::clamp(std::pow((
static_cast<float>(i) + 0.5f) / maxSize, gamma_) * maxSize - 0.5f, 0.f, maxSize));
2039 #define VTFPP_CREATE_GAMMA_LUTS(InputType) \
2040 std::unordered_map<uint8_t, std::array<uint8_t, 256>> gammaLUTs; \
2041 if constexpr (ImageFormatDetails::red(ImageFormat::InputType) > 0) { \
2042 if (!gammaLUTs.contains(ImageFormatDetails::red(ImageFormat::InputType))) { \
2043 gammaLUTs[ImageFormatDetails::red(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::red(ImageFormat::InputType)); \
2046 if constexpr (ImageFormatDetails::green(ImageFormat::InputType) > 0) { \
2047 if (!gammaLUTs.contains(ImageFormatDetails::green(ImageFormat::InputType))) { \
2048 gammaLUTs[ImageFormatDetails::green(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::green(ImageFormat::InputType)); \
2051 if constexpr (ImageFormatDetails::blue(ImageFormat::InputType) > 0) { \
2052 if (!gammaLUTs.contains(ImageFormatDetails::blue(ImageFormat::InputType))) { \
2053 gammaLUTs[ImageFormatDetails::blue(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::blue(ImageFormat::InputType)); \
2057 #define VTFPP_APPLY_GAMMA_RED(value) \
2058 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::red(PIXEL_TYPE::FORMAT))[value])
2060 #define VTFPP_APPLY_GAMMA_GREEN(value) \
2061 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::green(PIXEL_TYPE::FORMAT))[value])
2063 #define VTFPP_APPLY_GAMMA_BLUE(value) \
2064 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::blue(PIXEL_TYPE::FORMAT))[value])
2066 std::vector<std::byte> out(imageData.size());
2068#ifdef SOURCEPP_BUILD_WITH_TBB
2069 #define VTFPP_GAMMA_CORRECT(InputType, ...) \
2070 std::span imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
2071 std::span outSpan{reinterpret_cast<ImagePixel::InputType*>(out.data()), out.size() / sizeof(ImagePixel::InputType)}; \
2072 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), outSpan.begin(), [gammaLUTs](ImagePixel::InputType pixel) -> ImagePixel::InputType { \
2073 using PIXEL_TYPE = ImagePixel::InputType; \
2074 return __VA_ARGS__; \
2077 #define VTFPP_GAMMA_CORRECT(InputType, ...) \
2078 std::span imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
2079 std::span outSpan{reinterpret_cast<ImagePixel::InputType*>(out.data()), out.size() / sizeof(ImagePixel::InputType)}; \
2080 std::transform(imageDataSpan.begin(), imageDataSpan.end(), outSpan.begin(), [gammaLUTs](ImagePixel::InputType pixel) -> ImagePixel::InputType { \
2081 using PIXEL_TYPE = ImagePixel::InputType; \
2082 return __VA_ARGS__; \
2085 #define VTFPP_CASE_GAMMA_CORRECT_AND_BREAK(InputType, ...) \
2086 case InputType: { VTFPP_CREATE_GAMMA_LUTS(InputType) VTFPP_GAMMA_CORRECT(InputType, __VA_ARGS__); } break
2092 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)});
2094 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)});
2110 #undef VTFPP_CASE_GAMMA_CORRECT_AND_BREAK
2111 #undef VTFPP_GAMMA_CORRECT
2112 #undef VTFPP_APPLY_GAMMA_BLUE
2113 #undef VTFPP_APPLY_GAMMA_GREEN
2114 #undef VTFPP_APPLY_GAMMA_RED
2115 #undef VTFPP_CREATE_GAMMA_LUTS
2128 return convertImageDataToFormat(
invertGreenChannelForImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, height), container, format, width, height);
2131 #define VTFPP_INVERT_GREEN(PixelType, ChannelName, ...) \
2132 static constexpr auto channelSize = ImageFormatDetails::green(ImagePixel::PixelType::FORMAT); \
2133 std::span imageDataSpan{reinterpret_cast<const ImagePixel::PixelType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::PixelType)}; \
2134 std::span outSpan{reinterpret_cast<ImagePixel::PixelType*>(out.data()), out.size() / sizeof(ImagePixel::PixelType)}; \
2135 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), outSpan.begin(), [](ImagePixel::PixelType pixel) -> ImagePixel::PixelType { \
2136 if constexpr (std::same_as<decltype(pixel.ChannelName), float> || std::same_as<decltype(pixel.ChannelName), half>) { \
2137 pixel.ChannelName = static_cast<decltype(pixel.ChannelName)>(static_cast<float>(static_cast<uint64_t>(1) << channelSize) - 1.f - static_cast<float>(pixel.ChannelName)); \
2139 if constexpr (channelSize >= sizeof(uint32_t) * 8) { \
2140 pixel.ChannelName = static_cast<decltype(pixel.ChannelName)>((static_cast<uint64_t>(1) << channelSize) - 1 - static_cast<uint32_t>(pixel.ChannelName)); \
2142 pixel.ChannelName = static_cast<decltype(pixel.ChannelName)>(static_cast<uint32_t>(1 << channelSize) - 1 - static_cast<uint32_t>(pixel.ChannelName)); \
2147#ifdef SOURCEPP_BUILD_WITH_TBB
2148 #define VTFPP_INVERT_GREEN_CASE(PixelType) \
2149 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, g, std::execution::par_unseq); break; }
2150 #define VTFPP_INVERT_GREEN_CASE_CA_OVERRIDE(PixelType, ChannelName) \
2151 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, ChannelName, std::execution::par_unseq); break; }
2153 #define VTFPP_INVERT_GREEN_CASE(PixelType) \
2154 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, g); } break
2155 #define VTFPP_INVERT_GREEN_CASE_CA_OVERRIDE(PixelType, ChannelName) \
2156 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, ChannelName); } break
2159 std::vector<std::byte> out(imageData.size());
2201 #undef VTFPP_INVERT_GREEN_CASE_CA_OVERRIDE
2202 #undef VTFPP_INVERT_GREEN_CASE
2203 #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 > 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 > 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 sliceCount, float quality=DEFAULT_COMPRESSED_QUALITY)
Converts several images from one format to another.
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