From 85f55f4daedc67eb2ff6a9ad552e2e9ad03cd072 Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Sun, 27 Sep 2026 12:42:05 +0800 Subject: [PATCH 1/5] Create codec.h from internal.h --- include/avif/{internal.h => codec.h} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename include/avif/{internal.h => codec.h} (100%) diff --git a/include/avif/internal.h b/include/avif/codec.h similarity index 100% rename from include/avif/internal.h rename to include/avif/codec.h From e9b2fef07fc1f4eccfd16407ed93ae5d91b5a62f Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Sun, 27 Sep 2026 12:44:13 +0800 Subject: [PATCH 2/5] Cleanup codec.h with only codec definition --- include/avif/codec.h | 683 +------------------------------------------ 1 file changed, 4 insertions(+), 679 deletions(-) diff --git a/include/avif/codec.h b/include/avif/codec.h index 81d5239727..60b69d5fb5 100644 --- a/include/avif/codec.h +++ b/include/avif/codec.h @@ -1,8 +1,8 @@ // Copyright 2019 Joe Drago. All rights reserved. // SPDX-License-Identifier: BSD-2-Clause -#ifndef AVIF_INTERNAL_H -#define AVIF_INTERNAL_H +#ifndef AVIF_CODEC_H +#define AVIF_CODEC_H #include "avif/avif.h" // IWYU pragma: export @@ -10,129 +10,9 @@ extern "C" { #endif -#if defined(AVIF_DLL) && defined(AVIF_USING_STATIC_LIBS) -#error "Your target is linking against avif and avif_internal: only one should be chosen" -#endif - -// Yes, clamp macros are nasty. Do not use them. -#define AVIF_CLAMP(x, low, high) (((x) < (low)) ? (low) : (((high) < (x)) ? (high) : (x))) -#define AVIF_MIN(a, b) (((a) < (b)) ? (a) : (b)) -#define AVIF_MAX(a, b) (((a) > (b)) ? (a) : (b)) - -// Used for debugging. Define AVIF_BREAK_ON_ERROR to catch the earliest failure during encoding or decoding. -#if defined(AVIF_BREAK_ON_ERROR) -static inline void avifBreakOnError() -{ - // Same mechanism as OpenCV's error() function, or replace by a breakpoint. - int * p = NULL; - *p = 0; -} -#else -#define avifBreakOnError() -#endif - -// Used by stream related things. -#define AVIF_CHECK(A) \ - do { \ - if (!(A)) { \ - avifBreakOnError(); \ - return AVIF_FALSE; \ - } \ - } while (0) - -// Used instead of CHECK if needing to return a specific error on failure, instead of AVIF_FALSE -#define AVIF_CHECKERR(A, ERR) \ - do { \ - if (!(A)) { \ - avifBreakOnError(); \ - return ERR; \ - } \ - } while (0) - -// Forward any error to the caller now or continue execution. -#define AVIF_CHECKRES(A) \ - do { \ - const avifResult result__ = (A); \ - if (result__ != AVIF_RESULT_OK) { \ - avifBreakOnError(); \ - return result__; \ - } \ - } while (0) - -// AVIF_ASSERT_OR_RETURN() can be used instead of assert() for extra security in release builds. -#ifdef NDEBUG -#define AVIF_ASSERT_OR_RETURN(A) AVIF_CHECKERR((A), AVIF_RESULT_INTERNAL_ERROR) -#define AVIF_ASSERT_NOT_REACHED_OR_RETURN \ - do { \ - avifBreakOnError(); \ - return AVIF_RESULT_INTERNAL_ERROR; \ - } while (0) -#else -#define AVIF_ASSERT_OR_RETURN(A) assert(A) -#define AVIF_ASSERT_NOT_REACHED_OR_RETURN assert(0); -#endif - -// --------------------------------------------------------------------------- -// URNs and Content-Types - -#define AVIF_URN_ALPHA0 "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha" -#define AVIF_URN_ALPHA1 "urn:mpeg:hevc:2015:auxid:1" - -#define AVIF_CONTENT_TYPE_XMP "application/rdf+xml" - -// --------------------------------------------------------------------------- -// Memory management - -// Allocates count * size bytes and zero-initializes them. Returns NULL on memory -// allocation failure, including the case when count * size overflows size_t. -void * avifCalloc(size_t count, size_t size); - // --------------------------------------------------------------------------- // Utils -float avifRoundf(float v); - -// H (host) is platform-dependent. Could be little- or big-endian. -// N (network) is big-endian: most- to least-significant bytes. -// C (custom) is little-endian: least- to most-significant bytes. -// Never read N or C values; only access after casting to uint8_t*. -uint16_t avifHTONS(uint16_t s); -uint16_t avifNTOHS(uint16_t s); -uint16_t avifCTOHS(uint16_t s); -uint32_t avifHTONL(uint32_t l); -uint32_t avifNTOHL(uint32_t l); -uint32_t avifCTOHL(uint32_t l); -uint64_t avifHTON64(uint64_t l); -uint64_t avifNTOH64(uint64_t l); - -void avifCalcYUVCoefficients(const avifImage * image, float * outR, float * outG, float * outB); - -typedef float (*avifTransferFunction)(float); -// Returns a function to map from gamma-encoded values in the [0.0, 1.0] range to linear extended SDR values. -// Extended SDR values are in [0.0, 1.0] for SDR transfer chracteristics (all transfer characteristics except PQ and HLG) -// and can go beyond 1.0 for HDR transfer characteristics: -// - For AVIF_TRANSFER_CHARACTERISTICS_PQ, the linear range is [0.0, 10000/203] -// - For AVIF_TRANSFER_CHARACTERISTICS_HLG, the linear range is [0.0, 1000/203] -avifTransferFunction avifTransferCharacteristicsGetGammaToLinearFunction(avifTransferCharacteristics atc); -// Same as above in the opposite direction. toGamma(toLinear(v)) ~= v. -avifTransferFunction avifTransferCharacteristicsGetLinearToGammaFunction(avifTransferCharacteristics atc); - -// Computes the RGB->YUV conversion coefficients kr, kg, kb, such that Y=kr*R+kg*G+kb*B. -void avifColorPrimariesComputeYCoeffs(avifColorPrimaries colorPrimaries, float coeffs[3]); - -// Computes a conversion matrix from RGB to XYZ with a D50 white point. -AVIF_NODISCARD avifBool avifColorPrimariesComputeRGBToXYZD50Matrix(avifColorPrimaries colorPrimaries, double coeffs[3][3]); -// Computes a conversion matrix from XYZ with a D50 white point to RGB. -AVIF_NODISCARD avifBool avifColorPrimariesComputeXYZD50ToRGBMatrix(avifColorPrimaries colorPrimaries, double coeffs[3][3]); -// Computes the RGB->RGB conversion matrix to convert from one set of RGB primaries to another. -AVIF_NODISCARD avifBool avifColorPrimariesComputeRGBToRGBMatrix(avifColorPrimaries srcColorPrimaries, - avifColorPrimaries dstColorPrimaries, - double coeffs[3][3]); -// Converts the given linear RGB pixel from one color space to another using the provided coefficients. -// The coefficients can be obtained with avifColorPrimariesComputeRGBToRGBMatrix(). -// The output values are not clamped and may be < 0 or > 1. -void avifLinearRGBConvertColorSpace(float rgb[4], double coeffs[3][3]); - #define AVIF_ARRAY_DECLARE(TYPENAME, ITEMSTYPE, ITEMSNAME) \ typedef struct TYPENAME \ { \ @@ -146,323 +26,8 @@ AVIF_NODISCARD void * avifArrayPush(void * arrayStruct); void avifArrayPop(void * arrayStruct); void avifArrayDestroy(void * arrayStruct); -void avifFractionSimplify(avifFraction * f); -// Makes the fractions have a common denominator. -AVIF_NODISCARD avifBool avifFractionCD(avifFraction * a, avifFraction * b); -AVIF_NODISCARD avifBool avifFractionAdd(avifFraction a, avifFraction b, avifFraction * result); -AVIF_NODISCARD avifBool avifFractionSub(avifFraction a, avifFraction b, avifFraction * result); - -void avifImageSetDefaults(avifImage * image); -// Copies all fields that do not need to be freed/allocated from srcImage to dstImage. -void avifImageCopyNoAlloc(avifImage * dstImage, const avifImage * srcImage); - -// Copies the samples from srcImage to dstImage. dstImage must be allocated. -// srcImage and dstImage must have the same width, height, and depth. -// If the AVIF_PLANES_YUV bit is set in planes, then srcImage and dstImage must have the same yuvFormat. -// Ignores the gainMap field. -void avifImageCopySamples(avifImage * dstImage, const avifImage * srcImage, avifPlanesFlags planes); - -// Appends an opaque image item property. -avifResult avifImagePushProperty(avifImage * image, - const uint8_t boxtype[4], - const uint8_t usertype[16], - const uint8_t * boxPayload, - size_t boxPayloadSize); - -// Check if the FourCC property value is a known value -AVIF_NODISCARD avifBool avifIsKnownPropertyType(const uint8_t boxtype[4]); -// Check if the extended property (UUID) is valid -AVIF_NODISCARD avifBool avifIsValidUUID(const uint8_t uuid[16]); - // --------------------------------------------------------------------------- - -// Mapping used in the coding of Sample Transform metadata. -typedef enum avifSampleTransformBitDepth -{ - AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_8 = 0, // Signed 8-bit. - AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_16 = 1, // Signed 16-bit. - AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_32 = 2, // Signed 32-bit. - AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_64 = 3 // Signed 64-bit. -} avifSampleTransformBitDepth; - -// Meaning of an operand or operator in Sample Transform metadata. -typedef enum avifSampleTransformTokenType -{ - // Operands. - AVIF_SAMPLE_TRANSFORM_CONSTANT = 0, - AVIF_SAMPLE_TRANSFORM_INPUT_IMAGE_ITEM_INDEX = 1, - AVIF_SAMPLE_TRANSFORM_FIRST_INPUT_IMAGE_ITEM_INDEX = 1, - AVIF_SAMPLE_TRANSFORM_LAST_INPUT_IMAGE_ITEM_INDEX = 32, - - // Unary operators. L is the operand. - AVIF_SAMPLE_TRANSFORM_FIRST_UNARY_OPERATOR = 64, - AVIF_SAMPLE_TRANSFORM_NEGATION = 64, // S = -L - AVIF_SAMPLE_TRANSFORM_ABSOLUTE = 65, // S = |L| - AVIF_SAMPLE_TRANSFORM_NOT = 66, // S = ~L - AVIF_SAMPLE_TRANSFORM_BSR = 67, // S = L<=0 ? 0 : truncate(log2(L)) - AVIF_SAMPLE_TRANSFORM_LAST_UNARY_OPERATOR = 67, - - // Binary operators. L is the left operand. R is the right operand. - AVIF_SAMPLE_TRANSFORM_FIRST_BINARY_OPERATOR = 128, - AVIF_SAMPLE_TRANSFORM_SUM = 128, // S = L + R - AVIF_SAMPLE_TRANSFORM_DIFFERENCE = 129, // S = L - R - AVIF_SAMPLE_TRANSFORM_PRODUCT = 130, // S = L * R - AVIF_SAMPLE_TRANSFORM_QUOTIENT = 131, // S = R==0 ? L : truncate(L / R) - AVIF_SAMPLE_TRANSFORM_AND = 132, // S = L & R - AVIF_SAMPLE_TRANSFORM_OR = 133, // S = L | R - AVIF_SAMPLE_TRANSFORM_XOR = 134, // S = L ^ R - AVIF_SAMPLE_TRANSFORM_POW = 135, // S = L==0 ? 0 : truncate(pow(L, R)) - AVIF_SAMPLE_TRANSFORM_MIN = 136, // S = L<=R ? L : R - AVIF_SAMPLE_TRANSFORM_MAX = 137, // S = L<=R ? R : L - AVIF_SAMPLE_TRANSFORM_LAST_BINARY_OPERATOR = 137, - - AVIF_SAMPLE_TRANSFORM_RESERVED = 138 -} avifSampleTransformTokenType; - -typedef struct avifSampleTransformToken -{ - avifSampleTransformTokenType type; - int32_t constant; // If type is AVIF_SAMPLE_TRANSFORM_CONSTANT. - // Only 32-bit (bit_depth=2) constants are supported. - uint8_t inputImageItemIndex; // If type is AVIF_SAMPLE_TRANSFORM_INPUT_IMAGE_ITEM_INDEX. 1-based. -} avifSampleTransformToken; - -AVIF_ARRAY_DECLARE(avifSampleTransformExpression, avifSampleTransformToken, tokens); -avifBool avifSampleTransformExpressionIsValid(const avifSampleTransformExpression * tokens, uint32_t numInputImageItems); -avifBool avifSampleTransformExpressionIsEquivalentTo(const avifSampleTransformExpression * a, const avifSampleTransformExpression * b); - -avifResult avifSampleTransformRecipeToExpression(avifSampleTransformRecipe recipe, avifSampleTransformExpression * expression); -avifResult avifSampleTransformExpressionToRecipe(const avifSampleTransformExpression * expression, avifSampleTransformRecipe * recipe); - -// Applies the expression to the samples of the inputImageItems in the selected planes and stores -// the results in dstImage. dstImage can be part of the inputImageItems. -// dstImage and inputImageItems must be allocated and have the same planes and dimensions. -avifResult avifImageApplyExpression(avifImage * dstImage, - avifSampleTransformBitDepth bitDepth, - const avifSampleTransformExpression * expression, - uint8_t numInputImageItems, - const avifImage * inputImageItems[], - avifPlanesFlags planes); - -// Same as avifImageApplyExpression(). Convenience function. -avifResult avifImageApplyOperations(avifImage * dstImage, - avifSampleTransformBitDepth bitDepth, - uint32_t numTokens, - const avifSampleTransformToken tokens[], - uint8_t numInputImageItems, - const avifImage * inputImageItems[], - avifPlanesFlags planes); - -// --------------------------------------------------------------------------- -// Alpha - -typedef struct avifAlphaParams -{ - uint32_t width; - uint32_t height; - - uint32_t srcDepth; - const uint8_t * srcPlane; - uint32_t srcRowBytes; - uint32_t srcOffsetBytes; - uint32_t srcPixelBytes; - - uint32_t dstDepth; - uint8_t * dstPlane; - uint32_t dstRowBytes; - uint32_t dstOffsetBytes; - uint32_t dstPixelBytes; - -} avifAlphaParams; - -void avifFillAlpha(const avifAlphaParams * params); -void avifReformatAlpha(const avifAlphaParams * params); - -typedef enum avifReformatMode -{ - AVIF_REFORMAT_MODE_YUV_COEFFICIENTS = 0, // Normal YUV conversion using coefficients - AVIF_REFORMAT_MODE_IDENTITY, // Pack GBR directly into YUV planes (AVIF_MATRIX_COEFFICIENTS_IDENTITY) - AVIF_REFORMAT_MODE_YCGCO, // YUV conversion using AVIF_MATRIX_COEFFICIENTS_YCGCO - AVIF_REFORMAT_MODE_YCGCO_RE, // YUV conversion using AVIF_MATRIX_COEFFICIENTS_YCGCO_RE - AVIF_REFORMAT_MODE_YCGCO_RO, // YUV conversion using AVIF_MATRIX_COEFFICIENTS_YCGCO_RO -} avifReformatMode; - -typedef enum avifAlphaMultiplyMode -{ - AVIF_ALPHA_MULTIPLY_MODE_NO_OP = 0, - AVIF_ALPHA_MULTIPLY_MODE_MULTIPLY, - AVIF_ALPHA_MULTIPLY_MODE_UNMULTIPLY -} avifAlphaMultiplyMode; - -// Information about an RGB color space. -typedef struct avifRGBColorSpaceInfo -{ - uint32_t channelBytes; // Number of bytes per channel. - uint32_t pixelBytes; // Number of bytes per pixel (= channelBytes * num channels). - uint32_t offsetBytesR; // Offset in bytes of the red channel in a pixel. - uint32_t offsetBytesG; // Offset in bytes of the green channel in a pixel. - uint32_t offsetBytesB; // Offset in bytes of the blue channel in a pixel. - uint32_t offsetBytesA; // Offset in bytes of the alpha channel in a pixel. - uint32_t offsetBytesGray; // Offset in bytes of the gray channel in a pixel. - - int maxChannel; // Maximum value for a channel (e.g. 255 for 8 bit). - float maxChannelF; // Same as maxChannel but as a float. -} avifRGBColorSpaceInfo; - -avifBool avifGetRGBColorSpaceInfo(const avifRGBImage * rgb, avifRGBColorSpaceInfo * info); - -// Information about a YUV color space. -typedef struct avifYUVColorSpaceInfo -{ - // YUV coefficients. Y = kr*R + kg*G + kb*B. - float kr; - float kg; - float kb; - - uint32_t channelBytes; // Number of bytes per channel. - uint32_t depth; // Bit depth. - avifRange range; // Full or limited range. - int maxChannel; // Maximum value for a channel (e.g. 255 for 8 bit). - float biasY; // Minimum Y value. - float biasUV; // The value of 0.5 for the appropriate bit depth (128 for 8 bit, 512 for 10 bit, 2048 for 12 bit). - float rangeY; // Difference between max and min Y. - float rangeUV; // Difference between max and min UV. - - avifPixelFormatInfo formatInfo; // Chroma subsampling information. - avifReformatMode mode; // Appropriate RGB<->YUV conversion mode. -} avifYUVColorSpaceInfo; - -avifBool avifGetYUVColorSpaceInfo(const avifImage * image, avifYUVColorSpaceInfo * info); - -typedef struct avifReformatState -{ - avifRGBColorSpaceInfo rgb; - avifYUVColorSpaceInfo yuv; -} avifReformatState; - -// Retrieves the pixel value at position (x, y) expressed as floats in [0, 1]. If the image's format doesn't have alpha, -// rgbaPixel[3] is set to 1.0f. -void avifGetRGBAPixel(const avifRGBImage * src, uint32_t x, uint32_t y, const avifRGBColorSpaceInfo * info, float rgbaPixel[4]); -// Sets the pixel value at position (i, j) from RGBA values expressed as floats in [0, 1]. If the image's format doesn't -// support alpha, rgbaPixel[3] is ignored. -void avifSetRGBAPixel(const avifRGBImage * dst, uint32_t x, uint32_t y, const avifRGBColorSpaceInfo * info, const float rgbaPixel[4]); - -// Returns: -// * AVIF_RESULT_OK - Converted successfully with libyuv -// * AVIF_RESULT_NOT_IMPLEMENTED - The fast path for this combination is not implemented with libyuv, use built-in RGB conversion -// * [any other error] - Return error to caller -avifResult avifImageRGBToYUVLibYUV(avifImage * image, const avifRGBImage * rgb); - -// Parameters: -// * image - input YUV image -// * rgb - output RGB image -// * reformatAlpha - if set to AVIF_TRUE, the function will attempt to copy the alpha channel to the output RGB image using -// libyuv. -// * alphaReformattedWithLibYUV - Output parameter. If reformatAlpha is set to true and libyuv was able to copy over the alpha -// channel, then this will be set to AVIF_TRUE. Otherwise, this will be set to AVIF_FALSE. The value in this parameter is valid -// only if the return value of the function is AVIF_RESULT_OK or AVIF_RESULT_NOT_IMPLEMENTED. -// Returns: -// * AVIF_RESULT_OK - Converted successfully with libyuv -// * AVIF_RESULT_NOT_IMPLEMENTED - The fast path for this combination is not implemented with libyuv, use built-in YUV conversion -// * [any other error] - Return error to caller -avifResult avifImageYUVToRGBLibYUV(const avifImage * image, avifRGBImage * rgb, avifBool reformatAlpha, avifBool * alphaReformattedWithLibYUV); - -// Returns: -// * AVIF_RESULT_OK - Converted successfully with libsharpyuv -// * AVIF_RESULT_NOT_IMPLEMENTED - libsharpyuv is not compiled in, or doesn't support this type of input -// * [any other error] - Return error to caller -avifResult avifImageRGBToYUVLibSharpYUV(avifImage * image, const avifRGBImage * rgb, const avifReformatState * state); - -// Returns: -// * AVIF_RESULT_OK - Converted successfully with libyuv. -// * AVIF_RESULT_NOT_IMPLEMENTED - The fast path for this conversion is not implemented with libyuv, use built-in conversion. -// * AVIF_RESULT_INVALID_ARGUMENT - Return error to caller. -avifResult avifRGBImageToF16LibYUV(avifRGBImage * rgb); - -// Returns: -// * AVIF_RESULT_OK - (Un)Premultiply successfully with libyuv -// * AVIF_RESULT_NOT_IMPLEMENTED - The fast path for this combination is not implemented with libyuv, use built-in (Un)Premultiply -// * [any other error] - Return error to caller -avifResult avifRGBImagePremultiplyAlphaLibYUV(avifRGBImage * rgb); -avifResult avifRGBImageUnpremultiplyAlphaLibYUV(avifRGBImage * rgb); - -AVIF_NODISCARD avifBool avifDimensionsTooLarge(uint32_t width, uint32_t height, uint32_t imageSizeLimit, uint32_t imageDimensionLimit); - -// Given the number of encoding threads or decoding threads available and the image dimensions, -// chooses suitable values of *tileRowsLog2 and *tileColsLog2. -// -// Note: Although avifSetTileConfiguration() is only used in src/write.c and could be a static -// function in that file, it is defined as an internal global function so that it can be tested by -// unit tests. -void avifSetTileConfiguration(int threads, uint32_t width, uint32_t height, int * tileRowsLog2, int * tileColsLog2); - -// --------------------------------------------------------------------------- -// Scaling - -// Scales the YUV/A planes in-place. -avifResult avifImageScaleWithLimit(avifImage * image, - uint32_t dstWidth, - uint32_t dstHeight, - uint32_t imageSizeLimit, - uint32_t imageDimensionLimit, - avifDiagnostics * diag); - -// --------------------------------------------------------------------------- -// AVIF item category - -typedef enum avifItemCategory -{ - AVIF_ITEM_COLOR, - AVIF_ITEM_ALPHA, - AVIF_ITEM_GAIN_MAP, - AVIF_ITEM_SAMPLE_TRANSFORM, // Sample Transform derived image item 'sato'. - // Extra input image items for AVIF_ITEM_SAMPLE_TRANSFORM. "Extra" because AVIF_ITEM_COLOR could be one too. - AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_0_COLOR, - AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_1_COLOR, - AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_0_ALPHA, - AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_1_ALPHA, - AVIF_ITEM_CATEGORY_COUNT -} avifItemCategory; - -avifBool avifIsAlpha(avifItemCategory itemCategory); - -// AVIF allows up to 32 inputs for sample transforms but we only support a smaller number. -#define AVIF_SAMPLE_TRANSFORM_MAX_NUM_EXTRA_INPUT_IMAGE_ITEMS \ - (AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_0_ALPHA - AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_0_COLOR) -#define AVIF_SAMPLE_TRANSFORM_MAX_NUM_INPUT_IMAGE_ITEMS \ - (1 /* for AVIF_ITEM_COLOR */ + AVIF_SAMPLE_TRANSFORM_MAX_NUM_EXTRA_INPUT_IMAGE_ITEMS) - -#define AVIF_SAMPLE_TRANSFORM_MIN_CATEGORY AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_0_COLOR -#define AVIF_SAMPLE_TRANSFORM_MAX_CATEGORY \ - (AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_0_ALPHA + AVIF_SAMPLE_TRANSFORM_MAX_NUM_EXTRA_INPUT_IMAGE_ITEMS - 1) - -// --------------------------------------------------------------------------- -// Grid AVIF images - -// Returns false if the tiles in a grid image violate any standards. -// The image contains imageW*imageH pixels. The tiles are of tileW*tileH pixels each. -AVIF_NODISCARD avifBool avifAreGridDimensionsValid(avifPixelFormat yuvFormat, - uint32_t imageW, - uint32_t imageH, - uint32_t tileW, - uint32_t tileH, - avifDiagnostics * diag); - -// --------------------------------------------------------------------------- -// Metadata - -// Attempts to parse the image->exif payload for Exif orientation and sets image->transformFlags, image->irot and -// image->imir on success. Returns AVIF_RESULT_INVALID_EXIF_PAYLOAD on failure. -avifResult avifImageExtractExifOrientationToIrotImir(avifImage * image); - -#if defined(AVIF_ENABLE_EXPERIMENTAL_MINI) -// Returns the Exif orientation in [1-8] as defined in JEITA CP-3451C section 4.6.4.A Orientation -// corresponding to image->irot and image->imir. -uint8_t avifImageIrotImirToExifOrientation(const avifImage * image); -#endif // AVIF_ENABLE_EXPERIMENTAL_MINI - -// --------------------------------------------------------------------------- -// avifCodecDecodeInput +// avifDecodeSample // Legal spatial_id values are [0,1,2,3], so this serves as a sentinel value for "do not filter by spatial_id" #define AVIF_SPATIAL_ID_UNSET 0xff @@ -480,17 +45,6 @@ typedef struct avifDecodeSample // skipped until the output frame's spatial_id matches this ID. avifBool sync; // is sync sample (keyframe) } avifDecodeSample; -AVIF_ARRAY_DECLARE(avifDecodeSampleArray, avifDecodeSample, sample); - -typedef struct avifCodecDecodeInput -{ - avifDecodeSampleArray samples; - avifBool allLayers; // if true, the underlying codec must decode all layers, not just the best layer - avifItemCategory itemCategory; // category of item being decoded -} avifCodecDecodeInput; - -AVIF_NODISCARD avifCodecDecodeInput * avifCodecDecodeInputCreate(void); -void avifCodecDecodeInputDestroy(avifCodecDecodeInput * decodeInput); // --------------------------------------------------------------------------- // avifCodecEncodeOutput @@ -521,13 +75,6 @@ typedef struct avifCodecSpecificOption } avifCodecSpecificOption; AVIF_ARRAY_DECLARE(avifCodecSpecificOptions, avifCodecSpecificOption, entries); -// Returns NULL if a memory allocation failed. -AVIF_NODISCARD avifCodecSpecificOptions * avifCodecSpecificOptionsCreate(void); -void avifCodecSpecificOptionsClear(avifCodecSpecificOptions * csOptions); -void avifCodecSpecificOptionsDestroy(avifCodecSpecificOptions * csOptions); -// If value is NULL, key is deleted. On allocation failure, csOptions is left unchanged. -avifResult avifCodecSpecificOptionsSet(avifCodecSpecificOptions * csOptions, const char * key, const char * value); - // --------------------------------------------------------------------------- // avifCodecType (underlying video format) @@ -541,9 +88,6 @@ typedef enum avifCodecType #endif } avifCodecType; -// Returns AVIF_CODEC_TYPE_UNKNOWN unless the chosen codec is available with the requiredFlags. -avifCodecType avifCodecTypeFromChoice(avifCodecChoice choice, avifCodecFlags requiredFlags); - // --------------------------------------------------------------------------- // avifCodec (abstraction layer to use different codec implementations) @@ -623,227 +167,8 @@ typedef struct avifCodec avifCodecDestroyInternalFunc destroyInternal; } avifCodec; -avifResult avifCodecCreate(avifCodecChoice choice, avifCodecFlags requiredFlags, avifCodec ** codec); -void avifCodecDestroy(avifCodec * codec); - -AVIF_NODISCARD avifCodec * avifCodecCreateAOM(void); // requires AVIF_CODEC_AOM (codec_aom.c) -const char * avifCodecVersionAOM(void); // requires AVIF_CODEC_AOM (codec_aom.c) -AVIF_NODISCARD avifCodec * avifCodecCreateDav1d(void); // requires AVIF_CODEC_DAV1D (codec_dav1d.c) -const char * avifCodecVersionDav1d(void); // requires AVIF_CODEC_DAV1D (codec_dav1d.c) -AVIF_NODISCARD avifCodec * avifCodecCreateGav1(void); // requires AVIF_CODEC_LIBGAV1 (codec_libgav1.c) -const char * avifCodecVersionGav1(void); // requires AVIF_CODEC_LIBGAV1 (codec_libgav1.c) -AVIF_NODISCARD avifCodec * avifCodecCreateRav1e(void); // requires AVIF_CODEC_RAV1E (codec_rav1e.c) -const char * avifCodecVersionRav1e(void); // requires AVIF_CODEC_RAV1E (codec_rav1e.c) -AVIF_NODISCARD avifCodec * avifCodecCreateSvt(void); // requires AVIF_CODEC_SVT (codec_svt.c) -const char * avifCodecVersionSvt(void); // requires AVIF_CODEC_SVT (codec_svt.c) -AVIF_NODISCARD avifCodec * avifCodecCreateAVM(void); // requires AVIF_CODEC_AVM (codec_avm.c) -const char * avifCodecVersionAVM(void); // requires AVIF_CODEC_AVM (codec_avm.c) - -// --------------------------------------------------------------------------- -// avifDiagnostics - -#ifdef __clang__ -__attribute__((__format__(__printf__, 2, 3))) -#endif -void avifDiagnosticsPrintf(avifDiagnostics * diag, const char * format, ...); - -#if defined(AVIF_ENABLE_COMPLIANCE_WARDEN) -avifResult avifIsCompliant(const uint8_t * data, size_t size); -#endif - -// --------------------------------------------------------------------------- -// avifStream -// -// In network byte order (big-endian) unless otherwise specified. - -typedef size_t avifBoxMarker; - -typedef struct avifBoxHeader -{ - // If set to AVIF_TRUE, it means that the box goes on until the end of the - // stream. So, |size| must be set to the number of bytes left in the input - // stream. If set to AVIF_FALSE, |size| indicates the size of the box in - // bytes, excluding the box header. - avifBool isSizeZeroBox; - // Size of the box in bytes, excluding the box header. - size_t size; - - uint8_t type[4]; - uint8_t usertype[16]; // Unused unless |type| is "uuid". -} avifBoxHeader; - -// IMPORTANT: Functions operating on avifROStream * stream shall maintain the -// invariant that stream->offset <= stream->raw->size. -// Code outside src/stream.c shall only access stream->raw and stream->offset -// through the avifROStream*() functions. -typedef struct avifROStream -{ - avifROData * raw; - - // Index of the next byte in the raw stream. - size_t offset; - - // If 0, byte-aligned functions can be used (avifROStreamRead() etc.). - // Otherwise, it represents the number of bits already used in the last byte - // (located at offset-1). - size_t numUsedBitsInPartialByte; - - // Error information, if any. - avifDiagnostics * diag; - const char * diagContext; -} avifROStream; - -const uint8_t * avifROStreamCurrent(avifROStream * stream); -void avifROStreamStart(avifROStream * stream, avifROData * raw, avifDiagnostics * diag, const char * diagContext); -size_t avifROStreamOffset(const avifROStream * stream); -void avifROStreamSetOffset(avifROStream * stream, size_t offset); - -AVIF_NODISCARD avifBool avifROStreamHasBytesLeft(const avifROStream * stream, size_t byteCount); -size_t avifROStreamRemainingBytes(const avifROStream * stream); -// The following functions require byte alignment. -AVIF_NODISCARD avifBool avifROStreamSkip(avifROStream * stream, size_t byteCount); -AVIF_NODISCARD avifBool avifROStreamRead(avifROStream * stream, uint8_t * data, size_t size); -AVIF_NODISCARD avifBool avifROStreamReadU16(avifROStream * stream, uint16_t * v); -AVIF_NODISCARD avifBool avifROStreamReadU16Endianness(avifROStream * stream, uint16_t * v, avifBool littleEndian); -AVIF_NODISCARD avifBool avifROStreamReadU32(avifROStream * stream, uint32_t * v); -AVIF_NODISCARD avifBool avifROStreamReadU32Endianness(avifROStream * stream, uint32_t * v, avifBool littleEndian); -// Reads a factor*8 sized uint, saves in v. If factor is 0, reads nothing and saves 0 in v. -AVIF_NODISCARD avifBool avifROStreamReadUX8(avifROStream * stream, uint64_t * v, uint64_t factor); -AVIF_NODISCARD avifBool avifROStreamReadU64(avifROStream * stream, uint64_t * v); -AVIF_NODISCARD avifBool avifROStreamReadString(avifROStream * stream, char * output, size_t outputSize); -AVIF_NODISCARD avifBool avifROStreamReadBoxHeader(avifROStream * stream, avifBoxHeader * header); // This fails if the size reported by the header cannot fit in the stream -AVIF_NODISCARD avifBool avifROStreamReadBoxHeaderPartial(avifROStream * stream, avifBoxHeader * header, avifBool topLevel); // This doesn't require that the full box can fit in the stream -AVIF_NODISCARD avifBool avifROStreamReadVersionAndFlags(avifROStream * stream, uint8_t * version, uint32_t * flags); // version and flags ptrs are both optional -AVIF_NODISCARD avifBool avifROStreamReadAndEnforceVersion(avifROStream * stream, uint8_t enforcedVersion, uint32_t * flags); // flags ptr is optional -// The following functions can read non-aligned bits. -AVIF_NODISCARD avifBool avifROStreamSkipBits(avifROStream * stream, size_t bitCount); -AVIF_NODISCARD avifBool avifROStreamReadBitsU8(avifROStream * stream, uint8_t * v, size_t bitCount); -AVIF_NODISCARD avifBool avifROStreamReadBitsU16(avifROStream * stream, uint16_t * v, size_t bitCount); -AVIF_NODISCARD avifBool avifROStreamReadBitsU32(avifROStream * stream, uint32_t * v, size_t bitCount); - -typedef struct avifRWStream -{ - avifRWData * raw; - - // Index of the next byte in the raw stream. - size_t offset; - - // If 0, byte-aligned functions can be used (avifRWStreamWrite() etc.). - // Otherwise, it represents the number of bits already used in the last byte - // (located at offset-1). - size_t numUsedBitsInPartialByte; -} avifRWStream; - -void avifRWStreamStart(avifRWStream * stream, avifRWData * raw); -size_t avifRWStreamOffset(const avifRWStream * stream); -void avifRWStreamSetOffset(avifRWStream * stream, size_t offset); - -void avifRWStreamFinishWrite(avifRWStream * stream); -// The following functions require byte alignment. -avifResult avifRWStreamWrite(avifRWStream * stream, const void * data, size_t size); -avifResult avifRWStreamWriteChars(avifRWStream * stream, const char * chars, size_t size); -// On success, if marker is not null, *marker contains the offset of the size -// field in stream and should be passed to avifRWStreamFinishBox(). -avifResult avifRWStreamWriteBox(avifRWStream * stream, const char * type, size_t contentSize, avifBoxMarker * marker); -// On success, if marker is not null, *marker contains the offset of the size -// field in stream and should be passed to avifRWStreamFinishBox(). -avifResult avifRWStreamWriteFullBox(avifRWStream * stream, const char * type, size_t contentSize, int version, uint32_t flags, avifBoxMarker * marker); -// marker is the offset of the size field in stream, returned by a previous -// avifRWStreamWriteBox() or avifRWStreamWriteFullBox() call. -AVIF_NODISCARD avifResult avifRWStreamFinishBox(avifRWStream * stream, avifBoxMarker marker); -avifResult avifRWStreamWriteU8(avifRWStream * stream, uint8_t v); -avifResult avifRWStreamWriteU16(avifRWStream * stream, uint16_t v); -avifResult avifRWStreamWriteU32(avifRWStream * stream, uint32_t v); -avifResult avifRWStreamWriteU64(avifRWStream * stream, uint64_t v); -avifResult avifRWStreamWriteZeros(avifRWStream * stream, size_t byteCount); -// The following functions can write non-aligned bits. -avifResult avifRWStreamWriteBits(avifRWStream * stream, uint32_t v, size_t bitCount); - -// This is to make it clear that the box size is currently unknown, and will be determined later (with a call to avifRWStreamFinishBox) -#define AVIF_BOX_SIZE_TBD 0 - -// Used for both av1C and av2C. -typedef struct avifCodecConfigurationBox -{ - // [skipped; is constant] unsigned int (1)marker = 1; - // [skipped; is constant] unsigned int (7)version = 1; - - uint8_t seqProfile; // unsigned int (3) seq_profile; - uint8_t seqLevelIdx0; // unsigned int (5) seq_level_idx_0; - uint8_t seqTier0; // unsigned int (1) seq_tier_0; - uint8_t highBitdepth; // unsigned int (1) high_bitdepth; - uint8_t twelveBit; // unsigned int (1) twelve_bit; - uint8_t monochrome; // unsigned int (1) monochrome; - uint8_t chromaSubsamplingX; // unsigned int (1) chroma_subsampling_x; - uint8_t chromaSubsamplingY; // unsigned int (1) chroma_subsampling_y; - uint8_t chromaSamplePosition; // unsigned int (2) chroma_sample_position; - - // 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; - // } -} avifCodecConfigurationBox; - -typedef struct avifSequenceHeader -{ - uint8_t reduced_still_picture_header; - uint32_t maxWidth; - uint32_t maxHeight; - uint32_t bitDepth; - avifPixelFormat yuvFormat; - avifChromaSamplePosition chromaSamplePosition; - avifColorPrimaries colorPrimaries; - avifTransferCharacteristics transferCharacteristics; - avifMatrixCoefficients matrixCoefficients; - avifRange range; - avifCodecConfigurationBox av1C; // TODO(yguyon): Rename or add av2C -} avifSequenceHeader; - -AVIF_NODISCARD avifBool avifSequenceHeaderParse(avifSequenceHeader * header, const avifROData * sample, avifCodecType codecType); - -#if defined(AVIF_ENABLE_EXPERIMENTAL_EXTENDED_PIXI) -// Subsampling type as defined in ISO/IEC 23008-12:2024/CDAM 2:2025 section 6.5.6.3. -typedef enum avifPixiSubsamplingType -{ - AVIF_PIXI_444 = 0, - AVIF_PIXI_422 = 1, - AVIF_PIXI_420 = 2, - AVIF_PIXI_411 = 3, - AVIF_PIXI_440 = 4, - AVIF_PIXI_SUBSAMPLING_RESERVED = 5, -} avifPixiSubsamplingType; - -// Mapping from subsampling_x, subsampling_y as defined in AV1 specification Section 6.4.2 -// to PixelInformationBox subsampling_type as defined in ISO/IEC 23008-12:2024/CDAM 2:2025 section 6.5.6.3. -uint8_t avifCodecConfigurationBoxGetSubsamplingType(const avifCodecConfigurationBox * av1C, uint8_t channelIndex); -#endif - -// --------------------------------------------------------------------------- -// gain maps - -// Initializes avifGainMap to default values. -void avifGainMapSetDefaults(avifGainMap * gainMap); - -// Finds the approximate min/max values from the given gain map values, excluding outliers. -// Uses a histogram, with outliers defined as having at least one empty bucket between them -// and the rest of the distribution. Discards at most 0.1% of values. -// Removing outliers helps with accuracy/compression. -avifResult avifFindMinMaxWithoutOutliers(const float * gainMapF, size_t numPixels, float * rangeMin, float * rangeMax); - -avifResult avifGainMapValidateMetadata(const avifGainMap * gainMap, avifDiagnostics * diag); - -// Returns true if both gain maps have the same metadata. Pixels are not checked. -avifBool avifSameGainMapMetadata(const avifGainMap * a, const avifGainMap * b); -avifBool avifSameGainMapAltMetadata(const avifGainMap * a, const avifGainMap * b); - -#define AVIF_INDEFINITE_DURATION64 UINT64_MAX -#define AVIF_INDEFINITE_DURATION32 UINT32_MAX - #ifdef __cplusplus } // extern "C" #endif -#endif // ifndef AVIF_INTERNAL_H +#endif //AVIF_CODEC_H From cb814601746f61aba7bd686c40f2f1f53f3d3716 Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Sun, 27 Sep 2026 12:50:00 +0800 Subject: [PATCH 3/5] Remove codec definition from internal.h --- include/avif/internal.h | 143 +--------------------------------------- 1 file changed, 1 insertion(+), 142 deletions(-) diff --git a/include/avif/internal.h b/include/avif/internal.h index 81d5239727..8dcf47a13e 100644 --- a/include/avif/internal.h +++ b/include/avif/internal.h @@ -4,7 +4,7 @@ #ifndef AVIF_INTERNAL_H #define AVIF_INTERNAL_H -#include "avif/avif.h" // IWYU pragma: export +#include "avif/codec.h" // IWYU pragma: export #ifdef __cplusplus extern "C" { @@ -133,19 +133,6 @@ AVIF_NODISCARD avifBool avifColorPrimariesComputeRGBToRGBMatrix(avifColorPrimari // The output values are not clamped and may be < 0 or > 1. void avifLinearRGBConvertColorSpace(float rgb[4], double coeffs[3][3]); -#define AVIF_ARRAY_DECLARE(TYPENAME, ITEMSTYPE, ITEMSNAME) \ - typedef struct TYPENAME \ - { \ - ITEMSTYPE * ITEMSNAME; \ - uint32_t elementSize; \ - uint32_t count; \ - uint32_t capacity; \ - } TYPENAME -AVIF_NODISCARD avifBool avifArrayCreate(void * arrayStruct, uint32_t elementSize, uint32_t initialCapacity); -AVIF_NODISCARD void * avifArrayPush(void * arrayStruct); -void avifArrayPop(void * arrayStruct); -void avifArrayDestroy(void * arrayStruct); - void avifFractionSimplify(avifFraction * f); // Makes the fractions have a common denominator. AVIF_NODISCARD avifBool avifFractionCD(avifFraction * a, avifFraction * b); @@ -464,22 +451,6 @@ uint8_t avifImageIrotImirToExifOrientation(const avifImage * image); // --------------------------------------------------------------------------- // avifCodecDecodeInput -// Legal spatial_id values are [0,1,2,3], so this serves as a sentinel value for "do not filter by spatial_id" -#define AVIF_SPATIAL_ID_UNSET 0xff - -typedef struct avifDecodeSample -{ - avifROData data; - avifBool ownsData; - avifBool partialData; // if true, data exists but doesn't have all of the sample in it - - uint32_t itemID; // if non-zero, data comes from a mergedExtents buffer in an avifDecoderItem, not a file offset - uint64_t offset; // additional offset into data. Can be used to offset into an itemID's payload as well. - size_t size; // - uint8_t spatialID; // If set to a value other than AVIF_SPATIAL_ID_UNSET, output frames from this sample should be - // skipped until the output frame's spatial_id matches this ID. - avifBool sync; // is sync sample (keyframe) -} avifDecodeSample; AVIF_ARRAY_DECLARE(avifDecodeSampleArray, avifDecodeSample, sample); typedef struct avifCodecDecodeInput @@ -492,35 +463,9 @@ typedef struct avifCodecDecodeInput AVIF_NODISCARD avifCodecDecodeInput * avifCodecDecodeInputCreate(void); void avifCodecDecodeInputDestroy(avifCodecDecodeInput * decodeInput); -// --------------------------------------------------------------------------- -// avifCodecEncodeOutput - -typedef struct avifEncodeSample -{ - avifRWData data; - avifBool sync; // is sync sample (keyframe) -} avifEncodeSample; -AVIF_ARRAY_DECLARE(avifEncodeSampleArray, avifEncodeSample, sample); - -typedef struct avifCodecEncodeOutput -{ - avifEncodeSampleArray samples; -} avifCodecEncodeOutput; - -AVIF_NODISCARD avifCodecEncodeOutput * avifCodecEncodeOutputCreate(void); -avifResult avifCodecEncodeOutputAddSample(avifCodecEncodeOutput * encodeOutput, const uint8_t * data, size_t len, avifBool sync); -void avifCodecEncodeOutputDestroy(avifCodecEncodeOutput * encodeOutput); - // --------------------------------------------------------------------------- // avifCodecSpecificOptions (key/value string pairs for advanced tuning) -typedef struct avifCodecSpecificOption -{ - char * key; // Must be a simple lowercase alphanumeric string - char * value; // Free-form string to be interpreted by the codec -} avifCodecSpecificOption; -AVIF_ARRAY_DECLARE(avifCodecSpecificOptions, avifCodecSpecificOption, entries); - // Returns NULL if a memory allocation failed. AVIF_NODISCARD avifCodecSpecificOptions * avifCodecSpecificOptionsCreate(void); void avifCodecSpecificOptionsClear(avifCodecSpecificOptions * csOptions); @@ -531,98 +476,12 @@ avifResult avifCodecSpecificOptionsSet(avifCodecSpecificOptions * csOptions, con // --------------------------------------------------------------------------- // avifCodecType (underlying video format) -// Alliance for Open Media video formats that can be used in the AVIF image format. -typedef enum avifCodecType -{ - AVIF_CODEC_TYPE_UNKNOWN, - AVIF_CODEC_TYPE_AV1, -#if defined(AVIF_CODEC_AVM) - AVIF_CODEC_TYPE_AV2, // Experimental. -#endif -} avifCodecType; - // Returns AVIF_CODEC_TYPE_UNKNOWN unless the chosen codec is available with the requiredFlags. avifCodecType avifCodecTypeFromChoice(avifCodecChoice choice, avifCodecFlags requiredFlags); // --------------------------------------------------------------------------- // avifCodec (abstraction layer to use different codec implementations) -struct avifCodec; -struct avifCodecInternal; - -typedef enum avifEncoderChange -{ - AVIF_ENCODER_CHANGE_MIN_QUANTIZER = (1 << 0), - AVIF_ENCODER_CHANGE_MAX_QUANTIZER = (1 << 1), - AVIF_ENCODER_CHANGE_MIN_QUANTIZER_ALPHA = (1 << 2), - AVIF_ENCODER_CHANGE_MAX_QUANTIZER_ALPHA = (1 << 3), - AVIF_ENCODER_CHANGE_TILE_ROWS_LOG2 = (1 << 4), - AVIF_ENCODER_CHANGE_TILE_COLS_LOG2 = (1 << 5), - AVIF_ENCODER_CHANGE_QUALITY = (1 << 6), - AVIF_ENCODER_CHANGE_QUALITY_ALPHA = (1 << 7), - AVIF_ENCODER_CHANGE_SCALING_MODE = (1 << 8), - - AVIF_ENCODER_CHANGE_CODEC_SPECIFIC = (1 << 30) -} avifEncoderChange; -typedef int avifEncoderChanges; - -typedef avifBool (*avifCodecGetNextImageFunc)(struct avifCodec * codec, - const avifDecodeSample * sample, - avifBool alpha, - avifBool * isLimitedRangeAlpha, - avifImage * image); -// EncodeImage and EncodeFinish are not required to always emit a sample, but when all images are -// encoded and EncodeFinish is called, the number of samples emitted must match the number of submitted frames. -// avifCodecEncodeImageFunc may return AVIF_RESULT_UNKNOWN_ERROR to automatically emit the appropriate -// AVIF_RESULT_ENCODE_COLOR_FAILED or AVIF_RESULT_ENCODE_ALPHA_FAILED depending on the alpha argument. -// avifCodecEncodeImageFunc should use tileRowsLog2 and tileColsLog2 instead of -// encoder->tileRowsLog2, encoder->tileColsLog2, and encoder->autoTiling. The caller of -// avifCodecEncodeImageFunc is responsible for automatic tiling if encoder->autoTiling is set to -// AVIF_TRUE. The actual tiling values are passed to avifCodecEncodeImageFunc as parameters. -// Similarly, avifCodecEncodeImageFunc should use the quality parameter instead of -// encoder->quality, encoder->qualityAlpha, and encoder->qualityGainMap. If disableLaggedOutput is AVIF_TRUE, then -// the encoder will emit the output frame without any lag (if supported). Note that disableLaggedOutput is only -// used by the first call to this function (which initializes the encoder) and is ignored by the subsequent calls. -// -// Note: The caller of avifCodecEncodeImageFunc always passes encoder->data->tileRowsLog2 and -// encoder->data->tileColsLog2 as the tileRowsLog2 and tileColsLog2 arguments. Because -// encoder->data is of a struct type defined in src/write.c, avifCodecEncodeImageFunc cannot -// dereference encoder->data and has to receive encoder->data->tileRowsLog2 and -// encoder->data->tileColsLog2 via function parameters. -typedef avifResult (*avifCodecEncodeImageFunc)(struct avifCodec * codec, - avifEncoder * encoder, - const avifImage * image, - avifBool alpha, - int tileRowsLog2, - int tileColsLog2, - int quality, - avifEncoderChanges encoderChanges, - avifBool disableLaggedOutput, - avifAddImageFlags addImageFlags, - avifCodecEncodeOutput * output); -typedef avifBool (*avifCodecEncodeFinishFunc)(struct avifCodec * codec, avifCodecEncodeOutput * output); -typedef void (*avifCodecDestroyInternalFunc)(struct avifCodec * codec); - -typedef struct avifCodec -{ - const avifCodecSpecificOptions * csOptions; // Contains codec-specific key/value pairs for advanced tuning. - // This array is NOT owned by avifCodec. - struct avifCodecInternal * internal; // up to each codec to use how it wants - avifDiagnostics * diag; // Shallow copy; owned by avifEncoder or avifDecoder - - // Decoder options (for getNextImage): - int maxThreads; // See avifDecoder::maxThreads. - uint32_t imageSizeLimit; // See avifDecoder::imageSizeLimit. - uint32_t imageDimensionLimit; // See avifDecoder::imageDimensionLimit. - uint8_t operatingPoint; // Operating point, defaults to 0. - avifBool allLayers; // if true, the underlying codec must decode all layers, not just the best layer - - avifCodecGetNextImageFunc getNextImage; - avifCodecEncodeImageFunc encodeImage; - avifCodecEncodeFinishFunc encodeFinish; - avifCodecDestroyInternalFunc destroyInternal; -} avifCodec; - avifResult avifCodecCreate(avifCodecChoice choice, avifCodecFlags requiredFlags, avifCodec ** codec); void avifCodecDestroy(avifCodec * codec); From 113c4953b93dad0ba0fd503d07d4cbe4b5d03f9f Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Fri, 6 Mar 2026 20:41:16 +0800 Subject: [PATCH 4/5] Export APIs used by custom codecs --- include/avif/codec.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/include/avif/codec.h b/include/avif/codec.h index 60b69d5fb5..cd3978e6ae 100644 --- a/include/avif/codec.h +++ b/include/avif/codec.h @@ -21,10 +21,10 @@ extern "C" { uint32_t count; \ uint32_t capacity; \ } TYPENAME -AVIF_NODISCARD avifBool avifArrayCreate(void * arrayStruct, uint32_t elementSize, uint32_t initialCapacity); -AVIF_NODISCARD void * avifArrayPush(void * arrayStruct); -void avifArrayPop(void * arrayStruct); -void avifArrayDestroy(void * arrayStruct); +AVIF_NODISCARD AVIF_API avifBool avifArrayCreate(void * arrayStruct, uint32_t elementSize, uint32_t initialCapacity); +AVIF_NODISCARD AVIF_API void * avifArrayPush(void * arrayStruct); +AVIF_API void avifArrayPop(void * arrayStruct); +AVIF_API void avifArrayDestroy(void * arrayStruct); // --------------------------------------------------------------------------- // avifDecodeSample @@ -61,9 +61,9 @@ typedef struct avifCodecEncodeOutput avifEncodeSampleArray samples; } avifCodecEncodeOutput; -AVIF_NODISCARD avifCodecEncodeOutput * avifCodecEncodeOutputCreate(void); -avifResult avifCodecEncodeOutputAddSample(avifCodecEncodeOutput * encodeOutput, const uint8_t * data, size_t len, avifBool sync); -void avifCodecEncodeOutputDestroy(avifCodecEncodeOutput * encodeOutput); +AVIF_NODISCARD AVIF_API avifCodecEncodeOutput * avifCodecEncodeOutputCreate(void); +AVIF_API avifResult avifCodecEncodeOutputAddSample(avifCodecEncodeOutput * encodeOutput, const uint8_t * data, size_t len, avifBool sync); +AVIF_API void avifCodecEncodeOutputDestroy(avifCodecEncodeOutput * encodeOutput); // --------------------------------------------------------------------------- // avifCodecSpecificOptions (key/value string pairs for advanced tuning) From 7b4cd1c98dda9bd68c54af70bc9bb6d8ace53aa6 Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Fri, 6 Mar 2026 20:41:16 +0800 Subject: [PATCH 5/5] Add custom codec registry & loader --- CMakeLists.txt | 7 +- apps/avifdec.c | 116 ++++++++++++----- apps/avifenc.c | 67 ++++++++-- apps/shared/avifutil.c | 183 ++++++++++++++++++++++++++- apps/shared/avifutil.h | 15 ++- include/avif/apps.h | 37 ++++++ include/avif/avif.h | 4 +- include/avif/codec.h | 37 ++++++ src/avif.c | 86 ++++++++++--- tests/CMakeLists.txt | 15 +++ tests/customcodec/stub_codec.c | 173 +++++++++++++++++++++++++ tests/customcodec/stub_codec.h | 46 +++++++ tests/customcodec/stub_plugin.c | 25 ++++ tests/data/custom_codec_stub_8x8.y4m | 3 + tests/gtest/avifcustomcodectest.cc | 128 +++++++++++++++++++ tests/test_cmd_custom_codec.sh | 38 ++++++ 16 files changed, 911 insertions(+), 69 deletions(-) create mode 100644 include/avif/apps.h create mode 100644 tests/customcodec/stub_codec.c create mode 100644 tests/customcodec/stub_codec.h create mode 100644 tests/customcodec/stub_plugin.c create mode 100644 tests/data/custom_codec_stub_8x8.y4m create mode 100644 tests/gtest/avifcustomcodectest.cc create mode 100644 tests/test_cmd_custom_codec.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index ae2e6ec0dc..281b902c16 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -666,6 +666,9 @@ if(AVIF_BUILD_APPS OR (AVIF_BUILD_TESTS AND (AVIF_FUZZTEST OR AVIF_GTEST))) if(AVIF_ENABLE_JPEG_GAIN_MAP_CONVERSION) target_link_libraries(avif_apps${suffix} PRIVATE LibXml2::LibXml2) endif() + if(UNIX AND NOT APPLE) + target_link_libraries(avif_apps${suffix} PRIVATE ${CMAKE_DL_LIBS}) # for dlsym + endif() target_include_directories(avif_apps${suffix} INTERFACE apps/shared) # In GitHub CI's macos-latest os image, /usr/local/include has not only the headers of libpng # and libjpeg but also the headers of an older version of libavif. Put the avif include @@ -843,7 +846,9 @@ if(NOT SKIP_INSTALL_LIBRARIES AND NOT SKIP_INSTALL_ALL) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libavif.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) endif() if(NOT SKIP_INSTALL_HEADERS AND NOT SKIP_INSTALL_ALL) - install(FILES include/avif/avif.h include/avif/avif_cxx.h DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/avif") + install(FILES include/avif/avif.h include/avif/apps.h include/avif/avif_cxx.h include/avif/codec.h + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/avif" + ) endif() # --------------------------------------------------------------------------------------- diff --git a/apps/avifdec.c b/apps/avifdec.c index 5f269cd3af..93932743e8 100644 --- a/apps/avifdec.c +++ b/apps/avifdec.c @@ -29,31 +29,32 @@ static void syntax(void) printf("Syntax: avifdec [options] input.avif output.[jpg|jpeg|png|y4m]\n"); printf(" avifdec --info input.avif\n"); printf("Options:\n"); - printf(" -h,--help : Show syntax help\n"); - printf(" -V,--version : Show the version number\n"); - printf(" -j,--jobs J : Number of jobs (worker threads), or 'all' to potentially use as many cores as possible. (Default: all)\n"); - printf(" -c,--codec C : Codec to use (choose from versions list below)\n"); - printf(" -d,--depth D : Output depth, either 8 or 16. (PNG only; For y4m, depth is retained, and JPEG is always 8bpc)\n"); - printf(" --sato : Enable Sample Transforms decoding (e.g. 16-bit AVIF)\n"); - printf(" -q,--quality Q : Output quality in 0..100. (JPEG only, default: %d)\n", DEFAULT_JPEG_QUALITY); - printf(" --png-compress L : PNG compression level in 0..9 (PNG only; 0=none, 9=max). Defaults to libpng's builtin default\n"); - printf(" -u,--upsampling U : Chroma upsampling (for 420/422). One of 'automatic' (default), 'fastest', 'best', 'nearest', or 'bilinear'\n"); - printf(" -r,--raw-color : Output raw RGB values instead of multiplying by alpha when saving to opaque formats\n"); - printf(" (JPEG only; not applicable to y4m)\n"); - printf(" --index I : When decoding an image sequence or progressive image, specify which frame index to decode, where the first frame has index 0, or 'all' to decode all frames. (Default: 0)\n"); - printf(" --progressive : Enable progressive AVIF processing. If a progressive image is encountered and --progressive is passed,\n"); - printf(" avifdec will use --index to choose which layer to decode (in progressive order).\n"); - printf(" --no-strict : Disable strict decoding, which disables strict validation checks and errors\n"); - printf(" -i,--info : Decode all frames and display all image information instead of saving to disk\n"); - printf(" --ignore-exif : If the input file contains embedded Exif metadata, ignore it (no-op if absent)\n"); - printf(" --ignore-xmp : If the input file contains embedded XMP metadata, ignore it (no-op if absent)\n"); - printf(" --icc FILENAME : Provide an ICC profile payload (implies --ignore-icc)\n"); - printf(" --ignore-icc : If the input file contains an embedded ICC profile, ignore it (no-op if absent)\n"); - printf(" --size-limit C : Maximum image size (in total pixels) that should be tolerated.\n"); - printf(" 0 means unlimited. (Default: %u)\n", AVIF_DEFAULT_IMAGE_SIZE_LIMIT); - printf(" --dimension-limit C : Maximum image dimension (width or height) that should be tolerated.\n"); - printf(" 0 means unlimited. (Default: %u)\n", AVIF_DEFAULT_IMAGE_DIMENSION_LIMIT); - printf(" -- : Signal the end of options. Everything after this is interpreted as file names.\n"); + printf(" -h,--help : Show syntax help\n"); + printf(" -V,--version : Show the version number\n"); + printf(" -j,--jobs J : Number of jobs (worker threads), or 'all' to potentially use as many cores as possible. (Default: all)\n"); + printf(" -c,--codec C : Codec to use (choose from versions list below, or name of the custom codec loaded)\n"); + printf(" --custom-codec LIBRARY : Load a custom codec shared library\n"); + printf(" -d,--depth D : Output depth, either 8 or 16. (PNG only; For y4m, depth is retained, and JPEG is always 8bpc)\n"); + printf(" --sato : Enable Sample Transforms decoding (e.g. 16-bit AVIF)\n"); + printf(" -q,--quality Q : Output quality in 0..100. (JPEG only, default: %d)\n", DEFAULT_JPEG_QUALITY); + printf(" --png-compress L : PNG compression level in 0..9 (PNG only; 0=none, 9=max). Defaults to libpng's builtin default\n"); + printf(" -u,--upsampling U : Chroma upsampling (for 420/422). One of 'automatic' (default), 'fastest', 'best', 'nearest', or 'bilinear'\n"); + printf(" -r,--raw-color : Output raw RGB values instead of multiplying by alpha when saving to opaque formats\n"); + printf(" (JPEG only; not applicable to y4m)\n"); + printf(" --index I : When decoding an image sequence or progressive image, specify which frame index to decode, where the first frame has index 0, or 'all' to decode all frames. (Default: 0)\n"); + printf(" --progressive : Enable progressive AVIF processing. If a progressive image is encountered and --progressive is passed,\n"); + printf(" avifdec will use --index to choose which layer to decode (in progressive order).\n"); + printf(" --no-strict : Disable strict decoding, which disables strict validation checks and errors\n"); + printf(" -i,--info : Decode all frames and display all image information instead of saving to disk\n"); + printf(" --ignore-exif : If the input file contains embedded Exif metadata, ignore it (no-op if absent)\n"); + printf(" --ignore-xmp : If the input file contains embedded XMP metadata, ignore it (no-op if absent)\n"); + printf(" --icc FILENAME : Provide an ICC profile payload (implies --ignore-icc)\n"); + printf(" --ignore-icc : If the input file contains an embedded ICC profile, ignore it (no-op if absent)\n"); + printf(" --size-limit C : Maximum image size (in total pixels) that should be tolerated.\n"); + printf(" 0 means unlimited. (Default: %u)\n", AVIF_DEFAULT_IMAGE_SIZE_LIMIT); + printf(" --dimension-limit C : Maximum image dimension (width or height) that should be tolerated.\n"); + printf(" 0 means unlimited. (Default: %u)\n", AVIF_DEFAULT_IMAGE_DIMENSION_LIMIT); + printf(" -- : Signal the end of options. Everything after this is interpreted as file names.\n"); printf("\n"); avifPrintVersions(); } @@ -110,6 +111,9 @@ int main(int argc, char * argv[]) uint32_t imageSizeLimit = AVIF_DEFAULT_IMAGE_SIZE_LIMIT; uint32_t imageDimensionLimit = AVIF_DEFAULT_IMAGE_DIMENSION_LIMIT; avifRWData iccOverride = AVIF_DATA_EMPTY; + const char * requestedCodecName = NULL; + const char * customCodecLibraryName = NULL; + avifCustomCodecLibrary customCodecLibrary = { 0 }; if (argc < 2) { syntax(); @@ -154,19 +158,16 @@ int main(int argc, char * argv[]) jobs = 1; } } - } else if (!strcmp(arg, "-c") || !strcmp(arg, "--codec")) { + } else if (!strcmp(arg, "--custom-codec")) { NEXTARG(); - codecChoice = avifCodecChoiceFromName(arg); - if (codecChoice == AVIF_CODEC_CHOICE_AUTO) { - fprintf(stderr, "ERROR: Unrecognized codec: %s\n", arg); + if (customCodecLibraryName) { + fprintf(stderr, "ERROR: --custom-codec may only be specified once\n"); return 1; - } else { - const char * codecName = avifCodecName(codecChoice, AVIF_CODEC_FLAG_CAN_DECODE); - if (codecName == NULL) { - fprintf(stderr, "ERROR: Codec cannot decode: %s\n", arg); - return 1; - } } + customCodecLibraryName = arg; + } else if (!strcmp(arg, "-c") || !strcmp(arg, "--codec")) { + NEXTARG(); + requestedCodecName = arg; } else if (!strcmp(arg, "-d") || !strcmp(arg, "--depth")) { NEXTARG(); requestedDepth = atoi(arg); @@ -318,6 +319,32 @@ int main(int argc, char * argv[]) fprintf(stderr, "Memory allocation failure\n"); goto cleanup; } + + if (customCodecLibraryName) { + avifDiagnostics diag; + avifDiagnosticsClearError(&diag); + if (!avifCustomCodecLibrarySetup(&customCodecLibrary, customCodecLibraryName, &diag)) { + fprintf(stderr, + "ERROR: Failed to load custom codec shared library %s: %s\n", + customCodecLibraryName, + diag.error[0] ? diag.error : "unknown error"); + goto cleanup; + } + } + + if (requestedCodecName) { + codecChoice = avifCodecChoiceFromName(requestedCodecName); + if (codecChoice == AVIF_CODEC_CHOICE_AUTO) { + fprintf(stderr, "ERROR: Unrecognized codec: %s\n", requestedCodecName); + goto cleanup; + } + const char * codecName = avifCodecName(codecChoice, AVIF_CODEC_FLAG_CAN_DECODE); + if (codecName == NULL) { + fprintf(stderr, "ERROR: Codec cannot decode: %s\n", requestedCodecName); + goto cleanup; + } + } + decoder->maxThreads = jobs; decoder->codecChoice = codecChoice; decoder->imageSizeLimit = imageSizeLimit; @@ -506,5 +533,24 @@ int main(int argc, char * argv[]) avifDecoderDestroy(decoder); } avifRWDataFree(&iccOverride); + if (customCodecLibrary.handle) { + avifBool unloadCustomCodecLibrary = AVIF_TRUE; + if (customCodecLibrary.initialized) { + avifDiagnostics diag; + avifDiagnosticsClearError(&diag); + const avifResult shutdownResult = avifCustomCodecLibraryShutdown(&customCodecLibrary, &diag); + if (shutdownResult != AVIF_RESULT_OK) { + fprintf(stderr, + "ERROR: Failed to shut down custom codec shared library %s: %s\n", + customCodecLibrary.name ? customCodecLibrary.name : "(unknown)", + diag.error[0] ? diag.error : avifResultToString(shutdownResult)); + returnCode = 1; + unloadCustomCodecLibrary = AVIF_FALSE; + } + } + if (unloadCustomCodecLibrary) { + avifCustomCodecLibraryUnload(&customCodecLibrary); + } + } return returnCode; } diff --git a/apps/avifenc.c b/apps/avifenc.c index c3b37d0359..5d3ce442e8 100644 --- a/apps/avifenc.c +++ b/apps/avifenc.c @@ -249,7 +249,8 @@ static void syntaxLong(void) printf(" -g,--grid MxN : Encode a single-image grid AVIF with M cols & N rows. Either supply MxN identical W/H/D images, or a single\n"); printf(" image that can be evenly split into the MxN grid and follow AVIF grid image restrictions. The grid will adopt\n"); printf(" the color profile of the first image supplied.\n"); - printf(" -c,--codec C : Codec to use (choose from versions list below)\n"); + printf(" -c,--codec C : Codec to use (choose from versions list below, or name of the custom codec loaded)\n"); + printf(" --custom-codec LIBRARY : Load a custom codec shared library\n"); printf(" --exif FILENAME : Provide an Exif metadata payload to be associated with the primary item (implies --ignore-exif)\n"); printf(" --xmp FILENAME : Provide an XMP metadata payload to be associated with the primary item (implies --ignore-xmp)\n"); printf(" --icc FILENAME : Provide an ICC profile payload to be associated with the primary item (implies --ignore-icc)\n"); @@ -1483,6 +1484,9 @@ int main(int argc, char * argv[]) } const char * outputFilename = NULL; + const char * requestedCodecName = NULL; + const char * customCodecLibraryName = NULL; + avifCustomCodecLibrary customCodecLibrary = { 0 }; avifInput input; memset(&input, 0, sizeof(input)); @@ -1976,19 +1980,16 @@ int main(int argc, char * argv[]) } #endif settings.modificationTime = (uint64_t)modificationTime; - } else if (!strcmp(arg, "-c") || !strcmp(arg, "--codec")) { + } else if (!strcmp(arg, "--custom-codec")) { NEXTARG(); - settings.codecChoice = avifCodecChoiceFromName(arg); - if (settings.codecChoice == AVIF_CODEC_CHOICE_AUTO) { - fprintf(stderr, "ERROR: Unrecognized codec: %s\n", arg); + if (customCodecLibraryName) { + fprintf(stderr, "ERROR: --custom-codec may only be specified once\n"); goto cleanup; - } else { - const char * codecName = avifCodecName(settings.codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE); - if (codecName == NULL) { - fprintf(stderr, "ERROR: Codec cannot encode: %s\n", arg); - goto cleanup; - } } + customCodecLibraryName = arg; + } else if (!strcmp(arg, "-c") || !strcmp(arg, "--codec")) { + NEXTARG(); + requestedCodecName = arg; } else if (!strcmp(arg, "-a") || !strcmp(arg, "--advanced") || strpre(arg, "-a:") || strpre(arg, "--advanced:")) { avifOptionSuffixType type = parseOptionSuffix(arg, input.filesCount != 0); if (type == AVIF_OPTION_SUFFIX_INVALID) { @@ -2109,6 +2110,31 @@ int main(int argc, char * argv[]) settings.jobs = avifQueryCPUCount(); } + if (customCodecLibraryName) { + avifDiagnostics diag; + avifDiagnosticsClearError(&diag); + if (!avifCustomCodecLibrarySetup(&customCodecLibrary, customCodecLibraryName, &diag)) { + fprintf(stderr, + "ERROR: Failed to load custom codec shared library %s: %s\n", + customCodecLibraryName, + diag.error[0] ? diag.error : "unknown error"); + goto cleanup; + } + } + + if (requestedCodecName) { + settings.codecChoice = avifCodecChoiceFromName(requestedCodecName); + if (settings.codecChoice == AVIF_CODEC_CHOICE_AUTO) { + fprintf(stderr, "ERROR: Unrecognized codec: %s\n", requestedCodecName); + goto cleanup; + } + const char * codecName = avifCodecName(settings.codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE); + if (codecName == NULL) { + fprintf(stderr, "ERROR: Codec cannot encode: %s\n", requestedCodecName); + goto cleanup; + } + } + // Check global lossless parameters and set to default if needed. if (lossless) { // Pixel format. @@ -2817,6 +2843,25 @@ int main(int argc, char * argv[]) avifCodecSpecificOptionsFree(&file->settings.codecSpecificOptions); } free(input.files); + if (customCodecLibrary.handle) { + avifBool unloadCustomCodecLibrary = AVIF_TRUE; + if (customCodecLibrary.initialized) { + avifDiagnostics diag; + avifDiagnosticsClearError(&diag); + const avifResult shutdownResult = avifCustomCodecLibraryShutdown(&customCodecLibrary, &diag); + if (shutdownResult != AVIF_RESULT_OK) { + fprintf(stderr, + "ERROR: Failed to shut down custom codec shared library %s: %s\n", + customCodecLibrary.name ? customCodecLibrary.name : "(unknown)", + diag.error[0] ? diag.error : avifResultToString(shutdownResult)); + returnCode = 1; + unloadCustomCodecLibrary = AVIF_FALSE; + } + } + if (unloadCustomCodecLibrary) { + avifCustomCodecLibraryUnload(&customCodecLibrary); + } + } return returnCode; } diff --git a/apps/shared/avifutil.c b/apps/shared/avifutil.c index 6a370fc39d..7b2539d0a8 100644 --- a/apps/shared/avifutil.c +++ b/apps/shared/avifutil.c @@ -5,14 +5,195 @@ #include #include +#include #include #include #include +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#else +#include +#endif + #include "avifjpeg.h" #include "avifpng.h" #include "y4m.h" +static void avifSetDiagnostic(avifDiagnostics * diag, const char * fmt, ...) +{ + if (!diag) { + return; + } + + avifDiagnosticsClearError(diag); + va_list args; + va_start(args, fmt); + vsnprintf(diag->error, sizeof(diag->error), fmt, args); + va_end(args); +} + +static const char * avifCustomCodecLibraryError(char buffer[], size_t bufferSize) +{ +#if defined(_WIN32) + const DWORD error = GetLastError(); + if (error == 0) { + snprintf(buffer, bufferSize, "unknown error"); + return buffer; + } + + DWORD messageLength = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + buffer, + (DWORD)bufferSize, + NULL); + if (messageLength == 0) { + snprintf(buffer, bufferSize, "error 0x%08lx", (unsigned long)error); + return buffer; + } + while ((messageLength > 0) && ((buffer[messageLength - 1] == '\r') || (buffer[messageLength - 1] == '\n'))) { + buffer[--messageLength] = '\0'; + } + return buffer; +#else + const char * error = dlerror(); + if (!error) { + error = "unknown error"; + } + snprintf(buffer, bufferSize, "%s", error); + return buffer; +#endif +} + +#if defined(_WIN32) +static avifBool avifCustomCodecLibraryLoadFunction(HMODULE module, + const char * moduleName, + const char * symbolName, + void * function, + avifDiagnostics * diag) +{ + char errorBuffer[256]; + FARPROC proc = GetProcAddress(module, symbolName); + if (!proc) { + avifSetDiagnostic(diag, + "failed loading function %s from module %s: %s", + symbolName, + moduleName, + avifCustomCodecLibraryError(errorBuffer, sizeof(errorBuffer))); + return AVIF_FALSE; + } + + // MinGW/GCC warns on direct FARPROC-to-function-pointer casts. + memcpy(function, &proc, sizeof(proc)); + return AVIF_TRUE; +} +#endif + +void avifCustomCodecLibraryUnload(avifCustomCodecLibrary * library) +{ + if (!library) { + return; + } + if (library->handle) { +#if defined(_WIN32) + FreeLibrary((HMODULE)library->handle); +#else + dlclose(library->handle); +#endif + } + memset(library, 0, sizeof(*library)); +} + +static avifBool avifCustomCodecLibraryLoad(avifCustomCodecLibrary * library, const char * name, avifDiagnostics * diag) +{ + char errorBuffer[256]; + + memset(library, 0, sizeof(*library)); + library->name = name; +#if defined(_WIN32) + library->handle = LoadLibraryA(name); +#else + dlerror(); + library->handle = dlopen(name, RTLD_NOW | RTLD_LOCAL); +#endif + if (!library->handle) { + avifSetDiagnostic(diag, "failed loading module %s: %s", name, avifCustomCodecLibraryError(errorBuffer, sizeof(errorBuffer))); + return AVIF_FALSE; + } + +#if defined(_WIN32) + if (!avifCustomCodecLibraryLoadFunction((HMODULE)library->handle, name, AVIF_APPS_CUSTOM_CODEC_SETUP_SYMBOL, &library->setup, diag)) { + avifCustomCodecLibraryUnload(library); + return AVIF_FALSE; + } + if (!avifCustomCodecLibraryLoadFunction((HMODULE)library->handle, name, AVIF_APPS_CUSTOM_CODEC_SHUTDOWN_SYMBOL, &library->shutdown, diag)) { + avifCustomCodecLibraryUnload(library); + return AVIF_FALSE; + } +#else + dlerror(); + library->setup = (avifAppsCustomCodecSetupFunc)dlsym(library->handle, AVIF_APPS_CUSTOM_CODEC_SETUP_SYMBOL); + { + const char * loadError = dlerror(); + if (loadError) { + avifSetDiagnostic(diag, "failed loading function %s from module %s: %s", AVIF_APPS_CUSTOM_CODEC_SETUP_SYMBOL, name, loadError); + avifCustomCodecLibraryUnload(library); + return AVIF_FALSE; + } + } + dlerror(); + library->shutdown = (avifAppsCustomCodecShutdownFunc)dlsym(library->handle, AVIF_APPS_CUSTOM_CODEC_SHUTDOWN_SYMBOL); + { + const char * loadError = dlerror(); + if (loadError) { + avifSetDiagnostic(diag, "failed loading function %s from module %s: %s", AVIF_APPS_CUSTOM_CODEC_SHUTDOWN_SYMBOL, name, loadError); + avifCustomCodecLibraryUnload(library); + return AVIF_FALSE; + } + } +#endif + + return AVIF_TRUE; +} + +avifBool avifCustomCodecLibrarySetup(avifCustomCodecLibrary * library, const char * name, avifDiagnostics * diag) +{ + if (!avifCustomCodecLibraryLoad(library, name, diag)) { + return AVIF_FALSE; + } + + avifDiagnosticsClearError(diag); + const avifResult result = library->setup(diag); + if (result != AVIF_RESULT_OK) { + if (!diag->error[0]) { + avifSetDiagnostic(diag, "custom codec setup failed: %s", avifResultToString(result)); + } + avifCustomCodecLibraryUnload(library); + return AVIF_FALSE; + } + + library->initialized = AVIF_TRUE; + return AVIF_TRUE; +} + +avifResult avifCustomCodecLibraryShutdown(avifCustomCodecLibrary * library, avifDiagnostics * diag) +{ + if (!library || !library->initialized) { + return AVIF_RESULT_OK; + } + avifDiagnosticsClearError(diag); + const avifResult result = library->shutdown(diag); + if (result == AVIF_RESULT_OK) { + library->initialized = AVIF_FALSE; + } + return result; +} + char * avifFileFormatToString(avifAppFileFormat format) { switch (format) { @@ -461,8 +642,6 @@ void avifDumpDiagnostics(const avifDiagnostics * diag) // Windows -#include - int avifQueryCPUCount(void) { int numCPU; diff --git a/apps/shared/avifutil.h b/apps/shared/avifutil.h index bec09ab83e..427e1472c1 100644 --- a/apps/shared/avifutil.h +++ b/apps/shared/avifutil.h @@ -4,7 +4,7 @@ #ifndef LIBAVIF_APPS_SHARED_AVIFUTIL_H #define LIBAVIF_APPS_SHARED_AVIFUTIL_H -#include "avif/avif.h" +#include "avif/apps.h" #ifdef __cplusplus extern "C" { @@ -36,6 +36,19 @@ void avifPrintVersions(void); void avifDumpDiagnostics(const avifDiagnostics * diag); int avifQueryCPUCount(void); // Returns 1 if it cannot query or fails to query +typedef struct avifCustomCodecLibrary +{ + void * handle; + const char * name; + avifAppsCustomCodecSetupFunc setup; + avifAppsCustomCodecShutdownFunc shutdown; + avifBool initialized; +} avifCustomCodecLibrary; + +AVIF_NODISCARD avifBool avifCustomCodecLibrarySetup(avifCustomCodecLibrary * library, const char * name, avifDiagnostics * diag); +AVIF_NODISCARD avifResult avifCustomCodecLibraryShutdown(avifCustomCodecLibrary * library, avifDiagnostics * diag); +void avifCustomCodecLibraryUnload(avifCustomCodecLibrary * library); + typedef enum avifAppFileFormat { AVIF_APP_FILE_FORMAT_UNKNOWN = 0, diff --git a/include/avif/apps.h b/include/avif/apps.h new file mode 100644 index 0000000000..c799c7c3b6 --- /dev/null +++ b/include/avif/apps.h @@ -0,0 +1,37 @@ +// Copyright 2026 Yuan Tong. All rights reserved. +// SPDX-License-Identifier: BSD-2-Clause + +#ifndef AVIF_APPS_H +#define AVIF_APPS_H + +#include "avif/avif.h" // IWYU pragma: export + +#ifdef __cplusplus +extern "C" { +#endif + +// A shared library intended to be loaded by avifenc/avifdec with --custom-codec must export both +// functions below. Only works with build using shared libavif. +// +// Setup is called once after the library is loaded and before the codec name is resolved. The +// custom codec should initialize its private states and call avifRegisterCustomCodec(). +// Shutdown is called once before the library is unloaded, if Setup succeeded. The custom codec can +// use that as an opportunity to clean up their own state. Codecs stay registered after Shutdown, +// but the apps do not use them anymore at that point. +#define AVIF_APPS_CUSTOM_CODEC_SETUP_SYMBOL "avifAppsCustomCodecSetup" +#define AVIF_APPS_CUSTOM_CODEC_SHUTDOWN_SYMBOL "avifAppsCustomCodecShutdown" + +// These functions always need to be exported. +#define AVIF_APPS_CUSTOM_CODEC_API AVIF_HELPER_EXPORT + +typedef avifResult (*avifAppsCustomCodecSetupFunc)(avifDiagnostics * diag); +typedef avifResult (*avifAppsCustomCodecShutdownFunc)(avifDiagnostics * diag); + +AVIF_APPS_CUSTOM_CODEC_API avifResult avifAppsCustomCodecSetup(avifDiagnostics * diag); +AVIF_APPS_CUSTOM_CODEC_API avifResult avifAppsCustomCodecShutdown(avifDiagnostics * diag); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // AVIF_APPS_H diff --git a/include/avif/avif.h b/include/avif/avif.h index 92ea16132e..5a728d6839 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -1062,7 +1062,9 @@ typedef enum avifCodecChoice AVIF_CODEC_CHOICE_LIBGAV1, // Decode only AVIF_CODEC_CHOICE_RAV1E, // Encode only AVIF_CODEC_CHOICE_SVT, // Encode only - AVIF_CODEC_CHOICE_AVM // Experimental (AV2) + AVIF_CODEC_CHOICE_AVM, // Experimental (AV2) + + AVIF_CODEC_CHOICE_CUSTOM_BASE = 0x10000 } avifCodecChoice; typedef enum avifCodecFlag diff --git a/include/avif/codec.h b/include/avif/codec.h index cd3978e6ae..595f6657b8 100644 --- a/include/avif/codec.h +++ b/include/avif/codec.h @@ -167,6 +167,43 @@ typedef struct avifCodec avifCodecDestroyInternalFunc destroyInternal; } avifCodec; +// --------------------------------------------------------------------------- +// Codec registry + +typedef const char * (*versionFunc)(void); + +// The returned avifCodec struct must be allocated with avifAlloc(). +typedef avifCodec * (*avifCodecCreateFunc)(void); + +typedef struct avifCodecInformation +{ + // Output + avifCodecChoice choice; // Codec ID assigned by libavif. + + // Input + avifCodecType type; + const char * name; // Codec name, must be unique. + versionFunc version; + avifCodecCreateFunc create; + avifCodecFlags flags; +} avifCodecInformation; + +// Registers a custom codec. On success, codec->choice is set to the avifCodecChoice assigned to the +// codec, which can then be used as avifEncoder::codecChoice or avifDecoder::codecChoice. +// A custom codec is never picked by AVIF_CODEC_CHOICE_AUTO, and its avifCodecChoice value may +// differ between runs, so look it up with avifCodecChoiceFromName() instead of storing it. +// +// Custom codecs are meant to be registered once, early in the process, and used for the rest of +// its lifetime: +// - codec->name, codec->version and codec->create are stored as is and must stay valid until the +// process exits. If they live in a dynamically loaded library, that library must not be unloaded +// while libavif may still use them. +// - Registration is not thread-safe. Register all custom codecs before any avifEncoder or +// avifDecoder may be used concurrently, and do not register from multiple threads at once. +// - There's a limit on how many custom codecs can be registered. Prefer register once and dispatch +// inside your codec implementation. +AVIF_API avifResult avifRegisterCustomCodec(avifCodecInformation * codec); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/avif.c b/src/avif.c index 9b04f4e787..2de3efe649 100644 --- a/src/avif.c +++ b/src/avif.c @@ -1169,22 +1169,9 @@ avifResult avifCodecSpecificOptionsSet(avifCodecSpecificOptions * csOptions, con // --------------------------------------------------------------------------- // Codec availability and versions -typedef const char * (*versionFunc)(void); -typedef avifCodec * (*avifCodecCreateFunc)(void); - -struct AvailableCodec -{ - avifCodecChoice choice; - avifCodecType type; - 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[] = { +static struct avifCodecInformation availableCodecs[] = { // Ordered by preference (for AUTO) #if defined(AVIF_CODEC_DAV1D) @@ -1224,8 +1211,49 @@ static struct AvailableCodec availableCodecs[] = { static const int availableCodecsCount = (sizeof(availableCodecs) / sizeof(availableCodecs[0])) - 1; -static struct AvailableCodec * findAvailableCodec(avifCodecChoice choice, avifCodecFlags requiredFlags) +// --------------------------------------------------------------------------- +// Custom codec registry + +#define MAX_CUSTOM_CODECS 128 + +static avifCodecInformation customCodecs[MAX_CUSTOM_CODECS]; +static int customCodecCount = 0; +static avifCodecChoice nextCustomChoice = AVIF_CODEC_CHOICE_CUSTOM_BASE; + +avifResult avifRegisterCustomCodec(avifCodecInformation * codec) +{ + if (!codec || !codec->name || !codec->version || !codec->create) { + return AVIF_RESULT_INVALID_ARGUMENT; + } + + if (customCodecCount >= MAX_CUSTOM_CODECS) { + return AVIF_RESULT_OUT_OF_MEMORY; + } + + // Check if a codec with the same name already exists + for (int i = 0; i < availableCodecsCount; ++i) { + if (availableCodecs[i].name && !strcmp(availableCodecs[i].name, codec->name)) { + return AVIF_RESULT_INVALID_ARGUMENT; + } + } + for (int i = 0; i < customCodecCount; ++i) { + if (!strcmp(customCodecs[i].name, codec->name)) { + return AVIF_RESULT_INVALID_ARGUMENT; + } + } + + // Assign a custom choice value and register + customCodecs[customCodecCount] = *codec; + customCodecs[customCodecCount].choice = nextCustomChoice++; + codec->choice = customCodecs[customCodecCount].choice; + customCodecCount++; + + return AVIF_RESULT_OK; +} + +static struct avifCodecInformation * findavifCodecInformation(avifCodecChoice choice, avifCodecFlags requiredFlags) { + // First search built-in codecs for (int i = 0; i < availableCodecsCount; ++i) { if ((choice != AVIF_CODEC_CHOICE_AUTO) && (availableCodecs[i].choice != choice)) { continue; @@ -1239,12 +1267,29 @@ static struct AvailableCodec * findAvailableCodec(avifCodecChoice choice, avifCo } return &availableCodecs[i]; } + + if (choice == AVIF_CODEC_CHOICE_AUTO) { + // Custom codec cannot be the default, it must be explicitly selected. + return NULL; + } + + // Then search custom codecs + for (int i = 0; i < customCodecCount; ++i) { + if (customCodecs[i].choice != choice) { + continue; + } + if (requiredFlags && ((customCodecs[i].flags & requiredFlags) != requiredFlags)) { + continue; + } + return &customCodecs[i]; + } + return NULL; } const char * avifCodecName(avifCodecChoice choice, avifCodecFlags requiredFlags) { - struct AvailableCodec * availableCodec = findAvailableCodec(choice, requiredFlags); + struct avifCodecInformation * availableCodec = findavifCodecInformation(choice, requiredFlags); if (availableCodec) { return availableCodec->name; } @@ -1253,7 +1298,7 @@ const char * avifCodecName(avifCodecChoice choice, avifCodecFlags requiredFlags) avifCodecType avifCodecTypeFromChoice(avifCodecChoice choice, avifCodecFlags requiredFlags) { - struct AvailableCodec * availableCodec = findAvailableCodec(choice, requiredFlags); + struct avifCodecInformation * availableCodec = findavifCodecInformation(choice, requiredFlags); if (availableCodec) { return availableCodec->type; } @@ -1267,13 +1312,18 @@ avifCodecChoice avifCodecChoiceFromName(const char * name) return availableCodecs[i].choice; } } + for (int i = 0; i < customCodecCount; ++i) { + if (!strcmp(customCodecs[i].name, name)) { + return customCodecs[i].choice; + } + } return AVIF_CODEC_CHOICE_AUTO; } avifResult avifCodecCreate(avifCodecChoice choice, avifCodecFlags requiredFlags, avifCodec ** codec) { *codec = NULL; - struct AvailableCodec * availableCodec = findAvailableCodec(choice, requiredFlags); + struct avifCodecInformation * availableCodec = findavifCodecInformation(choice, requiredFlags); AVIF_CHECKERR(availableCodec != NULL, AVIF_RESULT_NO_CODEC_AVAILABLE); *codec = availableCodec->create(); AVIF_CHECKERR(*codec != NULL, AVIF_RESULT_OUT_OF_MEMORY); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8a3dd797a9..06d943ba92 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -88,6 +88,9 @@ if(AVIF_GTEST) add_avif_gtest(avifclaptest) add_avif_gtest(avifcllitest) add_avif_gtest(avifcodectest) + add_avif_gtest(avifcustomcodectest) + target_sources(avifcustomcodectest PRIVATE customcodec/stub_codec.c) + target_include_directories(avifcustomcodectest PRIVATE customcodec) add_avif_gtest_with_data(avifcolrconverttest) add_avif_gtest(avifcolrtest) add_avif_gtest_with_data(avifdecodetest) @@ -271,6 +274,18 @@ if(AVIF_BUILD_APPS) add_cmd_test(test_cmd_transform ${CMAKE_CURRENT_SOURCE_DIR}/data) add_cmd_test(test_cmd_avifgainmaputil ${CMAKE_CURRENT_SOURCE_DIR}/data) + # The custom codec library must share the libavif registry with the apps. Only the shared libavif build + # guarantees that: the static build hides libavif symbols (C_VISIBILITY_PRESET hidden), so the apps cannot + # export them to the library. + if(BUILD_SHARED_LIBS) + add_library(avif_custom_codec_stub MODULE customcodec/stub_codec.c customcodec/stub_plugin.c) + target_link_libraries(avif_custom_codec_stub PRIVATE avif avif_enable_warnings) + add_cmd_test(test_cmd_custom_codec ${CMAKE_CURRENT_SOURCE_DIR}/data) + set_tests_properties( + test_cmd_custom_codec PROPERTIES ENVIRONMENT "AVIF_CUSTOM_CODEC_STUB=$" + ) + endif() + if(NOT AVIF_ENABLE_JPEG_GAIN_MAP_CONVERSION) set_tests_properties(test_cmd_stdin PROPERTIES DISABLED True) endif() diff --git a/tests/customcodec/stub_codec.c b/tests/customcodec/stub_codec.c new file mode 100644 index 0000000000..a0084807a9 --- /dev/null +++ b/tests/customcodec/stub_codec.c @@ -0,0 +1,173 @@ +// Copyright 2026 Yuan Tong. All rights reserved. +// SPDX-License-Identifier: BSD-2-Clause + +#include "stub_codec.h" + +#include + +// clang-format off +static const uint8_t stubBitstream[] = { + // OBU_SEQUENCE_HEADER, has_size_field, size 5: + // seq_profile 0, reduced_still_picture_header, seq_level_idx 0, max_frame_width/height 8, + // 8-bit, not monochrome, color_range 0, chroma_sample_position 0, followed by trailing bits. + 0x0A, 0x05, 0x18, 0x08, 0xBF, 0x00, 0x02, + // OBU_PADDING, has_size_field, size 16: magic payload. + 0x7A, 0x10, 'a', 'v', 'i', 'f', ' ', 'c', 'u', 's', 't', 'o', 'm', ' ', 's', 't', 'u', 'b', +}; +// clang-format on + +static avifStubCodecStats stats; + +uint8_t avifStubCodecPixel(int plane, uint32_t x, uint32_t y) +{ + switch (plane) { + case AVIF_CHAN_Y: + return (uint8_t)(16 + 8 * y + x); + case AVIF_CHAN_U: + return (uint8_t)(96 + 4 * y + x); + default: + return (uint8_t)(160 + 4 * y + x); + } +} + +static avifBool stubImageMatches(const avifImage * image) +{ + if (image->width != AVIF_STUB_CODEC_WIDTH || image->height != AVIF_STUB_CODEC_HEIGHT || + image->depth != AVIF_STUB_CODEC_DEPTH || image->yuvFormat != AVIF_STUB_CODEC_FORMAT) { + return AVIF_FALSE; + } + for (int plane = AVIF_CHAN_Y; plane <= AVIF_CHAN_V; ++plane) { + const uint32_t width = avifImagePlaneWidth(image, plane); + const uint32_t height = avifImagePlaneHeight(image, plane); + const uint32_t rowBytes = avifImagePlaneRowBytes(image, plane); + const uint8_t * pixels = avifImagePlane(image, plane); + for (uint32_t y = 0; y < height; ++y) { + for (uint32_t x = 0; x < width; ++x) { + if (pixels[y * rowBytes + x] != avifStubCodecPixel(plane, x, y)) { + return AVIF_FALSE; + } + } + } + } + return AVIF_TRUE; +} + +static avifResult stubCodecEncodeImage(avifCodec * codec, + avifEncoder * encoder, + const avifImage * image, + avifBool alpha, + int tileRowsLog2, + int tileColsLog2, + int quality, + avifEncoderChanges encoderChanges, + avifBool disableLaggedOutput, + avifAddImageFlags addImageFlags, + avifCodecEncodeOutput * output) +{ + (void)codec; + (void)encoder; + (void)tileRowsLog2; + (void)tileColsLog2; + (void)quality; + (void)encoderChanges; + (void)disableLaggedOutput; + (void)addImageFlags; + + if (alpha || !stubImageMatches(image)) { + return AVIF_RESULT_UNKNOWN_ERROR; + } + const avifResult result = avifCodecEncodeOutputAddSample(output, stubBitstream, sizeof(stubBitstream), AVIF_TRUE); + if (result == AVIF_RESULT_OK) { + ++stats.encodedFrames; + } + return result; +} + +static avifBool stubCodecEncodeFinish(avifCodec * codec, avifCodecEncodeOutput * output) +{ + (void)codec; + (void)output; + return AVIF_TRUE; +} + +static avifBool stubCodecGetNextImage(avifCodec * codec, + const avifDecodeSample * sample, + avifBool alpha, + avifBool * isLimitedRangeAlpha, + avifImage * image) +{ + (void)codec; + (void)isLimitedRangeAlpha; + + if (alpha || !sample || sample->data.size != sizeof(stubBitstream) || + memcmp(sample->data.data, stubBitstream, sizeof(stubBitstream)) != 0) { + return AVIF_FALSE; + } + + avifImageFreePlanes(image, AVIF_PLANES_YUV); + image->width = AVIF_STUB_CODEC_WIDTH; + image->height = AVIF_STUB_CODEC_HEIGHT; + image->depth = AVIF_STUB_CODEC_DEPTH; + image->yuvFormat = AVIF_STUB_CODEC_FORMAT; + if (avifImageAllocatePlanes(image, AVIF_PLANES_YUV) != AVIF_RESULT_OK) { + return AVIF_FALSE; + } + for (int plane = AVIF_CHAN_Y; plane <= AVIF_CHAN_V; ++plane) { + const uint32_t width = avifImagePlaneWidth(image, plane); + const uint32_t height = avifImagePlaneHeight(image, plane); + const uint32_t rowBytes = avifImagePlaneRowBytes(image, plane); + uint8_t * pixels = avifImagePlane(image, plane); + for (uint32_t y = 0; y < height; ++y) { + for (uint32_t x = 0; x < width; ++x) { + pixels[y * rowBytes + x] = avifStubCodecPixel(plane, x, y); + } + } + } + ++stats.decodedFrames; + return AVIF_TRUE; +} + +static const char * stubCodecVersion(void) +{ + return "1.0"; +} + +static avifCodec * stubCodecCreate(void) +{ + // libavif releases the codec with avifFree(), so it must be allocated with avifAlloc(). + avifCodec * codec = (avifCodec *)avifAlloc(sizeof(avifCodec)); + if (!codec) { + return NULL; + } + memset(codec, 0, sizeof(*codec)); + codec->getNextImage = stubCodecGetNextImage; + codec->encodeImage = stubCodecEncodeImage; + codec->encodeFinish = stubCodecEncodeFinish; + return codec; +} + +avifResult avifStubCodecRegister(avifCodecChoice * choice) +{ + const avifCodecChoice existingChoice = avifCodecChoiceFromName(AVIF_STUB_CODEC_NAME); + if (existingChoice != AVIF_CODEC_CHOICE_AUTO) { + *choice = existingChoice; + return AVIF_RESULT_OK; + } + + avifCodecInformation info = { 0 }; + info.type = AVIF_CODEC_TYPE_AV1; + info.name = AVIF_STUB_CODEC_NAME; + info.version = stubCodecVersion; + info.create = stubCodecCreate; + info.flags = AVIF_CODEC_FLAG_CAN_DECODE | AVIF_CODEC_FLAG_CAN_ENCODE; + const avifResult result = avifRegisterCustomCodec(&info); + if (result == AVIF_RESULT_OK) { + *choice = info.choice; + } + return result; +} + +avifStubCodecStats avifStubCodecGetStats(void) +{ + return stats; +} diff --git a/tests/customcodec/stub_codec.h b/tests/customcodec/stub_codec.h new file mode 100644 index 0000000000..b7d55255d8 --- /dev/null +++ b/tests/customcodec/stub_codec.h @@ -0,0 +1,46 @@ +// Copyright 2026 Yuan Tong. All rights reserved. +// SPDX-License-Identifier: BSD-2-Clause + +// A stub custom codec used to test the custom codec registry. It only uses the public libavif API, +// so it can be linked statically into a test or built as a shared library loaded by the apps. +// +// The encoder only accepts the fixed input image described below and always emits the same +// hardcoded AV1 bitstream. The decoder only accepts that bitstream and always outputs the fixed +// image. A successful round trip therefore proves both requests reached the stub codec. + +#ifndef LIBAVIF_TESTS_CUSTOMCODEC_STUB_CODEC_H +#define LIBAVIF_TESTS_CUSTOMCODEC_STUB_CODEC_H + +#include "avif/codec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define AVIF_STUB_CODEC_NAME "stub" + +// The fixed image: 8x8, 8-bit, YUV 4:2:0. +#define AVIF_STUB_CODEC_WIDTH 8 +#define AVIF_STUB_CODEC_HEIGHT 8 +#define AVIF_STUB_CODEC_DEPTH 8 +#define AVIF_STUB_CODEC_FORMAT AVIF_PIXEL_FORMAT_YUV420 +// Sample value of the fixed image at (x, y) of the given plane (AVIF_CHAN_Y, AVIF_CHAN_U or AVIF_CHAN_V). +uint8_t avifStubCodecPixel(int plane, uint32_t x, uint32_t y); + +typedef struct avifStubCodecStats +{ + uint32_t encodedFrames; + uint32_t decodedFrames; +} avifStubCodecStats; + +// Registers the stub codec, or returns the existing registration if a codec named +// AVIF_STUB_CODEC_NAME is already registered. +avifResult avifStubCodecRegister(avifCodecChoice * choice); +// Number of frames that went through the stub encoder and decoder so far. +avifStubCodecStats avifStubCodecGetStats(void); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // LIBAVIF_TESTS_CUSTOMCODEC_STUB_CODEC_H diff --git a/tests/customcodec/stub_plugin.c b/tests/customcodec/stub_plugin.c new file mode 100644 index 0000000000..985796c7e6 --- /dev/null +++ b/tests/customcodec/stub_plugin.c @@ -0,0 +1,25 @@ +// Copyright 2026 Yuan Tong. All rights reserved. +// SPDX-License-Identifier: BSD-2-Clause + +// Exposes the stub codec to avifenc/avifdec through --custom-codec. + +#include + +#include "avif/apps.h" +#include "stub_codec.h" + +avifResult avifAppsCustomCodecSetup(avifDiagnostics * diag) +{ + (void)diag; + avifCodecChoice choice; + return avifStubCodecRegister(&choice); +} + +avifResult avifAppsCustomCodecShutdown(avifDiagnostics * diag) +{ + (void)diag; + const avifStubCodecStats stats = avifStubCodecGetStats(); + // Checked by test_cmd_custom_codec.sh. + printf("Custom codec stub: encoded %u frame(s), decoded %u frame(s)\n", stats.encodedFrames, stats.decodedFrames); + return AVIF_RESULT_OK; +} diff --git a/tests/data/custom_codec_stub_8x8.y4m b/tests/data/custom_codec_stub_8x8.y4m new file mode 100644 index 0000000000..853b5bd757 --- /dev/null +++ b/tests/data/custom_codec_stub_8x8.y4m @@ -0,0 +1,3 @@ +YUV4MPEG2 W8 H8 F25:1 Ip A0:0 C420jpeg +FRAME + !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO`abcdefghijklmno ¡¢£¤¥¦§¨©ª«¬­®¯ \ No newline at end of file diff --git a/tests/gtest/avifcustomcodectest.cc b/tests/gtest/avifcustomcodectest.cc new file mode 100644 index 0000000000..5df9112450 --- /dev/null +++ b/tests/gtest/avifcustomcodectest.cc @@ -0,0 +1,128 @@ +// Copyright 2026 Yuan Tong. All rights reserved. +// SPDX-License-Identifier: BSD-2-Clause + +#include "avif/avif.h" +#include "avif/codec.h" +#include "aviftest_helpers.h" +#include "gtest/gtest.h" +#include "stub_codec.h" + +namespace avif { +namespace { + +// Custom codec registry is process global, so the stub codec is registered once +// for all tests. +class CustomCodecTest : public testing::Test { + protected: + static void SetUpTestSuite() { + ASSERT_EQ(avifStubCodecRegister(&stub_choice_), AVIF_RESULT_OK); + } + + static avifCodecChoice stub_choice_; +}; + +avifCodecChoice CustomCodecTest::stub_choice_ = AVIF_CODEC_CHOICE_AUTO; + +const char* DummyVersion() { return "0"; } +avifCodec* DummyCreate() { return nullptr; } + +TEST_F(CustomCodecTest, Register) { + // The stub codec got a custom choice and can be looked up by name. + EXPECT_GE(stub_choice_, AVIF_CODEC_CHOICE_CUSTOM_BASE); + EXPECT_EQ(avifCodecChoiceFromName(AVIF_STUB_CODEC_NAME), stub_choice_); + EXPECT_STREQ(avifCodecName(stub_choice_, AVIF_CODEC_FLAG_CAN_ENCODE), + AVIF_STUB_CODEC_NAME); + EXPECT_STREQ(avifCodecName(stub_choice_, AVIF_CODEC_FLAG_CAN_DECODE), + AVIF_STUB_CODEC_NAME); + + avifCodecInformation info = {}; + info.type = AVIF_CODEC_TYPE_AV1; + info.name = "encodeonly"; + info.version = DummyVersion; + info.create = DummyCreate; + info.flags = AVIF_CODEC_FLAG_CAN_ENCODE; + + // Missing fields. + EXPECT_EQ(avifRegisterCustomCodec(nullptr), AVIF_RESULT_INVALID_ARGUMENT); + avifCodecInformation invalid = info; + invalid.name = nullptr; + EXPECT_EQ(avifRegisterCustomCodec(&invalid), AVIF_RESULT_INVALID_ARGUMENT); + invalid = info; + invalid.version = nullptr; + EXPECT_EQ(avifRegisterCustomCodec(&invalid), AVIF_RESULT_INVALID_ARGUMENT); + invalid = info; + invalid.create = nullptr; + EXPECT_EQ(avifRegisterCustomCodec(&invalid), AVIF_RESULT_INVALID_ARGUMENT); + + // Duplicate names. + invalid = info; + invalid.name = AVIF_STUB_CODEC_NAME; + EXPECT_EQ(avifRegisterCustomCodec(&invalid), AVIF_RESULT_INVALID_ARGUMENT); + const char* built_in_name = avifCodecName(AVIF_CODEC_CHOICE_AUTO, 0); + if (built_in_name != nullptr) { + invalid.name = built_in_name; + EXPECT_EQ(avifRegisterCustomCodec(&invalid), AVIF_RESULT_INVALID_ARGUMENT); + } + + // The flags of a custom codec are honored. + info.choice = AVIF_CODEC_CHOICE_AUTO; + ASSERT_EQ(avifRegisterCustomCodec(&info), AVIF_RESULT_OK); + EXPECT_GE(info.choice, AVIF_CODEC_CHOICE_CUSTOM_BASE); + EXPECT_NE(info.choice, stub_choice_); + EXPECT_EQ(avifCodecChoiceFromName("encodeonly"), info.choice); + EXPECT_STREQ(avifCodecName(info.choice, AVIF_CODEC_FLAG_CAN_ENCODE), + "encodeonly"); + EXPECT_EQ(avifCodecName(info.choice, AVIF_CODEC_FLAG_CAN_DECODE), nullptr); +} + +TEST_F(CustomCodecTest, NotSelectedByAuto) { + for (avifCodecFlags flags : + {avifCodecFlags{0}, avifCodecFlags{AVIF_CODEC_FLAG_CAN_ENCODE}, + avifCodecFlags{AVIF_CODEC_FLAG_CAN_DECODE}}) { + const char* name = avifCodecName(AVIF_CODEC_CHOICE_AUTO, flags); + if (name != nullptr) { + EXPECT_STRNE(name, AVIF_STUB_CODEC_NAME); + } + } +} + +TEST_F(CustomCodecTest, EncodeDecode) { + ImagePtr image = testutil::CreateImage( + AVIF_STUB_CODEC_WIDTH, AVIF_STUB_CODEC_HEIGHT, AVIF_STUB_CODEC_DEPTH, + AVIF_STUB_CODEC_FORMAT, AVIF_PLANES_YUV); + ASSERT_NE(image, nullptr); + for (int plane = AVIF_CHAN_Y; plane <= AVIF_CHAN_V; ++plane) { + uint8_t* row = avifImagePlane(image.get(), plane); + for (uint32_t y = 0; y < avifImagePlaneHeight(image.get(), plane); ++y) { + for (uint32_t x = 0; x < avifImagePlaneWidth(image.get(), plane); ++x) { + row[x] = avifStubCodecPixel(plane, x, y); + } + row += avifImagePlaneRowBytes(image.get(), plane); + } + } + const avifStubCodecStats stats_before = avifStubCodecGetStats(); + + EncoderPtr encoder(avifEncoderCreate()); + ASSERT_NE(encoder, nullptr); + encoder->codecChoice = stub_choice_; + testutil::AvifRwData encoded; + ASSERT_EQ(avifEncoderWrite(encoder.get(), image.get(), &encoded), + AVIF_RESULT_OK); + EXPECT_EQ(avifStubCodecGetStats().encodedFrames, + stats_before.encodedFrames + 1); + + DecoderPtr decoder(avifDecoderCreate()); + ASSERT_NE(decoder, nullptr); + decoder->codecChoice = stub_choice_; + ImagePtr decoded(avifImageCreateEmpty()); + ASSERT_NE(decoded, nullptr); + ASSERT_EQ(avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data, + encoded.size), + AVIF_RESULT_OK); + EXPECT_EQ(avifStubCodecGetStats().decodedFrames, + stats_before.decodedFrames + 1); + EXPECT_TRUE(testutil::AreImagesEqual(*image, *decoded)); +} + +} // namespace +} // namespace avif diff --git a/tests/test_cmd_custom_codec.sh b/tests/test_cmd_custom_codec.sh new file mode 100644 index 0000000000..f3375d7f63 --- /dev/null +++ b/tests/test_cmd_custom_codec.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# Copyright 2026 Yuan Tong. All rights reserved. +# SPDX-License-Identifier: BSD-2-Clause +# ------------------------------------------------------------------------------ +# +# Tests loading a custom codec shared library in avifenc and avifdec. +# AVIF_CUSTOM_CODEC_STUB must be set to the path of the avif_custom_codec_stub library. + +source $(dirname "$0")/cmd_test_common.sh || exit + +# Input file paths. This image is the only input accepted by the stub encoder, +# and the only output of the stub decoder. +INPUT_Y4M="${TESTDATA_DIR}/custom_codec_stub_8x8.y4m" +# Output file names. +ENCODED_FILE="avif_test_cmd_custom_codec_encoded.avif" +DECODED_FILE="avif_test_cmd_custom_codec_decoded.y4m" +OUT_MSG="avif_test_cmd_custom_codec_out_msg.txt" + +# Cleanup +cleanup() { + pushd ${TMP_DIR} + rm -f -- "${ENCODED_FILE}" "${DECODED_FILE}" "${OUT_MSG}" + popd +} +trap cleanup EXIT + +pushd ${TMP_DIR} + echo "Testing encoding with a custom codec" + "${AVIFENC}" --custom-codec "${AVIF_CUSTOM_CODEC_STUB}" -c stub "${INPUT_Y4M}" -o "${ENCODED_FILE}" > "${OUT_MSG}" + grep -F "Custom codec stub: encoded 1 frame(s), decoded 0 frame(s)" "${OUT_MSG}" + + echo "Testing decoding with a custom codec" + "${AVIFDEC}" --custom-codec "${AVIF_CUSTOM_CODEC_STUB}" -c stub "${ENCODED_FILE}" "${DECODED_FILE}" > "${OUT_MSG}" + grep -F "Custom codec stub: encoded 0 frame(s), decoded 1 frame(s)" "${OUT_MSG}" + "${ARE_IMAGES_EQUAL}" "${INPUT_Y4M}" "${DECODED_FILE}" 0 +popd + +exit 0