Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 43 additions & 38 deletions src/tabs/viewer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ const std::initializer_list<FileSignature> 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
};
Expand Down Expand Up @@ -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);
Expand All @@ -1636,21 +1646,21 @@ LoadLibraryTexture (image_s& image)

DirectX::ScratchImage temp_img;

if (SUCCEEDED (temp_img.Initialize2D (DXGI_FORMAT_R16G16B16A16_FLOAT, static_cast <size_t> (image.width),
static_cast <size_t> (image.height), 1, 1)))
if (SUCCEEDED (temp_img.Initialize2D (dxgi_format, static_cast <size_t> (image.width),
static_cast <size_t> (image.height), 1, 1)))
{
using namespace DirectX;

image.channels = 3;
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 <size_t> (image.width);
meta.height = static_cast <size_t> (image.height);
meta.depth = 1;
Expand All @@ -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
}
);
Expand Down Expand Up @@ -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 (
Expand All @@ -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,
Expand Down
105 changes: 102 additions & 3 deletions src/utility/image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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 <uint32_t> (image.width);
uint32_t height = static_cast <uint32_t> (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 <IWICMetadataQueryReader> pQueryReader;
///
Expand Down Expand Up @@ -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);
Expand Down