From 005df37f1b10ff97908dee7da96e776fa229cee7 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Tue, 23 Jan 2024 20:38:23 -0800
Subject: [PATCH] Added support for loading HDR AVIF images
---
CHANGES.txt | 2 +-
external/SDL | 2 +-
src/IMG_avif.c | 184 ++++++++++++++++++++++++++++++++++++++++++++-----
3 files changed, 167 insertions(+), 21 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index 9df178b1..a01aacdb 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,2 +1,2 @@
3.0.0:
- * Updated for SDL 3.0
+ * Added support for loading HDR AVIF images
diff --git a/external/SDL b/external/SDL
index 8a1afc9b..8fe257b5 160000
--- a/external/SDL
+++ b/external/SDL
@@ -1 +1 @@
-Subproject commit 8a1afc9b10e9153f764f562008795b415d0575e1
+Subproject commit 8fe257b541777f554470c427bf6c297484d6cee9
diff --git a/src/IMG_avif.c b/src/IMG_avif.c
index de403abc..909ac836 100644
--- a/src/IMG_avif.c
+++ b/src/IMG_avif.c
@@ -235,14 +235,87 @@ static void DestroyAVIFIO(struct avifIO * io)
}
}
+static int ConvertGBR444toXBGR2101010(avifImage *image, SDL_Surface *surface)
+{
+ const Uint16 *srcR, *srcG, *srcB;
+ int srcskipR, srcskipG, srcskipB;
+ int sR, sG, sB;
+ Uint32 *dst;
+ int dstskip;
+ int width, height;
+
+ srcR = (Uint16 *)image->yuvPlanes[2];
+ srcskipR = (image->yuvRowBytes[2] - image->width * sizeof(Uint16)) / sizeof(Uint16);
+ srcG = (Uint16 *)image->yuvPlanes[0];
+ srcskipG = (image->yuvRowBytes[0] - image->width * sizeof(Uint16)) / sizeof(Uint16);
+ srcB = (Uint16 *)image->yuvPlanes[1];
+ srcskipB = (image->yuvRowBytes[1] - image->width * sizeof(Uint16)) / sizeof(Uint16);
+ if (srcskipR < 0 || srcskipG < 0 || srcskipB < 0) {
+ return -1;
+ }
+
+ dst = (Uint32 *)surface->pixels;
+ dstskip = (surface->pitch - image->width * sizeof(Uint32)) / sizeof(Uint32);
+
+ height = image->height;
+ while (height--) {
+ width = image->width;
+ while (width--) {
+ sR = *srcR++;
+ sG = *srcG++;
+ sB = *srcB++;
+ sR = SDL_min(sR, 1023);
+ sG = SDL_min(sG, 1023);
+ sB = SDL_min(sB, 1023);
+ *dst = (0x03 << 30) | (sB << 20) | (sG << 10) | sR;
+ ++dst;
+ }
+ srcR += srcskipR;
+ srcG += srcskipG;
+ srcB += srcskipB;
+ dst += dstskip;
+ }
+ return 0;
+}
+
+static int ConvertRGB16toXBGR2101010(avifRGBImage *image, SDL_Surface *surface)
+{
+ const Uint16 *src;
+ Uint32 sR, sG, sB;
+ Uint32 *dst;
+ int dstskip;
+ int width, height;
+
+ src = (Uint16 *)image->pixels;
+ dst = (Uint32 *)surface->pixels;
+ dstskip = (surface->pitch - image->width * sizeof(Uint32)) / sizeof(Uint32);
+
+ height = image->height;
+ while (height--) {
+ width = image->width;
+ while (width--) {
+ sR = *src++;
+ sR >>= 6;
+ sG = *src++;
+ sG >>= 6;
+ sB = *src++;
+ sB >>= 6;
+ *dst = (0x03 << 30) | (sB << 20) | (sG << 10) | sR;
+ ++dst;
+ }
+ dst += dstskip;
+ }
+ return 0;
+}
+
/* Load a AVIF type image from an SDL datasource */
SDL_Surface *IMG_LoadAVIF_RW(SDL_RWops *src)
{
Sint64 start;
avifDecoder *decoder = NULL;
+ avifImage *image;
avifIO io;
avifIOContext context;
- avifRGBImage rgb;
avifResult result;
SDL_Surface *surface = NULL;
@@ -258,7 +331,6 @@ SDL_Surface *IMG_LoadAVIF_RW(SDL_RWops *src)
SDL_zero(context);
SDL_zero(io);
- SDL_zero(rgb);
decoder = lib.avifDecoderCreate();
if (!decoder) {
@@ -288,28 +360,102 @@ SDL_Surface *IMG_LoadAVIF_RW(SDL_RWops *src)
goto done;
}
- surface = SDL_CreateSurface(decoder->image->width, decoder->image->height, SDL_PIXELFORMAT_ARGB8888);
- if (!surface) {
- goto done;
+ image = decoder->image;
+ if (image->transferCharacteristics == AVIF_TRANSFER_CHARACTERISTICS_SMPTE2084) {
+ // This is an HDR PQ image
+
+ if (image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_IDENTITY &&
+ image->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) {
+ // This image uses identity GBR channel ordering
+ if (image->depth == 10) {
+ surface = SDL_CreateSurface(image->width, image->height, SDL_PIXELFORMAT_XBGR2101010);
+ if (surface) {
+ if (ConvertGBR444toXBGR2101010(image, surface) < 0) {
+ // Invalid image, let avif take care of it
+ SDL_free(surface);
+ surface = NULL;
+ }
+ }
+ }
+ }
+
+ if (!surface) {
+ avifRGBImage rgb;
+
+ // Convert the YUV image to 10-bit RGB
+ SDL_zero(rgb);
+ rgb.width = image->width;
+ rgb.height = image->height;
+ rgb.depth = 16;
+ rgb.format = AVIF_RGB_FORMAT_RGB;
+ rgb.rowBytes = (uint32_t)image->width * 3 * sizeof(Uint16);
+ rgb.pixels = (uint8_t *)SDL_malloc(image->height * rgb.rowBytes);
+ if (!rgb.pixels) {
+ goto done;
+ }
+ result = lib.avifImageYUVToRGB(image, &rgb);
+ if (result != AVIF_RESULT_OK) {
+ IMG_SetError("Couldn't convert AVIF image to RGB: %s", lib.avifResultToString(result));
+ SDL_free(rgb.pixels);
+ goto done;
+ }
+
+ surface = SDL_CreateSurface(image->width, image->height, SDL_PIXELFORMAT_ARGB8888);
+ if (!surface) {
+ SDL_free(rgb.pixels);
+ goto done;
+ }
+
+ surface = SDL_CreateSurface(image->width, image->height, SDL_PIXELFORMAT_XBGR2101010);
+ if (surface) {
+ if (ConvertRGB16toXBGR2101010(&rgb, surface) == 0) {
+ } else {
+ // Invalid image, let avif take care of it
+ SDL_free(surface);
+ surface = NULL;
+ }
+ }
+
+ SDL_free(rgb.pixels);
+ }
+
+ if (surface) {
+ // Set HDR properties
+ SDL_PropertiesID props = SDL_GetSurfaceProperties(surface);
+ SDL_SetNumberProperty(props, SDL_PROPERTY_SURFACE_COLOR_PRIMARIES_NUMBER, image->colorPrimaries);
+ SDL_SetNumberProperty(props, SDL_PROPERTY_SURFACE_TRANSFER_CHARACTERISTICS_NUMBER, image->transferCharacteristics);
+ SDL_SetNumberProperty(props, SDL_PROPERTY_SURFACE_MAXCLL_NUMBER, image->clli.maxCLL);
+ SDL_SetNumberProperty(props, SDL_PROPERTY_SURFACE_MAXFALL_NUMBER, image->clli.maxPALL);
+ }
}
- /* Convert the YUV image to RGB */
- rgb.width = surface->w;
- rgb.height = surface->h;
- rgb.depth = 8;
+ if (!surface) {
+ avifRGBImage rgb;
+
+ surface = SDL_CreateSurface(image->width, image->height, SDL_PIXELFORMAT_ARGB8888);
+ if (!surface) {
+ goto done;
+ }
+
+ /* Convert the YUV image to RGB */
+ SDL_zero(rgb);
+ rgb.width = surface->w;
+ rgb.height = surface->h;
+ rgb.depth = 8;
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
- rgb.format = AVIF_RGB_FORMAT_BGRA;
+ rgb.format = AVIF_RGB_FORMAT_BGRA;
#else
- rgb.format = AVIF_RGB_FORMAT_ARGB;
+ rgb.format = AVIF_RGB_FORMAT_ARGB;
#endif
- rgb.pixels = (uint8_t *)surface->pixels;
- rgb.rowBytes = (uint32_t)surface->pitch;
- result = lib.avifImageYUVToRGB(decoder->image, &rgb);
- if (result != AVIF_RESULT_OK) {
- IMG_SetError("Couldn't convert AVIF image to RGB: %s", lib.avifResultToString(result));
- SDL_DestroySurface(surface);
- surface = NULL;
- goto done;
+ rgb.pixels = (uint8_t *)surface->pixels;
+ rgb.rowBytes = (uint32_t)surface->pitch;
+ result = lib.avifImageYUVToRGB(image, &rgb);
+ if (result != AVIF_RESULT_OK) {
+ IMG_SetError("Couldn't convert AVIF image to RGB: %s", lib.avifResultToString(result));
+ SDL_DestroySurface(surface);
+ surface = NULL;
+ goto done;
+ }
}
done: