Skip to content

Commit

Permalink
Merge branch 'master' into msys
Browse files Browse the repository at this point in the history
  • Loading branch information
radarhere committed Feb 18, 2023
2 parents 041268a + d48dca3 commit 71df356
Show file tree
Hide file tree
Showing 18 changed files with 99 additions and 97 deletions.
3 changes: 2 additions & 1 deletion .ci/install.sh
Expand Up @@ -37,7 +37,8 @@ python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma

if [[ $(uname) != CYGWIN* ]]; then
python3 -m pip install numpy
# TODO Remove condition when NumPy supports 3.12
if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi

# PyQt6 doesn't support PyPy3
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/macos-install.sh
Expand Up @@ -13,7 +13,8 @@ python3 -m pip install -U pytest-cov
python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma

python3 -m pip install numpy
# TODO Remove condition when NumPy supports 3.12
if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi

# extra test images
pushd depends && ./install_extra_test_images.sh && popd
1 change: 1 addition & 0 deletions .github/workflows/test-docker.yml
Expand Up @@ -87,6 +87,7 @@ jobs:
with:
flags: GHA_Docker
name: ${{ matrix.docker }}
gcov: true

success:
permissions:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-windows.yml
Expand Up @@ -15,7 +15,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12-dev"]
architecture: ["x86", "x64"]
include:
# PyPy 7.3.4+ only ships 64-bit binaries for Windows
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/test.yml
Expand Up @@ -22,6 +22,7 @@ jobs:
python-version: [
"pypy3.9",
"pypy3.8",
"3.12-dev",
"3.11",
"3.10",
"3.9",
Expand Down Expand Up @@ -107,9 +108,9 @@ jobs:
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
flags: ${{ matrix.os == 'macos-latest' && 'GHA_macOS' || 'GHA_Ubuntu' }}
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
gcov: true

success:
permissions:
Expand Down
6 changes: 6 additions & 0 deletions CHANGES.rst
Expand Up @@ -5,6 +5,12 @@ Changelog (Pillow)
9.5.0 (unreleased)
------------------

- Fixed writing int as UNDEFINED tag #6950
[radarhere]

- Raise an error if EXIF data is too long when saving JPEG #6939
[radarhere]

- Handle more than one directory returned by pkg-config #6896
[sebastic, radarhere]

Expand Down
4 changes: 2 additions & 2 deletions LICENSE
Expand Up @@ -13,8 +13,8 @@ By obtaining, using, and/or copying this software and/or its associated
documentation, you agree that you have read, understood, and will comply
with the following terms and conditions:

Permission to use, copy, modify, and distribute this software and its
associated documentation for any purpose and without fee is hereby granted,
Permission to use, copy, modify and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appears in all copies, and that
both that copyright notice and this permission notice appear in supporting
documentation, and that the name of Secret Labs AB or the author not be
Expand Down
7 changes: 5 additions & 2 deletions Tests/test_file_jpeg.py
Expand Up @@ -270,7 +270,10 @@ def test_large_exif(self, tmp_path):
# https://github.com/python-pillow/Pillow/issues/148
f = str(tmp_path / "temp.jpg")
im = hopper()
im.save(f, "JPEG", quality=90, exif=b"1" * 65532)
im.save(f, "JPEG", quality=90, exif=b"1" * 65533)

with pytest.raises(ValueError):
im.save(f, "JPEG", quality=90, exif=b"1" * 65534)

def test_exif_typeerror(self):
with Image.open("Tests/images/exif_typeerror.jpg") as im:
Expand Down Expand Up @@ -445,7 +448,7 @@ def test_get_child_images(self):
ims = im.get_child_images()

assert len(ims) == 1
assert_image_equal_tofile(ims[0], "Tests/images/flower_thumbnail.png")
assert_image_similar_tofile(ims[0], "Tests/images/flower_thumbnail.png", 2.1)

def test_mp(self):
with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
Expand Down
16 changes: 16 additions & 0 deletions Tests/test_file_tiff_metadata.py
Expand Up @@ -216,6 +216,22 @@ def test_writing_other_types_to_bytes(value, tmp_path):
assert reloaded.tag_v2[700] == b"\x01"


def test_writing_other_types_to_undefined(tmp_path):
im = hopper()
info = TiffImagePlugin.ImageFileDirectory_v2()

tag = TiffTags.TAGS_V2[33723]
assert tag.type == TiffTags.UNDEFINED

info[33723] = 1

out = str(tmp_path / "temp.tiff")
im.save(out, tiffinfo=info)

with Image.open(out) as reloaded:
assert reloaded.tag_v2[33723] == b"1"


def test_undefined_zero(tmp_path):
# Check that the tag has not been changed since this test was created
tag = TiffTags.TAGS_V2[45059]
Expand Down
6 changes: 5 additions & 1 deletion docs/handbook/image-file-formats.rst
Expand Up @@ -1126,7 +1126,7 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
If present and true, instructs the WebP writer to use lossless compression.

**quality**
Integer, 1-100, Defaults to 80. For lossy, 0 gives the smallest
Integer, 0-100, Defaults to 80. For lossy, 0 gives the smallest
size and 100 the largest. For lossless, this parameter is the amount
of effort put into the compression: 0 is the fastest, but gives larger
files compared to the slowest, but best, 100.
Expand All @@ -1147,6 +1147,10 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
The exif data to include in the saved file. Only supported if
the system WebP library was built with webpmux support.

**xmp**
The XMP data to include in the saved file. Only supported if
the system WebP library was built with webpmux support.

Saving sequences
~~~~~~~~~~~~~~~~

Expand Down
16 changes: 9 additions & 7 deletions docs/installation.rst
Expand Up @@ -458,21 +458,23 @@ These platforms are built and tested for every change.
+----------------------------------+----------------------------+---------------------+
| Gentoo | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10, 3.11, | x86-64 |
| | PyPy3.8, PyPy3.9 | |
| macOS 12 Monterey | 3.7, 3.8, 3.9, 3.10, 3.11, | x86-64 |
| | 3.12, PyPy3.8, PyPy3.9 | |
+----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 18.04 LTS (Bionic) | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 20.04 LTS (Focal) | 3.7, 3.8, 3.9, 3.10, 3.11, | x86-64 |
| | PyPy3.8, PyPy3.9 | |
| Ubuntu Linux 20.04 LTS (Focal) | 3.8 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | arm64v8, ppc64le, |
| | | s390x, x86-64 |
| Ubuntu Linux 22.04 LTS (Jammy) | 3.7, 3.8, 3.9, 3.10, 3.11, | x86-64 |
| | 3.12, PyPy3.8, PyPy3.9 | |
| +----------------------------+---------------------+
| | 3.10 | arm64v8, ppc64le, |
| | | s390x |
+----------------------------------+----------------------------+---------------------+
| Windows Server 2016 | 3.7 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Windows Server 2022 | 3.7, 3.8, 3.9, 3.10, 3.11, | x86, x86-64 |
| | PyPy3.8, PyPy3.9 | |
| | 3.12, PyPy3.8, PyPy3.9 | |
+----------------------------------+----------------------------+---------------------+
| Windows Server 2022 (MSYS2) | 3.10 | x86-64 |
| | | (CLANG64, MINGW64) |
Expand Down
38 changes: 38 additions & 0 deletions src/PIL/Image.py
Expand Up @@ -1432,6 +1432,11 @@ def get_value(element):
return {get_name(root.tag): get_value(root)}

def getexif(self):
"""
Gets EXIF data from the image.
:returns: an :py:class:`~PIL.Image.Exif` object.
"""
if self._exif is None:
self._exif = Exif()
self._exif._loaded = False
Expand Down Expand Up @@ -3601,6 +3606,39 @@ def _apply_env_variables(env=None):


class Exif(MutableMapping):
"""
This class provides read and write access to EXIF image data::
from PIL import Image
im = Image.open("exif.png")
exif = im.getexif() # Returns an instance of this class
Information can be read and written, iterated over or deleted::
print(exif[274]) # 1
exif[274] = 2
for k, v in exif.items():
print("Tag", k, "Value", v) # Tag 274 Value 2
del exif[274]
To access information beyond IFD0, :py:meth:`~PIL.Image.Exif.get_ifd`
returns a dictionary::
from PIL import ExifTags
im = Image.open("exif_gps.jpg")
exif = im.getexif()
gps_ifd = exif.get_ifd(ExifTags.IFD.GPSInfo)
print(gps_ifd)
Other IFDs include ``ExifTags.IFD.Exif``, ``ExifTags.IFD.Makernote``,
``ExifTags.IFD.Interop`` and ``ExifTags.IFD.IFD1``.
:py:mod:`~PIL.ExifTags` also has enum classes to provide names for data::
print(exif[ExifTags.Base.Software]) # PIL
print(gps_ifd[ExifTags.GPS.GPSDateStamp]) # 1999:99:99 99:99:99
"""

endian = None
bigtiff = False

Expand Down
5 changes: 4 additions & 1 deletion src/PIL/JpegImagePlugin.py
Expand Up @@ -730,10 +730,10 @@ def validate_qtables(qtables):

extra = info.get("extra", b"")

MAX_BYTES_IN_MARKER = 65533
icc_profile = info.get("icc_profile")
if icc_profile:
ICC_OVERHEAD_LEN = 14
MAX_BYTES_IN_MARKER = 65533
MAX_DATA_BYTES_IN_MARKER = MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN
markers = []
while icc_profile:
Expand Down Expand Up @@ -764,6 +764,9 @@ def validate_qtables(qtables):
exif = info.get("exif", b"")
if isinstance(exif, Image.Exif):
exif = exif.tobytes()
if len(exif) > MAX_BYTES_IN_MARKER:
msg = "EXIF data is too long"
raise ValueError(msg)

# get keyword arguments
im.encoderconfig = (
Expand Down
2 changes: 2 additions & 0 deletions src/PIL/TiffImagePlugin.py
Expand Up @@ -764,6 +764,8 @@ def load_undefined(self, data, legacy_api=True):

@_register_writer(7)
def write_undefined(self, value):
if isinstance(value, int):
value = str(value).encode("ascii", "replace")
return value

@_register_loader(10, 8)
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/TiffTags.py
Expand Up @@ -312,7 +312,7 @@ def lookup(tag, group=None):
34910: "HylaFAX FaxRecvTime",
36864: "ExifVersion",
36867: "DateTimeOriginal",
36868: "DateTImeDigitized",
36868: "DateTimeDigitized",
37121: "ComponentsConfiguration",
37122: "CompressedBitsPerPixel",
37724: "ImageSourceData",
Expand Down
3 changes: 0 additions & 3 deletions src/_imaging.c
Expand Up @@ -3984,8 +3984,6 @@ PyImaging_GrabScreenWin32(PyObject *self, PyObject *args);
extern PyObject *
PyImaging_GrabClipboardWin32(PyObject *self, PyObject *args);
extern PyObject *
PyImaging_ListWindowsWin32(PyObject *self, PyObject *args);
extern PyObject *
PyImaging_EventLoopWin32(PyObject *self, PyObject *args);
extern PyObject *
PyImaging_DrawWmf(PyObject *self, PyObject *args);
Expand Down Expand Up @@ -4069,7 +4067,6 @@ static PyMethodDef functions[] = {
{"grabclipboard_win32", (PyCFunction)PyImaging_GrabClipboardWin32, METH_VARARGS},
{"createwindow", (PyCFunction)PyImaging_CreateWindowWin32, METH_VARARGS},
{"eventloop", (PyCFunction)PyImaging_EventLoopWin32, METH_VARARGS},
{"listwindows", (PyCFunction)PyImaging_ListWindowsWin32, METH_VARARGS},
{"drawwmf", (PyCFunction)PyImaging_DrawWmf, METH_VARARGS},
#endif
#ifdef HAVE_XCB
Expand Down
73 changes: 0 additions & 73 deletions src/display.c
Expand Up @@ -421,79 +421,6 @@ PyImaging_GrabScreenWin32(PyObject *self, PyObject *args) {
return NULL;
}

static BOOL CALLBACK
list_windows_callback(HWND hwnd, LPARAM lParam) {
PyObject *window_list = (PyObject *)lParam;
PyObject *item;
PyObject *title;
RECT inner, outer;
int title_size;
int status;

/* get window title */
title_size = GetWindowTextLength(hwnd);
if (title_size > 0) {
title = PyUnicode_FromStringAndSize(NULL, title_size);
if (title) {
GetWindowTextW(hwnd, PyUnicode_AS_UNICODE(title), title_size + 1);
}
} else {
title = PyUnicode_FromString("");
}
if (!title) {
return 0;
}

/* get bounding boxes */
GetClientRect(hwnd, &inner);
GetWindowRect(hwnd, &outer);

item = Py_BuildValue(
F_HANDLE "N(iiii)(iiii)",
hwnd,
title,
inner.left,
inner.top,
inner.right,
inner.bottom,
outer.left,
outer.top,
outer.right,
outer.bottom);
if (!item) {
return 0;
}

status = PyList_Append(window_list, item);

Py_DECREF(item);

if (status < 0) {
return 0;
}

return 1;
}

PyObject *
PyImaging_ListWindowsWin32(PyObject *self, PyObject *args) {
PyObject *window_list;

window_list = PyList_New(0);
if (!window_list) {
return NULL;
}

EnumWindows(list_windows_callback, (LPARAM)window_list);

if (PyErr_Occurred()) {
Py_DECREF(window_list);
return NULL;
}

return window_list;
}

/* -------------------------------------------------------------------- */
/* Windows clipboard grabber */

Expand Down
6 changes: 3 additions & 3 deletions winbuild/build_prepare.py
Expand Up @@ -356,9 +356,9 @@ def cmd_msbuild(
"libs": [r"imagequant.lib"],
},
"harfbuzz": {
"url": "https://github.com/harfbuzz/harfbuzz/archive/6.0.0.zip",
"filename": "harfbuzz-6.0.0.zip",
"dir": "harfbuzz-6.0.0",
"url": "https://github.com/harfbuzz/harfbuzz/archive/7.0.0.zip",
"filename": "harfbuzz-7.0.0.zip",
"dir": "harfbuzz-7.0.0",
"license": "COPYING",
"build": [
cmd_set("CXXFLAGS", "-d2FH4-"),
Expand Down

0 comments on commit 71df356

Please sign in to comment.