Skip to content

Commit

Permalink
Big RGB Conversion Refactor
Browse files Browse the repository at this point in the history
* Removed RGB planes from avifImage and associated avifPlanesFlags (breaking change)
* New struct avifRGBImage for storing RGB conversion settings and pixel buffer
* Added support for encoding and preserving limited alpha range (avifImage.alphaRange)
* Created avifyuv test for roundtrip testing of depth rescaling / range conversion / formats
* Removed memset(0) calls on YUVA plane allocation, they were unnecessary and slow
* Removed old AVIF_FIX_STUDIO_ALPHA block, now that limited alpha is allowed/supported
* Updated README with new RGB conversion examples
* Updated avif_example1 to use new conversion
  • Loading branch information
Joe Drago committed Mar 9, 2020
1 parent ecdd4b7 commit e2c3c87
Show file tree
Hide file tree
Showing 15 changed files with 1,304 additions and 458 deletions.
9 changes: 9 additions & 0 deletions CMakeLists.txt
Expand Up @@ -81,6 +81,7 @@ else()
endif()

set(AVIF_SRCS
src/alpha.c
src/avif.c
src/colr.c
src/mem.c
Expand Down Expand Up @@ -312,6 +313,14 @@ if(AVIF_BUILD_TESTS)
endif()
target_link_libraries(aviftest avif ${AVIF_PLATFORM_LIBRARIES})

add_executable(avifyuv
tests/avifyuv.c
)
if(AVIF_LOCAL_LIBGAV1)
set_target_properties(avifyuv PROPERTIES LINKER_LANGUAGE "CXX")
endif()
target_link_libraries(avifyuv avif ${AVIF_PLATFORM_LIBRARIES})

add_custom_target(avif_test_all
COMMAND $<TARGET_FILE:aviftest> ${CMAKE_CURRENT_SOURCE_DIR}/tests/data
DEPENDS aviftest
Expand Down
106 changes: 82 additions & 24 deletions README.md
Expand Up @@ -42,20 +42,37 @@ if (decodeResult == AVIF_RESULT_OK) {
... image->yuvPlanes;
... image->yuvRowBytes;

// Option 2: Convert to RGB and use RGB planes
avifImageYUVToRGB(image);
... image->rgbPlanes;
... image->rgbRowBytes;

// Option 3: Convert to pre-existing interleaved RGB(A)/BGR(A) buffer
uint8_t * pixels = ...;
uint32_t rowBytes = ...;
avifImageYUVToInterleavedRGBA(image, pixels, rowBytes);

// Use alpha plane, if present
// Option 2: Convert to interleaved RGB(A)/BGR(A) using a libavif-allocated buffer.
avifRGBImage rgb;
avifRGBImageSetDefaults(&rgb, image);
rgb.format = ...; // See choices in avif.h
rgb.depth = ...; // [8, 10, 12, 16]; Does not need to match image->depth.
// If >8, rgb->pixels is uint16_t*
avifRGBImageAllocatePixels(&rgb); // You can supply your own pixels/rowBytes, see Option 3
avifImageYUVToRGB(image, &rgb);
... rgb.pixels; // Pixels in interleaved rgbFormat chosen above;
... rgb.rowBytes; // all channels are always full range
avifRGBImageFreePixels(&rgb);

// Option 3: Convert directly into your own pre-existing interleaved RGB(A)/BGR(A) buffer
avifRGBImage rgb;
avifRGBImageSetDefaults(&rgb, image);
rgb.format = ...; // See choices in avif.h
rgb.depth = ...; // [8, 10, 12, 16]; Does not need to match image->depth.
// If >8, rgb->pixels is uint16_t*
rgb.pixels = ...; // Point at your RGB(A)/BGR(A) pixels here
rgb.rowBytes = ...;
avifImageYUVToRGB(image, &rgb);
... rgb.pixels; // Pixels in interleaved rgbFormat chosen above;
... rgb.rowBytes; // all channels are always full range
// Use your own buffer; no need to call avifRGBImageFreePixels()

// Use alpha plane, if present.
// Note: This might be limited range!
if (image->alphaPlane) {
... image->alphaPlane;
... image->alphaRowBytes;
... image->alphaRange;
}

// Optional: query color profile
Expand Down Expand Up @@ -133,20 +150,37 @@ if (decodeResult == AVIF_RESULT_OK) {
... decoder->image->yuvPlanes;
... decoder->image->yuvRowBytes;
// Option 2: Convert to RGB and use RGB planes
avifImageYUVToRGB(decoder->image); // (this is legal to call on decoder->image)
... decoder->image->rgbPlanes;
... decoder->image->rgbRowBytes;
// Option 3: Convert to pre-existing interleaved RGB(A)/BGR(A) buffer
uint8_t * pixels = ...;
uint32_t rowBytes = ...;
avifImageYUVToInterleavedRGBA(image, pixels, rowBytes);
// Use alpha plane, if present
// Option 2: Convert to interleaved RGB(A)/BGR(A) using a libavif-allocated buffer.
avifRGBImage rgb;
avifRGBImageSetDefaults(&rgb, image);
rgb.format = ...; // See choices in avif.h
rgb.depth = ...; // [8, 10, 12, 16]; Does not need to match image->depth.
// If >8, rgb->pixels is uint16_t*
avifRGBImageAllocatePixels(&rgb); // You can supply your own pixels/rowBytes, see Option 3
avifImageYUVToRGB(image, &rgb);
... rgb.pixels; // Pixels in interleaved rgbFormat chosen above;
... rgb.rowBytes; // all channels are always full range
avifRGBImageFreePixels(&rgb);
// Option 3: Convert directly into your own pre-existing interleaved RGB(A)/BGR(A) buffer
avifRGBImage rgb;
avifRGBImageSetDefaults(&rgb, image);
rgb.format = ...; // See choices in avif.h
rgb.depth = ...; // [8, 10, 12, 16]; Does not need to match image->depth.
// If >8, rgb->pixels is uint16_t*
rgb.pixels = ...; // Point at your RGB(A)/BGR(A) pixels here
rgb.rowBytes = ...;
avifImageYUVToRGB(image, &rgb);
... rgb.pixels; // Pixels in interleaved rgbFormat chosen above;
... rgb.rowBytes; // all channels are always full range
// Use your own buffer; no need to call avifRGBImageFreePixels()
// Use alpha plane, if present.
// Note: This might be limited range!
if (decoder->image->alphaPlane) {
... decoder->image->alphaPlane;
... decoder->image->alphaRowBytes;
... image->alphaPlane;
... image->alphaRowBytes;
... image->alphaRange;
}
// Timing and frame information
Expand Down Expand Up @@ -188,7 +222,31 @@ avifImageAllocatePlanes(image, AVIF_PLANES_RGB);
... image->rgbPlanes;
... image->rgbRowBytes;

// Option 2: Convert from interleaved RGB(A)/BGR(A) using a libavif-allocated buffer.
uint32_t rgbDepth = ...; // [8, 10, 12, 16]; Does not need to match image->depth.
// If >8, rgb->pixels is uint16_t*
avifRGBFormat rgbFormat = AVIF_RGB_FORMAT_RGBA; // See choices in avif.h
avifRGBImage * rgb = avifRGBImageCreate(image->width, image->width, rgbDepth, rgbFormat);
... rgb->pixels; // fill these pixels; all channel data must be full range
... rgb->rowBytes;
avifImageRGBToYUV(image, rgb); // if alpha is present, it will also be copied/converted
avifRGBImageDestroy(rgb);

// Option 3: Convert directly from your own pre-existing interleaved RGB(A)/BGR(A) buffer
avifRGBImage rgb;
rgb.width = image->width;
rgb.height = image->height;
rgb.depth = ...; // [8, 10, 12, 16]; Does not need to match image->depth.
// If >8, rgb->pixels is uint16_t*
rgb.format = ...; // See choices in avif.h, match to your buffer's pixel format
rgb.pixels = ...; // Point at your RGB(A)/BGR(A) pixels here
rgb.rowBytes = ...;
avifImageRGBToYUV(image, rgb); // if alpha is present, it will also be copied/converted
// no need to cleanup avifRGBImage

// Optional: Populate alpha plane
// Note: This step is unnecessary if you used avifImageRGBToYUV from an
// avifRGBImage containing an alpha channel.
avifImageAllocatePlanes(image, AVIF_PLANES_A);
... image->alphaPlane;
... image->alphaRowBytes;
Expand Down
42 changes: 32 additions & 10 deletions examples/avif_example1.c
Expand Up @@ -21,15 +21,25 @@ int main(int argc, char * argv[])

// Encode an orange, 8-bit, full opacity image
avifImage * image = avifImageCreate(width, height, depth, AVIF_PIXEL_FORMAT_YUV444);
avifImageAllocatePlanes(image, AVIF_PLANES_RGB | AVIF_PLANES_A);

avifRGBImage srcRGB;
avifRGBImageSetDefaults(&srcRGB, image);
avifRGBImageAllocatePixels(&srcRGB);

avifRGBImage dstRGB;
avifRGBImageSetDefaults(&dstRGB, image);
avifRGBImageAllocatePixels(&dstRGB);

for (int j = 0; j < height; ++j) {
for (int i = 0; i < width; ++i) {
image->rgbPlanes[0][i + (j * image->rgbRowBytes[0])] = 255; // R
image->rgbPlanes[1][i + (j * image->rgbRowBytes[1])] = 128; // G
image->rgbPlanes[2][i + (j * image->rgbRowBytes[2])] = 0; // B
image->alphaPlane[i + (j * image->alphaRowBytes)] = 255; // A
uint8_t * pixel = &srcRGB.pixels[(4 * i) + (srcRGB.rowBytes * j)];
pixel[0] = 255; // R
pixel[1] = 128; // G
pixel[2] = 0; // B
pixel[3] = 255; // A
}
}
avifImageRGBToYUV(image, &srcRGB);

// uint8_t * fakeICC = "abcdefg";
// uint32_t fakeICCSize = (uint32_t)strlen(fakeICC);
Expand Down Expand Up @@ -69,17 +79,27 @@ int main(int argc, char * argv[])
exitStatus = 1;
goto decodeCleanup;
}
if (avifImageYUVToRGB(decoded) != AVIF_RESULT_OK) {
if (avifImageYUVToRGB(decoded, &dstRGB) != AVIF_RESULT_OK) {
exitStatus = 1;
goto decodeCleanup;
}
for (int j = 0; j < height; ++j) {
for (int i = 0; i < width; ++i) {
for (int plane = 0; plane < 3; ++plane) {
uint32_t src = image->rgbPlanes[plane][i + (j * image->rgbRowBytes[plane])];
uint32_t dst = decoded->rgbPlanes[plane][i + (j * decoded->rgbRowBytes[plane])];
if (src != dst) {
printf("(%d,%d,p%d) %d != %d\n", i, j, plane, src, dst);
uint8_t * srcPixel = &srcRGB.pixels[(4 * i) + (srcRGB.rowBytes * j)];
uint8_t * dstPixel = &dstRGB.pixels[(4 * i) + (dstRGB.rowBytes * j)];
if (memcmp(srcPixel, dstPixel, 4) != 0) {
printf("(%d,%d) (%d, %d, %d, %d) != (%d, %d, %d, %d)\n",
i,
j,
srcPixel[0],
srcPixel[1],
srcPixel[2],
srcPixel[3],
dstPixel[0],
dstPixel[1],
dstPixel[2],
dstPixel[3]);
exitStatus = 1;
}
}
Expand All @@ -92,6 +112,8 @@ int main(int argc, char * argv[])

encodeCleanup:
avifImageDestroy(image);
avifRGBImageFreePixels(&srcRGB);
avifRGBImageFreePixels(&dstRGB);
#else /* if 1 */

FILE * f = fopen("test.avif", "rb");
Expand Down
83 changes: 60 additions & 23 deletions include/avif/avif.h
Expand Up @@ -27,7 +27,6 @@ typedef int avifBool;
#define AVIF_QUANTIZER_BEST_QUALITY 0
#define AVIF_QUANTIZER_WORST_QUALITY 63

#define AVIF_PLANE_COUNT_RGB 3
#define AVIF_PLANE_COUNT_YUV 3

#define AVIF_SPEED_DEFAULT -1
Expand All @@ -36,9 +35,8 @@ typedef int avifBool;

enum avifPlanesFlags
{
AVIF_PLANES_RGB = (1 << 0),
AVIF_PLANES_YUV = (1 << 1),
AVIF_PLANES_A = (1 << 2),
AVIF_PLANES_YUV = (1 << 0),
AVIF_PLANES_A = (1 << 1),

AVIF_PLANES_ALL = 0xff
};
Expand Down Expand Up @@ -283,17 +281,15 @@ typedef struct avifImage
// Image information
uint32_t width;
uint32_t height;
uint32_t depth; // all planes (RGB/YUV/A) must share this depth; if depth>8, all planes are uint16_t internally

uint8_t * rgbPlanes[AVIF_PLANE_COUNT_RGB];
uint32_t rgbRowBytes[AVIF_PLANE_COUNT_RGB];
uint32_t depth; // all planes must share this depth; if depth>8, all planes are uint16_t internally

avifPixelFormat yuvFormat;
avifRange yuvRange;
uint8_t * yuvPlanes[AVIF_PLANE_COUNT_YUV];
uint32_t yuvRowBytes[AVIF_PLANE_COUNT_YUV];
avifBool decoderOwnsYUVPlanes;

avifRange alphaRange;
uint8_t * alphaPlane;
uint32_t alphaRowBytes;
avifBool decoderOwnsAlphaPlane;
Expand Down Expand Up @@ -325,20 +321,53 @@ void avifImageAllocatePlanes(avifImage * image, uint32_t planes); // Ignores any
void avifImageFreePlanes(avifImage * image, uint32_t planes); // Ignores already-freed planes
void avifImageStealPlanes(avifImage * dstImage, avifImage * srcImage, uint32_t planes);

// ---------------------------------------------------------------------------
// Optional YUV<->RGB support
avifResult avifImageRGBToYUV(avifImage * image);
avifResult avifImageYUVToRGB(avifImage * image);

// Convert YUV -> RGB(A) without using intermediate RGB planes owned by avifImage. Pixel ptr passed
// in here must be at least (rowBytes * image->height) in size, and will be filled with either U8s
// or U16s depending on the depth of the image. Use avifImageUsesU16() as a helper function to make
// this determination.
avifResult avifImageYUVToInterleavedRGB(avifImage * image, uint8_t * pixels, uint32_t rowBytes);
avifResult avifImageYUVToInterleavedRGBA(avifImage * image, uint8_t * pixels, uint32_t rowBytes);
avifResult avifImageYUVToInterleavedARGB(avifImage * image, uint8_t * pixels, uint32_t rowBytes);
avifResult avifImageYUVToInterleavedBGR(avifImage * image, uint8_t * pixels, uint32_t rowBytes);
avifResult avifImageYUVToInterleavedBGRA(avifImage * image, uint8_t * pixels, uint32_t rowBytes);
avifResult avifImageYUVToInterleavedABGR(avifImage * image, uint8_t * pixels, uint32_t rowBytes);

// To convert to/from RGB, create an avifRGBImage on the stack, call avifRGBImageSetDefaults() on
// it, and then tweak the values inside of it accordingly. At a minimum, you should populate
// ->pixels and ->rowBytes with an appropriately sized pixel buffer, which should be at least
// (->rowBytes * ->height) bytes, where ->rowBytes is at least (->width * avifRGBImagePixelSize()).
// If you don't want to supply your own pixel buffer, you can use the
// avifRGBImageAllocatePixels()/avifRGBImageFreePixels() convenience functions.

// avifImageRGBToYUV() and avifImageYUVToRGB() will perform depth rescaling and limited<->full range
// conversion, if necessary. Pixels in an avifRGBImage buffer are always full range, and conversion
// routines will fail if the width and height don't match the associated avifImage.

typedef enum avifRGBFormat
{
AVIF_RGB_FORMAT_RGB = 0,
AVIF_RGB_FORMAT_RGBA,
AVIF_RGB_FORMAT_ARGB,
AVIF_RGB_FORMAT_BGR,
AVIF_RGB_FORMAT_BGRA,
AVIF_RGB_FORMAT_ABGR
} avifRGBFormat;
uint32_t avifRGBFormatChannelCount(avifRGBFormat format);
avifBool avifRGBFormatHasAlpha(avifRGBFormat format);

typedef struct avifRGBImage
{
uint32_t width; // must match associated avifImage
uint32_t height; // must match associated avifImage
uint32_t depth; // legal depths [8, 10, 12, 16]. if depth>8, pixels must be uint16_t internally
avifRGBFormat format; // all channels are always full range

uint8_t * pixels;
uint32_t rowBytes;
} avifRGBImage;

void avifRGBImageSetDefaults(avifRGBImage * rgb, avifImage * image);
uint32_t avifRGBImagePixelSize(avifRGBImage * rgb);

// Convenience functions. If you supply your own pixels/rowBytes, you do not need to use these.
void avifRGBImageAllocatePixels(avifRGBImage * rgb);
void avifRGBImageFreePixels(avifRGBImage * rgb);

// The main conversion functions
avifResult avifImageRGBToYUV(avifImage * image, avifRGBImage * rgb);
avifResult avifImageYUVToRGB(avifImage * image, avifRGBImage * rgb);

// ---------------------------------------------------------------------------
// YUV Utils
Expand All @@ -355,10 +384,18 @@ typedef struct avifReformatState
float kg;
float kb;

uint32_t yuvChannelBytes;
uint32_t rgbChannelBytes;
uint32_t rgbChannelCount;
uint32_t rgbPixelBytes;
uint32_t rgbOffsetBytesR;
uint32_t rgbOffsetBytesG;
uint32_t rgbOffsetBytesB;
uint32_t rgbOffsetBytesA;

avifPixelFormatInfo formatInfo;
avifBool usesU16;
} avifReformatState;
avifBool avifPrepareReformatState(avifImage * image, avifReformatState * state);
avifBool avifPrepareReformatState(avifImage * image, avifRGBImage * rgb, avifReformatState * state);

// ---------------------------------------------------------------------------
// Codec selection
Expand Down

0 comments on commit e2c3c87

Please sign in to comment.