| // Copyright 2019 Joe Drago. All rights reserved. |
| // SPDX-License-Identifier: BSD-2-Clause |
| |
| #include "avif/internal.h" |
| |
| #include <string.h> |
| |
| #define STR_HELPER(x) #x |
| #define STR(x) STR_HELPER(x) |
| #define AVIF_VERSION_STRING (STR(AVIF_VERSION_MAJOR) "." STR(AVIF_VERSION_MINOR) "." STR(AVIF_VERSION_PATCH)) |
| |
| const char * avifVersion(void) |
| { |
| return AVIF_VERSION_STRING; |
| } |
| |
| const char * avifPixelFormatToString(avifPixelFormat format) |
| { |
| switch (format) { |
| case AVIF_PIXEL_FORMAT_YUV444: |
| return "YUV444"; |
| case AVIF_PIXEL_FORMAT_YUV420: |
| return "YUV420"; |
| case AVIF_PIXEL_FORMAT_YUV422: |
| return "YUV422"; |
| case AVIF_PIXEL_FORMAT_YV12: |
| return "YV12"; |
| case AVIF_PIXEL_FORMAT_NONE: |
| default: |
| break; |
| } |
| return "Unknown"; |
| } |
| |
| void avifGetPixelFormatInfo(avifPixelFormat format, avifPixelFormatInfo * info) |
| { |
| memset(info, 0, sizeof(avifPixelFormatInfo)); |
| info->aomIndexU = 1; |
| info->aomIndexV = 2; |
| |
| switch (format) { |
| case AVIF_PIXEL_FORMAT_YUV444: |
| info->chromaShiftX = 0; |
| info->chromaShiftY = 0; |
| break; |
| |
| case AVIF_PIXEL_FORMAT_YUV422: |
| info->chromaShiftX = 1; |
| info->chromaShiftY = 0; |
| break; |
| |
| case AVIF_PIXEL_FORMAT_YUV420: |
| info->chromaShiftX = 1; |
| info->chromaShiftY = 1; |
| break; |
| |
| case AVIF_PIXEL_FORMAT_YV12: |
| info->chromaShiftX = 1; |
| info->chromaShiftY = 1; |
| info->aomIndexU = 2; |
| info->aomIndexV = 1; |
| break; |
| |
| case AVIF_PIXEL_FORMAT_NONE: |
| default: |
| break; |
| } |
| } |
| |
| const char * avifResultToString(avifResult result) |
| { |
| // clang-format off |
| switch (result) { |
| case AVIF_RESULT_OK: return "OK"; |
| case AVIF_RESULT_INVALID_FTYP: return "Invalid ftyp"; |
| case AVIF_RESULT_NO_CONTENT: return "No content"; |
| case AVIF_RESULT_NO_YUV_FORMAT_SELECTED: return "No YUV format selected"; |
| case AVIF_RESULT_REFORMAT_FAILED: return "Reformat failed"; |
| case AVIF_RESULT_UNSUPPORTED_DEPTH: return "Unsupported depth"; |
| case AVIF_RESULT_ENCODE_COLOR_FAILED: return "Encoding of color planes failed"; |
| case AVIF_RESULT_ENCODE_ALPHA_FAILED: return "Encoding of alpha plane failed"; |
| case AVIF_RESULT_BMFF_PARSE_FAILED: return "BMFF parsing failed"; |
| case AVIF_RESULT_NO_AV1_ITEMS_FOUND: return "No AV1 items found"; |
| case AVIF_RESULT_DECODE_COLOR_FAILED: return "Decoding of color planes failed"; |
| case AVIF_RESULT_DECODE_ALPHA_FAILED: return "Decoding of alpha plane failed"; |
| case AVIF_RESULT_COLOR_ALPHA_SIZE_MISMATCH: return "Color and alpha planes size mismatch"; |
| case AVIF_RESULT_ISPE_SIZE_MISMATCH: return "Plane sizes don't match ispe values"; |
| case AVIF_RESULT_NO_CODEC_AVAILABLE: return "No codec available"; |
| case AVIF_RESULT_NO_IMAGES_REMAINING: return "No images remaining"; |
| case AVIF_RESULT_INVALID_EXIF_PAYLOAD: return "Invalid Exif payload"; |
| case AVIF_RESULT_UNKNOWN_ERROR: |
| default: |
| break; |
| } |
| // clang-format on |
| return "Unknown Error"; |
| } |
| |
| // This function assumes nothing in this struct needs to be freed; use avifImageClear() externally |
| static void avifImageSetDefaults(avifImage * image) |
| { |
| memset(image, 0, sizeof(avifImage)); |
| image->yuvRange = AVIF_RANGE_FULL; |
| } |
| |
| avifImage * avifImageCreate(int width, int height, int depth, avifPixelFormat yuvFormat) |
| { |
| avifImage * image = (avifImage *)avifAlloc(sizeof(avifImage)); |
| avifImageSetDefaults(image); |
| image->width = width; |
| image->height = height; |
| image->depth = depth; |
| image->yuvFormat = yuvFormat; |
| return image; |
| } |
| |
| avifImage * avifImageCreateEmpty(void) |
| { |
| return avifImageCreate(0, 0, 0, AVIF_PIXEL_FORMAT_NONE); |
| } |
| |
| void avifImageCopy(avifImage * dstImage, avifImage * srcImage) |
| { |
| avifImageFreePlanes(dstImage, AVIF_PLANES_ALL); |
| |
| dstImage->width = srcImage->width; |
| dstImage->height = srcImage->height; |
| dstImage->depth = srcImage->depth; |
| dstImage->yuvFormat = srcImage->yuvFormat; |
| dstImage->yuvRange = srcImage->yuvRange; |
| |
| if (srcImage->profileFormat == AVIF_PROFILE_FORMAT_ICC) { |
| avifImageSetProfileICC(dstImage, srcImage->icc.data, srcImage->icc.size); |
| } else if (srcImage->profileFormat == AVIF_PROFILE_FORMAT_NCLX) { |
| avifImageSetProfileNCLX(dstImage, &srcImage->nclx); |
| } else { |
| avifImageSetProfileNone(dstImage); |
| } |
| |
| avifImageSetMetadataExif(dstImage, srcImage->exif.data, srcImage->exif.size); |
| avifImageSetMetadataXMP(dstImage, srcImage->xmp.data, srcImage->xmp.size); |
| |
| if (srcImage->rgbPlanes[AVIF_CHAN_R] && srcImage->rgbPlanes[AVIF_CHAN_G] && srcImage->rgbPlanes[AVIF_CHAN_B]) { |
| avifImageAllocatePlanes(dstImage, AVIF_PLANES_RGB); |
| |
| for (int plane = 0; plane < 3; ++plane) { |
| uint32_t planeBytes = srcImage->rgbRowBytes[plane] * srcImage->height; |
| memcpy(dstImage->rgbPlanes[plane], srcImage->rgbPlanes[plane], planeBytes); |
| } |
| } |
| |
| if (srcImage->yuvPlanes[AVIF_CHAN_Y]) { |
| avifImageAllocatePlanes(dstImage, AVIF_PLANES_YUV); |
| |
| avifPixelFormatInfo formatInfo; |
| avifGetPixelFormatInfo(srcImage->yuvFormat, &formatInfo); |
| int uvHeight = dstImage->height >> formatInfo.chromaShiftY; |
| for (int yuvPlane = 0; yuvPlane < 3; ++yuvPlane) { |
| int aomPlaneIndex = yuvPlane; |
| int planeHeight = dstImage->height; |
| if (yuvPlane == AVIF_CHAN_U) { |
| aomPlaneIndex = formatInfo.aomIndexU; |
| planeHeight = uvHeight; |
| } else if (yuvPlane == AVIF_CHAN_V) { |
| aomPlaneIndex = formatInfo.aomIndexV; |
| planeHeight = uvHeight; |
| } |
| |
| if (!srcImage->yuvRowBytes[aomPlaneIndex]) { |
| // plane is absent. If we're copying from a source without |
| // them, mimic the source image's state by removing our copy. |
| avifFree(dstImage->yuvPlanes[aomPlaneIndex]); |
| dstImage->yuvPlanes[aomPlaneIndex] = NULL; |
| dstImage->yuvRowBytes[aomPlaneIndex] = 0; |
| continue; |
| } |
| |
| for (int j = 0; j < planeHeight; ++j) { |
| uint8_t * srcRow = &srcImage->yuvPlanes[aomPlaneIndex][j * srcImage->yuvRowBytes[aomPlaneIndex]]; |
| uint8_t * dstRow = &dstImage->yuvPlanes[yuvPlane][j * dstImage->yuvRowBytes[yuvPlane]]; |
| memcpy(dstRow, srcRow, dstImage->yuvRowBytes[yuvPlane]); |
| } |
| } |
| } |
| |
| if (srcImage->alphaPlane) { |
| avifImageAllocatePlanes(dstImage, AVIF_PLANES_A); |
| for (uint32_t j = 0; j < dstImage->height; ++j) { |
| uint8_t * srcAlphaRow = &srcImage->alphaPlane[j * srcImage->alphaRowBytes]; |
| uint8_t * dstAlphaRow = &dstImage->alphaPlane[j * dstImage->alphaRowBytes]; |
| memcpy(dstAlphaRow, srcAlphaRow, dstImage->alphaRowBytes); |
| } |
| } |
| } |
| |
| void avifImageDestroy(avifImage * image) |
| { |
| avifImageFreePlanes(image, AVIF_PLANES_ALL); |
| avifRWDataFree(&image->icc); |
| avifRWDataFree(&image->exif); |
| avifRWDataFree(&image->xmp); |
| avifFree(image); |
| } |
| |
| void avifImageSetProfileNone(avifImage * image) |
| { |
| image->profileFormat = AVIF_PROFILE_FORMAT_NONE; |
| avifRWDataFree(&image->icc); |
| } |
| |
| void avifImageSetProfileICC(avifImage * image, const uint8_t * icc, size_t iccSize) |
| { |
| avifImageSetProfileNone(image); |
| if (iccSize) { |
| image->profileFormat = AVIF_PROFILE_FORMAT_ICC; |
| avifRWDataSet(&image->icc, icc, iccSize); |
| } |
| } |
| |
| void avifImageSetProfileNCLX(avifImage * image, avifNclxColorProfile * nclx) |
| { |
| avifImageSetProfileNone(image); |
| image->profileFormat = AVIF_PROFILE_FORMAT_NCLX; |
| memcpy(&image->nclx, nclx, sizeof(avifNclxColorProfile)); |
| } |
| |
| void avifImageSetMetadataExif(avifImage * image, const uint8_t * exif, size_t exifSize) |
| { |
| avifRWDataSet(&image->exif, exif, exifSize); |
| } |
| |
| void avifImageSetMetadataXMP(avifImage * image, const uint8_t * xmp, size_t xmpSize) |
| { |
| avifRWDataSet(&image->xmp, xmp, xmpSize); |
| } |
| |
| void avifImageAllocatePlanes(avifImage * image, uint32_t planes) |
| { |
| int channelSize = avifImageUsesU16(image) ? 2 : 1; |
| int fullRowBytes = channelSize * image->width; |
| int fullSize = fullRowBytes * image->height; |
| if (planes & AVIF_PLANES_RGB) { |
| if (!image->rgbPlanes[AVIF_CHAN_R]) { |
| image->rgbRowBytes[AVIF_CHAN_R] = fullRowBytes; |
| image->rgbPlanes[AVIF_CHAN_R] = avifAlloc(fullSize); |
| memset(image->rgbPlanes[AVIF_CHAN_R], 0, fullSize); |
| } |
| if (!image->rgbPlanes[AVIF_CHAN_G]) { |
| image->rgbRowBytes[AVIF_CHAN_G] = fullRowBytes; |
| image->rgbPlanes[AVIF_CHAN_G] = avifAlloc(fullSize); |
| memset(image->rgbPlanes[AVIF_CHAN_G], 0, fullSize); |
| } |
| if (!image->rgbPlanes[AVIF_CHAN_B]) { |
| image->rgbRowBytes[AVIF_CHAN_B] = fullRowBytes; |
| image->rgbPlanes[AVIF_CHAN_B] = avifAlloc(fullSize); |
| memset(image->rgbPlanes[AVIF_CHAN_B], 0, fullSize); |
| } |
| } |
| if ((planes & AVIF_PLANES_YUV) && (image->yuvFormat != AVIF_PIXEL_FORMAT_NONE)) { |
| avifPixelFormatInfo info; |
| avifGetPixelFormatInfo(image->yuvFormat, &info); |
| |
| int shiftedW = image->width; |
| if (info.chromaShiftX) { |
| shiftedW = (image->width + 1) >> info.chromaShiftX; |
| } |
| int shiftedH = image->height; |
| if (info.chromaShiftY) { |
| shiftedH = (image->height + 1) >> info.chromaShiftY; |
| } |
| |
| int uvRowBytes = channelSize * shiftedW; |
| int uvSize = uvRowBytes * shiftedH; |
| |
| if (!image->yuvPlanes[AVIF_CHAN_Y]) { |
| image->yuvRowBytes[AVIF_CHAN_Y] = fullRowBytes; |
| image->yuvPlanes[AVIF_CHAN_Y] = avifAlloc(fullSize); |
| memset(image->yuvPlanes[AVIF_CHAN_Y], 0, fullSize); |
| } |
| if (!image->yuvPlanes[AVIF_CHAN_U]) { |
| image->yuvRowBytes[AVIF_CHAN_U] = uvRowBytes; |
| image->yuvPlanes[AVIF_CHAN_U] = avifAlloc(uvSize); |
| memset(image->yuvPlanes[AVIF_CHAN_U], 0, uvSize); |
| } |
| if (!image->yuvPlanes[AVIF_CHAN_V]) { |
| image->yuvRowBytes[AVIF_CHAN_V] = uvRowBytes; |
| image->yuvPlanes[AVIF_CHAN_V] = avifAlloc(uvSize); |
| memset(image->yuvPlanes[AVIF_CHAN_V], 0, uvSize); |
| } |
| } |
| if (planes & AVIF_PLANES_A) { |
| if (!image->alphaPlane) { |
| image->alphaRowBytes = fullRowBytes; |
| image->alphaPlane = avifAlloc(fullRowBytes * image->height); |
| memset(image->alphaPlane, 0, fullRowBytes * image->height); |
| } |
| } |
| } |
| |
| void avifImageCopyDecoderAlpha(avifImage * image) |
| { |
| if (image->alphaPlane && image->alphaRowBytes && image->height && image->decoderOwnsAlphaPlane) { |
| int fullSize = image->alphaRowBytes * image->height; |
| uint8_t * oldAlphaPlane = image->alphaPlane; |
| image->alphaPlane = avifAlloc(fullSize); |
| memcpy(image->alphaPlane, oldAlphaPlane, fullSize); |
| image->decoderOwnsAlphaPlane = AVIF_FALSE; |
| } |
| } |
| |
| void avifImageFreePlanes(avifImage * image, uint32_t planes) |
| { |
| if (planes & AVIF_PLANES_RGB) { |
| avifFree(image->rgbPlanes[AVIF_CHAN_R]); |
| image->rgbPlanes[AVIF_CHAN_R] = NULL; |
| image->rgbRowBytes[AVIF_CHAN_R] = 0; |
| avifFree(image->rgbPlanes[AVIF_CHAN_G]); |
| image->rgbPlanes[AVIF_CHAN_G] = NULL; |
| image->rgbRowBytes[AVIF_CHAN_G] = 0; |
| avifFree(image->rgbPlanes[AVIF_CHAN_B]); |
| image->rgbPlanes[AVIF_CHAN_B] = NULL; |
| image->rgbRowBytes[AVIF_CHAN_B] = 0; |
| } |
| if ((planes & AVIF_PLANES_YUV) && (image->yuvFormat != AVIF_PIXEL_FORMAT_NONE)) { |
| if (!image->decoderOwnsYUVPlanes) { |
| avifFree(image->yuvPlanes[AVIF_CHAN_Y]); |
| avifFree(image->yuvPlanes[AVIF_CHAN_U]); |
| avifFree(image->yuvPlanes[AVIF_CHAN_V]); |
| } |
| image->yuvPlanes[AVIF_CHAN_Y] = NULL; |
| image->yuvRowBytes[AVIF_CHAN_Y] = 0; |
| image->yuvPlanes[AVIF_CHAN_U] = NULL; |
| image->yuvRowBytes[AVIF_CHAN_U] = 0; |
| image->yuvPlanes[AVIF_CHAN_V] = NULL; |
| image->yuvRowBytes[AVIF_CHAN_V] = 0; |
| image->decoderOwnsYUVPlanes = AVIF_FALSE; |
| } |
| if (planes & AVIF_PLANES_A) { |
| if (!image->decoderOwnsAlphaPlane) { |
| avifFree(image->alphaPlane); |
| } |
| image->alphaPlane = NULL; |
| image->alphaRowBytes = 0; |
| image->decoderOwnsAlphaPlane = AVIF_FALSE; |
| } |
| } |
| |
| avifBool avifImageUsesU16(avifImage * image) |
| { |
| return (image->depth > 8) ? AVIF_TRUE : AVIF_FALSE; |
| } |
| |
| // avifCodecCreate*() functions are in their respective codec_*.c files |
| |
| void avifCodecDestroy(avifCodec * codec) |
| { |
| if (codec && codec->destroyInternal) { |
| codec->destroyInternal(codec); |
| } |
| avifFree(codec); |
| } |
| |
| // --------------------------------------------------------------------------- |
| // Codec availability and versions |
| |
| typedef const char * (*versionFunc)(void); |
| typedef avifCodec * (*avifCodecCreateFunc)(void); |
| |
| struct AvailableCodec |
| { |
| avifCodecChoice choice; |
| const char * name; |
| versionFunc version; |
| avifCodecCreateFunc create; |
| uint32_t flags; |
| }; |
| |
| // This is the main codec table; it determines all usage/availability in libavif. |
| |
| static struct AvailableCodec availableCodecs[] = { |
| // Ordered by preference (for AUTO) |
| |
| #if defined(AVIF_CODEC_DAV1D) |
| { AVIF_CODEC_CHOICE_DAV1D, "dav1d", avifCodecVersionDav1d, avifCodecCreateDav1d, AVIF_CODEC_FLAG_CAN_DECODE }, |
| #endif |
| #if defined(AVIF_CODEC_RAV1E) |
| { AVIF_CODEC_CHOICE_RAV1E, "rav1e", avifCodecVersionRav1e, avifCodecCreateRav1e, AVIF_CODEC_FLAG_CAN_ENCODE }, |
| #endif |
| #if defined(AVIF_CODEC_AOM) |
| { AVIF_CODEC_CHOICE_AOM, "aom", avifCodecVersionAOM, avifCodecCreateAOM, AVIF_CODEC_FLAG_CAN_DECODE | AVIF_CODEC_FLAG_CAN_ENCODE }, |
| #endif |
| { AVIF_CODEC_CHOICE_AUTO, NULL, NULL, NULL, 0 } |
| }; |
| |
| static const int availableCodecsCount = (sizeof(availableCodecs) / sizeof(availableCodecs[0])) - 1; |
| |
| static struct AvailableCodec * findAvailableCodec(avifCodecChoice choice, uint32_t requiredFlags) |
| { |
| for (int i = 0; i < availableCodecsCount; ++i) { |
| if ((choice != AVIF_CODEC_CHOICE_AUTO) && (availableCodecs[i].choice != choice)) { |
| continue; |
| } |
| if (requiredFlags && ((availableCodecs[i].flags & requiredFlags) != requiredFlags)) { |
| continue; |
| } |
| return &availableCodecs[i]; |
| } |
| return NULL; |
| } |
| |
| const char * avifCodecName(avifCodecChoice choice, uint32_t requiredFlags) |
| { |
| struct AvailableCodec * availableCodec = findAvailableCodec(choice, requiredFlags); |
| if (availableCodec) { |
| return availableCodec->name; |
| } |
| return NULL; |
| } |
| |
| avifCodecChoice avifCodecChoiceFromName(const char * name) |
| { |
| for (int i = 0; i < availableCodecsCount; ++i) { |
| if (!strcmp(availableCodecs[i].name, name)) { |
| return availableCodecs[i].choice; |
| } |
| } |
| return AVIF_CODEC_CHOICE_AUTO; |
| } |
| |
| avifCodec * avifCodecCreate(avifCodecChoice choice, uint32_t requiredFlags) |
| { |
| struct AvailableCodec * availableCodec = findAvailableCodec(choice, requiredFlags); |
| if (availableCodec) { |
| return availableCodec->create(); |
| } |
| return NULL; |
| } |
| |
| void avifCodecVersions(char outBuffer[256]) |
| { |
| outBuffer[0] = 0; |
| for (int i = 0; i < availableCodecsCount; ++i) { |
| if (i > 0) { |
| strcat(outBuffer, ", "); |
| } |
| strcat(outBuffer, availableCodecs[i].name); |
| strcat(outBuffer, ":"); |
| strcat(outBuffer, availableCodecs[i].version()); |
| } |
| } |