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 Dec 16, 2022
1 parent 93035c1 commit 5950aa7
Show file tree
Hide file tree
Showing 9 changed files with 864 additions and 485 deletions.
4 changes: 4 additions & 0 deletions include/avif/avif.h
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,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 All @@ -807,6 +810,7 @@ typedef struct avifIOStats
{
size_t colorOBUSize;
size_t alphaOBUSize;
// TODO(yguyon): Add subdepthOBUSize
} avifIOStats;

struct avifDecoderData;
Expand Down
24 changes: 24 additions & 0 deletions include/avif/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,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 @@ -197,6 +200,26 @@ avifBool avifImageScale(avifImage * image,
// The image contains imageW*imageH pixels. The tiles are of tileW*tileH pixels each.
avifBool avifAreGridDimensionsValid(avifPixelFormat yuvFormat, uint32_t imageW, uint32_t imageH, uint32_t tileW, uint32_t tileH, avifDiagnostics * diag);

// ---------------------------------------------------------------------------
// 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;

// Copies the samples from srcImage to dstImage. dstImage must be allocated.
// srcImage and dstImage must have the same width, height and subsampling.
avifResult avifImageCopySamples(avifImage * dstImage,
avifSubdepthMode dstSubdepthMode,
const avifImage * srcImage,
avifSubdepthMode srcSubdepthMode,
avifPlanesFlags planes);

// ---------------------------------------------------------------------------
// Metadata

Expand Down Expand Up @@ -276,6 +299,7 @@ struct avifCodecInternal;

typedef enum avifEncoderChange
{
AVIF_ENCODER_NO_CHANGE = 0,
AVIF_ENCODER_CHANGE_MIN_QUANTIZER = (1u << 0),
AVIF_ENCODER_CHANGE_MAX_QUANTIZER = (1u << 1),
AVIF_ENCODER_CHANGE_MIN_QUANTIZER_ALPHA = (1u << 2),
Expand Down
797 changes: 390 additions & 407 deletions src/read.c

Large diffs are not rendered by default.

106 changes: 106 additions & 0 deletions src/scale.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Copyright 2021 Joe Drago. All rights reserved.
// SPDX-License-Identifier: BSD-2-Clause

#include <assert.h>
#include <string.h>

#include "avif/internal.h"

#if !defined(AVIF_LIBYUV_ENABLED)
Expand Down Expand Up @@ -180,3 +183,106 @@ avifBool avifImageScale(avifImage * image,
}

#endif

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

avifResult avifImageCopySamples(avifImage * dstImage,
avifSubdepthMode dstSubdepthMode,
const avifImage * srcImage,
avifSubdepthMode srcSubdepthMode,
avifPlanesFlags planes)
{
if ((srcSubdepthMode != AVIF_SUBDEPTH_NONE) && (dstSubdepthMode != AVIF_SUBDEPTH_NONE)) {
return 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 avifImageCopySamples(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 avifImageCopySamples(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.
}

for (int c = AVIF_CHAN_Y; c <= AVIF_CHAN_A; ++c) {
if ((!(planes & AVIF_PLANES_YUV) && (c != AVIF_CHAN_A)) || (!(planes & AVIF_PLANES_A) && (c == AVIF_CHAN_A))) {
continue;
}
const uint32_t planeWidth = avifImagePlaneWidth(srcImage, c);
const uint32_t planeHeight = avifImagePlaneHeight(srcImage, c);
const uint8_t * srcRow = avifImagePlane(srcImage, c);
uint8_t * dstRow = avifImagePlane(dstImage, c);
const uint32_t srcRowBytes = avifImagePlaneRowBytes(srcImage, c);
const uint32_t dstRowBytes = avifImagePlaneRowBytes(dstImage, c);
if ((planeWidth != avifImagePlaneWidth(dstImage, c)) || (planeHeight != avifImagePlaneHeight(dstImage, c)) ||
(!srcRow != !dstRow)) {
return AVIF_RESULT_INVALID_ARGUMENT;
}
if (!srcRow) {
continue;
}

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 * (size_t)(avifImageUsesU16(srcImage) ? 2 : 1);
for (uint32_t y = 0; y < planeHeight; ++y) {
memcpy(dstRow, srcRow, planeWidthBytes);
srcRow += srcRowBytes;
dstRow += dstRowBytes;
}
}
}
return AVIF_RESULT_OK;
}
Loading

0 comments on commit 5950aa7

Please sign in to comment.