Skip to content

Commit

Permalink
Experimental AVIF bit depth extension
Browse files Browse the repository at this point in the history
Allow encoding and decoding images of more than 12 bits, the limit
of the AV1 format. This commit experiments 16-bit AVIF images, made
of one primary lossless 8-bit image and one auxiliary lossy/lossless
bit depth extension 8-bit image. Grids and alpha are supported.

Warning: This feature is experimental and not covered by the current
         AVIF specification.
  • Loading branch information
y-guyon committed Aug 8, 2023
1 parent b415a2b commit b8a0aa6
Show file tree
Hide file tree
Showing 10 changed files with 714 additions and 182 deletions.
3 changes: 3 additions & 0 deletions include/avif/avif.h
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,9 @@ typedef enum avifStrictFlag
// https://crbug.com/1246678.
AVIF_STRICT_ALPHA_ISPE_REQUIRED = (1 << 2),

// TODO(yguyon): Generalize AVIF_STRICT_ALPHA_ISPE_REQUIRED to bit depth extension
// or add AVIF_STRICT_SUBDEPTH_ISPE_REQUIRED

// Maximum strictness; enables all bits above. This is avifDecoder's default.
AVIF_STRICT_ENABLED = AVIF_STRICT_PIXI_REQUIRED | AVIF_STRICT_CLAP_VALID | AVIF_STRICT_ALPHA_ISPE_REQUIRED
} avifStrictFlag;
Expand Down
27 changes: 26 additions & 1 deletion include/avif/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ extern "C" {
#define AVIF_URN_ALPHA0 "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"
#define AVIF_URN_ALPHA1 "urn:mpeg:hevc:2015:auxid:1"

// TODO(yguyon): Add reference to specification
#define AVIF_URN_SUBDEPTH_8LSB "urn:mpeg:mpegB:cicp:systems:auxiliary:8lsb"

#define AVIF_CONTENT_TYPE_XMP "application/rdf+xml"

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -92,7 +95,29 @@ void avifImageCopyNoAlloc(avifImage * dstImage, const avifImage * srcImage);
// Copies the samples from srcImage to dstImage. dstImage must be allocated.
// srcImage and dstImage must have the same width, height, and depth.
// If the AVIF_PLANES_YUV bit is set in planes, then srcImage and dstImage must have the same yuvFormat and yuvRange.
void avifImageCopySamples(avifImage * dstImage, const avifImage * srcImage, avifPlanesFlags planes);
avifResult avifImageCopySamples(avifImage * dstImage, const avifImage * srcImage, avifPlanesFlags planes);

// ---------------------------------------------------------------------------
// Subdepth

typedef enum avifSubdepthMode
{
// The image can be encoded as is with an AV1 codec.
AVIF_SUBDEPTH_NONE,
// The image is split into two images because its bit depth is not supported by AV1.
AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS,
AVIF_SUBDEPTH_8_LEAST_SIGNIFICANT_BITS
} avifSubdepthMode;

// Same as avifImageCopySamples() with source and destination bit masks and shifts.
avifResult avifImageCopySamplesExtended(avifImage * dstImage,
enum avifSubdepthMode dstSubdepthMode,
const avifImage * srcImage,
enum avifSubdepthMode srcSubdepthMode,
avifPlanesFlags planes);

// ---------------------------------------------------------------------------
// Alpha

typedef struct avifAlphaParams
{
Expand Down
102 changes: 88 additions & 14 deletions src/avif.c
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,43 @@ void avifImageCopyNoAlloc(avifImage * dstImage, const avifImage * srcImage)
dstImage->imir = srcImage->imir;
}

void avifImageCopySamples(avifImage * dstImage, const avifImage * srcImage, avifPlanesFlags planes)
avifResult avifImageCopySamples(avifImage * dstImage, const avifImage * srcImage, avifPlanesFlags planes)
{
assert(srcImage->depth == dstImage->depth);
if (planes & AVIF_PLANES_YUV) {
assert((srcImage->yuvFormat == dstImage->yuvFormat) && (srcImage->yuvRange == dstImage->yuvRange));
return avifImageCopySamplesExtended(dstImage, AVIF_SUBDEPTH_NONE, srcImage, AVIF_SUBDEPTH_NONE, planes);
}

avifResult avifImageCopySamplesExtended(avifImage * dstImage,
avifSubdepthMode dstSubdepthMode,
const avifImage * srcImage,
avifSubdepthMode srcSubdepthMode,
avifPlanesFlags planes)
{
if (!(planes & AVIF_PLANES_YUV) && !(planes & AVIF_PLANES_A)) {
// Early exit.
return AVIF_RESULT_OK;
}
AVIF_CHECKERR((srcSubdepthMode == AVIF_SUBDEPTH_NONE) || (dstSubdepthMode == AVIF_SUBDEPTH_NONE), AVIF_RESULT_UNSUPPORTED_DEPTH);
AVIF_CHECKERR((srcSubdepthMode == AVIF_SUBDEPTH_NONE) || (dstSubdepthMode == AVIF_SUBDEPTH_NONE), AVIF_RESULT_UNSUPPORTED_DEPTH);
if (((srcImage->depth != 8) && (srcImage->depth != 10) && (srcImage->depth != 12) && (srcImage->depth != 16)) ||
((dstImage->depth != 8) && (dstImage->depth != 10) && (dstImage->depth != 12) && (dstImage->depth != 16))) {
return AVIF_RESULT_UNSUPPORTED_DEPTH;
}
if ((srcSubdepthMode == AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS) || (srcSubdepthMode == AVIF_SUBDEPTH_8_LEAST_SIGNIFICANT_BITS)) {
if (srcImage->depth != 8) {
return AVIF_RESULT_INVALID_ARGUMENT;
}
if (dstImage->depth == 8) {
return avifImageCopySamplesExtended(dstImage, AVIF_SUBDEPTH_NONE, srcImage, AVIF_SUBDEPTH_NONE, planes);
}
} else if ((dstSubdepthMode == AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS) || (dstSubdepthMode == AVIF_SUBDEPTH_8_LEAST_SIGNIFICANT_BITS)) {
if (dstImage->depth != 8) {
return AVIF_RESULT_INVALID_ARGUMENT;
}
if (srcImage->depth == 8) {
return avifImageCopySamplesExtended(dstImage, AVIF_SUBDEPTH_NONE, srcImage, AVIF_SUBDEPTH_NONE, planes);
}
} else if ((srcSubdepthMode != AVIF_SUBDEPTH_NONE) || (dstSubdepthMode != AVIF_SUBDEPTH_NONE)) {
return AVIF_RESULT_UNSUPPORTED_DEPTH; // Only 8MSB and 8LSB are supported.
}
const size_t bytesPerPixel = avifImageUsesU16(srcImage) ? 2 : 1;

Expand All @@ -206,20 +238,62 @@ void avifImageCopySamples(avifImage * dstImage, const avifImage * srcImage, avif
uint8_t * dstRow = avifImagePlane(dstImage, c);
const uint32_t srcRowBytes = avifImagePlaneRowBytes(srcImage, c);
const uint32_t dstRowBytes = avifImagePlaneRowBytes(dstImage, c);
assert(!srcRow == !dstRow);
AVIF_CHECKERR(!srcRow == !dstRow, AVIF_RESULT_INVALID_ARGUMENT);
if (!srcRow) {
continue;
}
assert(planeWidth == avifImagePlaneWidth(dstImage, c));
assert(planeHeight == avifImagePlaneHeight(dstImage, c));

const size_t planeWidthBytes = planeWidth * bytesPerPixel;
for (uint32_t y = 0; y < planeHeight; ++y) {
memcpy(dstRow, srcRow, planeWidthBytes);
srcRow += srcRowBytes;
dstRow += dstRowBytes;
AVIF_CHECKERR(planeWidth == avifImagePlaneWidth(dstImage, c), AVIF_RESULT_INVALID_ARGUMENT);
AVIF_CHECKERR(planeHeight == avifImagePlaneHeight(dstImage, c), AVIF_RESULT_INVALID_ARGUMENT);

if ((srcSubdepthMode == AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS) || (srcSubdepthMode == AVIF_SUBDEPTH_8_LEAST_SIGNIFICANT_BITS)) {
// Copy samples that represent a part of the final bits to their place in the reconstructed buffer.
uint32_t srcShift;
uint16_t dstMask;
if (srcSubdepthMode == AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS) {
srcShift = dstImage->depth - 8;
dstMask = (1u << (dstImage->depth - 8)) - 1u;
} else {
srcShift = 0;
dstMask = ((1u << (dstImage->depth - 8)) - 1u) << 8;
}
for (uint32_t y = 0; y < planeHeight; ++y) {
uint16_t * dstRow16 = (uint16_t *)dstRow;
for (uint32_t x = 0; x < planeWidth; ++x) {
dstRow16[x] = (dstRow16[x] & dstMask) | (srcRow[x] << srcShift);
}
srcRow += srcRowBytes;
dstRow += dstRowBytes;
}
} else if ((dstSubdepthMode == AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS) ||
(dstSubdepthMode == AVIF_SUBDEPTH_8_LEAST_SIGNIFICANT_BITS)) {
// Copy the most or least significant bits of the source samples to a buffer with a lesser bit depth.
uint32_t srcShift;
uint16_t srcMask;
if (dstSubdepthMode == AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS) {
srcShift = srcImage->depth - 8;
srcMask = (1u << 8) - 1u;
} else {
srcShift = 0;
srcMask = (1u << 8) - 1u;
}
for (uint32_t y = 0; y < planeHeight; ++y) {
const uint16_t * srcRow16 = (uint16_t *)srcRow;
for (uint32_t x = 0; x < planeWidth; ++x) {
dstRow[x] = (uint8_t)((srcRow16[x] >> srcShift) & srcMask);
}
srcRow += srcRowBytes;
dstRow += dstRowBytes;
}
} else {
const size_t planeWidthBytes = planeWidth * bytesPerPixel;
for (uint32_t y = 0; y < planeHeight; ++y) {
memcpy(dstRow, srcRow, planeWidthBytes);
srcRow += srcRowBytes;
dstRow += dstRowBytes;
}
}
}
return AVIF_RESULT_OK;
}

avifResult avifImageCopy(avifImage * dstImage, const avifImage * srcImage, avifPlanesFlags planes)
Expand Down Expand Up @@ -248,7 +322,7 @@ avifResult avifImageCopy(avifImage * dstImage, const avifImage * srcImage, avifP
return allocationResult;
}
}
avifImageCopySamples(dstImage, srcImage, planes);
AVIF_CHECKRES(avifImageCopySamples(dstImage, srcImage, planes));
return AVIF_RESULT_OK;
}

Expand Down
Loading

0 comments on commit b8a0aa6

Please sign in to comment.