Merge 15f13ef46e into 877527cefc
This commit is contained in:
commit
a1237d9fd0
@ -29,6 +29,7 @@ from .helper import (
|
||||
assert_image_similar_tofile,
|
||||
hopper,
|
||||
skip_unless_feature,
|
||||
skip_unless_feature_version,
|
||||
)
|
||||
|
||||
try:
|
||||
@ -46,7 +47,7 @@ def assert_xmp_orientation(xmp: bytes, expected: int) -> None:
|
||||
assert int(xmp.split(b'tiff:Orientation="')[1].split(b'"')[0]) == expected
|
||||
|
||||
|
||||
def roundtrip(im: ImageFile.ImageFile, **options: Any) -> ImageFile.ImageFile:
|
||||
def roundtrip(im: Image.Image, **options: Any) -> ImageFile.ImageFile:
|
||||
out = BytesIO()
|
||||
im.save(out, "AVIF", **options)
|
||||
return Image.open(out)
|
||||
@ -128,6 +129,14 @@ class TestFileAvif:
|
||||
image, "Tests/images/avif/hopper_avif_write.png", 11.5
|
||||
)
|
||||
|
||||
@skip_unless_feature_version("avif", "1.3.0")
|
||||
def test_write_l(self) -> None:
|
||||
im = hopper("L")
|
||||
reloaded = roundtrip(im)
|
||||
|
||||
assert reloaded.mode == "L"
|
||||
assert_image_similar(reloaded, im, 1.67)
|
||||
|
||||
def test_write_rgb(self, tmp_path: Path) -> None:
|
||||
"""
|
||||
Can we write a RGB mode file to avif without error?
|
||||
@ -420,6 +429,14 @@ class TestFileAvif:
|
||||
test_file = tmp_path / "temp.avif"
|
||||
im.save(test_file, subsampling=subsampling)
|
||||
|
||||
@skip_unless_feature_version("avif", "1.3.0")
|
||||
def test_encoding_subsampling_400(self) -> None:
|
||||
im = hopper()
|
||||
reloaded = roundtrip(im, subsampling="4:0:0")
|
||||
|
||||
assert reloaded.mode == "L"
|
||||
assert_image_similar(reloaded, im.convert("L"), 1.67)
|
||||
|
||||
def test_encoder_subsampling_invalid(self, tmp_path: Path) -> None:
|
||||
with Image.open(TEST_AVIF_FILE) as im:
|
||||
test_file = tmp_path / "temp.avif"
|
||||
|
||||
@ -38,7 +38,8 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
|
||||
quality, 100 the largest size and best quality.
|
||||
|
||||
**subsampling**
|
||||
If present, sets the subsampling for the encoder. Defaults to ``4:2:0``.
|
||||
If present, sets the subsampling for the encoder. If absent, and all frames are in
|
||||
grayscale mode without alpha, ``4:0:0`` is used. Otherwise defaults to ``4:2:0``.
|
||||
Options include:
|
||||
|
||||
* ``4:0:0``
|
||||
|
||||
@ -4,7 +4,7 @@ import os
|
||||
from io import BytesIO
|
||||
from typing import IO
|
||||
|
||||
from . import ExifTags, Image, ImageFile
|
||||
from . import ExifTags, Image, ImageFile, ImageSequence
|
||||
|
||||
try:
|
||||
from . import _avif
|
||||
@ -153,9 +153,12 @@ def _save(
|
||||
else:
|
||||
append_images = []
|
||||
|
||||
total = 0
|
||||
for ims in [im] + append_images:
|
||||
total += getattr(ims, "n_frames", 1)
|
||||
grayscale_modes = {"1", "L", "I", "I;16", "I;16L", "I;16B", "I;16N", "F"}
|
||||
grayscale = all(
|
||||
frame.mode in grayscale_modes
|
||||
for ims in [im] + append_images
|
||||
for frame in ImageSequence.Iterator(ims)
|
||||
)
|
||||
|
||||
quality = info.get("quality", 75)
|
||||
if not isinstance(quality, int) or quality < 0 or quality > 100:
|
||||
@ -163,7 +166,7 @@ def _save(
|
||||
raise ValueError(msg)
|
||||
|
||||
duration = info.get("duration", 0)
|
||||
subsampling = info.get("subsampling", "4:2:0")
|
||||
subsampling = info.get("subsampling", "4:0:0" if grayscale else "4:2:0")
|
||||
speed = info.get("speed", 6)
|
||||
max_threads = info.get("max_threads", _get_default_max_threads())
|
||||
codec = info.get("codec", "auto")
|
||||
@ -236,21 +239,20 @@ def _save(
|
||||
frame_idx = 0
|
||||
frame_duration = 0
|
||||
cur_idx = im.tell()
|
||||
is_single_frame = total == 1
|
||||
is_single_frame = not append_images and not getattr(im, "is_animated", False)
|
||||
try:
|
||||
for ims in [im] + append_images:
|
||||
# Get number of frames in this image
|
||||
nfr = getattr(ims, "n_frames", 1)
|
||||
|
||||
for idx in range(nfr):
|
||||
ims.seek(idx)
|
||||
|
||||
for frame in ImageSequence.Iterator(ims):
|
||||
# Make sure image mode is supported
|
||||
frame = ims
|
||||
rawmode = ims.mode
|
||||
if ims.mode not in {"RGB", "RGBA"}:
|
||||
rawmode = "RGBA" if ims.has_transparency_data else "RGB"
|
||||
frame = ims.convert(rawmode)
|
||||
rawmode = frame.mode
|
||||
if ims.mode not in {"L", "RGB", "RGBA"}:
|
||||
if ims.has_transparency_data:
|
||||
rawmode = "RGBA"
|
||||
elif ims.mode in grayscale_modes:
|
||||
rawmode = "L"
|
||||
else:
|
||||
rawmode = "RGB"
|
||||
frame = frame.convert(rawmode)
|
||||
|
||||
# Update frame duration
|
||||
if isinstance(duration, (list, tuple)):
|
||||
|
||||
27
src/_avif.c
27
src/_avif.c
@ -505,6 +505,10 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) {
|
||||
|
||||
if (strcmp(mode, "RGBA") == 0) {
|
||||
rgb.format = AVIF_RGB_FORMAT_RGBA;
|
||||
#if AVIF_VERSION >= 1030000 // 1.3.0
|
||||
} else if (strcmp(mode, "L") == 0) {
|
||||
rgb.format = AVIF_RGB_FORMAT_GRAY;
|
||||
#endif
|
||||
} else {
|
||||
rgb.format = AVIF_RGB_FORMAT_RGB;
|
||||
}
|
||||
@ -706,6 +710,17 @@ _decoder_get_info(AvifDecoderObject *self) {
|
||||
PyObject *xmp = NULL;
|
||||
PyObject *ret = NULL;
|
||||
|
||||
char *mode;
|
||||
if (decoder->alphaPresent) {
|
||||
mode = "RGBA";
|
||||
#if AVIF_VERSION >= 1030000 // 1.3.0
|
||||
} else if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
|
||||
mode = "L";
|
||||
#endif
|
||||
} else {
|
||||
mode = "RGB";
|
||||
}
|
||||
|
||||
if (image->xmp.size) {
|
||||
xmp = PyBytes_FromStringAndSize((const char *)image->xmp.data, image->xmp.size);
|
||||
if (!xmp) {
|
||||
@ -736,7 +751,7 @@ _decoder_get_info(AvifDecoderObject *self) {
|
||||
image->width,
|
||||
image->height,
|
||||
decoder->imageCount,
|
||||
decoder->alphaPresent ? "RGBA" : "RGB",
|
||||
mode,
|
||||
NULL == icc ? Py_None : icc,
|
||||
NULL == exif ? Py_None : exif,
|
||||
irot_imir_to_exif_orientation(image),
|
||||
@ -783,7 +798,15 @@ _decoder_get_frame(AvifDecoderObject *self, PyObject *args) {
|
||||
avifRGBImageSetDefaults(&rgb, image);
|
||||
|
||||
rgb.depth = 8;
|
||||
rgb.format = decoder->alphaPresent ? AVIF_RGB_FORMAT_RGBA : AVIF_RGB_FORMAT_RGB;
|
||||
if (decoder->alphaPresent) {
|
||||
rgb.format = AVIF_RGB_FORMAT_RGBA;
|
||||
#if AVIF_VERSION >= 1030000 // 1.3.0
|
||||
} else if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
|
||||
rgb.format = AVIF_RGB_FORMAT_GRAY;
|
||||
#endif
|
||||
} else {
|
||||
rgb.format = AVIF_RGB_FORMAT_RGB;
|
||||
}
|
||||
|
||||
result = avifRGBImageAllocatePixels(&rgb);
|
||||
if (result != AVIF_RESULT_OK) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user