mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-07-04 20:03:20 +03:00
Merge branch 'main' into enum
This commit is contained in:
commit
9a4106c14f
|
@ -5,6 +5,12 @@ Changelog (Pillow)
|
||||||
9.1.0 (unreleased)
|
9.1.0 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Added get_photoshop_blocks() to parse Photoshop TIFF tag #6030
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Drop excess values in BITSPERSAMPLE #6041
|
||||||
|
[mikhail-iurkov]
|
||||||
|
|
||||||
- Added unpacker from RGBA;15 to RGB #6031
|
- Added unpacker from RGBA;15 to RGB #6031
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
|
@ -26,7 +32,7 @@ Changelog (Pillow)
|
||||||
- Ensure duplicated file pointer is closed #5946
|
- Ensure duplicated file pointer is closed #5946
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
- Added specific error if ImagePath coordinate type is incorrect #5942
|
- Added specific error if path coordinate type is incorrect #5942
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
- Return an empty bytestring from tobytes() for an empty image #5938
|
- Return an empty bytestring from tobytes() for an empty image #5938
|
||||||
|
|
BIN
Tests/images/tiff_wrong_bits_per_sample_2.tiff
Normal file
BIN
Tests/images/tiff_wrong_bits_per_sample_2.tiff
Normal file
Binary file not shown.
|
@ -90,11 +90,18 @@ class TestFileTiff:
|
||||||
|
|
||||||
assert_image_similar_tofile(im, "Tests/images/pil136.png", 1)
|
assert_image_similar_tofile(im, "Tests/images/pil136.png", 1)
|
||||||
|
|
||||||
def test_wrong_bits_per_sample(self):
|
@pytest.mark.parametrize(
|
||||||
with Image.open("Tests/images/tiff_wrong_bits_per_sample.tiff") as im:
|
"file_name,mode,size,offset",
|
||||||
assert im.mode == "RGBA"
|
[
|
||||||
assert im.size == (52, 53)
|
("tiff_wrong_bits_per_sample.tiff", "RGBA", (52, 53), 160),
|
||||||
assert im.tile == [("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))]
|
("tiff_wrong_bits_per_sample_2.tiff", "RGB", (16, 16), 8),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_wrong_bits_per_sample(self, file_name, mode, size, offset):
|
||||||
|
with Image.open("Tests/images/" + file_name) as im:
|
||||||
|
assert im.mode == mode
|
||||||
|
assert im.size == size
|
||||||
|
assert im.tile == [("raw", (0, 0) + size, offset, (mode, 0, 1))]
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
def test_set_legacy_api(self):
|
def test_set_legacy_api(self):
|
||||||
|
@ -685,6 +692,32 @@ class TestFileTiff:
|
||||||
assert description[0]["format"] == "image/tiff"
|
assert description[0]["format"] == "image/tiff"
|
||||||
assert description[3]["BitsPerSample"]["Seq"]["li"] == ["8", "8", "8"]
|
assert description[3]["BitsPerSample"]["Seq"]["li"] == ["8", "8", "8"]
|
||||||
|
|
||||||
|
def test_get_photoshop_blocks(self):
|
||||||
|
with Image.open("Tests/images/lab.tif") as im:
|
||||||
|
assert list(im.get_photoshop_blocks().keys()) == [
|
||||||
|
1061,
|
||||||
|
1002,
|
||||||
|
1005,
|
||||||
|
1062,
|
||||||
|
1037,
|
||||||
|
1049,
|
||||||
|
1011,
|
||||||
|
1034,
|
||||||
|
10000,
|
||||||
|
1013,
|
||||||
|
1016,
|
||||||
|
1032,
|
||||||
|
1054,
|
||||||
|
1050,
|
||||||
|
1064,
|
||||||
|
1041,
|
||||||
|
1044,
|
||||||
|
1036,
|
||||||
|
1057,
|
||||||
|
4000,
|
||||||
|
4001,
|
||||||
|
]
|
||||||
|
|
||||||
def test_close_on_load_exclusive(self, tmp_path):
|
def test_close_on_load_exclusive(self, tmp_path):
|
||||||
# similar to test_fd_leak, but runs on unixlike os
|
# similar to test_fd_leak, but runs on unixlike os
|
||||||
tmpfile = str(tmp_path / "temp.tif")
|
tmpfile = str(tmp_path / "temp.tif")
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
from packaging.version import parse as parse_version
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image, features
|
||||||
|
|
||||||
from .helper import assert_image_similar, hopper, is_ppc64le
|
from .helper import assert_image_similar, hopper, is_ppc64le, skip_unless_feature
|
||||||
|
|
||||||
|
|
||||||
def test_sanity():
|
def test_sanity():
|
||||||
|
@ -17,16 +18,14 @@ def test_sanity():
|
||||||
assert_image_similar(converted.convert("RGB"), image, 60)
|
assert_image_similar(converted.convert("RGB"), image, 60)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(is_ppc64le(), reason="failing on ppc64le on GHA")
|
@skip_unless_feature("libimagequant")
|
||||||
def test_libimagequant_quantize():
|
def test_libimagequant_quantize():
|
||||||
image = hopper()
|
image = hopper()
|
||||||
try:
|
if is_ppc64le():
|
||||||
|
libimagequant = parse_version(features.version_feature("libimagequant"))
|
||||||
|
if libimagequant < parse_version("4"):
|
||||||
|
pytest.skip("Fails with libimagequant earlier than 4.0.0 on ppc64le")
|
||||||
converted = image.quantize(100, Image.Quantize.LIBIMAGEQUANT)
|
converted = image.quantize(100, Image.Quantize.LIBIMAGEQUANT)
|
||||||
except ValueError as ex: # pragma: no cover
|
|
||||||
if "dependency" in str(ex).lower():
|
|
||||||
pytest.skip("libimagequant support not available")
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
assert converted.mode == "P"
|
assert converted.mode == "P"
|
||||||
assert_image_similar(converted.convert("RGB"), image, 15)
|
assert_image_similar(converted.convert("RGB"), image, 15)
|
||||||
assert len(converted.getcolors()) == 100
|
assert len(converted.getcolors()) == 100
|
||||||
|
|
56
docs/releasenotes/9.1.0.rst
Normal file
56
docs/releasenotes/9.1.0.rst
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
9.1.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
API Changes
|
||||||
|
===========
|
||||||
|
|
||||||
|
Raise an error when performing a negative crop
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Performing a negative crop on an image previously just returned a ``(0, 0)`` image. Now
|
||||||
|
it will raise a ``ValueError``, to help reduce confusion if a user has unintentionally
|
||||||
|
provided the wrong arguments.
|
||||||
|
|
||||||
|
Added specific error if path coordinate type is incorrect
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Rather than returning a ``SystemError``, passing the incorrect types of coordinates into
|
||||||
|
a path will now raise a more specific ``ValueError``, with the message "incorrect
|
||||||
|
coordinate type".
|
||||||
|
|
||||||
|
Deprecations
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
ImageShow.Viewer.show_file file argument
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The ``file`` argument in :py:meth:`~PIL.ImageShow.Viewer.show_file()` has been
|
||||||
|
deprecated, replaced by ``path``.
|
||||||
|
|
||||||
|
In effect, ``viewer.show_file("test.jpg")`` will continue to work unchanged.
|
||||||
|
``viewer.show_file(file="test.jpg")`` will raise a deprecation warning, and suggest
|
||||||
|
``viewer.show_file(path="test.jpg")`` instead.
|
||||||
|
|
||||||
|
API Additions
|
||||||
|
=============
|
||||||
|
|
||||||
|
Added get_photoshop_blocks() to parse Photoshop TIFF tag
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
:py:meth:`~PIL.TiffImagePlugin.TiffImageFile.get_photoshop_blocks` has been added, to
|
||||||
|
allow users to determine what Photoshop "Image Resource Blocks" are contained within an
|
||||||
|
image. The keys of the returned dictionary are the image resource IDs.
|
||||||
|
|
||||||
|
At present, the information within each block is merely returned as a dictionary with a
|
||||||
|
"data" entry. This will allow more useful information to be added in the future without
|
||||||
|
breaking backwards compatibility.
|
||||||
|
|
||||||
|
Other Changes
|
||||||
|
=============
|
||||||
|
|
||||||
|
Image._repr_pretty_
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
``im._repr_pretty_`` has been added to provide a representation of an image without the
|
||||||
|
identity of the object. This allows Jupyter to describe an image and have that
|
||||||
|
description stay the same on subsequent executions of the same code.
|
|
@ -14,6 +14,7 @@ expected to be backported to earlier versions.
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
9.1.0
|
||||||
9.0.1
|
9.0.1
|
||||||
9.0.0
|
9.0.0
|
||||||
8.4.0
|
8.4.0
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
import io
|
import io
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
|
import math
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
import warnings
|
import warnings
|
||||||
|
@ -49,6 +50,8 @@ from fractions import Fraction
|
||||||
from numbers import Number, Rational
|
from numbers import Number, Rational
|
||||||
|
|
||||||
from . import Image, ImageFile, ImageOps, ImagePalette, TiffTags
|
from . import Image, ImageFile, ImageOps, ImagePalette, TiffTags
|
||||||
|
from ._binary import i16be as i16
|
||||||
|
from ._binary import i32be as i32
|
||||||
from ._binary import o8
|
from ._binary import o8
|
||||||
from .TiffTags import TYPES
|
from .TiffTags import TYPES
|
||||||
|
|
||||||
|
@ -1129,6 +1132,27 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
"""
|
"""
|
||||||
return self._getxmp(self.tag_v2[700]) if 700 in self.tag_v2 else {}
|
return self._getxmp(self.tag_v2[700]) if 700 in self.tag_v2 else {}
|
||||||
|
|
||||||
|
def get_photoshop_blocks(self):
|
||||||
|
"""
|
||||||
|
Returns a dictionary of Photoshop "Image Resource Blocks".
|
||||||
|
The keys are the image resource ID. For more information, see
|
||||||
|
https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_pgfId-1037727
|
||||||
|
|
||||||
|
:returns: Photoshop "Image Resource Blocks" in a dictionary.
|
||||||
|
"""
|
||||||
|
blocks = {}
|
||||||
|
val = self.tag_v2.get(0x8649)
|
||||||
|
if val:
|
||||||
|
while val[:4] == b"8BIM":
|
||||||
|
id = i16(val[4:6])
|
||||||
|
n = math.ceil((val[6] + 1) / 2) * 2
|
||||||
|
size = i32(val[6 + n : 10 + n])
|
||||||
|
data = val[10 + n : 10 + n + size]
|
||||||
|
blocks[id] = {"data": data}
|
||||||
|
|
||||||
|
val = val[math.ceil((10 + n + size) / 2) * 2 :]
|
||||||
|
return blocks
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
if self.tile and self.use_load_libtiff:
|
if self.tile and self.use_load_libtiff:
|
||||||
return self._load_libtiff()
|
return self._load_libtiff()
|
||||||
|
@ -1308,9 +1332,14 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
else:
|
else:
|
||||||
bps_count = 1
|
bps_count = 1
|
||||||
bps_count += len(extra_tuple)
|
bps_count += len(extra_tuple)
|
||||||
# Some files have only one value in bps_tuple,
|
bps_actual_count = len(bps_tuple)
|
||||||
# while should have more. Fix it
|
if bps_count < bps_actual_count:
|
||||||
if bps_count > len(bps_tuple) and len(bps_tuple) == 1:
|
# If a file has more values in bps_tuple than expected,
|
||||||
|
# remove the excess.
|
||||||
|
bps_tuple = bps_tuple[:bps_count]
|
||||||
|
elif bps_count > bps_actual_count and bps_actual_count == 1:
|
||||||
|
# If a file has only one value in bps_tuple, when it should have more,
|
||||||
|
# presume it is the same number of bits for all of the samples.
|
||||||
bps_tuple = bps_tuple * bps_count
|
bps_tuple = bps_tuple * bps_count
|
||||||
|
|
||||||
samplesPerPixel = self.tag_v2.get(
|
samplesPerPixel = self.tag_v2.get(
|
||||||
|
|
|
@ -1216,9 +1216,7 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) {
|
||||||
convert = alpha ? pa2f : p2f;
|
convert = alpha ? pa2f : p2f;
|
||||||
} else if (strcmp(mode, "RGB") == 0) {
|
} else if (strcmp(mode, "RGB") == 0) {
|
||||||
convert = alpha ? pa2rgb : p2rgb;
|
convert = alpha ? pa2rgb : p2rgb;
|
||||||
} else if (strcmp(mode, "RGBA") == 0) {
|
} else if (strcmp(mode, "RGBA") == 0 || strcmp(mode, "RGBX") == 0) {
|
||||||
convert = alpha ? pa2rgba : p2rgba;
|
|
||||||
} else if (strcmp(mode, "RGBX") == 0) {
|
|
||||||
convert = alpha ? pa2rgba : p2rgba;
|
convert = alpha ? pa2rgba : p2rgba;
|
||||||
} else if (strcmp(mode, "CMYK") == 0) {
|
} else if (strcmp(mode, "CMYK") == 0) {
|
||||||
convert = alpha ? pa2cmyk : p2cmyk;
|
convert = alpha ? pa2cmyk : p2cmyk;
|
||||||
|
|
|
@ -280,9 +280,9 @@ deps = {
|
||||||
"libs": [r"imagequant.lib"],
|
"libs": [r"imagequant.lib"],
|
||||||
},
|
},
|
||||||
"harfbuzz": {
|
"harfbuzz": {
|
||||||
"url": "https://github.com/harfbuzz/harfbuzz/archive/3.3.2.zip",
|
"url": "https://github.com/harfbuzz/harfbuzz/archive/3.4.0.zip",
|
||||||
"filename": "harfbuzz-3.3.2.zip",
|
"filename": "harfbuzz-3.4.0.zip",
|
||||||
"dir": "harfbuzz-3.3.2",
|
"dir": "harfbuzz-3.4.0",
|
||||||
"build": [
|
"build": [
|
||||||
cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"),
|
cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"),
|
||||||
cmd_nmake(target="clean"),
|
cmd_nmake(target="clean"),
|
||||||
|
|
Loading…
Reference in New Issue
Block a user