| // Copyright 2019 Joe Drago. All rights reserved. |
| // SPDX-License-Identifier: BSD-2-Clause |
| |
| #include "avif/internal.h" |
| |
| #include <string.h> |
| |
| #define MAX_ASSOCIATIONS 16 |
| struct ipmaArray |
| { |
| uint8_t associations[MAX_ASSOCIATIONS]; |
| uint8_t count; |
| }; |
| static void ipmaPush(struct ipmaArray * ipma, uint8_t assoc) |
| { |
| ipma->associations[ipma->count] = assoc; |
| ++ipma->count; |
| } |
| |
| static const char alphaURN[] = URN_ALPHA0; |
| static const size_t alphaURNSize = sizeof(alphaURN); |
| |
| static const char xmpContentType[] = CONTENT_TYPE_XMP; |
| static const size_t xmpContentTypeSize = sizeof(xmpContentType); |
| |
| static avifBool avifImageIsOpaque(avifImage * image); |
| static void fillConfigBox(avifCodec * codec, avifImage * image, avifBool alpha); |
| static void writeConfigBox(avifRWStream * s, avifCodecConfigurationBox * cfg); |
| |
| avifEncoder * avifEncoderCreate(void) |
| { |
| avifEncoder * encoder = (avifEncoder *)avifAlloc(sizeof(avifEncoder)); |
| memset(encoder, 0, sizeof(avifEncoder)); |
| encoder->maxThreads = 1; |
| encoder->minQuantizer = AVIF_QUANTIZER_LOSSLESS; |
| encoder->maxQuantizer = AVIF_QUANTIZER_LOSSLESS; |
| encoder->tileRowsLog2 = 0; |
| encoder->tileColsLog2 = 0; |
| encoder->speed = AVIF_SPEED_DEFAULT; |
| return encoder; |
| } |
| |
| void avifEncoderDestroy(avifEncoder * encoder) |
| { |
| avifFree(encoder); |
| } |
| |
| avifResult avifEncoderWrite(avifEncoder * encoder, avifImage * image, avifRWData * output) |
| { |
| if ((image->depth != 8) && (image->depth != 10) && (image->depth != 12)) { |
| return AVIF_RESULT_UNSUPPORTED_DEPTH; |
| } |
| |
| avifResult result = AVIF_RESULT_UNKNOWN_ERROR; |
| avifRWData colorOBU = AVIF_DATA_EMPTY; |
| avifRWData alphaOBU = AVIF_DATA_EMPTY; |
| avifCodec * codec[AVIF_CODEC_PLANES_COUNT]; |
| |
| codec[AVIF_CODEC_PLANES_COLOR] = avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE); |
| if (!codec[AVIF_CODEC_PLANES_COLOR]) { |
| // Just bail out early, we're not surviving this function without an encoder compiled in |
| return AVIF_RESULT_NO_CODEC_AVAILABLE; |
| } |
| |
| avifBool imageIsOpaque = avifImageIsOpaque(image); |
| if (imageIsOpaque) { |
| codec[AVIF_CODEC_PLANES_ALPHA] = NULL; |
| } else { |
| codec[AVIF_CODEC_PLANES_ALPHA] = avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE); |
| if (!codec[AVIF_CODEC_PLANES_ALPHA]) { |
| return AVIF_RESULT_NO_CODEC_AVAILABLE; |
| } |
| } |
| |
| // ----------------------------------------------------------------------- |
| // 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; |
| } |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Pre-fill config boxes based on image (codec can query/update later) |
| |
| fillConfigBox(codec[AVIF_CODEC_PLANES_COLOR], image, AVIF_FALSE); |
| if (codec[AVIF_CODEC_PLANES_ALPHA]) { |
| fillConfigBox(codec[AVIF_CODEC_PLANES_ALPHA], image, AVIF_TRUE); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Begin write stream |
| |
| avifRWStream s; |
| avifRWStreamStart(&s, output); |
| |
| // ----------------------------------------------------------------------- |
| // Reformat pixels, if need be |
| |
| if (!image->width || !image->height || !image->depth) { |
| result = AVIF_RESULT_NO_CONTENT; |
| goto writeCleanup; |
| } |
| |
| if ((image->yuvFormat == AVIF_PIXEL_FORMAT_NONE) || !image->yuvPlanes[AVIF_CHAN_Y] || !image->yuvPlanes[AVIF_CHAN_U] || |
| !image->yuvPlanes[AVIF_CHAN_V]) { |
| if (!image->rgbPlanes[AVIF_CHAN_R] || !image->rgbPlanes[AVIF_CHAN_G] || !image->rgbPlanes[AVIF_CHAN_B]) { |
| result = AVIF_RESULT_NO_CONTENT; |
| goto writeCleanup; |
| } |
| |
| avifImageFreePlanes(image, AVIF_PLANES_YUV); |
| if (image->yuvFormat == AVIF_PIXEL_FORMAT_NONE) { |
| result = AVIF_RESULT_NO_YUV_FORMAT_SELECTED; |
| goto writeCleanup; |
| } |
| avifImageRGBToYUV(image); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Encode AV1 OBUs |
| |
| if (!codec[AVIF_CODEC_PLANES_COLOR]->encodeImage(codec[AVIF_CODEC_PLANES_COLOR], image, encoder, &colorOBU, AVIF_FALSE)) { |
| result = AVIF_RESULT_ENCODE_COLOR_FAILED; |
| goto writeCleanup; |
| } |
| |
| if (!imageIsOpaque) { |
| if (!codec[AVIF_CODEC_PLANES_ALPHA]->encodeImage(codec[AVIF_CODEC_PLANES_ALPHA], image, encoder, &alphaOBU, AVIF_TRUE)) { |
| result = AVIF_RESULT_ENCODE_ALPHA_FAILED; |
| goto writeCleanup; |
| } |
| } |
| |
| // TODO: consider collapsing all items into local structs for iteration / code sharing |
| avifBool hasAlpha = (alphaOBU.size > 0) ? AVIF_TRUE : AVIF_FALSE; |
| avifBool hasExif = (image->exif.size > 0) ? AVIF_TRUE : AVIF_FALSE; |
| avifBool hasXMP = (image->xmp.size > 0) ? AVIF_TRUE : AVIF_FALSE; |
| |
| // ----------------------------------------------------------------------- |
| // 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[] |
| } |
| } |
| avifRWStreamFinishBox(&s, ftyp); |
| |
| // ----------------------------------------------------------------------- |
| // Start meta |
| |
| avifBoxMarker meta = avifRWStreamWriteBox(&s, "meta", 0, 0); |
| |
| // ----------------------------------------------------------------------- |
| // Write hdlr |
| |
| avifBoxMarker hdlr = avifRWStreamWriteBox(&s, "hdlr", 0, 0); |
| avifRWStreamWriteU32(&s, 0); // unsigned int(32) pre_defined = 0; |
| avifRWStreamWriteChars(&s, "pict", 4); // unsigned int(32) handler_type; |
| avifRWStreamWriteZeros(&s, 12); // const unsigned int(32)[3] reserved = 0; |
| avifRWStreamWriteChars(&s, "libavif", 8); // string name; (writing null terminator) |
| avifRWStreamFinishBox(&s, hdlr); |
| |
| // ----------------------------------------------------------------------- |
| // Write pitm |
| |
| avifRWStreamWriteBox(&s, "pitm", 0, sizeof(uint16_t)); |
| avifRWStreamWriteU16(&s, 1); // unsigned int(16) item_ID; |
| |
| // ----------------------------------------------------------------------- |
| // Calculate item IDs and counts |
| |
| uint16_t itemCount = 1; |
| uint16_t colorItemID = 1; |
| uint16_t nextItemID = 2; |
| uint16_t alphaItemID = 0; |
| uint16_t exifItemID = 0; |
| uint16_t xmpItemID = 0; |
| if (hasAlpha) { |
| ++itemCount; |
| alphaItemID = nextItemID; |
| ++nextItemID; |
| } |
| if (hasExif) { |
| ++itemCount; |
| exifItemID = nextItemID; |
| ++nextItemID; |
| } |
| if (hasXMP) { |
| ++itemCount; |
| xmpItemID = nextItemID; |
| ++nextItemID; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Write iloc |
| |
| // Remember where we want to store the offsets to the mdat OBU offsets to adjust them later. |
| // These are named as such because they are remembering the stream offset where we will write an offset later. |
| size_t colorOBUOffsetOffset = 0; |
| size_t alphaOBUOffsetOffset = 0; |
| size_t exifOffsetOffset = 0; |
| size_t xmpOffsetOffset = 0; |
| |
| avifBoxMarker iloc = avifRWStreamWriteBox(&s, "iloc", 0, 0); |
| |
| // iloc header |
| uint8_t offsetSizeAndLengthSize = (4 << 4) + (4 << 0); // unsigned int(4) offset_size; |
| // unsigned int(4) length_size; |
| avifRWStreamWrite(&s, &offsetSizeAndLengthSize, 1); // |
| avifRWStreamWriteZeros(&s, 1); // unsigned int(4) base_offset_size; |
| // unsigned int(4) reserved; |
| avifRWStreamWriteU16(&s, itemCount); // unsigned int(16) item_count; |
| |
| // Item ID #1 (Color OBU) |
| avifRWStreamWriteU16(&s, colorItemID); // unsigned int(16) item_ID; |
| avifRWStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index; |
| avifRWStreamWriteU16(&s, 1); // unsigned int(16) extent_count; |
| colorOBUOffsetOffset = avifRWStreamOffset(&s); // |
| avifRWStreamWriteU32(&s, 0 /* set later */); // unsigned int(offset_size*8) extent_offset; |
| avifRWStreamWriteU32(&s, (uint32_t)colorOBU.size); // unsigned int(length_size*8) extent_length; |
| |
| if (hasAlpha) { |
| avifRWStreamWriteU16(&s, alphaItemID); // unsigned int(16) item_ID; |
| avifRWStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index; |
| avifRWStreamWriteU16(&s, 1); // unsigned int(16) extent_count; |
| alphaOBUOffsetOffset = avifRWStreamOffset(&s); // |
| avifRWStreamWriteU32(&s, 0 /* set later */); // unsigned int(offset_size*8) extent_offset; |
| avifRWStreamWriteU32(&s, (uint32_t)alphaOBU.size); // unsigned int(length_size*8) extent_length; |
| } |
| |
| if (hasExif) { |
| // From ISO/IEC 23008-12:2017 |
| // :: aligned(8) class ExifDataBlock() { |
| // :: unsigned int(32) exif_tiff_header_offset; |
| // :: unsigned int(8) exif_payload[]; |
| // :: } |
| uint32_t exifDataBlockSize = (uint32_t)(sizeof(uint32_t) + image->exif.size); |
| |
| avifRWStreamWriteU16(&s, exifItemID); // unsigned int(16) item_ID; |
| avifRWStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index; |
| avifRWStreamWriteU16(&s, 1); // unsigned int(16) extent_count; |
| exifOffsetOffset = avifRWStreamOffset(&s); // |
| avifRWStreamWriteU32(&s, 0 /* set later */); // unsigned int(offset_size*8) extent_offset; |
| avifRWStreamWriteU32(&s, exifDataBlockSize); // unsigned int(length_size*8) extent_length; |
| } |
| |
| if (hasXMP) { |
| avifRWStreamWriteU16(&s, xmpItemID); // unsigned int(16) item_ID; |
| avifRWStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index; |
| avifRWStreamWriteU16(&s, 1); // unsigned int(16) extent_count; |
| xmpOffsetOffset = avifRWStreamOffset(&s); // |
| avifRWStreamWriteU32(&s, 0 /* set later */); // unsigned int(offset_size*8) extent_offset; |
| avifRWStreamWriteU32(&s, (uint32_t)image->xmp.size); // unsigned int(length_size*8) extent_length; |
| } |
| |
| avifRWStreamFinishBox(&s, iloc); |
| |
| // ----------------------------------------------------------------------- |
| // Write iinf |
| |
| avifBoxMarker iinf = avifRWStreamWriteBox(&s, "iinf", 0, 0); |
| avifRWStreamWriteU16(&s, itemCount); // unsigned int(16) entry_count; |
| |
| avifBoxMarker infeImage = avifRWStreamWriteBox(&s, "infe", 2, 0); |
| avifRWStreamWriteU16(&s, colorItemID); // unsigned int(16) item_ID; |
| avifRWStreamWriteU16(&s, 0); // unsigned int(16) item_protection_index; |
| avifRWStreamWriteChars(&s, "av01", 4); // unsigned int(32) item_type; |
| avifRWStreamWriteChars(&s, "Color", 6); // string item_name; (writing null terminator) |
| avifRWStreamFinishBox(&s, infeImage); |
| if (hasAlpha) { |
| avifBoxMarker infeAlpha = avifRWStreamWriteBox(&s, "infe", 2, 0); |
| avifRWStreamWriteU16(&s, alphaItemID); // unsigned int(16) item_ID; |
| avifRWStreamWriteU16(&s, 0); // unsigned int(16) item_protection_index; |
| avifRWStreamWriteChars(&s, "av01", 4); // unsigned int(32) item_type; |
| avifRWStreamWriteChars(&s, "Alpha", 6); // string item_name; (writing null terminator) |
| avifRWStreamFinishBox(&s, infeAlpha); |
| } |
| if (hasExif) { |
| avifBoxMarker infeExif = avifRWStreamWriteBox(&s, "infe", 2, 0); |
| avifRWStreamWriteU16(&s, exifItemID); // unsigned int(16) item_ID; |
| avifRWStreamWriteU16(&s, 0); // unsigned int(16) item_protection_index; |
| avifRWStreamWriteChars(&s, "Exif", 4); // unsigned int(32) item_type; |
| avifRWStreamWriteChars(&s, "Exif", 5); // string item_name; (writing null terminator) |
| avifRWStreamFinishBox(&s, infeExif); |
| } |
| if (hasXMP) { |
| avifBoxMarker infeXMP = avifRWStreamWriteBox(&s, "infe", 2, 0); |
| avifRWStreamWriteU16(&s, xmpItemID); // unsigned int(16) item_ID; |
| avifRWStreamWriteU16(&s, 0); // unsigned int(16) item_protection_index; |
| avifRWStreamWriteChars(&s, "mime", 4); // unsigned int(32) item_type; |
| avifRWStreamWriteChars(&s, "XMP", 4); // string item_name; (writing null terminator) |
| avifRWStreamWriteChars(&s, xmpContentType, xmpContentTypeSize); // string content_type; (writing null terminator) |
| avifRWStreamFinishBox(&s, infeXMP); |
| } |
| avifRWStreamFinishBox(&s, iinf); |
| |
| // ----------------------------------------------------------------------- |
| // Write iref (auxl) for alpha, if any |
| |
| if (hasAlpha) { |
| avifBoxMarker iref = avifRWStreamWriteBox(&s, "iref", 0, 0); |
| avifBoxMarker auxl = avifRWStreamWriteBox(&s, "auxl", -1, 0); |
| avifRWStreamWriteU16(&s, alphaItemID); // unsigned int(16) from_item_ID; |
| avifRWStreamWriteU16(&s, 1); // unsigned int(16) reference_count; |
| avifRWStreamWriteU16(&s, colorItemID); // unsigned int(16) to_item_ID; |
| avifRWStreamFinishBox(&s, auxl); |
| avifRWStreamFinishBox(&s, iref); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Write iref (cdsc) for Exif, if any |
| |
| if (hasExif) { |
| avifBoxMarker iref = avifRWStreamWriteBox(&s, "iref", 0, 0); |
| avifBoxMarker cdsc = avifRWStreamWriteBox(&s, "cdsc", -1, 0); |
| avifRWStreamWriteU16(&s, exifItemID); // unsigned int(16) from_item_ID; |
| avifRWStreamWriteU16(&s, 1); // unsigned int(16) reference_count; |
| avifRWStreamWriteU16(&s, colorItemID); // unsigned int(16) to_item_ID; |
| avifRWStreamFinishBox(&s, cdsc); |
| avifRWStreamFinishBox(&s, iref); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Write iref (cdsc) for XMP, if any |
| |
| if (hasXMP) { |
| avifBoxMarker iref = avifRWStreamWriteBox(&s, "iref", 0, 0); |
| avifBoxMarker cdsc = avifRWStreamWriteBox(&s, "cdsc", -1, 0); |
| avifRWStreamWriteU16(&s, xmpItemID); // unsigned int(16) from_item_ID; |
| avifRWStreamWriteU16(&s, 1); // unsigned int(16) reference_count; |
| avifRWStreamWriteU16(&s, colorItemID); // unsigned int(16) to_item_ID; |
| avifRWStreamFinishBox(&s, cdsc); |
| avifRWStreamFinishBox(&s, iref); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Write iprp->ipco->ispe |
| |
| avifBoxMarker iprp = avifRWStreamWriteBox(&s, "iprp", -1, 0); |
| { |
| uint8_t ipcoIndex = 0; |
| struct ipmaArray ipmaColor; |
| memset(&ipmaColor, 0, sizeof(ipmaColor)); |
| struct ipmaArray ipmaAlpha; |
| memset(&ipmaAlpha, 0, sizeof(ipmaAlpha)); |
| |
| avifBoxMarker ipco = avifRWStreamWriteBox(&s, "ipco", -1, 0); |
| { |
| avifBoxMarker ispe = avifRWStreamWriteBox(&s, "ispe", 0, 0); |
| avifRWStreamWriteU32(&s, image->width); // unsigned int(32) image_width; |
| avifRWStreamWriteU32(&s, image->height); // unsigned int(32) image_height; |
| avifRWStreamFinishBox(&s, ispe); |
| ++ipcoIndex; |
| ipmaPush(&ipmaColor, ipcoIndex); // ipma is 1-indexed, doing this afterwards is correct |
| ipmaPush(&ipmaAlpha, ipcoIndex); // Alpha shares the ispe prop |
| |
| if (image->profileFormat == AVIF_PROFILE_FORMAT_NCLX) { |
| avifBoxMarker colr = avifRWStreamWriteBox(&s, "colr", -1, 0); |
| avifRWStreamWriteChars(&s, "nclx", 4); // unsigned int(32) colour_type; |
| avifRWStreamWriteU16(&s, image->nclx.colourPrimaries); // unsigned int(16) colour_primaries; |
| avifRWStreamWriteU16(&s, image->nclx.transferCharacteristics); // unsigned int(16) transfer_characteristics; |
| avifRWStreamWriteU16(&s, image->nclx.matrixCoefficients); // unsigned int(16) matrix_coefficients; |
| avifRWStreamWriteU8(&s, image->nclx.fullRangeFlag & 0x80); // unsigned int(1) full_range_flag; |
| // unsigned int(7) reserved = 0; |
| avifRWStreamFinishBox(&s, colr); |
| ++ipcoIndex; |
| ipmaPush(&ipmaColor, ipcoIndex); |
| } else if ((image->profileFormat == AVIF_PROFILE_FORMAT_ICC) && image->icc.data && (image->icc.size > 0)) { |
| avifBoxMarker colr = avifRWStreamWriteBox(&s, "colr", -1, 0); |
| avifRWStreamWriteChars(&s, "prof", 4); // unsigned int(32) colour_type; |
| avifRWStreamWrite(&s, image->icc.data, image->icc.size); |
| avifRWStreamFinishBox(&s, colr); |
| ++ipcoIndex; |
| ipmaPush(&ipmaColor, ipcoIndex); |
| } |
| |
| avifBoxMarker pixiC = avifRWStreamWriteBox(&s, "pixi", 0, 0); |
| avifRWStreamWriteU8(&s, 3); // unsigned int (8) num_channels; |
| avifRWStreamWriteU8(&s, (uint8_t)image->depth); // unsigned int (8) bits_per_channel; |
| avifRWStreamWriteU8(&s, (uint8_t)image->depth); // unsigned int (8) bits_per_channel; |
| avifRWStreamWriteU8(&s, (uint8_t)image->depth); // unsigned int (8) bits_per_channel; |
| avifRWStreamFinishBox(&s, pixiC); |
| ++ipcoIndex; |
| ipmaPush(&ipmaColor, ipcoIndex); |
| |
| writeConfigBox(&s, &codec[AVIF_CODEC_PLANES_COLOR]->configBox); |
| ++ipcoIndex; |
| ipmaPush(&ipmaColor, ipcoIndex); |
| |
| if (hasAlpha) { |
| avifBoxMarker pixiA = avifRWStreamWriteBox(&s, "pixi", 0, 0); |
| avifRWStreamWriteU8(&s, 1); // unsigned int (8) num_channels; |
| avifRWStreamWriteU8(&s, (uint8_t)image->depth); // unsigned int (8) bits_per_channel; |
| avifRWStreamFinishBox(&s, pixiA); |
| ++ipcoIndex; |
| ipmaPush(&ipmaAlpha, ipcoIndex); |
| |
| writeConfigBox(&s, &codec[AVIF_CODEC_PLANES_ALPHA]->configBox); |
| ++ipcoIndex; |
| ipmaPush(&ipmaAlpha, ipcoIndex); |
| |
| avifBoxMarker auxC = avifRWStreamWriteBox(&s, "auxC", 0, 0); |
| avifRWStreamWriteChars(&s, alphaURN, alphaURNSize); // string aux_type; |
| avifRWStreamFinishBox(&s, auxC); |
| ++ipcoIndex; |
| ipmaPush(&ipmaAlpha, ipcoIndex); |
| } |
| } |
| avifRWStreamFinishBox(&s, ipco); |
| |
| avifBoxMarker ipma = avifRWStreamWriteBox(&s, "ipma", 0, 0); |
| { |
| int ipmaCount = hasAlpha ? 2 : 1; |
| avifRWStreamWriteU32(&s, ipmaCount); // unsigned int(32) entry_count; |
| |
| avifRWStreamWriteU16(&s, 1); // unsigned int(16) item_ID; |
| avifRWStreamWriteU8(&s, ipmaColor.count); // unsigned int(8) association_count; |
| for (int i = 0; i < ipmaColor.count; ++i) { // |
| avifRWStreamWriteU8(&s, ipmaColor.associations[i]); // bit(1) essential; unsigned int(7) property_index; |
| } |
| |
| if (hasAlpha) { |
| avifRWStreamWriteU16(&s, 2); // unsigned int(16) item_ID; |
| avifRWStreamWriteU8(&s, ipmaAlpha.count); // unsigned int(8) association_count; |
| for (int i = 0; i < ipmaAlpha.count; ++i) { // |
| avifRWStreamWriteU8(&s, ipmaAlpha.associations[i]); // bit(1) essential; unsigned int(7) property_index; |
| } |
| } |
| } |
| avifRWStreamFinishBox(&s, ipma); |
| } |
| avifRWStreamFinishBox(&s, iprp); |
| |
| // ----------------------------------------------------------------------- |
| // Finish meta box |
| |
| avifRWStreamFinishBox(&s, meta); |
| |
| // ----------------------------------------------------------------------- |
| // Write mdat |
| |
| avifBoxMarker mdat = avifRWStreamWriteBox(&s, "mdat", -1, 0); |
| uint32_t colorOBUOffset = (uint32_t)s.offset; |
| avifRWStreamWrite(&s, colorOBU.data, colorOBU.size); |
| uint32_t alphaOBUOffset = (uint32_t)s.offset; |
| avifRWStreamWrite(&s, alphaOBU.data, alphaOBU.size); |
| uint32_t exifOffset = (uint32_t)s.offset; |
| if (image->exif.size > 0) { |
| avifRWStreamWriteU32(&s, (uint32_t)exifTiffHeaderOffset); // unsigned int(32) exif_tiff_header_offset; (Annex A.2.1) |
| avifRWStreamWrite(&s, image->exif.data, image->exif.size); |
| } |
| uint32_t xmpOffset = (uint32_t)s.offset; |
| avifRWStreamWrite(&s, image->xmp.data, image->xmp.size); |
| avifRWStreamFinishBox(&s, mdat); |
| |
| // ----------------------------------------------------------------------- |
| // Finish up stream |
| |
| // Set offsets needed in meta box based on where we eventually wrote mdat |
| size_t prevOffset = avifRWStreamOffset(&s); |
| if (colorOBUOffsetOffset != 0) { |
| avifRWStreamSetOffset(&s, colorOBUOffsetOffset); |
| avifRWStreamWriteU32(&s, colorOBUOffset); |
| } |
| if (alphaOBUOffsetOffset != 0) { |
| avifRWStreamSetOffset(&s, alphaOBUOffsetOffset); |
| avifRWStreamWriteU32(&s, alphaOBUOffset); |
| } |
| if (exifOffsetOffset != 0) { |
| avifRWStreamSetOffset(&s, exifOffsetOffset); |
| avifRWStreamWriteU32(&s, exifOffset); |
| } |
| if (xmpOffsetOffset != 0) { |
| avifRWStreamSetOffset(&s, xmpOffsetOffset); |
| avifRWStreamWriteU32(&s, xmpOffset); |
| } |
| avifRWStreamSetOffset(&s, prevOffset); |
| |
| // Close write stream |
| avifRWStreamFinishWrite(&s); |
| |
| // ----------------------------------------------------------------------- |
| // IO stats |
| |
| encoder->ioStats.colorOBUSize = colorOBU.size; |
| encoder->ioStats.alphaOBUSize = alphaOBU.size; |
| |
| // ----------------------------------------------------------------------- |
| // Set result and cleanup |
| |
| result = AVIF_RESULT_OK; |
| |
| writeCleanup: |
| if (codec[AVIF_CODEC_PLANES_COLOR]) { |
| avifCodecDestroy(codec[AVIF_CODEC_PLANES_COLOR]); |
| } |
| if (codec[AVIF_CODEC_PLANES_ALPHA]) { |
| avifCodecDestroy(codec[AVIF_CODEC_PLANES_ALPHA]); |
| } |
| avifRWDataFree(&colorOBU); |
| avifRWDataFree(&alphaOBU); |
| return result; |
| } |
| |
| static avifBool avifImageIsOpaque(avifImage * image) |
| { |
| if (!image->alphaPlane) { |
| return AVIF_TRUE; |
| } |
| |
| int maxChannel = (1 << image->depth) - 1; |
| if (avifImageUsesU16(image)) { |
| for (uint32_t j = 0; j < image->height; ++j) { |
| for (uint32_t i = 0; i < image->width; ++i) { |
| uint16_t * p = (uint16_t *)&image->alphaPlane[(i * 2) + (j * image->alphaRowBytes)]; |
| if (*p != maxChannel) { |
| return AVIF_FALSE; |
| } |
| } |
| } |
| } else { |
| for (uint32_t j = 0; j < image->height; ++j) { |
| for (uint32_t i = 0; i < image->width; ++i) { |
| if (image->alphaPlane[i + (j * image->alphaRowBytes)] != maxChannel) { |
| return AVIF_FALSE; |
| } |
| } |
| } |
| } |
| return AVIF_TRUE; |
| } |
| |
| static void fillConfigBox(avifCodec * codec, avifImage * image, avifBool alpha) |
| { |
| avifPixelFormatInfo formatInfo; |
| avifGetPixelFormatInfo(image->yuvFormat, &formatInfo); |
| |
| // Profile 0. 8-bit and 10-bit 4:2:0 and 4:0:0 only. |
| // Profile 1. 8-bit and 10-bit 4:4:4 |
| // Profile 2. 8-bit and 10-bit 4:2:2 |
| // 12-bit 4:0:0, 4:2:2 and 4:4:4 |
| uint8_t seqProfile = 0; |
| if (image->depth == 12) { |
| // Only seqProfile 2 can handle 12 bit |
| seqProfile = 2; |
| } else { |
| // 8-bit or 10-bit |
| |
| if (alpha) { |
| seqProfile = 0; |
| } else { |
| switch (image->yuvFormat) { |
| case AVIF_PIXEL_FORMAT_YUV444: |
| seqProfile = 1; |
| break; |
| case AVIF_PIXEL_FORMAT_YUV422: |
| seqProfile = 2; |
| break; |
| case AVIF_PIXEL_FORMAT_YUV420: |
| seqProfile = 0; |
| break; |
| case AVIF_PIXEL_FORMAT_YV12: |
| seqProfile = 0; |
| break; |
| case AVIF_PIXEL_FORMAT_NONE: |
| default: |
| break; |
| } |
| } |
| } |
| |
| // TODO: Choose correct value from Annex A.3 table: https://aomediacodec.github.io/av1-spec/av1-spec.pdf |
| uint8_t seqLevelIdx0 = 31; |
| if ((image->width <= 8192) && (image->height <= 4352) && ((image->width * image->height) <= 8912896)) { |
| // Image is 5.1 compatible |
| seqLevelIdx0 = 13; // 5.1 |
| } |
| |
| memset(&codec->configBox, 0, sizeof(avifCodecConfigurationBox)); |
| codec->configBox.seqProfile = seqProfile; |
| codec->configBox.seqLevelIdx0 = seqLevelIdx0; |
| codec->configBox.seqTier0 = 0; |
| codec->configBox.highBitdepth = (image->depth > 8) ? 1 : 0; |
| codec->configBox.twelveBit = (image->depth == 12) ? 1 : 0; |
| codec->configBox.monochrome = alpha ? 1 : 0; |
| codec->configBox.chromaSubsamplingX = (uint8_t)formatInfo.chromaShiftX; |
| codec->configBox.chromaSubsamplingY = (uint8_t)formatInfo.chromaShiftY; |
| |
| // TODO: choose the correct one from below: |
| // * 0 - CSP_UNKNOWN Unknown (in this case the source video transfer function must be signaled outside the AV1 bitstream) |
| // * 1 - CSP_VERTICAL Horizontally co-located with (0, 0) luma sample, vertical position in the middle between two luma samples |
| // * 2 - CSP_COLOCATED co-located with (0, 0) luma sample |
| // * 3 - CSP_RESERVED |
| codec->configBox.chromaSamplePosition = 0; |
| } |
| |
| static void writeConfigBox(avifRWStream * s, avifCodecConfigurationBox * cfg) |
| { |
| avifBoxMarker av1C = avifRWStreamWriteBox(s, "av1C", -1, 0); |
| |
| // unsigned int (1) marker = 1; |
| // unsigned int (7) version = 1; |
| avifRWStreamWriteU8(s, 0x80 | 0x1); |
| |
| // unsigned int (3) seq_profile; |
| // unsigned int (5) seq_level_idx_0; |
| avifRWStreamWriteU8(s, (uint8_t)((cfg->seqProfile & 0x7) << 5) | (uint8_t)(cfg->seqLevelIdx0 & 0x1f)); |
| |
| uint8_t bits = 0; |
| bits |= (cfg->seqTier0 & 0x1) << 7; // unsigned int (1) seq_tier_0; |
| bits |= (cfg->highBitdepth & 0x1) << 6; // unsigned int (1) high_bitdepth; |
| bits |= (cfg->twelveBit & 0x1) << 5; // unsigned int (1) twelve_bit; |
| bits |= (cfg->monochrome & 0x1) << 4; // unsigned int (1) monochrome; |
| bits |= (cfg->chromaSubsamplingX & 0x1) << 3; // unsigned int (1) chroma_subsampling_x; |
| bits |= (cfg->chromaSubsamplingY & 0x1) << 2; // unsigned int (1) chroma_subsampling_y; |
| bits |= (cfg->chromaSamplePosition & 0x3); // unsigned int (2) chroma_sample_position; |
| avifRWStreamWriteU8(s, bits); |
| |
| // unsigned int (3) reserved = 0; |
| // unsigned int (1) initial_presentation_delay_present; |
| // if (initial_presentation_delay_present) { |
| // unsigned int (4) initial_presentation_delay_minus_one; |
| // } else { |
| // unsigned int (4) reserved = 0; |
| // } |
| avifRWStreamWriteU8(s, 0); |
| |
| avifRWStreamFinishBox(s, av1C); |
| } |