16#include <unordered_map>
18#ifdef SOURCEPP_BUILD_WITH_TBB
22#ifdef SOURCEPP_BUILD_WITH_THREADS
26#define BCDEC_IMPLEMENTATION
29#ifdef VTFPP_BUILD_WITH_COMPRESSONATOR
30#include <compressonator.h>
33#ifdef VTFPP_SUPPORT_QOI
34#define QOI_IMPLEMENTATION
42#define STB_IMAGE_IMPLEMENTATION
43#define STB_IMAGE_STATIC
44#define STBI_NO_FAILURE_STRINGS
48#define STB_IMAGE_RESIZE_IMPLEMENTATION
49#define STB_IMAGE_RESIZE_STATIC
50#include <stb_image_resize2.h>
52#define STB_IMAGE_WRITE_IMPLEMENTATION
53#define STB_IMAGE_WRITE_STATIC
54#define STBI_WRITE_NO_STDIO
55#include <stb_image_write.h>
57#ifdef VTFPP_SUPPORT_EXR
63#ifdef VTFPP_SUPPORT_WEBP
64#include <webp/decode.h>
65#include <webp/encode.h>
73#ifdef VTFPP_BUILD_WITH_COMPRESSONATOR
74[[nodiscard]]
constexpr CMP_FORMAT imageFormatToCompressonatorFormat(
ImageFormat format) {
79 return CMP_FORMAT_RGBA_8888;
81 return CMP_FORMAT_ABGR_8888;
83 return CMP_FORMAT_RGB_888;
85 return CMP_FORMAT_BGR_888;
88 return CMP_FORMAT_R_8;
90 return CMP_FORMAT_ARGB_8888;
92 return CMP_FORMAT_BGRA_8888;
95 return CMP_FORMAT_DXT1;
97 return CMP_FORMAT_DXT3;
99 return CMP_FORMAT_DXT5;
101 return CMP_FORMAT_RG_8;
103 return CMP_FORMAT_R_16F;
105 return CMP_FORMAT_RG_16F;
107 return CMP_FORMAT_RGBA_16F;
109 return CMP_FORMAT_RGBA_16;
111 return CMP_FORMAT_R_32F;
113 return CMP_FORMAT_RG_32F;
115 return CMP_FORMAT_RGB_32F;
117 return CMP_FORMAT_RGBA_32F;
119 return CMP_FORMAT_ATI2N_XY;
121 return CMP_FORMAT_ATI1N;
123 return CMP_FORMAT_RGBA_1010102;
125 return CMP_FORMAT_R_8;
127 return CMP_FORMAT_BC6H;
130 return CMP_FORMAT_BC7;
132 return CMP_FORMAT_BC6H_SF;
162 return CMP_FORMAT_Unknown;
164 return CMP_FORMAT_Unknown;
168[[nodiscard]]
constexpr int imageFormatToSTBIRPixelLayout(
ImageFormat format) {
189 return STBIR_1CHANNEL;
197 return STBIR_2CHANNEL;
245[[nodiscard]]
constexpr int imageFormatToSTBIRDataType(
ImageFormat format,
bool srgb =
false) {
266 return srgb ? STBIR_TYPE_UINT8_SRGB : STBIR_TYPE_UINT8;
270 return STBIR_TYPE_HALF_FLOAT;
272 return STBIR_TYPE_UINT16;
277 return STBIR_TYPE_FLOAT;
316[[nodiscard]] std::vector<std::byte> decompressImageData(std::span<const std::byte> imageData,
ImageFormat inFormat,
ImageFormat& outFormat, uint16_t width, uint16_t height) {
318 return {imageData.begin(), imageData.end()};
321 uint16_t unpaddedWidth = width, unpaddedHeight = height;
322 if (width % 4 != 0 || height % 4 != 0) {
323 width += (4 - (width % 4)) % 4;
324 height += (4 - (height % 4)) % 4;
327 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> {
328 outFormat = OutputPixel::FORMAT;
330 std::vector<std::byte> out;
331 out.resize(imageData.size() / InputBlockSize *
sizeof(OutputPixel) * (4 * 4));
332 for (uint64_t src = 0, i = 0; i < height; i += 4) {
333 for (uint64_t j = 0; j < width; j += 4) {
337 callback(imageData.data() + src, out.data() + (i * width + j) *
sizeof(OutputPixel), width *
sizeof(OutputPixel));
339 src += InputBlockSize;
343 if (unpaddedWidth % 4 != 0 || unpaddedHeight % 4 != 0) {
352 return transformCompressed.operator()<BCDEC_BC1_BLOCK_SIZE, ImagePixel::RGBA8888>(&bcdec_bc1);
354 return transformCompressed.operator()<BCDEC_BC1_BLOCK_SIZE, ImagePixel::RGBA8888>(&bcdec_bc1a);
356 return transformCompressed.operator()<BCDEC_BC2_BLOCK_SIZE, ImagePixel::RGBA8888>(&bcdec_bc2);
358 return transformCompressed.operator()<BCDEC_BC3_BLOCK_SIZE, ImagePixel::RGBA8888>(&bcdec_bc3);
360 return transformCompressed.operator()<BCDEC_BC4_BLOCK_SIZE, ImagePixel::I8>(&bcdec_bc4);
362 auto out = transformCompressed.operator()<BCDEC_BC5_BLOCK_SIZE, ImagePixel::UV88>(&bcdec_bc5);
367 const auto nX =
static_cast<float>(pixel.u()) / 255.f * 2.f - 1.f;
368 const auto nY =
static_cast<float>(pixel.v()) / 255.f * 2.f - 1.f;
369 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)}};
375 return transformCompressed.operator()<BCDEC_BC6H_BLOCK_SIZE, ImagePixel::RGB323232F>([](
const void* compressedBlock,
void* decompressedBlock,
int destinationPitch) {
376 return bcdec_bc6h_float(compressedBlock, decompressedBlock, destinationPitch,
false);
379 return transformCompressed.operator()<BCDEC_BC6H_BLOCK_SIZE, ImagePixel::RGB323232F>([](
const void* compressedBlock,
void* decompressedBlock,
int destinationPitch) {
380 return bcdec_bc6h_float(compressedBlock, decompressedBlock, destinationPitch,
true);
384 return transformCompressed.operator()<BCDEC_BC7_BLOCK_SIZE, ImagePixel::RGBA8888>(&bcdec_bc7);
395#ifdef VTFPP_BUILD_WITH_COMPRESSONATOR
396 if (imageData.empty()) {
400 std::vector<std::byte> imageDataReplacement;
402 uint16_t paddingWidth = (4 - (width % 4)) % 4, paddingHeight = (4 - (height % 4)) % 4;
405 imageData = imageDataReplacement;
407 width += paddingWidth;
408 height += paddingHeight;
411 CMP_Texture srcTexture{};
412 srcTexture.dwSize =
sizeof(srcTexture);
413 srcTexture.dwWidth = width;
414 srcTexture.dwHeight = height;
416 srcTexture.format = ::imageFormatToCompressonatorFormat(oldFormat);
417 srcTexture.dwDataSize = imageData.size();
419 srcTexture.pData =
const_cast<CMP_BYTE*
>(
reinterpret_cast<const CMP_BYTE*
>(imageData.data()));
421 CMP_Texture destTexture{};
422 destTexture.dwSize =
sizeof(destTexture);
423 destTexture.dwWidth = width;
424 destTexture.dwHeight = height;
426 destTexture.format = ::imageFormatToCompressonatorFormat(newFormat);
427 destTexture.dwDataSize = CMP_CalculateBufferSize(&destTexture);
429 std::vector<std::byte> destData;
430 destData.resize(destTexture.dwDataSize);
431 destTexture.pData =
reinterpret_cast<CMP_BYTE*
>(destData.data());
433 CMP_CompressOptions options{};
434 options.dwSize =
sizeof(options);
435 if (quality >= 0.f) {
436 options.fquality = std::min(quality, 1.f);
442 options.fquality = 0.1f;
444 options.fquality = 1.f;
447 if (options.bDXT1UseAlpha) {
448 options.nAlphaThreshold = 128;
451 if (CMP_ConvertTexture(&srcTexture, &destTexture, &options,
nullptr) != CMP_OK) {
460[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA8888(std::span<const std::byte> imageData,
ImageFormat format) {
463 if (imageData.empty()) {
468 return {imageData.begin(), imageData.end()};
471 #define VTFPP_REMAP_TO_8(value, shift) math::remap<uint8_t>((value), (1 << (shift)) - 1, (1 << 8) - 1)
473 #define VTFPP_CASE_CONVERT(InputType, r, g, b, a) \
474 case InputType: return ImagePixel::transform<ImagePixel::InputType, ImagePixel::RGBA8888>(imageData, [](ImagePixel::InputType pixel) -> ImagePixel::RGBA8888 { \
475 return {{(r), (g), (b), (a)}}; \
515 #undef VTFPP_CASE_CONVERT
516 #undef VTFPP_REMAP_TO_8
521[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888(std::span<const std::byte> imageData,
ImageFormat format) {
524 if (imageData.empty()) {
529 return {imageData.begin(), imageData.end()};
532 #define VTFPP_REMAP_FROM_8(value, shift) math::remap<uint8_t>((value), (1 << 8) - 1, (1 << (shift)) - 1)
534 #define VTFPP_CASE_CONVERT(OutputType, ...) \
535 case OutputType: return ImagePixel::transform<ImagePixel::RGBA8888, ImagePixel::OutputType>(imageData, [](ImagePixel::RGBA8888 pixel) -> ImagePixel::OutputType { \
536 return {__VA_ARGS__}; \
548 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())});
569 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())});
576 #undef VTFPP_CASE_CONVERT
577 #undef VTFPP_REMAP_FROM_8
582[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA16161616(std::span<const std::byte> imageData,
ImageFormat format) {
585 if (imageData.empty()) {
590 return {imageData.begin(), imageData.end()};
593 #define VTFPP_REMAP_TO_16(value, shift) math::remap<uint16_t>((value), (1 << (shift)) - 1, (1 << 16) - 1)
595 #define VTFPP_CASE_CONVERT(InputType, r, g, b, a) \
596 case InputType: return ImagePixel::transform<ImagePixel::InputType, ImagePixel::RGBA16161616>(imageData, [](ImagePixel::InputType pixel) -> ImagePixel::RGBA16161616 { \
597 return {{(r), (g), (b), (a)}}; \
607 #undef VTFPP_CASE_CONVERT
608 #undef VTFPP_REMAP_TO_16
613[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616(std::span<const std::byte> imageData,
ImageFormat format) {
616 if (imageData.empty()) {
621 return {imageData.begin(), imageData.end()};
624 #define VTFPP_REMAP_FROM_16(value, shift) static_cast<uint8_t>(math::remap<uint16_t>((value), (1 << 16) - 1, (1 << (shift)) - 1))
626 #define VTFPP_CASE_CONVERT(OutputType, ...) \
627 case OutputType: return ImagePixel::transform<ImagePixel::RGBA16161616, ImagePixel::OutputType>(imageData, [](ImagePixel::RGBA16161616 pixel) -> ImagePixel::OutputType { \
628 return {__VA_ARGS__}; \
638 #undef VTFPP_CASE_CONVERT
639 #undef VTFPP_REMAP_FROM_16
644[[nodiscard]] std::vector<std::byte> convertImageDataToRGBA32323232F(std::span<const std::byte> imageData,
ImageFormat format) {
645 if (imageData.empty()) {
650 return {imageData.begin(), imageData.end()};
653 #define VTFPP_CASE_CONVERT(InputType, r, g, b, a) \
654 case InputType: return ImagePixel::transform<ImagePixel::InputType, ImagePixel::RGBA32323232F>(imageData, [](ImagePixel::InputType pixel) -> ImagePixel::RGBA32323232F { \
655 return {{(r), (g), (b), (a)}}; \
669 #undef VTFPP_CASE_CONVERT
674[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232F(std::span<const std::byte> imageData,
ImageFormat format) {
677 if (imageData.empty()) {
682 return {imageData.begin(), imageData.end()};
685 #define VTFPP_CASE_CONVERT(OutputType, ...) \
686 case OutputType: return ImagePixel::transform<ImagePixel::RGBA32323232F, ImagePixel::OutputType>(imageData, [](ImagePixel::RGBA32323232F pixel) -> ImagePixel::OutputType { \
687 return {__VA_ARGS__}; \
701 #undef VTFPP_CASE_CONVERT
706[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888ToRGBA32323232F(std::span<const std::byte> imageData) {
707 if (imageData.empty()) {
713 static_cast<float>(pixel.r()) /
static_cast<float>((1 << 8) - 1),
714 static_cast<float>(pixel.g()) /
static_cast<float>((1 << 8) - 1),
715 static_cast<float>(pixel.b()) /
static_cast<float>((1 << 8) - 1),
716 static_cast<float>(pixel.a()) /
static_cast<float>((1 << 8) - 1),
721[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232FToRGBA8888(std::span<const std::byte> imageData) {
722 if (imageData.empty()) {
728 static_cast<uint8_t
>(std::clamp(pixel.r(), 0.f, 1.f) * ((1 << 8) - 1)),
729 static_cast<uint8_t
>(std::clamp(pixel.g(), 0.f, 1.f) * ((1 << 8) - 1)),
730 static_cast<uint8_t
>(std::clamp(pixel.b(), 0.f, 1.f) * ((1 << 8) - 1)),
731 static_cast<uint8_t
>(std::clamp(pixel.a(), 0.f, 1.f) * ((1 << 8) - 1)),
736[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA8888ToRGBA16161616(std::span<const std::byte> imageData) {
737 if (imageData.empty()) {
751[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616ToRGBA8888(std::span<const std::byte> imageData) {
752 if (imageData.empty()) {
766[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA32323232FToRGBA16161616(std::span<const std::byte> imageData) {
767 if (imageData.empty()) {
773 static_cast<uint16_t
>(std::clamp(pixel.r(), 0.f, 1.f) * ((1 << 16) - 1)),
774 static_cast<uint16_t
>(std::clamp(pixel.g(), 0.f, 1.f) * ((1 << 16) - 1)),
775 static_cast<uint16_t
>(std::clamp(pixel.b(), 0.f, 1.f) * ((1 << 16) - 1)),
776 static_cast<uint16_t
>(std::clamp(pixel.a(), 0.f, 1.f) * ((1 << 16) - 1)),
781[[nodiscard]] std::vector<std::byte> convertImageDataFromRGBA16161616ToRGBA32323232F(std::span<const std::byte> imageData) {
782 if (imageData.empty()) {
788 static_cast<float>(pixel.r()) /
static_cast<float>((1 << 16) - 1),
789 static_cast<float>(pixel.g()) /
static_cast<float>((1 << 16) - 1),
790 static_cast<float>(pixel.b()) /
static_cast<float>((1 << 16) - 1),
791 static_cast<float>(pixel.a()) /
static_cast<float>((1 << 16) - 1),
804 oldFormat == newFormat ||
807 return {imageData.begin(), imageData.end()};
810 std::vector<std::byte> newData;
827 newData = ::decompressImageData(imageData, oldFormat, decompressedFormat, width, height);
828 if (decompressedFormat == newFormat) {
831 if (decompressedFormat != intermediaryOldFormat) {
833 case ImageFormat::RGBA8888: newData = ::convertImageDataToRGBA8888(newData, decompressedFormat);
break;
840 switch (intermediaryOldFormat) {
848 if (intermediaryOldFormat == newFormat) {
851 if (intermediaryOldFormat != intermediaryNewFormat) {
854 newData = ::convertImageDataFromRGBA8888ToRGBA16161616(newData);
856 newData = ::convertImageDataFromRGBA8888ToRGBA32323232F(newData);
862 newData = ::convertImageDataFromRGBA16161616ToRGBA8888(newData);
864 newData = ::convertImageDataFromRGBA16161616ToRGBA32323232F(newData);
870 newData = ::convertImageDataFromRGBA32323232FToRGBA8888(newData);
872 newData = ::convertImageDataFromRGBA32323232FToRGBA16161616(newData);
881 if (intermediaryNewFormat == newFormat) {
893 newData = ::compressImageData(newData, intermediaryNewFormat, newFormat, width, height, quality);
895 switch (intermediaryNewFormat) {
911 if (oldFormat == newFormat) {
912 return {imageData.begin(), imageData.end()};
916 for(
int mip = mipCount - 1; mip >= 0; mip--) {
918 for (
int frame = 0; frame < frameCount; frame++) {
919 for (
int face = 0; face < faceCount; face++) {
920 for (
int slice = 0; slice < mipDepth; slice++) {
921 if (uint32_t oldOffset, oldLength;
ImageFormatDetails::getDataPosition(oldOffset, oldLength, oldFormat, mip, mipCount, frame, frameCount, face, faceCount, width, height, slice, depth)) {
923 if (uint32_t newOffset, newLength;
ImageFormatDetails::getDataPosition(newOffset, newLength, newFormat, mip, mipCount, frame, frameCount, face, faceCount, width, height, slice, depth) && newLength == convertedImageData.size()) {
924 std::memcpy(out.data() + newOffset, convertedImageData.data(), newLength);
943 std::span imageDataRGBA32323232F{
reinterpret_cast<const float*
>(imageData.data()),
reinterpret_cast<const float*
>(imageData.data() + imageData.size())};
945 std::vector<std::byte> possiblyConvertedDataOrEmptyDontUseMeDirectly;
948 imageDataRGBA32323232F = {
reinterpret_cast<const float*
>(possiblyConvertedDataOrEmptyDontUseMeDirectly.data()),
reinterpret_cast<const float*
>(possiblyConvertedDataOrEmptyDontUseMeDirectly.data() + possiblyConvertedDataOrEmptyDontUseMeDirectly.size())};
953 static constexpr std::array<std::array<math::Vec3f, 3>, 6> startRightUpCubemap = {{
954 {{{ 1.0f, -1.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}},
955 {{{ 1.0f, 1.0f, 1.0f}, { 0.0f,-1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}},
956 {{{ 1.0f, 1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, { 0.0f,-1.0f, 0.0f}}},
957 {{{-1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}}},
958 {{{ 1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}}},
959 {{{ 1.0f, 1.0f, -1.0f}, { 0.0f, 0.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}},
961 static constexpr std::array<std::array<math::Vec3f, 3>, 6> startRightUpSkybox = {{
962 {{{ 1.0f, -1.0f, -1.0f}, { 0.0f, 0.0f, 1.0f}, { 0.0f, 1.0f, 0.0f}}},
963 {{{-1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}}},
964 {{{-1.0f, -1.0f, -1.0f}, { 1.0f, 0.0f, 0.0f}, { 0.0f, 1.0f, 0.0f}}},
965 {{{ 1.0f, -1.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}, { 0.0f, 1.0f, 0.0f}}},
966 {{{-1.0f, -1.0f, -1.0f}, { 0.0f, 0.0f, 1.0f}, { 1.0f, 0.0f, 0.0f}}},
967 {{{ 1.0f, 1.0f, -1.0f}, { 0.0f, 0.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}},
969 const auto& startRightUp = skybox ? startRightUpSkybox : startRightUpCubemap;
971 std::array<std::vector<std::byte>, 6> faceData;
973#ifdef SOURCEPP_BUILD_WITH_THREADS
974 const auto faceExtraction = [&](
int i) {
976 for (
int i = 0; i < faceData.size(); i++) {
978 const auto start = startRightUp[i][0];
979 const auto right = startRightUp[i][1];
980 const auto up = startRightUp[i][2];
982 faceData[i].resize(resolution * resolution *
sizeof(ImagePixel::RGBA32323232F));
983 std::span face{
reinterpret_cast<float*
>(faceData[i].data()),
reinterpret_cast<float*
>(faceData[i].data() + faceData[i].size())};
985 for (
int row = 0; row < resolution; row++) {
986 for (
int col = 0; col < resolution; col++) {
987 math::Vec3f pixelDirection3d{
988 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],
989 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],
990 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],
992 const float azimuth = std::atan2(pixelDirection3d[0], -pixelDirection3d[2]) +
math::pi_f32;
993 const float elevation = std::atan(pixelDirection3d[1] / std::sqrt(pixelDirection3d[0] * pixelDirection3d[0] + pixelDirection3d[2] * pixelDirection3d[2])) +
math::pi_f32 / 2.f;
994 const float colHdri = (azimuth /
math::pi_f32 / 2.f) *
static_cast<float>(width);
995 const float rowHdri = (elevation /
math::pi_f32) *
static_cast<float>(height);
997 const int colNearest = std::clamp(
static_cast<int>(colHdri), 0, width - 1);
998 const int rowNearest = std::clamp(
static_cast<int>(rowHdri), 0, height - 1);
999 face[col * 4 + resolution * row * 4 + 0] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 0];
1000 face[col * 4 + resolution * row * 4 + 1] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 1];
1001 face[col * 4 + resolution * row * 4 + 2] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 2];
1002 face[col * 4 + resolution * row * 4 + 3] = imageDataRGBA32323232F[colNearest * 4 + width * rowNearest * 4 + 3];
1004 float intCol, intRow;
1006 float factorCol = std::modf(colHdri - 0.5f, &intCol);
1007 float factorRow = std::modf(rowHdri - 0.5f, &intRow);
1008 const int low_idx_row =
static_cast<int>(intRow);
1009 const int low_idx_column =
static_cast<int>(intCol);
1010 int high_idx_column;
1011 if (factorCol < 0.f) {
1014 high_idx_column = width - 1;
1015 }
else if (low_idx_column == width - 1) {
1017 high_idx_column = 0;
1019 high_idx_column = low_idx_column + 1;
1022 if (factorRow < 0.f || low_idx_row == height - 1) {
1023 high_idx_row = low_idx_row;
1026 high_idx_row = low_idx_row + 1;
1028 factorCol = std::abs(factorCol);
1029 factorRow = std::abs(factorRow);
1030 const float f1 = (1 - factorRow) * (1 - factorCol);
1031 const float f2 = factorRow * (1 - factorCol);
1032 const float f3 = (1 - factorRow) * factorCol;
1033 const float f4 = factorRow * factorCol;
1034 for (
int j = 0; j < 4; j++) {
1035 face[col * 4 + resolution * row * 4 + j] =
1036 imageDataRGBA32323232F[low_idx_column * 4 + width * low_idx_row * 4 + j] * f1 +
1037 imageDataRGBA32323232F[low_idx_column * 4 + width * high_idx_row * 4 + j] * f2 +
1038 imageDataRGBA32323232F[high_idx_column * 4 + width * low_idx_row * 4 + j] * f3 +
1039 imageDataRGBA32323232F[high_idx_column * 4 + width * high_idx_row * 4 + j] * f4;
1048#ifdef SOURCEPP_BUILD_WITH_THREADS
1050 std::array faceFutures{
1051 std::async(std::launch::async, faceExtraction, 0),
1052 std::async(std::launch::async, faceExtraction, 1),
1053 std::async(std::launch::async, faceExtraction, 2),
1054 std::async(std::launch::async, faceExtraction, 3),
1055 std::async(std::launch::async, faceExtraction, 4),
1056 std::async(std::launch::async, faceExtraction, 5),
1058 for (
auto& future : faceFutures) {
1067 if (imageData.empty()) {
1072 static constexpr auto compressChannel = [](
float c,
float a,
float fac) {
1073 return static_cast<uint8_t
>(std::clamp(std::round(c / (a * fac) * 255.f), 0.f, 255.f));
1075 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));
1077 .b = compressChannel(pixel.b(), alpha, overbrightFactor),
1078 .g = compressChannel(pixel.g(), alpha, overbrightFactor),
1079 .r = compressChannel(pixel.r(), alpha, overbrightFactor),
1086 if (imageData.empty()) {
1091 static constexpr auto decompressChannel = [](uint8_t c,
float a,
float fac) {
1092 return (
static_cast<float>(c) / 255) * a * fac;
1094 const auto alpha =
static_cast<float>(pixel.a());
1096 .r = decompressChannel(pixel.r(), alpha, overbrightFactor),
1097 .g = decompressChannel(pixel.g(), alpha, overbrightFactor),
1098 .b = decompressChannel(pixel.b(), alpha, overbrightFactor),
1105 if (imageData.empty()) {
1110 static constexpr auto compressChannel = [](
float c,
bool flip) {
1111 auto out =
static_cast<uint16_t
>(std::round(c * (1 << 12)));
1113 const uint16_t exponent = out & 0b1111;
1115 out |= exponent << 12;
1120 .r = compressChannel(pixel.r(), flipExponentAndSignificand),
1121 .g = compressChannel(pixel.g(), flipExponentAndSignificand),
1122 .b = compressChannel(pixel.b(), flipExponentAndSignificand),
1123 .a = compressChannel(1.f, flipExponentAndSignificand),
1129 if (imageData.empty()) {
1134 static constexpr auto decompressChannel = [](uint16_t c,
bool flip) {
1136 const uint16_t exponent = c & 0b1111;
1138 c |= exponent << 12;
1140 return static_cast<float>(c) / (1 << 12);
1143 .r = decompressChannel(pixel.r(), flipExponentAndSignificand),
1144 .g = decompressChannel(pixel.g(), flipExponentAndSignificand),
1145 .b = decompressChannel(pixel.b(), flipExponentAndSignificand),
1153#ifdef VTFPP_SUPPORT_EXR
1164 std::vector<std::byte> out;
1165 auto stbWriteFunc = [](
void* out_,
void* data,
int size) {
1166 std::copy_n(
static_cast<std::byte*
>(data), size, std::back_inserter(*
static_cast<std::vector<std::byte>*
>(out_)));
1172 switch (fileFormat) {
1175 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), imageData.data(), 0);
1177 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGBA8888), imageData.data(), 0);
1180 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), rgb.data(), 0);
1183 stbi_write_png_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGBA8888), rgba.data(), 0);
1189 stbi_write_jpg_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), imageData.data(), 95);
1192 stbi_write_jpg_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), rgb.data(), 95);
1198 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), imageData.data());
1200 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGBA8888), imageData.data());
1203 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), rgb.data());
1206 stbi_write_bmp_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGBA8888), rgba.data());
1212 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), imageData.data());
1214 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGBA8888), imageData.data());
1217 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGB888), rgb.data());
1220 stbi_write_tga_to_func(stbWriteFunc, &out, width, height,
sizeof(ImagePixel::RGBA8888), rgba.data());
1224#ifdef VTFPP_SUPPORT_WEBP
1225 case FileFormat::WEBP: {
1227 WebPConfigInit(&config);
1228 WebPConfigPreset(&config, WEBP_PRESET_DRAWING, 75.f);
1229 WebPConfigLosslessPreset(&config, 6);
1232 if (!WebPPictureInit(&pic)) {
1236 pic.height = height;
1237 if (!WebPPictureAlloc(&pic)) {
1242 WebPPictureImportRGB(&pic,
reinterpret_cast<const uint8_t*
>(imageData.data()),
static_cast<int>(width *
sizeof(ImagePixel::RGB888)));
1244 WebPPictureImportRGBA(&pic,
reinterpret_cast<const uint8_t*
>(imageData.data()),
static_cast<int>(width *
sizeof(ImagePixel::RGBA8888)));
1247 WebPPictureImportRGB(&pic,
reinterpret_cast<const uint8_t*
>(rgb.data()),
static_cast<int>(width *
sizeof(ImagePixel::RGB888)));
1250 WebPPictureImportRGBA(&pic,
reinterpret_cast<const uint8_t*
>(rgba.data()),
static_cast<int>(width *
sizeof(ImagePixel::RGBA8888)));
1253 WebPMemoryWriter writer;
1254 WebPMemoryWriterInit(&writer);
1255 pic.writer = &WebPMemoryWrite;
1256 pic.custom_ptr = &writer;
1258 int ok = WebPEncode(&config, &pic);
1259 WebPPictureFree(&pic);
1261 WebPMemoryWriterClear(&writer);
1265 if (writer.mem && writer.size) {
1266 out.resize(writer.size);
1267 std::memcpy(out.data(), writer.mem, writer.size);
1269 WebPMemoryWriterClear(&writer);
1273#ifdef VTFPP_SUPPORT_QOI
1274 case FileFormat::QOI: {
1275 qoi_desc descriptor{
1279 .colorspace = QOI_SRGB,
1281 void* qoiData =
nullptr;
1284 descriptor.channels = 3;
1285 qoiData = qoi_encode(imageData.data(), &descriptor, &qoiDataLen);
1287 descriptor.channels = 4;
1288 qoiData = qoi_encode(imageData.data(), &descriptor, &qoiDataLen);
1291 descriptor.channels = 3;
1292 qoiData = qoi_encode(rgb.data(), &descriptor, &qoiDataLen);
1295 descriptor.channels = 4;
1296 qoiData = qoi_encode(rgba.data(), &descriptor, &qoiDataLen);
1298 if (qoiData && qoiDataLen) {
1299 out.resize(qoiDataLen);
1300 std::memcpy(out.data(), qoiData, qoiDataLen);
1315#ifdef VTFPP_SUPPORT_EXR
1316 case FileFormat::EXR: {
1318 InitEXRHeader(&header);
1320 std::vector<std::byte> rawData;
1330 rawData = {imageData.begin(), imageData.end()};
1334 header.channels =
static_cast<EXRChannelInfo*
>(std::malloc(header.num_channels *
sizeof(EXRChannelInfo)));
1335 header.pixel_types =
static_cast<int*
>(malloc(header.num_channels *
sizeof(
int)));
1336 header.requested_pixel_types =
static_cast<int*
>(malloc(header.num_channels *
sizeof(
int)));
1338 switch (header.num_channels) {
1340 header.channels[0].name[0] =
'A';
1341 header.channels[1].name[0] =
'B';
1342 header.channels[2].name[0] =
'G';
1343 header.channels[3].name[0] =
'R';
1346 header.channels[0].name[0] =
'B';
1347 header.channels[1].name[0] =
'G';
1348 header.channels[2].name[0] =
'R';
1351 header.channels[0].name[0] =
'G';
1352 header.channels[1].name[0] =
'R';
1355 header.channels[0].name[0] =
'R';
1358 FreeEXRHeader(&header);
1361 for (
int i = 0; i < header.num_channels; i++) {
1362 header.channels[i].name[1] =
'\0';
1365 int pixelType = (
ImageFormatDetails::red(format) / 8) ==
sizeof(half) ? TINYEXR_PIXELTYPE_HALF : TINYEXR_PIXELTYPE_FLOAT;
1366 for (
int i = 0; i < header.num_channels; i++) {
1367 header.pixel_types[i] = pixelType;
1368 header.requested_pixel_types[i] = pixelType;
1371 std::vector<std::vector<std::byte>> images(header.num_channels);
1372 std::vector<void*> imagePtrs(header.num_channels);
1373 switch (header.num_channels) {
1375 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1376 images[0] = extractChannelFromImageData(imageData, &ImagePixel::RGBA16161616F::a);
1377 images[1] = extractChannelFromImageData(imageData, &ImagePixel::RGBA16161616F::b);
1378 images[2] = extractChannelFromImageData(imageData, &ImagePixel::RGBA16161616F::g);
1379 images[3] = extractChannelFromImageData(imageData, &ImagePixel::RGBA16161616F::r);
1381 images[0] = extractChannelFromImageData(imageData, &ImagePixel::RGBA32323232F::a);
1382 images[1] = extractChannelFromImageData(imageData, &ImagePixel::RGBA32323232F::b);
1383 images[2] = extractChannelFromImageData(imageData, &ImagePixel::RGBA32323232F::g);
1384 images[3] = extractChannelFromImageData(imageData, &ImagePixel::RGBA32323232F::r);
1388 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1390 FreeEXRHeader(&header);
1393 images[0] = extractChannelFromImageData(imageData, &ImagePixel::RGB323232F::b);
1394 images[1] = extractChannelFromImageData(imageData, &ImagePixel::RGB323232F::g);
1395 images[2] = extractChannelFromImageData(imageData, &ImagePixel::RGB323232F::r);
1398 if (pixelType == TINYEXR_PIXELTYPE_HALF) {
1399 images[0] = extractChannelFromImageData(imageData, &ImagePixel::RG1616F::g);
1400 images[1] = extractChannelFromImageData(imageData, &ImagePixel::RG1616F::r);
1402 images[0] = extractChannelFromImageData(imageData, &ImagePixel::RG3232F::g);
1403 images[1] = extractChannelFromImageData(imageData, &ImagePixel::RG3232F::r);
1407 images[0] = rawData;
1410 FreeEXRHeader(&header);
1413 for (
int i = 0; i < header.num_channels; i++) {
1414 imagePtrs[i] = images[i].data();
1418 InitEXRImage(&image);
1419 image.width = width;
1420 image.height = height;
1421 image.images =
reinterpret_cast<unsigned char**
>(imagePtrs.data());
1422 image.num_channels = header.num_channels;
1424 unsigned char* data =
nullptr;
1425 const char* err =
nullptr;
1427 size_t size = SaveEXRImageToMemory(&image, &header, &data, &err);
1429 FreeEXRErrorMessage(err);
1430 FreeEXRHeader(&header);
1434 out = {
reinterpret_cast<std::byte*
>(data),
reinterpret_cast<std::byte*
>(data) + size};
1438 FreeEXRHeader(&header);
1451using stb_ptr = std::unique_ptr<T, void(*)(
void*)>;
1456 stbi_convert_iphone_png_to_rgb(
true);
1464#ifdef VTFPP_SUPPORT_EXR
1466 if (EXRVersion version; ParseEXRVersionFromMemory(&version,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size()) == TINYEXR_SUCCESS) {
1467 if (version.multipart || version.non_image) {
1472 InitEXRHeader(&header);
1473 const char* err =
nullptr;
1474 if (ParseEXRHeaderFromMemory(&header, &version,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size(), &err) != TINYEXR_SUCCESS) {
1475 FreeEXRErrorMessage(err);
1480 if (header.num_channels < 1) {
1481 FreeEXRHeader(&header);
1486 std::unordered_map<std::string_view, int> channelIndices{{
"R", -1}, {
"G", -1}, {
"B", -1}, {
"A", -1}, {
"Y", -1}};
1490 auto channelType = header.pixel_types[0];
1491 for (
int i = 1; i < header.num_channels; i++) {
1493 if (header.pixel_types[i] > channelType && channelIndices.contains(header.channels[i].name)) {
1494 channelType = header.pixel_types[i];
1498 if (channelType == TINYEXR_PIXELTYPE_UINT) {
1499 channelType = TINYEXR_PIXELTYPE_HALF;
1503 for (
int i = 0; i < header.num_channels; i++) {
1504 if (channelIndices.contains(header.channels[i].name)) {
1505 channelIndices[header.channels[i].name] = i;
1508 if (channelIndices[
"Y"] >= 0) {
1509 if (channelIndices[
"A"] >= 0) {
1512 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1514 channelType = TINYEXR_PIXELTYPE_FLOAT;
1518 channelIndices[
"R"] = channelIndices[
"Y"];
1519 channelIndices[
"G"] = channelIndices[
"Y"];
1520 channelIndices[
"B"] = channelIndices[
"Y"];
1521 }
else if (channelIndices[
"A"] >= 0) {
1523 }
else if (channelIndices[
"B"] >= 0) {
1524 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1526 channelType = TINYEXR_PIXELTYPE_FLOAT;
1529 }
else if (channelIndices[
"G"] >= 0) {
1531 }
else if (channelIndices[
"R"] >= 0) {
1534 FreeEXRHeader(&header);
1539 for (
int i = 0; i < header.num_channels; i++) {
1540 if (header.pixel_types[i] != channelType && channelIndices.contains(header.channels[i].name)) {
1541 header.requested_pixel_types[i] = channelType;
1546 InitEXRImage(&image);
1547 if (LoadEXRImageFromMemory(&image, &header,
reinterpret_cast<const unsigned char*
>(fileData.data()), fileData.size(), &err) != TINYEXR_SUCCESS) {
1548 FreeEXRErrorMessage(err);
1549 FreeEXRHeader(&header);
1553 width = image.width;
1554 height = image.height;
1558 const auto populateBuffer = [
1566 r=channelIndices[
"R"],
1567 g=channelIndices[
"G"],
1568 b=channelIndices[
"B"],
1569 a=channelIndices[
"A"],
1573 const auto channelCount = hasRed + hasGreen + hasBlue + hasAlpha;
1575 std::span out{
reinterpret_cast<C*
>(combinedChannels.data()), combinedChannels.size() /
sizeof(C)};
1577 for (
int t = 0; t < image.num_tiles; t++) {
1578 auto** src =
reinterpret_cast<C**
>(image.tiles[t].images);
1579 for (
int j = 0; j < header.tile_size_y; j++) {
1580 for (
int i = 0; i < header.tile_size_x; i++) {
1581 const auto ii =
static_cast<uint64_t
>(image.tiles[t].offset_x) * header.tile_size_x + i;
1582 const auto jj =
static_cast<uint64_t
>(image.tiles[t].offset_y) * header.tile_size_y + j;
1583 const auto idx = ii + jj * image.width;
1585 if (ii >= image.width || jj >= image.height) {
1589 const auto srcIdx = j *
static_cast<uint64_t
>(header.tile_size_x) + i;
1590 if (r >= 0) out[idx * channelCount + 0] = src[r][srcIdx];
1591 else if (hasRed) out[idx * channelCount + 0] = 0.f;
1592 if (g >= 0) out[idx * channelCount + 1] = src[g][srcIdx];
1593 else if (hasGreen) out[idx * channelCount + 1] = 0.f;
1594 if (b >= 0) out[idx * channelCount + 2] = src[b][srcIdx];
1595 else if (hasBlue) out[idx * channelCount + 2] = 0.f;
1596 if (a >= 0) out[idx * channelCount + 3] = src[a][srcIdx];
1597 else if (hasAlpha) out[idx * channelCount + 3] = 1.f;
1602 auto** src =
reinterpret_cast<C**
>(image.images);
1603 for (uint64_t i = 0; i < width * height; i++) {
1604 if (r >= 0) out[i * channelCount + 0] = src[r][i];
1605 else if (hasRed) out[i * channelCount + 0] = 0.f;
1606 if (g >= 0) out[i * channelCount + 1] = src[g][i];
1607 else if (hasGreen) out[i * channelCount + 1] = 0.f;
1608 if (b >= 0) out[i * channelCount + 2] = src[b][i];
1609 else if (hasBlue) out[i * channelCount + 2] = 0.f;
1610 if (a >= 0) out[i * channelCount + 3] = src[a][i];
1611 else if (hasAlpha) out[i * channelCount + 3] = 1.f;
1615 if (channelType == TINYEXR_PIXELTYPE_HALF) {
1616 populateBuffer.operator()<half>();
1618 populateBuffer.operator()<
float>();
1621 FreeEXRImage(&image);
1622 FreeEXRHeader(&header);
1623 return combinedChannels;
1628 if (stbi_is_hdr_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()))) {
1629 const ::stb_ptr<float> stbImage{
1630 stbi_loadf_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1646#ifdef VTFPP_SUPPORT_WEBP
1648 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) {
1649 width = features.width;
1650 height = features.height;
1654 std::vector<std::byte> out;
1655 if (features.has_alpha) {
1658 if (!WebPDecodeRGBAInto(
reinterpret_cast<const uint8_t*
>(fileData.data()), fileData.size(),
reinterpret_cast<uint8_t*
>(out.data()), out.size(), width * (
ImageFormatDetails::bpp(format) / 8))) {
1664 if (!WebPDecodeRGBInto(
reinterpret_cast<const uint8_t*
>(fileData.data()), fileData.size(),
reinterpret_cast<uint8_t*
>(out.data()), out.size(), width * (
ImageFormatDetails::bpp(format) / 8))) {
1673 if (fileData.size() > 3 &&
static_cast<char>(fileData[0]) ==
'G' &&
static_cast<char>(fileData[1]) ==
'I' &&
static_cast<char>(fileData[2]) ==
'F') {
1674 const ::stb_ptr<stbi_uc> stbImage{
1675 stbi_load_gif_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()),
nullptr, &width, &height, &frameCount, &channels, 0),
1678 if (!stbImage || !frameCount) {
1688 return {
reinterpret_cast<std::byte*
>(stbImage.get()),
reinterpret_cast<std::byte*
>(stbImage.get() + (
ImageFormatDetails::getDataLength(format, width, height) * frameCount))};
1694 stbi__start_mem(&s,
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()));
1695 if (stbi__png_test(&s)) {
1697 const auto apngDecoder = [&format, &width, &height, &frameCount]<
typename P>(
const auto& stbImage, std::size_t dirOffset) -> std::vector<std::byte> {
1698 auto* dir =
reinterpret_cast<stbi__apng_directory*
>(stbImage.get() + dirOffset);
1699 if (dir->type != STBI__STRUCTURE_TYPE_APNG_DIRECTORY) {
1704 frameCount =
static_cast<int>(dir->num_frames);
1706 static constexpr auto calcPixelOffset = [](uint32_t offsetX, uint32_t offsetY, uint32_t width_) {
1707 return ((offsetY * width_) + offsetX) *
sizeof(P);
1711 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) {
1712 for (uint32_t y = 0; y < srcHeight; y++) {
1714#ifdef SOURCEPP_BUILD_WITH_TBB
1715 std::execution::unseq,
1717 src.data() + calcPixelOffset( 0, y, srcWidth),
1718 src.data() + calcPixelOffset( srcWidth, y, srcWidth),
1719 dst.data() + calcPixelOffset(srcOffsetX, srcOffsetY + y, dstWidth));
1724 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) {
1725 for (uint32_t y = subOffsetY; y < subOffsetY + subHeight; y++) {
1727#ifdef SOURCEPP_BUILD_WITH_TBB
1728 std::execution::unseq,
1730 src.data() + calcPixelOffset(subOffsetX, y, imgWidth),
1731 src.data() + calcPixelOffset(subOffsetX + subWidth, y, imgWidth),
1732 dst.data() + calcPixelOffset(subOffsetX, y, imgWidth));
1736 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) {
1737 for (uint32_t y = 0; y < clrHeight; y++) {
1739#ifdef SOURCEPP_BUILD_WITH_TBB
1740 std::execution::unseq,
1742 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth),
1743 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth) + (clrWidth *
sizeof(P)),
1744 dst.data() + calcPixelOffset(clrOffsetX, clrOffsetY + y, dstWidth),
1745 [](std::byte) {
return std::byte{0}; });
1749 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) {
1750 for (uint32_t y = 0; y < srcHeight; y++) {
1751 const auto* sp =
reinterpret_cast<const uint8_t*
>(src.data() + calcPixelOffset(0, y, srcWidth));
1752 auto* dp =
reinterpret_cast<uint8_t*
>(dst.data() + calcPixelOffset(srcOffsetX, srcOffsetY + y, dstWidth));
1753 for (uint32_t x = 0; x < srcWidth; x++, sp += 4, dp += 4) {
1757 if ((sp[3] == 0xff) || (dp[3] == 0)) {
1758 std::copy_n(sp,
sizeof(P), dp);
1760 const int u = sp[3] * 0xff;
1761 const int v = (0xff - sp[3]) * dp[3];
1762 const int al = u + v;
1763 dp[0] = (sp[0] * u + dp[0] * v) / al;
1764 dp[1] = (sp[1] * u + dp[1] * v) / al;
1765 dp[2] = (sp[2] * u + dp[2] * v) / al;
1773 const uint64_t fullFrameSize =
sizeof(P) * width * height;
1774 uint64_t currentFrameSize = 0;
1775 std::vector<std::byte> out(fullFrameSize * frameCount);
1776 uint64_t srcFrameOffset = 0;
1777 uint64_t dstFrameOffset = 0;
1778 for (uint32_t i = 0; i < dir->num_frames; i++) {
1779 const auto& frame = dir->frames[i];
1780 currentFrameSize =
sizeof(P) * frame.width * frame.height;
1783 if (frame.width == width && frame.height == height && frame.x_offset == 0 && frame.y_offset == 0 && frame.blend_op == STBI_APNG_blend_op_source) {
1784 std::memcpy(out.data() + dstFrameOffset, stbImage.get() + srcFrameOffset, fullFrameSize);
1787 if (frame.blend_op == STBI_APNG_blend_op_source || (i == 0 && frame.blend_op == STBI_APNG_blend_op_over)) {
1788 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);
1789 }
else if (frame.blend_op == STBI_APNG_blend_op_over) {
1790 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);
1796 dstFrameOffset += fullFrameSize;
1797 srcFrameOffset += currentFrameSize;
1800 if (i == dir->num_frames - 1) {
1805 copyImageData({out.data() + dstFrameOffset, out.data() + dstFrameOffset + fullFrameSize}, width, height, {out.data() + dstFrameOffset - fullFrameSize, out.data() + dstFrameOffset}, width, height, 0, 0);
1808 if (frame.dispose_op == STBI_APNG_dispose_op_background || (i == 0 && frame.dispose_op == STBI_APNG_dispose_op_previous)) {
1809 clearImageData({out.data() + dstFrameOffset, out.data() + dstFrameOffset + fullFrameSize}, width, height, frame.width, frame.height, frame.x_offset, frame.y_offset);
1810 }
else if (frame.dispose_op == STBI_APNG_dispose_op_previous) {
1811 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);
1812 }
else if (frame.dispose_op != STBI_APNG_dispose_op_none) {
1819 static const char *dispose_ops[] = {
1820 "STBI_APNG_dispose_op_none",
1821 "STBI_APNG_dispose_op_background",
1822 "STBI_APNG_dispose_op_previous",
1825 static const char *blend_ops[] = {
1826 "STBI_APNG_blend_op_source",
1827 "STBI_APNG_blend_op_over",
1830 fprintf(stderr,
"dir_offset : %zu\n", dirOffset);
1831 fprintf(stderr,
"dir.type : %.*s\n", 4, (
unsigned char *) &dir->type);
1832 fprintf(stderr,
"dir.num_frames : %u\n", dir->num_frames);
1833 fprintf(stderr,
"dir.default_image_is_first_frame : %s\n",
1834 dir->default_image_is_first_frame ?
"yes" :
"no");
1835 fprintf(stderr,
"dir.num_plays : %u\n", dir->num_plays);
1837 for (
int i = 0; i < dir->num_frames; ++i) {
1838 stbi__apng_frame_directory_entry *frame = &dir->frames[i];
1840 fprintf(stderr,
"frame : %u\n", i);
1841 fprintf(stderr,
" width : %u\n", frame->width);
1842 fprintf(stderr,
" height : %u\n", frame->height);
1843 fprintf(stderr,
" x_offset : %u\n", frame->x_offset);
1844 fprintf(stderr,
" y_offset : %u\n", frame->y_offset);
1845 fprintf(stderr,
" delay_num : %u\n", frame->delay_num);
1846 fprintf(stderr,
" delay_den : %u\n", frame->delay_den);
1847 fprintf(stderr,
" dispose_op : %s\n", dispose_ops[frame->dispose_op]);
1848 fprintf(stderr,
" blend_op : %s\n", blend_ops[frame->blend_op]);
1854 std::size_t dirOffset = 0;
1855 if (stbi__png_is16(&s)) {
1856 const ::stb_ptr<stbi_us> stbImage{
1857 stbi__apng_load_16bit(&s, &width, &height, &channels, STBI_rgb_alpha, &dirOffset),
1860 if (stbImage && dirOffset) {
1861 return apngDecoder.operator()<ImagePixel::RGBA16161616>(stbImage, dirOffset);
1864 const ::stb_ptr<stbi_uc> stbImage{
1865 stbi__apng_load_8bit(&s, &width, &height, &channels, STBI_rgb_alpha, &dirOffset),
1868 if (stbImage && dirOffset) {
1869 return apngDecoder.operator()<ImagePixel::RGBA8888>(stbImage, dirOffset);
1875#ifdef VTFPP_SUPPORT_QOI
1878 qoi_desc descriptor;
1879 const ::stb_ptr<std::byte> qoiImage{
1880 static_cast<std::byte*
>(qoi_decode(fileData.data(),
static_cast<int>(fileData.size()), &descriptor, 0)),
1886 width =
static_cast<int>(descriptor.width);
1887 height =
static_cast<int>(descriptor.height);
1888 channels = descriptor.channels;
1899 if (stbi_is_16_bit_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()))) {
1900 const ::stb_ptr<stbi_us> stbImage{
1901 stbi_load_16_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1907 if (channels == 4) {
1909 }
else if (channels >= 1 && channels < 4) {
1913 std::span outPixels{
reinterpret_cast<ImagePixel::RGBA16161616*
>(out.data()), out.size() /
sizeof(ImagePixel::RGBA16161616)};
1916 std::span inPixels{stbImage.get(), outPixels.size()};
1918#ifdef SOURCEPP_BUILD_WITH_TBB
1919 std::execution::par_unseq,
1921 inPixels.begin(), inPixels.end(), outPixels.begin(), [](uint16_t pixel) -> ImagePixel::RGBA16161616 {
1922 return {{pixel, 0, 0, 0xffff}};
1931 std::span inPixels{
reinterpret_cast<RG1616*
>(stbImage.get()), outPixels.size()};
1933#ifdef SOURCEPP_BUILD_WITH_TBB
1934 std::execution::par_unseq,
1936 inPixels.begin(), inPixels.end(), outPixels.begin(), [](RG1616 pixel) -> ImagePixel::RGBA16161616 {
1937 return {{pixel.r, pixel.g, 0, 0xffff}};
1947 std::span inPixels{
reinterpret_cast<RGB161616*
>(stbImage.get()), outPixels.size()};
1949#ifdef SOURCEPP_BUILD_WITH_TBB
1950 std::execution::par_unseq,
1952 inPixels.begin(), inPixels.end(), outPixels.begin(), [](RGB161616 pixel) -> ImagePixel::RGBA16161616 {
1953 return {{pixel.r, pixel.g, pixel.b, 0xffff}};
1967 const ::stb_ptr<stbi_uc> stbImage{
1968 stbi_load_from_memory(
reinterpret_cast<const stbi_uc*
>(fileData.data()),
static_cast<int>(fileData.size()), &width, &height, &channels, 0),
1997 return {width, height};
2022 return convertImageDataToFormat(
resizeImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, newWidth, height, newHeight, srgb, filter, edge), container, format, newWidth, newHeight);
2025 STBIR_RESIZE resize;
2026 const auto setEdgeModesAndFiltersAndDoResize = [edge, filter, &resize] {
2027 stbir_set_edgemodes(&resize,
static_cast<stbir_edge
>(edge),
static_cast<stbir_edge
>(edge));
2036 stbir_set_filters(&resize,
static_cast<stbir_filter
>(filter),
static_cast<stbir_filter
>(filter));
2040 static constexpr auto KAISER_RADIUS = 3.f;
2041 static constexpr auto KAISER_ALPHA = 2.f;
2042 static constexpr auto KAISER_WINDOW = [](
double u) ->
double {
2043 if (u < -1 || u > 1)
return 0;
2046 static constexpr auto KAISER_FILTER = [](
float x, float,
void*) ->
float {
2047 if (x <= -KAISER_RADIUS || x >= KAISER_RADIUS)
return 0;
2048 if (std::abs(x) < 1e-6f)
return 1;
2049 return static_cast<float>(
math::sinc(x) * KAISER_WINDOW(std::abs(x) / KAISER_RADIUS));
2051 static constexpr auto KAISER_SUPPORT = [](float,
void*) ->
float {
2052 return KAISER_RADIUS;
2054 stbir_set_filter_callbacks(&resize, KAISER_FILTER, KAISER_SUPPORT, KAISER_FILTER, KAISER_SUPPORT);
2058 static constexpr auto NICE_RADIUS = 3.f;
2059 static constexpr auto NICE_SHARPEN = 1.25f;
2060 static constexpr auto NICE_FILTER = [](
float x, float,
void*) ->
float {
2061 if (x <= -NICE_RADIUS || x >= NICE_RADIUS)
return 0;
2062 if (std::abs(x) < 1e-6f)
return 1;
2064 if (out < 0) out *= NICE_SHARPEN;
2067 static constexpr auto NICE_SUPPORT = [](float,
void*) ->
float {
2070 stbir_set_filter_callbacks(&resize, NICE_FILTER, NICE_SUPPORT, NICE_FILTER, NICE_SUPPORT);
2074 stbir_resize_extended(&resize);
2077 const auto pixelLayout = ::imageFormatToSTBIRPixelLayout(format);
2078 if (pixelLayout == -1) {
2082 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)));
2083 setEdgeModesAndFiltersAndDoResize();
2087 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)));
2088 setEdgeModesAndFiltersAndDoResize();
2092std::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) {
2098 return resizeImageData(imageData, format, width, widthOut, height, heightOut, srgb, filter, edge);
2102std::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) {
2103 if (imageData.empty() || format ==
ImageFormat::EMPTY || xOffset + newWidth >= width || yOffset + newHeight >= height) {
2109 return convertImageDataToFormat(
cropImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, newWidth, xOffset, height, newHeight, yOffset), container, format, newWidth, newHeight);
2113 std::vector<std::byte> out(pixelSize * newWidth * newHeight);
2114 for (uint16_t y = yOffset; y < yOffset + newHeight; y++) {
2115 std::memcpy(out.data() + (((y - yOffset) * newWidth) * pixelSize), imageData.data() + (((y * width) + xOffset) * pixelSize), newWidth * pixelSize);
2125 if (!widthPad && !heightPad) {
2126 return {imageData.begin(), imageData.end()};
2131 return convertImageDataToFormat(
padImageData(
convertImageDataToFormat(imageData, format, container, width, height), container, width, widthPad, height, heightPad), container, format, width + widthPad, height + heightPad);
2135 std::vector<std::byte> out(pixelSize * (width + widthPad) * (height + heightPad));
2138 for (uint16_t y = 0; y < height; y++) {
2139 std::memcpy(out.data() + ((y * (width + widthPad)) * pixelSize), imageData.data() + ((y * width) * pixelSize), width * pixelSize);
2143 for (
int y = 0; y < height + heightPad; y++) {
2144 for (
int x = 0; x < width + widthPad; x++) {
2145 if (x >= width && y >= height) {
2146 std::memcpy(out.data() + ((y * (width + widthPad) + x) * pixelSize), imageData.data() + (((height - 1) * (width) + (width - 1)) * pixelSize), pixelSize);
2147 }
else if (x >= width) {
2148 std::memcpy(out.data() + ((y * (width + widthPad) + x) * pixelSize), imageData.data() + ((y * (width) + (width - 1)) * pixelSize), pixelSize);
2149 }
else if (y >= height) {
2150 std::memcpy(out.data() + ((y * (width + widthPad) + x) * pixelSize), imageData.data() + (((height - 1) * (width) + x) * pixelSize), pixelSize);
2172 std::memcpy(imageData.data(), newData.data(), imageData.size());
2176 static constexpr auto calculateGammaLUT = [](
float gamma_, uint8_t channelSize) -> std::array<uint8_t, 256> {
2177 const auto maxSize =
static_cast<float>((1 << channelSize) - 1);
2178 std::array<uint8_t, 256> gammaLUT{};
2179 for (
int i = 0; i < gammaLUT.size(); i++) {
2180 gammaLUT[i] =
static_cast<uint8_t
>(std::clamp(std::pow((
static_cast<float>(i) + 0.5f) / maxSize, gamma_) * maxSize - 0.5f, 0.f, maxSize));
2185 #define VTFPP_CREATE_GAMMA_LUTS(InputType) \
2186 std::unordered_map<uint8_t, std::array<uint8_t, 256>> gammaLUTs; \
2187 if constexpr (ImageFormatDetails::red(ImageFormat::InputType) > 0) { \
2188 if (!gammaLUTs.contains(ImageFormatDetails::red(ImageFormat::InputType))) { \
2189 gammaLUTs[ImageFormatDetails::red(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::red(ImageFormat::InputType)); \
2192 if constexpr (ImageFormatDetails::green(ImageFormat::InputType) > 0) { \
2193 if (!gammaLUTs.contains(ImageFormatDetails::green(ImageFormat::InputType))) { \
2194 gammaLUTs[ImageFormatDetails::green(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::green(ImageFormat::InputType)); \
2197 if constexpr (ImageFormatDetails::blue(ImageFormat::InputType) > 0) { \
2198 if (!gammaLUTs.contains(ImageFormatDetails::blue(ImageFormat::InputType))) { \
2199 gammaLUTs[ImageFormatDetails::blue(ImageFormat::InputType)] = calculateGammaLUT(gamma, ImageFormatDetails::blue(ImageFormat::InputType)); \
2203 #define VTFPP_APPLY_GAMMA_RED(value) \
2204 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::red(PIXEL_TYPE::FORMAT))[value])
2206 #define VTFPP_APPLY_GAMMA_GREEN(value) \
2207 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::green(PIXEL_TYPE::FORMAT))[value])
2209 #define VTFPP_APPLY_GAMMA_BLUE(value) \
2210 static_cast<decltype(value)>(gammaLUTs.at(ImageFormatDetails::blue(PIXEL_TYPE::FORMAT))[value])
2212 #define VTFPP_GAMMA_CORRECT_CASE(InputType, ...) \
2214 VTFPP_CREATE_GAMMA_LUTS(InputType) \
2215 return ImagePixel::transformInPlace<ImagePixel::InputType>(imageData, [&gammaLUTs](ImagePixel::InputType pixel) -> ImagePixel::InputType { \
2216 using PIXEL_TYPE = ImagePixel::InputType; \
2217 return {__VA_ARGS__}; \
2244 #undef VTFPP_GAMMA_CORRECT_CASE
2245 #undef VTFPP_APPLY_GAMMA_BLUE
2246 #undef VTFPP_APPLY_GAMMA_GREEN
2247 #undef VTFPP_APPLY_GAMMA_RED
2248 #undef VTFPP_CREATE_GAMMA_LUTS
2265 std::memcpy(imageData.data(), newData.data(), imageData.size());
2269 #define VTFPP_INVERT_GREEN_CASE_OVERRIDE(PixelType, ChannelName, ChannelNameCaps, ...) \
2270 case PixelType: return ImagePixel::transformInPlace<ImagePixel::PixelType>(imageData, [](ImagePixel::PixelType pixel) -> ImagePixel::PixelType { \
2271 static constexpr auto channelSize = ImageFormatDetails::green(ImagePixel::PixelType::FORMAT); \
2272 if constexpr (std::same_as<decltype(pixel.ChannelName()), float> || std::same_as<decltype(pixel.ChannelName()), half>) { \
2273 pixel.set##ChannelNameCaps(static_cast<decltype(pixel.ChannelName())>(static_cast<float>(static_cast<uint64_t>(1) << channelSize) - 1.f - static_cast<float>(pixel.ChannelName()))); \
2275 if constexpr (channelSize >= sizeof(uint32_t) * 8) { \
2276 pixel.set##ChannelNameCaps(static_cast<decltype(pixel.ChannelName())>((static_cast<uint64_t>(1) << channelSize) - 1 - static_cast<uint32_t>(pixel.ChannelName()))); \
2278 pixel.set##ChannelNameCaps(static_cast<decltype(pixel.ChannelName())>(static_cast<uint32_t>(1 << channelSize) - 1 - static_cast<uint32_t>(pixel.ChannelName()))); \
2284 #define VTFPP_INVERT_GREEN_CASE(PixelType) VTFPP_INVERT_GREEN_CASE_OVERRIDE(PixelType, g, G);
2328 #undef VTFPP_INVERT_GREEN_CASE
2329 #undef VTFPP_INVERT_GREEN_CASE_OVERRIDE
2345 std::memcpy(imageData.data(), newData.data(), imageData.size());
2350 static constexpr auto hableTonemapChannel = [](
float x)
constexpr {
2351 constexpr auto hableTonemapChannelPartial = [](
float y)
constexpr {
2352 constexpr float A = 0.15f, B = 0.5f, C = 0.1f, D = 0.2f, E = 0.02f, F = 0.3f;
2353 return ((y * (A * y + C * B) + D * E) / (y * (A * y + B) + D * F)) - E / F;
2355 constexpr float EXPOSURE_BIAS = 2.0f, W = 11.2f;
2356 return hableTonemapChannelPartial(x * EXPOSURE_BIAS) * (1.f / hableTonemapChannelPartial(W));
2359 #define VTFPP_TONEMAP_CASE(InputType, ...) \
2361 return ImagePixel::transformInPlace<ImagePixel::InputType>(imageData, [](ImagePixel::InputType pixel) -> ImagePixel::InputType { \
2362 return {__VA_ARGS__}; \
2372 VTFPP_TONEMAP_CASE(
RGBA16161616F, {half{hableTonemapChannel(pixel.r())}, half{hableTonemapChannel(pixel.g())}, half{hableTonemapChannel(pixel.b())}, pixel.a()});
2377 #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 > 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.
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 > 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 > 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, 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