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
58[[nodiscard]]
constexpr CMP_FORMAT imageFormatToCompressonatorFormat(
ImageFormat format) {
63 return CMP_FORMAT_RGBA_8888;
65 return CMP_FORMAT_ABGR_8888;
67 return CMP_FORMAT_RGB_888;
69 return CMP_FORMAT_BGR_888;
72 return CMP_FORMAT_R_8;
74 return CMP_FORMAT_ARGB_8888;
76 return CMP_FORMAT_BGRA_8888;
79 return CMP_FORMAT_DXT1;
81 return CMP_FORMAT_DXT3;
83 return CMP_FORMAT_DXT5;
85 return CMP_FORMAT_RG_8;
87 return CMP_FORMAT_R_16F;
89 return CMP_FORMAT_RG_16F;
91 return CMP_FORMAT_RGBA_16F;
93 return CMP_FORMAT_RGBA_16;
95 return CMP_FORMAT_R_32F;
97 return CMP_FORMAT_RG_32F;
99 return CMP_FORMAT_RGB_32F;
101 return CMP_FORMAT_RGBA_32F;
103 return CMP_FORMAT_ATI2N;
105 return CMP_FORMAT_ATI1N;
107 return CMP_FORMAT_RGBA_1010102;
109 return CMP_FORMAT_R_8;
111 return CMP_FORMAT_BC7;
113 return CMP_FORMAT_BC6H_SF;
140 return CMP_FORMAT_Unknown;
142 return CMP_FORMAT_Unknown;
145[[nodiscard]]
constexpr int imageFormatToSTBIRPixelLayout(
ImageFormat format) {
166 return STBIR_1CHANNEL;
174 return STBIR_2CHANNEL;
217[[nodiscard]]
constexpr int imageFormatToSTBIRDataType(
ImageFormat format,
bool srgb =
false) {
238 return srgb ? STBIR_TYPE_UINT8_SRGB : STBIR_TYPE_UINT8;
242 return STBIR_TYPE_HALF_FLOAT;
244 return STBIR_TYPE_UINT16;
249 return STBIR_TYPE_FLOAT;
283[[nodiscard]] std::vector<std::byte> convertImageDataUsingCompressonator(std::span<const std::byte> imageData,
ImageFormat oldFormat,
ImageFormat newFormat, uint16_t width, uint16_t height) {
284 if (imageData.empty()) {
288 CMP_Texture srcTexture{};
289 srcTexture.dwSize =
sizeof(srcTexture);
290 srcTexture.dwWidth = width;
291 srcTexture.dwHeight = height;
293 srcTexture.format = ::imageFormatToCompressonatorFormat(oldFormat);
294 srcTexture.dwDataSize = imageData.size();
296 srcTexture.pData =
const_cast<CMP_BYTE*
>(
reinterpret_cast<const CMP_BYTE*
>(imageData.data()));
298 CMP_Texture destTexture{};
299 destTexture.dwSize =
sizeof(destTexture);
300 destTexture.dwWidth = width;
301 destTexture.dwHeight = height;
303 destTexture.format = ::imageFormatToCompressonatorFormat(newFormat);
304 destTexture.dwDataSize = CMP_CalculateBufferSize(&destTexture);
306 std::vector<std::byte> destData;
307 destData.resize(destTexture.dwDataSize);
308 destTexture.pData =
reinterpret_cast<CMP_BYTE*
>(destData.data());
310 CMP_CompressOptions options{};
311 options.dwSize =
sizeof(options);
312 options.bDXT1UseAlpha = oldFormat == ImageFormat::DXT1_ONE_BIT_ALPHA || newFormat == ImageFormat::DXT1_ONE_BIT_ALPHA;
313 options.dwnumThreads = 0;
315 if (CMP_ConvertTexture(&srcTexture, &destTexture, &options,
nullptr) != CMP_OK) {
321[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA8888(std::span<const std::byte> imageData,
ImageFormat format) {
322 using namespace ImageConversion;
324 if (imageData.empty()) {
328 if (format == ImageFormat::RGBA8888 || format == ImageFormat::UVWQ8888) {
329 return {imageData.begin(), imageData.end()};
332 std::vector<std::byte> newData;
336 #define VTFPP_REMAP_TO_8(value, shift) math::remap<uint8_t>((value), (1 << (shift)) - 1, (1 << 8) - 1)
338 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
339 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
340 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA8888 { \
341 return {(r), (g), (b), (a)}; \
343#ifdef SOURCEPP_BUILD_WITH_TBB
344 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
346 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
348 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
349 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
388 #undef VTFPP_CASE_CONVERT_AND_BREAK
390 #undef VTFPP_CONVERT_DETAIL
391 #undef VTFPP_REMAP_TO_8
396[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888(std::span<const std::byte> imageData,
ImageFormat format) {
397 using namespace ImageConversion;
399 if (imageData.empty()) {
403 if (format == ImageFormat::RGBA8888) {
404 return {imageData.begin(), imageData.end()};
408 std::vector<std::byte> newData;
411 #define VTFPP_REMAP_FROM_8(value, shift) math::remap<uint8_t>((value), (1 << 8) - 1, (1 << (shift)) - 1)
413#ifdef SOURCEPP_BUILD_WITH_TBB
414 #define VTFPP_CONVERT(InputType, ...) \
415 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
416 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA8888 pixel) -> ImagePixel::InputType { \
417 return __VA_ARGS__; \
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(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA8888 pixel) -> ImagePixel::InputType { \
423 return __VA_ARGS__; \
426 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
427 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
466 #undef VTFPP_CASE_CONVERT_AND_BREAK
468 #undef VTFPP_REMAP_FROM_8
473[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA16161616(std::span<const std::byte> imageData,
ImageFormat format) {
474 using namespace ImageConversion;
476 if (imageData.empty()) {
480 if (format == ImageFormat::RGBA16161616) {
481 return {imageData.begin(), imageData.end()};
484 std::vector<std::byte> newData;
488 #define VTFPP_REMAP_TO_16(value, shift) math::remap<uint16_t>((value), (1 << (shift)) - 1, (1 << 16) - 1)
490 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
491 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
492 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA16161616 { \
493 return { static_cast<uint16_t>(r), static_cast<uint16_t>(g), static_cast<uint16_t>(b), static_cast<uint16_t>(a) }; \
495#ifdef SOURCEPP_BUILD_WITH_TBB
496 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
498 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
500 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
501 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
503 #define VTFPP_CONVERT_REMAP(InputType, r, g, b, a) \
505 if constexpr (ImageFormatDetails::alpha(ImageFormat::InputType) > 1) { \
506 VTFPP_CONVERT(InputType, \
507 VTFPP_REMAP_TO_16((r), ImageFormatDetails::red(ImageFormat::InputType)), \
508 VTFPP_REMAP_TO_16((g), ImageFormatDetails::green(ImageFormat::InputType)), \
509 VTFPP_REMAP_TO_16((b), ImageFormatDetails::blue(ImageFormat::InputType)), \
510 VTFPP_REMAP_TO_16((a), ImageFormatDetails::alpha(ImageFormat::InputType))); \
511 } else 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)), \
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)), \
525 #define VTFPP_CASE_CONVERT_REMAP_AND_BREAK(InputType, r, g, b, a) \
526 case InputType: { VTFPP_CONVERT_REMAP(InputType, r, g, b, a); } \
537 #undef VTFPP_CASE_CONVERT_REMAP_AND_BREAK
538 #undef VTFPP_CONVERT_REMAP
539 #undef VTFPP_CASE_CONVERT_AND_BREAK
541 #undef VTFPP_CONVERT_DETAIL
542 #undef VTFPP_REMAP_TO_16
547[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616(std::span<const std::byte> imageData,
ImageFormat format) {
548 using namespace ImageConversion;
550 if (imageData.empty()) {
554 if (format == ImageFormat::RGBA16161616) {
555 return {imageData.begin(), imageData.end()};
559 std::vector<std::byte> newData;
562 #define VTFPP_REMAP_FROM_16(value, shift) static_cast<uint8_t>(math::remap<uint16_t>((value), (1 << 16) - 1, (1 << (shift)) - 1))
564#ifdef SOURCEPP_BUILD_WITH_TBB
565 #define VTFPP_CONVERT(InputType, ...) \
566 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
567 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA16161616 pixel) -> ImagePixel::InputType { \
568 return __VA_ARGS__; \
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(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA16161616 pixel) -> ImagePixel::InputType { \
574 return __VA_ARGS__; \
577 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
578 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
588 #undef VTFPP_CASE_CONVERT_AND_BREAK
590 #undef VTFPP_REMAP_FROM_16
595[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA32323232F(std::span<const std::byte> imageData,
ImageFormat format) {
596 if (imageData.empty()) {
600 if (format == ImageFormat::RGBA32323232F) {
601 return {imageData.begin(), imageData.end()};
604 std::vector<std::byte> newData;
608 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
609 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
610 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA32323232F { return {(r), (g), (b), (a)}; })
611#ifdef SOURCEPP_BUILD_WITH_TBB
612 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
614 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
616 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
617 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
630 #undef VTFPP_CASE_CONVERT_AND_BREAK
632 #undef VTFPP_CONVERT_DETAIL
637[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232F(std::span<const std::byte> imageData,
ImageFormat format) {
638 using namespace ImageConversion;
640 if (imageData.empty()) {
644 if (format == ImageFormat::RGBA32323232F) {
645 return {imageData.begin(), imageData.end()};
649 std::vector<std::byte> newData;
652#ifdef SOURCEPP_BUILD_WITH_TBB
653 #define VTFPP_CONVERT(InputType, ...) \
654 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
655 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA32323232F pixel) -> ImagePixel::InputType { \
656 return __VA_ARGS__; \
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(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA32323232F pixel) -> ImagePixel::InputType { \
662 return __VA_ARGS__; \
665 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
666 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
679 #undef VTFPP_CASE_CONVERT_AND_BREAK
685[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888ToRGBA32323232F(std::span<const std::byte> imageData) {
686 if (imageData.empty()) {
690 std::vector<std::byte> newData;
696#ifdef SOURCEPP_BUILD_WITH_TBB
697 std::execution::par_unseq,
701 static_cast<float>(pixel.r) / static_cast<float>((1 << 8) - 1),
702 static_cast<float>(pixel.g) / static_cast<float>((1 << 8) - 1),
703 static_cast<float>(pixel.b) / static_cast<float>((1 << 8) - 1),
704 static_cast<float>(pixel.a) / static_cast<float>((1 << 8) - 1),
711[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232FToRGBA8888(std::span<const std::byte> imageData) {
712 if (imageData.empty()) {
716 std::vector<std::byte> newData;
722#ifdef SOURCEPP_BUILD_WITH_TBB
723 std::execution::par_unseq,
727 static_cast<uint8_t>(std::clamp(pixel.r, 0.f, 1.f) * ((1 << 8) - 1)),
728 static_cast<uint8_t>(std::clamp(pixel.g, 0.f, 1.f) * ((1 << 8) - 1)),
729 static_cast<uint8_t>(std::clamp(pixel.b, 0.f, 1.f) * ((1 << 8) - 1)),
730 static_cast<uint8_t>(std::clamp(pixel.a, 0.f, 1.f) * ((1 << 8) - 1)),
737[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888ToRGBA16161616(std::span<const std::byte> imageData) {
738 if (imageData.empty()) {
742 std::vector<std::byte> newData;
748#ifdef SOURCEPP_BUILD_WITH_TBB
749 std::execution::par_unseq,
753 math::remap<uint16_t>(pixel.r, (1 << 8) - 1, (1 << 16) - 1),
754 math::remap<uint16_t>(pixel.g, (1 << 8) - 1, (1 << 16) - 1),
755 math::remap<uint16_t>(pixel.b, (1 << 8) - 1, (1 << 16) - 1),
756 math::remap<uint16_t>(pixel.a, (1 << 8) - 1, (1 << 16) - 1),
763[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616ToRGBA8888(std::span<const std::byte> imageData) {
764 if (imageData.empty()) {
768 std::vector<std::byte> newData;
774#ifdef SOURCEPP_BUILD_WITH_TBB
775 std::execution::par_unseq,
779 static_cast<uint8_t>(math::remap<uint16_t>(pixel.r, (1 << 16) - 1, (1 << 8) - 1)),
780 static_cast<uint8_t>(math::remap<uint16_t>(pixel.g, (1 << 16) - 1, (1 << 8) - 1)),
781 static_cast<uint8_t>(math::remap<uint16_t>(pixel.b, (1 << 16) - 1, (1 << 8) - 1)),
782 static_cast<uint8_t>(math::remap<uint16_t>(pixel.a, (1 << 16) - 1, (1 << 8) - 1)),
789[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232FToRGBA16161616(std::span<const std::byte> imageData) {
790 if (imageData.empty()) {
794 std::vector<std::byte> newData;
800#ifdef SOURCEPP_BUILD_WITH_TBB
801 std::execution::par_unseq,
805 static_cast<uint16_t>(std::clamp(pixel.r, 0.f, 1.f) * ((1 << 16) - 1)),
806 static_cast<uint16_t>(std::clamp(pixel.g, 0.f, 1.f) * ((1 << 16) - 1)),
807 static_cast<uint16_t>(std::clamp(pixel.b, 0.f, 1.f) * ((1 << 16) - 1)),
808 static_cast<uint16_t>(std::clamp(pixel.a, 0.f, 1.f) * ((1 << 16) - 1)),
815[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616ToRGBA32323232F(std::span<const std::byte> imageData) {
816 if (imageData.empty()) {
820 std::vector<std::byte> newData;
826#ifdef SOURCEPP_BUILD_WITH_TBB
827 std::execution::par_unseq,
831 static_cast<float>(pixel.r) / static_cast<float>((1 << 16) - 1),
832 static_cast<float>(pixel.g) / static_cast<float>((1 << 16) - 1),
833 static_cast<float>(pixel.b) / static_cast<float>((1 << 16) - 1),
834 static_cast<float>(pixel.a) / static_cast<float>((1 << 16) - 1),
844 if (imageData.empty() || oldFormat == ImageFormat::EMPTY) {
848 if (oldFormat == newFormat) {
849 return {imageData.begin(), imageData.end()};
852 std::vector<std::byte> newData;
856 newData = ::convertImageDataUsingCompressonator(imageData, oldFormat, intermediaryOldFormat, width, height);
858 switch (intermediaryOldFormat) {
859 case ImageFormat::RGBA8888: newData = ::convertImageDataToRGBA8888(imageData, oldFormat);
break;
860 case ImageFormat::RGBA16161616: newData = ::convertImageDataToRGBA16161616(imageData, oldFormat);
break;
861 case ImageFormat::RGBA32323232F: newData = ::convertImageDataToRGBA32323232F(imageData, oldFormat);
break;
866 if (intermediaryOldFormat == newFormat) {
871 if (intermediaryOldFormat != intermediaryNewFormat) {
872 if (intermediaryOldFormat == ImageFormat::RGBA8888) {
873 if (intermediaryNewFormat == ImageFormat::RGBA16161616) {
874 newData = ::convertImageDataFromRGBA8888ToRGBA16161616(newData);
875 }
else if (intermediaryNewFormat == ImageFormat::RGBA32323232F) {
876 newData = ::convertImageDataFromRGBA8888ToRGBA32323232F(newData);
880 }
else if (intermediaryOldFormat == ImageFormat::RGBA16161616) {
881 if (intermediaryNewFormat == ImageFormat::RGBA8888) {
882 newData = ::convertImageDataFromRGBA16161616ToRGBA8888(newData);
883 }
else if (intermediaryNewFormat == ImageFormat::RGBA32323232F) {
884 newData = ::convertImageDataFromRGBA16161616ToRGBA32323232F(newData);
888 }
else if (intermediaryOldFormat == ImageFormat::RGBA32323232F) {
889 if (intermediaryNewFormat == ImageFormat::RGBA8888) {
890 newData = ::convertImageDataFromRGBA32323232FToRGBA8888(newData);
891 }
else if (intermediaryNewFormat == ImageFormat::RGBA16161616) {
892 newData = ::convertImageDataFromRGBA32323232FToRGBA16161616(newData);
901 if (intermediaryNewFormat == newFormat) {
906 newData = ::convertImageDataUsingCompressonator(newData, intermediaryNewFormat, newFormat, width, height);
908 switch (intermediaryNewFormat) {
909 case ImageFormat::RGBA8888: newData = ::convertImageDataFromRGBA8888(newData, newFormat);
break;
910 case ImageFormat::RGBA16161616: newData = ::convertImageDataFromRGBA16161616(newData, newFormat);
break;
911 case ImageFormat::RGBA32323232F: newData = ::convertImageDataFromRGBA32323232F(newData, newFormat);
break;
920 if (imageData.empty() || oldFormat == ImageFormat::EMPTY) {
924 if (oldFormat == newFormat) {
925 return {imageData.begin(), imageData.end()};
929 for(
int mip = mipCount - 1; mip >= 0; mip--) {
930 for (
int frame = 0; frame < frameCount; frame++) {
931 for (
int face = 0; face < faceCount; face++) {
932 for (
int slice = 0; slice < sliceCount; slice++) {
933 if (uint32_t oldOffset, oldLength;
ImageFormatDetails::getDataPosition(oldOffset, oldLength, oldFormat, mip, mipCount, frame, frameCount, face, faceCount, width, height, slice, sliceCount)) {
935 if (uint32_t newOffset, newLength;
ImageFormatDetails::getDataPosition(newOffset, newLength, newFormat, mip, mipCount, frame, frameCount, face, faceCount, width, height, slice, sliceCount) && newLength == convertedImageData.size()) {
936 std::memcpy(out.data() + newOffset, convertedImageData.data(), newLength);
947 if (imageData.empty() || format == ImageFormat::EMPTY) {
955 std::span<const float> imageDataRGBA32323232F{
reinterpret_cast<const float*
>(imageData.data()),
reinterpret_cast<const float*
>(imageData.data() + imageData.size())};
957 std::vector<std::byte> possiblyConvertedDataOrEmptyDontUseMeDirectly;
958 if (format != ImageFormat::RGBA32323232F) {
959 possiblyConvertedDataOrEmptyDontUseMeDirectly =
convertImageDataToFormat(imageData, format, ImageFormat::RGBA32323232F, width, height);
960 imageDataRGBA32323232F = {
reinterpret_cast<const float*
>(possiblyConvertedDataOrEmptyDontUseMeDirectly.data()),
reinterpret_cast<const float*
>(possiblyConvertedDataOrEmptyDontUseMeDirectly.data() + possiblyConvertedDataOrEmptyDontUseMeDirectly.size())};
965 static constexpr std::array<std::array<math::Vec3f, 3>, 6> startRightUp = {{
966 {{{ 1.0f, -1.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}},
967 {{{ 1.0f, 1.0f, 1.0f}, { 0.0f,-1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}},
968 {{{ 1.0f, 1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, { 0.0f,-1.0f, 0.0f}}},
969 {{{-1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}}},
970 {{{ 1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}}},
971 {{{ 1.0f, 1.0f, -1.0f}, { 0.0f, 0.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}},
974 std::array<std::vector<std::byte>, 6> faceData;
976#ifdef SOURCEPP_BUILD_WITH_THREADS
977 const auto faceExtraction = [&](
int i) {
979 for (
int i = 0; i < faceData.size(); i++) {
981 const auto start = startRightUp[i][0];
982 const auto right = startRightUp[i][1];
983 const auto up = startRightUp[i][2];
986 std::span<float> face{
reinterpret_cast<float*
>(faceData[i].data()),
reinterpret_cast<float*
>(faceData[i].data() + faceData[i].size())};
988 for (
int row = 0; row < resolution; row++) {
989 for (
int col = 0; col < resolution; col++) {
990 math::Vec3f pixelDirection3d{
991 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],
992 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],
993 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],
995 float azimuth = std::atan2(pixelDirection3d[0], -pixelDirection3d[2]) +
math::pi_f32;
996 float elevation = std::atan(pixelDirection3d[1] / std::sqrt(pixelDirection3d[0] * pixelDirection3d[0] + pixelDirection3d[2] * pixelDirection3d[2])) +
math::pi_f32 / 2.f;
997 float colHdri = (azimuth /
math::pi_f32 / 2.f) *
static_cast<float>(width);
998 float rowHdri = (elevation /
math::pi_f32) *
static_cast<float>(height);
1000 int colNearest = std::clamp(
static_cast<int>(colHdri), 0, width - 1);
1001 int rowNearest = std::clamp(
static_cast<int>(rowHdri), 0, height - 1);
1002 face[col * 4 + resolution * row * 4 + 0] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 0];
1003 face[col * 4 + resolution * row * 4 + 1] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 1];
1004 face[col * 4 + resolution * row * 4 + 2] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 2];
1005 face[col * 4 + resolution * row * 4 + 3] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 3];
1007 float intCol, intRow;
1009 float factorCol = std::modf(colHdri - 0.5f, &intCol);
1010 float factorRow = std::modf(rowHdri - 0.5f, &intRow);
1011 int low_idx_row =
static_cast<int>(intRow);
1012 int low_idx_column =
static_cast<int>(intCol);
1013 int high_idx_column;
1014 if (factorCol < 0.f) {
1017 high_idx_column = width - 1;
1018 }
else if (low_idx_column == width - 1) {
1020 high_idx_column = 0;
1022 high_idx_column = low_idx_column + 1;
1025 if (factorRow < 0.f || low_idx_row == height - 1) {
1026 high_idx_row = low_idx_row;
1029 high_idx_row = low_idx_row + 1;
1031 factorCol = std::abs(factorCol);
1032 factorRow = std::abs(factorRow);
1033 float f1 = (1 - factorRow) * (1 - factorCol);
1034 float f2 = factorRow * (1 - factorCol);
1035 float f3 = (1 - factorRow) * factorCol;
1036 float f4 = factorRow * factorCol;
1037 for (
int j = 0; j < 4; j++) {
1038 face[col * 4 + resolution * row * 4 + j] =
1039 imageDataRGBA32323232F[low_idx_column * 4 + width * low_idx_row * 4 + j] * f1 +
1040 imageDataRGBA32323232F[low_idx_column * 4 + width * high_idx_row * 4 + j] * f2 +
1041 imageDataRGBA32323232F[high_idx_column * 4 + width * low_idx_row * 4 + j] * f3 +
1042 imageDataRGBA32323232F[high_idx_column * 4 + width * high_idx_row * 4 + j] * f4;
1047 if (format != ImageFormat::RGBA32323232F) {
1051#ifdef SOURCEPP_BUILD_WITH_THREADS
1053 std::array<std::future<void>, 6> faceFutures{
1054 std::async(std::launch::async, faceExtraction, 0),
1055 std::async(std::launch::async, faceExtraction, 1),
1056 std::async(std::launch::async, faceExtraction, 2),
1057 std::async(std::launch::async, faceExtraction, 3),
1058 std::async(std::launch::async, faceExtraction, 4),
1059 std::async(std::launch::async, faceExtraction, 5),
1061 for (
auto& future : faceFutures) {
1075 if (imageData.empty() || format == ImageFormat::EMPTY) {
1078 std::vector<std::byte> out;
1079 auto stbWriteFunc = [](
void* out_,
void* data,
int size) {
1080 std::copy_n(
static_cast<std::byte*
>(data), size, std::back_inserter(*
static_cast<std::vector<std::byte>*
>(out_)));
1083 if (fileFormat == FileFormat::DEFAULT) {
1086 switch (fileFormat) {
1087 case FileFormat::PNG: {
1088 if (format == ImageFormat::RGB888) {
1089 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data(), 0);
1090 }
else if (format == ImageFormat::RGBA8888) {
1091 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), imageData.data(), 0);
1094 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data(), 0);
1097 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), rgba.data(), 0);
1101 case FileFormat::JPG: {
1102 if (format == ImageFormat::RGB888) {
1103 stbi_write_jpg_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data(), 95);
1106 stbi_write_jpg_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data(), 95);
1110 case FileFormat::BMP: {
1111 if (format == ImageFormat::RGB888) {
1112 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data());
1113 }
else if (format == ImageFormat::RGBA8888) {
1114 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), imageData.data());
1117 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data());
1120 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), rgba.data());
1124 case FileFormat::TGA: {
1125 if (format == ImageFormat::RGB888) {
1126 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data());
1127 }
else if (format == ImageFormat::RGBA8888) {
1128 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), imageData.data());
1131 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data());
1134 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), rgba.data());
1138 case FileFormat::QOI: {
1139 qoi_desc descriptor{
1143 .colorspace = QOI_SRGB,
1145 void* qoiData =
nullptr;
1147 if (format == ImageFormat::RGB888) {
1148 descriptor.channels = 3;
1149 qoiData = qoi_encode(imageData.data(), &descriptor, &qoiDataLen);
1150 }
else if (format == ImageFormat::RGBA8888) {
1151 descriptor.channels = 4;
1152 qoiData = qoi_encode(imageData.data(), &descriptor, &qoiDataLen);
1155 descriptor.channels = 3;
1156 qoiData = qoi_encode(rgb.data(), &descriptor, &qoiDataLen);
1159 descriptor.channels = 4;
1160 qoiData = qoi_encode(rgba.data(), &descriptor, &qoiDataLen);
1162 if (qoiData && qoiDataLen) {
1163 out.resize(qoiDataLen);
1164 std::memcpy(out.data(), qoiData, qoiDataLen);
1169 case FileFormat::HDR: {
1170 if (format == ImageFormat::RGB323232F) {
1171 stbi_write_hdr_to_func(stbWriteFunc, &out, width, height,
ImageFormatDetails::bpp(ImageFormat::RGB323232F) / (8 *
sizeof(
float)),
reinterpret_cast<const float*
>(imageData.data()));
1174 stbi_write_hdr_to_func(stbWriteFunc, &out, width, height,
ImageFormatDetails::bpp(ImageFormat::RGB323232F) / (8 *
sizeof(
float)),
reinterpret_cast<const float*
>(hdr.data()));
1178 case FileFormat::EXR: {
1180 InitEXRHeader(&header);
1182 std::vector<std::byte> rawData;
1186 format = ImageFormat::RGBA32323232F;
1189 format = ImageFormat::RGB323232F;
1192 rawData = {imageData.begin(), imageData.end()};
1196 header.channels =
static_cast<EXRChannelInfo*
>(std::malloc(header.num_channels *
sizeof(EXRChannelInfo)));
1197 header.pixel_types =
static_cast<int*
>(malloc(header.num_channels *
sizeof(
int)));
1198 header.requested_pixel_types =
static_cast<int*
>(malloc(header.num_channels *
sizeof(
int)));
1200 switch (header.num_channels) {
1202 header.channels[0].name[0] =
'A';
1203 header.channels[1].name[0] =
'B';
1204 header.channels[2].name[0] =
'G';
1205 header.channels[3].name[0] =
'R';
1208 header.channels[0].name[0] =
'B';
1209 header.channels[1].name[0] =
'G';
1210 header.channels[2].name[0] =
'R';
1213 header.channels[0].name[0] =
'G';
1214 header.channels[1].name[0] =
'R';
1217 header.channels[0].name[0] =
'R';
1220 FreeEXRHeader(&header);
1223 for (
int i = 0; i < header.num_channels; i++) {
1224 header.channels[i].name[1] =
'\0';
1227 int pixelType = (
ImageFormatDetails::red(format) / 8) ==
sizeof(half) ? TINYEXR_PIXELTYPE_HALF : TINYEXR_PIXELTYPE_FLOAT;
1228 for (
int i = 0; i < header.num_channels; i++) {
1229 header.pixel_types[i] = pixelType;
1230 header.requested_pixel_types[i] = pixelType;
1233 std::vector<std::vector<std::byte>> images(header.num_channels);
1234 std::vector<void*> imagePtrs(header.num_channels);
1235 switch (header.num_channels) {
1237 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1250 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1252 FreeEXRHeader(&header);
1260 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1269 images[0] = rawData;
1272 FreeEXRHeader(&header);
1275 for (
int i = 0; i < header.num_channels; i++) {
1276 imagePtrs[i] = images[i].data();
1280 InitEXRImage(&image);
1281 image.width = width;
1282 image.height = height;
1283 image.images =
reinterpret_cast<unsigned char**
>(imagePtrs.data());
1284 image.num_channels = header.num_channels;
1286 unsigned char* data =
nullptr;
1287 const char* err =
nullptr;
1289 size_t size = SaveEXRImageToMemory(&image, &header, &data, &err);
1291 FreeEXRErrorMessage(err);
1292 FreeEXRHeader(&header);
1296 out = {
reinterpret_cast<std::byte*
>(data),
reinterpret_cast<std::byte*
>(data) + size};
1300 FreeEXRHeader(&header);
1303 case FileFormat::DEFAULT:
1312using stb_ptr = std::unique_ptr<T, void(*)(
void*)>;
1317 stbi_convert_iphone_png_to_rgb(
true);
1319 format = ImageFormat::EMPTY;
1326 if (EXRVersion version; ParseEXRVersionFromMemory(&version,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size()) == TINYEXR_SUCCESS) {
1327 if (version.multipart || version.non_image) {
1332 InitEXRHeader(&header);
1333 const char* err =
nullptr;
1334 if (ParseEXRHeaderFromMemory(&header, &version,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size(), &err) != TINYEXR_SUCCESS) {
1335 FreeEXRErrorMessage(err);
1340 if (header.num_channels < 1) {
1341 FreeEXRHeader(&header);
1346 std::unordered_map<std::string_view, int> channelIndices{{
"R", -1}, {
"G", -1}, {
"B", -1}, {
"A", -1}, {
"Y", -1}};
1350 auto channelType = header.pixel_types[0];
1351 for (
int i = 1; i < header.num_channels; i++) {
1353 if (header.pixel_types[i] > channelType && channelIndices.contains(header.channels[i].name)) {
1354 channelType = header.pixel_types[i];
1358 if (channelType == TINYEXR_PIXELTYPE_UINT) {
1359 channelType = TINYEXR_PIXELTYPE_HALF;
1363 for (
int i = 0; i < header.num_channels; i++) {
1364 if (channelIndices.contains(header.channels[i].name)) {
1365 channelIndices[header.channels[i].name] = i;
1368 if (channelIndices[
"Y"] >= 0) {
1369 if (channelIndices[
"A"] >= 0) {
1370 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::RGBA16161616F : ImageFormat::RGBA32323232F;
1372 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1374 channelType = TINYEXR_PIXELTYPE_FLOAT;
1376 format = ImageFormat::RGB323232F;
1378 channelIndices[
"R"] = channelIndices[
"Y"];
1379 channelIndices[
"G"] = channelIndices[
"Y"];
1380 channelIndices[
"B"] = channelIndices[
"Y"];
1381 }
else if (channelIndices[
"A"] >= 0) {
1382 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::RGBA16161616F : ImageFormat::RGBA32323232F;
1383 }
else if (channelIndices[
"B"] >= 0) {
1384 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1386 channelType = TINYEXR_PIXELTYPE_FLOAT;
1388 format = ImageFormat::RGB323232F;
1389 }
else if (channelIndices[
"G"] >= 0) {
1390 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::RG1616F : ImageFormat::RG3232F;
1391 }
else if (channelIndices[
"R"] >= 0) {
1392 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::R16F : ImageFormat::R32F;
1394 FreeEXRHeader(&header);
1399 for (
int i = 0; i < header.num_channels; i++) {
1400 if (header.pixel_types[i] != channelType && channelIndices.contains(header.channels[i].name)) {
1401 header.requested_pixel_types[i] = channelType;
1406 InitEXRImage(&image);
1407 if (LoadEXRImageFromMemory(&image, &header,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size(), &err) != TINYEXR_SUCCESS) {
1408 FreeEXRErrorMessage(err);
1409 FreeEXRHeader(&header);
1413 width = image.width;
1414 height = image.height;
1418 const auto populateBuffer = [
1426 r=channelIndices[
"R"],
1427 g=channelIndices[
"G"],
1428 b=channelIndices[
"B"],
1429 a=channelIndices[
"A"],
1433 const auto channelCount = hasRed + hasGreen + hasBlue + hasAlpha;
1434 std::span out{
reinterpret_cast<C*
>(combinedChannels.data()), combinedChannels.size() /
sizeof(C)};
1436 for (
int t = 0; t < image.num_tiles; t++) {
1437 auto** src =
reinterpret_cast<C**
>(image.tiles[t].images);
1438 for (
int j = 0; j < header.tile_size_y; j++) {
1439 for (
int i = 0; i < header.tile_size_x; i++) {
1440 const auto ii =
static_cast<uint64_t
>(image.tiles[t].offset_x) * header.tile_size_x + i;
1441 const auto jj =
static_cast<uint64_t
>(image.tiles[t].offset_y) * header.tile_size_y + j;
1442 const auto idx = ii + jj * image.width;
1444 if (ii >= image.width || jj >= image.height) {
1448 const auto srcIdx = j *
static_cast<uint64_t
>(header.tile_size_x) + i;
1449 if (r >= 0) out[idx * channelCount + 0] = src[r][srcIdx];
1450 else if (hasRed) out[idx * channelCount + 0] = 0.f;
1451 if (g >= 0) out[idx * channelCount + 1] = src[g][srcIdx];
1452 else if (hasGreen) out[idx * channelCount + 1] = 0.f;
1453 if (b >= 0) out[idx * channelCount + 2] = src[b][srcIdx];
1454 else if (hasBlue) out[idx * channelCount + 2] = 0.f;
1455 if (a >= 0) out[idx * channelCount + 3] = src[a][srcIdx];
1456 else if (hasAlpha) out[idx * channelCount + 3] = 1.f;
1461 auto** src =
reinterpret_cast<C**
>(image.images);
1462 for (uint64_t i = 0; i < width * height; i++) {
1463 if (r >= 0) out[i * channelCount + 0] = src[r][i];
1464 else if (hasRed) out[i * channelCount + 0] = 0.f;
1465 if (g >= 0) out[i * channelCount + 1] = src[g][i];
1466 else if (hasGreen) out[i * channelCount + 1] = 0.f;
1467 if (b >= 0) out[i * channelCount + 2] = src[b][i];
1468 else if (hasBlue) out[i * channelCount + 2] = 0.f;
1469 if (a >= 0) out[i * channelCount + 3] = src[a][i];
1470 else if (hasAlpha) out[i * channelCount + 3] = 1.f;
1474 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1475 populateBuffer.operator()<half>();
1477 populateBuffer.operator()<
float>();
1480 FreeEXRImage(&image);
1481 FreeEXRHeader(&header);
1482 return combinedChannels;
1486 if (stbi_is_hdr_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()))) {
1487 const ::stb_ptr<float> stbImage{
1488 stbi_loadf_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1495 case 1: format = ImageFormat::R32F;
break;
1496 case 2: format = ImageFormat::RG3232F;
break;
1497 case 3: format = ImageFormat::RGB323232F;
break;
1498 case 4: format = ImageFormat::RGBA32323232F;
break;
1505 if (fileData.size() >= 3 &&
static_cast<char>(fileData[0]) ==
'G' &&
static_cast<char>(fileData[1]) ==
'I' &&
static_cast<char>(fileData[2]) ==
'F') {
1506 const ::stb_ptr<stbi_uc> stbImage{
1507 stbi_load_gif_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()),
nullptr, &width, &height, &frameCount, &channels, 0),
1510 if (!stbImage || !frameCount) {
1514 case 1: format = ImageFormat::I8;
break;
1515 case 2: format = ImageFormat::UV88;
break;
1516 case 3: format = ImageFormat::RGB888;
break;
1517 case 4: format = ImageFormat::RGBA8888;
break;
1520 return {
reinterpret_cast<std::byte*
>(stbImage.get()),
reinterpret_cast<std::byte*
>(stbImage.get() + (
ImageFormatDetails::getDataLength(format, width, height) * frameCount))};
1526 stbi__start_mem(&s,
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()));
1527 if (stbi__png_test(&s)) {
1529 const auto apngDecoder = [&format, &width, &height, &frameCount]<
typename P>(
const auto& stbImage, std::size_t dirOffset) -> std::vector<std::byte> {
1530 auto* dir =
reinterpret_cast<stbi__apng_directory*
>(stbImage.get() + dirOffset);
1531 if (dir->type != STBI__STRUCTURE_TYPE_APNG_DIRECTORY) {
1536 frameCount =
static_cast<int>(dir->num_frames);
1538 static constexpr auto calcPixelOffset = [](uint32_t offsetX, uint32_t offsetY, uint32_t width) {
1539 return ((offsetY * width) + offsetX) *
sizeof(P);
1543 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) {
1544 for (uint32_t y = 0; y < srcHeight; y++) {
1546#ifdef SOURCEPP_BUILD_WITH_TBB
1547 std::execution::unseq,
1549 src.data() + calcPixelOffset( 0, y, srcWidth),
1550 src.data() + calcPixelOffset( srcWidth, y, srcWidth),
1551 dst.data() + calcPixelOffset(srcOffsetX, srcOffsetY + y, dstWidth));
1556 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) {
1557 for (uint32_t y = subOffsetY; y < subOffsetY + subHeight; y++) {
1559#ifdef SOURCEPP_BUILD_WITH_TBB
1560 std::execution::unseq,
1562 src.data() + calcPixelOffset(subOffsetX, y, imgWidth),
1563 src.data() + calcPixelOffset(subOffsetX + subWidth, y, imgWidth),
1564 dst.data() + calcPixelOffset(subOffsetX, y, imgWidth));
1568 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) {
1569 for (uint32_t y = 0; y < clrHeight; y++) {
1571#ifdef SOURCEPP_BUILD_WITH_TBB
1572 std::execution::unseq,
1574 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth),
1575 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth) + (clrWidth *
sizeof(P)),
1576 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth),
1577 [](std::byte) {
return std::byte{0}; });
1581 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) {
1582 for (uint32_t y = 0; y < srcHeight; y++) {
1583 const auto* sp =
reinterpret_cast<const uint8_t*
>(src.data() + calcPixelOffset(0, y, srcWidth));
1584 auto* dp =
reinterpret_cast<uint8_t*
>(dst.data() + calcPixelOffset(srcOffsetX, srcOffsetY + y, dstWidth));
1585 for (uint32_t x = 0; x < srcWidth; x++, sp += 4, dp += 4) {
1588 }
else if ((sp[3] == 0xff) || (dp[3] == 0)) {
1589 std::copy(sp, sp +
sizeof(P), dp);
1591 int u = sp[3] * 0xff;
1592 int v = (0xff - sp[3]) * dp[3];
1594 dp[0] = (sp[0] * u + dp[0] * v) / al;
1595 dp[1] = (sp[1] * u + dp[1] * v) / al;
1596 dp[2] = (sp[2] * u + dp[2] * v) / al;
1604 const uint64_t fullFrameSize =
sizeof(P) * width * height;
1605 uint64_t currentFrameSize = 0;
1606 std::vector<std::byte> out(fullFrameSize * frameCount);
1607 uint64_t srcFrameOffset = 0;
1608 uint64_t dstFrameOffset = 0;
1609 for (uint32_t i = 0; i < dir->num_frames; i++) {
1610 const auto& frame = dir->frames[i];
1611 currentFrameSize =
sizeof(P) * frame.width * frame.height;
1614 if (frame.width == width && frame.height == height && frame.x_offset == 0 && frame.y_offset == 0 && frame.blend_op == STBI_APNG_blend_op_source) {
1615 std::memcpy(out.data() + dstFrameOffset, stbImage.get() + srcFrameOffset, fullFrameSize);
1618 if (frame.blend_op == STBI_APNG_blend_op_source || (i == 0 && frame.blend_op == STBI_APNG_blend_op_over)) {
1619 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);
1620 }
else if (frame.blend_op == STBI_APNG_blend_op_over) {
1621 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);
1627 dstFrameOffset += fullFrameSize;
1628 srcFrameOffset += currentFrameSize;
1631 if (i == dir->num_frames - 1) {
1636 copyImageData({out.data() + dstFrameOffset, out.data() + dstFrameOffset + fullFrameSize}, width, height, {out.data() + dstFrameOffset - fullFrameSize, out.data() + dstFrameOffset}, width, height, 0, 0);
1639 if (frame.dispose_op == STBI_APNG_dispose_op_background || (i == 0 && frame.dispose_op == STBI_APNG_dispose_op_previous)) {
1640 clearImageData({out.data() + dstFrameOffset, out.data() + dstFrameOffset + fullFrameSize}, width, height, frame.width, frame.height, frame.x_offset, frame.y_offset);
1641 }
else if (frame.dispose_op == STBI_APNG_dispose_op_previous) {
1642 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);
1643 }
else if (frame.dispose_op != STBI_APNG_dispose_op_none) {
1650 static const char *dispose_ops[] = {
1651 "STBI_APNG_dispose_op_none",
1652 "STBI_APNG_dispose_op_background",
1653 "STBI_APNG_dispose_op_previous",
1656 static const char *blend_ops[] = {
1657 "STBI_APNG_blend_op_source",
1658 "STBI_APNG_blend_op_over",
1661 fprintf(stderr,
"dir_offset : %zu\n", dirOffset);
1662 fprintf(stderr,
"dir.type : %.*s\n", 4, (
unsigned char *) &dir->type);
1663 fprintf(stderr,
"dir.num_frames : %u\n", dir->num_frames);
1664 fprintf(stderr,
"dir.default_image_is_first_frame : %s\n",
1665 dir->default_image_is_first_frame ?
"yes" :
"no");
1666 fprintf(stderr,
"dir.num_plays : %u\n", dir->num_plays);
1668 for (
int i = 0; i < dir->num_frames; ++i) {
1669 stbi__apng_frame_directory_entry *frame = &dir->frames[i];
1671 fprintf(stderr,
"frame : %u\n", i);
1672 fprintf(stderr,
" width : %u\n", frame->width);
1673 fprintf(stderr,
" height : %u\n", frame->height);
1674 fprintf(stderr,
" x_offset : %u\n", frame->x_offset);
1675 fprintf(stderr,
" y_offset : %u\n", frame->y_offset);
1676 fprintf(stderr,
" delay_num : %u\n", frame->delay_num);
1677 fprintf(stderr,
" delay_den : %u\n", frame->delay_den);
1678 fprintf(stderr,
" dispose_op : %s\n", dispose_ops[frame->dispose_op]);
1679 fprintf(stderr,
" blend_op : %s\n", blend_ops[frame->blend_op]);
1685 std::size_t dirOffset = 0;
1686 if (stbi__png_is16(&s)) {
1687 const ::stb_ptr<stbi_us> stbImage{
1688 stbi__apng_load_16bit(&s, &width, &height, &channels, STBI_rgb_alpha, &dirOffset),
1691 if (stbImage && dirOffset) {
1695 const ::stb_ptr<stbi_uc> stbImage{
1696 stbi__apng_load_8bit(&s, &width, &height, &channels, STBI_rgb_alpha, &dirOffset),
1699 if (stbImage && dirOffset) {
1708 qoi_desc descriptor;
1709 const ::stb_ptr<std::byte> qoiImage{
1710 static_cast<std::byte*
>(qoi_decode(fileData.data(), fileData.size(), &descriptor, 0)),
1716 width = descriptor.width;
1717 height = descriptor.height;
1718 channels = descriptor.channels;
1720 case 3: format = ImageFormat::RGB888;
break;
1721 case 4: format = ImageFormat::RGBA8888;
break;
1728 if (stbi_is_16_bit_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()))) {
1729 const ::stb_ptr<stbi_us> stbImage{
1730 stbi_load_16_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1736 if (channels == 4) {
1737 format = ImageFormat::RGBA16161616;
1738 }
else if (channels >= 1 && channels < 4) {
1740 format = ImageFormat::RGBA16161616;
1745 std::span<uint16_t> inPixels{
reinterpret_cast<uint16_t*
>(stbImage.get()), outPixels.size()};
1747#ifdef SOURCEPP_BUILD_WITH_TBB
1748 std::execution::par_unseq,
1751 return {pixel, 0, 0, 0xffff};
1760 std::span<RG1616> inPixels{
reinterpret_cast<RG1616*
>(stbImage.get()), outPixels.size()};
1762#ifdef SOURCEPP_BUILD_WITH_TBB
1763 std::execution::par_unseq,
1766 return {pixel.r, pixel.g, 0, 0xffff};
1776 std::span<RGB161616> inPixels{
reinterpret_cast<RGB161616*
>(stbImage.get()), outPixels.size()};
1778#ifdef SOURCEPP_BUILD_WITH_TBB
1779 std::execution::par_unseq,
1782 return {pixel.r, pixel.g, pixel.b, 0xffff};
1796 const ::stb_ptr<stbi_uc> stbImage{
1797 stbi_load_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1804 case 1: format = ImageFormat::I8;
break;
1805 case 2: format = ImageFormat::UV88;
break;
1806 case 3: format = ImageFormat::RGB888;
break;
1807 case 4: format = ImageFormat::RGBA8888;
break;
1815 case ResizeMethod::NONE:
break;
1816 case ResizeMethod::POWER_OF_TWO_BIGGER:
return std::bit_ceil(n);
1817 case ResizeMethod::POWER_OF_TWO_SMALLER:
return std::bit_floor(n);
1829 if (imageData.empty() || format == ImageFormat::EMPTY) {
1833 STBIR_RESIZE resize;
1834 const auto setEdgeModesAndFiltersAndDoResize = [edge, filter, &resize] {
1835 stbir_set_edgemodes(&resize,
static_cast<stbir_edge
>(edge),
static_cast<stbir_edge
>(edge));
1837 case ResizeFilter::DEFAULT:
1838 case ResizeFilter::BOX:
1839 case ResizeFilter::BILINEAR:
1840 case ResizeFilter::CUBIC_BSPLINE:
1841 case ResizeFilter::CATMULL_ROM:
1842 case ResizeFilter::MITCHELL:
1843 case ResizeFilter::POINT_SAMPLE: {
1844 stbir_set_filters(&resize,
static_cast<stbir_filter
>(filter),
static_cast<stbir_filter
>(filter));
1847 case ResizeFilter::KAISER: {
1848 static constexpr auto KAISER_BETA = [](
float s) {
1857 static constexpr auto KAISER_FILTER = [](
float x,
float s,
void*) ->
float {
1858 if (x < -1.f || x > 1.f) {
1863 static constexpr auto KAISER_SUPPORT = [](
float s,
void*) ->
float {
1864 float baseSupport = KAISER_BETA(s) / 2.f;
1866 return std::max(2.f, baseSupport - 0.5f);
1868 return std::max(3.f, baseSupport);
1870 stbir_set_filter_callbacks(&resize, KAISER_FILTER, KAISER_SUPPORT, KAISER_FILTER, KAISER_SUPPORT);
1873 case ResizeFilter::NICE: {
1874 static constexpr auto SINC = [](
float x) ->
float {
1875 if (x == 0.f)
return 1.f;
1879 static constexpr auto NICE_FILTER = [](
float x, float,
void*) ->
float {
1880 if (x >= 3.f || x <= -3.f)
return 0.f;
1881 return SINC(x) * SINC(x / 3.f);
1885 static constexpr auto NICE_SUPPORT = [](
float invScale,
void*) ->
float {
1886 return invScale * 3.f;
1888 stbir_set_filter_callbacks(&resize, NICE_FILTER, NICE_SUPPORT, NICE_FILTER, NICE_SUPPORT);
1892 stbir_resize_extended(&resize);
1895 const auto pixelLayout = ::imageFormatToSTBIRPixelLayout(format);
1896 if (pixelLayout == -1) {
1900 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)));
1901 setEdgeModesAndFiltersAndDoResize();
1905 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)));
1906 setEdgeModesAndFiltersAndDoResize();
1910std::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) {
1911 if (imageData.empty() || format == ImageFormat::EMPTY) {
1916 return resizeImageData(imageData, format, width, widthOut, height, heightOut, srgb, filter, edge);
1920std::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) {
1921 if (imageData.empty() || format == ImageFormat::EMPTY || xOffset + newWidth >= width || yOffset + newHeight >= height) {
1927 return convertImageDataToFormat(
cropImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, newWidth, xOffset, height, newHeight, yOffset), container, format, newWidth, newHeight);
1931 std::vector<std::byte> out(pixelSize * newWidth * newHeight);
1932 for (uint16_t y = yOffset; y < yOffset + newHeight; y++) {
1933 std::memcpy(out.data() + (((y - yOffset) * newWidth) * pixelSize), imageData.data() + (((y * width) + xOffset) * pixelSize), newWidth * pixelSize);
1940 if (imageData.empty() || format == ImageFormat::EMPTY) {
1945 return {imageData.begin(), imageData.end()};
1950 return convertImageDataToFormat(
gammaCorrectImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, height, gamma), container, format, width, height);
1953 static constexpr auto calculateGammaLUT = [](
float gamma_, uint8_t channelSize) -> std::array<uint8_t, 256> {
1954 const auto maxSize =
static_cast<float>((1 << channelSize) - 1);
1955 std::array<uint8_t, 256> gammaLUT{};
1956 for (
int i = 0; i < gammaLUT.size(); i++) {
1957 gammaLUT[i] =
static_cast<uint8_t
>(std::clamp(std::pow((
static_cast<float>(i) + 0.5f) / maxSize, gamma_) * maxSize - 0.5f, 0.f, maxSize));
1962 #define VTFPP_CREATE_GAMMA_LUTS(InputType) \
1963 std::unordered_map<uint8_t, std::array<uint8_t, 256>> gammaLUTs; \
1964 if constexpr (ImageFormatDetails::red(ImageFormat::InputType) > 0) { \
1965 if (!gammaLUTs.contains(ImageFormatDetails::red(ImageFormat::InputType))) { \
1966 gammaLUTs[ImageFormatDetails::red(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::red(ImageFormat::InputType)); \
1969 if constexpr (ImageFormatDetails::green(ImageFormat::InputType) > 0) { \
1970 if (!gammaLUTs.contains(ImageFormatDetails::green(ImageFormat::InputType))) { \
1971 gammaLUTs[ImageFormatDetails::green(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::green(ImageFormat::InputType)); \
1974 if constexpr (ImageFormatDetails::blue(ImageFormat::InputType) > 0) { \
1975 if (!gammaLUTs.contains(ImageFormatDetails::blue(ImageFormat::InputType))) { \
1976 gammaLUTs[ImageFormatDetails::blue(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::blue(ImageFormat::InputType)); \
1980 #define VTFPP_APPLY_GAMMA_RED(value) \
1981 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::red(PIXEL_TYPE::FORMAT))[value])
1983 #define VTFPP_APPLY_GAMMA_GREEN(value) \
1984 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::green(PIXEL_TYPE::FORMAT))[value])
1986 #define VTFPP_APPLY_GAMMA_BLUE(value) \
1987 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::blue(PIXEL_TYPE::FORMAT))[value])
1989 std::vector<std::byte> out(imageData.size());
1991#ifdef SOURCEPP_BUILD_WITH_TBB
1992 #define VTFPP_GAMMA_CORRECT(InputType, ...) \
1993 std::span imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
1994 std::span outSpan{reinterpret_cast<ImagePixel::InputType*>(out.data()), out.size() / sizeof(ImagePixel::InputType)}; \
1995 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), outSpan.begin(), [gammaLUTs](ImagePixel::InputType pixel) -> ImagePixel::InputType { \
1996 using PIXEL_TYPE = ImagePixel::InputType; \
1997 return __VA_ARGS__; \
2000 #define VTFPP_GAMMA_CORRECT(InputType, ...) \
2001 std::span imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
2002 std::span outSpan{reinterpret_cast<ImagePixel::InputType*>(out.data()), out.size() / sizeof(ImagePixel::InputType)}; \
2003 std::transform(imageDataSpan.begin(), imageDataSpan.end(), outSpan.begin(), [gammaLUTs](ImagePixel::InputType pixel) -> ImagePixel::InputType { \
2004 using PIXEL_TYPE = ImagePixel::InputType; \
2005 return __VA_ARGS__; \
2008 #define VTFPP_CASE_GAMMA_CORRECT_AND_BREAK(InputType, ...) \
2009 case InputType: { VTFPP_CREATE_GAMMA_LUTS(InputType) VTFPP_GAMMA_CORRECT(InputType, __VA_ARGS__); } break
2015 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)});
2017 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)});
2033 #undef VTFPP_CASE_GAMMA_CORRECT_AND_BREAK
2034 #undef VTFPP_GAMMA_CORRECT
2035 #undef VTFPP_APPLY_GAMMA_BLUE
2036 #undef VTFPP_APPLY_GAMMA_GREEN
2037 #undef VTFPP_APPLY_GAMMA_RED
2038 #undef VTFPP_CREATE_GAMMA_LUTS
2051 return convertImageDataToFormat(
invertGreenChannelForImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, height), container, format, width, height);
2054 #define VTFPP_INVERT_GREEN(PixelType, ChannelName, ...) \
2055 static constexpr auto channelSize = ImageFormatDetails::green(ImagePixel::PixelType::FORMAT); \
2056 std::span imageDataSpan{reinterpret_cast<const ImagePixel::PixelType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::PixelType)}; \
2057 std::span outSpan{reinterpret_cast<ImagePixel::PixelType*>(out.data()), out.size() / sizeof(ImagePixel::PixelType)}; \
2058 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), outSpan.begin(), [](ImagePixel::PixelType pixel) -> ImagePixel::PixelType { \
2059 if constexpr (std::same_as<decltype(pixel.ChannelName), float> || std::same_as<decltype(pixel.ChannelName), half>) { \
2060 pixel.ChannelName = static_cast<decltype(pixel.ChannelName)>(static_cast<float>(static_cast<uint64_t>(1) << channelSize) - 1.f - static_cast<float>(pixel.ChannelName)); \
2062 if constexpr (channelSize >= sizeof(uint32_t) * 8) { \
2063 pixel.ChannelName = static_cast<decltype(pixel.ChannelName)>((static_cast<uint64_t>(1) << channelSize) - 1 - static_cast<uint32_t>(pixel.ChannelName)); \
2065 pixel.ChannelName = static_cast<decltype(pixel.ChannelName)>(static_cast<uint32_t>(1 << channelSize) - 1 - static_cast<uint32_t>(pixel.ChannelName)); \
2070#ifdef SOURCEPP_BUILD_WITH_TBB
2071 #define VTFPP_INVERT_GREEN_CASE(PixelType) \
2072 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, g, std::execution::par_unseq); break; }
2073 #define VTFPP_INVERT_GREEN_CASE_CA_OVERRIDE(PixelType, ChannelName) \
2074 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, ChannelName, std::execution::par_unseq); break; }
2076 #define VTFPP_INVERT_GREEN_CASE(PixelType) \
2077 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, g); } break
2078 #define VTFPP_INVERT_GREEN_CASE_CA_OVERRIDE(PixelType, ChannelName) \
2079 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, ChannelName); } break
2082 std::vector<std::byte> out(imageData.size());
2124 #undef VTFPP_INVERT_GREEN_CASE_CA_OVERRIDE
2125 #undef VTFPP_INVERT_GREEN_CASE
2126 #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