Merge branch 'main' into avif_gray
This commit is contained in:
commit
15f13ef46e
2
.github/workflows/wheels.yml
vendored
2
.github/workflows/wheels.yml
vendored
@ -104,7 +104,7 @@ jobs:
|
||||
cibw_arch: arm64_iphonesimulator
|
||||
- name: "iOS x86_64 simulator"
|
||||
platform: ios
|
||||
os: macos-26-intel
|
||||
os: macos-15-intel
|
||||
cibw_arch: x86_64_iphonesimulator
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
@ -6,7 +6,14 @@ from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, ImageFile, JpegImagePlugin, MpoImagePlugin
|
||||
from PIL import (
|
||||
Image,
|
||||
ImageFile,
|
||||
JpegImagePlugin,
|
||||
MpoImagePlugin,
|
||||
TiffImagePlugin,
|
||||
_binary,
|
||||
)
|
||||
|
||||
from .helper import (
|
||||
assert_image_equal,
|
||||
@ -145,6 +152,32 @@ def test_parallax() -> None:
|
||||
assert exif.get_ifd(0x927C)[0xB211] == -3.125
|
||||
|
||||
|
||||
def test_truncated_makernote() -> None:
|
||||
def check(ifd: TiffImagePlugin.ImageFileDirectory_v2) -> None:
|
||||
fp = BytesIO()
|
||||
ifd.save(fp)
|
||||
|
||||
e = Image.Exif()
|
||||
e.load(fp.getvalue())
|
||||
assert e.get_ifd(37500) == {}
|
||||
|
||||
# Nintendo
|
||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||
ifd[271] = "Nintendo"
|
||||
ifd[34665] = {37500: b" "}
|
||||
check(ifd)
|
||||
|
||||
# Fujifilm
|
||||
for data in (
|
||||
b"FUJIFILM",
|
||||
b"FUJIFILM" + _binary.o32le(50),
|
||||
b"FUJIFILM" + _binary.o32le(0),
|
||||
):
|
||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||
ifd[34665] = {37500: data}
|
||||
check(ifd)
|
||||
|
||||
|
||||
def test_reload_exif_after_seek() -> None:
|
||||
with Image.open("Tests/images/sugarshack.mpo") as im:
|
||||
exif = im.getexif()
|
||||
|
||||
@ -16,6 +16,7 @@ from PIL import (
|
||||
TiffImagePlugin,
|
||||
TiffTags,
|
||||
UnidentifiedImageError,
|
||||
_binary,
|
||||
)
|
||||
from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION
|
||||
|
||||
@ -941,6 +942,15 @@ class TestFileTiff:
|
||||
4001,
|
||||
]
|
||||
|
||||
def test_truncated_photoshop_blocks(self) -> None:
|
||||
with Image.open("Tests/images/hopper.tif") as im:
|
||||
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
||||
im.tag_v2[34377] = b"8BIM"
|
||||
assert im.get_photoshop_blocks() == {}
|
||||
|
||||
im.tag_v2[34377] = b"8BIM" + _binary.o16be(0) + _binary.o8(2) + b" " * 5
|
||||
assert im.get_photoshop_blocks() == {}
|
||||
|
||||
def test_tiff_chunks(self, tmp_path: Path) -> None:
|
||||
tmpfile = tmp_path / "temp.tif"
|
||||
|
||||
|
||||
131
src/PIL/Image.py
131
src/PIL/Image.py
@ -4234,80 +4234,83 @@ class Exif(_ExifBase):
|
||||
if tag == ExifTags.IFD.MakerNote:
|
||||
from .TiffImagePlugin import ImageFileDirectory_v2
|
||||
|
||||
if tag_data.startswith(b"FUJIFILM"):
|
||||
ifd_offset = i32le(tag_data, 8)
|
||||
ifd_data = tag_data[ifd_offset:]
|
||||
try:
|
||||
if tag_data.startswith(b"FUJIFILM"):
|
||||
ifd_offset = i32le(tag_data, 8)
|
||||
ifd_data = tag_data[ifd_offset:]
|
||||
|
||||
makernote = {}
|
||||
for i in range(struct.unpack("<H", ifd_data[:2])[0]):
|
||||
ifd_tag, typ, count, data = struct.unpack(
|
||||
"<HHL4s", ifd_data[i * 12 + 2 : (i + 1) * 12 + 2]
|
||||
)
|
||||
try:
|
||||
(
|
||||
unit_size,
|
||||
handler,
|
||||
) = ImageFileDirectory_v2._load_dispatch[typ]
|
||||
except KeyError:
|
||||
continue
|
||||
size = count * unit_size
|
||||
if size > 4:
|
||||
(offset,) = struct.unpack("<L", data)
|
||||
data = ifd_data[offset - 12 : offset + size - 12]
|
||||
else:
|
||||
data = data[:size]
|
||||
|
||||
if len(data) != size:
|
||||
warnings.warn(
|
||||
"Possibly corrupt EXIF MakerNote data. "
|
||||
f"Expecting to read {size} bytes but only got "
|
||||
f"{len(data)}. Skipping tag {ifd_tag}"
|
||||
makernote = {}
|
||||
for i in range(struct.unpack("<H", ifd_data[:2])[0]):
|
||||
ifd_tag, typ, count, data = struct.unpack(
|
||||
"<HHL4s", ifd_data[i * 12 + 2 : (i + 1) * 12 + 2]
|
||||
)
|
||||
continue
|
||||
try:
|
||||
(
|
||||
unit_size,
|
||||
handler,
|
||||
) = ImageFileDirectory_v2._load_dispatch[typ]
|
||||
except KeyError:
|
||||
continue
|
||||
size = count * unit_size
|
||||
if size > 4:
|
||||
(offset,) = struct.unpack("<L", data)
|
||||
data = ifd_data[offset - 12 : offset + size - 12]
|
||||
else:
|
||||
data = data[:size]
|
||||
|
||||
if not data:
|
||||
continue
|
||||
if len(data) != size:
|
||||
warnings.warn(
|
||||
"Possibly corrupt EXIF MakerNote data. "
|
||||
f"Expecting to read {size} bytes but only got "
|
||||
f"{len(data)}. Skipping tag {ifd_tag}"
|
||||
)
|
||||
continue
|
||||
|
||||
makernote[ifd_tag] = handler(
|
||||
ImageFileDirectory_v2(), data, False
|
||||
)
|
||||
self._ifds[tag] = dict(self._fixup_dict(makernote))
|
||||
elif self.get(0x010F) == "Nintendo":
|
||||
makernote = {}
|
||||
for i in range(struct.unpack(">H", tag_data[:2])[0]):
|
||||
ifd_tag, typ, count, data = struct.unpack(
|
||||
">HHL4s", tag_data[i * 12 + 2 : (i + 1) * 12 + 2]
|
||||
)
|
||||
if ifd_tag == 0x1101:
|
||||
# CameraInfo
|
||||
(offset,) = struct.unpack(">L", data)
|
||||
self.fp.seek(offset)
|
||||
if not data:
|
||||
continue
|
||||
|
||||
camerainfo: dict[str, int | bytes] = {
|
||||
"ModelID": self.fp.read(4)
|
||||
}
|
||||
makernote[ifd_tag] = handler(
|
||||
ImageFileDirectory_v2(), data, False
|
||||
)
|
||||
self._ifds[tag] = dict(self._fixup_dict(makernote))
|
||||
elif self.get(0x010F) == "Nintendo":
|
||||
makernote = {}
|
||||
for i in range(struct.unpack(">H", tag_data[:2])[0]):
|
||||
ifd_tag, typ, count, data = struct.unpack(
|
||||
">HHL4s", tag_data[i * 12 + 2 : (i + 1) * 12 + 2]
|
||||
)
|
||||
if ifd_tag == 0x1101:
|
||||
# CameraInfo
|
||||
(offset,) = struct.unpack(">L", data)
|
||||
self.fp.seek(offset)
|
||||
|
||||
self.fp.read(4)
|
||||
# Seconds since 2000
|
||||
camerainfo["TimeStamp"] = i32le(self.fp.read(12))
|
||||
camerainfo: dict[str, int | bytes] = {
|
||||
"ModelID": self.fp.read(4)
|
||||
}
|
||||
|
||||
self.fp.read(4)
|
||||
camerainfo["InternalSerialNumber"] = self.fp.read(4)
|
||||
self.fp.read(4)
|
||||
# Seconds since 2000
|
||||
camerainfo["TimeStamp"] = i32le(self.fp.read(12))
|
||||
|
||||
self.fp.read(12)
|
||||
parallax = self.fp.read(4)
|
||||
handler = ImageFileDirectory_v2._load_dispatch[
|
||||
TiffTags.FLOAT
|
||||
][1]
|
||||
camerainfo["Parallax"] = handler(
|
||||
ImageFileDirectory_v2(), parallax, False
|
||||
)[0]
|
||||
self.fp.read(4)
|
||||
camerainfo["InternalSerialNumber"] = self.fp.read(4)
|
||||
|
||||
self.fp.read(4)
|
||||
camerainfo["Category"] = self.fp.read(2)
|
||||
self.fp.read(12)
|
||||
parallax = self.fp.read(4)
|
||||
handler = ImageFileDirectory_v2._load_dispatch[
|
||||
TiffTags.FLOAT
|
||||
][1]
|
||||
camerainfo["Parallax"] = handler(
|
||||
ImageFileDirectory_v2(), parallax, False
|
||||
)[0]
|
||||
|
||||
makernote = {0x1101: camerainfo}
|
||||
self._ifds[tag] = makernote
|
||||
self.fp.read(4)
|
||||
camerainfo["Category"] = self.fp.read(2)
|
||||
|
||||
makernote = {0x1101: camerainfo}
|
||||
self._ifds[tag] = makernote
|
||||
except struct.error:
|
||||
pass
|
||||
else:
|
||||
# Interop
|
||||
ifd = self._get_ifd_dict(tag_data, tag)
|
||||
|
||||
@ -1287,10 +1287,13 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||
blocks = {}
|
||||
val = self.tag_v2.get(ExifTags.Base.ImageResources)
|
||||
if val:
|
||||
while val.startswith(b"8BIM"):
|
||||
while val.startswith(b"8BIM") and len(val) >= 12:
|
||||
id = i16(val[4:6])
|
||||
n = math.ceil((val[6] + 1) / 2) * 2
|
||||
size = i32(val[6 + n : 10 + n])
|
||||
try:
|
||||
size = i32(val[6 + n : 10 + n])
|
||||
except struct.error:
|
||||
break
|
||||
data = val[10 + n : 10 + n + size]
|
||||
blocks[id] = {"data": data}
|
||||
|
||||
|
||||
24
src/_avif.c
24
src/_avif.c
@ -721,17 +721,29 @@ _decoder_get_info(AvifDecoderObject *self) {
|
||||
mode = "RGB";
|
||||
}
|
||||
|
||||
if (image->icc.size) {
|
||||
icc = PyBytes_FromStringAndSize((const char *)image->icc.data, image->icc.size);
|
||||
if (image->xmp.size) {
|
||||
xmp = PyBytes_FromStringAndSize((const char *)image->xmp.data, image->xmp.size);
|
||||
if (!xmp) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (image->exif.size) {
|
||||
exif =
|
||||
PyBytes_FromStringAndSize((const char *)image->exif.data, image->exif.size);
|
||||
if (!exif) {
|
||||
Py_XDECREF(xmp);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (image->xmp.size) {
|
||||
xmp = PyBytes_FromStringAndSize((const char *)image->xmp.data, image->xmp.size);
|
||||
if (image->icc.size) {
|
||||
icc = PyBytes_FromStringAndSize((const char *)image->icc.data, image->icc.size);
|
||||
if (!icc) {
|
||||
Py_XDECREF(xmp);
|
||||
Py_XDECREF(exif);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
ret = Py_BuildValue(
|
||||
@ -822,6 +834,7 @@ _decoder_get_frame(AvifDecoderObject *self, PyObject *args) {
|
||||
|
||||
if (rgb.height > PY_SSIZE_T_MAX / rgb.rowBytes) {
|
||||
PyErr_SetString(PyExc_MemoryError, "Integer overflow in pixel size");
|
||||
avifRGBImageFreePixels(&rgb);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -829,6 +842,9 @@ _decoder_get_frame(AvifDecoderObject *self, PyObject *args) {
|
||||
|
||||
bytes = PyBytes_FromStringAndSize((char *)rgb.pixels, size);
|
||||
avifRGBImageFreePixels(&rgb);
|
||||
if (!bytes) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = Py_BuildValue(
|
||||
"SKKK",
|
||||
|
||||
@ -254,6 +254,9 @@ void
|
||||
ReleaseArrowSchemaPyCapsule(PyObject *capsule) {
|
||||
struct ArrowSchema *schema =
|
||||
(struct ArrowSchema *)PyCapsule_GetPointer(capsule, "arrow_schema");
|
||||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
if (schema->release != NULL) {
|
||||
schema->release(schema);
|
||||
}
|
||||
@ -276,6 +279,9 @@ void
|
||||
ReleaseArrowArrayPyCapsule(PyObject *capsule) {
|
||||
struct ArrowArray *array =
|
||||
(struct ArrowArray *)PyCapsule_GetPointer(capsule, "arrow_array");
|
||||
if (!array) {
|
||||
return;
|
||||
}
|
||||
if (array->release != NULL) {
|
||||
array->release(array);
|
||||
}
|
||||
@ -2473,6 +2479,9 @@ _split(ImagingObject *self) {
|
||||
}
|
||||
|
||||
list = PyTuple_New(self->image->bands);
|
||||
if (!list) {
|
||||
return NULL;
|
||||
}
|
||||
for (i = 0; i < self->image->bands; i++) {
|
||||
imaging_object = PyImagingNew(bands[i]);
|
||||
if (!imaging_object) {
|
||||
@ -3769,6 +3778,9 @@ _ptr_destructor(PyObject *capsule) {
|
||||
static PyObject *
|
||||
_getattr_ptr(ImagingObject *self, void *closure) {
|
||||
PyObject *capsule = PyCapsule_New(self->image, IMAGING_MAGIC, _ptr_destructor);
|
||||
if (!capsule) {
|
||||
return NULL;
|
||||
}
|
||||
Py_INCREF(self);
|
||||
PyCapsule_SetContext(capsule, self);
|
||||
return capsule;
|
||||
|
||||
@ -558,7 +558,13 @@ cms_transform_apply(CmsTransformObject *self, PyObject *args) {
|
||||
}
|
||||
|
||||
im = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
|
||||
if (!im) {
|
||||
return NULL;
|
||||
}
|
||||
imOut = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC);
|
||||
if (!imOut) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return Py_BuildValue("i", pyCMSdoTransform(im, imOut, self->transform));
|
||||
}
|
||||
|
||||
@ -941,8 +941,18 @@ font_render(FontObject *self, PyObject *args) {
|
||||
return NULL;
|
||||
}
|
||||
PyObject *imagePtr = PyObject_GetAttrString(image, "ptr");
|
||||
if (!imagePtr) {
|
||||
PyMem_Del(glyph_info);
|
||||
Py_DECREF(image);
|
||||
return NULL;
|
||||
}
|
||||
im = (Imaging)PyCapsule_GetPointer(imagePtr, IMAGING_MAGIC);
|
||||
Py_XDECREF(imagePtr);
|
||||
Py_DECREF(imagePtr);
|
||||
if (!im) {
|
||||
PyMem_Del(glyph_info);
|
||||
Py_DECREF(image);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
x_offset = round(x_offset - stroke_width);
|
||||
y_offset = round(y_offset - stroke_width);
|
||||
|
||||
@ -187,8 +187,17 @@ _unop(PyObject *self, PyObject *args) {
|
||||
}
|
||||
|
||||
unop = (void *)PyCapsule_GetPointer(op, MATH_FUNC_UNOP_MAGIC);
|
||||
if (!unop) {
|
||||
return NULL;
|
||||
}
|
||||
out = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
|
||||
if (!out) {
|
||||
return NULL;
|
||||
}
|
||||
im1 = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC);
|
||||
if (!im1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
unop(out, im1);
|
||||
|
||||
@ -219,9 +228,21 @@ _binop(PyObject *self, PyObject *args) {
|
||||
}
|
||||
|
||||
binop = (void *)PyCapsule_GetPointer(op, MATH_FUNC_BINOP_MAGIC);
|
||||
if (!binop) {
|
||||
return NULL;
|
||||
}
|
||||
out = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
|
||||
if (!out) {
|
||||
return NULL;
|
||||
}
|
||||
im1 = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC);
|
||||
if (!im1) {
|
||||
return NULL;
|
||||
}
|
||||
im2 = (Imaging)PyCapsule_GetPointer(i2, IMAGING_MAGIC);
|
||||
if (!im2) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
binop(out, im1, im2);
|
||||
|
||||
|
||||
@ -53,7 +53,13 @@ apply(PyObject *self, PyObject *args) {
|
||||
}
|
||||
|
||||
imgin = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
|
||||
if (!imgin) {
|
||||
return NULL;
|
||||
}
|
||||
imgout = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC);
|
||||
if (!imgout) {
|
||||
return NULL;
|
||||
}
|
||||
width = imgin->xsize;
|
||||
height = imgin->ysize;
|
||||
|
||||
@ -143,6 +149,9 @@ match(PyObject *self, PyObject *args) {
|
||||
}
|
||||
|
||||
imgin = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
|
||||
if (!imgin) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (imgin->type != IMAGING_TYPE_UINT8 || imgin->bands != 1) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Unsupported image type");
|
||||
@ -185,6 +194,10 @@ match(PyObject *self, PyObject *args) {
|
||||
(b6 << 6) | (b7 << 7) | (b8 << 8));
|
||||
if (lut[lut_idx]) {
|
||||
PyObject *coordObj = Py_BuildValue("(nn)", col_idx, row_idx);
|
||||
if (!coordObj) {
|
||||
Py_DECREF(ret);
|
||||
return NULL;
|
||||
}
|
||||
PyList_Append(ret, coordObj);
|
||||
Py_XDECREF(coordObj);
|
||||
}
|
||||
@ -216,6 +229,9 @@ get_on_pixels(PyObject *self, PyObject *args) {
|
||||
}
|
||||
|
||||
img = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
|
||||
if (!img) {
|
||||
return NULL;
|
||||
}
|
||||
rows = img->image8;
|
||||
width = img->xsize;
|
||||
height = img->ysize;
|
||||
@ -230,6 +246,10 @@ get_on_pixels(PyObject *self, PyObject *args) {
|
||||
for (col_idx = 0; col_idx < width; col_idx++) {
|
||||
if (row[col_idx]) {
|
||||
PyObject *coordObj = Py_BuildValue("(nn)", col_idx, row_idx);
|
||||
if (!coordObj) {
|
||||
Py_DECREF(ret);
|
||||
return NULL;
|
||||
}
|
||||
PyList_Append(ret, coordObj);
|
||||
Py_XDECREF(coordObj);
|
||||
}
|
||||
|
||||
@ -262,6 +262,9 @@ _anim_encoder_add(PyObject *self, PyObject *args) {
|
||||
}
|
||||
|
||||
im = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
|
||||
if (!im) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Setup config for this frame
|
||||
if (!WebPConfigInit(&config)) {
|
||||
@ -505,6 +508,9 @@ _anim_decoder_get_next(PyObject *self) {
|
||||
bytes = PyBytes_FromStringAndSize(
|
||||
(char *)buf, decp->info.canvas_width * 4 * decp->info.canvas_height
|
||||
);
|
||||
if (!bytes) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = Py_BuildValue("Si", bytes, timestamp);
|
||||
|
||||
@ -607,6 +613,9 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) {
|
||||
}
|
||||
|
||||
im = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
|
||||
if (!im) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Setup config for this frame
|
||||
if (!WebPConfigInit(&config)) {
|
||||
|
||||
@ -480,6 +480,9 @@ PyImaging_GrabClipboardWin32(PyObject *self, PyObject *args) {
|
||||
GlobalUnlock(handle);
|
||||
CloseClipboard();
|
||||
|
||||
if (!result) {
|
||||
return NULL;
|
||||
}
|
||||
return Py_BuildValue("zN", format_names[format], result);
|
||||
}
|
||||
|
||||
|
||||
@ -677,9 +677,15 @@ ImagingNewArrow(
|
||||
Imaging im;
|
||||
struct ArrowSchema *schema =
|
||||
(struct ArrowSchema *)PyCapsule_GetPointer(schema_capsule, "arrow_schema");
|
||||
if (!schema) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct ArrowArray *external_array =
|
||||
(struct ArrowArray *)PyCapsule_GetPointer(array_capsule, "arrow_array");
|
||||
if (!external_array) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (xsize < 0 || ysize < 0) {
|
||||
return (Imaging)ImagingError_ValueError("bad image size");
|
||||
|
||||
@ -41,6 +41,9 @@ _imaging_write_pyFd(PyObject *fd, char *src, Py_ssize_t bytes) {
|
||||
PyObject *byteObj;
|
||||
|
||||
byteObj = PyBytes_FromStringAndSize(src, bytes);
|
||||
if (!byteObj) {
|
||||
return -1;
|
||||
}
|
||||
result = PyObject_CallMethod(fd, "write", "O", byteObj);
|
||||
|
||||
Py_DECREF(byteObj);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user