Refactor codec implementations to pave the way for image sequence encoding
diff --git a/include/avif/avif.h b/include/avif/avif.h
index ec0e8c9..41a827a 100644
--- a/include/avif/avif.h
+++ b/include/avif/avif.h
@@ -336,8 +336,8 @@
} avifImage;
avifImage * avifImageCreate(int width, int height, int depth, avifPixelFormat yuvFormat);
-avifImage * avifImageCreateEmpty(void); // helper for making an image to decode into
-void avifImageCopy(avifImage * dstImage, const avifImage * srcImage); // deep copy
+avifImage * avifImageCreateEmpty(void); // helper for making an image to decode into
+void avifImageCopy(avifImage * dstImage, const avifImage * srcImage, uint32_t planes); // deep copy
void avifImageDestroy(avifImage * image);
void avifImageSetProfileICC(avifImage * image, const uint8_t * icc, size_t iccSize);
diff --git a/include/avif/internal.h b/include/avif/internal.h
index 6f8a52f..3f80251 100644
--- a/include/avif/internal.h
+++ b/include/avif/internal.h
@@ -136,11 +136,8 @@
typedef avifBool (*avifCodecOpenFunc)(struct avifCodec * codec, uint32_t firstSampleIndex);
typedef avifBool (*avifCodecGetNextImageFunc)(struct avifCodec * codec, avifImage * image);
// avifCodecEncodeImageFunc: if either OBU* is null, skip its encode. alpha should always be lossless
-typedef avifBool (*avifCodecEncodeImageFunc)(struct avifCodec * codec,
- const avifImage * image,
- avifEncoder * encoder,
- avifRWData * obu,
- avifBool alpha);
+typedef avifBool (*avifCodecEncodeImageFunc)(struct avifCodec * codec, const avifImage * image, avifEncoder * encoder, avifBool alpha);
+typedef avifBool (*avifCodecEncodeFinishFunc)(struct avifCodec * codec, avifRWData * obu);
typedef void (*avifCodecDestroyInternalFunc)(struct avifCodec * codec);
typedef struct avifCodec
@@ -152,6 +149,7 @@
avifCodecOpenFunc open;
avifCodecGetNextImageFunc getNextImage;
avifCodecEncodeImageFunc encodeImage;
+ avifCodecEncodeFinishFunc encodeFinish;
avifCodecDestroyInternalFunc destroyInternal;
} avifCodec;
diff --git a/src/avif.c b/src/avif.c
index 28a5031..6d98729 100644
--- a/src/avif.c
+++ b/src/avif.c
@@ -131,7 +131,7 @@
return avifImageCreate(0, 0, 0, AVIF_PIXEL_FORMAT_NONE);
}
-void avifImageCopy(avifImage * dstImage, const avifImage * srcImage)
+void avifImageCopy(avifImage * dstImage, const avifImage * srcImage, uint32_t planes)
{
avifImageFreePlanes(dstImage, AVIF_PLANES_ALL);
@@ -157,7 +157,7 @@
avifImageSetMetadataExif(dstImage, srcImage->exif.data, srcImage->exif.size);
avifImageSetMetadataXMP(dstImage, srcImage->xmp.data, srcImage->xmp.size);
- if (srcImage->yuvPlanes[AVIF_CHAN_Y]) {
+ if ((planes & AVIF_PLANES_YUV) && srcImage->yuvPlanes[AVIF_CHAN_Y]) {
avifImageAllocatePlanes(dstImage, AVIF_PLANES_YUV);
avifPixelFormatInfo formatInfo;
@@ -191,7 +191,7 @@
}
}
- if (srcImage->alphaPlane) {
+ if ((planes & AVIF_PLANES_A) && srcImage->alphaPlane) {
avifImageAllocatePlanes(dstImage, AVIF_PLANES_A);
for (uint32_t j = 0; j < dstImage->height; ++j) {
uint8_t * srcAlphaRow = &srcImage->alphaPlane[j * srcImage->alphaRowBytes];
diff --git a/src/codec_aom.c b/src/codec_aom.c
index 9e447c7..b313a12 100644
--- a/src/codec_aom.c
+++ b/src/codec_aom.c
@@ -34,6 +34,12 @@
aom_codec_iter_t iter;
uint32_t inputSampleIndex;
aom_image_t * image;
+
+ avifBool encoderInitialized;
+ aom_codec_ctx_t encoder;
+ avifPixelFormatInfo formatInfo;
+ aom_img_fmt_t aomFormat;
+ int yShift;
};
static void aomCodecDestroyInternal(avifCodec * codec)
@@ -41,6 +47,9 @@
if (codec->internal->decoderInitialized) {
aom_codec_destroy(&codec->internal->decoder);
}
+ if (codec->internal->encoderInitialized) {
+ aom_codec_destroy(&codec->internal->encoder);
+ }
avifFree(codec->internal);
}
@@ -226,122 +235,120 @@
return fmt;
}
-static avifBool aomCodecEncodeImage(avifCodec * codec, const avifImage * image, avifEncoder * encoder, avifRWData * obu, avifBool alpha)
+static avifBool aomCodecEncodeImage(avifCodec * codec, const avifImage * image, avifEncoder * encoder, avifBool alpha)
{
- avifBool success = AVIF_FALSE;
- aom_codec_iface_t * encoder_interface = aom_codec_av1_cx();
- aom_codec_ctx_t aomEncoder;
+ if (!codec->internal->encoderInitialized) {
+ // Map encoder speed to AOM usage + CpuUsed:
+ // Speed 0: GoodQuality CpuUsed 0
+ // Speed 1: GoodQuality CpuUsed 1
+ // Speed 2: GoodQuality CpuUsed 2
+ // Speed 3: GoodQuality CpuUsed 3
+ // Speed 4: GoodQuality CpuUsed 4
+ // Speed 5: GoodQuality CpuUsed 5
+ // Speed 6: GoodQuality CpuUsed 5
+ // Speed 7: GoodQuality CpuUsed 5
+ // Speed 8: RealTime CpuUsed 6
+ // Speed 9: RealTime CpuUsed 7
+ // Speed 10: RealTime CpuUsed 8
+ unsigned int aomUsage = AOM_USAGE_GOOD_QUALITY;
+ int aomCpuUsed = -1;
+ if (encoder->speed != AVIF_SPEED_DEFAULT) {
+ if (encoder->speed < 8) {
+ aomUsage = AOM_USAGE_GOOD_QUALITY;
+ aomCpuUsed = AVIF_CLAMP(encoder->speed, 0, 5);
+ } else {
+ aomUsage = AOM_USAGE_REALTIME;
+ aomCpuUsed = AVIF_CLAMP(encoder->speed - 2, 6, 8);
+ }
+ }
- // Map encoder speed to AOM usage + CpuUsed:
- // Speed 0: GoodQuality CpuUsed 0
- // Speed 1: GoodQuality CpuUsed 1
- // Speed 2: GoodQuality CpuUsed 2
- // Speed 3: GoodQuality CpuUsed 3
- // Speed 4: GoodQuality CpuUsed 4
- // Speed 5: GoodQuality CpuUsed 5
- // Speed 6: GoodQuality CpuUsed 5
- // Speed 7: GoodQuality CpuUsed 5
- // Speed 8: RealTime CpuUsed 6
- // Speed 9: RealTime CpuUsed 7
- // Speed 10: RealTime CpuUsed 8
- unsigned int aomUsage = AOM_USAGE_GOOD_QUALITY;
- int aomCpuUsed = -1;
- if (encoder->speed != AVIF_SPEED_DEFAULT) {
- if (encoder->speed < 8) {
- aomUsage = AOM_USAGE_GOOD_QUALITY;
- aomCpuUsed = AVIF_CLAMP(encoder->speed, 0, 5);
- } else {
- aomUsage = AOM_USAGE_REALTIME;
- aomCpuUsed = AVIF_CLAMP(encoder->speed - 2, 6, 8);
+ int aomMajorVersion = aom_codec_version_major();
+ if ((aomMajorVersion < 2) && (image->depth > 8)) {
+ // Due to a known issue with libavif v1.0.0-errata1-avif, 10bpc and
+ // 12bpc image encodes will call the wrong variant of
+ // aom_subtract_block when cpu-used is 7 or 8, and crash. Until we get
+ // a new tagged release from libaom with the fix and can verify we're
+ // running with that version of libaom, we must avoid using
+ // cpu-used=7/8 on any >8bpc image encodes.
+ //
+ // Context:
+ // * https://github.com/AOMediaCodec/libavif/issues/49
+ // * https://bugs.chromium.org/p/aomedia/issues/detail?id=2587
+ //
+ // Continued bug tracking here:
+ // * https://github.com/AOMediaCodec/libavif/issues/56
+
+ if (aomCpuUsed > 6) {
+ aomCpuUsed = 6;
+ }
+ }
+
+ codec->internal->aomFormat = avifImageCalcAOMFmt(image, alpha, &codec->internal->yShift);
+ if (codec->internal->aomFormat == AOM_IMG_FMT_NONE) {
+ return AVIF_FALSE;
+ }
+
+ avifGetPixelFormatInfo(image->yuvFormat, &codec->internal->formatInfo);
+
+ aom_codec_iface_t * encoder_interface = aom_codec_av1_cx();
+ struct aom_codec_enc_cfg cfg;
+ aom_codec_enc_config_default(encoder_interface, &cfg, aomUsage);
+ codec->internal->encoderInitialized = AVIF_TRUE;
+
+ cfg.g_profile = codec->configBox.seqProfile;
+ cfg.g_bit_depth = image->depth;
+ cfg.g_input_bit_depth = image->depth;
+ cfg.g_w = image->width;
+ cfg.g_h = image->height;
+ if (encoder->maxThreads > 1) {
+ cfg.g_threads = encoder->maxThreads;
+ }
+
+ int minQuantizer = AVIF_CLAMP(encoder->minQuantizer, 0, 63);
+ int maxQuantizer = AVIF_CLAMP(encoder->maxQuantizer, 0, 63);
+ if (alpha) {
+ minQuantizer = AVIF_CLAMP(encoder->minQuantizerAlpha, 0, 63);
+ maxQuantizer = AVIF_CLAMP(encoder->maxQuantizerAlpha, 0, 63);
+ }
+ avifBool lossless = ((minQuantizer == AVIF_QUANTIZER_LOSSLESS) && (maxQuantizer == AVIF_QUANTIZER_LOSSLESS));
+ cfg.rc_min_quantizer = minQuantizer;
+ cfg.rc_max_quantizer = maxQuantizer;
+
+ if (alpha || (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) {
+ cfg.monochrome = 1;
+ }
+
+ aom_codec_flags_t encoderFlags = 0;
+ if (image->depth > 8) {
+ encoderFlags |= AOM_CODEC_USE_HIGHBITDEPTH;
+ }
+ aom_codec_enc_init(&codec->internal->encoder, encoder_interface, &cfg, encoderFlags);
+
+ if (lossless) {
+ aom_codec_control(&codec->internal->encoder, AV1E_SET_LOSSLESS, 1);
+ }
+ if (encoder->maxThreads > 1) {
+ aom_codec_control(&codec->internal->encoder, AV1E_SET_ROW_MT, 1);
+ }
+ if (encoder->tileRowsLog2 != 0) {
+ int tileRowsLog2 = AVIF_CLAMP(encoder->tileRowsLog2, 0, 6);
+ aom_codec_control(&codec->internal->encoder, AV1E_SET_TILE_ROWS, tileRowsLog2);
+ }
+ if (encoder->tileColsLog2 != 0) {
+ int tileColsLog2 = AVIF_CLAMP(encoder->tileColsLog2, 0, 6);
+ aom_codec_control(&codec->internal->encoder, AV1E_SET_TILE_COLUMNS, tileColsLog2);
+ }
+ if (aomCpuUsed != -1) {
+ aom_codec_control(&codec->internal->encoder, AOME_SET_CPUUSED, aomCpuUsed);
}
}
- int aomMajorVersion = aom_codec_version_major();
- if ((aomMajorVersion < 2) && (image->depth > 8)) {
- // Due to a known issue with libavif v1.0.0-errata1-avif, 10bpc and
- // 12bpc image encodes will call the wrong variant of
- // aom_subtract_block when cpu-used is 7 or 8, and crash. Until we get
- // a new tagged release from libaom with the fix and can verify we're
- // running with that version of libaom, we must avoid using
- // cpu-used=7/8 on any >8bpc image encodes.
- //
- // Context:
- // * https://github.com/AOMediaCodec/libavif/issues/49
- // * https://bugs.chromium.org/p/aomedia/issues/detail?id=2587
- //
- // Continued bug tracking here:
- // * https://github.com/AOMediaCodec/libavif/issues/56
-
- if (aomCpuUsed > 6) {
- aomCpuUsed = 6;
- }
- }
-
- int yShift = 0;
- aom_img_fmt_t aomFormat = avifImageCalcAOMFmt(image, alpha, &yShift);
- if (aomFormat == AOM_IMG_FMT_NONE) {
- return AVIF_FALSE;
- }
-
- avifPixelFormatInfo formatInfo;
- avifGetPixelFormatInfo(image->yuvFormat, &formatInfo);
-
- struct aom_codec_enc_cfg cfg;
- aom_codec_enc_config_default(encoder_interface, &cfg, aomUsage);
-
- cfg.g_profile = codec->configBox.seqProfile;
- cfg.g_bit_depth = image->depth;
- cfg.g_input_bit_depth = image->depth;
- cfg.g_w = image->width;
- cfg.g_h = image->height;
- if (encoder->maxThreads > 1) {
- cfg.g_threads = encoder->maxThreads;
- }
-
- int minQuantizer = AVIF_CLAMP(encoder->minQuantizer, 0, 63);
- int maxQuantizer = AVIF_CLAMP(encoder->maxQuantizer, 0, 63);
- if (alpha) {
- minQuantizer = AVIF_CLAMP(encoder->minQuantizerAlpha, 0, 63);
- maxQuantizer = AVIF_CLAMP(encoder->maxQuantizerAlpha, 0, 63);
- }
- avifBool lossless = ((minQuantizer == AVIF_QUANTIZER_LOSSLESS) && (maxQuantizer == AVIF_QUANTIZER_LOSSLESS));
- cfg.rc_min_quantizer = minQuantizer;
- cfg.rc_max_quantizer = maxQuantizer;
-
- if (alpha || (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) {
- cfg.monochrome = 1;
- }
-
- aom_codec_flags_t encoderFlags = 0;
- if (image->depth > 8) {
- encoderFlags |= AOM_CODEC_USE_HIGHBITDEPTH;
- }
- aom_codec_enc_init(&aomEncoder, encoder_interface, &cfg, encoderFlags);
-
- if (lossless) {
- aom_codec_control(&aomEncoder, AV1E_SET_LOSSLESS, 1);
- }
- if (encoder->maxThreads > 1) {
- aom_codec_control(&aomEncoder, AV1E_SET_ROW_MT, 1);
- }
- if (encoder->tileRowsLog2 != 0) {
- int tileRowsLog2 = AVIF_CLAMP(encoder->tileRowsLog2, 0, 6);
- aom_codec_control(&aomEncoder, AV1E_SET_TILE_ROWS, tileRowsLog2);
- }
- if (encoder->tileColsLog2 != 0) {
- int tileColsLog2 = AVIF_CLAMP(encoder->tileColsLog2, 0, 6);
- aom_codec_control(&aomEncoder, AV1E_SET_TILE_COLUMNS, tileColsLog2);
- }
- if (aomCpuUsed != -1) {
- aom_codec_control(&aomEncoder, AOME_SET_CPUUSED, aomCpuUsed);
- }
-
- uint32_t uvHeight = (image->height + yShift) >> yShift;
- aom_image_t * aomImage = aom_img_alloc(NULL, aomFormat, image->width, image->height, 16);
+ uint32_t uvHeight = (image->height + codec->internal->yShift) >> codec->internal->yShift;
+ aom_image_t * aomImage = aom_img_alloc(NULL, codec->internal->aomFormat, image->width, image->height, 16);
if (alpha) {
aomImage->range = (image->alphaRange == AVIF_RANGE_FULL) ? AOM_CR_FULL_RANGE : AOM_CR_STUDIO_RANGE;
- aom_codec_control(&aomEncoder, AV1E_SET_COLOR_RANGE, aomImage->range);
+ aom_codec_control(&codec->internal->encoder, AV1E_SET_COLOR_RANGE, aomImage->range);
aomImage->monochrome = 1;
for (uint32_t j = 0; j < image->height; ++j) {
uint8_t * srcAlphaRow = &image->alphaPlane[j * image->alphaRowBytes];
@@ -352,7 +359,7 @@
// Ignore UV planes when monochrome
} else {
aomImage->range = (image->yuvRange == AVIF_RANGE_FULL) ? AOM_CR_FULL_RANGE : AOM_CR_STUDIO_RANGE;
- aom_codec_control(&aomEncoder, AV1E_SET_COLOR_RANGE, aomImage->range);
+ aom_codec_control(&codec->internal->encoder, AV1E_SET_COLOR_RANGE, aomImage->range);
int yuvPlaneCount = 3;
if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
yuvPlaneCount = 1; // Ignore UV planes when monochrome
@@ -362,10 +369,10 @@
int aomPlaneIndex = yuvPlane;
int planeHeight = image->height;
if (yuvPlane == AVIF_CHAN_U) {
- aomPlaneIndex = formatInfo.aomIndexU;
+ aomPlaneIndex = codec->internal->formatInfo.aomIndexU;
planeHeight = uvHeight;
} else if (yuvPlane == AVIF_CHAN_V) {
- aomPlaneIndex = formatInfo.aomIndexV;
+ aomPlaneIndex = codec->internal->formatInfo.aomIndexV;
planeHeight = uvHeight;
}
@@ -379,22 +386,29 @@
aomImage->cp = (aom_color_primaries_t)image->colorPrimaries;
aomImage->tc = (aom_transfer_characteristics_t)image->transferCharacteristics;
aomImage->mc = (aom_matrix_coefficients_t)image->matrixCoefficients;
- aom_codec_control(&aomEncoder, AV1E_SET_COLOR_PRIMARIES, aomImage->cp);
- aom_codec_control(&aomEncoder, AV1E_SET_TRANSFER_CHARACTERISTICS, aomImage->tc);
- aom_codec_control(&aomEncoder, AV1E_SET_MATRIX_COEFFICIENTS, aomImage->mc);
+ aom_codec_control(&codec->internal->encoder, AV1E_SET_COLOR_PRIMARIES, aomImage->cp);
+ aom_codec_control(&codec->internal->encoder, AV1E_SET_TRANSFER_CHARACTERISTICS, aomImage->tc);
+ aom_codec_control(&codec->internal->encoder, AV1E_SET_MATRIX_COEFFICIENTS, aomImage->mc);
}
- aom_codec_encode(&aomEncoder, aomImage, 0, 1, 0);
+ aom_codec_encode(&codec->internal->encoder, aomImage, 0, 1, 0);
+ aom_img_free(aomImage);
+ return AVIF_TRUE;
+}
+
+static avifBool aomCodecEncodeFinish(avifCodec * codec, avifRWData * obu)
+{
+ avifBool success = AVIF_FALSE;
avifBool flushed = AVIF_FALSE;
aom_codec_iter_t iter = NULL;
for (;;) {
- const aom_codec_cx_pkt_t * pkt = aom_codec_get_cx_data(&aomEncoder, &iter);
+ const aom_codec_cx_pkt_t * pkt = aom_codec_get_cx_data(&codec->internal->encoder, &iter);
if (pkt == NULL) {
if (flushed)
break;
- aom_codec_encode(&aomEncoder, NULL, 0, 1, 0); // flush
+ aom_codec_encode(&codec->internal->encoder, NULL, 0, 1, 0); // flush
flushed = AVIF_TRUE;
continue;
}
@@ -404,9 +418,6 @@
break;
}
}
-
- aom_img_free(aomImage);
- aom_codec_destroy(&aomEncoder);
return success;
}
@@ -422,6 +433,7 @@
codec->open = aomCodecOpen;
codec->getNextImage = aomCodecGetNextImage;
codec->encodeImage = aomCodecEncodeImage;
+ codec->encodeFinish = aomCodecEncodeFinish;
codec->destroyInternal = aomCodecDestroyInternal;
codec->internal = (struct avifCodecInternal *)avifAlloc(sizeof(struct avifCodecInternal));
diff --git a/src/codec_rav1e.c b/src/codec_rav1e.c
index 7c7a6f4..02bccf9 100644
--- a/src/codec_rav1e.c
+++ b/src/codec_rav1e.c
@@ -9,168 +9,159 @@
struct avifCodecInternal
{
- uint32_t unused; // rav1e codec has no state
+ RaContext * rav1eContext;
+ RaChromaSampling chromaSampling;
+ int yShift;
};
static void rav1eCodecDestroyInternal(avifCodec * codec)
{
+ if (codec->internal->rav1eContext) {
+ rav1e_context_unref(codec->internal->rav1eContext);
+ codec->internal->rav1eContext = NULL;
+ }
avifFree(codec->internal);
}
static avifBool rav1eCodecOpen(struct avifCodec * codec, uint32_t firstSampleIndex)
{
(void)firstSampleIndex; // Codec is encode-only, this isn't used
- (void)codec;
+
+ codec->internal->rav1eContext = NULL;
return AVIF_TRUE;
}
-static avifBool rav1eCodecEncodeImage(avifCodec * codec, const avifImage * image, avifEncoder * encoder, avifRWData * obu, avifBool alpha)
+static avifBool rav1eCodecEncodeImage(avifCodec * codec, const avifImage * image, avifEncoder * encoder, avifBool alpha)
{
(void)codec; // unused
avifBool success = AVIF_FALSE;
RaConfig * rav1eConfig = NULL;
- RaContext * rav1eContext = NULL;
RaFrame * rav1eFrame = NULL;
- RaPacket * pkt = NULL;
- int yShift = 0;
- RaChromaSampling chromaSampling;
- RaPixelRange rav1eRange;
- if (alpha) {
- rav1eRange = (image->alphaRange == AVIF_RANGE_FULL) ? RA_PIXEL_RANGE_FULL : RA_PIXEL_RANGE_LIMITED;
- chromaSampling = RA_CHROMA_SAMPLING_CS400;
- } else {
- rav1eRange = (image->yuvRange == AVIF_RANGE_FULL) ? RA_PIXEL_RANGE_FULL : RA_PIXEL_RANGE_LIMITED;
- switch (image->yuvFormat) {
- case AVIF_PIXEL_FORMAT_YUV444:
- chromaSampling = RA_CHROMA_SAMPLING_CS444;
- break;
- case AVIF_PIXEL_FORMAT_YUV422:
- chromaSampling = RA_CHROMA_SAMPLING_CS422;
- break;
- case AVIF_PIXEL_FORMAT_YUV420:
- chromaSampling = RA_CHROMA_SAMPLING_CS420;
- yShift = 1;
- break;
- case AVIF_PIXEL_FORMAT_YUV400:
- chromaSampling = RA_CHROMA_SAMPLING_CS400;
- yShift = 1;
- break;
- case AVIF_PIXEL_FORMAT_YV12:
- case AVIF_PIXEL_FORMAT_NONE:
- default:
- return AVIF_FALSE;
+ if (!codec->internal->rav1eContext) {
+ RaPixelRange rav1eRange;
+ if (alpha) {
+ rav1eRange = (image->alphaRange == AVIF_RANGE_FULL) ? RA_PIXEL_RANGE_FULL : RA_PIXEL_RANGE_LIMITED;
+ codec->internal->chromaSampling = RA_CHROMA_SAMPLING_CS400;
+ } else {
+ rav1eRange = (image->yuvRange == AVIF_RANGE_FULL) ? RA_PIXEL_RANGE_FULL : RA_PIXEL_RANGE_LIMITED;
+ codec->internal->yShift = 0;
+ switch (image->yuvFormat) {
+ case AVIF_PIXEL_FORMAT_YUV444:
+ codec->internal->chromaSampling = RA_CHROMA_SAMPLING_CS444;
+ break;
+ case AVIF_PIXEL_FORMAT_YUV422:
+ codec->internal->chromaSampling = RA_CHROMA_SAMPLING_CS422;
+ break;
+ case AVIF_PIXEL_FORMAT_YUV420:
+ codec->internal->chromaSampling = RA_CHROMA_SAMPLING_CS420;
+ codec->internal->yShift = 1;
+ break;
+ case AVIF_PIXEL_FORMAT_YUV400:
+ codec->internal->chromaSampling = RA_CHROMA_SAMPLING_CS400;
+ codec->internal->yShift = 1;
+ break;
+ case AVIF_PIXEL_FORMAT_YV12:
+ case AVIF_PIXEL_FORMAT_NONE:
+ default:
+ return AVIF_FALSE;
+ }
}
- }
- rav1eConfig = rav1e_config_default();
- if (rav1e_config_set_pixel_format(
- rav1eConfig, (uint8_t)image->depth, chromaSampling, RA_CHROMA_SAMPLE_POSITION_UNKNOWN, rav1eRange) < 0) {
- goto cleanup;
- }
-
- if (rav1e_config_parse(rav1eConfig, "still_picture", "true") == -1) {
- goto cleanup;
- }
- if (rav1e_config_parse_int(rav1eConfig, "width", image->width) == -1) {
- goto cleanup;
- }
- if (rav1e_config_parse_int(rav1eConfig, "height", image->height) == -1) {
- goto cleanup;
- }
- if (rav1e_config_parse_int(rav1eConfig, "threads", encoder->maxThreads) == -1) {
- goto cleanup;
- }
-
- int minQuantizer = AVIF_CLAMP(encoder->minQuantizer, 0, 63);
- int maxQuantizer = AVIF_CLAMP(encoder->maxQuantizer, 0, 63);
- if (alpha) {
- minQuantizer = AVIF_CLAMP(encoder->minQuantizerAlpha, 0, 63);
- maxQuantizer = AVIF_CLAMP(encoder->maxQuantizerAlpha, 0, 63);
- }
- minQuantizer = (minQuantizer * 255) / 63; // Rescale quantizer values as rav1e's QP range is [0,255]
- maxQuantizer = (maxQuantizer * 255) / 63;
- if (rav1e_config_parse_int(rav1eConfig, "min_quantizer", minQuantizer) == -1) {
- goto cleanup;
- }
- if (rav1e_config_parse_int(rav1eConfig, "quantizer", maxQuantizer) == -1) {
- goto cleanup;
- }
- if (encoder->tileRowsLog2 != 0) {
- int tileRowsLog2 = AVIF_CLAMP(encoder->tileRowsLog2, 0, 6);
- if (rav1e_config_parse_int(rav1eConfig, "tile_rows", 1 << tileRowsLog2) == -1) {
+ rav1eConfig = rav1e_config_default();
+ if (rav1e_config_set_pixel_format(
+ rav1eConfig, (uint8_t)image->depth, codec->internal->chromaSampling, RA_CHROMA_SAMPLE_POSITION_UNKNOWN, rav1eRange) < 0) {
goto cleanup;
}
- }
- if (encoder->tileColsLog2 != 0) {
- int tileColsLog2 = AVIF_CLAMP(encoder->tileColsLog2, 0, 6);
- if (rav1e_config_parse_int(rav1eConfig, "tile_cols", 1 << tileColsLog2) == -1) {
+
+ if (rav1e_config_parse(rav1eConfig, "still_picture", "true") == -1) {
goto cleanup;
}
- }
- if (encoder->speed != AVIF_SPEED_DEFAULT) {
- int speed = AVIF_CLAMP(encoder->speed, 0, 10);
- if (rav1e_config_parse_int(rav1eConfig, "speed", speed) == -1) {
+ if (rav1e_config_parse_int(rav1eConfig, "width", image->width) == -1) {
+ goto cleanup;
+ }
+ if (rav1e_config_parse_int(rav1eConfig, "height", image->height) == -1) {
+ goto cleanup;
+ }
+ if (rav1e_config_parse_int(rav1eConfig, "threads", encoder->maxThreads) == -1) {
+ goto cleanup;
+ }
+
+ int minQuantizer = AVIF_CLAMP(encoder->minQuantizer, 0, 63);
+ int maxQuantizer = AVIF_CLAMP(encoder->maxQuantizer, 0, 63);
+ if (alpha) {
+ minQuantizer = AVIF_CLAMP(encoder->minQuantizerAlpha, 0, 63);
+ maxQuantizer = AVIF_CLAMP(encoder->maxQuantizerAlpha, 0, 63);
+ }
+ minQuantizer = (minQuantizer * 255) / 63; // Rescale quantizer values as rav1e's QP range is [0,255]
+ maxQuantizer = (maxQuantizer * 255) / 63;
+ if (rav1e_config_parse_int(rav1eConfig, "min_quantizer", minQuantizer) == -1) {
+ goto cleanup;
+ }
+ if (rav1e_config_parse_int(rav1eConfig, "quantizer", maxQuantizer) == -1) {
+ goto cleanup;
+ }
+ if (encoder->tileRowsLog2 != 0) {
+ int tileRowsLog2 = AVIF_CLAMP(encoder->tileRowsLog2, 0, 6);
+ if (rav1e_config_parse_int(rav1eConfig, "tile_rows", 1 << tileRowsLog2) == -1) {
+ goto cleanup;
+ }
+ }
+ if (encoder->tileColsLog2 != 0) {
+ int tileColsLog2 = AVIF_CLAMP(encoder->tileColsLog2, 0, 6);
+ if (rav1e_config_parse_int(rav1eConfig, "tile_cols", 1 << tileColsLog2) == -1) {
+ goto cleanup;
+ }
+ }
+ if (encoder->speed != AVIF_SPEED_DEFAULT) {
+ int speed = AVIF_CLAMP(encoder->speed, 0, 10);
+ if (rav1e_config_parse_int(rav1eConfig, "speed", speed) == -1) {
+ goto cleanup;
+ }
+ }
+
+ rav1e_config_set_color_description(rav1eConfig,
+ (RaMatrixCoefficients)image->matrixCoefficients,
+ (RaColorPrimaries)image->colorPrimaries,
+ (RaTransferCharacteristics)image->transferCharacteristics);
+
+ codec->internal->rav1eContext = rav1e_context_new(rav1eConfig);
+ if (!codec->internal->rav1eContext) {
goto cleanup;
}
}
- rav1e_config_set_color_description(rav1eConfig,
- (RaMatrixCoefficients)image->matrixCoefficients,
- (RaColorPrimaries)image->colorPrimaries,
- (RaTransferCharacteristics)image->transferCharacteristics);
-
- rav1eContext = rav1e_context_new(rav1eConfig);
- if (!rav1eContext) {
- goto cleanup;
- }
- rav1eFrame = rav1e_frame_new(rav1eContext);
+ rav1eFrame = rav1e_frame_new(codec->internal->rav1eContext);
int byteWidth = (image->depth > 8) ? 2 : 1;
if (alpha) {
rav1e_frame_fill_plane(rav1eFrame, 0, image->alphaPlane, image->alphaRowBytes * image->height, image->alphaRowBytes, byteWidth);
} else {
- uint32_t uvHeight = (image->height + yShift) >> yShift;
+ uint32_t uvHeight = (image->height + codec->internal->yShift) >> codec->internal->yShift;
rav1e_frame_fill_plane(rav1eFrame, 0, image->yuvPlanes[0], image->yuvRowBytes[0] * image->height, image->yuvRowBytes[0], byteWidth);
- if (chromaSampling != RA_CHROMA_SAMPLING_CS400) {
+ if (codec->internal->chromaSampling != RA_CHROMA_SAMPLING_CS400) {
rav1e_frame_fill_plane(rav1eFrame, 1, image->yuvPlanes[1], image->yuvRowBytes[1] * uvHeight, image->yuvRowBytes[1], byteWidth);
rav1e_frame_fill_plane(rav1eFrame, 2, image->yuvPlanes[2], image->yuvRowBytes[2] * uvHeight, image->yuvRowBytes[2], byteWidth);
}
}
- RaEncoderStatus encoderStatus = rav1e_send_frame(rav1eContext, rav1eFrame);
+ RaEncoderStatus encoderStatus = rav1e_send_frame(codec->internal->rav1eContext, rav1eFrame);
if (encoderStatus != 0) {
goto cleanup;
}
- encoderStatus = rav1e_send_frame(rav1eContext, NULL); // flush
+ encoderStatus = rav1e_send_frame(codec->internal->rav1eContext, NULL); // flush
if (encoderStatus != 0) {
goto cleanup;
}
- encoderStatus = rav1e_receive_packet(rav1eContext, &pkt);
- if (encoderStatus != 0) {
- goto cleanup;
- }
-
- if (pkt && pkt->data && (pkt->len > 0)) {
- avifRWDataSet(obu, pkt->data, pkt->len);
- success = AVIF_TRUE;
- }
+ success = AVIF_TRUE;
cleanup:
- if (pkt) {
- rav1e_packet_unref(pkt);
- pkt = NULL;
- }
if (rav1eFrame) {
rav1e_frame_unref(rav1eFrame);
rav1eFrame = NULL;
}
- if (rav1eContext) {
- rav1e_context_unref(rav1eContext);
- rav1eContext = NULL;
- }
if (rav1eConfig) {
rav1e_config_unref(rav1eConfig);
rav1eConfig = NULL;
@@ -178,6 +169,19 @@
return success;
}
+static avifBool rav1eCodecEncodeFinish(avifCodec * codec, avifRWData * obu)
+{
+ RaPacket * pkt = NULL;
+ RaEncoderStatus encoderStatus = rav1e_receive_packet(codec->internal->rav1eContext, &pkt);
+ if ((encoderStatus == 0) && pkt && pkt->data && (pkt->len > 0)) {
+ avifRWDataSet(obu, pkt->data, pkt->len);
+ rav1e_packet_unref(pkt);
+ pkt = NULL;
+ return AVIF_TRUE;
+ }
+ return AVIF_FALSE;
+}
+
const char * avifCodecVersionRav1e(void)
{
return rav1e_version_full();
@@ -189,6 +193,7 @@
memset(codec, 0, sizeof(struct avifCodec));
codec->open = rav1eCodecOpen;
codec->encodeImage = rav1eCodecEncodeImage;
+ codec->encodeFinish = rav1eCodecEncodeFinish;
codec->destroyInternal = rav1eCodecDestroyInternal;
codec->internal = (struct avifCodecInternal *)avifAlloc(sizeof(struct avifCodecInternal));
diff --git a/src/read.c b/src/read.c
index 22249f7..ee52c97 100644
--- a/src/read.c
+++ b/src/read.c
@@ -2481,6 +2481,6 @@
if (result != AVIF_RESULT_OK) {
return result;
}
- avifImageCopy(image, decoder->image);
+ avifImageCopy(image, decoder->image, AVIF_PLANES_ALL);
return AVIF_RESULT_OK;
}
diff --git a/src/write.c b/src/write.c
index d643aee..434e95d 100644
--- a/src/write.c
+++ b/src/write.c
@@ -37,9 +37,8 @@
{
uint16_t id;
uint8_t type[4];
- const avifImage * image; // avifImage* to use when encoding or populating ipma for this item (unowned)
- avifCodec * codec; // only present on type==av01
- avifRWData content; // OBU data on av01, metadata payload for Exif/XMP
+ avifCodec * codec; // only present on type==av01
+ avifRWData content; // OBU data on av01, metadata payload for Exif/XMP
avifBool alpha;
const char * infeName;
@@ -61,6 +60,9 @@
typedef struct avifEncoderData
{
avifEncoderItemArray items;
+ avifImage * imageMetadata;
+ avifEncoderItem * colorItem;
+ avifEncoderItem * alphaItem;
uint16_t lastItemID;
uint16_t primaryItemID;
} avifEncoderData;
@@ -69,6 +71,7 @@
{
avifEncoderData * data = (avifEncoderData *)avifAlloc(sizeof(avifEncoderData));
memset(data, 0, sizeof(avifEncoderData));
+ data->imageMetadata = avifImageCreateEmpty();
avifArrayCreate(&data->items, sizeof(avifEncoderItem), 8);
return data;
}
@@ -93,6 +96,7 @@
}
avifRWDataFree(&item->content);
}
+ avifImageDestroy(data->imageMetadata);
avifArrayDestroy(&data->items);
avifFree(data);
}
@@ -121,112 +125,111 @@
avifFree(encoder);
}
-avifResult avifEncoderWrite(avifEncoder * encoder, const avifImage * image, avifRWData * output)
+static avifResult avifEncoderAddImage(avifEncoder * encoder, const avifImage * image)
{
+ // -----------------------------------------------------------------------
+ // Validate image
+
if ((image->depth != 8) && (image->depth != 10) && (image->depth != 12)) {
return AVIF_RESULT_UNSUPPORTED_DEPTH;
}
- avifResult result = AVIF_RESULT_UNKNOWN_ERROR;
-
- avifEncoderItem * colorItem = avifEncoderDataCreateItem(encoder->data, "av01", "Color", 6);
- colorItem->image = image;
- colorItem->codec = avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE);
- if (!colorItem->codec) {
- // Just bail out early, we're not surviving this function without an encoder compiled in
- return AVIF_RESULT_NO_CODEC_AVAILABLE;
- }
- encoder->data->primaryItemID = colorItem->id;
-
- avifBool imageIsOpaque = avifImageIsOpaque(image);
- if (!imageIsOpaque) {
- avifEncoderItem * alphaItem = avifEncoderDataCreateItem(encoder->data, "av01", "Alpha", 6);
- alphaItem->image = image;
- alphaItem->codec = avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE);
- if (!alphaItem->codec) {
- return AVIF_RESULT_NO_CODEC_AVAILABLE;
- }
- alphaItem->alpha = AVIF_TRUE;
- alphaItem->irefToID = encoder->data->primaryItemID;
- alphaItem->irefType = "auxl";
- }
-
- // -----------------------------------------------------------------------
- // Create metadata items (Exif, XMP)
-
- if (image->exif.size > 0) {
- // Validate Exif payload (if any) and find TIFF header offset
- uint32_t exifTiffHeaderOffset = 0;
- if (image->exif.size > 0) {
- if (image->exif.size < 4) {
- // Can't even fit the TIFF header, something is wrong
- return AVIF_RESULT_INVALID_EXIF_PAYLOAD;
- }
-
- const uint8_t tiffHeaderBE[4] = { 'M', 'M', 0, 42 };
- const uint8_t tiffHeaderLE[4] = { 'I', 'I', 42, 0 };
- for (; exifTiffHeaderOffset < (image->exif.size - 4); ++exifTiffHeaderOffset) {
- if (!memcmp(&image->exif.data[exifTiffHeaderOffset], tiffHeaderBE, sizeof(tiffHeaderBE))) {
- break;
- }
- if (!memcmp(&image->exif.data[exifTiffHeaderOffset], tiffHeaderLE, sizeof(tiffHeaderLE))) {
- break;
- }
- }
-
- if (exifTiffHeaderOffset >= image->exif.size - 4) {
- // Couldn't find the TIFF header
- return AVIF_RESULT_INVALID_EXIF_PAYLOAD;
- }
- }
-
- avifEncoderItem * exifItem = avifEncoderDataCreateItem(encoder->data, "Exif", "Exif", 5);
- exifItem->irefToID = encoder->data->primaryItemID;
- exifItem->irefType = "cdsc";
-
- avifRWDataRealloc(&exifItem->content, sizeof(uint32_t) + image->exif.size);
- exifTiffHeaderOffset = avifHTONL(exifTiffHeaderOffset);
- memcpy(exifItem->content.data, &exifTiffHeaderOffset, sizeof(uint32_t));
- memcpy(exifItem->content.data + sizeof(uint32_t), image->exif.data, image->exif.size);
- }
-
- if (image->xmp.size > 0) {
- avifEncoderItem * xmpItem = avifEncoderDataCreateItem(encoder->data, "mime", "XMP", 4);
- xmpItem->irefToID = encoder->data->primaryItemID;
- xmpItem->irefType = "cdsc";
-
- xmpItem->infeContentType = xmpContentType;
- xmpItem->infeContentTypeSize = xmpContentTypeSize;
- avifRWDataSet(&xmpItem->content, image->xmp.data, image->xmp.size);
- }
-
- // -----------------------------------------------------------------------
- // Pre-fill config boxes based on image (codec can query/update later)
-
- for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
- avifEncoderItem * item = &encoder->data->items.item[itemIndex];
- if (item->codec && item->image) {
- fillConfigBox(item->codec, item->image, item->alpha);
- }
- }
-
- // -----------------------------------------------------------------------
- // Begin write stream
-
- avifRWStream s;
- avifRWStreamStart(&s, output);
-
- // -----------------------------------------------------------------------
- // Validate image
-
if (!image->width || !image->height || !image->yuvPlanes[AVIF_CHAN_Y]) {
- result = AVIF_RESULT_NO_CONTENT;
- goto writeCleanup;
+ return AVIF_RESULT_NO_CONTENT;
}
if (image->yuvFormat == AVIF_PIXEL_FORMAT_NONE) {
- result = AVIF_RESULT_NO_YUV_FORMAT_SELECTED;
- goto writeCleanup;
+ return AVIF_RESULT_NO_YUV_FORMAT_SELECTED;
+ }
+
+ // -----------------------------------------------------------------------
+
+ if (encoder->data->items.count == 0) {
+ // Make a copy of the first image's metadata (sans pixels) for future writing/validation
+ avifImageCopy(encoder->data->imageMetadata, image, 0);
+
+ // Prepare all AV1 items
+
+ encoder->data->colorItem = avifEncoderDataCreateItem(encoder->data, "av01", "Color", 6);
+ encoder->data->colorItem->codec = avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE);
+ if (!encoder->data->colorItem->codec) {
+ // Just bail out early, we're not surviving this function without an encoder compiled in
+ return AVIF_RESULT_NO_CODEC_AVAILABLE;
+ }
+ encoder->data->primaryItemID = encoder->data->colorItem->id;
+
+ avifBool imageIsOpaque = avifImageIsOpaque(image);
+ if (!imageIsOpaque) {
+ encoder->data->alphaItem = avifEncoderDataCreateItem(encoder->data, "av01", "Alpha", 6);
+ encoder->data->alphaItem->codec = avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE);
+ if (!encoder->data->alphaItem->codec) {
+ return AVIF_RESULT_NO_CODEC_AVAILABLE;
+ }
+ encoder->data->alphaItem->alpha = AVIF_TRUE;
+ encoder->data->alphaItem->irefToID = encoder->data->primaryItemID;
+ encoder->data->alphaItem->irefType = "auxl";
+ }
+
+ // -----------------------------------------------------------------------
+ // Create metadata items (Exif, XMP)
+
+ if (image->exif.size > 0) {
+ // Validate Exif payload (if any) and find TIFF header offset
+ uint32_t exifTiffHeaderOffset = 0;
+ if (image->exif.size > 0) {
+ if (image->exif.size < 4) {
+ // Can't even fit the TIFF header, something is wrong
+ return AVIF_RESULT_INVALID_EXIF_PAYLOAD;
+ }
+
+ const uint8_t tiffHeaderBE[4] = { 'M', 'M', 0, 42 };
+ const uint8_t tiffHeaderLE[4] = { 'I', 'I', 42, 0 };
+ for (; exifTiffHeaderOffset < (image->exif.size - 4); ++exifTiffHeaderOffset) {
+ if (!memcmp(&image->exif.data[exifTiffHeaderOffset], tiffHeaderBE, sizeof(tiffHeaderBE))) {
+ break;
+ }
+ if (!memcmp(&image->exif.data[exifTiffHeaderOffset], tiffHeaderLE, sizeof(tiffHeaderLE))) {
+ break;
+ }
+ }
+
+ if (exifTiffHeaderOffset >= image->exif.size - 4) {
+ // Couldn't find the TIFF header
+ return AVIF_RESULT_INVALID_EXIF_PAYLOAD;
+ }
+ }
+
+ avifEncoderItem * exifItem = avifEncoderDataCreateItem(encoder->data, "Exif", "Exif", 5);
+ exifItem->irefToID = encoder->data->primaryItemID;
+ exifItem->irefType = "cdsc";
+
+ avifRWDataRealloc(&exifItem->content, sizeof(uint32_t) + image->exif.size);
+ exifTiffHeaderOffset = avifHTONL(exifTiffHeaderOffset);
+ memcpy(exifItem->content.data, &exifTiffHeaderOffset, sizeof(uint32_t));
+ memcpy(exifItem->content.data + sizeof(uint32_t), image->exif.data, image->exif.size);
+ }
+
+ if (image->xmp.size > 0) {
+ avifEncoderItem * xmpItem = avifEncoderDataCreateItem(encoder->data, "mime", "XMP", 4);
+ xmpItem->irefToID = encoder->data->primaryItemID;
+ xmpItem->irefType = "cdsc";
+
+ xmpItem->infeContentType = xmpContentType;
+ xmpItem->infeContentTypeSize = xmpContentTypeSize;
+ avifRWDataSet(&xmpItem->content, image->xmp.data, image->xmp.size);
+ }
+
+ // -----------------------------------------------------------------------
+ // Pre-fill config boxes based on image (codec can query/update later)
+
+ for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
+ avifEncoderItem * item = &encoder->data->items.item[itemIndex];
+ if (item->codec) {
+ fillConfigBox(item->codec, image, item->alpha);
+ }
+ }
+ } else {
+ // Another frame in an image sequence
}
// -----------------------------------------------------------------------
@@ -234,13 +237,31 @@
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
- if (item->codec && item->image) {
- if (!item->codec->encodeImage(item->codec, item->image, encoder, &item->content, item->alpha)) {
- result = item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED;
- goto writeCleanup;
+ if (item->codec) {
+ if (!item->codec->encodeImage(item->codec, image, encoder, item->alpha)) {
+ return item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED;
+ }
+ }
+ }
+ return AVIF_RESULT_OK;
+}
+
+static avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output)
+{
+ if (encoder->data->items.count < 1) {
+ return AVIF_RESULT_NO_CONTENT;
+ }
+
+ // -----------------------------------------------------------------------
+ // Finish up AV1 encoding
+
+ for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
+ avifEncoderItem * item = &encoder->data->items.item[itemIndex];
+ if (item->codec) {
+ if (!item->codec->encodeFinish(item->codec, &item->content)) {
+ return item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED;
}
- // TODO: rethink this if/when image grid encoding support is added
if (item->alpha) {
encoder->ioStats.alphaOBUSize = item->content.size;
} else {
@@ -250,19 +271,27 @@
}
// -----------------------------------------------------------------------
+ // Begin write stream
+
+ avifImage * imageMetadata = encoder->data->imageMetadata;
+
+ avifRWStream s;
+ avifRWStreamStart(&s, output);
+
+ // -----------------------------------------------------------------------
// Write ftyp
avifBoxMarker ftyp = avifRWStreamWriteBox(&s, "ftyp", -1, 0);
- avifRWStreamWriteChars(&s, "avif", 4); // unsigned int(32) major_brand;
- avifRWStreamWriteU32(&s, 0); // unsigned int(32) minor_version;
- avifRWStreamWriteChars(&s, "avif", 4); // unsigned int(32) compatible_brands[];
- avifRWStreamWriteChars(&s, "mif1", 4); // ... compatible_brands[]
- avifRWStreamWriteChars(&s, "miaf", 4); // ... compatible_brands[]
- if ((image->depth == 8) || (image->depth == 10)) { //
- if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420) { //
- avifRWStreamWriteChars(&s, "MA1B", 4); // ... compatible_brands[]
- } else if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) { //
- avifRWStreamWriteChars(&s, "MA1A", 4); // ... compatible_brands[]
+ avifRWStreamWriteChars(&s, "avif", 4); // unsigned int(32) major_brand;
+ avifRWStreamWriteU32(&s, 0); // unsigned int(32) minor_version;
+ avifRWStreamWriteChars(&s, "avif", 4); // unsigned int(32) compatible_brands[];
+ avifRWStreamWriteChars(&s, "mif1", 4); // ... compatible_brands[]
+ avifRWStreamWriteChars(&s, "miaf", 4); // ... compatible_brands[]
+ if ((imageMetadata->depth == 8) || (imageMetadata->depth == 10)) { //
+ if (imageMetadata->yuvFormat == AVIF_PIXEL_FORMAT_YUV420) { //
+ avifRWStreamWriteChars(&s, "MA1B", 4); // ... compatible_brands[]
+ } else if (imageMetadata->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) { //
+ avifRWStreamWriteChars(&s, "MA1A", 4); // ... compatible_brands[]
}
}
avifRWStreamFinishBox(&s, ftyp);
@@ -362,7 +391,7 @@
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
memset(&item->ipma, 0, sizeof(item->ipma));
- if (!item->image || !item->codec) {
+ if (!item->codec) {
// No ipma to write for this item
continue;
}
@@ -370,8 +399,8 @@
// Properties all av01 items need
avifBoxMarker ispe = avifRWStreamWriteBox(&s, "ispe", 0, 0);
- avifRWStreamWriteU32(&s, item->image->width); // unsigned int(32) image_width;
- avifRWStreamWriteU32(&s, item->image->height); // unsigned int(32) image_height;
+ avifRWStreamWriteU32(&s, imageMetadata->width); // unsigned int(32) image_width;
+ avifRWStreamWriteU32(&s, imageMetadata->height); // unsigned int(32) image_height;
avifRWStreamFinishBox(&s, ispe);
ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_FALSE); // ipma is 1-indexed, doing this afterwards is correct
@@ -379,7 +408,7 @@
avifBoxMarker pixi = avifRWStreamWriteBox(&s, "pixi", 0, 0);
avifRWStreamWriteU8(&s, channelCount); // unsigned int (8) num_channels;
for (uint8_t chan = 0; chan < channelCount; ++chan) {
- avifRWStreamWriteU8(&s, (uint8_t)item->image->depth); // unsigned int (8) bits_per_channel;
+ avifRWStreamWriteU8(&s, (uint8_t)imageMetadata->depth); // unsigned int (8) bits_per_channel;
}
avifRWStreamFinishBox(&s, pixi);
ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_FALSE);
@@ -397,55 +426,55 @@
} else {
// Color specific properties
- if (item->image->icc.data && (item->image->icc.size > 0)) {
+ if (imageMetadata->icc.data && (imageMetadata->icc.size > 0)) {
avifBoxMarker colr = avifRWStreamWriteBox(&s, "colr", -1, 0);
avifRWStreamWriteChars(&s, "prof", 4); // unsigned int(32) colour_type;
- avifRWStreamWrite(&s, item->image->icc.data, item->image->icc.size);
+ avifRWStreamWrite(&s, imageMetadata->icc.data, imageMetadata->icc.size);
avifRWStreamFinishBox(&s, colr);
ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_FALSE);
} else {
avifBoxMarker colr = avifRWStreamWriteBox(&s, "colr", -1, 0);
- avifRWStreamWriteChars(&s, "nclx", 4); // unsigned int(32) colour_type;
- avifRWStreamWriteU16(&s, (uint16_t)item->image->colorPrimaries); // unsigned int(16) colour_primaries;
- avifRWStreamWriteU16(&s, (uint16_t)item->image->transferCharacteristics); // unsigned int(16) transfer_characteristics;
- avifRWStreamWriteU16(&s, (uint16_t)item->image->matrixCoefficients); // unsigned int(16) matrix_coefficients;
- avifRWStreamWriteU8(&s, (item->image->yuvRange == AVIF_RANGE_FULL) ? 0x80 : 0); // unsigned int(1) full_range_flag;
- // unsigned int(7) reserved = 0;
+ avifRWStreamWriteChars(&s, "nclx", 4); // unsigned int(32) colour_type;
+ avifRWStreamWriteU16(&s, (uint16_t)imageMetadata->colorPrimaries); // unsigned int(16) colour_primaries;
+ avifRWStreamWriteU16(&s, (uint16_t)imageMetadata->transferCharacteristics); // unsigned int(16) transfer_characteristics;
+ avifRWStreamWriteU16(&s, (uint16_t)imageMetadata->matrixCoefficients); // unsigned int(16) matrix_coefficients;
+ avifRWStreamWriteU8(&s, (imageMetadata->yuvRange == AVIF_RANGE_FULL) ? 0x80 : 0); // unsigned int(1) full_range_flag;
+ // unsigned int(7) reserved = 0;
avifRWStreamFinishBox(&s, colr);
ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_FALSE);
}
// Write (Optional) Transformations
- if (item->image->transformFlags & AVIF_TRANSFORM_PASP) {
+ if (imageMetadata->transformFlags & AVIF_TRANSFORM_PASP) {
avifBoxMarker pasp = avifRWStreamWriteBox(&s, "pasp", -1, 0);
- avifRWStreamWriteU32(&s, item->image->pasp.hSpacing); // unsigned int(32) hSpacing;
- avifRWStreamWriteU32(&s, item->image->pasp.vSpacing); // unsigned int(32) vSpacing;
+ avifRWStreamWriteU32(&s, imageMetadata->pasp.hSpacing); // unsigned int(32) hSpacing;
+ avifRWStreamWriteU32(&s, imageMetadata->pasp.vSpacing); // unsigned int(32) vSpacing;
avifRWStreamFinishBox(&s, pasp);
ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_FALSE);
}
- if (item->image->transformFlags & AVIF_TRANSFORM_CLAP) {
+ if (imageMetadata->transformFlags & AVIF_TRANSFORM_CLAP) {
avifBoxMarker clap = avifRWStreamWriteBox(&s, "clap", -1, 0);
- avifRWStreamWriteU32(&s, item->image->clap.widthN); // unsigned int(32) cleanApertureWidthN;
- avifRWStreamWriteU32(&s, item->image->clap.widthD); // unsigned int(32) cleanApertureWidthD;
- avifRWStreamWriteU32(&s, item->image->clap.heightN); // unsigned int(32) cleanApertureHeightN;
- avifRWStreamWriteU32(&s, item->image->clap.heightD); // unsigned int(32) cleanApertureHeightD;
- avifRWStreamWriteU32(&s, item->image->clap.horizOffN); // unsigned int(32) horizOffN;
- avifRWStreamWriteU32(&s, item->image->clap.horizOffD); // unsigned int(32) horizOffD;
- avifRWStreamWriteU32(&s, item->image->clap.vertOffN); // unsigned int(32) vertOffN;
- avifRWStreamWriteU32(&s, item->image->clap.vertOffD); // unsigned int(32) vertOffD;
+ avifRWStreamWriteU32(&s, imageMetadata->clap.widthN); // unsigned int(32) cleanApertureWidthN;
+ avifRWStreamWriteU32(&s, imageMetadata->clap.widthD); // unsigned int(32) cleanApertureWidthD;
+ avifRWStreamWriteU32(&s, imageMetadata->clap.heightN); // unsigned int(32) cleanApertureHeightN;
+ avifRWStreamWriteU32(&s, imageMetadata->clap.heightD); // unsigned int(32) cleanApertureHeightD;
+ avifRWStreamWriteU32(&s, imageMetadata->clap.horizOffN); // unsigned int(32) horizOffN;
+ avifRWStreamWriteU32(&s, imageMetadata->clap.horizOffD); // unsigned int(32) horizOffD;
+ avifRWStreamWriteU32(&s, imageMetadata->clap.vertOffN); // unsigned int(32) vertOffN;
+ avifRWStreamWriteU32(&s, imageMetadata->clap.vertOffD); // unsigned int(32) vertOffD;
avifRWStreamFinishBox(&s, clap);
ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_TRUE);
}
- if (item->image->transformFlags & AVIF_TRANSFORM_IROT) {
+ if (imageMetadata->transformFlags & AVIF_TRANSFORM_IROT) {
avifBoxMarker irot = avifRWStreamWriteBox(&s, "irot", -1, 0);
- uint8_t angle = item->image->irot.angle & 0x3;
+ uint8_t angle = imageMetadata->irot.angle & 0x3;
avifRWStreamWrite(&s, &angle, 1); // unsigned int (6) reserved = 0; unsigned int (2) angle;
avifRWStreamFinishBox(&s, irot);
ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_TRUE);
}
- if (item->image->transformFlags & AVIF_TRANSFORM_IMIR) {
+ if (imageMetadata->transformFlags & AVIF_TRANSFORM_IMIR) {
avifBoxMarker imir = avifRWStreamWriteBox(&s, "imir", -1, 0);
- uint8_t axis = item->image->imir.axis & 0x1;
+ uint8_t axis = imageMetadata->imir.axis & 0x1;
avifRWStreamWrite(&s, &axis, 1); // unsigned int (7) reserved = 0; unsigned int (1) axis;
avifRWStreamFinishBox(&s, imir);
ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_TRUE);
@@ -518,13 +547,16 @@
avifRWStreamFinishWrite(&s);
- // -----------------------------------------------------------------------
- // Set result and cleanup
+ return AVIF_RESULT_OK;
+}
- result = AVIF_RESULT_OK;
-
-writeCleanup:
- return result;
+avifResult avifEncoderWrite(avifEncoder * encoder, const avifImage * image, avifRWData * output)
+{
+ avifResult addImageResult = avifEncoderAddImage(encoder, image);
+ if (addImageResult != AVIF_RESULT_OK) {
+ return addImageResult;
+ }
+ return avifEncoderFinish(encoder, output);
}
static avifBool avifImageIsOpaque(const avifImage * image)