17#include <unordered_map>
19#ifdef SOURCEPP_BUILD_WITH_TBB
23#ifdef SOURCEPP_BUILD_WITH_THREADS
27#define BCDEC_IMPLEMENTATION
30#ifdef VTFPP_BUILD_WITH_COMPRESSONATOR
31#include <compressonator.h>
34#ifdef VTFPP_SUPPORT_JXL
35#include <jxl/decode_cxx.h>
36#include <jxl/encode_cxx.h>
37#ifdef SOURCEPP_BUILD_WITH_THREADS
38#include <jxl/resizable_parallel_runner_cxx.h>
39#include <jxl/thread_parallel_runner_cxx.h>
43#ifdef VTFPP_SUPPORT_QOI
44#define QOI_IMPLEMENTATION
52#define STB_IMAGE_IMPLEMENTATION
53#define STB_IMAGE_STATIC
54#define STBI_NO_FAILURE_STRINGS
58#define STB_IMAGE_RESIZE_IMPLEMENTATION
59#define STB_IMAGE_RESIZE_STATIC
60#include <stb_image_resize2.h>
62#define STB_IMAGE_WRITE_IMPLEMENTATION
63#define STB_IMAGE_WRITE_STATIC
64#define STBI_WRITE_NO_STDIO
65#include <stb_image_write.h>
67#ifdef VTFPP_SUPPORT_EXR
73#ifdef VTFPP_SUPPORT_WEBP
74#include <webp/decode.h>
75#include <webp/encode.h>
83#ifdef VTFPP_BUILD_WITH_COMPRESSONATOR
84[[nodiscard]]
constexpr CMP_FORMAT imageFormatToCompressonatorFormat(
ImageFormat format) {
89 return CMP_FORMAT_RGBA_8888;
91 return CMP_FORMAT_ABGR_8888;
93 return CMP_FORMAT_RGB_888;
95 return CMP_FORMAT_BGR_888;
98 return CMP_FORMAT_R_8;
100 return CMP_FORMAT_ARGB_8888;
102 return CMP_FORMAT_BGRA_8888;
105 return CMP_FORMAT_DXT1;
107 return CMP_FORMAT_DXT3;
109 return CMP_FORMAT_DXT5;
111 return CMP_FORMAT_RG_8;
113 return CMP_FORMAT_R_16F;
115 return CMP_FORMAT_RG_16F;
117 return CMP_FORMAT_RGBA_16F;
119 return CMP_FORMAT_RGBA_16;
121 return CMP_FORMAT_R_32F;
123 return CMP_FORMAT_RG_32F;
125 return CMP_FORMAT_RGB_32F;
127 return CMP_FORMAT_RGBA_32F;
129 return CMP_FORMAT_ATI2N;
132 return CMP_FORMAT_ATI1N;
134 return CMP_FORMAT_RGBA_1010102;
136 return CMP_FORMAT_R_8;
139 return CMP_FORMAT_BC6H;
142 return CMP_FORMAT_BC7;
144 return CMP_FORMAT_BC6H_SF;
146 return CMP_FORMAT_ATI2N_XY;
176 return CMP_FORMAT_Unknown;
178 return CMP_FORMAT_Unknown;
182[[nodiscard]]
constexpr int imageFormatToSTBIRPixelLayout(
ImageFormat format,
bool premultipliedAlpha) {
190 return premultipliedAlpha ? STBIR_RGBA_PM : STBIR_RGBA;
192 return premultipliedAlpha ? STBIR_ABGR_PM : STBIR_ABGR;
203 return STBIR_1CHANNEL;
205 return premultipliedAlpha ? STBIR_ARGB_PM : STBIR_ARGB;
207 return premultipliedAlpha ? STBIR_BGRA_PM : STBIR_BGRA;
211 return STBIR_2CHANNEL;
213 return premultipliedAlpha ? STBIR_RA_PM : STBIR_RA;
262[[nodiscard]]
constexpr int imageFormatToSTBIRDataType(
ImageFormat format,
bool srgb) {
283 return srgb ? STBIR_TYPE_UINT8_SRGB : STBIR_TYPE_UINT8;
287 return STBIR_TYPE_HALF_FLOAT;
289 return STBIR_TYPE_UINT16;
294 return STBIR_TYPE_FLOAT;
336[[nodiscard]] std::vector<std::byte> decompressImageData(std::span<const std::byte> imageData,
ImageFormat inFormat,
ImageFormat& outFormat, uint16_t width, uint16_t height) {
338 return {imageData.begin(), imageData.end()};
341 uint16_t unpaddedWidth = width, unpaddedHeight = height;
342 if (width % 4 != 0 || height % 4 != 0) {
343 width += (4 - (width % 4)) % 4;
344 height += (4 - (height % 4)) % 4;
347 const auto transformCompressed = [imageData, &outFormat, width, height, unpaddedWidth, unpaddedHeight]<uint8_t InputBlockSize, ImagePixel::PixelType OutputPixel>(void(*callback)(
const void*,
void*, int)) -> std::vector<std::byte> {
348 outFormat = OutputPixel::FORMAT;
350 std::vector<std::byte> out;
351 out.resize(imageData.size() / InputBlockSize *
sizeof(OutputPixel) * (4 * 4));
352 for (uint64_t src = 0, i = 0; i < height; i += 4) {
353 for (uint64_t j = 0; j < width; j += 4) {
357 callback(imageData.data() + src, out.data() + (i * width + j) *
sizeof(OutputPixel), width *
sizeof(OutputPixel));
359 src += InputBlockSize;
363 if (unpaddedWidth % 4 != 0 || unpaddedHeight % 4 != 0) {
370 const auto postProcessBC5 = [&outFormat](std::vector<std::byte>& out) {
374 const auto nX =
static_cast<float>(pixel.u()) / 255.f * 2.f - 1.f;
375 const auto nY =
static_cast<float>(pixel.v()) / 255.f * 2.f - 1.f;
376 return {{pixel.u(), pixel.v(),
static_cast<uint8_t
>(std::clamp(std::sqrt(1.f - (nX * nX) - (nY * nY)), 0.f, 1.f) * 255.f)}};
383 return transformCompressed.operator()<BCDEC_BC1_BLOCK_SIZE, ImagePixel::RGBA8888>(&bcdec_bc1);
385 return transformCompressed.operator()<BCDEC_BC2_BLOCK_SIZE, ImagePixel::RGBA8888>(&bcdec_bc2);
387 return transformCompressed.operator()<BCDEC_BC3_BLOCK_SIZE, ImagePixel::RGBA8888>(&bcdec_bc3);
390 return transformCompressed.operator()<BCDEC_BC4_BLOCK_SIZE, ImagePixel::I8>(&bcdec_bc4);
392 auto out = transformCompressed.operator()<BCDEC_BC5_BLOCK_SIZE, ImagePixel::UV88>([](
const void* compressedBlock,
void* decompressedBlock,
int destinationPitch) {
394 bcdec__smooth_alpha_block(compressedBlock,
static_cast<char*
>(decompressedBlock) + 1, destinationPitch, 2);
395 bcdec__smooth_alpha_block(
static_cast<const char*
>(compressedBlock) + 8, decompressedBlock, destinationPitch, 2);
401 auto out = transformCompressed.operator()<BCDEC_BC5_BLOCK_SIZE, ImagePixel::UV88>(&bcdec_bc5);
407 return transformCompressed.operator()<BCDEC_BC6H_BLOCK_SIZE, ImagePixel::RGB323232F>([](
const void* compressedBlock,
void* decompressedBlock,
int destinationPitch) {
408 return bcdec_bc6h_float(compressedBlock, decompressedBlock, destinationPitch,
false);
411 return transformCompressed.operator()<BCDEC_BC6H_BLOCK_SIZE, ImagePixel::RGB323232F>([](
const void* compressedBlock,
void* decompressedBlock,
int destinationPitch) {
412 return bcdec_bc6h_float(compressedBlock, decompressedBlock, destinationPitch,
true);
416 return transformCompressed.operator()<BCDEC_BC7_BLOCK_SIZE, ImagePixel::RGBA8888>(&bcdec_bc7);
427#ifdef VTFPP_BUILD_WITH_COMPRESSONATOR
428 if (imageData.empty()) {
432 std::vector<std::byte> imageDataReplacement;
434 uint16_t paddingWidth = (4 - (width % 4)) % 4, paddingHeight = (4 - (height % 4)) % 4;
437 imageData = imageDataReplacement;
439 width += paddingWidth;
440 height += paddingHeight;
443 CMP_Texture srcTexture{};
444 srcTexture.dwSize =
sizeof(srcTexture);
445 srcTexture.dwWidth = width;
446 srcTexture.dwHeight = height;
448 srcTexture.format = ::imageFormatToCompressonatorFormat(oldFormat);
449 srcTexture.dwDataSize = imageData.size();
451 srcTexture.pData =
const_cast<CMP_BYTE*
>(
reinterpret_cast<const CMP_BYTE*
>(imageData.data()));
453 CMP_Texture destTexture{};
454 destTexture.dwSize =
sizeof(destTexture);
455 destTexture.dwWidth = width;
456 destTexture.dwHeight = height;
458 destTexture.format = ::imageFormatToCompressonatorFormat(newFormat);
459 destTexture.dwDataSize = CMP_CalculateBufferSize(&destTexture);
461 std::vector<std::byte> destData;
462 destData.resize(destTexture.dwDataSize);
463 destTexture.pData =
reinterpret_cast<CMP_BYTE*
>(destData.data());
465 CMP_CompressOptions options{};
466 options.dwSize =
sizeof(options);
467 if (quality >= 0.f) {
468 options.fquality = std::min(quality, 1.f);
475 options.fquality = 0.1f;
477 options.fquality = 1.f;
480 if (options.bDXT1UseAlpha) {
481 options.nAlphaThreshold = 128;
484 if (CMP_ConvertTexture(&srcTexture, &destTexture, &options,
nullptr) != CMP_OK) {
493[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA8888(std::span<const std::byte> imageData,
ImageFormat format) {
496 if (imageData.empty()) {
501 return {imageData.begin(), imageData.end()};
504 #define VTFPP_REMAP_TO_8(value, shift) math::remap<uint8_t>((value), (1 << (shift)) - 1, (1 << 8) - 1)
506 #define VTFPP_CASE_CONVERT(InputType, r, g, b, a) \
507 case InputType: return ImagePixel::transform<ImagePixel::InputType, ImagePixel::RGBA8888>(imageData, [](ImagePixel::InputType pixel) -> ImagePixel::RGBA8888 { \
508 return {{(r), (g), (b), (a)}}; \
548 #undef VTFPP_CASE_CONVERT
549 #undef VTFPP_REMAP_TO_8
554[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888(std::span<const std::byte> imageData,
ImageFormat format) {
557 if (imageData.empty()) {
562 return {imageData.begin(), imageData.end()};
565 #define VTFPP_REMAP_FROM_8(value, shift) math::remap<uint8_t>((value), (1 << 8) - 1, (1 << (shift)) - 1)
567 #define VTFPP_CASE_CONVERT(OutputType, ...) \
568 case OutputType: return ImagePixel::transform<ImagePixel::RGBA8888, ImagePixel::OutputType>(imageData, [](ImagePixel::RGBA8888 pixel) -> ImagePixel::OutputType { \
569 return {__VA_ARGS__}; \
581 VTFPP_CASE_CONVERT(
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())});
602 VTFPP_CASE_CONVERT(
CONSOLE_I8_LINEAR, {std::clamp<uint8_t>(0.2126 * pixel.r() + 0.7152 * pixel.g() + 0.0722 * pixel.b(), std::numeric_limits<uint8_t>::min(), std::numeric_limits<uint8_t>::max())});
609 #undef VTFPP_CASE_CONVERT
610 #undef VTFPP_REMAP_FROM_8
615[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA16161616(std::span<const std::byte> imageData,
ImageFormat format) {
618 if (imageData.empty()) {
623 return {imageData.begin(), imageData.end()};
626 #define VTFPP_REMAP_TO_16(value, shift) math::remap<uint16_t>((value), (1 << (shift)) - 1, (1 << 16) - 1)
628 #define VTFPP_CASE_CONVERT(InputType, r, g, b, a) \
629 case InputType: return ImagePixel::transform<ImagePixel::InputType, ImagePixel::RGBA16161616>(imageData, [](ImagePixel::InputType pixel) -> ImagePixel::RGBA16161616 { \
630 return {{(r), (g), (b), (a)}}; \
640 #undef VTFPP_CASE_CONVERT
641 #undef VTFPP_REMAP_TO_16
646[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616(std::span<const std::byte> imageData,
ImageFormat format) {
649 if (imageData.empty()) {
654 return {imageData.begin(), imageData.end()};
657 #define VTFPP_REMAP_FROM_16(value, shift) static_cast<uint8_t>(math::remap<uint16_t>((value), (1 << 16) - 1, (1 << (shift)) - 1))
659 #define VTFPP_CASE_CONVERT(OutputType, ...) \
660 case OutputType: return ImagePixel::transform<ImagePixel::RGBA16161616, ImagePixel::OutputType>(imageData, [](ImagePixel::RGBA16161616 pixel) -> ImagePixel::OutputType { \
661 return {__VA_ARGS__}; \
671 #undef VTFPP_CASE_CONVERT
672 #undef VTFPP_REMAP_FROM_16
677[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA32323232F(std::span<const std::byte> imageData,
ImageFormat format) {
678 if (imageData.empty()) {
683 return {imageData.begin(), imageData.end()};
686 #define VTFPP_CASE_CONVERT(InputType, r, g, b, a) \
687 case InputType: return ImagePixel::transform<ImagePixel::InputType, ImagePixel::RGBA32323232F>(imageData, [](ImagePixel::InputType pixel) -> ImagePixel::RGBA32323232F { \
688 return {{(r), (g), (b), (a)}}; \
702 #undef VTFPP_CASE_CONVERT
707[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232F(std::span<const std::byte> imageData,
ImageFormat format) {
710 if (imageData.empty()) {
715 return {imageData.begin(), imageData.end()};
718 #define VTFPP_CASE_CONVERT(OutputType, ...) \
719 case OutputType: return ImagePixel::transform<ImagePixel::RGBA32323232F, ImagePixel::OutputType>(imageData, [](ImagePixel::RGBA32323232F pixel) -> ImagePixel::OutputType { \
720 return {__VA_ARGS__}; \
730 VTFPP_CASE_CONVERT(RGBA16161616F, {half{pixel.r()}, half{pixel.g()}, half{pixel.b()}, half{pixel.a()}});
734 #undef VTFPP_CASE_CONVERT
739[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888ToRGBA32323232F(std::span<const std::byte> imageData) {
740 if (imageData.empty()) {
746 static_cast<float>(pixel.r()) /
static_cast<float>((1 << 8) - 1),
747 static_cast<float>(pixel.g()) /
static_cast<float>((1 << 8) - 1),
748 static_cast<float>(pixel.b()) /
static_cast<float>((1 << 8) - 1),
749 static_cast<float>(pixel.a()) /
static_cast<float>((1 << 8) - 1),
754[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232FToRGBA8888(std::span<const std::byte> imageData) {
755 if (imageData.empty()) {
761 static_cast<uint8_t
>(std::clamp(pixel.r(), 0.f, 1.f) * ((1 << 8) - 1)),
762 static_cast<uint8_t
>(std::clamp(pixel.g(), 0.f, 1.f) * ((1 << 8) - 1)),
763 static_cast<uint8_t
>(std::clamp(pixel.b(), 0.f, 1.f) * ((1 << 8) - 1)),
764 static_cast<uint8_t
>(std::clamp(pixel.a(), 0.f, 1.f) * ((1 << 8) - 1)),
769[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888ToRGBA16161616(std::span<const std::byte> imageData) {
770 if (imageData.empty()) {
784[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616ToRGBA8888(std::span<const std::byte> imageData) {
785 if (imageData.empty()) {
799[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232FToRGBA16161616(std::span<const std::byte> imageData) {
800 if (imageData.empty()) {
806 static_cast<uint16_t
>(std::clamp(pixel.r(), 0.f, 1.f) * ((1 << 16) - 1)),
807 static_cast<uint16_t
>(std::clamp(pixel.g(), 0.f, 1.f) * ((1 << 16) - 1)),
808 static_cast<uint16_t
>(std::clamp(pixel.b(), 0.f, 1.f) * ((1 << 16) - 1)),
809 static_cast<uint16_t
>(std::clamp(pixel.a(), 0.f, 1.f) * ((1 << 16) - 1)),
814[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616ToRGBA32323232F(std::span<const std::byte> imageData) {
815 if (imageData.empty()) {
821 static_cast<float>(pixel.r()) /
static_cast<float>((1 << 16) - 1),
822 static_cast<float>(pixel.g()) /
static_cast<float>((1 << 16) - 1),
823 static_cast<float>(pixel.b()) /
static_cast<float>((1 << 16) - 1),
824 static_cast<float>(pixel.a()) /
static_cast<float>((1 << 16) - 1),
837 oldFormat == newFormat ||
840 return {imageData.begin(), imageData.end()};
843 std::vector<std::byte> newData;
860 newData = ::decompressImageData(imageData, oldFormat, decompressedFormat, width, height);
861 if (decompressedFormat == newFormat) {
864 if (decompressedFormat != intermediaryOldFormat) {
866 case ImageFormat::RGBA8888: newData = ::convertImageDataToRGBA8888(newData, decompressedFormat);
break;
873 switch (intermediaryOldFormat) {
881 if (intermediaryOldFormat == newFormat) {
884 if (intermediaryOldFormat != intermediaryNewFormat) {
887 newData = ::convertImageDataFromRGBA8888ToRGBA16161616(newData);
889 newData = ::convertImageDataFromRGBA8888ToRGBA32323232F(newData);
895 newData = ::convertImageDataFromRGBA16161616ToRGBA8888(newData);
897 newData = ::convertImageDataFromRGBA16161616ToRGBA32323232F(newData);
903 newData = ::convertImageDataFromRGBA32323232FToRGBA8888(newData);
905 newData = ::convertImageDataFromRGBA32323232FToRGBA16161616(newData);
914 if (intermediaryNewFormat == newFormat) {
926 newData = ::compressImageData(newData, intermediaryNewFormat, newFormat, width, height, quality);
928 switch (intermediaryNewFormat) {
944 if (oldFormat == newFormat) {
945 return {imageData.begin(), imageData.end()};
949 for(
int mip = mipCount - 1; mip >= 0; mip--) {
951 for (
int frame = 0; frame < frameCount; frame++) {
952 for (
int face = 0; face < faceCount; face++) {
953 for (
int slice = 0; slice < mipDepth; slice++) {
954 if (uint32_t oldOffset, oldLength;
ImageFormatDetails::getDataPosition(oldOffset, oldLength, oldFormat, mip, mipCount, frame, frameCount, face, faceCount, width, height, slice, depth)) {
956 if (uint32_t newOffset, newLength;
ImageFormatDetails::getDataPosition(newOffset, newLength, newFormat, mip, mipCount, frame, frameCount, face, faceCount, width, height, slice, depth) && newLength == convertedImageData.size()) {
957 std::memcpy(out.data() + newOffset, convertedImageData.data(), newLength);
976 std::span imageDataRGBA32323232F{
reinterpret_cast<const float*
>(imageData.data()),
reinterpret_cast<const float*
>(imageData.data() + imageData.size())};
978 std::vector<std::byte> possiblyConvertedDataOrEmptyDontUseMeDirectly;
981 imageDataRGBA32323232F = {
reinterpret_cast<const float*
>(possiblyConvertedDataOrEmptyDontUseMeDirectly.data()),
reinterpret_cast<const float*
>(possiblyConvertedDataOrEmptyDontUseMeDirectly.data() + possiblyConvertedDataOrEmptyDontUseMeDirectly.size())};
986 static constexpr std::array<std::array<math::Vec3f, 3>, 6> startRightUpCubemap = {{
987 {{{ 1.0f, -1.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}},
988 {{{ 1.0f, 1.0f, 1.0f}, { 0.0f,-1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}},
989 {{{ 1.0f, 1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, { 0.0f,-1.0f, 0.0f}}},
990 {{{-1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}}},
991 {{{ 1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}}},
992 {{{ 1.0f, 1.0f, -1.0f}, { 0.0f, 0.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}},
994 static constexpr std::array<std::array<math::Vec3f, 3>, 6> startRightUpSkybox = {{
995 {{{ 1.0f, -1.0f, -1.0f}, { 0.0f, 0.0f, 1.0f}, { 0.0f, 1.0f, 0.0f}}},
996 {{{-1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}}},
997 {{{-1.0f, -1.0f, -1.0f}, { 1.0f, 0.0f, 0.0f}, { 0.0f, 1.0f, 0.0f}}},
998 {{{ 1.0f, -1.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}, { 0.0f, 1.0f, 0.0f}}},
999 {{{-1.0f, -1.0f, -1.0f}, { 0.0f, 0.0f, 1.0f}, { 1.0f, 0.0f, 0.0f}}},
1000 {{{ 1.0f, 1.0f, -1.0f}, { 0.0f, 0.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}},
1002 const auto& startRightUp = skybox ? startRightUpSkybox : startRightUpCubemap;
1004 std::array<std::vector<std::byte>, 6> faceData;
1006#ifdef SOURCEPP_BUILD_WITH_THREADS
1007 const auto faceExtraction = [&](
int i) {
1009 for (
int i = 0; i < faceData.size(); i++) {
1011 const auto start = startRightUp[i][0];
1012 const auto right = startRightUp[i][1];
1013 const auto up = startRightUp[i][2];
1015 faceData[i].resize(resolution * resolution *
sizeof(ImagePixel::RGBA32323232F));
1016 std::span face{
reinterpret_cast<float*
>(faceData[i].data()),
reinterpret_cast<float*
>(faceData[i].data() + faceData[i].size())};
1018 for (
int row = 0; row < resolution; row++) {
1019 for (
int col = 0; col < resolution; col++) {
1020 math::Vec3f pixelDirection3d{
1021 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],
1022 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],
1023 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],
1025 const float azimuth = std::atan2(pixelDirection3d[0], -pixelDirection3d[2]) +
math::pi_f32;
1026 const float elevation = std::atan(pixelDirection3d[1] / std::sqrt(pixelDirection3d[0] * pixelDirection3d[0] + pixelDirection3d[2] * pixelDirection3d[2])) +
math::pi_f32 / 2.f;
1027 const float colHdri = (azimuth /
math::pi_f32 / 2.f) *
static_cast<float>(width);
1028 const float rowHdri = (elevation /
math::pi_f32) *
static_cast<float>(height);
1030 const int colNearest = std::clamp(
static_cast<int>(colHdri), 0, width - 1);
1031 const int rowNearest = std::clamp(
static_cast<int>(rowHdri), 0, height - 1);
1032 face[col * 4 + resolution * row * 4 + 0] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 0];
1033 face[col * 4 + resolution * row * 4 + 1] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 1];
1034 face[col * 4 + resolution * row * 4 + 2] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 2];
1035 face[col * 4 + resolution * row * 4 + 3] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 3];
1037 float intCol, intRow;
1039 float factorCol = std::modf(colHdri - 0.5f, &intCol);
1040 float factorRow = std::modf(rowHdri - 0.5f, &intRow);
1041 const int low_idx_row =
static_cast<int>(intRow);
1042 const int low_idx_column =
static_cast<int>(intCol);
1043 int high_idx_column;
1044 if (factorCol < 0.f) {
1047 high_idx_column = width - 1;
1048 }
else if (low_idx_column == width - 1) {
1050 high_idx_column = 0;
1052 high_idx_column = low_idx_column + 1;
1055 if (factorRow < 0.f || low_idx_row == height - 1) {
1056 high_idx_row = low_idx_row;
1059 high_idx_row = low_idx_row + 1;
1061 factorCol = std::abs(factorCol);
1062 factorRow = std::abs(factorRow);
1063 const float f1 = (1 - factorRow) * (1 - factorCol);
1064 const float f2 = factorRow * (1 - factorCol);
1065 const float f3 = (1 - factorRow) * factorCol;
1066 const float f4 = factorRow * factorCol;
1067 for (
int j = 0; j < 4; j++) {
1068 face[col * 4 + resolution * row * 4 + j] =
1069 imageDataRGBA32323232F[low_idx_column * 4 + width * low_idx_row * 4 + j] * f1 +
1070 imageDataRGBA32323232F[low_idx_column * 4 + width * high_idx_row * 4 + j] * f2 +
1071 imageDataRGBA32323232F[high_idx_column * 4 + width * low_idx_row * 4 + j] * f3 +
1072 imageDataRGBA32323232F[high_idx_column * 4 + width * high_idx_row * 4 + j] * f4;
1081#ifdef SOURCEPP_BUILD_WITH_THREADS
1083 std::array faceFutures{
1084 std::async(std::launch::async, faceExtraction, 0),
1085 std::async(std::launch::async, faceExtraction, 1),
1086 std::async(std::launch::async, faceExtraction, 2),
1087 std::async(std::launch::async, faceExtraction, 3),
1088 std::async(std::launch::async, faceExtraction, 4),
1089 std::async(std::launch::async, faceExtraction, 5),
1091 for (
auto& future : faceFutures) {
1100 if (imageData.empty()) {
1105 static constexpr auto compressChannel = [](
float c,
float a,
float fac) {
1106 return static_cast<uint8_t
>(std::clamp(std::round(c / (a * fac) * 255.f), 0.f, 255.f));
1108 const auto alpha =
static_cast<uint8_t
>(std::clamp(std::round((overbrightFactor != 0.f ? std::max({pixel.r(), pixel.g(), pixel.b()}) : 0.f) * 255.f), 0.f, 255.f));
1110 .b = compressChannel(pixel.b(), alpha, overbrightFactor),
1111 .g = compressChannel(pixel.g(), alpha, overbrightFactor),
1112 .r = compressChannel(pixel.r(), alpha, overbrightFactor),
1119 if (imageData.empty()) {
1124 static constexpr auto decompressChannel = [](uint8_t c,
float a,
float fac) {
1125 return (
static_cast<float>(c) / 255) * a * fac;
1127 const auto alpha =
static_cast<float>(pixel.a());
1129 .r = decompressChannel(pixel.r(), alpha, overbrightFactor),
1130 .g = decompressChannel(pixel.g(), alpha, overbrightFactor),
1131 .b = decompressChannel(pixel.b(), alpha, overbrightFactor),
1138 if (imageData.empty()) {
1143 static constexpr auto compressChannel = [](
float c,
bool flip) {
1144 auto out =
static_cast<uint16_t
>(std::round(c * (1 << 12)));
1146 const uint16_t exponent = out & 0b1111;
1148 out |= exponent << 12;
1153 .r = compressChannel(pixel.r(), flipExponentAndSignificand),
1154 .g = compressChannel(pixel.g(), flipExponentAndSignificand),
1155 .b = compressChannel(pixel.b(), flipExponentAndSignificand),
1156 .a = compressChannel(1.f, flipExponentAndSignificand),
1162 if (imageData.empty()) {
1167 static constexpr auto decompressChannel = [](uint16_t c,
bool flip) {
1169 const uint16_t exponent = c & 0b1111;
1171 c |= exponent << 12;
1173 return static_cast<float>(c) / (1 << 12);
1176 .r = decompressChannel(pixel.r(), flipExponentAndSignificand),
1177 .g = decompressChannel(pixel.g(), flipExponentAndSignificand),
1178 .b = decompressChannel(pixel.b(), flipExponentAndSignificand),
1186#ifdef VTFPP_SUPPORT_EXR
1197 std::vector<std::byte> out;
1198 auto stbWriteFunc = [](
void* out_,
void* data,
int size) {
1199 std::copy_n(
static_cast<std::byte*
>(data), size, std::back_inserter(*
static_cast<std::vector<std::byte>*
>(out_)));
1205 quality = std::min(quality, 1.f);
1207 switch (fileFormat) {
1210 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), imageData.data(), 0);
1212 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGBA8888), imageData.data(), 0);
1215 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), rgb.data(), 0);
1218 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGBA8888), rgba.data(), 0);
1224 stbi_write_jpg_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), imageData.data(), quality < 0.f ? 100 :
static_cast<int>(quality * 100.f));
1227 stbi_write_jpg_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), rgb.data(), quality < 0.f ? 100 :
static_cast<int>(quality * 100.f));
1233 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), imageData.data());
1235 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGBA8888), imageData.data());
1238 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), rgb.data());
1241 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGBA8888), rgba.data());
1247 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), imageData.data());
1249 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGBA8888), imageData.data());
1252 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), rgb.data());
1255 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGBA8888), rgba.data());
1259#ifdef VTFPP_SUPPORT_WEBP
1260 case FileFormat::WEBP: {
1262 WebPConfigInit(&config);
1263 WebPConfigPreset(&config, WEBP_PRESET_DRAWING, quality > 0.f ? quality : 75.f);
1264 if (quality < 0.f) {
1265 WebPConfigLosslessPreset(&config, 6);
1269 if (!WebPPictureInit(&pic)) {
1273 pic.height = height;
1274 if (!WebPPictureAlloc(&pic)) {
1279 WebPPictureImportRGB(&pic,
reinterpret_cast<const uint8_t*
>(imageData.data()),
static_cast<int>(width *
sizeof(ImagePixel::RGB888)));
1281 WebPPictureImportRGBA(&pic,
reinterpret_cast<const uint8_t*
>(imageData.data()),
static_cast<int>(width *
sizeof(ImagePixel::RGBA8888)));
1284 WebPPictureImportRGB(&pic,
reinterpret_cast<const uint8_t*
>(rgb.data()),
static_cast<int>(width *
sizeof(ImagePixel::RGB888)));
1287 WebPPictureImportRGBA(&pic,
reinterpret_cast<const uint8_t*
>(rgba.data()),
static_cast<int>(width *
sizeof(ImagePixel::RGBA8888)));
1290 WebPMemoryWriter writer;
1291 WebPMemoryWriterInit(&writer);
1292 pic.writer = &WebPMemoryWrite;
1293 pic.custom_ptr = &writer;
1295 int ok = WebPEncode(&config, &pic);
1296 WebPPictureFree(&pic);
1298 WebPMemoryWriterClear(&writer);
1302 if (writer.mem && writer.size) {
1303 out.resize(writer.size);
1304 std::memcpy(out.data(), writer.mem, writer.size);
1306 WebPMemoryWriterClear(&writer);
1310#ifdef VTFPP_SUPPORT_QOI
1311 case FileFormat::QOI: {
1312 qoi_desc descriptor{
1316 .colorspace = QOI_SRGB,
1318 void* qoiData =
nullptr;
1321 descriptor.channels = 3;
1322 qoiData = qoi_encode(imageData.data(), &descriptor, &qoiDataLen);
1324 descriptor.channels = 4;
1325 qoiData = qoi_encode(imageData.data(), &descriptor, &qoiDataLen);
1328 descriptor.channels = 3;
1329 qoiData = qoi_encode(rgb.data(), &descriptor, &qoiDataLen);
1332 descriptor.channels = 4;
1333 qoiData = qoi_encode(rgba.data(), &descriptor, &qoiDataLen);
1335 if (qoiData && qoiDataLen) {
1336 out.resize(qoiDataLen);
1337 std::memcpy(out.data(), qoiData, qoiDataLen);
1352#ifdef VTFPP_SUPPORT_EXR
1353 case FileFormat::EXR: {
1355 InitEXRHeader(&header);
1357 std::vector<std::byte> rawData;
1367 rawData = {imageData.begin(), imageData.end()};
1371 header.channels =
static_cast<EXRChannelInfo*
>(std::malloc(header.num_channels *
sizeof(EXRChannelInfo)));
1372 header.pixel_types =
static_cast<int*
>(malloc(header.num_channels *
sizeof(
int)));
1373 header.requested_pixel_types =
static_cast<int*
>(malloc(header.num_channels *
sizeof(
int)));
1375 switch (header.num_channels) {
1377 header.channels[0].name[0] =
'A';
1378 header.channels[1].name[0] =
'B';
1379 header.channels[2].name[0] =
'G';
1380 header.channels[3].name[0] =
'R';
1383 header.channels[0].name[0] =
'B';
1384 header.channels[1].name[0] =
'G';
1385 header.channels[2].name[0] =
'R';
1388 header.channels[0].name[0] =
'G';
1389 header.channels[1].name[0] =
'R';
1392 header.channels[0].name[0] =
'R';
1395 FreeEXRHeader(&header);
1398 for (
int i = 0; i < header.num_channels; i++) {
1399 header.channels[i].name[1] =
'\0';
1402 int pixelType = (
ImageFormatDetails::red(format) / 8) ==
sizeof(half) ? TINYEXR_PIXELTYPE_HALF : TINYEXR_PIXELTYPE_FLOAT;
1403 for (
int i = 0; i < header.num_channels; i++) {
1404 header.pixel_types[i] = pixelType;
1405 header.requested_pixel_types[i] = pixelType;
1408 std::vector<std::vector<std::byte>> images(header.num_channels);
1409 std::vector<void*> imagePtrs(header.num_channels);
1410 switch (header.num_channels) {
1412 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1413 images[0] = extractChannelFromImageData(imageData, &ImagePixel::RGBA16161616F::a);
1414 images[1] = extractChannelFromImageData(imageData, &ImagePixel::RGBA16161616F::b);
1415 images[2] = extractChannelFromImageData(imageData, &ImagePixel::RGBA16161616F::g);
1416 images[3] = extractChannelFromImageData(imageData, &ImagePixel::RGBA16161616F::r);
1418 images[0] = extractChannelFromImageData(imageData, &ImagePixel::RGBA32323232F::a);
1419 images[1] = extractChannelFromImageData(imageData, &ImagePixel::RGBA32323232F::b);
1420 images[2] = extractChannelFromImageData(imageData, &ImagePixel::RGBA32323232F::g);
1421 images[3] = extractChannelFromImageData(imageData, &ImagePixel::RGBA32323232F::r);
1425 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1427 FreeEXRHeader(&header);
1430 images[0] = extractChannelFromImageData(imageData, &ImagePixel::RGB323232F::b);
1431 images[1] = extractChannelFromImageData(imageData, &ImagePixel::RGB323232F::g);
1432 images[2] = extractChannelFromImageData(imageData, &ImagePixel::RGB323232F::r);
1435 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1436 images[0] = extractChannelFromImageData(imageData, &ImagePixel::RG1616F::g);
1437 images[1] = extractChannelFromImageData(imageData, &ImagePixel::RG1616F::r);
1439 images[0] = extractChannelFromImageData(imageData, &ImagePixel::RG3232F::g);
1440 images[1] = extractChannelFromImageData(imageData, &ImagePixel::RG3232F::r);
1444 images[0] = rawData;
1447 FreeEXRHeader(&header);
1450 for (
int i = 0; i < header.num_channels; i++) {
1451 imagePtrs[i] = images[i].data();
1455 InitEXRImage(&image);
1456 image.width = width;
1457 image.height = height;
1458 image.images =
reinterpret_cast<unsigned char**
>(imagePtrs.data());
1459 image.num_channels = header.num_channels;
1461 unsigned char* data =
nullptr;
1462 const char* err =
nullptr;
1464 size_t size = SaveEXRImageToMemory(&image, &header, &data, &err);
1466 FreeEXRErrorMessage(err);
1467 FreeEXRHeader(&header);
1471 out = {
reinterpret_cast<std::byte*
>(data),
reinterpret_cast<std::byte*
>(data) + size};
1475 FreeEXRHeader(&header);
1479#ifdef VTFPP_SUPPORT_JXL
1480 case FileFormat::JXL: {
1481 auto encoder = JxlEncoderMake(
nullptr);
1486#ifdef SOURCEPP_BUILD_WITH_THREADS
1487 auto runner = JxlThreadParallelRunnerMake(
nullptr, JxlThreadParallelRunnerDefaultNumWorkerThreads());
1488 if (JxlEncoderSetParallelRunner(encoder.get(), &JxlThreadParallelRunner, runner.get()) != JXL_ENC_SUCCESS) {
1494 JxlEncoderInitBasicInfo(&info);
1496 info.ysize = height;
1499 auto* frameSettings = JxlEncoderFrameSettingsCreate(encoder.get(),
nullptr);
1500 if (!frameSettings) {
1503 JxlEncoderFrameSettingsSetOption(frameSettings, JXL_ENC_FRAME_SETTING_DECODING_SPEED, 0);
1504 if (quality < 0.f) {
1505 JxlEncoderSetFrameLossless(frameSettings,
true);
1506 info.uses_original_profile =
true;
1508 JxlEncoderSetFrameDistance(frameSettings, JxlEncoderDistanceFromQuality(quality * 100.f));
1509 info.uses_original_profile =
false;
1512 JxlPixelFormat pixelFormat{ .endianness = JXL_LITTLE_ENDIAN };
1513 std::vector<std::byte> imageDataConverted;
1517 info.bits_per_sample = 32;
1518 info.exponent_bits_per_sample = 8;
1520 pixelFormat.num_channels = 3;
1521 info.alpha_bits = 0;
1522 info.alpha_exponent_bits = 0;
1524 pixelFormat.num_channels = 4;
1525 info.alpha_bits = 32;
1526 info.alpha_exponent_bits = 8;
1529 pixelFormat.num_channels = 3;
1530 info.alpha_bits = 0;
1531 info.alpha_exponent_bits = 0;
1534 pixelFormat.num_channels = 4;
1535 info.alpha_bits = 32;
1536 info.alpha_exponent_bits = 8;
1538 pixelFormat.data_type = JXL_TYPE_FLOAT;
1541 info.bits_per_sample = 16;
1542 info.exponent_bits_per_sample = 5;
1543 pixelFormat.num_channels = 4;
1544 info.alpha_bits = 16;
1545 info.alpha_exponent_bits = 5;
1546 pixelFormat.data_type = JXL_TYPE_UINT16;
1550 info.bits_per_sample = 8;
1552 pixelFormat.num_channels = 3;
1553 info.alpha_bits = 0;
1555 pixelFormat.num_channels = 4;
1556 info.alpha_bits = 8;
1559 pixelFormat.num_channels = 3;
1560 info.alpha_bits = 0;
1563 pixelFormat.num_channels = 4;
1564 info.alpha_bits = 8;
1566 pixelFormat.data_type = JXL_TYPE_UINT8;
1571 if (!imageDataConverted.empty()) {
1572 imageData = imageDataConverted;
1575 if (JxlEncoderSetBasicInfo(encoder.get(), &info) != JXL_ENC_SUCCESS) {
1579 JxlColorEncoding colorEncoding;
1581 JxlColorEncodingSetToSRGB(&colorEncoding,
false);
1583 JxlColorEncodingSetToLinearSRGB(&colorEncoding,
false);
1585 if (JxlEncoderSetColorEncoding(encoder.get(), &colorEncoding) != JXL_ENC_SUCCESS) {
1589 if (JxlEncoderAddImageFrame(frameSettings, &pixelFormat, imageData.data(), imageData.size()) != JXL_ENC_SUCCESS) {
1592 JxlEncoderCloseInput(encoder.get());
1594 out.resize(2 * 1024 * 1024);
1595 auto* nextOut = out.data();
1596 size_t availOut = out.size();
1597 JxlEncoderStatus result;
1599 if ((result = JxlEncoderProcessOutput(encoder.get(),
reinterpret_cast<uint8_t**
>(&nextOut), &availOut)) != JXL_ENC_NEED_MORE_OUTPUT) {
1602 const auto offset = nextOut - out.data();
1603 out.resize(out.size() * 2);
1604 nextOut = out.data() + offset;
1605 availOut = out.size() - offset;
1607 if (result != JXL_ENC_SUCCESS) {
1610 out.resize(nextOut - out.data());
1623using stb_ptr = std::unique_ptr<T, void(*)(
void*)>;
1628 stbi_convert_iphone_png_to_rgb(
true);
1636#ifdef VTFPP_SUPPORT_JXL
1638 if (
const auto signature = JxlSignatureCheck(
reinterpret_cast<const uint8_t*
>(fileData.data()), fileData.size()); signature == JXL_SIG_CODESTREAM || signature == JXL_SIG_CONTAINER) {
1639 auto decoder = JxlDecoderMake(
nullptr);
1644#ifdef SOURCEPP_BUILD_WITH_THREADS
1645 auto runner = JxlResizableParallelRunnerMake(
nullptr);
1646 if (JxlDecoderSetParallelRunner(decoder.get(), &JxlResizableParallelRunner, runner.get()) != JXL_DEC_SUCCESS) {
1651 if (JxlDecoderSubscribeEvents(decoder.get(), JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
1654 JxlDecoderSetInput(decoder.get(),
reinterpret_cast<const uint8_t*
>(fileData.data()), fileData.size());
1655 JxlDecoderCloseInput(decoder.get());
1657 std::vector<std::byte> out;
1658 JxlPixelFormat pixelFormat{ .endianness = JXL_LITTLE_ENDIAN };
1659 bool infoHasBeenSet =
false;
1661 switch (JxlDecoderProcessInput(decoder.get())) {
1664 case JXL_DEC_NEED_MORE_INPUT:
1666 case JXL_DEC_BASIC_INFO: {
1668 if (JxlDecoderGetBasicInfo(decoder.get(), &info) != JXL_DEC_SUCCESS) {
1671 width =
static_cast<int>(info.xsize);
1672 height =
static_cast<int>(info.ysize);
1673 JxlResizableParallelRunnerSetThreads(runner.get(), JxlResizableParallelRunnerSuggestThreads(width, height));
1675 if (info.bits_per_sample > 16) {
1676 pixelFormat.data_type = JXL_TYPE_FLOAT;
1677 if (!info.alpha_bits) {
1678 pixelFormat.num_channels = 3;
1681 pixelFormat.num_channels = 4;
1684 }
else if (info.bits_per_sample > 8) {
1685 pixelFormat.data_type = JXL_TYPE_UINT16;
1686 pixelFormat.num_channels = 4;
1689 pixelFormat.data_type = JXL_TYPE_UINT8;
1690 if (!info.alpha_bits) {
1691 pixelFormat.num_channels = 3;
1694 pixelFormat.num_channels = 4;
1699 infoHasBeenSet =
true;
1702 case JXL_DEC_NEED_IMAGE_OUT_BUFFER: {
1703 if (!infoHasBeenSet) {
1707 if (JxlDecoderImageOutBufferSize(decoder.get(), &pixelFormat, &bufferSize) != JXL_DEC_SUCCESS) {
1710 out.resize(bufferSize);
1711 if (JxlDecoderSetImageOutBuffer(decoder.get(), &pixelFormat, out.data(), out.size()) != JXL_DEC_SUCCESS) {
1716 case JXL_DEC_FULL_IMAGE:
1717 case JXL_DEC_SUCCESS:
1724#ifdef VTFPP_SUPPORT_EXR
1726 if (EXRVersion version; ParseEXRVersionFromMemory(&version,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size()) == TINYEXR_SUCCESS) {
1727 if (version.multipart || version.non_image) {
1732 InitEXRHeader(&header);
1733 const char* err =
nullptr;
1734 if (ParseEXRHeaderFromMemory(&header, &version,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size(), &err) != TINYEXR_SUCCESS) {
1735 FreeEXRErrorMessage(err);
1740 if (header.num_channels < 1) {
1741 FreeEXRHeader(&header);
1746 std::unordered_map<std::string_view, int> channelIndices{{
"R", -1}, {
"G", -1}, {
"B", -1}, {
"A", -1}, {
"Y", -1}};
1750 auto channelType = header.pixel_types[0];
1751 for (
int i = 1; i < header.num_channels; i++) {
1753 if (header.pixel_types[i] > channelType && channelIndices.contains(header.channels[i].name)) {
1754 channelType = header.pixel_types[i];
1758 if (channelType == TINYEXR_PIXELTYPE_UINT) {
1759 channelType = TINYEXR_PIXELTYPE_HALF;
1763 for (
int i = 0; i < header.num_channels; i++) {
1764 if (channelIndices.contains(header.channels[i].name)) {
1765 channelIndices[header.channels[i].name] = i;
1768 if (channelIndices[
"Y"] >= 0) {
1769 if (channelIndices[
"A"] >= 0) {
1772 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1774 channelType = TINYEXR_PIXELTYPE_FLOAT;
1778 channelIndices[
"R"] = channelIndices[
"Y"];
1779 channelIndices[
"G"] = channelIndices[
"Y"];
1780 channelIndices[
"B"] = channelIndices[
"Y"];
1781 }
else if (channelIndices[
"A"] >= 0) {
1783 }
else if (channelIndices[
"B"] >= 0) {
1784 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1786 channelType = TINYEXR_PIXELTYPE_FLOAT;
1789 }
else if (channelIndices[
"G"] >= 0) {
1791 }
else if (channelIndices[
"R"] >= 0) {
1794 FreeEXRHeader(&header);
1799 for (
int i = 0; i < header.num_channels; i++) {
1800 if (header.pixel_types[i] != channelType && channelIndices.contains(header.channels[i].name)) {
1801 header.requested_pixel_types[i] = channelType;
1806 InitEXRImage(&image);
1807 if (LoadEXRImageFromMemory(&image, &header,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size(), &err) != TINYEXR_SUCCESS) {
1808 FreeEXRErrorMessage(err);
1809 FreeEXRHeader(&header);
1813 width = image.width;
1814 height = image.height;
1818 const auto populateBuffer = [
1826 r=channelIndices[
"R"],
1827 g=channelIndices[
"G"],
1828 b=channelIndices[
"B"],
1829 a=channelIndices[
"A"],
1833 const auto channelCount = hasRed + hasGreen + hasBlue + hasAlpha;
1835 std::span out{
reinterpret_cast<C*
>(combinedChannels.data()), combinedChannels.size() /
sizeof(C)};
1837 for (
int t = 0; t < image.num_tiles; t++) {
1838 auto** src =
reinterpret_cast<C**
>(image.tiles[t].images);
1839 for (
int j = 0; j < header.tile_size_y; j++) {
1840 for (
int i = 0; i < header.tile_size_x; i++) {
1841 const auto ii =
static_cast<uint64_t
>(image.tiles[t].offset_x) * header.tile_size_x + i;
1842 const auto jj =
static_cast<uint64_t
>(image.tiles[t].offset_y) * header.tile_size_y + j;
1843 const auto idx = ii + jj * image.width;
1845 if (ii >= image.width || jj >= image.height) {
1849 const auto srcIdx = j *
static_cast<uint64_t
>(header.tile_size_x) + i;
1850 if (r >= 0) out[idx * channelCount + 0] = src[r][srcIdx];
1851 else if (hasRed) out[idx * channelCount + 0] = 0.f;
1852 if (g >= 0) out[idx * channelCount + 1] = src[g][srcIdx];
1853 else if (hasGreen) out[idx * channelCount + 1] = 0.f;
1854 if (b >= 0) out[idx * channelCount + 2] = src[b][srcIdx];
1855 else if (hasBlue) out[idx * channelCount + 2] = 0.f;
1856 if (a >= 0) out[idx * channelCount + 3] = src[a][srcIdx];
1857 else if (hasAlpha) out[idx * channelCount + 3] = 1.f;
1862 auto** src =
reinterpret_cast<C**
>(image.images);
1863 for (uint64_t i = 0; i < width * height; i++) {
1864 if (r >= 0) out[i * channelCount + 0] = src[r][i];
1865 else if (hasRed) out[i * channelCount + 0] = 0.f;
1866 if (g >= 0) out[i * channelCount + 1] = src[g][i];
1867 else if (hasGreen) out[i * channelCount + 1] = 0.f;
1868 if (b >= 0) out[i * channelCount + 2] = src[b][i];
1869 else if (hasBlue) out[i * channelCount + 2] = 0.f;
1870 if (a >= 0) out[i * channelCount + 3] = src[a][i];
1871 else if (hasAlpha) out[i * channelCount + 3] = 1.f;
1875 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1876 populateBuffer.operator()<half>();
1878 populateBuffer.operator()<
float>();
1881 FreeEXRImage(&image);
1882 FreeEXRHeader(&header);
1883 return combinedChannels;
1888 if (stbi_is_hdr_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()))) {
1889 const ::stb_ptr<float> stbImage{
1890 stbi_loadf_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1906#ifdef VTFPP_SUPPORT_WEBP
1908 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) {
1909 width = features.width;
1910 height = features.height;
1914 std::vector<std::byte> out;
1915 if (features.has_alpha) {
1918 if (!WebPDecodeRGBAInto(
reinterpret_cast<const uint8_t*
>(fileData.data()), fileData.size(),
reinterpret_cast<uint8_t*
>(out.data()), out.size(), width * (
ImageFormatDetails::bpp(format) / 8))) {
1924 if (!WebPDecodeRGBInto(
reinterpret_cast<const uint8_t*
>(fileData.data()), fileData.size(),
reinterpret_cast<uint8_t*
>(out.data()), out.size(), width * (
ImageFormatDetails::bpp(format) / 8))) {
1933 if (fileData.size() > 3 &&
static_cast<char>(fileData[0]) ==
'G' &&
static_cast<char>(fileData[1]) ==
'I' &&
static_cast<char>(fileData[2]) ==
'F') {
1934 const ::stb_ptr<stbi_uc> stbImage{
1935 stbi_load_gif_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()),
nullptr, &width, &height, &frameCount, &channels, 0),
1938 if (!stbImage || !frameCount) {
1948 return {
reinterpret_cast<std::byte*
>(stbImage.get()),
reinterpret_cast<std::byte*
>(stbImage.get() + (
ImageFormatDetails::getDataLength(format, width, height) * frameCount))};
1954 stbi__start_mem(&s,
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()));
1955 if (stbi__png_test(&s)) {
1957 const auto apngDecoder = [&format, &width, &height, &frameCount]<
typename P>(
const auto& stbImage, std::size_t dirOffset) -> std::vector<std::byte> {
1958 auto* dir =
reinterpret_cast<stbi__apng_directory*
>(stbImage.get() + dirOffset);
1959 if (dir->type != STBI__STRUCTURE_TYPE_APNG_DIRECTORY) {
1964 frameCount =
static_cast<int>(dir->num_frames);
1966 static constexpr auto calcPixelOffset = [](uint32_t offsetX, uint32_t offsetY, uint32_t width_) {
1967 return ((offsetY * width_) + offsetX) *
sizeof(P);
1971 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) {
1972 for (uint32_t y = 0; y < srcHeight; y++) {
1974#ifdef SOURCEPP_BUILD_WITH_TBB
1975 std::execution::unseq,
1977 src.data() + calcPixelOffset( 0, y, srcWidth),
1978 src.data() + calcPixelOffset( srcWidth, y, srcWidth),
1979 dst.data() + calcPixelOffset(srcOffsetX, srcOffsetY + y, dstWidth));
1984 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) {
1985 for (uint32_t y = subOffsetY; y < subOffsetY + subHeight; y++) {
1987#ifdef SOURCEPP_BUILD_WITH_TBB
1988 std::execution::unseq,
1990 src.data() + calcPixelOffset(subOffsetX, y, imgWidth),
1991 src.data() + calcPixelOffset(subOffsetX + subWidth, y, imgWidth),
1992 dst.data() + calcPixelOffset(subOffsetX, y, imgWidth));
1996 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) {
1997 for (uint32_t y = 0; y < clrHeight; y++) {
1999#ifdef SOURCEPP_BUILD_WITH_TBB
2000 std::execution::unseq,
2002 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth),
2003 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth) + (clrWidth *
sizeof(P)),
2004 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth),
2005 [](std::byte) {
return std::byte{0}; });
2009 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) {
2010 for (uint32_t y = 0; y < srcHeight; y++) {
2011 const auto* sp =
reinterpret_cast<const uint8_t*
>(src.data() + calcPixelOffset(0, y, srcWidth));
2012 auto* dp =
reinterpret_cast<uint8_t*
>(dst.data() + calcPixelOffset(srcOffsetX, srcOffsetY + y, dstWidth));
2013 for (uint32_t x = 0; x < srcWidth; x++, sp += 4, dp += 4) {
2017 if ((sp[3] == 0xff) || (dp[3] == 0)) {
2018 std::copy_n(sp,
sizeof(P), dp);
2020 const int u = sp[3] * 0xff;
2021 const int v = (0xff - sp[3]) * dp[3];
2022 const int al = u + v;
2023 dp[0] = (sp[0] * u + dp[0] * v) / al;
2024 dp[1] = (sp[1] * u + dp[1] * v) / al;
2025 dp[2] = (sp[2] * u + dp[2] * v) / al;
2033 const uint64_t fullFrameSize =
sizeof(P) * width * height;
2034 uint64_t currentFrameSize = 0;
2035 std::vector<std::byte> out(fullFrameSize * frameCount);
2036 uint64_t srcFrameOffset = 0;
2037 uint64_t dstFrameOffset = 0;
2038 for (uint32_t i = 0; i < dir->num_frames; i++) {
2039 const auto& frame = dir->frames[i];
2040 currentFrameSize =
sizeof(P) * frame.width * frame.height;
2043 if (frame.width == width && frame.height == height && frame.x_offset == 0 && frame.y_offset == 0 && frame.blend_op == STBI_APNG_blend_op_source) {
2044 std::memcpy(out.data() + dstFrameOffset, stbImage.get() + srcFrameOffset, fullFrameSize);
2047 if (frame.blend_op == STBI_APNG_blend_op_source || (i == 0 && frame.blend_op == STBI_APNG_blend_op_over)) {
2048 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);
2049 }
else if (frame.blend_op == STBI_APNG_blend_op_over) {
2050 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);
2056 dstFrameOffset += fullFrameSize;
2057 srcFrameOffset += currentFrameSize;
2060 if (i == dir->num_frames - 1) {
2065 copyImageData({out.data() + dstFrameOffset, out.data() + dstFrameOffset + fullFrameSize}, width, height, {out.data() + dstFrameOffset - fullFrameSize, out.data() + dstFrameOffset}, width, height, 0, 0);
2068 if (frame.dispose_op == STBI_APNG_dispose_op_background || (i == 0 && frame.dispose_op == STBI_APNG_dispose_op_previous)) {
2069 clearImageData({out.data() + dstFrameOffset, out.data() + dstFrameOffset + fullFrameSize}, width, height, frame.width, frame.height, frame.x_offset, frame.y_offset);
2070 }
else if (frame.dispose_op == STBI_APNG_dispose_op_previous) {
2071 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);
2072 }
else if (frame.dispose_op != STBI_APNG_dispose_op_none) {
2079 static const char *dispose_ops[] = {
2080 "STBI_APNG_dispose_op_none",
2081 "STBI_APNG_dispose_op_background",
2082 "STBI_APNG_dispose_op_previous",
2085 static const char *blend_ops[] = {
2086 "STBI_APNG_blend_op_source",
2087 "STBI_APNG_blend_op_over",
2090 fprintf(stderr,
"dir_offset : %zu\n", dirOffset);
2091 fprintf(stderr,
"dir.type : %.*s\n", 4, (
unsigned char *) &dir->type);
2092 fprintf(stderr,
"dir.num_frames : %u\n", dir->num_frames);
2093 fprintf(stderr,
"dir.default_image_is_first_frame : %s\n",
2094 dir->default_image_is_first_frame ?
"yes" :
"no");
2095 fprintf(stderr,
"dir.num_plays : %u\n", dir->num_plays);
2097 for (
int i = 0; i < dir->num_frames; ++i) {
2098 stbi__apng_frame_directory_entry *frame = &dir->frames[i];
2100 fprintf(stderr,
"frame : %u\n", i);
2101 fprintf(stderr,
" width : %u\n", frame->width);
2102 fprintf(stderr,
" height : %u\n", frame->height);
2103 fprintf(stderr,
" x_offset : %u\n", frame->x_offset);
2104 fprintf(stderr,
" y_offset : %u\n", frame->y_offset);
2105 fprintf(stderr,
" delay_num : %u\n", frame->delay_num);
2106 fprintf(stderr,
" delay_den : %u\n", frame->delay_den);
2107 fprintf(stderr,
" dispose_op : %s\n", dispose_ops[frame->dispose_op]);
2108 fprintf(stderr,
" blend_op : %s\n", blend_ops[frame->blend_op]);
2114 std::size_t dirOffset = 0;
2115 if (stbi__png_is16(&s)) {
2116 const ::stb_ptr<stbi_us> stbImage{
2117 stbi__apng_load_16bit(&s, &width, &height, &channels, STBI_rgb_alpha, &dirOffset),
2120 if (stbImage && dirOffset) {
2121 return apngDecoder.operator()<ImagePixel::RGBA16161616>(stbImage, dirOffset);
2124 const ::stb_ptr<stbi_uc> stbImage{
2125 stbi__apng_load_8bit(&s, &width, &height, &channels, STBI_rgb_alpha, &dirOffset),
2128 if (stbImage && dirOffset) {
2129 return apngDecoder.operator()<ImagePixel::RGBA8888>(stbImage, dirOffset);
2135#ifdef VTFPP_SUPPORT_QOI
2138 qoi_desc descriptor;
2139 const ::stb_ptr<std::byte> qoiImage{
2140 static_cast<std::byte*
>(qoi_decode(fileData.data(),
static_cast<int>(fileData.size()), &descriptor, 0)),
2146 width =
static_cast<int>(descriptor.width);
2147 height =
static_cast<int>(descriptor.height);
2148 channels = descriptor.channels;
2159 if (stbi_is_16_bit_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()))) {
2160 const ::stb_ptr<stbi_us> stbImage{
2161 stbi_load_16_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
2167 if (channels == 4) {
2169 }
else if (channels >= 1 && channels < 4) {
2173 std::span outPixels{
reinterpret_cast<ImagePixel::RGBA16161616*
>(out.data()), out.size() /
sizeof(ImagePixel::RGBA16161616)};
2176 std::span inPixels{stbImage.get(), outPixels.size()};
2178#ifdef SOURCEPP_BUILD_WITH_TBB
2179 std::execution::par_unseq,
2181 inPixels.begin(), inPixels.end(), outPixels.begin(), [](uint16_t pixel) -> ImagePixel::RGBA16161616 {
2182 return {{pixel, 0, 0, 0xffff}};
2191 std::span inPixels{
reinterpret_cast<RG1616*
>(stbImage.get()), outPixels.size()};
2193#ifdef SOURCEPP_BUILD_WITH_TBB
2194 std::execution::par_unseq,
2196 inPixels.begin(), inPixels.end(), outPixels.begin(), [](RG1616 pixel) -> ImagePixel::RGBA16161616 {
2197 return {{pixel.r, pixel.g, 0, 0xffff}};
2207 std::span inPixels{
reinterpret_cast<RGB161616*
>(stbImage.get()), outPixels.size()};
2209#ifdef SOURCEPP_BUILD_WITH_TBB
2210 std::execution::par_unseq,
2212 inPixels.begin(), inPixels.end(), outPixels.begin(), [](RGB161616 pixel) -> ImagePixel::RGBA16161616 {
2213 return {{pixel.r, pixel.g, pixel.b, 0xffff}};
2227 const ::stb_ptr<stbi_uc> stbImage{
2228 stbi_load_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
2257 return {width, height};
2282 return convertImageDataToFormat(
resizeImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, newWidth, height, newHeight, srgb, premultipliedAlpha, filter, edge), container, format, newWidth, newHeight);
2285 STBIR_RESIZE resize;
2286 const auto setEdgeModesAndFiltersAndDoResize = [edge, filter, &resize] {
2287 stbir_set_edgemodes(&resize,
static_cast<stbir_edge
>(edge),
static_cast<stbir_edge
>(edge));
2296 stbir_set_filters(&resize,
static_cast<stbir_filter
>(filter),
static_cast<stbir_filter
>(filter));
2300 static constexpr auto KAISER_RADIUS = 3.f;
2301 static constexpr auto KAISER_ALPHA = 2.f;
2302 static constexpr auto KAISER_WINDOW = [](
double u) ->
double {
2303 if (u < -1 || u > 1)
return 0;
2306 static constexpr auto KAISER_FILTER = [](
float x, float,
void*) ->
float {
2307 if (x <= -KAISER_RADIUS || x >= KAISER_RADIUS)
return 0;
2308 if (std::abs(x) < 1e-6f)
return 1;
2309 return static_cast<float>(
math::sinc(x) * KAISER_WINDOW(std::abs(x) / KAISER_RADIUS));
2311 static constexpr auto KAISER_SUPPORT = [](float,
void*) ->
float {
2312 return KAISER_RADIUS;
2314 stbir_set_filter_callbacks(&resize, KAISER_FILTER, KAISER_SUPPORT, KAISER_FILTER, KAISER_SUPPORT);
2318 static constexpr auto NICE_RADIUS = 3.f;
2319 static constexpr auto NICE_SHARPEN = 1.25f;
2320 static constexpr auto NICE_FILTER = [](
float x, float,
void*) ->
float {
2321 if (x <= -NICE_RADIUS || x >= NICE_RADIUS)
return 0;
2322 if (std::abs(x) < 1e-6f)
return 1;
2324 if (out < 0) out *= NICE_SHARPEN;
2327 static constexpr auto NICE_SUPPORT = [](float,
void*) ->
float {
2330 stbir_set_filter_callbacks(&resize, NICE_FILTER, NICE_SUPPORT, NICE_FILTER, NICE_SUPPORT);
2334 stbir_resize_extended(&resize);
2337 const auto pixelLayout = ::imageFormatToSTBIRPixelLayout(format, premultipliedAlpha);
2338 if (pixelLayout == -1) {
2342 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, premultipliedAlpha)),
static_cast<stbir_datatype
>(::imageFormatToSTBIRDataType(containerFormat, srgb)));
2343 setEdgeModesAndFiltersAndDoResize();
2347 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)));
2348 setEdgeModesAndFiltersAndDoResize();
2352std::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,
bool premultipliedAlpha,
ResizeFilter filter,
ResizeEdge edge) {
2358 return resizeImageData(imageData, format, width, widthOut, height, heightOut, srgb, premultipliedAlpha, filter, edge);
2362std::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) {
2363 if (imageData.empty() || format ==
ImageFormat::EMPTY || xOffset + newWidth > width || yOffset + newHeight > height) {
2369 return convertImageDataToFormat(
cropImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, newWidth, xOffset, height, newHeight, yOffset), container, format, newWidth, newHeight);
2373 std::vector<std::byte> out(pixelSize * newWidth * newHeight);
2374 for (uint16_t y = yOffset; y < yOffset + newHeight; y++) {
2375 std::memcpy(out.data() + (((y - yOffset) * newWidth) * pixelSize), imageData.data() + (((y * width) + xOffset) * pixelSize), newWidth * pixelSize);
2385 if (!widthPad && !heightPad) {
2386 return {imageData.begin(), imageData.end()};
2391 return convertImageDataToFormat(
padImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, widthPad, height, heightPad), container, format, width + widthPad, height + heightPad);
2395 std::vector<std::byte> out(pixelSize * (width + widthPad) * (height + heightPad));
2398 for (uint16_t y = 0; y < height; y++) {
2399 std::memcpy(out.data() + ((y * (width + widthPad)) * pixelSize), imageData.data() + ((y * width) * pixelSize), width * pixelSize);
2403 for (
int y = 0; y < height + heightPad; y++) {
2404 for (
int x = 0; x < width + widthPad; x++) {
2405 if (x >= width && y >= height) {
2406 std::memcpy(out.data() + ((y * (width + widthPad) + x) * pixelSize), imageData.data() + (((height - 1) * (width) + (width - 1)) * pixelSize), pixelSize);
2407 }
else if (x >= width) {
2408 std::memcpy(out.data() + ((y * (width + widthPad) + x) * pixelSize), imageData.data() + ((y * (width) + (width - 1)) * pixelSize), pixelSize);
2409 }
else if (y >= height) {
2410 std::memcpy(out.data() + ((y * (width + widthPad) + x) * pixelSize), imageData.data() + (((height - 1) * (width) + x) * pixelSize), pixelSize);
2432 std::memcpy(imageData.data(), newData.data(), imageData.size());
2436 static constexpr auto calculateGammaLUT = [](
float gamma_, uint8_t channelSize) -> std::array<uint8_t, 256> {
2437 const auto maxSize =
static_cast<float>((1 << channelSize) - 1);
2438 std::array<uint8_t, 256> gammaLUT{};
2439 for (
int i = 0; i < gammaLUT.size(); i++) {
2440 gammaLUT[i] =
static_cast<uint8_t
>(std::clamp(std::pow((
static_cast<float>(i) + 0.5f) / maxSize, gamma_) * maxSize - 0.5f, 0.f, maxSize));
2445 #define VTFPP_CREATE_GAMMA_LUTS(InputType) \
2446 std::unordered_map<uint8_t, std::array<uint8_t, 256>> gammaLUTs; \
2447 if constexpr (ImageFormatDetails::red(ImageFormat::InputType) > 0) { \
2448 if (!gammaLUTs.contains(ImageFormatDetails::red(ImageFormat::InputType))) { \
2449 gammaLUTs[ImageFormatDetails::red(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::red(ImageFormat::InputType)); \
2452 if constexpr (ImageFormatDetails::green(ImageFormat::InputType) > 0) { \
2453 if (!gammaLUTs.contains(ImageFormatDetails::green(ImageFormat::InputType))) { \
2454 gammaLUTs[ImageFormatDetails::green(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::green(ImageFormat::InputType)); \
2457 if constexpr (ImageFormatDetails::blue(ImageFormat::InputType) > 0) { \
2458 if (!gammaLUTs.contains(ImageFormatDetails::blue(ImageFormat::InputType))) { \
2459 gammaLUTs[ImageFormatDetails::blue(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::blue(ImageFormat::InputType)); \
2463 #define VTFPP_APPLY_GAMMA_RED(value) \
2464 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::red(PIXEL_TYPE::FORMAT))[value])
2466 #define VTFPP_APPLY_GAMMA_GREEN(value) \
2467 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::green(PIXEL_TYPE::FORMAT))[value])
2469 #define VTFPP_APPLY_GAMMA_BLUE(value) \
2470 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::blue(PIXEL_TYPE::FORMAT))[value])
2472 #define VTFPP_GAMMA_CORRECT_CASE(InputType, ...) \
2474 VTFPP_CREATE_GAMMA_LUTS(InputType) \
2475 return ImagePixel::transformInPlace<ImagePixel::InputType>(imageData, [&gammaLUTs](ImagePixel::InputType pixel) -> ImagePixel::InputType { \
2476 using PIXEL_TYPE = ImagePixel::InputType; \
2477 return {__VA_ARGS__}; \
2504 #undef VTFPP_GAMMA_CORRECT_CASE
2505 #undef VTFPP_APPLY_GAMMA_BLUE
2506 #undef VTFPP_APPLY_GAMMA_GREEN
2507 #undef VTFPP_APPLY_GAMMA_RED
2508 #undef VTFPP_CREATE_GAMMA_LUTS
2525 std::memcpy(imageData.data(), newData.data(), imageData.size());
2529 #define VTFPP_INVERT_GREEN_CASE_OVERRIDE(PixelType, ChannelName, ChannelNameCaps, ...) \
2530 case PixelType: return ImagePixel::transformInPlace<ImagePixel::PixelType>(imageData, [](ImagePixel::PixelType pixel) -> ImagePixel::PixelType { \
2531 static constexpr auto channelSize = ImageFormatDetails::green(ImagePixel::PixelType::FORMAT); \
2532 if constexpr (std::same_as<decltype(pixel.ChannelName()), float> || std::same_as<decltype(pixel.ChannelName()), half>) { \
2533 pixel.set##ChannelNameCaps(static_cast<decltype(pixel.ChannelName())>(static_cast<float>(static_cast<uint64_t>(1) << channelSize) - 1.f - static_cast<float>(pixel.ChannelName()))); \
2535 if constexpr (channelSize >= sizeof(uint32_t) * 8) { \
2536 pixel.set##ChannelNameCaps(static_cast<decltype(pixel.ChannelName())>((static_cast<uint64_t>(1) << channelSize) - 1 - static_cast<uint32_t>(pixel.ChannelName()))); \
2538 pixel.set##ChannelNameCaps(static_cast<decltype(pixel.ChannelName())>(static_cast<uint32_t>(1 << channelSize) - 1 - static_cast<uint32_t>(pixel.ChannelName()))); \
2544 #define VTFPP_INVERT_GREEN_CASE(PixelType) VTFPP_INVERT_GREEN_CASE_OVERRIDE(PixelType, g, G);
2588 #undef VTFPP_INVERT_GREEN_CASE
2589 #undef VTFPP_INVERT_GREEN_CASE_OVERRIDE
2605 std::memcpy(imageData.data(), newData.data(), imageData.size());
2610 static constexpr auto hableTonemapChannel = [](
float x)
constexpr {
2611 constexpr auto hableTonemapChannelPartial = [](
float y)
constexpr {
2612 constexpr float A = 0.15f, B = 0.5f, C = 0.1f, D = 0.2f, E = 0.02f, F = 0.3f;
2613 return ((y * (A * y + C * B) + D * E) / (y * (A * y + B) + D * F)) - E / F;
2615 constexpr float EXPOSURE_BIAS = 2.0f, W = 11.2f;
2616 return hableTonemapChannelPartial(x * EXPOSURE_BIAS) * (1.f / hableTonemapChannelPartial(W));
2619 #define VTFPP_TONEMAP_CASE(InputType, ...) \
2621 return ImagePixel::transformInPlace<ImagePixel::InputType>(imageData, [](ImagePixel::InputType pixel) -> ImagePixel::InputType { \
2622 return {__VA_ARGS__}; \
2632 VTFPP_TONEMAP_CASE(
RGBA16161616F, {half{hableTonemapChannel(pixel.r())}, half{hableTonemapChannel(pixel.g())}, half{hableTonemapChannel(pixel.b())}, pixel.a()});
2637 #undef VTFPP_TONEMAP_CASE
#define VTFPP_APPLY_GAMMA_BLUE(value)
#define VTFPP_GAMMA_CORRECT_CASE(InputType,...)
#define VTFPP_REMAP_TO_16(value, shift)
#define VTFPP_APPLY_GAMMA_RED(value)
#define VTFPP_APPLY_GAMMA_GREEN(value)
#define VTFPP_INVERT_GREEN_CASE_OVERRIDE(PixelType, ChannelName, ChannelNameCaps,...)
#define VTFPP_REMAP_TO_8(value, shift)
#define VTFPP_TONEMAP_CASE(InputType,...)
#define VTFPP_REMAP_FROM_8(value, shift)
#define VTFPP_CASE_CONVERT(InputType, r, g, b, a)
#define VTFPP_REMAP_FROM_16(value, shift)
#define VTFPP_INVERT_GREEN_CASE(PixelType)
#define SOURCEPP_DEBUG_ASSERT(cond)
Break in debugger if condition is not true.
#define SOURCEPP_DEBUG_BREAK
Create a breakpoint in debug.
constexpr double sinc(double x)
constexpr T nearestPowerOf2(T n)
constexpr double besselI0(double x)
constexpr T remap(T value, T l1, T h1, T l2, T h2)
consteval uint32_t makeFourCC(const char fourCC[4])
Creates a FourCC identifier from a string of 4 characters.
void invertGreenChannelForImageData(std::span< std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height)
Invert the green channel on the given image data. Meant for converting normal maps between OpenGL and...
std::vector< std::byte > convertFileToImageData(std::span< const std::byte > fileData, ImageFormat &format, int &width, int &height, int &frameCount)
std::vector< std::byte > compressBGRA8888HDR(std::span< const std::byte > imageData, float overbrightFactor=16.f)
Takes in RGBA32323232F format image data, returns SOURCEPP_BGRA8888_HDR compressed HDR image data (al...
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, bool premultipliedAlpha, 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...
void setResizedDims(uint16_t &width, ResizeMethod widthResize, uint16_t &height, ResizeMethod heightResize)
Set the new image dimensions given a resize method.
void hableTonemapImageData(std::span< std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height)
Perform Hable tonemapping on the given image data.
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::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, bool skybox=false)
Converts an HDRI into six cubemap (or skybox) faces.
@ 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 > decompressBGRA8888HDR(std::span< const std::byte > imageData, float overbrightFactor=16.f)
Takes in SOURCEPP_BGRA8888_HDR compressed HDR image data (alias for BGRA8888), returns RGBA32323232F ...
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 > compressRGBA16161616HDR(std::span< const std::byte > imageData, bool flipExponentAndSignificand=false)
Takes in RGBA32323232F format image data, returns SOURCEPP_RGBA16161616_HDR compressed HDR image data...
void gammaCorrectImageData(std::span< 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...
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 > convertImageDataToFile(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height, FileFormat fileFormat=FileFormat::DEFAULT, float quality=-1.f)
Converts image data to the given file format (PNG or EXR by default). Quality parameter (0-1) is igno...
std::vector< std::byte > decompressRGBA16161616HDR(std::span< const std::byte > imageData, bool flipExponentAndSignificand=false)
Takes in SOURCEPP_RGBA16161616_HDR compressed HDR image data (alias for RGBA16161616),...
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, bool premultipliedAlpha, 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.
std::vector< std::byte > transform(std::span< const std::byte > imageData, Func callback)
Run a parallelizable/vectorizable operation on the given image data, and return new image data.
@ SOURCEPP_CONSOLE_RGBA16161616_HDR
@ CONSOLE_ARGB8888_LINEAR
@ CONSOLE_BGRX8888_LINEAR
@ CONSOLE_RGBA8888_LINEAR
@ CONSOLE_ABGR8888_LINEAR
@ SOURCEPP_RGBA16161616_HDR
@ CONSOLE_BGRX5551_LINEAR
@ CONSOLE_BGRA8888_LINEAR
@ CONSOLE_RGBA16161616_LINEAR
std::pair< uint16_t, uint16_t > clamp(uint16_t width, uint16_t height) const