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;
286[[nodiscard]] std::vector<std::byte> convertImageDataUsingCompressonator(std::span<const std::byte> imageData,
ImageFormat oldFormat,
ImageFormat newFormat, uint16_t width, uint16_t height) {
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.bDXT1UseAlpha = oldFormat == ImageFormat::DXT1_ONE_BIT_ALPHA || newFormat == ImageFormat::DXT1_ONE_BIT_ALPHA;
316 options.dwnumThreads = 0;
318 if (CMP_ConvertTexture(&srcTexture, &destTexture, &options,
nullptr) != CMP_OK) {
324[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA8888(std::span<const std::byte> imageData,
ImageFormat format) {
325 using namespace ImageConversion;
327 if (imageData.empty()) {
331 if (format == ImageFormat::RGBA8888 || format == ImageFormat::UVWQ8888) {
332 return {imageData.begin(), imageData.end()};
335 std::vector<std::byte> newData;
339 #define VTFPP_REMAP_TO_8(value, shift) math::remap<uint8_t>((value), (1 << (shift)) - 1, (1 << 8) - 1)
341 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
342 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
343 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA8888 { \
344 return {(r), (g), (b), (a)}; \
346#ifdef SOURCEPP_BUILD_WITH_TBB
347 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
349 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
351 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
352 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
391 #undef VTFPP_CASE_CONVERT_AND_BREAK
393 #undef VTFPP_CONVERT_DETAIL
394 #undef VTFPP_REMAP_TO_8
399[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888(std::span<const std::byte> imageData,
ImageFormat format) {
400 using namespace ImageConversion;
402 if (imageData.empty()) {
406 if (format == ImageFormat::RGBA8888) {
407 return {imageData.begin(), imageData.end()};
411 std::vector<std::byte> newData;
414 #define VTFPP_REMAP_FROM_8(value, shift) math::remap<uint8_t>((value), (1 << 8) - 1, (1 << (shift)) - 1)
416#ifdef SOURCEPP_BUILD_WITH_TBB
417 #define VTFPP_CONVERT(InputType, ...) \
418 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
419 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA8888 pixel) -> ImagePixel::InputType { \
420 return __VA_ARGS__; \
423 #define VTFPP_CONVERT(InputType, ...) \
424 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
425 std::transform(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA8888 pixel) -> ImagePixel::InputType { \
426 return __VA_ARGS__; \
429 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
430 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
469 #undef VTFPP_CASE_CONVERT_AND_BREAK
471 #undef VTFPP_REMAP_FROM_8
476[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA16161616(std::span<const std::byte> imageData,
ImageFormat format) {
477 using namespace ImageConversion;
479 if (imageData.empty()) {
483 if (format == ImageFormat::RGBA16161616) {
484 return {imageData.begin(), imageData.end()};
487 std::vector<std::byte> newData;
491 #define VTFPP_REMAP_TO_16(value, shift) math::remap<uint16_t>((value), (1 << (shift)) - 1, (1 << 16) - 1)
493 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
494 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
495 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA16161616 { \
496 return { static_cast<uint16_t>(r), static_cast<uint16_t>(g), static_cast<uint16_t>(b), static_cast<uint16_t>(a) }; \
498#ifdef SOURCEPP_BUILD_WITH_TBB
499 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
501 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
503 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
504 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
506 #define VTFPP_CONVERT_REMAP(InputType, r, g, b, a) \
508 if constexpr (ImageFormatDetails::alpha(ImageFormat::InputType) > 1) { \
509 VTFPP_CONVERT(InputType, \
510 VTFPP_REMAP_TO_16((r), ImageFormatDetails::red(ImageFormat::InputType)), \
511 VTFPP_REMAP_TO_16((g), ImageFormatDetails::green(ImageFormat::InputType)), \
512 VTFPP_REMAP_TO_16((b), ImageFormatDetails::blue(ImageFormat::InputType)), \
513 VTFPP_REMAP_TO_16((a), ImageFormatDetails::alpha(ImageFormat::InputType))); \
514 } else if constexpr (ImageFormatDetails::alpha(ImageFormat::InputType) == 1) { \
515 VTFPP_CONVERT(InputType, \
516 VTFPP_REMAP_TO_16((r), ImageFormatDetails::red(ImageFormat::InputType)), \
517 VTFPP_REMAP_TO_16((g), ImageFormatDetails::green(ImageFormat::InputType)), \
518 VTFPP_REMAP_TO_16((b), ImageFormatDetails::blue(ImageFormat::InputType)), \
521 VTFPP_CONVERT(InputType, \
522 VTFPP_REMAP_TO_16((r), ImageFormatDetails::red(ImageFormat::InputType)), \
523 VTFPP_REMAP_TO_16((g), ImageFormatDetails::green(ImageFormat::InputType)), \
524 VTFPP_REMAP_TO_16((b), ImageFormatDetails::blue(ImageFormat::InputType)), \
528 #define VTFPP_CASE_CONVERT_REMAP_AND_BREAK(InputType, r, g, b, a) \
529 case InputType: { VTFPP_CONVERT_REMAP(InputType, r, g, b, a); } \
540 #undef VTFPP_CASE_CONVERT_REMAP_AND_BREAK
541 #undef VTFPP_CONVERT_REMAP
542 #undef VTFPP_CASE_CONVERT_AND_BREAK
544 #undef VTFPP_CONVERT_DETAIL
545 #undef VTFPP_REMAP_TO_16
550[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616(std::span<const std::byte> imageData,
ImageFormat format) {
551 using namespace ImageConversion;
553 if (imageData.empty()) {
557 if (format == ImageFormat::RGBA16161616) {
558 return {imageData.begin(), imageData.end()};
562 std::vector<std::byte> newData;
565 #define VTFPP_REMAP_FROM_16(value, shift) static_cast<uint8_t>(math::remap<uint16_t>((value), (1 << 16) - 1, (1 << (shift)) - 1))
567#ifdef SOURCEPP_BUILD_WITH_TBB
568 #define VTFPP_CONVERT(InputType, ...) \
569 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
570 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA16161616 pixel) -> ImagePixel::InputType { \
571 return __VA_ARGS__; \
574 #define VTFPP_CONVERT(InputType, ...) \
575 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
576 std::transform(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA16161616 pixel) -> ImagePixel::InputType { \
577 return __VA_ARGS__; \
580 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
581 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
591 #undef VTFPP_CASE_CONVERT_AND_BREAK
593 #undef VTFPP_REMAP_FROM_16
598[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA32323232F(std::span<const std::byte> imageData,
ImageFormat format) {
599 if (imageData.empty()) {
603 if (format == ImageFormat::RGBA32323232F) {
604 return {imageData.begin(), imageData.end()};
607 std::vector<std::byte> newData;
611 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
612 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
613 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA32323232F { return {(r), (g), (b), (a)}; })
614#ifdef SOURCEPP_BUILD_WITH_TBB
615 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
617 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
619 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
620 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
633 #undef VTFPP_CASE_CONVERT_AND_BREAK
635 #undef VTFPP_CONVERT_DETAIL
640[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232F(std::span<const std::byte> imageData,
ImageFormat format) {
641 using namespace ImageConversion;
643 if (imageData.empty()) {
647 if (format == ImageFormat::RGBA32323232F) {
648 return {imageData.begin(), imageData.end()};
652 std::vector<std::byte> newData;
655#ifdef SOURCEPP_BUILD_WITH_TBB
656 #define VTFPP_CONVERT(InputType, ...) \
657 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
658 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA32323232F pixel) -> ImagePixel::InputType { \
659 return __VA_ARGS__; \
662 #define VTFPP_CONVERT(InputType, ...) \
663 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
664 std::transform(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA32323232F pixel) -> ImagePixel::InputType { \
665 return __VA_ARGS__; \
668 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
669 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
682 #undef VTFPP_CASE_CONVERT_AND_BREAK
688[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888ToRGBA32323232F(std::span<const std::byte> imageData) {
689 if (imageData.empty()) {
693 std::vector<std::byte> newData;
699#ifdef SOURCEPP_BUILD_WITH_TBB
700 std::execution::par_unseq,
704 static_cast<float>(pixel.r) / static_cast<float>((1 << 8) - 1),
705 static_cast<float>(pixel.g) / static_cast<float>((1 << 8) - 1),
706 static_cast<float>(pixel.b) / static_cast<float>((1 << 8) - 1),
707 static_cast<float>(pixel.a) / static_cast<float>((1 << 8) - 1),
714[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232FToRGBA8888(std::span<const std::byte> imageData) {
715 if (imageData.empty()) {
719 std::vector<std::byte> newData;
725#ifdef SOURCEPP_BUILD_WITH_TBB
726 std::execution::par_unseq,
730 static_cast<uint8_t>(std::clamp(pixel.r, 0.f, 1.f) * ((1 << 8) - 1)),
731 static_cast<uint8_t>(std::clamp(pixel.g, 0.f, 1.f) * ((1 << 8) - 1)),
732 static_cast<uint8_t>(std::clamp(pixel.b, 0.f, 1.f) * ((1 << 8) - 1)),
733 static_cast<uint8_t>(std::clamp(pixel.a, 0.f, 1.f) * ((1 << 8) - 1)),
740[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888ToRGBA16161616(std::span<const std::byte> imageData) {
741 if (imageData.empty()) {
745 std::vector<std::byte> newData;
751#ifdef SOURCEPP_BUILD_WITH_TBB
752 std::execution::par_unseq,
756 math::remap<uint16_t>(pixel.r, (1 << 8) - 1, (1 << 16) - 1),
757 math::remap<uint16_t>(pixel.g, (1 << 8) - 1, (1 << 16) - 1),
758 math::remap<uint16_t>(pixel.b, (1 << 8) - 1, (1 << 16) - 1),
759 math::remap<uint16_t>(pixel.a, (1 << 8) - 1, (1 << 16) - 1),
766[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616ToRGBA8888(std::span<const std::byte> imageData) {
767 if (imageData.empty()) {
771 std::vector<std::byte> newData;
777#ifdef SOURCEPP_BUILD_WITH_TBB
778 std::execution::par_unseq,
782 static_cast<uint8_t>(math::remap<uint16_t>(pixel.r, (1 << 16) - 1, (1 << 8) - 1)),
783 static_cast<uint8_t>(math::remap<uint16_t>(pixel.g, (1 << 16) - 1, (1 << 8) - 1)),
784 static_cast<uint8_t>(math::remap<uint16_t>(pixel.b, (1 << 16) - 1, (1 << 8) - 1)),
785 static_cast<uint8_t>(math::remap<uint16_t>(pixel.a, (1 << 16) - 1, (1 << 8) - 1)),
792[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232FToRGBA16161616(std::span<const std::byte> imageData) {
793 if (imageData.empty()) {
797 std::vector<std::byte> newData;
803#ifdef SOURCEPP_BUILD_WITH_TBB
804 std::execution::par_unseq,
808 static_cast<uint16_t>(std::clamp(pixel.r, 0.f, 1.f) * ((1 << 16) - 1)),
809 static_cast<uint16_t>(std::clamp(pixel.g, 0.f, 1.f) * ((1 << 16) - 1)),
810 static_cast<uint16_t>(std::clamp(pixel.b, 0.f, 1.f) * ((1 << 16) - 1)),
811 static_cast<uint16_t>(std::clamp(pixel.a, 0.f, 1.f) * ((1 << 16) - 1)),
818[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616ToRGBA32323232F(std::span<const std::byte> imageData) {
819 if (imageData.empty()) {
823 std::vector<std::byte> newData;
829#ifdef SOURCEPP_BUILD_WITH_TBB
830 std::execution::par_unseq,
834 static_cast<float>(pixel.r) / static_cast<float>((1 << 16) - 1),
835 static_cast<float>(pixel.g) / static_cast<float>((1 << 16) - 1),
836 static_cast<float>(pixel.b) / static_cast<float>((1 << 16) - 1),
837 static_cast<float>(pixel.a) / static_cast<float>((1 << 16) - 1),
847 if (imageData.empty() || oldFormat == ImageFormat::EMPTY) {
851 if (oldFormat == newFormat) {
852 return {imageData.begin(), imageData.end()};
855 std::vector<std::byte> newData;
859 newData = ::convertImageDataUsingCompressonator(imageData, oldFormat, intermediaryOldFormat, width, height);
861 switch (intermediaryOldFormat) {
862 case ImageFormat::RGBA8888: newData = ::convertImageDataToRGBA8888(imageData, oldFormat);
break;
863 case ImageFormat::RGBA16161616: newData = ::convertImageDataToRGBA16161616(imageData, oldFormat);
break;
864 case ImageFormat::RGBA32323232F: newData = ::convertImageDataToRGBA32323232F(imageData, oldFormat);
break;
869 if (intermediaryOldFormat == newFormat) {
874 if (intermediaryOldFormat != intermediaryNewFormat) {
875 if (intermediaryOldFormat == ImageFormat::RGBA8888) {
876 if (intermediaryNewFormat == ImageFormat::RGBA16161616) {
877 newData = ::convertImageDataFromRGBA8888ToRGBA16161616(newData);
878 }
else if (intermediaryNewFormat == ImageFormat::RGBA32323232F) {
879 newData = ::convertImageDataFromRGBA8888ToRGBA32323232F(newData);
883 }
else if (intermediaryOldFormat == ImageFormat::RGBA16161616) {
884 if (intermediaryNewFormat == ImageFormat::RGBA8888) {
885 newData = ::convertImageDataFromRGBA16161616ToRGBA8888(newData);
886 }
else if (intermediaryNewFormat == ImageFormat::RGBA32323232F) {
887 newData = ::convertImageDataFromRGBA16161616ToRGBA32323232F(newData);
891 }
else if (intermediaryOldFormat == ImageFormat::RGBA32323232F) {
892 if (intermediaryNewFormat == ImageFormat::RGBA8888) {
893 newData = ::convertImageDataFromRGBA32323232FToRGBA8888(newData);
894 }
else if (intermediaryNewFormat == ImageFormat::RGBA16161616) {
895 newData = ::convertImageDataFromRGBA32323232FToRGBA16161616(newData);
904 if (intermediaryNewFormat == newFormat) {
909 newData = ::convertImageDataUsingCompressonator(newData, intermediaryNewFormat, newFormat, width, height);
911 switch (intermediaryNewFormat) {
912 case ImageFormat::RGBA8888: newData = ::convertImageDataFromRGBA8888(newData, newFormat);
break;
913 case ImageFormat::RGBA16161616: newData = ::convertImageDataFromRGBA16161616(newData, newFormat);
break;
914 case ImageFormat::RGBA32323232F: newData = ::convertImageDataFromRGBA32323232F(newData, newFormat);
break;
923 if (imageData.empty() || oldFormat == ImageFormat::EMPTY) {
927 if (oldFormat == newFormat) {
928 return {imageData.begin(), imageData.end()};
932 for(
int mip = mipCount - 1; mip >= 0; mip--) {
933 for (
int frame = 0; frame < frameCount; frame++) {
934 for (
int face = 0; face < faceCount; face++) {
935 for (
int slice = 0; slice < sliceCount; slice++) {
936 if (uint32_t oldOffset, oldLength;
ImageFormatDetails::getDataPosition(oldOffset, oldLength, oldFormat, mip, mipCount, frame, frameCount, face, faceCount, width, height, slice, sliceCount)) {
938 if (uint32_t newOffset, newLength;
ImageFormatDetails::getDataPosition(newOffset, newLength, newFormat, mip, mipCount, frame, frameCount, face, faceCount, width, height, slice, sliceCount) && newLength == convertedImageData.size()) {
939 std::memcpy(out.data() + newOffset, convertedImageData.data(), newLength);
950 if (imageData.empty() || format == ImageFormat::EMPTY) {
958 std::span<const float> imageDataRGBA32323232F{
reinterpret_cast<const float*
>(imageData.data()),
reinterpret_cast<const float*
>(imageData.data() + imageData.size())};
960 std::vector<std::byte> possiblyConvertedDataOrEmptyDontUseMeDirectly;
961 if (format != ImageFormat::RGBA32323232F) {
962 possiblyConvertedDataOrEmptyDontUseMeDirectly =
convertImageDataToFormat(imageData, format, ImageFormat::RGBA32323232F, width, height);
963 imageDataRGBA32323232F = {
reinterpret_cast<const float*
>(possiblyConvertedDataOrEmptyDontUseMeDirectly.data()),
reinterpret_cast<const float*
>(possiblyConvertedDataOrEmptyDontUseMeDirectly.data() + possiblyConvertedDataOrEmptyDontUseMeDirectly.size())};
968 static constexpr std::array<std::array<math::Vec3f, 3>, 6> startRightUp = {{
969 {{{ 1.0f, -1.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}},
970 {{{ 1.0f, 1.0f, 1.0f}, { 0.0f,-1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}},
971 {{{ 1.0f, 1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, { 0.0f,-1.0f, 0.0f}}},
972 {{{-1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}}},
973 {{{ 1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}}},
974 {{{ 1.0f, 1.0f, -1.0f}, { 0.0f, 0.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}},
977 std::array<std::vector<std::byte>, 6> faceData;
979#ifdef SOURCEPP_BUILD_WITH_THREADS
980 const auto faceExtraction = [&](
int i) {
982 for (
int i = 0; i < faceData.size(); i++) {
984 const auto start = startRightUp[i][0];
985 const auto right = startRightUp[i][1];
986 const auto up = startRightUp[i][2];
989 std::span<float> face{
reinterpret_cast<float*
>(faceData[i].data()),
reinterpret_cast<float*
>(faceData[i].data() + faceData[i].size())};
991 for (
int row = 0; row < resolution; row++) {
992 for (
int col = 0; col < resolution; col++) {
993 math::Vec3f pixelDirection3d{
994 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],
995 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],
996 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],
998 float azimuth = std::atan2(pixelDirection3d[0], -pixelDirection3d[2]) +
math::pi_f32;
999 float elevation = std::atan(pixelDirection3d[1] / std::sqrt(pixelDirection3d[0] * pixelDirection3d[0] + pixelDirection3d[2] * pixelDirection3d[2])) +
math::pi_f32 / 2.f;
1000 float colHdri = (azimuth /
math::pi_f32 / 2.f) *
static_cast<float>(width);
1001 float rowHdri = (elevation /
math::pi_f32) *
static_cast<float>(height);
1003 int colNearest = std::clamp(
static_cast<int>(colHdri), 0, width - 1);
1004 int rowNearest = std::clamp(
static_cast<int>(rowHdri), 0, height - 1);
1005 face[col * 4 + resolution * row * 4 + 0] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 0];
1006 face[col * 4 + resolution * row * 4 + 1] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 1];
1007 face[col * 4 + resolution * row * 4 + 2] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 2];
1008 face[col * 4 + resolution * row * 4 + 3] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 3];
1010 float intCol, intRow;
1012 float factorCol = std::modf(colHdri - 0.5f, &intCol);
1013 float factorRow = std::modf(rowHdri - 0.5f, &intRow);
1014 int low_idx_row =
static_cast<int>(intRow);
1015 int low_idx_column =
static_cast<int>(intCol);
1016 int high_idx_column;
1017 if (factorCol < 0.f) {
1020 high_idx_column = width - 1;
1021 }
else if (low_idx_column == width - 1) {
1023 high_idx_column = 0;
1025 high_idx_column = low_idx_column + 1;
1028 if (factorRow < 0.f || low_idx_row == height - 1) {
1029 high_idx_row = low_idx_row;
1032 high_idx_row = low_idx_row + 1;
1034 factorCol = std::abs(factorCol);
1035 factorRow = std::abs(factorRow);
1036 float f1 = (1 - factorRow) * (1 - factorCol);
1037 float f2 = factorRow * (1 - factorCol);
1038 float f3 = (1 - factorRow) * factorCol;
1039 float f4 = factorRow * factorCol;
1040 for (
int j = 0; j < 4; j++) {
1041 face[col * 4 + resolution * row * 4 + j] =
1042 imageDataRGBA32323232F[low_idx_column * 4 + width * low_idx_row * 4 + j] * f1 +
1043 imageDataRGBA32323232F[low_idx_column * 4 + width * high_idx_row * 4 + j] * f2 +
1044 imageDataRGBA32323232F[high_idx_column * 4 + width * low_idx_row * 4 + j] * f3 +
1045 imageDataRGBA32323232F[high_idx_column * 4 + width * high_idx_row * 4 + j] * f4;
1050 if (format != ImageFormat::RGBA32323232F) {
1054#ifdef SOURCEPP_BUILD_WITH_THREADS
1056 std::array<std::future<void>, 6> faceFutures{
1057 std::async(std::launch::async, faceExtraction, 0),
1058 std::async(std::launch::async, faceExtraction, 1),
1059 std::async(std::launch::async, faceExtraction, 2),
1060 std::async(std::launch::async, faceExtraction, 3),
1061 std::async(std::launch::async, faceExtraction, 4),
1062 std::async(std::launch::async, faceExtraction, 5),
1064 for (
auto& future : faceFutures) {
1078 if (imageData.empty() || format == ImageFormat::EMPTY) {
1081 std::vector<std::byte> out;
1082 auto stbWriteFunc = [](
void* out_,
void* data,
int size) {
1083 std::copy_n(
static_cast<std::byte*
>(data), size, std::back_inserter(*
static_cast<std::vector<std::byte>*
>(out_)));
1086 if (fileFormat == FileFormat::DEFAULT) {
1089 switch (fileFormat) {
1090 case FileFormat::PNG: {
1091 if (format == ImageFormat::RGB888) {
1092 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data(), 0);
1093 }
else if (format == ImageFormat::RGBA8888) {
1094 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), imageData.data(), 0);
1097 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data(), 0);
1100 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), rgba.data(), 0);
1104 case FileFormat::JPG: {
1105 if (format == ImageFormat::RGB888) {
1106 stbi_write_jpg_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data(), 95);
1109 stbi_write_jpg_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data(), 95);
1113 case FileFormat::BMP: {
1114 if (format == ImageFormat::RGB888) {
1115 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data());
1116 }
else if (format == ImageFormat::RGBA8888) {
1117 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), imageData.data());
1120 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data());
1123 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), rgba.data());
1127 case FileFormat::TGA: {
1128 if (format == ImageFormat::RGB888) {
1129 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data());
1130 }
else if (format == ImageFormat::RGBA8888) {
1131 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), imageData.data());
1134 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data());
1137 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), rgba.data());
1141 case FileFormat::WEBP: {
1143 WebPConfigInit(&config);
1144 WebPConfigPreset(&config, WEBP_PRESET_DRAWING, 75.f);
1145 WebPConfigLosslessPreset(&config, 6);
1148 if (!WebPPictureInit(&pic)) {
1152 pic.height = height;
1153 if (!WebPPictureAlloc(&pic)) {
1157 if (format == ImageFormat::RGB888) {
1158 WebPPictureImportRGB(&pic,
reinterpret_cast<const uint8_t*
>(imageData.data()),
static_cast<int>(width *
sizeof(
ImagePixel::RGB888)));
1159 }
else if (format == ImageFormat::RGBA8888) {
1160 WebPPictureImportRGBA(&pic,
reinterpret_cast<const uint8_t*
>(imageData.data()),
static_cast<int>(width *
sizeof(
ImagePixel::RGBA8888)));
1163 WebPPictureImportRGB(&pic,
reinterpret_cast<const uint8_t*
>(rgb.data()),
static_cast<int>(width *
sizeof(
ImagePixel::RGB888)));
1166 WebPPictureImportRGBA(&pic,
reinterpret_cast<const uint8_t*
>(rgba.data()),
static_cast<int>(width *
sizeof(
ImagePixel::RGBA8888)));
1169 WebPMemoryWriter writer;
1170 WebPMemoryWriterInit(&writer);
1171 pic.writer = &WebPMemoryWrite;
1172 pic.custom_ptr = &writer;
1174 int ok = WebPEncode(&config, &pic);
1175 WebPPictureFree(&pic);
1177 WebPMemoryWriterClear(&writer);
1181 if (writer.mem && writer.size) {
1182 out.resize(writer.size);
1183 std::memcpy(out.data(), writer.mem, writer.size);
1185 WebPMemoryWriterClear(&writer);
1188 case FileFormat::QOI: {
1189 qoi_desc descriptor{
1193 .colorspace = QOI_SRGB,
1195 void* qoiData =
nullptr;
1197 if (format == ImageFormat::RGB888) {
1198 descriptor.channels = 3;
1199 qoiData = qoi_encode(imageData.data(), &descriptor, &qoiDataLen);
1200 }
else if (format == ImageFormat::RGBA8888) {
1201 descriptor.channels = 4;
1202 qoiData = qoi_encode(imageData.data(), &descriptor, &qoiDataLen);
1205 descriptor.channels = 3;
1206 qoiData = qoi_encode(rgb.data(), &descriptor, &qoiDataLen);
1209 descriptor.channels = 4;
1210 qoiData = qoi_encode(rgba.data(), &descriptor, &qoiDataLen);
1212 if (qoiData && qoiDataLen) {
1213 out.resize(qoiDataLen);
1214 std::memcpy(out.data(), qoiData, qoiDataLen);
1219 case FileFormat::HDR: {
1220 if (format == ImageFormat::RGB323232F) {
1221 stbi_write_hdr_to_func(stbWriteFunc, &out, width, height,
ImageFormatDetails::bpp(ImageFormat::RGB323232F) / (8 *
sizeof(
float)),
reinterpret_cast<const float*
>(imageData.data()));
1224 stbi_write_hdr_to_func(stbWriteFunc, &out, width, height,
ImageFormatDetails::bpp(ImageFormat::RGB323232F) / (8 *
sizeof(
float)),
reinterpret_cast<const float*
>(hdr.data()));
1228 case FileFormat::EXR: {
1230 InitEXRHeader(&header);
1232 std::vector<std::byte> rawData;
1236 format = ImageFormat::RGBA32323232F;
1239 format = ImageFormat::RGB323232F;
1242 rawData = {imageData.begin(), imageData.end()};
1246 header.channels =
static_cast<EXRChannelInfo*
>(std::malloc(header.num_channels *
sizeof(EXRChannelInfo)));
1247 header.pixel_types =
static_cast<int*
>(malloc(header.num_channels *
sizeof(
int)));
1248 header.requested_pixel_types =
static_cast<int*
>(malloc(header.num_channels *
sizeof(
int)));
1250 switch (header.num_channels) {
1252 header.channels[0].name[0] =
'A';
1253 header.channels[1].name[0] =
'B';
1254 header.channels[2].name[0] =
'G';
1255 header.channels[3].name[0] =
'R';
1258 header.channels[0].name[0] =
'B';
1259 header.channels[1].name[0] =
'G';
1260 header.channels[2].name[0] =
'R';
1263 header.channels[0].name[0] =
'G';
1264 header.channels[1].name[0] =
'R';
1267 header.channels[0].name[0] =
'R';
1270 FreeEXRHeader(&header);
1273 for (
int i = 0; i < header.num_channels; i++) {
1274 header.channels[i].name[1] =
'\0';
1277 int pixelType = (
ImageFormatDetails::red(format) / 8) ==
sizeof(half) ? TINYEXR_PIXELTYPE_HALF : TINYEXR_PIXELTYPE_FLOAT;
1278 for (
int i = 0; i < header.num_channels; i++) {
1279 header.pixel_types[i] = pixelType;
1280 header.requested_pixel_types[i] = pixelType;
1283 std::vector<std::vector<std::byte>> images(header.num_channels);
1284 std::vector<void*> imagePtrs(header.num_channels);
1285 switch (header.num_channels) {
1287 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1300 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1302 FreeEXRHeader(&header);
1310 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1319 images[0] = rawData;
1322 FreeEXRHeader(&header);
1325 for (
int i = 0; i < header.num_channels; i++) {
1326 imagePtrs[i] = images[i].data();
1330 InitEXRImage(&image);
1331 image.width = width;
1332 image.height = height;
1333 image.images =
reinterpret_cast<unsigned char**
>(imagePtrs.data());
1334 image.num_channels = header.num_channels;
1336 unsigned char* data =
nullptr;
1337 const char* err =
nullptr;
1339 size_t size = SaveEXRImageToMemory(&image, &header, &data, &err);
1341 FreeEXRErrorMessage(err);
1342 FreeEXRHeader(&header);
1346 out = {
reinterpret_cast<std::byte*
>(data),
reinterpret_cast<std::byte*
>(data) + size};
1350 FreeEXRHeader(&header);
1353 case FileFormat::DEFAULT:
1362using stb_ptr = std::unique_ptr<T, void(*)(
void*)>;
1367 stbi_convert_iphone_png_to_rgb(
true);
1369 format = ImageFormat::EMPTY;
1376 if (EXRVersion version; ParseEXRVersionFromMemory(&version,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size()) == TINYEXR_SUCCESS) {
1377 if (version.multipart || version.non_image) {
1382 InitEXRHeader(&header);
1383 const char* err =
nullptr;
1384 if (ParseEXRHeaderFromMemory(&header, &version,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size(), &err) != TINYEXR_SUCCESS) {
1385 FreeEXRErrorMessage(err);
1390 if (header.num_channels < 1) {
1391 FreeEXRHeader(&header);
1396 std::unordered_map<std::string_view, int> channelIndices{{
"R", -1}, {
"G", -1}, {
"B", -1}, {
"A", -1}, {
"Y", -1}};
1400 auto channelType = header.pixel_types[0];
1401 for (
int i = 1; i < header.num_channels; i++) {
1403 if (header.pixel_types[i] > channelType && channelIndices.contains(header.channels[i].name)) {
1404 channelType = header.pixel_types[i];
1408 if (channelType == TINYEXR_PIXELTYPE_UINT) {
1409 channelType = TINYEXR_PIXELTYPE_HALF;
1413 for (
int i = 0; i < header.num_channels; i++) {
1414 if (channelIndices.contains(header.channels[i].name)) {
1415 channelIndices[header.channels[i].name] = i;
1418 if (channelIndices[
"Y"] >= 0) {
1419 if (channelIndices[
"A"] >= 0) {
1420 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::RGBA16161616F : ImageFormat::RGBA32323232F;
1422 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1424 channelType = TINYEXR_PIXELTYPE_FLOAT;
1426 format = ImageFormat::RGB323232F;
1428 channelIndices[
"R"] = channelIndices[
"Y"];
1429 channelIndices[
"G"] = channelIndices[
"Y"];
1430 channelIndices[
"B"] = channelIndices[
"Y"];
1431 }
else if (channelIndices[
"A"] >= 0) {
1432 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::RGBA16161616F : ImageFormat::RGBA32323232F;
1433 }
else if (channelIndices[
"B"] >= 0) {
1434 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1436 channelType = TINYEXR_PIXELTYPE_FLOAT;
1438 format = ImageFormat::RGB323232F;
1439 }
else if (channelIndices[
"G"] >= 0) {
1440 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::RG1616F : ImageFormat::RG3232F;
1441 }
else if (channelIndices[
"R"] >= 0) {
1442 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::R16F : ImageFormat::R32F;
1444 FreeEXRHeader(&header);
1449 for (
int i = 0; i < header.num_channels; i++) {
1450 if (header.pixel_types[i] != channelType && channelIndices.contains(header.channels[i].name)) {
1451 header.requested_pixel_types[i] = channelType;
1456 InitEXRImage(&image);
1457 if (LoadEXRImageFromMemory(&image, &header,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size(), &err) != TINYEXR_SUCCESS) {
1458 FreeEXRErrorMessage(err);
1459 FreeEXRHeader(&header);
1463 width = image.width;
1464 height = image.height;
1468 const auto populateBuffer = [
1476 r=channelIndices[
"R"],
1477 g=channelIndices[
"G"],
1478 b=channelIndices[
"B"],
1479 a=channelIndices[
"A"],
1483 const auto channelCount = hasRed + hasGreen + hasBlue + hasAlpha;
1484 std::span out{
reinterpret_cast<C*
>(combinedChannels.data()), combinedChannels.size() /
sizeof(C)};
1486 for (
int t = 0; t < image.num_tiles; t++) {
1487 auto** src =
reinterpret_cast<C**
>(image.tiles[t].images);
1488 for (
int j = 0; j < header.tile_size_y; j++) {
1489 for (
int i = 0; i < header.tile_size_x; i++) {
1490 const auto ii =
static_cast<uint64_t
>(image.tiles[t].offset_x) * header.tile_size_x + i;
1491 const auto jj =
static_cast<uint64_t
>(image.tiles[t].offset_y) * header.tile_size_y + j;
1492 const auto idx = ii + jj * image.width;
1494 if (ii >= image.width || jj >= image.height) {
1498 const auto srcIdx = j *
static_cast<uint64_t
>(header.tile_size_x) + i;
1499 if (r >= 0) out[idx * channelCount + 0] = src[r][srcIdx];
1500 else if (hasRed) out[idx * channelCount + 0] = 0.f;
1501 if (g >= 0) out[idx * channelCount + 1] = src[g][srcIdx];
1502 else if (hasGreen) out[idx * channelCount + 1] = 0.f;
1503 if (b >= 0) out[idx * channelCount + 2] = src[b][srcIdx];
1504 else if (hasBlue) out[idx * channelCount + 2] = 0.f;
1505 if (a >= 0) out[idx * channelCount + 3] = src[a][srcIdx];
1506 else if (hasAlpha) out[idx * channelCount + 3] = 1.f;
1511 auto** src =
reinterpret_cast<C**
>(image.images);
1512 for (uint64_t i = 0; i < width * height; i++) {
1513 if (r >= 0) out[i * channelCount + 0] = src[r][i];
1514 else if (hasRed) out[i * channelCount + 0] = 0.f;
1515 if (g >= 0) out[i * channelCount + 1] = src[g][i];
1516 else if (hasGreen) out[i * channelCount + 1] = 0.f;
1517 if (b >= 0) out[i * channelCount + 2] = src[b][i];
1518 else if (hasBlue) out[i * channelCount + 2] = 0.f;
1519 if (a >= 0) out[i * channelCount + 3] = src[a][i];
1520 else if (hasAlpha) out[i * channelCount + 3] = 1.f;
1524 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1525 populateBuffer.operator()<half>();
1527 populateBuffer.operator()<
float>();
1530 FreeEXRImage(&image);
1531 FreeEXRHeader(&header);
1532 return combinedChannels;
1536 if (stbi_is_hdr_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()))) {
1537 const ::stb_ptr<float> stbImage{
1538 stbi_loadf_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1545 case 1: format = ImageFormat::R32F;
break;
1546 case 2: format = ImageFormat::RG3232F;
break;
1547 case 3: format = ImageFormat::RGB323232F;
break;
1548 case 4: format = ImageFormat::RGBA32323232F;
break;
1555 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) {
1556 width = features.width;
1557 height = features.height;
1561 std::vector<std::byte> out;
1562 if (features.has_alpha) {
1563 format = ImageFormat::RGBA8888;
1565 if (!WebPDecodeRGBAInto(
reinterpret_cast<const uint8_t*
>(fileData.data()), fileData.size(),
reinterpret_cast<uint8_t*
>(out.data()), out.size(), width * (
ImageFormatDetails::bpp(format) / 8))) {
1569 format = ImageFormat::RGB888;
1571 if (!WebPDecodeRGBInto(
reinterpret_cast<const uint8_t*
>(fileData.data()), fileData.size(),
reinterpret_cast<uint8_t*
>(out.data()), out.size(), width * (
ImageFormatDetails::bpp(format) / 8))) {
1579 if (fileData.size() > 3 &&
static_cast<char>(fileData[0]) ==
'G' &&
static_cast<char>(fileData[1]) ==
'I' &&
static_cast<char>(fileData[2]) ==
'F') {
1580 const ::stb_ptr<stbi_uc> stbImage{
1581 stbi_load_gif_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()),
nullptr, &width, &height, &frameCount, &channels, 0),
1584 if (!stbImage || !frameCount) {
1588 case 1: format = ImageFormat::I8;
break;
1589 case 2: format = ImageFormat::UV88;
break;
1590 case 3: format = ImageFormat::RGB888;
break;
1591 case 4: format = ImageFormat::RGBA8888;
break;
1594 return {
reinterpret_cast<std::byte*
>(stbImage.get()),
reinterpret_cast<std::byte*
>(stbImage.get() + (
ImageFormatDetails::getDataLength(format, width, height) * frameCount))};
1600 stbi__start_mem(&s,
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()));
1601 if (stbi__png_test(&s)) {
1603 const auto apngDecoder = [&format, &width, &height, &frameCount]<
typename P>(
const auto& stbImage, std::size_t dirOffset) -> std::vector<std::byte> {
1604 auto* dir =
reinterpret_cast<stbi__apng_directory*
>(stbImage.get() + dirOffset);
1605 if (dir->type != STBI__STRUCTURE_TYPE_APNG_DIRECTORY) {
1610 frameCount =
static_cast<int>(dir->num_frames);
1612 static constexpr auto calcPixelOffset = [](uint32_t offsetX, uint32_t offsetY, uint32_t width) {
1613 return ((offsetY * width) + offsetX) *
sizeof(P);
1617 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) {
1618 for (uint32_t y = 0; y < srcHeight; y++) {
1620#ifdef SOURCEPP_BUILD_WITH_TBB
1621 std::execution::unseq,
1623 src.data() + calcPixelOffset( 0, y, srcWidth),
1624 src.data() + calcPixelOffset( srcWidth, y, srcWidth),
1625 dst.data() + calcPixelOffset(srcOffsetX, srcOffsetY + y, dstWidth));
1630 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) {
1631 for (uint32_t y = subOffsetY; y < subOffsetY + subHeight; y++) {
1633#ifdef SOURCEPP_BUILD_WITH_TBB
1634 std::execution::unseq,
1636 src.data() + calcPixelOffset(subOffsetX, y, imgWidth),
1637 src.data() + calcPixelOffset(subOffsetX + subWidth, y, imgWidth),
1638 dst.data() + calcPixelOffset(subOffsetX, y, imgWidth));
1642 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) {
1643 for (uint32_t y = 0; y < clrHeight; y++) {
1645#ifdef SOURCEPP_BUILD_WITH_TBB
1646 std::execution::unseq,
1648 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth),
1649 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth) + (clrWidth *
sizeof(P)),
1650 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth),
1651 [](std::byte) {
return std::byte{0}; });
1655 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) {
1656 for (uint32_t y = 0; y < srcHeight; y++) {
1657 const auto* sp =
reinterpret_cast<const uint8_t*
>(src.data() + calcPixelOffset(0, y, srcWidth));
1658 auto* dp =
reinterpret_cast<uint8_t*
>(dst.data() + calcPixelOffset(srcOffsetX, srcOffsetY + y, dstWidth));
1659 for (uint32_t x = 0; x < srcWidth; x++, sp += 4, dp += 4) {
1662 }
else if ((sp[3] == 0xff) || (dp[3] == 0)) {
1663 std::copy(sp, sp +
sizeof(P), dp);
1665 int u = sp[3] * 0xff;
1666 int v = (0xff - sp[3]) * dp[3];
1668 dp[0] = (sp[0] * u + dp[0] * v) / al;
1669 dp[1] = (sp[1] * u + dp[1] * v) / al;
1670 dp[2] = (sp[2] * u + dp[2] * v) / al;
1678 const uint64_t fullFrameSize =
sizeof(P) * width * height;
1679 uint64_t currentFrameSize = 0;
1680 std::vector<std::byte> out(fullFrameSize * frameCount);
1681 uint64_t srcFrameOffset = 0;
1682 uint64_t dstFrameOffset = 0;
1683 for (uint32_t i = 0; i < dir->num_frames; i++) {
1684 const auto& frame = dir->frames[i];
1685 currentFrameSize =
sizeof(P) * frame.width * frame.height;
1688 if (frame.width == width && frame.height == height && frame.x_offset == 0 && frame.y_offset == 0 && frame.blend_op == STBI_APNG_blend_op_source) {
1689 std::memcpy(out.data() + dstFrameOffset, stbImage.get() + srcFrameOffset, fullFrameSize);
1692 if (frame.blend_op == STBI_APNG_blend_op_source || (i == 0 && frame.blend_op == STBI_APNG_blend_op_over)) {
1693 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);
1694 }
else if (frame.blend_op == STBI_APNG_blend_op_over) {
1695 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);
1701 dstFrameOffset += fullFrameSize;
1702 srcFrameOffset += currentFrameSize;
1705 if (i == dir->num_frames - 1) {
1710 copyImageData({out.data() + dstFrameOffset, out.data() + dstFrameOffset + fullFrameSize}, width, height, {out.data() + dstFrameOffset - fullFrameSize, out.data() + dstFrameOffset}, width, height, 0, 0);
1713 if (frame.dispose_op == STBI_APNG_dispose_op_background || (i == 0 && frame.dispose_op == STBI_APNG_dispose_op_previous)) {
1714 clearImageData({out.data() + dstFrameOffset, out.data() + dstFrameOffset + fullFrameSize}, width, height, frame.width, frame.height, frame.x_offset, frame.y_offset);
1715 }
else if (frame.dispose_op == STBI_APNG_dispose_op_previous) {
1716 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);
1717 }
else if (frame.dispose_op != STBI_APNG_dispose_op_none) {
1724 static const char *dispose_ops[] = {
1725 "STBI_APNG_dispose_op_none",
1726 "STBI_APNG_dispose_op_background",
1727 "STBI_APNG_dispose_op_previous",
1730 static const char *blend_ops[] = {
1731 "STBI_APNG_blend_op_source",
1732 "STBI_APNG_blend_op_over",
1735 fprintf(stderr,
"dir_offset : %zu\n", dirOffset);
1736 fprintf(stderr,
"dir.type : %.*s\n", 4, (
unsigned char *) &dir->type);
1737 fprintf(stderr,
"dir.num_frames : %u\n", dir->num_frames);
1738 fprintf(stderr,
"dir.default_image_is_first_frame : %s\n",
1739 dir->default_image_is_first_frame ?
"yes" :
"no");
1740 fprintf(stderr,
"dir.num_plays : %u\n", dir->num_plays);
1742 for (
int i = 0; i < dir->num_frames; ++i) {
1743 stbi__apng_frame_directory_entry *frame = &dir->frames[i];
1745 fprintf(stderr,
"frame : %u\n", i);
1746 fprintf(stderr,
" width : %u\n", frame->width);
1747 fprintf(stderr,
" height : %u\n", frame->height);
1748 fprintf(stderr,
" x_offset : %u\n", frame->x_offset);
1749 fprintf(stderr,
" y_offset : %u\n", frame->y_offset);
1750 fprintf(stderr,
" delay_num : %u\n", frame->delay_num);
1751 fprintf(stderr,
" delay_den : %u\n", frame->delay_den);
1752 fprintf(stderr,
" dispose_op : %s\n", dispose_ops[frame->dispose_op]);
1753 fprintf(stderr,
" blend_op : %s\n", blend_ops[frame->blend_op]);
1759 std::size_t dirOffset = 0;
1760 if (stbi__png_is16(&s)) {
1761 const ::stb_ptr<stbi_us> stbImage{
1762 stbi__apng_load_16bit(&s, &width, &height, &channels, STBI_rgb_alpha, &dirOffset),
1765 if (stbImage && dirOffset) {
1769 const ::stb_ptr<stbi_uc> stbImage{
1770 stbi__apng_load_8bit(&s, &width, &height, &channels, STBI_rgb_alpha, &dirOffset),
1773 if (stbImage && dirOffset) {
1782 qoi_desc descriptor;
1783 const ::stb_ptr<std::byte> qoiImage{
1784 static_cast<std::byte*
>(qoi_decode(fileData.data(), fileData.size(), &descriptor, 0)),
1790 width = descriptor.width;
1791 height = descriptor.height;
1792 channels = descriptor.channels;
1794 case 3: format = ImageFormat::RGB888;
break;
1795 case 4: format = ImageFormat::RGBA8888;
break;
1802 if (stbi_is_16_bit_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()))) {
1803 const ::stb_ptr<stbi_us> stbImage{
1804 stbi_load_16_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1810 if (channels == 4) {
1811 format = ImageFormat::RGBA16161616;
1812 }
else if (channels >= 1 && channels < 4) {
1814 format = ImageFormat::RGBA16161616;
1819 std::span<uint16_t> inPixels{
reinterpret_cast<uint16_t*
>(stbImage.get()), outPixels.size()};
1821#ifdef SOURCEPP_BUILD_WITH_TBB
1822 std::execution::par_unseq,
1825 return {pixel, 0, 0, 0xffff};
1834 std::span<RG1616> inPixels{
reinterpret_cast<RG1616*
>(stbImage.get()), outPixels.size()};
1836#ifdef SOURCEPP_BUILD_WITH_TBB
1837 std::execution::par_unseq,
1840 return {pixel.r, pixel.g, 0, 0xffff};
1850 std::span<RGB161616> inPixels{
reinterpret_cast<RGB161616*
>(stbImage.get()), outPixels.size()};
1852#ifdef SOURCEPP_BUILD_WITH_TBB
1853 std::execution::par_unseq,
1856 return {pixel.r, pixel.g, pixel.b, 0xffff};
1870 const ::stb_ptr<stbi_uc> stbImage{
1871 stbi_load_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1878 case 1: format = ImageFormat::I8;
break;
1879 case 2: format = ImageFormat::UV88;
break;
1880 case 3: format = ImageFormat::RGB888;
break;
1881 case 4: format = ImageFormat::RGBA8888;
break;
1889 case ResizeMethod::NONE:
break;
1890 case ResizeMethod::POWER_OF_TWO_BIGGER:
return std::bit_ceil(n);
1891 case ResizeMethod::POWER_OF_TWO_SMALLER:
return std::bit_floor(n);
1903 if (imageData.empty() || format == ImageFormat::EMPTY) {
1907 STBIR_RESIZE resize;
1908 const auto setEdgeModesAndFiltersAndDoResize = [edge, filter, &resize] {
1909 stbir_set_edgemodes(&resize,
static_cast<stbir_edge
>(edge),
static_cast<stbir_edge
>(edge));
1911 case ResizeFilter::DEFAULT:
1912 case ResizeFilter::BOX:
1913 case ResizeFilter::BILINEAR:
1914 case ResizeFilter::CUBIC_BSPLINE:
1915 case ResizeFilter::CATMULL_ROM:
1916 case ResizeFilter::MITCHELL:
1917 case ResizeFilter::POINT_SAMPLE: {
1918 stbir_set_filters(&resize,
static_cast<stbir_filter
>(filter),
static_cast<stbir_filter
>(filter));
1921 case ResizeFilter::KAISER: {
1922 static constexpr auto KAISER_BETA = [](
float s) {
1931 static constexpr auto KAISER_FILTER = [](
float x,
float s,
void*) ->
float {
1932 if (x < -1.f || x > 1.f) {
1937 static constexpr auto KAISER_SUPPORT = [](
float s,
void*) ->
float {
1938 float baseSupport = KAISER_BETA(s) / 2.f;
1940 return std::max(2.f, baseSupport - 0.5f);
1942 return std::max(3.f, baseSupport);
1944 stbir_set_filter_callbacks(&resize, KAISER_FILTER, KAISER_SUPPORT, KAISER_FILTER, KAISER_SUPPORT);
1947 case ResizeFilter::NICE: {
1948 static constexpr auto SINC = [](
float x) ->
float {
1949 if (x == 0.f)
return 1.f;
1953 static constexpr auto NICE_FILTER = [](
float x, float,
void*) ->
float {
1954 if (x >= 3.f || x <= -3.f)
return 0.f;
1955 return SINC(x) * SINC(x / 3.f);
1959 static constexpr auto NICE_SUPPORT = [](
float invScale,
void*) ->
float {
1960 return invScale * 3.f;
1962 stbir_set_filter_callbacks(&resize, NICE_FILTER, NICE_SUPPORT, NICE_FILTER, NICE_SUPPORT);
1966 stbir_resize_extended(&resize);
1969 const auto pixelLayout = ::imageFormatToSTBIRPixelLayout(format);
1970 if (pixelLayout == -1) {
1974 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)));
1975 setEdgeModesAndFiltersAndDoResize();
1979 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)));
1980 setEdgeModesAndFiltersAndDoResize();
1984std::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) {
1985 if (imageData.empty() || format == ImageFormat::EMPTY) {
1990 return resizeImageData(imageData, format, width, widthOut, height, heightOut, srgb, filter, edge);
1994std::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) {
1995 if (imageData.empty() || format == ImageFormat::EMPTY || xOffset + newWidth >= width || yOffset + newHeight >= height) {
2001 return convertImageDataToFormat(
cropImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, newWidth, xOffset, height, newHeight, yOffset), container, format, newWidth, newHeight);
2005 std::vector<std::byte> out(pixelSize * newWidth * newHeight);
2006 for (uint16_t y = yOffset; y < yOffset + newHeight; y++) {
2007 std::memcpy(out.data() + (((y - yOffset) * newWidth) * pixelSize), imageData.data() + (((y * width) + xOffset) * pixelSize), newWidth * pixelSize);
2014 if (imageData.empty() || format == ImageFormat::EMPTY) {
2019 return {imageData.begin(), imageData.end()};
2024 return convertImageDataToFormat(
gammaCorrectImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, height, gamma), container, format, width, height);
2027 static constexpr auto calculateGammaLUT = [](
float gamma_, uint8_t channelSize) -> std::array<uint8_t, 256> {
2028 const auto maxSize =
static_cast<float>((1 << channelSize) - 1);
2029 std::array<uint8_t, 256> gammaLUT{};
2030 for (
int i = 0; i < gammaLUT.size(); i++) {
2031 gammaLUT[i] =
static_cast<uint8_t
>(std::clamp(std::pow((
static_cast<float>(i) + 0.5f) / maxSize, gamma_) * maxSize - 0.5f, 0.f, maxSize));
2036 #define VTFPP_CREATE_GAMMA_LUTS(InputType) \
2037 std::unordered_map<uint8_t, std::array<uint8_t, 256>> gammaLUTs; \
2038 if constexpr (ImageFormatDetails::red(ImageFormat::InputType) > 0) { \
2039 if (!gammaLUTs.contains(ImageFormatDetails::red(ImageFormat::InputType))) { \
2040 gammaLUTs[ImageFormatDetails::red(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::red(ImageFormat::InputType)); \
2043 if constexpr (ImageFormatDetails::green(ImageFormat::InputType) > 0) { \
2044 if (!gammaLUTs.contains(ImageFormatDetails::green(ImageFormat::InputType))) { \
2045 gammaLUTs[ImageFormatDetails::green(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::green(ImageFormat::InputType)); \
2048 if constexpr (ImageFormatDetails::blue(ImageFormat::InputType) > 0) { \
2049 if (!gammaLUTs.contains(ImageFormatDetails::blue(ImageFormat::InputType))) { \
2050 gammaLUTs[ImageFormatDetails::blue(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::blue(ImageFormat::InputType)); \
2054 #define VTFPP_APPLY_GAMMA_RED(value) \
2055 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::red(PIXEL_TYPE::FORMAT))[value])
2057 #define VTFPP_APPLY_GAMMA_GREEN(value) \
2058 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::green(PIXEL_TYPE::FORMAT))[value])
2060 #define VTFPP_APPLY_GAMMA_BLUE(value) \
2061 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::blue(PIXEL_TYPE::FORMAT))[value])
2063 std::vector<std::byte> out(imageData.size());
2065#ifdef SOURCEPP_BUILD_WITH_TBB
2066 #define VTFPP_GAMMA_CORRECT(InputType, ...) \
2067 std::span imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
2068 std::span outSpan{reinterpret_cast<ImagePixel::InputType*>(out.data()), out.size() / sizeof(ImagePixel::InputType)}; \
2069 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), outSpan.begin(), [gammaLUTs](ImagePixel::InputType pixel) -> ImagePixel::InputType { \
2070 using PIXEL_TYPE = ImagePixel::InputType; \
2071 return __VA_ARGS__; \
2074 #define VTFPP_GAMMA_CORRECT(InputType, ...) \
2075 std::span imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
2076 std::span outSpan{reinterpret_cast<ImagePixel::InputType*>(out.data()), out.size() / sizeof(ImagePixel::InputType)}; \
2077 std::transform(imageDataSpan.begin(), imageDataSpan.end(), outSpan.begin(), [gammaLUTs](ImagePixel::InputType pixel) -> ImagePixel::InputType { \
2078 using PIXEL_TYPE = ImagePixel::InputType; \
2079 return __VA_ARGS__; \
2082 #define VTFPP_CASE_GAMMA_CORRECT_AND_BREAK(InputType, ...) \
2083 case InputType: { VTFPP_CREATE_GAMMA_LUTS(InputType) VTFPP_GAMMA_CORRECT(InputType, __VA_ARGS__); } break
2089 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)});
2091 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)});
2107 #undef VTFPP_CASE_GAMMA_CORRECT_AND_BREAK
2108 #undef VTFPP_GAMMA_CORRECT
2109 #undef VTFPP_APPLY_GAMMA_BLUE
2110 #undef VTFPP_APPLY_GAMMA_GREEN
2111 #undef VTFPP_APPLY_GAMMA_RED
2112 #undef VTFPP_CREATE_GAMMA_LUTS
2125 return convertImageDataToFormat(
invertGreenChannelForImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, height), container, format, width, height);
2128 #define VTFPP_INVERT_GREEN(PixelType, ChannelName, ...) \
2129 static constexpr auto channelSize = ImageFormatDetails::green(ImagePixel::PixelType::FORMAT); \
2130 std::span imageDataSpan{reinterpret_cast<const ImagePixel::PixelType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::PixelType)}; \
2131 std::span outSpan{reinterpret_cast<ImagePixel::PixelType*>(out.data()), out.size() / sizeof(ImagePixel::PixelType)}; \
2132 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), outSpan.begin(), [](ImagePixel::PixelType pixel) -> ImagePixel::PixelType { \
2133 if constexpr (std::same_as<decltype(pixel.ChannelName), float> || std::same_as<decltype(pixel.ChannelName), half>) { \
2134 pixel.ChannelName = static_cast<decltype(pixel.ChannelName)>(static_cast<float>(static_cast<uint64_t>(1) << channelSize) - 1.f - static_cast<float>(pixel.ChannelName)); \
2136 if constexpr (channelSize >= sizeof(uint32_t) * 8) { \
2137 pixel.ChannelName = static_cast<decltype(pixel.ChannelName)>((static_cast<uint64_t>(1) << channelSize) - 1 - static_cast<uint32_t>(pixel.ChannelName)); \
2139 pixel.ChannelName = static_cast<decltype(pixel.ChannelName)>(static_cast<uint32_t>(1 << channelSize) - 1 - static_cast<uint32_t>(pixel.ChannelName)); \
2144#ifdef SOURCEPP_BUILD_WITH_TBB
2145 #define VTFPP_INVERT_GREEN_CASE(PixelType) \
2146 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, g, std::execution::par_unseq); break; }
2147 #define VTFPP_INVERT_GREEN_CASE_CA_OVERRIDE(PixelType, ChannelName) \
2148 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, ChannelName, std::execution::par_unseq); break; }
2150 #define VTFPP_INVERT_GREEN_CASE(PixelType) \
2151 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, g); } break
2152 #define VTFPP_INVERT_GREEN_CASE_CA_OVERRIDE(PixelType, ChannelName) \
2153 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, ChannelName); } break
2156 std::vector<std::byte> out(imageData.size());
2198 #undef VTFPP_INVERT_GREEN_CASE_CA_OVERRIDE
2199 #undef VTFPP_INVERT_GREEN_CASE
2200 #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.
uint16_t getResizedDim(uint16_t n, ResizeMethod method)
Get the new image size given a resize method.
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 > 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)
Converts several images from one format to another.
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 > convertImageDataToFormat(std::span< const std::byte > imageData, ImageFormat oldFormat, ImageFormat newFormat, uint16_t width, uint16_t height)
Converts an image from one format to another.
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