Skip to content

Commit

Permalink
Layered encoding support
Browse files Browse the repository at this point in the history
Layered image can be encoded by calling avifEncoderAddImage() multiple
times, one layer per call.
  • Loading branch information
tongyuantongyu authored Jan 20, 2023
1 parent 7c4c2d4 commit 5d16f1f
Show file tree
Hide file tree
Showing 12 changed files with 602 additions and 148 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ avifEncoder struct.
minQuantizer, maxQuantizer, minQuantizerAlpha, and maxQuantizerAlpha
initialized to the default values.
* Add the public API function avifImageIsOpaque() in avif.h.
* Add experimental API for progressive AVIF encoding.

### Changed
* Exif and XMP metadata is exported to PNG and JPEG files by default,
Expand Down
62 changes: 47 additions & 15 deletions include/avif/avif.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ typedef int avifBool;
// to handle this case however they want.
#define AVIF_REPETITION_COUNT_UNKNOWN -2

// The number of spatial layers in AV1, with spatial_id = 0..3.
#define AVIF_MAX_AV1_LAYER_COUNT 4

typedef enum avifPlanesFlag
{
AVIF_PLANES_YUV = (1 << 0),
Expand Down Expand Up @@ -344,6 +347,15 @@ typedef struct avifDiagnostics

AVIF_API void avifDiagnosticsClearError(avifDiagnostics * diag);

// ---------------------------------------------------------------------------
// Fraction utility

typedef struct avifFraction
{
int32_t n;
int32_t d;
} avifFraction;

// ---------------------------------------------------------------------------
// Optional transformation structs

Expand Down Expand Up @@ -1067,6 +1079,12 @@ AVIF_API avifResult avifDecoderNthImageMaxExtent(const avifDecoder * decoder, ui
struct avifEncoderData;
struct avifCodecSpecificOptions;

typedef struct avifScalingMode
{
avifFraction horizontal;
avifFraction vertical;
} avifScalingMode;

// Notes:
// * If avifEncoderWrite() returns AVIF_RESULT_OK, output must be freed with avifRWDataFree()
// * If (maxThreads < 2), multithreading is disabled
Expand All @@ -1087,6 +1105,8 @@ struct avifCodecSpecificOptions;
// image in less bytes. AVIF_SPEED_DEFAULT means "Leave the AV1 codec to its default speed settings"./
// If avifEncoder uses rav1e, the speed value is directly passed through (0-10). If libaom is used,
// a combination of settings are tweaked to simulate this speed range.
// * Extra layer count: [0 - (AVIF_MAX_AV1_LAYER_COUNT-1)]. Non-zero value indicates a layered
// (progressive) image.
// * Some encoder settings can be changed after encoding starts. Changes will take effect in the next
// call to avifEncoderAddImage().
typedef struct avifEncoder
Expand All @@ -1097,12 +1117,13 @@ typedef struct avifEncoder
// settings (see Notes above)
int maxThreads;
int speed;
int keyframeInterval; // How many frames between automatic forced keyframes; 0 to disable (default).
uint64_t timescale; // timescale of the media (Hz)
int repetitionCount; // Number of times the image sequence should be repeated. This can also be set to
// AVIF_REPETITION_COUNT_INFINITE for infinite repetitions. Only applicable for image sequences.
// Essentially, if repetitionCount is a non-negative integer `n`, then the image sequence should be
// played back `n + 1` times. Defaults to AVIF_REPETITION_COUNT_INFINITE.
int keyframeInterval; // How many frames between automatic forced keyframes; 0 to disable (default).
uint64_t timescale; // timescale of the media (Hz)
int repetitionCount; // Number of times the image sequence should be repeated. This can also be set to
// AVIF_REPETITION_COUNT_INFINITE for infinite repetitions. Only applicable for image sequences.
// Essentially, if repetitionCount is a non-negative integer `n`, then the image sequence should be
// played back `n + 1` times. Defaults to AVIF_REPETITION_COUNT_INFINITE.
uint32_t extraLayerCount; // EXPERIMENTAL: Non-zero value encodes layered image.

// changeable encoder settings
int quality;
Expand All @@ -1114,6 +1135,7 @@ typedef struct avifEncoder
int tileRowsLog2;
int tileColsLog2;
avifBool autoTiling;
avifScalingMode scalingMode;

// stats from the most recent write
avifIOStats ioStats;
Expand All @@ -1137,26 +1159,36 @@ typedef enum avifAddImageFlag
// Force this frame to be a keyframe (sync frame).
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME = (1 << 0),

// Use this flag when encoding a single image. Signals "still_picture" to AV1 encoders, which
// tweaks various compression rules. This is enabled automatically when using the
// avifEncoderWrite() single-image encode path.
// Use this flag when encoding a single frame, single layer image.
// Signals "still_picture" to AV1 encoders, which tweaks various compression rules.
// This is enabled automatically when using the avifEncoderWrite() single-image encode path.
AVIF_ADD_IMAGE_FLAG_SINGLE = (1 << 1)
} avifAddImageFlag;
typedef uint32_t avifAddImageFlags;

// Multi-function alternative to avifEncoderWrite() for image sequences.
// Multi-function alternative to avifEncoderWrite() for advanced features.
//
// Usage / function call order is:
// * avifEncoderCreate()
// * Set encoder->timescale (Hz) correctly
// * avifEncoderAddImage() ... [repeatedly; at least once]
// OR
// * avifEncoderAddImageGrid() [exactly once, AVIF_ADD_IMAGE_FLAG_SINGLE is assumed]
// - Still image:
// * avifEncoderAddImage() [exactly once]
// - Still image grid:
// * avifEncoderAddImageGrid() [exactly once, AVIF_ADD_IMAGE_FLAG_SINGLE is assumed]
// - Image sequence:
// * Set encoder->timescale (Hz) correctly
// * avifEncoderAddImage() ... [repeatedly; at least once]
// - Still layered image:
// * Set encoder->extraLayerCount correctly
// * avifEncoderAddImage() ... [exactly encoder->extraLayerCount+1 times]
// - Still layered grid:
// * Set encoder->extraLayerCount correctly
// * avifEncoderAddImageGrid() ... [exactly encoder->extraLayerCount+1 times]
// * avifEncoderFinish()
// * avifEncoderDestroy()
//

// durationInTimescales is ignored if AVIF_ADD_IMAGE_FLAG_SINGLE is set in addImageFlags.
// durationInTimescales is ignored if AVIF_ADD_IMAGE_FLAG_SINGLE is set in addImageFlags,
// or if we are encoding a layered image.
AVIF_API avifResult avifEncoderAddImage(avifEncoder * encoder, const avifImage * image, uint64_t durationInTimescales, avifAddImageFlags addImageFlags);
AVIF_API avifResult avifEncoderAddImageGrid(avifEncoder * encoder,
uint32_t gridCols,
Expand Down
6 changes: 6 additions & 0 deletions include/avif/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ void avifArrayPush(void * arrayStruct, void * element);
void avifArrayPop(void * arrayStruct);
void avifArrayDestroy(void * arrayStruct);

void avifFractionSimplify(avifFraction * f);
avifBool avifFractionCD(avifFraction * a, avifFraction * b);
avifBool avifFractionAdd(avifFraction a, avifFraction b, avifFraction * result);
avifBool avifFractionSub(avifFraction a, avifFraction b, avifFraction * result);

void avifImageSetDefaults(avifImage * image);
// Copies all fields that do not need to be freed/allocated from srcImage to dstImage.
void avifImageCopyNoAlloc(avifImage * dstImage, const avifImage * srcImage);
Expand Down Expand Up @@ -288,6 +293,7 @@ typedef enum avifEncoderChange
AVIF_ENCODER_CHANGE_TILE_COLS_LOG2 = (1u << 5),
AVIF_ENCODER_CHANGE_QUANTIZER = (1u << 6),
AVIF_ENCODER_CHANGE_QUANTIZER_ALPHA = (1u << 7),
AVIF_ENCODER_CHANGE_SCALING_MODE = (1u << 8),

AVIF_ENCODER_CHANGE_CODEC_SPECIFIC = (1u << 31)
} avifEncoderChange;
Expand Down
138 changes: 24 additions & 114 deletions src/avif.c
Original file line number Diff line number Diff line change
Expand Up @@ -563,15 +563,9 @@ void avifRGBImageFreePixels(avifRGBImage * rgb)
// ---------------------------------------------------------------------------
// avifCropRect

typedef struct clapFraction
static avifFraction calcCenter(int32_t dim)
{
int32_t n;
int32_t d;
} clapFraction;

static clapFraction calcCenter(int32_t dim)
{
clapFraction f;
avifFraction f;
f.n = dim >> 1;
f.d = 1;
if ((dim % 2) != 0) {
Expand All @@ -581,95 +575,11 @@ static clapFraction calcCenter(int32_t dim)
return f;
}

// |a| and |b| hold int32_t values. The int64_t type is used so that we can negate INT32_MIN without
// overflowing int32_t.
static int64_t calcGCD(int64_t a, int64_t b)
{
if (a < 0) {
a *= -1;
}
if (b < 0) {
b *= -1;
}
while (b != 0) {
int64_t r = a % b;
a = b;
b = r;
}
return a;
}

static void clapFractionSimplify(clapFraction * f)
{
int64_t gcd = calcGCD(f->n, f->d);
if (gcd > 1) {
f->n = (int32_t)(f->n / gcd);
f->d = (int32_t)(f->d / gcd);
}
}

static avifBool overflowsInt32(int64_t x)
{
return (x < INT32_MIN) || (x > INT32_MAX);
}

// Make the fractions have a common denominator
static avifBool clapFractionCD(clapFraction * a, clapFraction * b)
{
clapFractionSimplify(a);
clapFractionSimplify(b);
if (a->d != b->d) {
const int64_t ad = a->d;
const int64_t bd = b->d;
const int64_t anNew = a->n * bd;
const int64_t adNew = a->d * bd;
const int64_t bnNew = b->n * ad;
const int64_t bdNew = b->d * ad;
if (overflowsInt32(anNew) || overflowsInt32(adNew) || overflowsInt32(bnNew) || overflowsInt32(bdNew)) {
return AVIF_FALSE;
}
a->n = (int32_t)anNew;
a->d = (int32_t)adNew;
b->n = (int32_t)bnNew;
b->d = (int32_t)bdNew;
}
return AVIF_TRUE;
}

static avifBool clapFractionAdd(clapFraction a, clapFraction b, clapFraction * result)
{
if (!clapFractionCD(&a, &b)) {
return AVIF_FALSE;
}

const int64_t resultN = (int64_t)a.n + b.n;
if (overflowsInt32(resultN)) {
return AVIF_FALSE;
}
result->n = (int32_t)resultN;
result->d = a.d;

clapFractionSimplify(result);
return AVIF_TRUE;
}

static avifBool clapFractionSub(clapFraction a, clapFraction b, clapFraction * result)
{
if (!clapFractionCD(&a, &b)) {
return AVIF_FALSE;
}

const int64_t resultN = (int64_t)a.n - b.n;
if (overflowsInt32(resultN)) {
return AVIF_FALSE;
}
result->n = (int32_t)resultN;
result->d = a.d;

clapFractionSimplify(result);
return AVIF_TRUE;
}

static avifBool avifCropRectIsValid(const avifCropRect * cropRect, uint32_t imageW, uint32_t imageH, avifPixelFormat yuvFormat, avifDiagnostics * diag)

{
Expand Down Expand Up @@ -755,32 +665,32 @@ avifBool avifCropRectConvertCleanApertureBox(avifCropRect * cropRect,
avifDiagnosticsPrintf(diag, "[Strict] image width %u or height %u is greater than INT32_MAX", imageW, imageH);
return AVIF_FALSE;
}
clapFraction uncroppedCenterX = calcCenter((int32_t)imageW);
clapFraction uncroppedCenterY = calcCenter((int32_t)imageH);
avifFraction uncroppedCenterX = calcCenter((int32_t)imageW);
avifFraction uncroppedCenterY = calcCenter((int32_t)imageH);

clapFraction horizOff;
avifFraction horizOff;
horizOff.n = horizOffN;
horizOff.d = horizOffD;
clapFraction croppedCenterX;
if (!clapFractionAdd(uncroppedCenterX, horizOff, &croppedCenterX)) {
avifFraction croppedCenterX;
if (!avifFractionAdd(uncroppedCenterX, horizOff, &croppedCenterX)) {
avifDiagnosticsPrintf(diag, "[Strict] croppedCenterX overflowed");
return AVIF_FALSE;
}

clapFraction vertOff;
avifFraction vertOff;
vertOff.n = vertOffN;
vertOff.d = vertOffD;
clapFraction croppedCenterY;
if (!clapFractionAdd(uncroppedCenterY, vertOff, &croppedCenterY)) {
avifFraction croppedCenterY;
if (!avifFractionAdd(uncroppedCenterY, vertOff, &croppedCenterY)) {
avifDiagnosticsPrintf(diag, "[Strict] croppedCenterY overflowed");
return AVIF_FALSE;
}

clapFraction halfW;
avifFraction halfW;
halfW.n = clapW;
halfW.d = 2;
clapFraction cropX;
if (!clapFractionSub(croppedCenterX, halfW, &cropX)) {
avifFraction cropX;
if (!avifFractionSub(croppedCenterX, halfW, &cropX)) {
avifDiagnosticsPrintf(diag, "[Strict] cropX overflowed");
return AVIF_FALSE;
}
Expand All @@ -789,11 +699,11 @@ avifBool avifCropRectConvertCleanApertureBox(avifCropRect * cropRect,
return AVIF_FALSE;
}

clapFraction halfH;
avifFraction halfH;
halfH.n = clapH;
halfH.d = 2;
clapFraction cropY;
if (!clapFractionSub(croppedCenterY, halfH, &cropY)) {
avifFraction cropY;
if (!avifFractionSub(croppedCenterY, halfH, &cropY)) {
avifDiagnosticsPrintf(diag, "[Strict] cropY overflowed");
return AVIF_FALSE;
}
Expand Down Expand Up @@ -831,8 +741,8 @@ avifBool avifCleanApertureBoxConvertCropRect(avifCleanApertureBox * clap,
avifDiagnosticsPrintf(diag, "[Strict] image width %u or height %u is greater than INT32_MAX", imageW, imageH);
return AVIF_FALSE;
}
clapFraction uncroppedCenterX = calcCenter((int32_t)imageW);
clapFraction uncroppedCenterY = calcCenter((int32_t)imageH);
avifFraction uncroppedCenterX = calcCenter((int32_t)imageW);
avifFraction uncroppedCenterY = calcCenter((int32_t)imageH);

if ((cropRect->width > INT32_MAX) || (cropRect->height > INT32_MAX)) {
avifDiagnosticsPrintf(diag,
Expand All @@ -841,28 +751,28 @@ avifBool avifCleanApertureBoxConvertCropRect(avifCleanApertureBox * clap,
cropRect->height);
return AVIF_FALSE;
}
clapFraction croppedCenterX = calcCenter((int32_t)cropRect->width);
avifFraction croppedCenterX = calcCenter((int32_t)cropRect->width);
const int64_t croppedCenterXN = croppedCenterX.n + (int64_t)cropRect->x * croppedCenterX.d;
if (overflowsInt32(croppedCenterXN)) {
avifDiagnosticsPrintf(diag, "[Strict] croppedCenterX overflowed");
return AVIF_FALSE;
}
croppedCenterX.n = (int32_t)croppedCenterXN;
clapFraction croppedCenterY = calcCenter((int32_t)cropRect->height);
avifFraction croppedCenterY = calcCenter((int32_t)cropRect->height);
const int64_t croppedCenterYN = croppedCenterY.n + (int64_t)cropRect->y * croppedCenterY.d;
if (overflowsInt32(croppedCenterYN)) {
avifDiagnosticsPrintf(diag, "[Strict] croppedCenterY overflowed");
return AVIF_FALSE;
}
croppedCenterY.n = (int32_t)croppedCenterYN;

clapFraction horizOff;
if (!clapFractionSub(croppedCenterX, uncroppedCenterX, &horizOff)) {
avifFraction horizOff;
if (!avifFractionSub(croppedCenterX, uncroppedCenterX, &horizOff)) {
avifDiagnosticsPrintf(diag, "[Strict] horizOff overflowed");
return AVIF_FALSE;
}
clapFraction vertOff;
if (!clapFractionSub(croppedCenterY, uncroppedCenterY, &vertOff)) {
avifFraction vertOff;
if (!avifFractionSub(croppedCenterY, uncroppedCenterY, &vertOff)) {
avifDiagnosticsPrintf(diag, "[Strict] vertOff overflowed");
return AVIF_FALSE;
}
Expand Down
Loading

0 comments on commit 5d16f1f

Please sign in to comment.