10#include <unordered_map>
12#ifdef SOURCEPP_BUILD_WITH_OPENCL
13#define CL_HPP_MINIMUM_OPENCL_VERSION 120
14#define CL_HPP_TARGET_OPENCL_VERSION 120
15#include <CL/opencl.hpp>
18#ifdef SOURCEPP_BUILD_WITH_TBB
22#include <Compressonator.h>
26#define STB_IMAGE_IMPLEMENTATION
27#define STB_IMAGE_STATIC
28#define STBI_NO_FAILURE_STRINGS
32#define STB_IMAGE_RESIZE_IMPLEMENTATION
33#define STB_IMAGE_RESIZE_STATIC
34#include <stb_image_resize2.h>
36#define STB_IMAGE_WRITE_IMPLEMENTATION
37#define STB_IMAGE_WRITE_STATIC
38#define STBI_WRITE_NO_STDIO
39#include <stb_image_write.h>
41#define TINYEXR_IMPLEMENTATION 1
42#ifdef SOURCEPP_BUILD_WITH_THREADS
43#define TINYEXR_USE_THREAD 1
45#define TINYEXR_USE_THREAD 0
54[[nodiscard]]
constexpr CMP_FORMAT imageFormatToCompressonatorFormat(
ImageFormat format) {
59 return CMP_FORMAT_RGBA_8888;
61 return CMP_FORMAT_ABGR_8888;
63 return CMP_FORMAT_RGB_888;
65 return CMP_FORMAT_BGR_888;
68 return CMP_FORMAT_R_8;
70 return CMP_FORMAT_ARGB_8888;
72 return CMP_FORMAT_BGRA_8888;
75 return CMP_FORMAT_DXT1;
77 return CMP_FORMAT_DXT3;
79 return CMP_FORMAT_DXT5;
81 return CMP_FORMAT_RG_8;
83 return CMP_FORMAT_R_16F;
85 return CMP_FORMAT_RG_16F;
87 return CMP_FORMAT_RGBA_16F;
89 return CMP_FORMAT_RGBA_16;
91 return CMP_FORMAT_R_32F;
93 return CMP_FORMAT_RG_32F;
95 return CMP_FORMAT_RGB_32F;
97 return CMP_FORMAT_RGBA_32F;
99 return CMP_FORMAT_ATI2N;
101 return CMP_FORMAT_ATI1N;
103 return CMP_FORMAT_RGBA_1010102;
105 return CMP_FORMAT_R_8;
107 return CMP_FORMAT_BC7;
109 return CMP_FORMAT_BC6H_SF;
136 return CMP_FORMAT_Unknown;
138 return CMP_FORMAT_Unknown;
141[[nodiscard]]
constexpr int imageFormatToSTBIRPixelLayout(
ImageFormat format) {
162 return STBIR_1CHANNEL;
170 return STBIR_2CHANNEL;
213[[nodiscard]]
constexpr int imageFormatToSTBIRDataType(
ImageFormat format,
bool srgb =
false) {
234 return srgb ? STBIR_TYPE_UINT8_SRGB : STBIR_TYPE_UINT8;
238 return STBIR_TYPE_HALF_FLOAT;
240 return STBIR_TYPE_UINT16;
245 return STBIR_TYPE_FLOAT;
279[[nodiscard]] std::vector<std::byte> convertImageDataUsingCompressonator(std::span<const std::byte> imageData,
ImageFormat oldFormat,
ImageFormat newFormat, uint16_t width, uint16_t height) {
280 if (imageData.empty()) {
284 CMP_Texture srcTexture{};
285 srcTexture.dwSize =
sizeof(srcTexture);
286 srcTexture.dwWidth = width;
287 srcTexture.dwHeight = height;
289 srcTexture.format = ::imageFormatToCompressonatorFormat(oldFormat);
290 srcTexture.dwDataSize = imageData.size();
292 srcTexture.pData =
const_cast<CMP_BYTE*
>(
reinterpret_cast<const CMP_BYTE*
>(imageData.data()));
294 CMP_Texture destTexture{};
295 destTexture.dwSize =
sizeof(destTexture);
296 destTexture.dwWidth = width;
297 destTexture.dwHeight = height;
299 destTexture.format = ::imageFormatToCompressonatorFormat(newFormat);
300 destTexture.dwDataSize = CMP_CalculateBufferSize(&destTexture);
302 std::vector<std::byte> destData;
303 destData.resize(destTexture.dwDataSize);
304 destTexture.pData =
reinterpret_cast<CMP_BYTE*
>(destData.data());
306 CMP_CompressOptions options{};
307 options.dwSize =
sizeof(options);
308 options.bDXT1UseAlpha = oldFormat == ImageFormat::DXT1_ONE_BIT_ALPHA || newFormat == ImageFormat::DXT1_ONE_BIT_ALPHA;
309 options.dwnumThreads = 0;
311 if (CMP_ConvertTexture(&srcTexture, &destTexture, &options,
nullptr) != CMP_OK) {
317[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA8888(std::span<const std::byte> imageData,
ImageFormat format) {
318 using namespace ImageConversion;
320 if (imageData.empty()) {
324 if (format == ImageFormat::RGBA8888 || format == ImageFormat::UVWQ8888) {
325 return {imageData.begin(), imageData.end()};
328 std::vector<std::byte> newData;
332 #define VTFPP_REMAP_TO_8(value, shift) math::remap<uint8_t>((value), (1 << (shift)) - 1, (1 << 8) - 1)
334 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
335 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
336 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA8888 { \
337 return {(r), (g), (b), (a)}; \
339#ifdef SOURCEPP_BUILD_WITH_TBB
340 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
342 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
344 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
345 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
384 #undef VTFPP_CASE_CONVERT_AND_BREAK
386 #undef VTFPP_CONVERT_DETAIL
387 #undef VTFPP_REMAP_TO_8
392[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888(std::span<const std::byte> imageData,
ImageFormat format) {
393 using namespace ImageConversion;
395 if (imageData.empty()) {
399 if (format == ImageFormat::RGBA8888) {
400 return {imageData.begin(), imageData.end()};
404 std::vector<std::byte> newData;
407 #define VTFPP_REMAP_FROM_8(value, shift) math::remap<uint8_t>((value), (1 << 8) - 1, (1 << (shift)) - 1)
409#ifdef SOURCEPP_BUILD_WITH_TBB
410 #define VTFPP_CONVERT(InputType, ...) \
411 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
412 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA8888 pixel) -> ImagePixel::InputType { \
413 return __VA_ARGS__; \
416 #define VTFPP_CONVERT(InputType, ...) \
417 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
418 std::transform(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA8888 pixel) -> ImagePixel::InputType { \
419 return __VA_ARGS__; \
422 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
423 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
462 #undef VTFPP_CASE_CONVERT_AND_BREAK
464 #undef VTFPP_REMAP_FROM_8
469[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA16161616(std::span<const std::byte> imageData,
ImageFormat format) {
470 using namespace ImageConversion;
472 if (imageData.empty()) {
476 if (format == ImageFormat::RGBA16161616) {
477 return {imageData.begin(), imageData.end()};
480 std::vector<std::byte> newData;
484 #define VTFPP_REMAP_TO_16(value, shift) math::remap<uint16_t>((value), (1 << (shift)) - 1, (1 << 16) - 1)
486 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
487 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
488 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA16161616 { \
489 return { static_cast<uint16_t>(r), static_cast<uint16_t>(g), static_cast<uint16_t>(b), static_cast<uint16_t>(a) }; \
491#ifdef SOURCEPP_BUILD_WITH_TBB
492 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
494 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
496 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
497 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
499 #define VTFPP_CONVERT_REMAP(InputType, r, g, b, a) \
501 if constexpr (ImageFormatDetails::alpha(ImageFormat::InputType) > 1) { \
502 VTFPP_CONVERT(InputType, \
503 VTFPP_REMAP_TO_16((r), ImageFormatDetails::red(ImageFormat::InputType)), \
504 VTFPP_REMAP_TO_16((g), ImageFormatDetails::green(ImageFormat::InputType)), \
505 VTFPP_REMAP_TO_16((b), ImageFormatDetails::blue(ImageFormat::InputType)), \
506 VTFPP_REMAP_TO_16((a), ImageFormatDetails::alpha(ImageFormat::InputType))); \
507 } else if constexpr (ImageFormatDetails::alpha(ImageFormat::InputType) == 1) { \
508 VTFPP_CONVERT(InputType, \
509 VTFPP_REMAP_TO_16((r), ImageFormatDetails::red(ImageFormat::InputType)), \
510 VTFPP_REMAP_TO_16((g), ImageFormatDetails::green(ImageFormat::InputType)), \
511 VTFPP_REMAP_TO_16((b), ImageFormatDetails::blue(ImageFormat::InputType)), \
514 VTFPP_CONVERT(InputType, \
515 VTFPP_REMAP_TO_16((r), ImageFormatDetails::red(ImageFormat::InputType)), \
516 VTFPP_REMAP_TO_16((g), ImageFormatDetails::green(ImageFormat::InputType)), \
517 VTFPP_REMAP_TO_16((b), ImageFormatDetails::blue(ImageFormat::InputType)), \
521 #define VTFPP_CASE_CONVERT_REMAP_AND_BREAK(InputType, r, g, b, a) \
522 case InputType: { VTFPP_CONVERT_REMAP(InputType, r, g, b, a); } \
533 #undef VTFPP_CASE_CONVERT_REMAP_AND_BREAK
534 #undef VTFPP_CONVERT_REMAP
535 #undef VTFPP_CASE_CONVERT_AND_BREAK
537 #undef VTFPP_CONVERT_DETAIL
538 #undef VTFPP_REMAP_TO_16
543[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616(std::span<const std::byte> imageData,
ImageFormat format) {
544 using namespace ImageConversion;
546 if (imageData.empty()) {
550 if (format == ImageFormat::RGBA16161616) {
551 return {imageData.begin(), imageData.end()};
555 std::vector<std::byte> newData;
558 #define VTFPP_REMAP_FROM_16(value, shift) static_cast<uint8_t>(math::remap<uint16_t>((value), (1 << 16) - 1, (1 << (shift)) - 1))
560#ifdef SOURCEPP_BUILD_WITH_TBB
561 #define VTFPP_CONVERT(InputType, ...) \
562 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
563 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA16161616 pixel) -> ImagePixel::InputType { \
564 return __VA_ARGS__; \
567 #define VTFPP_CONVERT(InputType, ...) \
568 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
569 std::transform(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA16161616 pixel) -> ImagePixel::InputType { \
570 return __VA_ARGS__; \
573 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
574 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
584 #undef VTFPP_CASE_CONVERT_AND_BREAK
586 #undef VTFPP_REMAP_FROM_16
591[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA32323232F(std::span<const std::byte> imageData,
ImageFormat format) {
592 if (imageData.empty()) {
596 if (format == ImageFormat::RGBA32323232F) {
597 return {imageData.begin(), imageData.end()};
600 std::vector<std::byte> newData;
604 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
605 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
606 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA32323232F { return {(r), (g), (b), (a)}; })
607#ifdef SOURCEPP_BUILD_WITH_TBB
608 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
610 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
612 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
613 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
626 #undef VTFPP_CASE_CONVERT_AND_BREAK
628 #undef VTFPP_CONVERT_DETAIL
633[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232F(std::span<const std::byte> imageData,
ImageFormat format) {
634 using namespace ImageConversion;
636 if (imageData.empty()) {
640 if (format == ImageFormat::RGBA32323232F) {
641 return {imageData.begin(), imageData.end()};
645 std::vector<std::byte> newData;
648#ifdef SOURCEPP_BUILD_WITH_TBB
649 #define VTFPP_CONVERT(InputType, ...) \
650 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
651 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA32323232F pixel) -> ImagePixel::InputType { \
652 return __VA_ARGS__; \
655 #define VTFPP_CONVERT(InputType, ...) \
656 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
657 std::transform(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA32323232F pixel) -> ImagePixel::InputType { \
658 return __VA_ARGS__; \
661 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
662 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
675 #undef VTFPP_CASE_CONVERT_AND_BREAK
681[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888ToRGBA32323232F(std::span<const std::byte> imageData) {
682 if (imageData.empty()) {
686 std::vector<std::byte> newData;
692#ifdef SOURCEPP_BUILD_WITH_TBB
693 std::execution::par_unseq,
697 static_cast<float>(pixel.r) / static_cast<float>((1 << 8) - 1),
698 static_cast<float>(pixel.g) / static_cast<float>((1 << 8) - 1),
699 static_cast<float>(pixel.b) / static_cast<float>((1 << 8) - 1),
700 static_cast<float>(pixel.a) / static_cast<float>((1 << 8) - 1),
707[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232FToRGBA8888(std::span<const std::byte> imageData) {
708 if (imageData.empty()) {
712 std::vector<std::byte> newData;
718#ifdef SOURCEPP_BUILD_WITH_TBB
719 std::execution::par_unseq,
723 static_cast<uint8_t>(std::clamp(pixel.r, 0.f, 1.f) * ((1 << 8) - 1)),
724 static_cast<uint8_t>(std::clamp(pixel.g, 0.f, 1.f) * ((1 << 8) - 1)),
725 static_cast<uint8_t>(std::clamp(pixel.b, 0.f, 1.f) * ((1 << 8) - 1)),
726 static_cast<uint8_t>(std::clamp(pixel.a, 0.f, 1.f) * ((1 << 8) - 1)),
733[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888ToRGBA16161616(std::span<const std::byte> imageData) {
734 if (imageData.empty()) {
738 std::vector<std::byte> newData;
744#ifdef SOURCEPP_BUILD_WITH_TBB
745 std::execution::par_unseq,
749 math::remap<uint16_t>(pixel.r, (1 << 8) - 1, (1 << 16) - 1),
750 math::remap<uint16_t>(pixel.g, (1 << 8) - 1, (1 << 16) - 1),
751 math::remap<uint16_t>(pixel.b, (1 << 8) - 1, (1 << 16) - 1),
752 math::remap<uint16_t>(pixel.a, (1 << 8) - 1, (1 << 16) - 1),
759[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616ToRGBA8888(std::span<const std::byte> imageData) {
760 if (imageData.empty()) {
764 std::vector<std::byte> newData;
770#ifdef SOURCEPP_BUILD_WITH_TBB
771 std::execution::par_unseq,
775 static_cast<uint8_t>(math::remap<uint16_t>(pixel.r, (1 << 16) - 1, (1 << 8) - 1)),
776 static_cast<uint8_t>(math::remap<uint16_t>(pixel.g, (1 << 16) - 1, (1 << 8) - 1)),
777 static_cast<uint8_t>(math::remap<uint16_t>(pixel.b, (1 << 16) - 1, (1 << 8) - 1)),
778 static_cast<uint8_t>(math::remap<uint16_t>(pixel.a, (1 << 16) - 1, (1 << 8) - 1)),
785[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232FToRGBA16161616(std::span<const std::byte> imageData) {
786 if (imageData.empty()) {
790 std::vector<std::byte> newData;
796#ifdef SOURCEPP_BUILD_WITH_TBB
797 std::execution::par_unseq,
801 static_cast<uint16_t>(std::clamp(pixel.r, 0.f, 1.f) * ((1 << 16) - 1)),
802 static_cast<uint16_t>(std::clamp(pixel.g, 0.f, 1.f) * ((1 << 16) - 1)),
803 static_cast<uint16_t>(std::clamp(pixel.b, 0.f, 1.f) * ((1 << 16) - 1)),
804 static_cast<uint16_t>(std::clamp(pixel.a, 0.f, 1.f) * ((1 << 16) - 1)),
811[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616ToRGBA32323232F(std::span<const std::byte> imageData) {
812 if (imageData.empty()) {
816 std::vector<std::byte> newData;
822#ifdef SOURCEPP_BUILD_WITH_TBB
823 std::execution::par_unseq,
827 static_cast<float>(pixel.r) / static_cast<float>((1 << 16) - 1),
828 static_cast<float>(pixel.g) / static_cast<float>((1 << 16) - 1),
829 static_cast<float>(pixel.b) / static_cast<float>((1 << 16) - 1),
830 static_cast<float>(pixel.a) / static_cast<float>((1 << 16) - 1),
837void convertHDRIToCubeMapCPUFallback(std::span<const float> imageDataRGBA32323232F,
ImageFormat outputFormat, uint16_t width, uint16_t height, uint16_t resolution,
bool bilinear,
const std::array<std::array<math::Vec3f, 3>, 6>& startRightUp, std::array<std::vector<std::byte>, 6>& faceData) {
838 for (
int i = 0; i < 6; i++) {
839 const math::Vec3f& start = startRightUp[i][0];
840 const math::Vec3f& right = startRightUp[i][1];
841 const math::Vec3f& up = startRightUp[i][2];
844 std::span<float> face{
reinterpret_cast<float*
>(faceData[i].data()),
reinterpret_cast<float*
>(faceData[i].data() + faceData[i].size())};
846 for (
int row = 0; row < resolution; row++) {
847 for (
int col = 0; col < resolution; col++) {
848 math::Vec3f pixelDirection3d{
849 start[0] + ((float) col * 2.f + 0.5f) / (float) resolution * right[0] + ((
float) row * 2.f + 0.5f) / (
float) resolution * up[0],
850 start[1] + ((float) col * 2.f + 0.5f) / (float) resolution * right[1] + ((
float) row * 2.f + 0.5f) / (
float) resolution * up[1],
851 start[2] + ((float) col * 2.f + 0.5f) / (float) resolution * right[2] + ((
float) row * 2.f + 0.5f) / (
float) resolution * up[2],
853 float azimuth = std::atan2(pixelDirection3d[0], -pixelDirection3d[2]) +
math::pi_f32;
854 float elevation = std::atan(pixelDirection3d[1] / std::sqrt(pixelDirection3d[0] * pixelDirection3d[0] + pixelDirection3d[2] * pixelDirection3d[2])) +
math::pi_f32 / 2.f;
855 float colHdri = (azimuth /
math::pi_f32 / 2.f) * (
float) width;
856 float rowHdri = (elevation /
math::pi_f32) * (
float) height;
858 int colNearest = std::clamp((
int) colHdri, 0, width - 1);
859 int rowNearest = std::clamp((
int) rowHdri, 0, height - 1);
860 face[col * 4 + resolution * row * 4 + 0] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 0];
861 face[col * 4 + resolution * row * 4 + 1] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 1];
862 face[col * 4 + resolution * row * 4 + 2] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 2];
863 face[col * 4 + resolution * row * 4 + 3] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 3];
865 float intCol, intRow;
867 float factorCol = std::modf(colHdri - 0.5f, &intCol);
868 float factorRow = std::modf(rowHdri - 0.5f, &intRow);
869 int low_idx_row =
static_cast<int>(intRow);
870 int low_idx_column =
static_cast<int>(intCol);
872 if (factorCol < 0.0f) {
875 high_idx_column = width - 1;
876 }
else if (low_idx_column == width - 1) {
880 high_idx_column = low_idx_column + 1;
883 if (factorRow < 0.0f) {
884 high_idx_row = height - 1;
885 }
else if (low_idx_row == height - 1) {
888 high_idx_row = low_idx_row + 1;
890 factorCol = std::abs(factorCol);
891 factorRow = std::abs(factorRow);
892 float f1 = (1 - factorRow) * (1 - factorCol);
893 float f2 = factorRow * (1 - factorCol);
894 float f3 = (1 - factorRow) * factorCol;
895 float f4 = factorRow * factorCol;
896 for (
int j = 0; j < 4; j++) {
897 auto interpolatedValue =
static_cast<uint8_t
>(
898 face[low_idx_column * 4 + width * low_idx_row * 4 + j] * f1 +
899 face[low_idx_column * 4 + width * high_idx_row * 4 + j] * f2 +
900 face[high_idx_column * 4 + width * low_idx_row * 4 + j] * f3 +
901 face[high_idx_column * 4 + width * high_idx_row * 4 + j] * f4
903 face[col * 4 + resolution * row * 4 + j] = std::clamp<uint8_t>(interpolatedValue, 0, 255);
908 if (outputFormat != ImageFormat::RGBA32323232F) {
917 if (imageData.empty() || oldFormat == ImageFormat::EMPTY) {
921 if (oldFormat == newFormat) {
922 return {imageData.begin(), imageData.end()};
925 std::vector<std::byte> newData;
929 newData = ::convertImageDataUsingCompressonator(imageData, oldFormat, intermediaryOldFormat, width, height);
931 switch (intermediaryOldFormat) {
932 case ImageFormat::RGBA8888: newData = ::convertImageDataToRGBA8888(imageData, oldFormat);
break;
933 case ImageFormat::RGBA16161616: newData = ::convertImageDataToRGBA16161616(imageData, oldFormat);
break;
934 case ImageFormat::RGBA32323232F: newData = ::convertImageDataToRGBA32323232F(imageData, oldFormat);
break;
939 if (intermediaryOldFormat == newFormat) {
944 if (intermediaryOldFormat != intermediaryNewFormat) {
945 if (intermediaryOldFormat == ImageFormat::RGBA8888) {
946 if (intermediaryNewFormat == ImageFormat::RGBA16161616) {
947 newData = ::convertImageDataFromRGBA8888ToRGBA16161616(newData);
948 }
else if (intermediaryNewFormat == ImageFormat::RGBA32323232F) {
949 newData = ::convertImageDataFromRGBA8888ToRGBA32323232F(newData);
953 }
else if (intermediaryOldFormat == ImageFormat::RGBA16161616) {
954 if (intermediaryNewFormat == ImageFormat::RGBA8888) {
955 newData = ::convertImageDataFromRGBA16161616ToRGBA8888(newData);
956 }
else if (intermediaryNewFormat == ImageFormat::RGBA32323232F) {
957 newData = ::convertImageDataFromRGBA16161616ToRGBA32323232F(newData);
961 }
else if (intermediaryOldFormat == ImageFormat::RGBA32323232F) {
962 if (intermediaryNewFormat == ImageFormat::RGBA8888) {
963 newData = ::convertImageDataFromRGBA32323232FToRGBA8888(newData);
964 }
else if (intermediaryNewFormat == ImageFormat::RGBA16161616) {
965 newData = ::convertImageDataFromRGBA32323232FToRGBA16161616(newData);
974 if (intermediaryNewFormat == newFormat) {
979 newData = ::convertImageDataUsingCompressonator(newData, intermediaryNewFormat, newFormat, width, height);
981 switch (intermediaryNewFormat) {
982 case ImageFormat::RGBA8888: newData = ::convertImageDataFromRGBA8888(newData, newFormat);
break;
983 case ImageFormat::RGBA16161616: newData = ::convertImageDataFromRGBA16161616(newData, newFormat);
break;
984 case ImageFormat::RGBA32323232F: newData = ::convertImageDataFromRGBA32323232F(newData, newFormat);
break;
993 if (imageData.empty() || oldFormat == ImageFormat::EMPTY) {
997 if (oldFormat == newFormat) {
998 return {imageData.begin(), imageData.end()};
1002 for(
int mip = mipCount - 1; mip >= 0; mip--) {
1003 for (
int frame = 0; frame < frameCount; frame++) {
1004 for (
int face = 0; face < faceCount; face++) {
1005 for (
int slice = 0; slice < sliceCount; slice++) {
1006 if (uint32_t oldOffset, oldLength;
ImageFormatDetails::getDataPosition(oldOffset, oldLength, oldFormat, mip, mipCount, frame, frameCount, face, faceCount, width, height, slice, sliceCount)) {
1008 if (uint32_t newOffset, newLength;
ImageFormatDetails::getDataPosition(newOffset, newLength, newFormat, mip, mipCount, frame, frameCount, face, faceCount, width, height, slice, sliceCount) && newLength == convertedImageData.size()) {
1009 std::memcpy(out.data() + newOffset, convertedImageData.data(), newLength);
1020 if (imageData.empty() || format == ImageFormat::EMPTY) {
1025 resolution = height;
1028 std::span<const float> imageDataRGBA32323232F{
reinterpret_cast<const float*
>(imageData.data()),
reinterpret_cast<const float*
>(imageData.data() + imageData.size())};
1030 std::vector<std::byte> possiblyConvertedDataOrEmptyDontUseMeDirectly;
1031 if (format != ImageFormat::RGBA32323232F) {
1032 possiblyConvertedDataOrEmptyDontUseMeDirectly =
convertImageDataToFormat(imageData, format, ImageFormat::RGBA32323232F, width, height);
1033 imageDataRGBA32323232F = {
reinterpret_cast<const float*
>(possiblyConvertedDataOrEmptyDontUseMeDirectly.data()),
reinterpret_cast<const float*
>(possiblyConvertedDataOrEmptyDontUseMeDirectly.data() + possiblyConvertedDataOrEmptyDontUseMeDirectly.size())};
1038 static constexpr std::array<std::array<math::Vec3f, 3>, 6> startRightUp = {{
1039 {{{-1.0f, -1.0f, -1.0f}, { 1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}}},
1040 {{{ 1.0f, -1.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}}},
1041 {{{-1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, {0.0f, 1.0f, 0.0f}}},
1042 {{{ 1.0f, -1.0f, -1.0f}, { 0.0f, 0.0f, 1.0f}, {0.0f, 1.0f, 0.0f}}},
1043 {{{-1.0f, 1.0f, -1.0f}, { 1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}},
1044 {{{-1.0f, -1.0f, 1.0f}, { 1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}}},
1047 std::array<std::vector<std::byte>, 6> faceData;
1049#ifdef SOURCEPP_BUILD_WITH_OPENCL
1050 std::vector<cl::Platform> platforms;
1051 if (cl::Platform::get(&platforms) != CL_SUCCESS || platforms.empty()) {
1052 ::convertHDRIToCubeMapCPUFallback(imageDataRGBA32323232F, format, width, height, resolution, bilinear, startRightUp, faceData);
1056 std::vector<cl::Device> devices;
1057 for (
const auto& platform : platforms) {
1058 if (platforms.front().getDevices(CL_DEVICE_TYPE_GPU, &devices) == CL_SUCCESS && !devices.empty()) {
1063 if (devices.empty()) {
1064 ::convertHDRIToCubeMapCPUFallback(imageDataRGBA32323232F, format, width, height, resolution, bilinear, startRightUp, faceData);
1067 const auto& device = devices.front();
1069 cl::Program::Sources sources{R
"(
1070__constant sampler_t sampler = CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP_TO_EDGE | VTFPP_FILTER;
1071__kernel void processFace(read_only image2d_t hdriImg, write_only image2d_t faceImg, __global float* startRightUp) {
1072 int2 pixelCoordFace = (int2)(get_global_id(0), get_global_id(1));
1073 int resolutionFace = get_global_size(0);
1074 float3 start = (float3)(startRightUp[0], startRightUp[1], startRightUp[2]);
1075 float3 right = (float3)(startRightUp[3], startRightUp[4], startRightUp[5]);
1076 float3 up = (float3)(startRightUp[6], startRightUp[7], startRightUp[8]);
1077 float3 direction = (float3)(
1078 start.x + (pixelCoordFace.x * 2.f + 0.5f)/(float)resolutionFace * right.x + (pixelCoordFace.y * 2.f + 0.5f)/(float)resolutionFace * up.x,
1079 start.y + (pixelCoordFace.x * 2.f + 0.5f)/(float)resolutionFace * right.y + (pixelCoordFace.y * 2.f + 0.5f)/(float)resolutionFace * up.y,
1080 start.z + (pixelCoordFace.x * 2.f + 0.5f)/(float)resolutionFace * right.z + (pixelCoordFace.y * 2.f + 0.5f)/(float)resolutionFace * up.z);
1081 float azimuth = atan2(direction.x, -direction.z) + radians(180.f);
1082 float elevation = atan(direction.y / sqrt(pow(direction.x, 2) + pow(direction.z, 2))) + radians(90.f);
1083 float2 pixelCoordHdri = (float2)(azimuth / radians(360.f) * get_image_width(hdriImg), elevation / radians(180.f) * get_image_height(hdriImg));
1084 uint4 pixel = read_imageui(hdriImg, sampler, pixelCoordHdri);
1085 write_imageui(faceImg, pixelCoordFace, pixel);
1087 cl::Context context{device};
1088 cl::Program program{context, sources};
1089 if (
int err = program.build(bilinear ?
"-cl-std=CL1.2 -DVTFPP_FILTER=CLK_FILTER_LINEAR" :
"-cl-std=CL1.2 -DVTFPP_FILTER=CLK_FILTER_NEAREST"); err != CL_SUCCESS) {
1091 if (err == CL_BUILD_PROGRAM_FAILURE) {
1092 const auto buildLog = program.getBuildInfo<CL_PROGRAM_BUILD_LOG>();
1096 ::convertHDRIToCubeMapCPUFallback(imageDataRGBA32323232F, format, width, height, resolution, bilinear, startRightUp, faceData);
1100 cl::ImageFormat clFormat{CL_RGBA, CL_FLOAT};
1101 cl::Image2D imgHdri{context, CL_MEM_READ_ONLY, clFormat, width, height};
1102 cl::Image2D imgFace{context, CL_MEM_WRITE_ONLY, clFormat, resolution, resolution};
1103 cl::CommandQueue queue{context, device};
1104 queue.enqueueWriteImage(imgHdri, CL_TRUE, {0, 0, 0}, {width, height, 1}, 0, 0, imageDataRGBA32323232F.data());
1105 cl::Buffer bufDirections(context, CL_MEM_READ_ONLY,
sizeof(
float) * 9);
1107 for (
int i = 0; i < 6; i++) {
1108 auto& face = faceData[i];
1111 cl::Kernel kernel{program,
"processFace"};
1112 kernel.setArg(0, imgHdri);
1113 kernel.setArg(1, imgFace);
1114 kernel.setArg(2, bufDirections);
1116 queue.enqueueWriteBuffer(bufDirections, CL_TRUE, 0, 9 *
sizeof(
float), &startRightUp[i][0]);
1117 queue.enqueueNDRangeKernel(kernel, cl::NullRange, cl::NDRange(resolution, resolution));
1120 std::array<std::size_t, 3> origin_out{0, 0, 0};
1121 std::array<std::size_t, 3> region_out{resolution, resolution, 1};
1122 queue.enqueueReadImage(imgFace, CL_TRUE, origin_out, region_out, 0, 0, face.data());
1125 if (format != ImageFormat::RGBA32323232F) {
1130 ::convertHDRIToCubeMapCPUFallback(imageDataRGBA32323232F, format, width, height, resolution, bilinear, startRightUp, faceData);
1141 if (imageData.empty() || format == ImageFormat::EMPTY) {
1144 std::vector<std::byte> out;
1145 auto stbWriteFunc = [](
void* out_,
void* data,
int size) {
1146 std::copy_n(
static_cast<std::byte*
>(data), size, std::back_inserter(*
static_cast<std::vector<std::byte>*
>(out_)));
1149 if (fileFormat == FileFormat::DEFAULT) {
1152 switch (fileFormat) {
1153 case FileFormat::PNG: {
1154 if (format == ImageFormat::RGB888) {
1155 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data(), 0);
1156 }
else if (format == ImageFormat::RGBA8888) {
1157 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), imageData.data(), 0);
1160 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data(), 0);
1163 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), rgba.data(), 0);
1167 case FileFormat::JPEG: {
1168 if (format == ImageFormat::RGB888) {
1169 stbi_write_jpg_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data(), 95);
1172 stbi_write_jpg_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data(), 95);
1176 case FileFormat::BMP: {
1177 if (format == ImageFormat::RGB888) {
1178 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data());
1179 }
else if (format == ImageFormat::RGBA8888) {
1180 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), imageData.data());
1183 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data());
1186 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), rgba.data());
1190 case FileFormat::TGA: {
1191 if (format == ImageFormat::RGB888) {
1192 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), imageData.data());
1193 }
else if (format == ImageFormat::RGBA8888) {
1194 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), imageData.data());
1197 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGB888), rgb.data());
1200 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(
ImagePixel::RGBA8888), rgba.data());
1204 case FileFormat::HDR: {
1205 if (format == ImageFormat::RGB323232F) {
1206 stbi_write_hdr_to_func(stbWriteFunc, &out, width, height,
ImageFormatDetails::bpp(ImageFormat::RGB323232F) / (8 *
sizeof(
float)),
reinterpret_cast<const float*
>(imageData.data()));
1209 stbi_write_hdr_to_func(stbWriteFunc, &out, width, height,
ImageFormatDetails::bpp(ImageFormat::RGB323232F) / (8 *
sizeof(
float)),
reinterpret_cast<const float*
>(hdr.data()));
1213 case FileFormat::EXR: {
1215 InitEXRHeader(&header);
1217 std::vector<std::byte> rawData;
1221 format = ImageFormat::RGBA32323232F;
1224 format = ImageFormat::RGB323232F;
1227 rawData = {imageData.begin(), imageData.end()};
1231 header.channels =
static_cast<EXRChannelInfo*
>(std::malloc(header.num_channels *
sizeof(EXRChannelInfo)));
1232 header.pixel_types =
static_cast<int*
>(malloc(header.num_channels *
sizeof(
int)));
1233 header.requested_pixel_types =
static_cast<int*
>(malloc(header.num_channels *
sizeof(
int)));
1235 switch (header.num_channels) {
1237 header.channels[0].name[0] =
'A';
1238 header.channels[1].name[0] =
'B';
1239 header.channels[2].name[0] =
'G';
1240 header.channels[3].name[0] =
'R';
1243 header.channels[0].name[0] =
'B';
1244 header.channels[1].name[0] =
'G';
1245 header.channels[2].name[0] =
'R';
1248 header.channels[0].name[0] =
'G';
1249 header.channels[1].name[0] =
'R';
1252 header.channels[0].name[0] =
'R';
1255 FreeEXRHeader(&header);
1258 for (
int i = 0; i < header.num_channels; i++) {
1259 header.channels[i].name[1] =
'\0';
1262 int pixelType = (
ImageFormatDetails::red(format) / 8) ==
sizeof(half) ? TINYEXR_PIXELTYPE_HALF : TINYEXR_PIXELTYPE_FLOAT;
1263 for (
int i = 0; i < header.num_channels; i++) {
1264 header.pixel_types[i] = pixelType;
1265 header.requested_pixel_types[i] = pixelType;
1268 std::vector<std::vector<std::byte>> images(header.num_channels);
1269 std::vector<void*> imagePtrs(header.num_channels);
1270 switch (header.num_channels) {
1272 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1285 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1287 FreeEXRHeader(&header);
1295 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1304 images[0] = rawData;
1307 FreeEXRHeader(&header);
1310 for (
int i = 0; i < header.num_channels; i++) {
1311 imagePtrs[i] = images[i].data();
1315 InitEXRImage(&image);
1316 image.width = width;
1317 image.height = height;
1318 image.images =
reinterpret_cast<unsigned char**
>(imagePtrs.data());
1319 image.num_channels = header.num_channels;
1321 unsigned char* data =
nullptr;
1322 const char* err =
nullptr;
1324 size_t size = SaveEXRImageToMemory(&image, &header, &data, &err);
1326 FreeEXRErrorMessage(err);
1327 FreeEXRHeader(&header);
1331 out = {
reinterpret_cast<std::byte*
>(data),
reinterpret_cast<std::byte*
>(data) + size};
1335 FreeEXRHeader(&header);
1338 case FileFormat::DEFAULT:
1347using stb_ptr = std::unique_ptr<T, void(*)(
void*)>;
1352 stbi_convert_iphone_png_to_rgb(
true);
1354 format = ImageFormat::EMPTY;
1361 if (EXRVersion version; ParseEXRVersionFromMemory(&version,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size()) == TINYEXR_SUCCESS) {
1362 if (version.multipart || version.non_image) {
1367 InitEXRHeader(&header);
1368 const char* err =
nullptr;
1369 if (ParseEXRHeaderFromMemory(&header, &version,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size(), &err) != TINYEXR_SUCCESS) {
1370 FreeEXRErrorMessage(err);
1375 if (header.num_channels < 1) {
1376 FreeEXRHeader(&header);
1381 std::unordered_map<std::string_view, int> channelIndices{{
"R", -1}, {
"G", -1}, {
"B", -1}, {
"A", -1}, {
"Y", -1}};
1385 auto channelType = header.pixel_types[0];
1386 for (
int i = 1; i < header.num_channels; i++) {
1388 if (header.pixel_types[i] > channelType && channelIndices.contains(header.channels[i].name)) {
1389 channelType = header.pixel_types[i];
1393 if (channelType == TINYEXR_PIXELTYPE_UINT) {
1394 channelType = TINYEXR_PIXELTYPE_HALF;
1398 for (
int i = 0; i < header.num_channels; i++) {
1399 if (channelIndices.contains(header.channels[i].name)) {
1400 channelIndices[header.channels[i].name] = i;
1403 if (channelIndices[
"Y"] >= 0) {
1404 if (channelIndices[
"A"] >= 0) {
1405 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::RGBA16161616F : ImageFormat::RGBA32323232F;
1407 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1409 channelType = TINYEXR_PIXELTYPE_FLOAT;
1411 format = ImageFormat::RGB323232F;
1413 channelIndices[
"R"] = channelIndices[
"Y"];
1414 channelIndices[
"G"] = channelIndices[
"Y"];
1415 channelIndices[
"B"] = channelIndices[
"Y"];
1416 }
else if (channelIndices[
"A"] >= 0) {
1417 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::RGBA16161616F : ImageFormat::RGBA32323232F;
1418 }
else if (channelIndices[
"B"] >= 0) {
1419 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1421 channelType = TINYEXR_PIXELTYPE_FLOAT;
1423 format = ImageFormat::RGB323232F;
1424 }
else if (channelIndices[
"G"] >= 0) {
1425 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::RG1616F : ImageFormat::RG3232F;
1426 }
else if (channelIndices[
"R"] >= 0) {
1427 format = channelType == TINYEXR_PIXELTYPE_HALF ? ImageFormat::R16F : ImageFormat::R32F;
1429 FreeEXRHeader(&header);
1434 for (
int i = 0; i < header.num_channels; i++) {
1435 if (header.pixel_types[i] != channelType && channelIndices.contains(header.channels[i].name)) {
1436 header.requested_pixel_types[i] = channelType;
1441 InitEXRImage(&image);
1442 if (LoadEXRImageFromMemory(&image, &header,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size(), &err) != TINYEXR_SUCCESS) {
1443 FreeEXRErrorMessage(err);
1444 FreeEXRHeader(&header);
1448 width = image.width;
1449 height = image.height;
1453 const auto populateBuffer = [
1461 r=channelIndices[
"R"],
1462 g=channelIndices[
"G"],
1463 b=channelIndices[
"B"],
1464 a=channelIndices[
"A"],
1468 const auto channelCount = hasRed + hasGreen + hasBlue + hasAlpha;
1469 std::span out{
reinterpret_cast<C*
>(combinedChannels.data()), combinedChannels.size() /
sizeof(C)};
1471 for (
int t = 0; t < image.num_tiles; t++) {
1472 auto** src =
reinterpret_cast<C**
>(image.tiles[t].images);
1473 for (
int j = 0; j < header.tile_size_y; j++) {
1474 for (
int i = 0; i < header.tile_size_x; i++) {
1475 const auto ii =
static_cast<uint64_t
>(image.tiles[t].offset_x) * header.tile_size_x + i;
1476 const auto jj =
static_cast<uint64_t
>(image.tiles[t].offset_y) * header.tile_size_y + j;
1477 const auto idx = ii + jj * image.width;
1479 if (ii >= image.width || jj >= image.height) {
1483 const auto srcIdx = j *
static_cast<uint64_t
>(header.tile_size_x) + i;
1484 if (r >= 0) out[idx * channelCount + 0] = src[r][srcIdx];
1485 else if (hasRed) out[idx * channelCount + 0] = 0.f;
1486 if (g >= 0) out[idx * channelCount + 1] = src[g][srcIdx];
1487 else if (hasGreen) out[idx * channelCount + 1] = 0.f;
1488 if (b >= 0) out[idx * channelCount + 2] = src[b][srcIdx];
1489 else if (hasBlue) out[idx * channelCount + 2] = 0.f;
1490 if (a >= 0) out[idx * channelCount + 3] = src[a][srcIdx];
1491 else if (hasAlpha) out[idx * channelCount + 3] = 1.f;
1496 auto** src =
reinterpret_cast<C**
>(image.images);
1497 for (uint64_t i = 0; i < width * height; i++) {
1498 if (r >= 0) out[i * channelCount + 0] = src[r][i];
1499 else if (hasRed) out[i * channelCount + 0] = 0.f;
1500 if (g >= 0) out[i * channelCount + 1] = src[g][i];
1501 else if (hasGreen) out[i * channelCount + 1] = 0.f;
1502 if (b >= 0) out[i * channelCount + 2] = src[b][i];
1503 else if (hasBlue) out[i * channelCount + 2] = 0.f;
1504 if (a >= 0) out[i * channelCount + 3] = src[a][i];
1505 else if (hasAlpha) out[i * channelCount + 3] = 1.f;
1509 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1510 populateBuffer.operator()<half>();
1512 populateBuffer.operator()<
float>();
1515 FreeEXRImage(&image);
1516 FreeEXRHeader(&header);
1517 return combinedChannels;
1521 if (stbi_is_hdr_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()))) {
1522 const ::stb_ptr<float> stbImage{
1523 stbi_loadf_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1530 case 1: format = ImageFormat::R32F;
break;
1531 case 2: format = ImageFormat::RG3232F;
break;
1532 case 3: format = ImageFormat::RGB323232F;
break;
1533 case 4: format = ImageFormat::RGBA32323232F;
break;
1540 if (fileData.size() >= 3 &&
static_cast<char>(fileData[0]) ==
'G' &&
static_cast<char>(fileData[1]) ==
'I' &&
static_cast<char>(fileData[2]) ==
'F') {
1541 const ::stb_ptr<stbi_uc> stbImage{
1542 stbi_load_gif_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()),
nullptr, &width, &height, &frameCount, &channels, 0),
1545 if (!stbImage || !frameCount) {
1549 case 1: format = ImageFormat::I8;
break;
1550 case 2: format = ImageFormat::UV88;
break;
1551 case 3: format = ImageFormat::RGB888;
break;
1552 case 4: format = ImageFormat::RGBA8888;
break;
1555 return {
reinterpret_cast<std::byte*
>(stbImage.get()),
reinterpret_cast<std::byte*
>(stbImage.get() + (
ImageFormatDetails::getDataLength(format, width, height) * frameCount))};
1561 stbi__start_mem(&s,
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()));
1562 if (stbi__png_test(&s)) {
1564 const auto apngDecoder = [&format, &width, &height, &frameCount]<
typename P>(
const auto& stbImage, std::size_t dirOffset) -> std::vector<std::byte> {
1565 auto* dir =
reinterpret_cast<stbi__apng_directory*
>(stbImage.get() + dirOffset);
1566 if (dir->type != STBI__STRUCTURE_TYPE_APNG_DIRECTORY) {
1571 frameCount =
static_cast<int>(dir->num_frames);
1573 static constexpr auto calcPixelOffset = [](uint32_t offsetX, uint32_t offsetY, uint32_t width) {
1574 return ((offsetY * width) + offsetX) *
sizeof(P);
1578 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) {
1579 for (uint32_t y = 0; y < srcHeight; y++) {
1581#ifdef SOURCEPP_BUILD_WITH_TBB
1582 std::execution::unseq,
1584 src.data() + calcPixelOffset( 0, y, srcWidth),
1585 src.data() + calcPixelOffset( srcWidth, y, srcWidth),
1586 dst.data() + calcPixelOffset(srcOffsetX, srcOffsetY + y, dstWidth));
1591 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) {
1592 for (uint32_t y = subOffsetY; y < subOffsetY + subHeight; y++) {
1594#ifdef SOURCEPP_BUILD_WITH_TBB
1595 std::execution::unseq,
1597 src.data() + calcPixelOffset(subOffsetX, y, imgWidth),
1598 src.data() + calcPixelOffset(subOffsetX + subWidth, y, imgWidth),
1599 dst.data() + calcPixelOffset(subOffsetX, y, imgWidth));
1603 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) {
1604 for (uint32_t y = 0; y < clrHeight; y++) {
1606#ifdef SOURCEPP_BUILD_WITH_TBB
1607 std::execution::unseq,
1609 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth),
1610 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth) + (clrWidth *
sizeof(P)),
1611 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth),
1612 [](std::byte) {
return std::byte{0}; });
1616 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) {
1617 for (uint32_t y = 0; y < srcHeight; y++) {
1618 const auto* sp =
reinterpret_cast<const uint8_t*
>(src.data() + calcPixelOffset(0, y, srcWidth));
1619 auto* dp =
reinterpret_cast<uint8_t*
>(dst.data() + calcPixelOffset(srcOffsetX, srcOffsetY + y, dstWidth));
1620 for (uint32_t x = 0; x < srcWidth; x++, sp += 4, dp += 4) {
1623 }
else if ((sp[3] == 0xff) || (dp[3] == 0)) {
1624 std::copy(sp, sp +
sizeof(P), dp);
1626 int u = sp[3] * 0xff;
1627 int v = (0xff - sp[3]) * dp[3];
1629 dp[0] = (sp[0] * u + dp[0] * v) / al;
1630 dp[1] = (sp[1] * u + dp[1] * v) / al;
1631 dp[2] = (sp[2] * u + dp[2] * v) / al;
1639 const uint64_t fullFrameSize =
sizeof(P) * width * height;
1640 uint64_t currentFrameSize = 0;
1641 std::vector<std::byte> out(fullFrameSize * frameCount);
1642 uint64_t srcFrameOffset = 0;
1643 uint64_t dstFrameOffset = 0;
1644 for (uint32_t i = 0; i < dir->num_frames; i++) {
1645 const auto& frame = dir->frames[i];
1646 currentFrameSize =
sizeof(P) * frame.width * frame.height;
1649 if (frame.width == width && frame.height == height && frame.x_offset == 0 && frame.y_offset == 0 && frame.blend_op == STBI_APNG_blend_op_source) {
1650 std::memcpy(out.data() + dstFrameOffset, stbImage.get() + srcFrameOffset, fullFrameSize);
1653 if (frame.blend_op == STBI_APNG_blend_op_source || (i == 0 && frame.blend_op == STBI_APNG_blend_op_over)) {
1654 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);
1655 }
else if (frame.blend_op == STBI_APNG_blend_op_over) {
1656 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);
1662 dstFrameOffset += fullFrameSize;
1663 srcFrameOffset += currentFrameSize;
1666 if (i == dir->num_frames - 1) {
1671 copyImageData({out.data() + dstFrameOffset, out.data() + dstFrameOffset + fullFrameSize}, width, height, {out.data() + dstFrameOffset - fullFrameSize, out.data() + dstFrameOffset}, width, height, 0, 0);
1674 if (frame.dispose_op == STBI_APNG_dispose_op_background || (i == 0 && frame.dispose_op == STBI_APNG_dispose_op_previous)) {
1675 clearImageData({out.data() + dstFrameOffset, out.data() + dstFrameOffset + fullFrameSize}, width, height, frame.width, frame.height, frame.x_offset, frame.y_offset);
1676 }
else if (frame.dispose_op == STBI_APNG_dispose_op_previous) {
1677 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);
1678 }
else if (frame.dispose_op != STBI_APNG_dispose_op_none) {
1685 static const char *dispose_ops[] = {
1686 "STBI_APNG_dispose_op_none",
1687 "STBI_APNG_dispose_op_background",
1688 "STBI_APNG_dispose_op_previous",
1691 static const char *blend_ops[] = {
1692 "STBI_APNG_blend_op_source",
1693 "STBI_APNG_blend_op_over",
1696 fprintf(stderr,
"dir_offset : %zu\n", dirOffset);
1697 fprintf(stderr,
"dir.type : %.*s\n", 4, (
unsigned char *) &dir->type);
1698 fprintf(stderr,
"dir.num_frames : %u\n", dir->num_frames);
1699 fprintf(stderr,
"dir.default_image_is_first_frame : %s\n",
1700 dir->default_image_is_first_frame ?
"yes" :
"no");
1701 fprintf(stderr,
"dir.num_plays : %u\n", dir->num_plays);
1703 for (
int i = 0; i < dir->num_frames; ++i) {
1704 stbi__apng_frame_directory_entry *frame = &dir->frames[i];
1706 fprintf(stderr,
"frame : %u\n", i);
1707 fprintf(stderr,
" width : %u\n", frame->width);
1708 fprintf(stderr,
" height : %u\n", frame->height);
1709 fprintf(stderr,
" x_offset : %u\n", frame->x_offset);
1710 fprintf(stderr,
" y_offset : %u\n", frame->y_offset);
1711 fprintf(stderr,
" delay_num : %u\n", frame->delay_num);
1712 fprintf(stderr,
" delay_den : %u\n", frame->delay_den);
1713 fprintf(stderr,
" dispose_op : %s\n", dispose_ops[frame->dispose_op]);
1714 fprintf(stderr,
" blend_op : %s\n", blend_ops[frame->blend_op]);
1720 std::size_t dirOffset = 0;
1721 if (stbi__png_is16(&s)) {
1722 const ::stb_ptr<stbi_us> stbImage{
1723 stbi__apng_load_16bit(&s, &width, &height, &channels, STBI_rgb_alpha, &dirOffset),
1726 if (stbImage && dirOffset) {
1730 const ::stb_ptr<stbi_uc> stbImage{
1731 stbi__apng_load_8bit(&s, &width, &height, &channels, STBI_rgb_alpha, &dirOffset),
1734 if (stbImage && dirOffset) {
1742 if (stbi_is_16_bit_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()))) {
1743 const ::stb_ptr<stbi_us> stbImage{
1744 stbi_load_16_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1750 if (channels == 4) {
1751 format = ImageFormat::RGBA16161616;
1752 }
else if (channels >= 1 && channels < 4) {
1754 format = ImageFormat::RGBA16161616;
1759 std::span<uint16_t> inPixels{
reinterpret_cast<uint16_t*
>(stbImage.get()), outPixels.size()};
1761#ifdef SOURCEPP_BUILD_WITH_TBB
1762 std::execution::par_unseq,
1765 return {pixel, 0, 0, 0xffff};
1774 std::span<RG1616> inPixels{
reinterpret_cast<RG1616*
>(stbImage.get()), outPixels.size()};
1776#ifdef SOURCEPP_BUILD_WITH_TBB
1777 std::execution::par_unseq,
1780 return {pixel.r, pixel.g, 0, 0xffff};
1790 std::span<RGB161616> inPixels{
reinterpret_cast<RGB161616*
>(stbImage.get()), outPixels.size()};
1792#ifdef SOURCEPP_BUILD_WITH_TBB
1793 std::execution::par_unseq,
1796 return {pixel.r, pixel.g, pixel.b, 0xffff};
1810 const ::stb_ptr<stbi_uc> stbImage{
1811 stbi_load_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1818 case 1: format = ImageFormat::I8;
break;
1819 case 2: format = ImageFormat::UV88;
break;
1820 case 3: format = ImageFormat::RGB888;
break;
1821 case 4: format = ImageFormat::RGBA8888;
break;
1829 case ResizeMethod::NONE:
break;
1830 case ResizeMethod::POWER_OF_TWO_BIGGER:
return std::bit_ceil(n);
1831 case ResizeMethod::POWER_OF_TWO_SMALLER:
return std::bit_floor(n);
1843 if (imageData.empty() || format == ImageFormat::EMPTY) {
1847 STBIR_RESIZE resize;
1848 const auto setEdgeModesAndFiltersAndDoResize = [edge, filter, &resize] {
1849 stbir_set_edgemodes(&resize,
static_cast<stbir_edge
>(edge),
static_cast<stbir_edge
>(edge));
1851 case ResizeFilter::DEFAULT:
1852 case ResizeFilter::BOX:
1853 case ResizeFilter::BILINEAR:
1854 case ResizeFilter::CUBIC_BSPLINE:
1855 case ResizeFilter::CATMULL_ROM:
1856 case ResizeFilter::MITCHELL:
1857 case ResizeFilter::POINT_SAMPLE: {
1858 stbir_set_filters(&resize,
static_cast<stbir_filter
>(filter),
static_cast<stbir_filter
>(filter));
1861 case ResizeFilter::KAISER: {
1862 static constexpr auto KAISER_BETA = [](
float s) {
1871 static constexpr auto KAISER_FILTER = [](
float x,
float s,
void*) ->
float {
1872 if (x < -1.f || x > 1.f) {
1877 static constexpr auto KAISER_SUPPORT = [](
float s,
void*) ->
float {
1878 float baseSupport = KAISER_BETA(s) / 2.f;
1880 return std::max(2.f, baseSupport - 0.5f);
1882 return std::max(3.f, baseSupport);
1884 stbir_set_filter_callbacks(&resize, KAISER_FILTER, KAISER_SUPPORT, KAISER_FILTER, KAISER_SUPPORT);
1888 stbir_resize_extended(&resize);
1891 const auto pixelLayout = ::imageFormatToSTBIRPixelLayout(format);
1892 if (pixelLayout == -1) {
1896 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)));
1897 setEdgeModesAndFiltersAndDoResize();
1901 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)));
1902 setEdgeModesAndFiltersAndDoResize();
1906std::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) {
1907 if (imageData.empty() || format == ImageFormat::EMPTY) {
1912 return resizeImageData(imageData, format, width, widthOut, height, heightOut, srgb, filter, edge);
1916std::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) {
1917 if (imageData.empty() || format == ImageFormat::EMPTY || xOffset + newWidth >= width || yOffset + newHeight >= height) {
1923 return convertImageDataToFormat(
cropImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, newWidth, xOffset, height, newHeight, yOffset), container, format, newWidth, newHeight);
1927 std::vector<std::byte> out(pixelSize * newWidth * newHeight);
1928 for (uint16_t y = yOffset; y < yOffset + newHeight; y++) {
1929 std::memcpy(out.data() + (((y - yOffset) * newWidth) * pixelSize), imageData.data() + (((y * width) + xOffset) * pixelSize), newWidth * pixelSize);
#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 SOURCEPP_DEBUG_BREAK
Create a breakpoint in debug.
constexpr T nearestPowerOf2(T n)
constexpr double kaiserWindow(double x, double b)
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.
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 > 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