16#include <unordered_map>
18#ifdef SOURCEPP_BUILD_WITH_TBB
22#ifdef SOURCEPP_BUILD_WITH_THREADS
26#include <compressonator.h>
28#ifdef VTFPP_SUPPORT_QOI
29#define QOI_IMPLEMENTATION
37#define STB_IMAGE_IMPLEMENTATION
38#define STB_IMAGE_STATIC
39#define STBI_NO_FAILURE_STRINGS
43#define STB_IMAGE_RESIZE_IMPLEMENTATION
44#define STB_IMAGE_RESIZE_STATIC
45#include <stb_image_resize2.h>
47#define STB_IMAGE_WRITE_IMPLEMENTATION
48#define STB_IMAGE_WRITE_STATIC
49#define STBI_WRITE_NO_STDIO
50#include <stb_image_write.h>
52#ifdef VTFPP_SUPPORT_EXR
58#ifdef VTFPP_SUPPORT_WEBP
59#include <webp/decode.h>
60#include <webp/encode.h>
68[[nodiscard]]
constexpr CMP_FORMAT imageFormatToCompressonatorFormat(
ImageFormat format) {
73 return CMP_FORMAT_RGBA_8888;
75 return CMP_FORMAT_ABGR_8888;
77 return CMP_FORMAT_RGB_888;
79 return CMP_FORMAT_BGR_888;
82 return CMP_FORMAT_R_8;
84 return CMP_FORMAT_ARGB_8888;
86 return CMP_FORMAT_BGRA_8888;
89 return CMP_FORMAT_DXT1;
91 return CMP_FORMAT_DXT3;
93 return CMP_FORMAT_DXT5;
95 return CMP_FORMAT_RG_8;
97 return CMP_FORMAT_R_16F;
99 return CMP_FORMAT_RG_16F;
101 return CMP_FORMAT_RGBA_16F;
103 return CMP_FORMAT_RGBA_16;
105 return CMP_FORMAT_R_32F;
107 return CMP_FORMAT_RG_32F;
109 return CMP_FORMAT_RGB_32F;
111 return CMP_FORMAT_RGBA_32F;
113 return CMP_FORMAT_ATI2N;
115 return CMP_FORMAT_ATI1N;
117 return CMP_FORMAT_RGBA_1010102;
119 return CMP_FORMAT_R_8;
121 return CMP_FORMAT_BC6H;
124 return CMP_FORMAT_BC7;
126 return CMP_FORMAT_BC6H_SF;
153 return CMP_FORMAT_Unknown;
155 return CMP_FORMAT_Unknown;
158[[nodiscard]]
constexpr int imageFormatToSTBIRPixelLayout(
ImageFormat format) {
179 return STBIR_1CHANNEL;
187 return STBIR_2CHANNEL;
232[[nodiscard]]
constexpr int imageFormatToSTBIRDataType(
ImageFormat format,
bool srgb =
false) {
253 return srgb ? STBIR_TYPE_UINT8_SRGB : STBIR_TYPE_UINT8;
257 return STBIR_TYPE_HALF_FLOAT;
259 return STBIR_TYPE_UINT16;
264 return STBIR_TYPE_FLOAT;
301 if (imageData.empty()) {
305 uint16_t unpaddedWidth = width, unpaddedHeight = height;
307 uint16_t paddingWidth = (4 - (width % 4)) % 4, paddingHeight = (4 - (height % 4)) % 4;
310 imageData = std::move(paddedImageData);
312 width += paddingWidth;
313 height += paddingHeight;
316 CMP_Texture srcTexture{};
317 srcTexture.dwSize =
sizeof(srcTexture);
318 srcTexture.dwWidth = width;
319 srcTexture.dwHeight = height;
321 srcTexture.format = ::imageFormatToCompressonatorFormat(oldFormat);
322 srcTexture.dwDataSize = imageData.size();
324 srcTexture.pData =
const_cast<CMP_BYTE*
>(
reinterpret_cast<const CMP_BYTE*
>(imageData.data()));
326 CMP_Texture destTexture{};
327 destTexture.dwSize =
sizeof(destTexture);
328 destTexture.dwWidth = width;
329 destTexture.dwHeight = height;
331 destTexture.format = ::imageFormatToCompressonatorFormat(newFormat);
332 destTexture.dwDataSize = CMP_CalculateBufferSize(&destTexture);
334 std::vector<std::byte> destData;
335 destData.resize(destTexture.dwDataSize);
336 destTexture.pData =
reinterpret_cast<CMP_BYTE*
>(destData.data());
338 CMP_CompressOptions options{};
339 options.dwSize =
sizeof(options);
340 if (quality >= 0.f) {
341 options.fquality = std::min(quality, 1.f);
347 options.fquality = 0.1f;
349 options.fquality = 1.f;
352 if (options.bDXT1UseAlpha) {
353 options.nAlphaThreshold = 128;
356 if (CMP_ConvertTexture(&srcTexture, &destTexture, &options,
nullptr) != CMP_OK) {
363 std::span destSpan{
reinterpret_cast<ImagePixel::RGBA8888*
>(destData.data()), destData.size() /
sizeof(ImagePixel::RGBA8888)};
365#ifdef SOURCEPP_BUILD_WITH_TBB
366 std::execution::par_unseq,
368 destSpan.begin(), destSpan.end(), destSpan.begin(), [](ImagePixel::RGBA8888 pixel) -> ImagePixel::RGBA8888 {
369 const auto nX = static_cast<float>(pixel.r()) / 255.f * 2.f - 1.f;
370 const auto nY = static_cast<float>(pixel.g()) / 255.f * 2.f - 1.f;
371 return {{pixel.r(), pixel.g(), static_cast<uint8_t>(std::clamp(1.f - (nX * nX) - (nY * nY), 0.f, 1.f) * 255.f), pixel.a()}};
374 if (unpaddedWidth % 4 != 0 || unpaddedHeight % 4 != 0) {
381[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA8888(std::span<const std::byte> imageData,
ImageFormat format) {
384 if (imageData.empty()) {
389 return {imageData.begin(), imageData.end()};
392 std::vector<std::byte> newData;
394 std::span newDataSpan{
reinterpret_cast<ImagePixel::RGBA8888*
>(newData.data()), newData.size() /
sizeof(ImagePixel::RGBA8888)};
396 #define VTFPP_REMAP_TO_8(value, shift) math::remap<uint8_t>((value), (1 << (shift)) - 1, (1 << 8) - 1)
398 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
399 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
400 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA8888 { \
401 return {{(r), (g), (b), (a)}}; \
403#ifdef SOURCEPP_BUILD_WITH_TBB
404 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
406 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
408 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
409 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
448 #undef VTFPP_CASE_CONVERT_AND_BREAK
450 #undef VTFPP_CONVERT_DETAIL
451 #undef VTFPP_REMAP_TO_8
456[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888(std::span<const std::byte> imageData,
ImageFormat format) {
459 if (imageData.empty()) {
464 return {imageData.begin(), imageData.end()};
467 std::span imageDataSpan{
reinterpret_cast<const ImagePixel::RGBA8888*
>(imageData.data()), imageData.size() /
sizeof(ImagePixel::RGBA8888)};
468 std::vector<std::byte> newData;
471 #define VTFPP_REMAP_FROM_8(value, shift) math::remap<uint8_t>((value), (1 << 8) - 1, (1 << (shift)) - 1)
473#ifdef SOURCEPP_BUILD_WITH_TBB
474 #define VTFPP_CONVERT(InputType, ...) \
475 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
476 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA8888 pixel) -> ImagePixel::InputType { \
477 return {__VA_ARGS__}; \
480 #define VTFPP_CONVERT(InputType, ...) \
481 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
482 std::transform(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA8888 pixel) -> ImagePixel::InputType { \
483 return {__VA_ARGS__}; \
486 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
487 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
498 VTFPP_CASE_CONVERT_AND_BREAK(
I8, {std::clamp<uint8_t>(0.299 * pixel.r() + 0.518 * pixel.g() + 0.183 * pixel.b(), std::numeric_limits<uint8_t>::min(), std::numeric_limits<uint8_t>::max())});
526 #undef VTFPP_CASE_CONVERT_AND_BREAK
528 #undef VTFPP_REMAP_FROM_8
533[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA16161616(std::span<const std::byte> imageData,
ImageFormat format) {
536 if (imageData.empty()) {
541 return {imageData.begin(), imageData.end()};
544 std::vector<std::byte> newData;
546 std::span newDataSpan{
reinterpret_cast<ImagePixel::RGBA16161616*
>(newData.data()), newData.size() /
sizeof(ImagePixel::RGBA16161616)};
548 #define VTFPP_REMAP_TO_16(value, shift) math::remap<uint16_t>((value), (1 << (shift)) - 1, (1 << 16) - 1)
550 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
551 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
552 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA16161616 { \
553 return {{static_cast<uint16_t>(r), static_cast<uint16_t>(g), static_cast<uint16_t>(b), static_cast<uint16_t>(a)}}; \
555#ifdef SOURCEPP_BUILD_WITH_TBB
556 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
558 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
560 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
561 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
563 #define VTFPP_CONVERT_REMAP(InputType, r, g, b, a) \
565 if constexpr (ImageFormatDetails::alpha(ImageFormat::InputType) > 1) { \
566 VTFPP_CONVERT(InputType, \
567 VTFPP_REMAP_TO_16((r), ImageFormatDetails::red(ImageFormat::InputType)), \
568 VTFPP_REMAP_TO_16((g), ImageFormatDetails::green(ImageFormat::InputType)), \
569 VTFPP_REMAP_TO_16((b), ImageFormatDetails::blue(ImageFormat::InputType)), \
570 VTFPP_REMAP_TO_16((a), ImageFormatDetails::alpha(ImageFormat::InputType))); \
571 } else if constexpr (ImageFormatDetails::alpha(ImageFormat::InputType) == 1) { \
572 VTFPP_CONVERT(InputType, \
573 VTFPP_REMAP_TO_16((r), ImageFormatDetails::red(ImageFormat::InputType)), \
574 VTFPP_REMAP_TO_16((g), ImageFormatDetails::green(ImageFormat::InputType)), \
575 VTFPP_REMAP_TO_16((b), ImageFormatDetails::blue(ImageFormat::InputType)), \
578 VTFPP_CONVERT(InputType, \
579 VTFPP_REMAP_TO_16((r), ImageFormatDetails::red(ImageFormat::InputType)), \
580 VTFPP_REMAP_TO_16((g), ImageFormatDetails::green(ImageFormat::InputType)), \
581 VTFPP_REMAP_TO_16((b), ImageFormatDetails::blue(ImageFormat::InputType)), \
585 #define VTFPP_CASE_CONVERT_REMAP_AND_BREAK(InputType, r, g, b, a) \
586 case InputType: { VTFPP_CONVERT_REMAP(InputType, r, g, b, a); } \
597 #undef VTFPP_CASE_CONVERT_REMAP_AND_BREAK
598 #undef VTFPP_CONVERT_REMAP
599 #undef VTFPP_CASE_CONVERT_AND_BREAK
601 #undef VTFPP_CONVERT_DETAIL
602 #undef VTFPP_REMAP_TO_16
607[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616(std::span<const std::byte> imageData,
ImageFormat format) {
610 if (imageData.empty()) {
615 return {imageData.begin(), imageData.end()};
618 std::span imageDataSpan{
reinterpret_cast<const ImagePixel::RGBA16161616*
>(imageData.data()), imageData.size() /
sizeof(ImagePixel::RGBA16161616)};
619 std::vector<std::byte> newData;
622 #define VTFPP_REMAP_FROM_16(value, shift) static_cast<uint8_t>(math::remap<uint16_t>((value), (1 << 16) - 1, (1 << (shift)) - 1))
624#ifdef SOURCEPP_BUILD_WITH_TBB
625 #define VTFPP_CONVERT(InputType, ...) \
626 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
627 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA16161616 pixel) -> ImagePixel::InputType { \
628 return {__VA_ARGS__}; \
631 #define VTFPP_CONVERT(InputType, ...) \
632 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
633 std::transform(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA16161616 pixel) -> ImagePixel::InputType { \
634 return {__VA_ARGS__}; \
637 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
638 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
648 #undef VTFPP_CASE_CONVERT_AND_BREAK
650 #undef VTFPP_REMAP_FROM_16
655[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA32323232F(std::span<const std::byte> imageData,
ImageFormat format) {
656 if (imageData.empty()) {
661 return {imageData.begin(), imageData.end()};
664 std::vector<std::byte> newData;
666 std::span newDataSpan{
reinterpret_cast<ImagePixel::RGBA32323232F*
>(newData.data()), newData.size() /
sizeof(ImagePixel::RGBA32323232F)};
668 #define VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, ...) \
669 std::span<const ImagePixel::InputType> imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
670 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::InputType pixel) -> ImagePixel::RGBA32323232F { \
671 return {{(r), (g), (b), (a)}}; \
673#ifdef SOURCEPP_BUILD_WITH_TBB
674 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a, std::execution::par_unseq)
676 #define VTFPP_CONVERT(InputType, r, g, b, a) VTFPP_CONVERT_DETAIL(InputType, r, g, b, a)
678 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, r, g, b, a) \
679 case InputType: { VTFPP_CONVERT(InputType, r, g, b, a); } break
692 #undef VTFPP_CASE_CONVERT_AND_BREAK
694 #undef VTFPP_CONVERT_DETAIL
699[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232F(std::span<const std::byte> imageData,
ImageFormat format) {
702 if (imageData.empty()) {
707 return {imageData.begin(), imageData.end()};
710 std::span imageDataSpan{
reinterpret_cast<const ImagePixel::RGBA32323232F*
>(imageData.data()), imageData.size() /
sizeof(ImagePixel::RGBA32323232F)};
711 std::vector<std::byte> newData;
714#ifdef SOURCEPP_BUILD_WITH_TBB
715 #define VTFPP_CONVERT(InputType, ...) \
716 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
717 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA32323232F pixel) -> ImagePixel::InputType { \
718 return {__VA_ARGS__}; \
721 #define VTFPP_CONVERT(InputType, ...) \
722 std::span<ImagePixel::InputType> newDataSpan{reinterpret_cast<ImagePixel::InputType*>(newData.data()), newData.size() / sizeof(ImagePixel::InputType)}; \
723 std::transform(imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA32323232F pixel) -> ImagePixel::InputType { \
724 return {__VA_ARGS__}; \
727 #define VTFPP_CASE_CONVERT_AND_BREAK(InputType, ...) \
728 case InputType: { VTFPP_CONVERT(InputType, __VA_ARGS__); } break
741 #undef VTFPP_CASE_CONVERT_AND_BREAK
747[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888ToRGBA32323232F(std::span<const std::byte> imageData) {
748 if (imageData.empty()) {
752 std::vector<std::byte> newData;
753 newData.resize(imageData.size() /
sizeof(ImagePixel::RGBA8888) *
sizeof(ImagePixel::RGBA32323232F));
754 std::span newDataSpan{
reinterpret_cast<ImagePixel::RGBA32323232F*
>(newData.data()), newData.size() /
sizeof(ImagePixel::RGBA32323232F)};
756 std::span imageDataSpan{
reinterpret_cast<const ImagePixel::RGBA8888*
>(imageData.data()), imageData.size() /
sizeof(ImagePixel::RGBA8888)};
758#ifdef SOURCEPP_BUILD_WITH_TBB
759 std::execution::par_unseq,
761 imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA8888 pixel) -> ImagePixel::RGBA32323232F {
763 static_cast<float>(pixel.r()) / static_cast<float>((1 << 8) - 1),
764 static_cast<float>(pixel.g()) / static_cast<float>((1 << 8) - 1),
765 static_cast<float>(pixel.b()) / static_cast<float>((1 << 8) - 1),
766 static_cast<float>(pixel.a()) / static_cast<float>((1 << 8) - 1),
773[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232FToRGBA8888(std::span<const std::byte> imageData) {
774 if (imageData.empty()) {
778 std::vector<std::byte> newData;
779 newData.resize(imageData.size() /
sizeof(ImagePixel::RGBA32323232F) *
sizeof(ImagePixel::RGBA8888));
780 std::span newDataSpan{
reinterpret_cast<ImagePixel::RGBA8888*
>(newData.data()), newData.size() /
sizeof(ImagePixel::RGBA8888)};
782 std::span imageDataSpan{
reinterpret_cast<const ImagePixel::RGBA32323232F*
>(imageData.data()), imageData.size() /
sizeof(ImagePixel::RGBA32323232F)};
784#ifdef SOURCEPP_BUILD_WITH_TBB
785 std::execution::par_unseq,
787 imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA32323232F pixel) -> ImagePixel::RGBA8888 {
789 static_cast<uint8_t>(std::clamp(pixel.r(), 0.f, 1.f) * ((1 << 8) - 1)),
790 static_cast<uint8_t>(std::clamp(pixel.g(), 0.f, 1.f) * ((1 << 8) - 1)),
791 static_cast<uint8_t>(std::clamp(pixel.b(), 0.f, 1.f) * ((1 << 8) - 1)),
792 static_cast<uint8_t>(std::clamp(pixel.a(), 0.f, 1.f) * ((1 << 8) - 1)),
799[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888ToRGBA16161616(std::span<const std::byte> imageData) {
800 if (imageData.empty()) {
804 std::vector<std::byte> newData;
805 newData.resize(imageData.size() /
sizeof(ImagePixel::RGBA8888) *
sizeof(ImagePixel::RGBA16161616));
806 std::span newDataSpan{
reinterpret_cast<ImagePixel::RGBA16161616*
>(newData.data()), newData.size() /
sizeof(ImagePixel::RGBA16161616)};
808 std::span imageDataSpan{
reinterpret_cast<const ImagePixel::RGBA8888*
>(imageData.data()), imageData.size() /
sizeof(ImagePixel::RGBA8888)};
810#ifdef SOURCEPP_BUILD_WITH_TBB
811 std::execution::par_unseq,
813 imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA8888 pixel) -> ImagePixel::RGBA16161616 {
815 math::remap<uint16_t>(pixel.r(), (1 << 8) - 1, (1 << 16) - 1),
816 math::remap<uint16_t>(pixel.g(), (1 << 8) - 1, (1 << 16) - 1),
817 math::remap<uint16_t>(pixel.b(), (1 << 8) - 1, (1 << 16) - 1),
818 math::remap<uint16_t>(pixel.a(), (1 << 8) - 1, (1 << 16) - 1),
825[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616ToRGBA8888(std::span<const std::byte> imageData) {
826 if (imageData.empty()) {
830 std::vector<std::byte> newData;
831 newData.resize(imageData.size() /
sizeof(ImagePixel::RGBA16161616) *
sizeof(ImagePixel::RGBA8888));
832 std::span newDataSpan{
reinterpret_cast<ImagePixel::RGBA8888*
>(newData.data()), newData.size() /
sizeof(ImagePixel::RGBA8888)};
834 std::span imageDataSpan{
reinterpret_cast<const ImagePixel::RGBA16161616*
>(imageData.data()), imageData.size() /
sizeof(ImagePixel::RGBA16161616)};
836#ifdef SOURCEPP_BUILD_WITH_TBB
837 std::execution::par_unseq,
839 imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA16161616 pixel) -> ImagePixel::RGBA8888 {
841 static_cast<uint8_t>(math::remap<uint16_t>(pixel.r(), (1 << 16) - 1, (1 << 8) - 1)),
842 static_cast<uint8_t>(math::remap<uint16_t>(pixel.g(), (1 << 16) - 1, (1 << 8) - 1)),
843 static_cast<uint8_t>(math::remap<uint16_t>(pixel.b(), (1 << 16) - 1, (1 << 8) - 1)),
844 static_cast<uint8_t>(math::remap<uint16_t>(pixel.a(), (1 << 16) - 1, (1 << 8) - 1)),
851[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232FToRGBA16161616(std::span<const std::byte> imageData) {
852 if (imageData.empty()) {
856 std::vector<std::byte> newData;
857 newData.resize(imageData.size() /
sizeof(ImagePixel::RGBA32323232F) *
sizeof(ImagePixel::RGBA16161616));
858 std::span newDataSpan{
reinterpret_cast<ImagePixel::RGBA16161616*
>(newData.data()), newData.size() /
sizeof(ImagePixel::RGBA16161616)};
860 std::span imageDataSpan{
reinterpret_cast<const ImagePixel::RGBA32323232F*
>(imageData.data()), imageData.size() /
sizeof(ImagePixel::RGBA32323232F)};
862#ifdef SOURCEPP_BUILD_WITH_TBB
863 std::execution::par_unseq,
865 imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA32323232F pixel) -> ImagePixel::RGBA16161616 {
867 static_cast<uint16_t>(std::clamp(pixel.r(), 0.f, 1.f) * ((1 << 16) - 1)),
868 static_cast<uint16_t>(std::clamp(pixel.g(), 0.f, 1.f) * ((1 << 16) - 1)),
869 static_cast<uint16_t>(std::clamp(pixel.b(), 0.f, 1.f) * ((1 << 16) - 1)),
870 static_cast<uint16_t>(std::clamp(pixel.a(), 0.f, 1.f) * ((1 << 16) - 1)),
877[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616ToRGBA32323232F(std::span<const std::byte> imageData) {
878 if (imageData.empty()) {
882 std::vector<std::byte> newData;
883 newData.resize(imageData.size() /
sizeof(ImagePixel::RGBA16161616) *
sizeof(ImagePixel::RGBA32323232F));
884 std::span newDataSpan{
reinterpret_cast<ImagePixel::RGBA32323232F*
>(newData.data()), newData.size() /
sizeof(ImagePixel::RGBA32323232F)};
886 std::span imageDataSpan{
reinterpret_cast<const ImagePixel::RGBA16161616*
>(imageData.data()), imageData.size() /
sizeof(ImagePixel::RGBA16161616)};
888#ifdef SOURCEPP_BUILD_WITH_TBB
889 std::execution::par_unseq,
891 imageDataSpan.begin(), imageDataSpan.end(), newDataSpan.begin(), [](ImagePixel::RGBA16161616 pixel) -> ImagePixel::RGBA32323232F {
893 static_cast<float>(pixel.r()) / static_cast<float>((1 << 16) - 1),
894 static_cast<float>(pixel.g()) / static_cast<float>((1 << 16) - 1),
895 static_cast<float>(pixel.b()) / static_cast<float>((1 << 16) - 1),
896 static_cast<float>(pixel.a()) / static_cast<float>((1 << 16) - 1),
911 oldFormat == newFormat ||
915 return {imageData.begin(), imageData.end()};
918 std::vector<std::byte> newData;
922 newData = ::convertImageDataUsingCompressonator(imageData, oldFormat, intermediaryOldFormat, width, height, quality);
924 switch (intermediaryOldFormat) {
932 if (intermediaryOldFormat == newFormat) {
937 if (intermediaryOldFormat != intermediaryNewFormat) {
940 newData = ::convertImageDataFromRGBA8888ToRGBA16161616(newData);
942 newData = ::convertImageDataFromRGBA8888ToRGBA32323232F(newData);
948 newData = ::convertImageDataFromRGBA16161616ToRGBA8888(newData);
950 newData = ::convertImageDataFromRGBA16161616ToRGBA32323232F(newData);
956 newData = ::convertImageDataFromRGBA32323232FToRGBA8888(newData);
958 newData = ::convertImageDataFromRGBA32323232FToRGBA16161616(newData);
967 if (intermediaryNewFormat == newFormat) {
972 newData = ::convertImageDataUsingCompressonator(newData, intermediaryNewFormat, newFormat, width, height, quality);
974 switch (intermediaryNewFormat) {
990 if (oldFormat == newFormat) {
991 return {imageData.begin(), imageData.end()};
995 for(
int mip = mipCount - 1; mip >= 0; mip--) {
997 for (
int frame = 0; frame < frameCount; frame++) {
998 for (
int face = 0; face < faceCount; face++) {
999 for (
int slice = 0; slice < mipDepth; slice++) {
1000 if (uint32_t oldOffset, oldLength;
ImageFormatDetails::getDataPosition(oldOffset, oldLength, oldFormat, mip, mipCount, frame, frameCount, face, faceCount, width, height, slice, depth)) {
1002 if (uint32_t newOffset, newLength;
ImageFormatDetails::getDataPosition(newOffset, newLength, newFormat, mip, mipCount, frame, frameCount, face, faceCount, width, height, slice, depth) && newLength == convertedImageData.size()) {
1003 std::memcpy(out.data() + newOffset, convertedImageData.data(), newLength);
1019 resolution = height;
1022 std::span imageDataRGBA32323232F{
reinterpret_cast<const float*
>(imageData.data()),
reinterpret_cast<const float*
>(imageData.data() + imageData.size())};
1024 std::vector<std::byte> possiblyConvertedDataOrEmptyDontUseMeDirectly;
1027 imageDataRGBA32323232F = {
reinterpret_cast<const float*
>(possiblyConvertedDataOrEmptyDontUseMeDirectly.data()),
reinterpret_cast<const float*
>(possiblyConvertedDataOrEmptyDontUseMeDirectly.data() + possiblyConvertedDataOrEmptyDontUseMeDirectly.size())};
1032 static constexpr std::array<std::array<math::Vec3f, 3>, 6> startRightUp = {{
1033 {{{ 1.0f, -1.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}},
1034 {{{ 1.0f, 1.0f, 1.0f}, { 0.0f,-1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}},
1035 {{{ 1.0f, 1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, { 0.0f,-1.0f, 0.0f}}},
1036 {{{-1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}}},
1037 {{{ 1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}}},
1038 {{{ 1.0f, 1.0f, -1.0f}, { 0.0f, 0.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}},
1041 std::array<std::vector<std::byte>, 6> faceData;
1043#ifdef SOURCEPP_BUILD_WITH_THREADS
1044 const auto faceExtraction = [&](
int i) {
1046 for (
int i = 0; i < faceData.size(); i++) {
1048 const auto start = startRightUp[i][0];
1049 const auto right = startRightUp[i][1];
1050 const auto up = startRightUp[i][2];
1052 faceData[i].resize(resolution * resolution *
sizeof(ImagePixel::RGBA32323232F));
1053 std::span face{
reinterpret_cast<float*
>(faceData[i].data()),
reinterpret_cast<float*
>(faceData[i].data() + faceData[i].size())};
1055 for (
int row = 0; row < resolution; row++) {
1056 for (
int col = 0; col < resolution; col++) {
1057 math::Vec3f pixelDirection3d{
1058 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],
1059 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],
1060 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],
1062 const float azimuth = std::atan2(pixelDirection3d[0], -pixelDirection3d[2]) +
math::pi_f32;
1063 const float elevation = std::atan(pixelDirection3d[1] / std::sqrt(pixelDirection3d[0] * pixelDirection3d[0] + pixelDirection3d[2] * pixelDirection3d[2])) +
math::pi_f32 / 2.f;
1064 const float colHdri = (azimuth /
math::pi_f32 / 2.f) *
static_cast<float>(width);
1065 const float rowHdri = (elevation /
math::pi_f32) *
static_cast<float>(height);
1067 const int colNearest = std::clamp(
static_cast<int>(colHdri), 0, width - 1);
1068 const int rowNearest = std::clamp(
static_cast<int>(rowHdri), 0, height - 1);
1069 face[col * 4 + resolution * row * 4 + 0] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 0];
1070 face[col * 4 + resolution * row * 4 + 1] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 1];
1071 face[col * 4 + resolution * row * 4 + 2] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 2];
1072 face[col * 4 + resolution * row * 4 + 3] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 3];
1074 float intCol, intRow;
1076 float factorCol = std::modf(colHdri - 0.5f, &intCol);
1077 float factorRow = std::modf(rowHdri - 0.5f, &intRow);
1078 const int low_idx_row =
static_cast<int>(intRow);
1079 const int low_idx_column =
static_cast<int>(intCol);
1080 int high_idx_column;
1081 if (factorCol < 0.f) {
1084 high_idx_column = width - 1;
1085 }
else if (low_idx_column == width - 1) {
1087 high_idx_column = 0;
1089 high_idx_column = low_idx_column + 1;
1092 if (factorRow < 0.f || low_idx_row == height - 1) {
1093 high_idx_row = low_idx_row;
1096 high_idx_row = low_idx_row + 1;
1098 factorCol = std::abs(factorCol);
1099 factorRow = std::abs(factorRow);
1100 const float f1 = (1 - factorRow) * (1 - factorCol);
1101 const float f2 = factorRow * (1 - factorCol);
1102 const float f3 = (1 - factorRow) * factorCol;
1103 const float f4 = factorRow * factorCol;
1104 for (
int j = 0; j < 4; j++) {
1105 face[col * 4 + resolution * row * 4 + j] =
1106 imageDataRGBA32323232F[low_idx_column * 4 + width * low_idx_row * 4 + j] * f1 +
1107 imageDataRGBA32323232F[low_idx_column * 4 + width * high_idx_row * 4 + j] * f2 +
1108 imageDataRGBA32323232F[high_idx_column * 4 + width * low_idx_row * 4 + j] * f3 +
1109 imageDataRGBA32323232F[high_idx_column * 4 + width * high_idx_row * 4 + j] * f4;
1118#ifdef SOURCEPP_BUILD_WITH_THREADS
1120 std::array faceFutures{
1121 std::async(std::launch::async, faceExtraction, 0),
1122 std::async(std::launch::async, faceExtraction, 1),
1123 std::async(std::launch::async, faceExtraction, 2),
1124 std::async(std::launch::async, faceExtraction, 3),
1125 std::async(std::launch::async, faceExtraction, 4),
1126 std::async(std::launch::async, faceExtraction, 5),
1128 for (
auto& future : faceFutures) {
1138#ifdef VTFPP_SUPPORT_EXR
1149 std::vector<std::byte> out;
1150 auto stbWriteFunc = [](
void* out_,
void* data,
int size) {
1151 std::copy_n(
static_cast<std::byte*
>(data), size, std::back_inserter(*
static_cast<std::vector<std::byte>*
>(out_)));
1157 switch (fileFormat) {
1160 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), imageData.data(), 0);
1162 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGBA8888), imageData.data(), 0);
1165 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), rgb.data(), 0);
1168 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGBA8888), rgba.data(), 0);
1174 stbi_write_jpg_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), imageData.data(), 95);
1177 stbi_write_jpg_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), rgb.data(), 95);
1183 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), imageData.data());
1185 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGBA8888), imageData.data());
1188 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), rgb.data());
1191 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGBA8888), rgba.data());
1197 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), imageData.data());
1199 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGBA8888), imageData.data());
1202 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), rgb.data());
1205 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGBA8888), rgba.data());
1209#ifdef VTFPP_SUPPORT_WEBP
1210 case FileFormat::WEBP: {
1212 WebPConfigInit(&config);
1213 WebPConfigPreset(&config, WEBP_PRESET_DRAWING, 75.f);
1214 WebPConfigLosslessPreset(&config, 6);
1217 if (!WebPPictureInit(&pic)) {
1221 pic.height = height;
1222 if (!WebPPictureAlloc(&pic)) {
1227 WebPPictureImportRGB(&pic,
reinterpret_cast<const uint8_t*
>(imageData.data()),
static_cast<int>(width *
sizeof(ImagePixel::RGB888)));
1229 WebPPictureImportRGBA(&pic,
reinterpret_cast<const uint8_t*
>(imageData.data()),
static_cast<int>(width *
sizeof(ImagePixel::RGBA8888)));
1232 WebPPictureImportRGB(&pic,
reinterpret_cast<const uint8_t*
>(rgb.data()),
static_cast<int>(width *
sizeof(ImagePixel::RGB888)));
1235 WebPPictureImportRGBA(&pic,
reinterpret_cast<const uint8_t*
>(rgba.data()),
static_cast<int>(width *
sizeof(ImagePixel::RGBA8888)));
1238 WebPMemoryWriter writer;
1239 WebPMemoryWriterInit(&writer);
1240 pic.writer = &WebPMemoryWrite;
1241 pic.custom_ptr = &writer;
1243 int ok = WebPEncode(&config, &pic);
1244 WebPPictureFree(&pic);
1246 WebPMemoryWriterClear(&writer);
1250 if (writer.mem && writer.size) {
1251 out.resize(writer.size);
1252 std::memcpy(out.data(), writer.mem, writer.size);
1254 WebPMemoryWriterClear(&writer);
1258#ifdef VTFPP_SUPPORT_QOI
1259 case FileFormat::QOI: {
1260 qoi_desc descriptor{
1264 .colorspace = QOI_SRGB,
1266 void* qoiData =
nullptr;
1269 descriptor.channels = 3;
1270 qoiData = qoi_encode(imageData.data(), &descriptor, &qoiDataLen);
1272 descriptor.channels = 4;
1273 qoiData = qoi_encode(imageData.data(), &descriptor, &qoiDataLen);
1276 descriptor.channels = 3;
1277 qoiData = qoi_encode(rgb.data(), &descriptor, &qoiDataLen);
1280 descriptor.channels = 4;
1281 qoiData = qoi_encode(rgba.data(), &descriptor, &qoiDataLen);
1283 if (qoiData && qoiDataLen) {
1284 out.resize(qoiDataLen);
1285 std::memcpy(out.data(), qoiData, qoiDataLen);
1300#ifdef VTFPP_SUPPORT_EXR
1301 case FileFormat::EXR: {
1303 InitEXRHeader(&header);
1305 std::vector<std::byte> rawData;
1315 rawData = {imageData.begin(), imageData.end()};
1319 header.channels =
static_cast<EXRChannelInfo*
>(std::malloc(header.num_channels *
sizeof(EXRChannelInfo)));
1320 header.pixel_types =
static_cast<int*
>(malloc(header.num_channels *
sizeof(
int)));
1321 header.requested_pixel_types =
static_cast<int*
>(malloc(header.num_channels *
sizeof(
int)));
1323 switch (header.num_channels) {
1325 header.channels[0].name[0] =
'A';
1326 header.channels[1].name[0] =
'B';
1327 header.channels[2].name[0] =
'G';
1328 header.channels[3].name[0] =
'R';
1331 header.channels[0].name[0] =
'B';
1332 header.channels[1].name[0] =
'G';
1333 header.channels[2].name[0] =
'R';
1336 header.channels[0].name[0] =
'G';
1337 header.channels[1].name[0] =
'R';
1340 header.channels[0].name[0] =
'R';
1343 FreeEXRHeader(&header);
1346 for (
int i = 0; i < header.num_channels; i++) {
1347 header.channels[i].name[1] =
'\0';
1350 int pixelType = (
ImageFormatDetails::red(format) / 8) ==
sizeof(half) ? TINYEXR_PIXELTYPE_HALF : TINYEXR_PIXELTYPE_FLOAT;
1351 for (
int i = 0; i < header.num_channels; i++) {
1352 header.pixel_types[i] = pixelType;
1353 header.requested_pixel_types[i] = pixelType;
1356 std::vector<std::vector<std::byte>> images(header.num_channels);
1357 std::vector<void*> imagePtrs(header.num_channels);
1358 switch (header.num_channels) {
1360 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1361 images[0] = extractChannelFromImageData(imageData, &ImagePixel::RGBA16161616F::a);
1362 images[1] = extractChannelFromImageData(imageData, &ImagePixel::RGBA16161616F::b);
1363 images[2] = extractChannelFromImageData(imageData, &ImagePixel::RGBA16161616F::g);
1364 images[3] = extractChannelFromImageData(imageData, &ImagePixel::RGBA16161616F::r);
1366 images[0] = extractChannelFromImageData(imageData, &ImagePixel::RGBA32323232F::a);
1367 images[1] = extractChannelFromImageData(imageData, &ImagePixel::RGBA32323232F::b);
1368 images[2] = extractChannelFromImageData(imageData, &ImagePixel::RGBA32323232F::g);
1369 images[3] = extractChannelFromImageData(imageData, &ImagePixel::RGBA32323232F::r);
1373 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1375 FreeEXRHeader(&header);
1378 images[0] = extractChannelFromImageData(imageData, &ImagePixel::RGB323232F::b);
1379 images[1] = extractChannelFromImageData(imageData, &ImagePixel::RGB323232F::g);
1380 images[2] = extractChannelFromImageData(imageData, &ImagePixel::RGB323232F::r);
1383 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1384 images[0] = extractChannelFromImageData(imageData, &ImagePixel::RG1616F::g);
1385 images[1] = extractChannelFromImageData(imageData, &ImagePixel::RG1616F::r);
1387 images[0] = extractChannelFromImageData(imageData, &ImagePixel::RG3232F::g);
1388 images[1] = extractChannelFromImageData(imageData, &ImagePixel::RG3232F::r);
1392 images[0] = rawData;
1395 FreeEXRHeader(&header);
1398 for (
int i = 0; i < header.num_channels; i++) {
1399 imagePtrs[i] = images[i].data();
1403 InitEXRImage(&image);
1404 image.width = width;
1405 image.height = height;
1406 image.images =
reinterpret_cast<unsigned char**
>(imagePtrs.data());
1407 image.num_channels = header.num_channels;
1409 unsigned char* data =
nullptr;
1410 const char* err =
nullptr;
1412 size_t size = SaveEXRImageToMemory(&image, &header, &data, &err);
1414 FreeEXRErrorMessage(err);
1415 FreeEXRHeader(&header);
1419 out = {
reinterpret_cast<std::byte*
>(data),
reinterpret_cast<std::byte*
>(data) + size};
1423 FreeEXRHeader(&header);
1436using stb_ptr = std::unique_ptr<T, void(*)(
void*)>;
1441 stbi_convert_iphone_png_to_rgb(
true);
1449#ifdef VTFPP_SUPPORT_EXR
1451 if (EXRVersion version; ParseEXRVersionFromMemory(&version,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size()) == TINYEXR_SUCCESS) {
1452 if (version.multipart || version.non_image) {
1457 InitEXRHeader(&header);
1458 const char* err =
nullptr;
1459 if (ParseEXRHeaderFromMemory(&header, &version,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size(), &err) != TINYEXR_SUCCESS) {
1460 FreeEXRErrorMessage(err);
1465 if (header.num_channels < 1) {
1466 FreeEXRHeader(&header);
1471 std::unordered_map<std::string_view, int> channelIndices{{
"R", -1}, {
"G", -1}, {
"B", -1}, {
"A", -1}, {
"Y", -1}};
1475 auto channelType = header.pixel_types[0];
1476 for (
int i = 1; i < header.num_channels; i++) {
1478 if (header.pixel_types[i] > channelType && channelIndices.contains(header.channels[i].name)) {
1479 channelType = header.pixel_types[i];
1483 if (channelType == TINYEXR_PIXELTYPE_UINT) {
1484 channelType = TINYEXR_PIXELTYPE_HALF;
1488 for (
int i = 0; i < header.num_channels; i++) {
1489 if (channelIndices.contains(header.channels[i].name)) {
1490 channelIndices[header.channels[i].name] = i;
1493 if (channelIndices[
"Y"] >= 0) {
1494 if (channelIndices[
"A"] >= 0) {
1497 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1499 channelType = TINYEXR_PIXELTYPE_FLOAT;
1503 channelIndices[
"R"] = channelIndices[
"Y"];
1504 channelIndices[
"G"] = channelIndices[
"Y"];
1505 channelIndices[
"B"] = channelIndices[
"Y"];
1506 }
else if (channelIndices[
"A"] >= 0) {
1508 }
else if (channelIndices[
"B"] >= 0) {
1509 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1511 channelType = TINYEXR_PIXELTYPE_FLOAT;
1514 }
else if (channelIndices[
"G"] >= 0) {
1516 }
else if (channelIndices[
"R"] >= 0) {
1519 FreeEXRHeader(&header);
1524 for (
int i = 0; i < header.num_channels; i++) {
1525 if (header.pixel_types[i] != channelType && channelIndices.contains(header.channels[i].name)) {
1526 header.requested_pixel_types[i] = channelType;
1531 InitEXRImage(&image);
1532 if (LoadEXRImageFromMemory(&image, &header,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size(), &err) != TINYEXR_SUCCESS) {
1533 FreeEXRErrorMessage(err);
1534 FreeEXRHeader(&header);
1538 width = image.width;
1539 height = image.height;
1543 const auto populateBuffer = [
1551 r=channelIndices[
"R"],
1552 g=channelIndices[
"G"],
1553 b=channelIndices[
"B"],
1554 a=channelIndices[
"A"],
1558 const auto channelCount = hasRed + hasGreen + hasBlue + hasAlpha;
1560 std::span out{
reinterpret_cast<C*
>(combinedChannels.data()), combinedChannels.size() /
sizeof(C)};
1562 for (
int t = 0; t < image.num_tiles; t++) {
1563 auto** src =
reinterpret_cast<C**
>(image.tiles[t].images);
1564 for (
int j = 0; j < header.tile_size_y; j++) {
1565 for (
int i = 0; i < header.tile_size_x; i++) {
1566 const auto ii =
static_cast<uint64_t
>(image.tiles[t].offset_x) * header.tile_size_x + i;
1567 const auto jj =
static_cast<uint64_t
>(image.tiles[t].offset_y) * header.tile_size_y + j;
1568 const auto idx = ii + jj * image.width;
1570 if (ii >= image.width || jj >= image.height) {
1574 const auto srcIdx = j *
static_cast<uint64_t
>(header.tile_size_x) + i;
1575 if (r >= 0) out[idx * channelCount + 0] = src[r][srcIdx];
1576 else if (hasRed) out[idx * channelCount + 0] = 0.f;
1577 if (g >= 0) out[idx * channelCount + 1] = src[g][srcIdx];
1578 else if (hasGreen) out[idx * channelCount + 1] = 0.f;
1579 if (b >= 0) out[idx * channelCount + 2] = src[b][srcIdx];
1580 else if (hasBlue) out[idx * channelCount + 2] = 0.f;
1581 if (a >= 0) out[idx * channelCount + 3] = src[a][srcIdx];
1582 else if (hasAlpha) out[idx * channelCount + 3] = 1.f;
1587 auto** src =
reinterpret_cast<C**
>(image.images);
1588 for (uint64_t i = 0; i < width * height; i++) {
1589 if (r >= 0) out[i * channelCount + 0] = src[r][i];
1590 else if (hasRed) out[i * channelCount + 0] = 0.f;
1591 if (g >= 0) out[i * channelCount + 1] = src[g][i];
1592 else if (hasGreen) out[i * channelCount + 1] = 0.f;
1593 if (b >= 0) out[i * channelCount + 2] = src[b][i];
1594 else if (hasBlue) out[i * channelCount + 2] = 0.f;
1595 if (a >= 0) out[i * channelCount + 3] = src[a][i];
1596 else if (hasAlpha) out[i * channelCount + 3] = 1.f;
1600 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1601 populateBuffer.operator()<half>();
1603 populateBuffer.operator()<
float>();
1606 FreeEXRImage(&image);
1607 FreeEXRHeader(&header);
1608 return combinedChannels;
1613 if (stbi_is_hdr_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()))) {
1614 const ::stb_ptr<float> stbImage{
1615 stbi_loadf_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1631#ifdef VTFPP_SUPPORT_WEBP
1633 if (WebPBitstreamFeatures features; fileData.size() > 12 &&
static_cast<char>(fileData[8]) ==
'W' &&
static_cast<char>(fileData[9]) ==
'E' &&
static_cast<char>(fileData[10]) ==
'B' &&
static_cast<char>(fileData[11]) ==
'P' && WebPGetFeatures(
reinterpret_cast<const uint8_t*
>(fileData.data()), fileData.size(), &features) == VP8_STATUS_OK) {
1634 width = features.width;
1635 height = features.height;
1639 std::vector<std::byte> out;
1640 if (features.has_alpha) {
1643 if (!WebPDecodeRGBAInto(
reinterpret_cast<const uint8_t*
>(fileData.data()), fileData.size(),
reinterpret_cast<uint8_t*
>(out.data()), out.size(), width * (
ImageFormatDetails::bpp(format) / 8))) {
1649 if (!WebPDecodeRGBInto(
reinterpret_cast<const uint8_t*
>(fileData.data()), fileData.size(),
reinterpret_cast<uint8_t*
>(out.data()), out.size(), width * (
ImageFormatDetails::bpp(format) / 8))) {
1658 if (fileData.size() > 3 &&
static_cast<char>(fileData[0]) ==
'G' &&
static_cast<char>(fileData[1]) ==
'I' &&
static_cast<char>(fileData[2]) ==
'F') {
1659 const ::stb_ptr<stbi_uc> stbImage{
1660 stbi_load_gif_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()),
nullptr, &width, &height, &frameCount, &channels, 0),
1663 if (!stbImage || !frameCount) {
1673 return {
reinterpret_cast<std::byte*
>(stbImage.get()),
reinterpret_cast<std::byte*
>(stbImage.get() + (
ImageFormatDetails::getDataLength(format, width, height) * frameCount))};
1679 stbi__start_mem(&s,
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()));
1680 if (stbi__png_test(&s)) {
1682 const auto apngDecoder = [&format, &width, &height, &frameCount]<
typename P>(
const auto& stbImage, std::size_t dirOffset) -> std::vector<std::byte> {
1683 auto* dir =
reinterpret_cast<stbi__apng_directory*
>(stbImage.get() + dirOffset);
1684 if (dir->type != STBI__STRUCTURE_TYPE_APNG_DIRECTORY) {
1689 frameCount =
static_cast<int>(dir->num_frames);
1691 static constexpr auto calcPixelOffset = [](uint32_t offsetX, uint32_t offsetY, uint32_t width_) {
1692 return ((offsetY * width_) + offsetX) *
sizeof(P);
1696 static constexpr auto copyImageData = [](std::span<std::byte> dst, uint32_t dstWidth, uint32_t , std::span<const std::byte> src, uint32_t srcWidth, uint32_t srcHeight, uint32_t srcOffsetX, uint32_t srcOffsetY) {
1697 for (uint32_t y = 0; y < srcHeight; y++) {
1699#ifdef SOURCEPP_BUILD_WITH_TBB
1700 std::execution::unseq,
1702 src.data() + calcPixelOffset( 0, y, srcWidth),
1703 src.data() + calcPixelOffset( srcWidth, y, srcWidth),
1704 dst.data() + calcPixelOffset(srcOffsetX, srcOffsetY + y, dstWidth));
1709 static constexpr auto copyImageSubRectData = [](std::span<std::byte> dst, std::span<const std::byte> src, uint32_t imgWidth, uint32_t , uint32_t subWidth, uint32_t subHeight, uint32_t subOffsetX, uint32_t subOffsetY) {
1710 for (uint32_t y = subOffsetY; y < subOffsetY + subHeight; y++) {
1712#ifdef SOURCEPP_BUILD_WITH_TBB
1713 std::execution::unseq,
1715 src.data() + calcPixelOffset(subOffsetX, y, imgWidth),
1716 src.data() + calcPixelOffset(subOffsetX + subWidth, y, imgWidth),
1717 dst.data() + calcPixelOffset(subOffsetX, y, imgWidth));
1721 static constexpr auto clearImageData = [](std::span<std::byte> dst, uint32_t dstWidth, uint32_t , uint32_t clrWidth, uint32_t clrHeight, uint32_t clrOffsetX, uint32_t clrOffsetY) {
1722 for (uint32_t y = 0; y < clrHeight; y++) {
1724#ifdef SOURCEPP_BUILD_WITH_TBB
1725 std::execution::unseq,
1727 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth),
1728 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth) + (clrWidth *
sizeof(P)),
1729 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth),
1730 [](std::byte) {
return std::byte{0}; });
1734 static constexpr auto overlayImageData = [](std::span<std::byte> dst, uint32_t dstWidth, uint32_t , std::span<const std::byte> src, uint32_t srcWidth, uint32_t srcHeight, uint32_t srcOffsetX, uint32_t srcOffsetY) {
1735 for (uint32_t y = 0; y < srcHeight; y++) {
1736 const auto* sp =
reinterpret_cast<const uint8_t*
>(src.data() + calcPixelOffset(0, y, srcWidth));
1737 auto* dp =
reinterpret_cast<uint8_t*
>(dst.data() + calcPixelOffset(srcOffsetX, srcOffsetY + y, dstWidth));
1738 for (uint32_t x = 0; x < srcWidth; x++, sp += 4, dp += 4) {
1742 if ((sp[3] == 0xff) || (dp[3] == 0)) {
1743 std::copy_n(sp,
sizeof(P), dp);
1745 const int u = sp[3] * 0xff;
1746 const int v = (0xff - sp[3]) * dp[3];
1747 const int al = u + v;
1748 dp[0] = (sp[0] * u + dp[0] * v) / al;
1749 dp[1] = (sp[1] * u + dp[1] * v) / al;
1750 dp[2] = (sp[2] * u + dp[2] * v) / al;
1758 const uint64_t fullFrameSize =
sizeof(P) * width * height;
1759 uint64_t currentFrameSize = 0;
1760 std::vector<std::byte> out(fullFrameSize * frameCount);
1761 uint64_t srcFrameOffset = 0;
1762 uint64_t dstFrameOffset = 0;
1763 for (uint32_t i = 0; i < dir->num_frames; i++) {
1764 const auto& frame = dir->frames[i];
1765 currentFrameSize =
sizeof(P) * frame.width * frame.height;
1768 if (frame.width == width && frame.height == height && frame.x_offset == 0 && frame.y_offset == 0 && frame.blend_op == STBI_APNG_blend_op_source) {
1769 std::memcpy(out.data() + dstFrameOffset, stbImage.get() + srcFrameOffset, fullFrameSize);
1772 if (frame.blend_op == STBI_APNG_blend_op_source || (i == 0 && frame.blend_op == STBI_APNG_blend_op_over)) {
1773 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);
1774 }
else if (frame.blend_op == STBI_APNG_blend_op_over) {
1775 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);
1781 dstFrameOffset += fullFrameSize;
1782 srcFrameOffset += currentFrameSize;
1785 if (i == dir->num_frames - 1) {
1790 copyImageData({out.data() + dstFrameOffset, out.data() + dstFrameOffset + fullFrameSize}, width, height, {out.data() + dstFrameOffset - fullFrameSize, out.data() + dstFrameOffset}, width, height, 0, 0);
1793 if (frame.dispose_op == STBI_APNG_dispose_op_background || (i == 0 && frame.dispose_op == STBI_APNG_dispose_op_previous)) {
1794 clearImageData({out.data() + dstFrameOffset, out.data() + dstFrameOffset + fullFrameSize}, width, height, frame.width, frame.height, frame.x_offset, frame.y_offset);
1795 }
else if (frame.dispose_op == STBI_APNG_dispose_op_previous) {
1796 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);
1797 }
else if (frame.dispose_op != STBI_APNG_dispose_op_none) {
1804 static const char *dispose_ops[] = {
1805 "STBI_APNG_dispose_op_none",
1806 "STBI_APNG_dispose_op_background",
1807 "STBI_APNG_dispose_op_previous",
1810 static const char *blend_ops[] = {
1811 "STBI_APNG_blend_op_source",
1812 "STBI_APNG_blend_op_over",
1815 fprintf(stderr,
"dir_offset : %zu\n", dirOffset);
1816 fprintf(stderr,
"dir.type : %.*s\n", 4, (
unsigned char *) &dir->type);
1817 fprintf(stderr,
"dir.num_frames : %u\n", dir->num_frames);
1818 fprintf(stderr,
"dir.default_image_is_first_frame : %s\n",
1819 dir->default_image_is_first_frame ?
"yes" :
"no");
1820 fprintf(stderr,
"dir.num_plays : %u\n", dir->num_plays);
1822 for (
int i = 0; i < dir->num_frames; ++i) {
1823 stbi__apng_frame_directory_entry *frame = &dir->frames[i];
1825 fprintf(stderr,
"frame : %u\n", i);
1826 fprintf(stderr,
" width : %u\n", frame->width);
1827 fprintf(stderr,
" height : %u\n", frame->height);
1828 fprintf(stderr,
" x_offset : %u\n", frame->x_offset);
1829 fprintf(stderr,
" y_offset : %u\n", frame->y_offset);
1830 fprintf(stderr,
" delay_num : %u\n", frame->delay_num);
1831 fprintf(stderr,
" delay_den : %u\n", frame->delay_den);
1832 fprintf(stderr,
" dispose_op : %s\n", dispose_ops[frame->dispose_op]);
1833 fprintf(stderr,
" blend_op : %s\n", blend_ops[frame->blend_op]);
1839 std::size_t dirOffset = 0;
1840 if (stbi__png_is16(&s)) {
1841 const ::stb_ptr<stbi_us> stbImage{
1842 stbi__apng_load_16bit(&s, &width, &height, &channels, STBI_rgb_alpha, &dirOffset),
1845 if (stbImage && dirOffset) {
1846 return apngDecoder.operator()<ImagePixel::RGBA16161616>(stbImage, dirOffset);
1849 const ::stb_ptr<stbi_uc> stbImage{
1850 stbi__apng_load_8bit(&s, &width, &height, &channels, STBI_rgb_alpha, &dirOffset),
1853 if (stbImage && dirOffset) {
1854 return apngDecoder.operator()<ImagePixel::RGBA8888>(stbImage, dirOffset);
1860#ifdef VTFPP_SUPPORT_QOI
1863 qoi_desc descriptor;
1864 const ::stb_ptr<std::byte> qoiImage{
1865 static_cast<std::byte*
>(qoi_decode(fileData.data(),
static_cast<int>(fileData.size()), &descriptor, 0)),
1871 width =
static_cast<int>(descriptor.width);
1872 height =
static_cast<int>(descriptor.height);
1873 channels = descriptor.channels;
1884 if (stbi_is_16_bit_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()))) {
1885 const ::stb_ptr<stbi_us> stbImage{
1886 stbi_load_16_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1892 if (channels == 4) {
1894 }
else if (channels >= 1 && channels < 4) {
1898 std::span outPixels{
reinterpret_cast<ImagePixel::RGBA16161616*
>(out.data()), out.size() /
sizeof(ImagePixel::RGBA16161616)};
1901 std::span inPixels{stbImage.get(), outPixels.size()};
1903#ifdef SOURCEPP_BUILD_WITH_TBB
1904 std::execution::par_unseq,
1906 inPixels.begin(), inPixels.end(), outPixels.begin(), [](uint16_t pixel) -> ImagePixel::RGBA16161616 {
1907 return {{pixel, 0, 0, 0xffff}};
1916 std::span inPixels{
reinterpret_cast<RG1616*
>(stbImage.get()), outPixels.size()};
1918#ifdef SOURCEPP_BUILD_WITH_TBB
1919 std::execution::par_unseq,
1921 inPixels.begin(), inPixels.end(), outPixels.begin(), [](RG1616 pixel) -> ImagePixel::RGBA16161616 {
1922 return {{pixel.r, pixel.g, 0, 0xffff}};
1932 std::span inPixels{
reinterpret_cast<RGB161616*
>(stbImage.get()), outPixels.size()};
1934#ifdef SOURCEPP_BUILD_WITH_TBB
1935 std::execution::par_unseq,
1937 inPixels.begin(), inPixels.end(), outPixels.begin(), [](RGB161616 pixel) -> ImagePixel::RGBA16161616 {
1938 return {{pixel.r, pixel.g, pixel.b, 0xffff}};
1952 const ::stb_ptr<stbi_uc> stbImage{
1953 stbi_load_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1982 return {width, height};
2007 return convertImageDataToFormat(
resizeImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, newWidth, height, newHeight, srgb, filter, edge), container, format, newWidth, newHeight);
2010 STBIR_RESIZE resize;
2011 const auto setEdgeModesAndFiltersAndDoResize = [edge, filter, &resize] {
2012 stbir_set_edgemodes(&resize,
static_cast<stbir_edge
>(edge),
static_cast<stbir_edge
>(edge));
2021 stbir_set_filters(&resize,
static_cast<stbir_filter
>(filter),
static_cast<stbir_filter
>(filter));
2025 static constexpr auto KAISER_RADIUS = 3.f;
2026 static constexpr auto KAISER_ALPHA = 2.f;
2027 static constexpr auto KAISER_WINDOW = [](
double u) ->
double {
2028 if (u < -1 || u > 1)
return 0;
2031 static constexpr auto KAISER_FILTER = [](
float x, float,
void*) ->
float {
2032 if (x <= -KAISER_RADIUS || x >= KAISER_RADIUS)
return 0;
2033 if (std::abs(x) < 1e-6f)
return 1;
2034 return static_cast<float>(
math::sinc(x) * KAISER_WINDOW(std::abs(x) / KAISER_RADIUS));
2036 static constexpr auto KAISER_SUPPORT = [](float,
void*) ->
float {
2037 return KAISER_RADIUS;
2039 stbir_set_filter_callbacks(&resize, KAISER_FILTER, KAISER_SUPPORT, KAISER_FILTER, KAISER_SUPPORT);
2043 static constexpr auto NICE_RADIUS = 3.f;
2044 static constexpr auto NICE_SHARPEN = 1.25f;
2045 static constexpr auto NICE_FILTER = [](
float x, float,
void*) ->
float {
2046 if (x <= -NICE_RADIUS || x >= NICE_RADIUS)
return 0;
2047 if (std::abs(x) < 1e-6f)
return 1;
2049 if (out < 0) out *= NICE_SHARPEN;
2052 static constexpr auto NICE_SUPPORT = [](float,
void*) ->
float {
2055 stbir_set_filter_callbacks(&resize, NICE_FILTER, NICE_SUPPORT, NICE_FILTER, NICE_SUPPORT);
2059 stbir_resize_extended(&resize);
2062 const auto pixelLayout = ::imageFormatToSTBIRPixelLayout(format);
2063 if (pixelLayout == -1) {
2067 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)));
2068 setEdgeModesAndFiltersAndDoResize();
2072 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)));
2073 setEdgeModesAndFiltersAndDoResize();
2077std::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) {
2083 return resizeImageData(imageData, format, width, widthOut, height, heightOut, srgb, filter, edge);
2087std::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) {
2088 if (imageData.empty() || format ==
ImageFormat::EMPTY || xOffset + newWidth >= width || yOffset + newHeight >= height) {
2094 return convertImageDataToFormat(
cropImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, newWidth, xOffset, height, newHeight, yOffset), container, format, newWidth, newHeight);
2098 std::vector<std::byte> out(pixelSize * newWidth * newHeight);
2099 for (uint16_t y = yOffset; y < yOffset + newHeight; y++) {
2100 std::memcpy(out.data() + (((y - yOffset) * newWidth) * pixelSize), imageData.data() + (((y * width) + xOffset) * pixelSize), newWidth * pixelSize);
2110 if (!widthPad && !heightPad) {
2111 return {imageData.begin(), imageData.end()};
2116 return convertImageDataToFormat(
padImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, widthPad, height, heightPad), container, format, width + widthPad, height + heightPad);
2120 std::vector<std::byte> out(pixelSize * (width + widthPad) * (height + heightPad));
2123 for (uint16_t y = 0; y < height; y++) {
2124 std::memcpy(out.data() + ((y * (width + widthPad)) * pixelSize), imageData.data() + ((y * width) * pixelSize), width * pixelSize);
2128 for (
int y = 0; y < height + heightPad; y++) {
2129 for (
int x = 0; x < width + widthPad; x++) {
2130 if (x >= width && y >= height) {
2131 std::memcpy(out.data() + ((y * (width + widthPad) + x) * pixelSize), imageData.data() + (((height - 1) * (width) + (width - 1)) * pixelSize), pixelSize);
2132 }
else if (x >= width) {
2133 std::memcpy(out.data() + ((y * (width + widthPad) + x) * pixelSize), imageData.data() + ((y * (width) + (width - 1)) * pixelSize), pixelSize);
2134 }
else if (y >= height) {
2135 std::memcpy(out.data() + ((y * (width + widthPad) + x) * pixelSize), imageData.data() + (((height - 1) * (width) + x) * pixelSize), pixelSize);
2149 return {imageData.begin(), imageData.end()};
2154 return convertImageDataToFormat(
gammaCorrectImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, height, gamma), container, format, width, height);
2157 static constexpr auto calculateGammaLUT = [](
float gamma_, uint8_t channelSize) -> std::array<uint8_t, 256> {
2158 const auto maxSize =
static_cast<float>((1 << channelSize) - 1);
2159 std::array<uint8_t, 256> gammaLUT{};
2160 for (
int i = 0; i < gammaLUT.size(); i++) {
2161 gammaLUT[i] =
static_cast<uint8_t
>(std::clamp(std::pow((
static_cast<float>(i) + 0.5f) / maxSize, gamma_) * maxSize - 0.5f, 0.f, maxSize));
2166 #define VTFPP_CREATE_GAMMA_LUTS(InputType) \
2167 std::unordered_map<uint8_t, std::array<uint8_t, 256>> gammaLUTs; \
2168 if constexpr (ImageFormatDetails::red(ImageFormat::InputType) > 0) { \
2169 if (!gammaLUTs.contains(ImageFormatDetails::red(ImageFormat::InputType))) { \
2170 gammaLUTs[ImageFormatDetails::red(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::red(ImageFormat::InputType)); \
2173 if constexpr (ImageFormatDetails::green(ImageFormat::InputType) > 0) { \
2174 if (!gammaLUTs.contains(ImageFormatDetails::green(ImageFormat::InputType))) { \
2175 gammaLUTs[ImageFormatDetails::green(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::green(ImageFormat::InputType)); \
2178 if constexpr (ImageFormatDetails::blue(ImageFormat::InputType) > 0) { \
2179 if (!gammaLUTs.contains(ImageFormatDetails::blue(ImageFormat::InputType))) { \
2180 gammaLUTs[ImageFormatDetails::blue(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::blue(ImageFormat::InputType)); \
2184 #define VTFPP_APPLY_GAMMA_RED(value) \
2185 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::red(PIXEL_TYPE::FORMAT))[value])
2187 #define VTFPP_APPLY_GAMMA_GREEN(value) \
2188 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::green(PIXEL_TYPE::FORMAT))[value])
2190 #define VTFPP_APPLY_GAMMA_BLUE(value) \
2191 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::blue(PIXEL_TYPE::FORMAT))[value])
2193 std::vector<std::byte> out(imageData.size());
2195#ifdef SOURCEPP_BUILD_WITH_TBB
2196 #define VTFPP_GAMMA_CORRECT(InputType, ...) \
2197 std::span imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
2198 std::span outSpan{reinterpret_cast<ImagePixel::InputType*>(out.data()), out.size() / sizeof(ImagePixel::InputType)}; \
2199 std::transform(std::execution::par_unseq, imageDataSpan.begin(), imageDataSpan.end(), outSpan.begin(), [gammaLUTs](ImagePixel::InputType pixel) -> ImagePixel::InputType { \
2200 using PIXEL_TYPE = ImagePixel::InputType; \
2201 return {__VA_ARGS__}; \
2204 #define VTFPP_GAMMA_CORRECT(InputType, ...) \
2205 std::span imageDataSpan{reinterpret_cast<const ImagePixel::InputType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::InputType)}; \
2206 std::span outSpan{reinterpret_cast<ImagePixel::InputType*>(out.data()), out.size() / sizeof(ImagePixel::InputType)}; \
2207 std::transform(imageDataSpan.begin(), imageDataSpan.end(), outSpan.begin(), [gammaLUTs](ImagePixel::InputType pixel) -> ImagePixel::InputType { \
2208 using PIXEL_TYPE = ImagePixel::InputType; \
2209 return {__VA_ARGS__}; \
2212 #define VTFPP_CASE_GAMMA_CORRECT_AND_BREAK(InputType, ...) \
2213 case InputType: { VTFPP_CREATE_GAMMA_LUTS(InputType) VTFPP_GAMMA_CORRECT(InputType, __VA_ARGS__); } break
2238 #undef VTFPP_CASE_GAMMA_CORRECT_AND_BREAK
2239 #undef VTFPP_GAMMA_CORRECT
2240 #undef VTFPP_APPLY_GAMMA_BLUE
2241 #undef VTFPP_APPLY_GAMMA_GREEN
2242 #undef VTFPP_APPLY_GAMMA_RED
2243 #undef VTFPP_CREATE_GAMMA_LUTS
2254 return {imageData.begin(), imageData.end()};
2259 return convertImageDataToFormat(
invertGreenChannelForImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, height), container, format, width, height);
2262 #define VTFPP_INVERT_GREEN(PixelType, ChannelName, ...) \
2263 static constexpr auto channelSize = ImageFormatDetails::green(ImagePixel::PixelType::FORMAT); \
2264 std::span imageDataSpan{reinterpret_cast<const ImagePixel::PixelType*>(imageData.data()), imageData.size() / sizeof(ImagePixel::PixelType)}; \
2265 std::span outSpan{reinterpret_cast<ImagePixel::PixelType*>(out.data()), out.size() / sizeof(ImagePixel::PixelType)}; \
2266 std::transform(__VA_ARGS__ __VA_OPT__(,) imageDataSpan.begin(), imageDataSpan.end(), outSpan.begin(), [](ImagePixel::PixelType pixel) -> ImagePixel::PixelType { \
2267 if constexpr (std::same_as<decltype(pixel.ChannelName()), float> || std::same_as<decltype(pixel.ChannelName()), half>) { \
2268 pixel.set_##ChannelName(static_cast<decltype(pixel.ChannelName())>(static_cast<float>(static_cast<uint64_t>(1) << channelSize) - 1.f - static_cast<float>(pixel.ChannelName()))); \
2270 if constexpr (channelSize >= sizeof(uint32_t) * 8) { \
2271 pixel.set_##ChannelName(static_cast<decltype(pixel.ChannelName())>((static_cast<uint64_t>(1) << channelSize) - 1 - static_cast<uint32_t>(pixel.ChannelName()))); \
2273 pixel.set_##ChannelName(static_cast<decltype(pixel.ChannelName())>(static_cast<uint32_t>(1 << channelSize) - 1 - static_cast<uint32_t>(pixel.ChannelName()))); \
2278#ifdef SOURCEPP_BUILD_WITH_TBB
2279 #define VTFPP_INVERT_GREEN_CASE(PixelType) \
2280 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, g, std::execution::par_unseq); break; }
2281 #define VTFPP_INVERT_GREEN_CASE_CA_OVERRIDE(PixelType, ChannelName) \
2282 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, ChannelName, std::execution::par_unseq); break; }
2284 #define VTFPP_INVERT_GREEN_CASE(PixelType) \
2285 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, g); } break
2286 #define VTFPP_INVERT_GREEN_CASE_CA_OVERRIDE(PixelType, ChannelName) \
2287 case ImageFormat::PixelType: { VTFPP_INVERT_GREEN(PixelType, ChannelName); } break
2290 std::vector<std::byte> out(imageData.size());
2332 #undef VTFPP_INVERT_GREEN_CASE_CA_OVERRIDE
2333 #undef VTFPP_INVERT_GREEN_CASE
2334 #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 double sinc(double x)
constexpr T nearestPowerOf2(T n)
constexpr double besselI0(double x)
consteval uint32_t makeFourCC(const char fourCC[4])
Creates a FourCC identifier from a string of 4 characters.
std::vector< std::byte > convertFileToImageData(std::span< const std::byte > fileData, ImageFormat &format, int &width, int &height, int &frameCount)
std::vector< std::byte > convertImageDataToFile(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height, FileFormat fileFormat=FileFormat::DEFAULT)
Converts image data to the given file format (PNG or EXR by default).
void setResizedDims(uint16_t &width, ResizeMethod widthResize, uint16_t &height, ResizeMethod heightResize)
Set the new image dimensions given a resize method.
std::array< std::vector< std::byte >, 6 > convertHDRIToCubeMap(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height, uint16_t resolution=0, bool bilinear=true)
Converts an HDRI into a cubemap.
std::vector< std::byte > convertImageDataToFormat(std::span< const std::byte > imageData, ImageFormat oldFormat, ImageFormat newFormat, uint16_t width, uint16_t height, float quality=DEFAULT_COMPRESSED_QUALITY)
Converts an image from one format to another.
uint16_t getResizedDim(uint16_t n, ResizeMethod method)
Get the new image size given a resize method.
constexpr float DEFAULT_COMPRESSED_QUALITY
std::vector< std::byte > convertSeveralImageDataToFormat(std::span< const std::byte > imageData, ImageFormat oldFormat, ImageFormat newFormat, uint8_t mipCount, uint16_t frameCount, uint8_t faceCount, uint16_t width, uint16_t height, uint16_t depth, float quality=DEFAULT_COMPRESSED_QUALITY)
Converts several images from one format to another.
std::vector< std::byte > gammaCorrectImageData(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height, float gamma)
Perform gamma correction on the given image data. Will not perform gamma correction if the input imag...
@ NICE
Valve NICE filtering, equivalent to Lanczos-3.
FileFormat getDefaultFileFormatForImageFormat(ImageFormat format)
PNG for integer formats, EXR for floating point formats (or HDR if EXR support is disabled).
std::vector< std::byte > padImageData(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t widthPad, uint16_t height, uint16_t heightPad)
Pad the given image with pixels that are the same color as the edge. Padding is applied to the right ...
std::vector< std::byte > cropImageData(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t xOffset, uint16_t height, uint16_t newHeight, uint16_t yOffset)
Crops the given image to the new dimensions. If the image format is compressed it will be converted t...
std::vector< std::byte > resizeImageDataStrict(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t &widthOut, ResizeMethod widthResize, uint16_t height, uint16_t newHeight, uint16_t &heightOut, ResizeMethod heightResize, bool srgb, ResizeFilter filter, ResizeEdge edge=ResizeEdge::CLAMP)
Resize given image data to the new dimensions, where the new width and height are governed by the res...
std::vector< std::byte > invertGreenChannelForImageData(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height)
Invert the green channel. Meant for converting normal maps between OpenGL and DirectX formats.
std::vector< std::byte > resizeImageData(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t height, uint16_t newHeight, bool srgb, ResizeFilter filter, ResizeEdge edge=ResizeEdge::CLAMP)
Resize given image data to the new dimensions.
constexpr std::pair< uint16_t, uint16_t > getMipDims(uint8_t mip, uint16_t width, uint16_t height, bool addCompressedFormatPadding=false)
Get the width and height at a given mip level.
@ CONSOLE_ARGB8888_LINEAR
@ CONSOLE_BGRX8888_LINEAR
@ CONSOLE_RGBA8888_LINEAR
@ CONSOLE_ABGR8888_LINEAR
@ CONSOLE_BGRX5551_LINEAR
@ CONSOLE_BGRA8888_LINEAR
@ CONSOLE_RGBA16161616_LINEAR
std::pair< uint16_t, uint16_t > clamp(uint16_t width, uint16_t height) const