Skip to content

Add AVIF plugin (decoder + encoder using libavif) #5201

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 64 commits into from
Apr 1, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
3878b58
Add AVIF plugin (using libavif)
fdintino Jan 3, 2021
e2add24
Added type hints (#2)
radarhere Oct 16, 2024
e5494a2
Fix PLAT envvar in cibuildwheel container
fdintino Oct 16, 2024
8b8bbba
Update Tests/check_avif_leaks.py
fdintino Oct 18, 2024
58ef692
Simplified code (#3)
radarhere Oct 19, 2024
d6a0a15
Merge branch 'main' into libavif-plugin
radarhere Nov 8, 2024
50b993a
Set default max threads in Python (#4)
radarhere Nov 11, 2024
671e3c8
Removed unused upsampling setting (#5)
radarhere Nov 12, 2024
658cdf3
Merge branch 'main' into libavif-plugin
radarhere Nov 18, 2024
7225cb9
Merge branch 'main' into libavif-plugin
radarhere Nov 24, 2024
3730bf2
Merge branch 'main' into libavif-plugin
radarhere Nov 30, 2024
c40bcbf
Improved error handling
radarhere Dec 2, 2024
d76ae2f
Do not ignore SyntaxError when saving EXIF data (#8)
radarhere Dec 7, 2024
9ad8311
Allow libavif to install rav1e, except on manylinux2014 and aarch64 (#7)
radarhere Dec 7, 2024
de4c6c1
Removed ld64 flag (#6)
radarhere Dec 7, 2024
524d802
fix: set exif orientation from irot/imir when decoding AVIF
fdintino Dec 9, 2024
7b73d77
Merge branch 'main' into libavif-plugin
radarhere Dec 13, 2024
a56acd8
Removed unittest mock (#10)
radarhere Dec 13, 2024
f5dc957
Use cmds_cmake (#9)
radarhere Dec 13, 2024
8d77678
chore(docs): update quality and speed with correct defaults
fdintino Dec 13, 2024
4eaa6b7
Merge branch 'main' into libavif-plugin
radarhere Dec 14, 2024
bdb24f9
Removed `_avif.HAVE_AVIF` and `_avif.VERSION` (#11)
radarhere Dec 15, 2024
ddc8e7e
Use "rav1e" if available as default ("auto") avif encoder
fdintino Dec 15, 2024
b585f9e
Simplified EXIF code (#12)
radarhere Dec 15, 2024
da2e18d
Revert "Use "rav1e" if available as default ("auto") avif encoder"
fdintino Dec 17, 2024
9b6e575
Merge branch 'main' into libavif-plugin
radarhere Dec 18, 2024
3a9a3ab
Reduced epsilons (#13)
radarhere Dec 24, 2024
9328932
Removed avifEncOptions (#14)
radarhere Jan 3, 2025
be02830
Merge branch 'main' into libavif-plugin
radarhere Jan 3, 2025
4135664
Merge branch 'main' into libavif-plugin
radarhere Jan 4, 2025
29c158d
Merge branch 'main' into libavif-plugin
radarhere Jan 8, 2025
4c63ea6
Fixed series of tuples as advanced argument (#15)
radarhere Jan 15, 2025
ce6bf21
Merge branch 'main' into libavif-plugin
radarhere Jan 17, 2025
4b29af4
Skip building libavif on 32-bit Windows (#16)
radarhere Jan 21, 2025
38f0d10
Merge branch 'main' into libavif-plugin
radarhere Jan 25, 2025
1410d23
Removed qmin and qmax (#17)
radarhere Jan 25, 2025
6cbad27
Merge branch 'main' into libavif-plugin
radarhere Feb 1, 2025
19ba2dd
Use rgb.rowBytes in overflow check (#18)
radarhere Feb 3, 2025
4508f37
Use aom LICENSE instead of PATENTS (#19)
radarhere Feb 3, 2025
7de1212
Merge branch 'main' into libavif-plugin
radarhere Feb 7, 2025
e1509ee
Removed memset and ignoreAlpha (#20)
radarhere Feb 9, 2025
0590f08
Handle avifDecoderCreate and avifEncoderCreate errors (#21)
radarhere Feb 12, 2025
5761b44
Merge branch 'main' into libavif-plugin
radarhere Feb 14, 2025
38b9941
Sort formats alphabetically
radarhere Feb 15, 2025
10dfa63
Simplified code
radarhere Feb 15, 2025
9abfdbc
Merge branch 'main' into libavif-plugin
radarhere Mar 3, 2025
d80ac3c
Use default PyTypeObject values
radarhere Feb 8, 2025
b4eec64
Merge branch 'main' into libavif-plugin
radarhere Mar 17, 2025
d552087
Updated libavif to 1.2.1 (#26)
radarhere Mar 17, 2025
79f7339
Updated wording
radarhere Mar 19, 2025
46f4508
Added version comments
radarhere Mar 19, 2025
9bebf37
Sort alphabetically
radarhere Mar 19, 2025
5da2113
Simplified code
radarhere Mar 19, 2025
0732554
Merge branch 'main' into libavif-plugin
radarhere Mar 21, 2025
9ea5e3d
Remove support for libavif < 1.0.0
radarhere Mar 21, 2025
fca6df2
Added get_codec_version() (#29)
radarhere Mar 21, 2025
024a894
Merge branch 'radarhere-avif_1' into libavif-plugin
fdintino Mar 21, 2025
2ba9356
Update rust (#33)
radarhere Mar 25, 2025
fdc68e6
Merge branch 'main' into libavif-plugin
radarhere Mar 31, 2025
9e63868
Continue to build libyuv on macOS
radarhere Mar 31, 2025
eff2680
Added release notes
radarhere Mar 31, 2025
fb096e1
Derive some avif test images from existing Pillow test images
fdintino Mar 31, 2025
1276543
Updated size
radarhere Mar 31, 2025
c8d0408
Continue to build libyuv on Linux
radarhere Mar 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Remove support for libavif < 1.0.0
  • Loading branch information
radarhere committed Mar 21, 2025
commit 9ea5e3dc664bd582ed08b3cea1d1d3300eb9a264
11 changes: 4 additions & 7 deletions docs/handbook/image-file-formats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,20 +63,17 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:

**tile_rows** / **tile_cols**
For tile encoding, the (log 2) number of tile rows and columns to use.
Valid values are 0-6, default 0. Ignored if "autotiling" is set to true in libavif
version **0.11.0** or greater.
Valid values are 0-6, default 0. Ignored if "autotiling" is set to true.

**autotiling**
Split the image up to allow parallelization. Enabled automatically if "tile_rows"
and "tile_cols" both have their default values of zero. Requires libavif version
**0.11.0** or greater.
and "tile_cols" both have their default values of zero.

**alpha_premultiplied**
Encode the image with premultiplied alpha. Defaults to ``False``. Requires libavif
version **0.9.0** or greater.
Encode the image with premultiplied alpha. Defaults to ``False``.

**advanced**
Codec specific options. Requires libavif version **0.8.2** or greater.
Codec specific options.

**icc_profile**
The ICC Profile to include in the saved file.
Expand Down
3 changes: 1 addition & 2 deletions docs/installation/building-from-source.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,7 @@ Many of Pillow's features require external libraries:

* **libavif** provides support for the AVIF format.

* Pillow requires libavif version **0.8.0** or greater, which is when
AVIF image sequence support was added.
* Pillow requires libavif version **1.0.0** or greater.
* libavif is merely an API that wraps AVIF codecs. If you are compiling
libavif from source, you will also need to install both an AVIF encoder
and decoder, such as rav1e and dav1d, or libaom, which both encodes and
Expand Down
56 changes: 2 additions & 54 deletions src/_avif.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,6 @@ typedef struct {

static PyTypeObject AvifDecoder_Type;

#if AVIF_VERSION < 1000000 // 1.0.0
static int
normalize_quantize_value(int qvalue) {
if (qvalue < AVIF_QUANTIZER_BEST_QUALITY) {
return AVIF_QUANTIZER_BEST_QUALITY;
} else if (qvalue > AVIF_QUANTIZER_WORST_QUALITY) {
return AVIF_QUANTIZER_WORST_QUALITY;
} else {
return qvalue;
}
}
#endif

static int
normalize_tiles_log2(int value) {
if (value < 0) {
Expand Down Expand Up @@ -62,12 +49,7 @@ exc_type_for_avif_result(avifResult result) {

static uint8_t
irot_imir_to_exif_orientation(const avifImage *image) {
uint8_t axis;
#if AVIF_VERSION_MAJOR >= 1
axis = image->imir.axis;
#else
axis = image->imir.mode;
#endif
uint8_t axis = image->imir.axis;
int imir = image->transformFlags & AVIF_TRANSFORM_IMIR;
int irot = image->transformFlags & AVIF_TRANSFORM_IROT;
if (irot) {
Expand Down Expand Up @@ -112,11 +94,7 @@ exif_orientation_to_irot_imir(avifImage *image, int orientation) {
case 2: // The 0th row is at the visual top of the image, and the 0th column is
// the visual right-hand side.
image->transformFlags |= AVIF_TRANSFORM_IMIR;
#if AVIF_VERSION_MAJOR >= 1
image->imir.axis = 1;
#else
image->imir.mode = 1;
#endif
break;
case 3: // The 0th row is at the visual bottom of the image, and the 0th column
// is the visual right-hand side.
Expand Down Expand Up @@ -182,7 +160,6 @@ _encoder_codec_available(PyObject *self, PyObject *args) {
return PyBool_FromLong(is_available);
}

#if AVIF_VERSION >= 80200 // 0.8.2
static int
_add_codec_specific_options(avifEncoder *encoder, PyObject *opts) {
Py_ssize_t i, size;
Expand Down Expand Up @@ -224,7 +201,6 @@ _add_codec_specific_options(avifEncoder *encoder, PyObject *opts) {
}
return 0;
}
#endif

// Encoder functions
PyObject *
Expand Down Expand Up @@ -318,9 +294,7 @@ AvifEncoderNew(PyObject *self_, PyObject *args) {
image->height = height;

image->depth = 8;
#if AVIF_VERSION >= 90000 // 0.9.0
image->alphaPremultiplied = alpha_premultiplied ? AVIF_TRUE : AVIF_FALSE;
#endif

encoder = avifEncoderCreate();
if (!encoder) {
Expand All @@ -334,12 +308,7 @@ AvifEncoderNew(PyObject *self_, PyObject *args) {
_codec_available("aom", AVIF_CODEC_FLAG_CAN_ENCODE));
encoder->maxThreads = is_aom_encode && max_threads > 64 ? 64 : max_threads;

#if AVIF_VERSION >= 1000000 // 1.0.0
encoder->quality = quality;
#else
encoder->minQuantizer = normalize_quantize_value(64 - quality);
encoder->maxQuantizer = normalize_quantize_value(100 - quality);
#endif

if (strcmp(codec, "auto") == 0) {
encoder->codecChoice = AVIF_CODEC_CHOICE_AUTO;
Expand All @@ -354,30 +323,15 @@ AvifEncoderNew(PyObject *self_, PyObject *args) {
encoder->speed = speed;
encoder->timescale = (uint64_t)1000;

#if AVIF_VERSION >= 110000 // 0.11.0
encoder->autoTiling = autotiling ? AVIF_TRUE : AVIF_FALSE;
if (!autotiling) {
encoder->tileRowsLog2 = normalize_tiles_log2(tile_rows_log2);
encoder->tileColsLog2 = normalize_tiles_log2(tile_cols_log2);
}
#else
encoder->tileRowsLog2 = normalize_tiles_log2(tile_rows_log2);
encoder->tileColsLog2 = normalize_tiles_log2(tile_cols_log2);
#endif

if (advanced != Py_None) {
#if AVIF_VERSION >= 80200 // 0.8.2
if (_add_codec_specific_options(encoder, advanced)) {
error = 1;
goto end;
}
#else
PyErr_SetString(
PyExc_ValueError, "Advanced codec options require libavif >= 0.8.2"
);
if (advanced != Py_None && _add_codec_specific_options(encoder, advanced)) {
error = 1;
goto end;
#endif
}

self = PyObject_New(AvifEncoderObject, &AvifEncoder_Type);
Expand Down Expand Up @@ -537,9 +491,7 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) {
frame->yuvRange = image->yuvRange;
frame->yuvFormat = image->yuvFormat;
frame->depth = image->depth;
#if AVIF_VERSION >= 90000 // 0.9.0
frame->alphaPremultiplied = image->alphaPremultiplied;
#endif
}

avifRGBImageSetDefaults(&rgb, frame);
Expand Down Expand Up @@ -689,17 +641,13 @@ AvifDecoderNew(PyObject *self_, PyObject *args) {
PyObject_Del(self);
return NULL;
}
#if AVIF_VERSION >= 80400 // 0.8.4
decoder->maxThreads = max_threads;
#endif
#if AVIF_VERSION >= 90200 // 0.9.2
// Turn off libavif's 'clap' (clean aperture) property validation.
decoder->strictFlags &= ~AVIF_STRICT_CLAP_VALID;
// Allow the PixelInformationProperty ('pixi') to be missing in AV1 image
// items. libheif v1.11.0 and older does not add the 'pixi' item property to
// AV1 image items.
decoder->strictFlags &= ~AVIF_STRICT_PIXI_REQUIRED;
#endif
decoder->codecChoice = codec;

result = avifDecoderSetIOMemory(decoder, buffer.buf, buffer.len);
Expand Down