From a5a532d45dba762d48ff940319dd0f005c7e503e Mon Sep 17 00:00:00 2001 From: MagisterX <36625965+MagisterX@users.noreply.github.com> Date: Sat, 28 Mar 2026 02:04:15 +0400 Subject: [PATCH] Added support for sdr avif decode/encode --- src/tabs/viewer.cpp | 81 +++++++++++++++++--------------- src/utility/image.cpp | 105 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 145 insertions(+), 41 deletions(-) diff --git a/src/tabs/viewer.cpp b/src/tabs/viewer.cpp index 0ddb5b7..7d135dc 100644 --- a/src/tabs/viewer.cpp +++ b/src/tabs/viewer.cpp @@ -160,6 +160,7 @@ const std::initializer_list supported_sdr_encode_formats = FileSignature { L"image/bmp", { L".bmp" }, { 0x42, 0x4D } }, FileSignature { L"image/tiff", { L".tiff", L".tif" }, { 0x49, 0x49, 0x2A, 0x00 } }, // TIFF: little-endian FileSignature { L"image/tiff", { L".tiff", L".tif" }, { 0x4D, 0x4D, 0x00, 0x2A } }, // TIFF: big-endian + FileSignature { L"image/avif", { L".avif" }, { 0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66 } }, // ftypavif //FileSignature { L"image/vnd-ms.dds", { L".dds" }, { 0x44, 0x44, 0x53, 0x20 } }, //FileSignature { L"image/x-targa", { L".tga" }, { 0x00, } }, // TGA has no real unique header identifier, so just use the file extension on those }; @@ -1620,13 +1621,22 @@ LoadLibraryTexture (image_s& image) SK_avifRGBImageSetDefaults (&rgb, avif_decoder->image); int bpc = rgb.depth; + bool is_hdr_image = (avif_decoder->image->depth > 8) || + (avif_decoder->image->transferCharacteristics == AVIF_TRANSFER_CHARACTERISTICS_SMPTE2084); + DXGI_FORMAT dxgi_format = is_hdr_image ? DXGI_FORMAT_R16G16B16A16_FLOAT : + DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; - rgb.depth = 16; - rgb.format = AVIF_RGB_FORMAT_RGBA; + if (avif_decoder->image->colorPrimaries == AVIF_COLOR_PRIMARIES_UNSPECIFIED) + { + //ideally try to retrieve primaries from icc here + } + + rgb.depth = is_hdr_image ? 16 : 8; + rgb.format = is_hdr_image ? AVIF_RGB_FORMAT_RGBA : AVIF_RGB_FORMAT_BGRA; rgb.maxThreads = std::min (64U, std::min ((UINT)si.dwNumberOfProcessors, (UINT)__popcnt64 (si.dwActiveProcessorMask))); - rgb.ignoreAlpha = true; - rgb.isFloat = true; + rgb.ignoreAlpha = avif_decoder->image->alphaPlane ? false : true; + rgb.isFloat = is_hdr_image ? true : false; SK_avifRGBImageAllocatePixels ( &rgb); SK_avifImageYUVToRGB (avif_decoder->image, &rgb); @@ -1636,8 +1646,8 @@ LoadLibraryTexture (image_s& image) DirectX::ScratchImage temp_img; - if (SUCCEEDED (temp_img.Initialize2D (DXGI_FORMAT_R16G16B16A16_FLOAT, static_cast (image.width), - static_cast (image.height), 1, 1))) + if (SUCCEEDED (temp_img.Initialize2D (dxgi_format, static_cast (image.width), + static_cast (image.height), 1, 1))) { using namespace DirectX; @@ -1645,12 +1655,12 @@ LoadLibraryTexture (image_s& image) image.bpc = bpc; // XXX - image.light_info.isHDR = true; - image.is_hdr = true; + image.light_info.isHDR = is_hdr_image; + image.is_hdr = is_hdr_image; succeeded = true; - meta.format = DXGI_FORMAT_R16G16B16A16_FLOAT; + meta.format = dxgi_format; meta.width = static_cast (image.width); meta.height = static_cast (image.height); meta.depth = 1; @@ -1667,13 +1677,13 @@ LoadLibraryTexture (image_s& image) memcpy (pixels_buffer, rgb.pixels, rgb.rowBytes * rgb.height); - if (avif_decoder->image->transferCharacteristics != AVIF_TRANSFER_CHARACTERISTICS_PQ) + if (avif_decoder->image->transferCharacteristics == AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED) { - ImGui::InsertNotification ( + ImGui::InsertNotification( { ImGuiToastType::Error, - 15000, - "Unsupported AVIF Transfer Characteristics: %d", + 10000, + "Unspecified AVIF Transfer Characteristics: %d", avif_decoder->image->transferCharacteristics } ); @@ -1769,36 +1779,25 @@ LoadLibraryTexture (image_s& image) else if (avif_decoder->image->colorPrimaries == AVIF_COLOR_PRIMARIES_BT709 || avif_decoder->image->colorPrimaries == AVIF_COLOR_PRIMARIES_SRGB) { - if ( SUCCEEDED ( TransformImage (*temp_img.GetImages (), - [&]( XMVECTOR* outPixels, - const XMVECTOR* inPixels, - size_t width, - size_t y) - { - UNREFERENCED_PARAMETER(y); - - for (size_t j = 0; j < width; ++j) - { - XMVECTOR v = inPixels [j]; - - v = - XMVectorScale ( - SKIV_Image_PQToLinear (v), 125.0f - ); - - outPixels [j] = v; - } - }, img ) - ) - ) { + std::swap(img, temp_img); temp_img.Release (); } } else { - if (avif_decoder->image->colorPrimaries != AVIF_COLOR_PRIMARIES_BT2100 && + if (avif_decoder->image->colorPrimaries == AVIF_COLOR_PRIMARIES_UNSPECIFIED) { + ImGui::InsertNotification( + { + ImGuiToastType::Error, + 10000, + "Unspecified AVIF Color Primaries: %d", + avif_decoder->image->transferCharacteristics + } + ); + } + else if (avif_decoder->image->colorPrimaries != AVIF_COLOR_PRIMARIES_BT2100 && avif_decoder->image->colorPrimaries != AVIF_COLOR_PRIMARIES_BT2020) { ImGui::InsertNotification ( @@ -1810,8 +1809,14 @@ LoadLibraryTexture (image_s& image) } ); } - - if ( SUCCEEDED ( TransformImage (*temp_img.GetImages (), + //HDR 8-bpc is not handled correctly + if (bpc == 8 && avif_decoder->image->colorPrimaries == AVIF_COLOR_PRIMARIES_UNSPECIFIED) { + //for now treat it as SDR, but that's incorrect measure in long run + std::swap(img, temp_img); + temp_img.Release(); + } + //everything that reached here is treated as HDR image + else if ( SUCCEEDED ( TransformImage (*temp_img.GetImages (), [&]( XMVECTOR* outPixels, const XMVECTOR* inPixels, size_t width, diff --git a/src/utility/image.cpp b/src/utility/image.cpp index 088eaac..4a27d66 100644 --- a/src/utility/image.cpp +++ b/src/utility/image.cpp @@ -1603,6 +1603,8 @@ SKIV_Image_TonemapToSDR (const DirectX::Image& image, DirectX::ScratchImage& fin HRESULT SKIV_Image_SaveToDisk_SDR (const DirectX::Image& image, const wchar_t* wszFileName, const bool force_sRGB) { + static SKIF_RegistrySettings& _registry = + SKIF_RegistrySettings::GetInstance(); using namespace DirectX; ScratchImage @@ -1792,8 +1794,9 @@ SKIV_Image_SaveToDisk_SDR (const DirectX::Image& image, const wchar_t* wszFileNa bPrefer10bpcAs32bpp = is_hdr; } - // AVIF technically works for SDR... do we want to support it? - // If we do, WIC won't help us, however. + else if (StrStrIW(wszExtension, L"avif")) { + + } else { @@ -2047,6 +2050,102 @@ SKIV_Image_SaveToDisk_SDR (const DirectX::Image& image, const wchar_t* wszFileNa final_sdr.GetImages (); } + if (StrStrIW(wszExtension, L"avif")) + { + // Ensure the AVIF library functions are available + extern bool isAVIFEncoderAvailable(void); + if (!isAVIFEncoderAvailable()) + return E_NOTIMPL; + + uint32_t width = static_cast (image.width); + uint32_t height = static_cast (image.height); + + // --- SDR Specific AVIF Settings --- + // use 8-bit depth for SDR + int bit_depth = 8; + // YUV 4:4:4 is highest quality + avifPixelFormat yuv_format = AVIF_PIXEL_FORMAT_YUV444; + + avifResult rgbToYuvResult = AVIF_RESULT_NO_CONTENT; + avifResult addResult = AVIF_RESULT_NO_CONTENT; + avifResult encodeResult = AVIF_RESULT_NO_CONTENT; + + avifRWData avifOutput = AVIF_DATA_EMPTY; + avifRGBImage rgb = { }; + avifEncoder* encoder = nullptr; + + avifImage* avif_image = SK_avifImageCreate(width, height, bit_depth, yuv_format); + + if (avif_image != nullptr) { + // SDR Colorimetry (BT.709 / sRGB) + avif_image->colorPrimaries = AVIF_COLOR_PRIMARIES_BT709; + avif_image->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB; + avif_image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT709; + avif_image->yuvRange = AVIF_RANGE_FULL; + + SK_avifRGBImageSetDefaults(&rgb, avif_image); + rgb.rowBytes = pOutputImage->rowPitch; + rgb.depth = 8; + rgb.ignoreAlpha = false; + + //maybe there's a better way to handle format + if (image.format == DXGI_FORMAT_R8G8B8A8_UNORM || + image.format == DXGI_FORMAT_R8G8B8A8_UNORM_SRGB) + rgb.format = AVIF_RGB_FORMAT_RGBA; + else + rgb.format = AVIF_RGB_FORMAT_BGRA; + rgb.pixels = (uint8_t*)pOutputImage->pixels; + + // RGB data to YUV + rgbToYuvResult = SK_avifImageRGBToYUV(avif_image, &rgb); + + if (rgbToYuvResult == AVIF_RESULT_OK) { + encoder = SK_avifEncoderCreate(); + if (encoder != nullptr) { + SYSTEM_INFO si = { }; + GetSystemInfo(&si); + // encoder settings + encoder->quality = _registry.avif.quality; + encoder->qualityAlpha = _registry.avif.quality; + encoder->speed = _registry.avif.speed; + encoder->timescale = 1; + encoder->minQuantizer = AVIF_QUANTIZER_BEST_QUALITY; + encoder->maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY; + encoder->codecChoice = AVIF_CODEC_CHOICE_AUTO; + encoder->repetitionCount = AVIF_REPETITION_COUNT_INFINITE; +#ifdef _M_X64 + encoder->maxThreads = std::min(64U, std::min((UINT)si.dwNumberOfProcessors, (UINT)__popcnt64(si.dwActiveProcessorMask))); +#endif + addResult = SK_avifEncoderAddImage(encoder, avif_image, 1, AVIF_ADD_IMAGE_FLAG_SINGLE); + encodeResult = SK_avifEncoderFinish(encoder, &avifOutput); + } + } + } + + + if (rgbToYuvResult != AVIF_RESULT_OK || addResult != AVIF_RESULT_OK || encodeResult != AVIF_RESULT_OK) { + // Optional: Add logging for error codes here + PLOG_ERROR << "avif conversion failed"; + } + + if (encodeResult == AVIF_RESULT_OK) { + // Write the encoded data to disk + FILE* fAVIF = _wfopen(wszImplicitFileName, L"wb"); + if (fAVIF != nullptr) { + fwrite(avifOutput.data, 1, avifOutput.size, fAVIF); + fclose(fAVIF); + } + } + + //cleanup + if (avif_image != nullptr) SK_avifImageDestroy(avif_image); + if (encoder != nullptr) SK_avifEncoderDestroy(encoder); + + //SK_avifRGBImageFreePixels(&rgb); + + return (encodeResult == AVIF_RESULT_OK) ? S_OK : E_FAIL; + } + ///DirectX::TexMetadata orig_tex_metadata; ///CComPtr pQueryReader; /// @@ -2830,7 +2929,7 @@ SKIV_Image_SaveToDisk_HDR (const DirectX::Image& image, const wchar_t* wszFileNa avif_image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT2020_NCL; break; default: - return false; + return E_UNEXPECTED; } SK_avifRGBImageSetDefaults (&rgb, avif_image);