Initial commit (basic functionality, not feature complete)
diff --git a/src/write.c b/src/write.c
new file mode 100644
index 0000000..a3f0f6f
--- /dev/null
+++ b/src/write.c
@@ -0,0 +1,287 @@
+// Copyright 2019 Joe Drago. All rights reserved.
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include "avif/internal.h"
+
+#include <string.h>
+
+#include "aom/aom_encoder.h"
+#include "aom/aomcx.h"
+
+static avifBool encodeOBU(avifImage * image, avifBool alphaOnly, avifRawData * outputOBU, int quality);
+static avifBool avifImageIsOpaque(avifImage * image);
+
+avifResult avifImageWrite(avifImage * image, avifRawData * output, int quality)
+{
+ avifResult result = AVIF_RESULT_UNKNOWN_ERROR;
+ avifRawData colorOBU = AVIF_RAW_DATA_EMPTY;
+ avifRawData alphaOBU = AVIF_RAW_DATA_EMPTY;
+
+ avifStream s;
+ avifStreamStart(&s, output);
+
+ // -----------------------------------------------------------------------
+ // Reformat pixels, if need be
+
+ avifPixelFormat dstPixelFormat = image->pixelFormat;
+ if (image->pixelFormat == AVIF_PIXEL_FORMAT_RGBA) {
+ // AV1 doesn't support RGB, reformat
+ dstPixelFormat = AVIF_PIXEL_FORMAT_YUV444;
+ }
+
+#if 0 // TODO: implement choice in depth
+ int dstDepth = AVIF_CLAMP(image->depth, 8, 12);
+ if ((dstDepth == 9) || (dstDepth == 11)) {
+ ++dstDepth;
+ }
+#else
+ int dstDepth = 12;
+#endif
+
+ avifImage * pixelImage = image;
+ avifImage * reformattedImage = NULL;
+ if ((image->pixelFormat != dstPixelFormat) || (image->depth != dstDepth)) {
+ reformattedImage = avifImageCreate();
+ avifResult reformatResult = avifImageReformatPixels(image, reformattedImage, dstPixelFormat, dstDepth);
+ if (reformatResult != AVIF_RESULT_OK) {
+ result = reformatResult;
+ goto writeCleanup;
+ }
+ pixelImage = reformattedImage;
+ }
+
+ // -----------------------------------------------------------------------
+ // Encode AV1 OBUs
+
+ if (!encodeOBU(pixelImage, AVIF_FALSE, &colorOBU, quality)) {
+ result = AVIF_RESULT_ENCODE_COLOR_FAILED;
+ goto writeCleanup;
+ }
+
+ // Skip alpha creation on opaque images
+ avifBool hasAlpha = AVIF_FALSE;
+ if (!avifImageIsOpaque(pixelImage)) {
+ if (!encodeOBU(pixelImage, AVIF_TRUE, &alphaOBU, quality)) {
+ result = AVIF_RESULT_ENCODE_ALPHA_FAILED;
+ goto writeCleanup;
+ }
+ hasAlpha = AVIF_TRUE;
+ }
+
+ // -----------------------------------------------------------------------
+ // Write ftyp
+
+ avifBoxMarker ftyp = avifStreamWriteBox(&s, "ftyp", -1, 0);
+ avifStreamWrite(&s, "mif1", 4); // unsigned int(32) major_brand;
+ avifStreamWriteU32(&s, 0); // unsigned int(32) minor_version;
+ avifStreamWrite(&s, "mif1", 4); // unsigned int(32) compatible_brands[];
+ avifStreamWrite(&s, "avif", 4); // ... compatible_brands[]
+ avifStreamWrite(&s, "miaf", 4); // ... compatible_brands[]
+ avifStreamFinishBox(&s, ftyp);
+
+ // -----------------------------------------------------------------------
+ // Write mdat
+
+ avifBoxMarker mdat = avifStreamWriteBox(&s, "mdat", -1, 0);
+ uint32_t colorOBUOffset = (uint32_t)s.offset;
+ avifStreamWrite(&s, colorOBU.data, colorOBU.size);
+ uint32_t alphaOBUOffset = (uint32_t)s.offset;
+ avifStreamWrite(&s, alphaOBU.data, alphaOBU.size);
+ avifStreamFinishBox(&s, mdat);
+
+ // -----------------------------------------------------------------------
+ // Start meta
+
+ avifBoxMarker meta = avifStreamWriteBox(&s, "meta", 0, 0);
+
+ // -----------------------------------------------------------------------
+ // Write hdlr
+
+ avifBoxMarker hdlr = avifStreamWriteBox(&s, "hdlr", 0, 0);
+ avifStreamWriteU32(&s, 0); // unsigned int(32) pre_defined = 0;
+ avifStreamWrite(&s, "pict", 4); // unsigned int(32) handler_type;
+ avifStreamWriteZeros(&s, 12); // const unsigned int(32)[3] reserved = 0;
+ avifStreamWrite(&s, "libavif", 8); // string name; (writing null terminator)
+ avifStreamFinishBox(&s, hdlr);
+
+ // -----------------------------------------------------------------------
+ // Write pitm
+
+ avifStreamWriteBox(&s, "pitm", 0, sizeof(uint16_t));
+ avifStreamWriteU16(&s, 1); // unsigned int(16) item_ID;
+
+ // -----------------------------------------------------------------------
+ // Write iloc
+
+ avifBoxMarker iloc = avifStreamWriteBox(&s, "iloc", 0, 0);
+
+ // iloc header
+ uint8_t offsetSizeAndLengthSize = (4 << 4) + (4 << 0); // unsigned int(4) offset_size; unsigned int(4) length_size;
+ avifStreamWrite(&s, &offsetSizeAndLengthSize, 1); //
+ avifStreamWriteZeros(&s, 1); // unsigned int(4) base_offset_size; unsigned int(4) reserved;
+ avifStreamWriteU16(&s, hasAlpha ? 2 : 1); // unsigned int(16) item_count;
+
+ // Item ID #1 (Color OBU)
+ avifStreamWriteU16(&s, 1); // unsigned int(16) item_ID;
+ avifStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index;
+ avifStreamWriteU16(&s, 1); // unsigned int(16) extent_count;
+ avifStreamWriteU32(&s, colorOBUOffset); // unsigned int(offset_size*8) extent_offset;
+ avifStreamWriteU32(&s, (uint32_t)colorOBU.size); // unsigned int(length_size*8) extent_length;
+
+ if (hasAlpha) {
+ avifStreamWriteU16(&s, 2); // unsigned int(16) item_ID;
+ avifStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index;
+ avifStreamWriteU16(&s, 1); // unsigned int(16) extent_count;
+ avifStreamWriteU32(&s, alphaOBUOffset); // unsigned int(offset_size*8) extent_offset;
+ avifStreamWriteU32(&s, (uint32_t)alphaOBU.size); // unsigned int(length_size*8) extent_length;
+ }
+
+ avifStreamFinishBox(&s, iloc);
+
+ // -----------------------------------------------------------------------
+ // Write iinf
+
+ avifBoxMarker iinf = avifStreamWriteBox(&s, "iinf", 0, 0);
+ avifStreamWriteU16(&s, hasAlpha ? 2 : 1); // unsigned int(16) entry_count;
+
+ avifBoxMarker infe0 = avifStreamWriteBox(&s, "infe", 2, 0);
+ avifStreamWriteU16(&s, 1); // unsigned int(16) item_ID;
+ avifStreamWriteU16(&s, 0); // unsigned int(16) item_protection_index;
+ avifStreamWrite(&s, "av01", 4); // unsigned int(32) item_type;
+ avifStreamWrite(&s, "Color", 6); // string item_name; (writing null terminator)
+ avifStreamFinishBox(&s, infe0);
+ if (hasAlpha) {
+ avifBoxMarker infe1 = avifStreamWriteBox(&s, "infe", 2, 0);
+ avifStreamWriteU16(&s, 2); // unsigned int(16) item_ID;
+ avifStreamWriteU16(&s, 0); // unsigned int(16) item_protection_index;
+ avifStreamWrite(&s, "av01", 4); // unsigned int(32) item_type;
+ avifStreamWrite(&s, "Alpha", 6); // string item_name; (writing null terminator)
+ avifStreamFinishBox(&s, infe1);
+ }
+ avifStreamFinishBox(&s, iinf);
+
+ // -----------------------------------------------------------------------
+ // Write iprp->ipco->ispe
+
+ avifBoxMarker iprp = avifStreamWriteBox(&s, "iprp", -1, 0);
+ avifBoxMarker ipco = avifStreamWriteBox(&s, "ipco", -1, 0);
+ avifBoxMarker ispe = avifStreamWriteBox(&s, "ispe", 0, 0);
+ avifStreamWriteU32(&s, image->width); // unsigned int(32) image_width;
+ avifStreamWriteU32(&s, image->height); // unsigned int(32) image_height;
+ avifStreamFinishBox(&s, ispe);
+ avifStreamFinishBox(&s, ipco);
+ avifStreamFinishBox(&s, iprp);
+
+ // -----------------------------------------------------------------------
+ // Finish up stream
+
+ avifStreamFinishBox(&s, meta);
+ avifStreamFinishWrite(&s);
+ result = AVIF_RESULT_OK;
+
+ // -----------------------------------------------------------------------
+ // Cleanup
+
+writeCleanup:
+ if (reformattedImage) {
+ avifImageDestroy(reformattedImage);
+ }
+ avifRawDataFree(&colorOBU);
+ avifRawDataFree(&alphaOBU);
+ return result;
+}
+
+static avifBool encodeOBU(avifImage * image, avifBool alphaOnly, avifRawData * outputOBU, int quality)
+{
+ avifBool success = AVIF_FALSE;
+ aom_codec_iface_t * encoder_interface = aom_codec_av1_cx();
+ aom_codec_ctx_t encoder;
+
+ struct aom_codec_enc_cfg cfg;
+ aom_codec_enc_config_default(encoder_interface, &cfg, 0);
+
+ // Profile 2. 8-bit and 10-bit 4:2:2
+ // 12-bit 4:0:0, 4:2:2 and 4:4:4
+ cfg.g_profile = 2;
+ cfg.g_bit_depth = AOM_BITS_12;
+ cfg.g_input_bit_depth = 12;
+ cfg.g_w = image->width;
+ cfg.g_h = image->height;
+ // cfg.g_threads = ...;
+
+ avifBool lossless = (quality == 0) || (quality == 100) || alphaOnly; // alpha is always lossless
+ cfg.rc_min_quantizer = 0;
+ if (lossless) {
+ cfg.rc_max_quantizer = 0;
+ } else {
+ cfg.rc_max_quantizer = quality;
+ }
+
+ aom_codec_enc_init(&encoder, encoder_interface, &cfg, AOM_CODEC_USE_HIGHBITDEPTH);
+ aom_codec_control(&encoder, AV1E_SET_COLOR_RANGE, AOM_CR_FULL_RANGE);
+ if (lossless) {
+ aom_codec_control(&encoder, AV1E_SET_LOSSLESS, 1);
+ }
+
+ aom_image_t * aomImage = aom_img_alloc(NULL, AOM_IMG_FMT_I44416, image->width, image->height, 16);
+ aomImage->range = AOM_CR_FULL_RANGE; // always use full range
+ if (alphaOnly) {
+ }
+
+ if (alphaOnly) {
+ aomImage->monochrome = 1;
+ for (int j = 0; j < image->height; ++j) {
+ for (int i = 0; i < image->width; ++i) {
+ for (int plane = 0; plane < 3; ++plane) {
+ uint16_t * planeChannel = (uint16_t *)&aomImage->planes[plane][(j * aomImage->stride[plane]) + (2 * i)];
+ if (plane == 0) {
+ *planeChannel = image->planes[3][i + (j * image->strides[plane])];
+ } else {
+ *planeChannel = 0;
+ }
+ }
+ }
+ }
+ } else {
+ for (int j = 0; j < image->height; ++j) {
+ for (int i = 0; i < image->width; ++i) {
+ for (int plane = 0; plane < 3; ++plane) {
+ uint16_t * planeChannel = (uint16_t *)&aomImage->planes[plane][(j * aomImage->stride[plane]) + (2 * i)];
+ *planeChannel = image->planes[plane][i + (j * image->strides[plane])];
+ }
+ }
+ }
+ }
+
+ aom_codec_encode(&encoder, aomImage, 0, 1, 0);
+ aom_codec_encode(&encoder, NULL, 0, 1, 0); // flush
+
+ aom_codec_iter_t iter = NULL;
+ for (;;) {
+ const aom_codec_cx_pkt_t * pkt = aom_codec_get_cx_data(&encoder, &iter);
+ if (pkt == NULL)
+ break;
+ if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) {
+ avifRawDataSet(outputOBU, pkt->data.frame.buf, pkt->data.frame.sz);
+ success = AVIF_TRUE;
+ break;
+ }
+ }
+
+ aom_img_free(aomImage);
+ aom_codec_destroy(&encoder);
+ return success;
+}
+
+static avifBool avifImageIsOpaque(avifImage * image)
+{
+ int maxChannel = (1 << image->depth) - 1;
+ for (int j = 0; j < image->height; ++j) {
+ for (int i = 0; i < image->width; ++i) {
+ if (image->planes[3][i + (j * image->strides[3])] != maxChannel) {
+ return AVIF_FALSE;
+ }
+ }
+ }
+ return AVIF_TRUE;
+}