mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-07-28 08:59:57 +03:00
Merge branch 'main' into qoi_write
This commit is contained in:
commit
2f5137fdce
2
.github/workflows/wheels-dependencies.sh
vendored
2
.github/workflows/wheels-dependencies.sh
vendored
|
@ -40,7 +40,7 @@ ARCHIVE_SDIR=pillow-depends-main
|
|||
FREETYPE_VERSION=2.13.3
|
||||
HARFBUZZ_VERSION=11.2.1
|
||||
LIBPNG_VERSION=1.6.48
|
||||
JPEGTURBO_VERSION=3.1.0
|
||||
JPEGTURBO_VERSION=3.1.1
|
||||
OPENJPEG_VERSION=2.5.3
|
||||
XZ_VERSION=5.8.1
|
||||
TIFF_VERSION=4.7.0
|
||||
|
|
Binary file not shown.
BIN
Tests/images/op_index.qoi
Normal file
BIN
Tests/images/op_index.qoi
Normal file
Binary file not shown.
|
@ -288,12 +288,13 @@ def test_non_integer_token(tmp_path: Path) -> None:
|
|||
pass
|
||||
|
||||
|
||||
def test_header_token_too_long(tmp_path: Path) -> None:
|
||||
@pytest.mark.parametrize("data", (b"P3\x0cAAAAAAAAAA\xee", b"P6\n 01234567890"))
|
||||
def test_header_token_too_long(tmp_path: Path, data: bytes) -> None:
|
||||
path = tmp_path / "temp.ppm"
|
||||
with open(path, "wb") as f:
|
||||
f.write(b"P6\n 01234567890")
|
||||
f.write(data)
|
||||
|
||||
with pytest.raises(ValueError, match="Token too long in file header: 01234567890"):
|
||||
with pytest.raises(ValueError, match="Token too long in file header: "):
|
||||
with Image.open(path):
|
||||
pass
|
||||
|
||||
|
|
|
@ -32,6 +32,12 @@ def test_invalid_file() -> None:
|
|||
QoiImagePlugin.QoiImageFile(invalid_file)
|
||||
|
||||
|
||||
def test_op_index() -> None:
|
||||
# QOI_OP_INDEX as the first chunk
|
||||
with Image.open("Tests/images/op_index.qoi") as im:
|
||||
assert im.getpixel((0, 0)) == (0, 0, 0, 0)
|
||||
|
||||
|
||||
def test_save(tmp_path: Path) -> None:
|
||||
f = tmp_path / "temp.qoi"
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ from PIL import (
|
|||
ImageFile,
|
||||
JpegImagePlugin,
|
||||
TiffImagePlugin,
|
||||
TiffTags,
|
||||
UnidentifiedImageError,
|
||||
)
|
||||
from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION
|
||||
|
@ -900,6 +901,29 @@ class TestFileTiff:
|
|||
assert description[0]["format"] == "image/tiff"
|
||||
assert description[3]["BitsPerSample"]["Seq"]["li"] == ["8", "8", "8"]
|
||||
|
||||
def test_getxmp_undefined(self, tmp_path: Path) -> None:
|
||||
tmpfile = tmp_path / "temp.tif"
|
||||
im = Image.new("L", (1, 1))
|
||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||
ifd.tagtype[700] = TiffTags.UNDEFINED
|
||||
with Image.open("Tests/images/lab.tif") as im_xmp:
|
||||
ifd[700] = im_xmp.info["xmp"]
|
||||
im.save(tmpfile, tiffinfo=ifd)
|
||||
|
||||
with Image.open(tmpfile) as im_reloaded:
|
||||
if ElementTree is None:
|
||||
with pytest.warns(
|
||||
UserWarning,
|
||||
match="XMP data cannot be read without defusedxml dependency",
|
||||
):
|
||||
assert im_reloaded.getxmp() == {}
|
||||
else:
|
||||
assert "xmp" in im_reloaded.info
|
||||
xmp = im_reloaded.getxmp()
|
||||
|
||||
description = xmp["xmpmeta"]["RDF"]["Description"]
|
||||
assert description[0]["format"] == "image/tiff"
|
||||
|
||||
def test_get_photoshop_blocks(self) -> None:
|
||||
with Image.open("Tests/images/lab.tif") as im:
|
||||
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
||||
|
|
|
@ -974,6 +974,11 @@ class TestImage:
|
|||
assert tag not in exif.get_ifd(0x8769)
|
||||
assert exif.get_ifd(0xA005)
|
||||
|
||||
def test_exif_from_xmp_bytes(self) -> None:
|
||||
im = Image.new("RGB", (1, 1))
|
||||
im.info["xmp"] = b'\xff tiff:Orientation="2"'
|
||||
assert im.getexif()[274] == 2
|
||||
|
||||
def test_empty_xmp(self) -> None:
|
||||
with Image.open("Tests/images/hopper.gif") as im:
|
||||
if ElementTree is None:
|
||||
|
|
|
@ -783,9 +783,10 @@ def test_rectangle_I16(bbox: Coords) -> None:
|
|||
draw = ImageDraw.Draw(im)
|
||||
|
||||
# Act
|
||||
draw.rectangle(bbox, outline=0xFFFF)
|
||||
draw.rectangle(bbox, outline=0xCDEF)
|
||||
|
||||
# Assert
|
||||
assert im.getpixel((X0, Y0)) == 0xCDEF
|
||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_I.tiff")
|
||||
|
||||
|
||||
|
|
|
@ -52,3 +52,17 @@ def test_tiff_crashes(test_file: str) -> None:
|
|||
pytest.skip("test image not found")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def test_tiff_mmap() -> None:
|
||||
try:
|
||||
with Image.open("Tests/images/crash_mmap.tif") as im:
|
||||
im.seek(1)
|
||||
im.load()
|
||||
|
||||
im.seek(0)
|
||||
im.load()
|
||||
except FileNotFoundError:
|
||||
if on_ci():
|
||||
raise
|
||||
pytest.skip("test image not found")
|
||||
|
|
|
@ -18,6 +18,9 @@ OpenType fonts (as well as other font formats supported by the FreeType
|
|||
library). For earlier versions, TrueType support is only available as part of
|
||||
the imToolkit package.
|
||||
|
||||
When measuring text sizes, this module will not break at newline characters. For
|
||||
multiline text, see the :py:mod:`~PIL.ImageDraw` module.
|
||||
|
||||
.. warning::
|
||||
To protect against potential DOS attacks when using arbitrary strings as
|
||||
text input, Pillow will raise a :py:exc:`ValueError` if the number of characters
|
||||
|
|
|
@ -1542,10 +1542,11 @@ class Image:
|
|||
# XMP tags
|
||||
if ExifTags.Base.Orientation not in self._exif:
|
||||
xmp_tags = self.info.get("XML:com.adobe.xmp")
|
||||
pattern: str | bytes = r'tiff:Orientation(="|>)([0-9])'
|
||||
if not xmp_tags and (xmp_tags := self.info.get("xmp")):
|
||||
xmp_tags = xmp_tags.decode("utf-8")
|
||||
pattern = rb'tiff:Orientation(="|>)([0-9])'
|
||||
if xmp_tags:
|
||||
match = re.search(r'tiff:Orientation(="|>)([0-9])', xmp_tags)
|
||||
match = re.search(pattern, xmp_tags)
|
||||
if match:
|
||||
self._exif[ExifTags.Base.Orientation] = int(match[2])
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ class PcxImageFile(ImageFile.ImageFile):
|
|||
# header
|
||||
assert self.fp is not None
|
||||
|
||||
s = self.fp.read(128)
|
||||
s = self.fp.read(68)
|
||||
if not _accept(s):
|
||||
msg = "not a PCX file"
|
||||
raise SyntaxError(msg)
|
||||
|
@ -66,6 +66,8 @@ class PcxImageFile(ImageFile.ImageFile):
|
|||
raise SyntaxError(msg)
|
||||
logger.debug("BBox: %s %s %s %s", *bbox)
|
||||
|
||||
offset = self.fp.tell() + 60
|
||||
|
||||
# format
|
||||
version = s[1]
|
||||
bits = s[3]
|
||||
|
@ -102,7 +104,6 @@ class PcxImageFile(ImageFile.ImageFile):
|
|||
break
|
||||
if mode == "P":
|
||||
self.palette = ImagePalette.raw("RGB", s[1:])
|
||||
self.fp.seek(128)
|
||||
|
||||
elif version == 5 and bits == 8 and planes == 3:
|
||||
mode = "RGB"
|
||||
|
@ -128,9 +129,7 @@ class PcxImageFile(ImageFile.ImageFile):
|
|||
bbox = (0, 0) + self.size
|
||||
logger.debug("size: %sx%s", *self.size)
|
||||
|
||||
self.tile = [
|
||||
ImageFile._Tile("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))
|
||||
]
|
||||
self.tile = [ImageFile._Tile("pcx", bbox, offset, (rawmode, planes * stride))]
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
|
|
@ -94,8 +94,8 @@ class PpmImageFile(ImageFile.ImageFile):
|
|||
msg = "Reached EOF while reading header"
|
||||
raise ValueError(msg)
|
||||
elif len(token) > 10:
|
||||
msg = f"Token too long in file header: {token.decode()}"
|
||||
raise ValueError(msg)
|
||||
msg_too_long = b"Token too long in file header: %s" % token
|
||||
raise ValueError(msg_too_long)
|
||||
return token
|
||||
|
||||
def _open(self) -> None:
|
||||
|
|
|
@ -54,7 +54,7 @@ class QoiDecoder(ImageFile.PyDecoder):
|
|||
assert self.fd is not None
|
||||
|
||||
self._previously_seen_pixels = {}
|
||||
self._add_to_previous_pixels(bytearray((0, 0, 0, 255)))
|
||||
self._previous_pixel = bytearray((0, 0, 0, 255))
|
||||
|
||||
data = bytearray()
|
||||
bands = Image.getmodebands(self.mode)
|
||||
|
|
|
@ -1217,9 +1217,10 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
return
|
||||
self._seek(frame)
|
||||
if self._im is not None and (
|
||||
self.im.size != self._tile_size or self.im.mode != self.mode
|
||||
self.im.size != self._tile_size
|
||||
or self.im.mode != self.mode
|
||||
or self.readonly
|
||||
):
|
||||
# The core image will no longer be used
|
||||
self._im = None
|
||||
|
||||
def _seek(self, frame: int) -> None:
|
||||
|
@ -1259,7 +1260,10 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
self.fp.seek(self._frame_pos[frame])
|
||||
self.tag_v2.load(self.fp)
|
||||
if XMP in self.tag_v2:
|
||||
self.info["xmp"] = self.tag_v2[XMP]
|
||||
xmp = self.tag_v2[XMP]
|
||||
if isinstance(xmp, tuple) and len(xmp) == 1:
|
||||
xmp = xmp[0]
|
||||
self.info["xmp"] = xmp
|
||||
elif "xmp" in self.info:
|
||||
del self.info["xmp"]
|
||||
self._reload_exif()
|
||||
|
|
|
@ -104,8 +104,6 @@ point32rgba(Imaging im, int x, int y, int ink) {
|
|||
|
||||
static inline void
|
||||
hline8(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) {
|
||||
int pixelwidth;
|
||||
|
||||
if (y0 >= 0 && y0 < im->ysize) {
|
||||
if (x0 < 0) {
|
||||
x0 = 0;
|
||||
|
@ -118,20 +116,30 @@ hline8(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) {
|
|||
x1 = im->xsize - 1;
|
||||
}
|
||||
if (x0 <= x1) {
|
||||
pixelwidth = strncmp(im->mode, "I;16", 4) == 0 ? 2 : 1;
|
||||
if (mask == NULL) {
|
||||
memset(
|
||||
im->image8[y0] + x0 * pixelwidth,
|
||||
(UINT8)ink,
|
||||
(x1 - x0 + 1) * pixelwidth
|
||||
);
|
||||
int bigendian = -1;
|
||||
if (strncmp(im->mode, "I;16", 4) == 0) {
|
||||
bigendian =
|
||||
(
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
strcmp(im->mode, "I;16") == 0 || strcmp(im->mode, "I;16L") == 0
|
||||
#else
|
||||
strcmp(im->mode, "I;16B") == 0
|
||||
#endif
|
||||
)
|
||||
? 1
|
||||
: 0;
|
||||
}
|
||||
if (mask == NULL && bigendian == -1) {
|
||||
memset(im->image8[y0] + x0, (UINT8)ink, (x1 - x0 + 1));
|
||||
} else {
|
||||
UINT8 *p = im->image8[y0];
|
||||
while (x0 <= x1) {
|
||||
if (mask->image8[y0][x0]) {
|
||||
p[x0 * pixelwidth] = ink;
|
||||
if (pixelwidth == 2) {
|
||||
p[x0 * pixelwidth + 1] = ink;
|
||||
if (mask == NULL || mask->image8[y0][x0]) {
|
||||
if (bigendian == -1) {
|
||||
p[x0] = ink;
|
||||
} else {
|
||||
p[x0 * 2 + (bigendian ? 1 : 0)] = ink;
|
||||
p[x0 * 2 + (bigendian ? 0 : 1)] = ink >> 8;
|
||||
}
|
||||
}
|
||||
x0++;
|
||||
|
|
|
@ -137,6 +137,7 @@ PyImaging_MapBuffer(PyObject *self, PyObject *args) {
|
|||
}
|
||||
}
|
||||
|
||||
im->read_only = view.readonly;
|
||||
im->destroy = mapping_destroy_buffer;
|
||||
|
||||
Py_INCREF(target);
|
||||
|
|
|
@ -114,7 +114,7 @@ V = {
|
|||
"FREETYPE": "2.13.3",
|
||||
"FRIBIDI": "1.0.16",
|
||||
"HARFBUZZ": "11.2.1",
|
||||
"JPEGTURBO": "3.1.0",
|
||||
"JPEGTURBO": "3.1.1",
|
||||
"LCMS2": "2.17",
|
||||
"LIBAVIF": "1.3.0",
|
||||
"LIBIMAGEQUANT": "4.3.4",
|
||||
|
|
Loading…
Reference in New Issue
Block a user