Merge branch 'main' into apng_duration

This commit is contained in:
Andrew Murray 2023-06-06 18:00:48 +10:00
commit 400716da0d
33 changed files with 216 additions and 72 deletions

View File

@ -22,7 +22,8 @@ set -e
if [[ $(uname) != CYGWIN* ]]; then if [[ $(uname) != CYGWIN* ]]; then
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\ sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\ ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\
cmake meson imagemagick libharfbuzz-dev libfribidi-dev cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
sway wl-clipboard
fi fi
python3 -m pip install --upgrade pip python3 -m pip install --upgrade pip
@ -41,7 +42,7 @@ if [[ $(uname) != CYGWIN* ]]; then
if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi
# PyQt6 doesn't support PyPy3 # PyQt6 doesn't support PyPy3
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then if [[ "$GHA_PYTHON_VERSION" != "3.12-dev" && $GHA_PYTHON_VERSION == 3.* ]]; then
sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0 sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
python3 -m pip install pyqt6 python3 -m pip install pyqt6
fi fi

View File

@ -13,10 +13,6 @@ indent_style = space
trim_trailing_whitespace = true trim_trailing_whitespace = true
[*.rst]
# Four-space indentation
indent_size = 4
[*.yml] [*.yml]
# Two-space indentation # Two-space indentation
indent_size = 2 indent_size = 2

View File

@ -65,8 +65,8 @@ jobs:
- name: Print build system information - name: Print build system information
run: python3 .github/workflows/system-info.py run: python3 .github/workflows/system-info.py
- name: python3 -m pip install wheel pytest pytest-cov pytest-timeout defusedxml - name: python3 -m pip install setuptools wheel pytest pytest-cov pytest-timeout defusedxml
run: python3 -m pip install wheel pytest pytest-cov pytest-timeout defusedxml run: python3 -m pip install setuptools wheel pytest pytest-cov pytest-timeout defusedxml
- name: Install dependencies - name: Install dependencies
id: install id: install

View File

@ -84,7 +84,9 @@ jobs:
python3 -m pip install pytest-reverse python3 -m pip install pytest-reverse
fi fi
if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then
xvfb-run -s '-screen 0 1024x768x24' .ci/test.sh xvfb-run -s '-screen 0 1024x768x24' sway&
export WAYLAND_DISPLAY=wayland-1
.ci/test.sh
else else
.ci/test.sh .ci/test.sh
fi fi

View File

@ -4,9 +4,6 @@ repos:
hooks: hooks:
- id: black - id: black
args: [--target-version=py38] args: [--target-version=py38]
# Only .py files, until https://github.com/psf/black/issues/402 resolved
files: \.py$
types: []
- repo: https://github.com/PyCQA/isort - repo: https://github.com/PyCQA/isort
rev: 5.12.0 rev: 5.12.0

View File

@ -1,6 +1,6 @@
version: 2 version: 2
formats: all formats: [pdf]
build: build:
os: ubuntu-22.04 os: ubuntu-22.04

View File

@ -5,6 +5,21 @@ Changelog (Pillow)
10.0.0 (unreleased) 10.0.0 (unreleased)
------------------- -------------------
- Improved wl-paste mimetype handling in ImageGrab #7094
[rrcgat, radarhere]
- Added _repr_jpeg_() for IPython display_jpeg #7135
[n3011, radarhere, nulano]
- Use "/sbin/ldconfig" if ldconfig is not found #7068
[radarhere]
- Prefer screenshots using XCB over gnome-screenshot #7143
[nulano, radarhere]
- Fixed joined corners for ImageDraw rounded_rectangle() odd dimensions #7151
[radarhere]
- Support reading signed 8-bit TIFF images #7111 - Support reading signed 8-bit TIFF images #7111
[radarhere] [radarhere]

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 B

View File

@ -252,6 +252,19 @@ def test_roundtrip_save_all(tmp_path):
assert reread.n_frames == 5 assert reread.n_frames == 5
def test_roundtrip_save_all_1(tmp_path):
out = str(tmp_path / "temp.gif")
im = Image.new("1", (1, 1))
im2 = Image.new("1", (1, 1), 1)
im.save(out, save_all=True, append_images=[im2])
with Image.open(out) as reloaded:
assert reloaded.getpixel((0, 0)) == 0
reloaded.seek(1)
assert reloaded.getpixel((0, 0)) == 255
@pytest.mark.parametrize( @pytest.mark.parametrize(
"path, mode", "path, mode",
( (

View File

@ -922,6 +922,19 @@ class TestFileJpeg:
im.load() im.load()
ImageFile.LOAD_TRUNCATED_IMAGES = False ImageFile.LOAD_TRUNCATED_IMAGES = False
def test_repr_jpeg(self):
im = hopper()
with Image.open(BytesIO(im._repr_jpeg_())) as repr_jpeg:
assert repr_jpeg.format == "JPEG"
assert_image_similar(im, repr_jpeg, 17)
def test_repr_jpeg_error(self):
im = hopper("F")
with pytest.raises(ValueError):
im._repr_jpeg_()
@pytest.mark.skipif(not is_win32(), reason="Windows only") @pytest.mark.skipif(not is_win32(), reason="Windows only")
@skip_unless_feature("jpg") @skip_unless_feature("jpg")

View File

@ -839,7 +839,9 @@ def test_rounded_rectangle_zero_radius(bbox):
"xy, suffix", "xy, suffix",
[ [
((20, 10, 80, 90), "x"), ((20, 10, 80, 90), "x"),
((20, 10, 81, 90), "x_odd"),
((10, 20, 90, 80), "y"), ((10, 20, 90, 80), "y"),
((10, 20, 90, 81), "y_odd"),
((20, 20, 80, 80), "both"), ((20, 20, 80, 80), "both"),
], ],
) )

View File

@ -98,3 +98,18 @@ $ms = new-object System.IO.MemoryStream(, $bytes)
im = ImageGrab.grabclipboard() im = ImageGrab.grabclipboard()
assert_image_equal_tofile(im, "Tests/images/hopper.png") assert_image_equal_tofile(im, "Tests/images/hopper.png")
@pytest.mark.skipif(
(
sys.platform != "linux"
or not all(shutil.which(cmd) for cmd in ("wl-paste", "wl-copy"))
),
reason="Linux with wl-clipboard only",
)
@pytest.mark.parametrize("ext", ("gif", "png", "ico"))
def test_grabclipboard_wl_clipboard(self, ext):
image_path = "Tests/images/hopper." + ext
with open(image_path, "rb") as fp:
subprocess.call(["wl-copy"], stdin=fp)
im = ImageGrab.grabclipboard()
assert_image_equal_tofile(im, image_path)

View File

@ -1,9 +1,9 @@
# Documentation: https://docs.codecov.io/docs/codecov-yaml # Documentation: https://docs.codecov.com/docs/codecov-yaml
codecov: codecov:
# Avoid "Missing base report" due to committing CHANGES.rst with "[CI skip]" # Avoid "Missing base report" due to committing CHANGES.rst with "[CI skip]"
# https://github.com/codecov/support/issues/363 # https://github.com/codecov/support/issues/363
# https://docs.codecov.io/docs/comparing-commits # https://docs.codecov.com/docs/comparing-commits
allow_coverage_offsets: true allow_coverage_offsets: true
comment: false comment: false

View File

@ -2,7 +2,7 @@
from livereload.compiler import shell from livereload.compiler import shell
from livereload.task import Task from livereload.task import Task
Task.add('*.rst', shell('make html')) Task.add("*.rst", shell("make html"))
Task.add('*/*.rst', shell('make html')) Task.add("*/*.rst", shell("make html"))
Task.add('Makefile', shell('make html')) Task.add("Makefile", shell("make html"))
Task.add('conf.py', shell('make html')) Task.add("conf.py", shell("make html"))

View File

@ -317,6 +317,17 @@ def setup(app):
app.add_css_file("css/dark.css") app.add_css_file("css/dark.css")
linkcheck_allowed_redirects = {
r"https://bestpractices.coreinfrastructure.org/projects/6331": r"https://bestpractices.coreinfrastructure.org/en/.*", # noqa: E501
r"https://badges.gitter.im/python-pillow/Pillow.svg": r"https://badges.gitter.im/repo.svg", # noqa: E501
r"https://gitter.im/python-pillow/Pillow?.*": r"https://app.gitter.im/#/room/#python-pillow_Pillow:gitter.im?.*", # noqa: E501
r"https://pillow.readthedocs.io/?badge=latest": r"https://pillow.readthedocs.io/en/stable/?badge=latest", # noqa: E501
r"https://pillow.readthedocs.io": r"https://pillow.readthedocs.io/en/stable/",
r"https://tidelift.com/badges/package/pypi/Pillow?.*": r"https://img.shields.io/badge/.*", # noqa: E501
r"https://zenodo.org/badge/17549/python-pillow/Pillow.svg": r"https://zenodo.org/badge/doi/[\.0-9]+/zenodo.[0-9]+.svg", # noqa: E501
r"https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow": r"https://zenodo.org/record/[0-9]+", # noqa: E501
}
# sphinx.ext.extlinks # sphinx.ext.extlinks
# This config is a dictionary of external sites, # This config is a dictionary of external sites,
# mapping unique short aliases to a base URL and a prefix. # mapping unique short aliases to a base URL and a prefix.

View File

@ -210,7 +210,7 @@ open-source users (and will reach EOL on 2023-12-08 for commercial licence holde
Support for PyQt5 and PySide2 has been removed from ``ImageQt``. Upgrade to Support for PyQt5 and PySide2 has been removed from ``ImageQt``. Upgrade to
`PyQt6 <https://www.riverbankcomputing.com/static/Docs/PyQt6/>`_ or `PyQt6 <https://www.riverbankcomputing.com/static/Docs/PyQt6/>`_ or
`PySide6 <https://doc.qt.io/qtforpython/>`_ instead. `PySide6 <https://doc.qt.io/qtforpython-6/>`_ instead.
Image.coerce_e Image.coerce_e
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~

View File

@ -243,6 +243,7 @@ Methods
.. py:method:: ImageDraw.line(xy, fill=None, width=0, joint=None) .. py:method:: ImageDraw.line(xy, fill=None, width=0, joint=None)
Draws a line between the coordinates in the ``xy`` list. Draws a line between the coordinates in the ``xy`` list.
The coordinate pixels are included in the drawn line.
:param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or :param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or
numeric values like ``[x, y, x, y, ...]``. numeric values like ``[x, y, x, y, ...]``.
@ -287,7 +288,7 @@ Methods
The polygon outline consists of straight lines between the given The polygon outline consists of straight lines between the given
coordinates, plus a straight line between the last and the first coordinates, plus a straight line between the last and the first
coordinate. coordinate. The coordinate pixels are included in the drawn polygon.
:param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or :param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or
numeric values like ``[x, y, x, y, ...]``. numeric values like ``[x, y, x, y, ...]``.

View File

@ -15,8 +15,9 @@ or the clipboard to a PIL image memory.
returned as an "RGBA" on macOS, or an "RGB" image otherwise. returned as an "RGBA" on macOS, or an "RGB" image otherwise.
If the bounding box is omitted, the entire screen is copied. If the bounding box is omitted, the entire screen is copied.
On Linux, if ``xdisplay`` is ``None`` then ``gnome-screenshot`` will be used if it On Linux, if ``xdisplay`` is ``None`` and the default X11 display does not return
is installed. To capture the default X11 display instead, pass ``xdisplay=""``. a snapshot of the screen, ``gnome-screenshot`` will be used as fallback if it is
installed. To disable this behaviour, pass ``xdisplay=""`` instead.
.. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS), 7.1.0 (Linux) .. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS), 7.1.0 (Linux)
@ -39,9 +40,11 @@ or the clipboard to a PIL image memory.
.. py:function:: grabclipboard() .. py:function:: grabclipboard()
Take a snapshot of the clipboard image, if any. Only macOS and Windows are currently supported. Take a snapshot of the clipboard image, if any.
.. versionadded:: 1.1.4 (Windows), 3.3.0 (macOS) On Linux, ``wl-paste`` or ``xclip`` is required.
.. versionadded:: 1.1.4 (Windows), 3.3.0 (macOS), 9.4.0 (Linux)
:return: On Windows, an image, a list of filenames, :return: On Windows, an image, a list of filenames,
or None if the clipboard does not contain image data or filenames. or None if the clipboard does not contain image data or filenames.
@ -49,3 +52,5 @@ or the clipboard to a PIL image memory.
On Mac, an image, On Mac, an image,
or None if the clipboard does not contain image data. or None if the clipboard does not contain image data.
On Linux, an image.

View File

@ -117,7 +117,7 @@ open-source users (and will reach EOL on 2023-12-08 for commercial licence holde
Support for PyQt5 and PySide2 has been removed from ``ImageQt``. Upgrade to Support for PyQt5 and PySide2 has been removed from ``ImageQt``. Upgrade to
`PyQt6 <https://www.riverbankcomputing.com/static/Docs/PyQt6/>`_ or `PyQt6 <https://www.riverbankcomputing.com/static/Docs/PyQt6/>`_ or
`PySide6 <https://doc.qt.io/qtforpython/>`_ instead. `PySide6 <https://doc.qt.io/qtforpython-6/>`_ instead.
Image.coerce_e Image.coerce_e
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
@ -160,6 +160,18 @@ TODO
Other Changes Other Changes
============= =============
Support display_jpeg() in IPython
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In addition to ``display()`` and ``display_png``, ``display_jpeg()`` can now
also be used to display images in IPython::
from PIL import Image
from IPython.display import display_jpeg
im = Image.new("RGB", (100, 100), (255, 0, 0))
display_jpeg(im)
Support reading signed 8-bit TIFF images Support reading signed 8-bit TIFF images
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -15,7 +15,7 @@ open-source users (and will reach EOL on 2023-12-08 for commercial licence holde
Support for PyQt5 and PySide2 has been deprecated from ``ImageQt`` and will be removed Support for PyQt5 and PySide2 has been deprecated from ``ImageQt`` and will be removed
in Pillow 10 (2023-07-01). Upgrade to in Pillow 10 (2023-07-01). Upgrade to
`PyQt6 <https://www.riverbankcomputing.com/static/Docs/PyQt6/>`_ or `PyQt6 <https://www.riverbankcomputing.com/static/Docs/PyQt6/>`_ or
`PySide6 <https://doc.qt.io/qtforpython/>`_ instead. `PySide6 <https://doc.qt.io/qtforpython-6/>`_ instead.
FreeTypeFont.getmask2 fill parameter FreeTypeFont.getmask2 fill parameter
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -10,6 +10,7 @@
import os import os
import re import re
import shutil
import struct import struct
import subprocess import subprocess
import sys import sys
@ -150,6 +151,7 @@ def _dbg(s, tp=None):
def _find_library_dirs_ldconfig(): def _find_library_dirs_ldconfig():
# Based on ctypes.util from Python 2 # Based on ctypes.util from Python 2
ldconfig = "ldconfig" if shutil.which("ldconfig") else "/sbin/ldconfig"
if sys.platform.startswith("linux") or sys.platform.startswith("gnu"): if sys.platform.startswith("linux") or sys.platform.startswith("gnu"):
if struct.calcsize("l") == 4: if struct.calcsize("l") == 4:
machine = os.uname()[4] + "-32" machine = os.uname()[4] + "-32"
@ -166,14 +168,14 @@ def _find_library_dirs_ldconfig():
# Assuming GLIBC's ldconfig (with option -p) # Assuming GLIBC's ldconfig (with option -p)
# Alpine Linux uses musl that can't print cache # Alpine Linux uses musl that can't print cache
args = ["ldconfig", "-p"] args = [ldconfig, "-p"]
expr = rf".*\({abi_type}.*\) => (.*)" expr = rf".*\({abi_type}.*\) => (.*)"
env = dict(os.environ) env = dict(os.environ)
env["LC_ALL"] = "C" env["LC_ALL"] = "C"
env["LANG"] = "C" env["LANG"] = "C"
elif sys.platform.startswith("freebsd"): elif sys.platform.startswith("freebsd"):
args = ["ldconfig", "-r"] args = [ldconfig, "-r"]
expr = r".* => (.*)" expr = r".* => (.*)"
env = {} env = {}

View File

@ -879,7 +879,7 @@ def _get_palette_bytes(im):
:param im: Image object :param im: Image object
:returns: Bytes, len<=768 suitable for inclusion in gif header :returns: Bytes, len<=768 suitable for inclusion in gif header
""" """
return im.palette.palette return im.palette.palette if im.palette else b""
def _get_background(im, info_background): def _get_background(im, info_background):

View File

@ -22,11 +22,11 @@ import os
import struct import struct
import sys import sys
from PIL import Image, ImageFile, PngImagePlugin, features from . import Image, ImageFile, PngImagePlugin, features
enable_jpeg2k = features.check_codec("jpg_2000") enable_jpeg2k = features.check_codec("jpg_2000")
if enable_jpeg2k: if enable_jpeg2k:
from PIL import Jpeg2KImagePlugin from . import Jpeg2KImagePlugin
MAGIC = b"icns" MAGIC = b"icns"
HEADERSIZE = 8 HEADERSIZE = 8

View File

@ -633,19 +633,34 @@ class Image:
) )
) )
def _repr_png_(self): def _repr_image(self, image_format):
"""iPython display hook support """Helper function for iPython display hook.
:returns: png version of the image as bytes :param image_format: Image format.
:returns: image as bytes, saved into the given format.
""" """
b = io.BytesIO() b = io.BytesIO()
try: try:
self.save(b, "PNG") self.save(b, image_format)
except Exception as e: except Exception as e:
msg = "Could not save to PNG for display" msg = f"Could not save to {image_format} for display"
raise ValueError(msg) from e raise ValueError(msg) from e
return b.getvalue() return b.getvalue()
def _repr_png_(self):
"""iPython display hook support for PNG format.
:returns: PNG version of the image as bytes
"""
return self._repr_image("PNG")
def _repr_jpeg_(self):
"""iPython display hook support for JPEG format.
:returns: JPEG version of the image as bytes
"""
return self._repr_image("JPEG")
@property @property
def __array_interface__(self): def __array_interface__(self):
# numpy array interface support # numpy array interface support
@ -1108,7 +1123,6 @@ class Image:
Available methods are :data:`Dither.NONE` or :data:`Dither.FLOYDSTEINBERG` Available methods are :data:`Dither.NONE` or :data:`Dither.FLOYDSTEINBERG`
(default). (default).
:returns: A new image :returns: A new image
""" """
self.load() self.load()

View File

@ -18,10 +18,10 @@
import sys import sys
from enum import IntEnum from enum import IntEnum
from PIL import Image from . import Image
try: try:
from PIL import _imagingcms from . import _imagingcms
except ImportError as ex: except ImportError as ex:
# Allow error import for doc purposes, but error out when accessing # Allow error import for doc purposes, but error out when accessing
# anything in core. # anything in core.
@ -271,7 +271,7 @@ def get_display_profile(handle=None):
if sys.platform != "win32": if sys.platform != "win32":
return None return None
from PIL import ImageWin from . import ImageWin
if isinstance(handle, ImageWin.HDC): if isinstance(handle, ImageWin.HDC):
profile = core.get_display_profile_win32(handle, 1) profile = core.get_display_profile_win32(handle, 1)

View File

@ -314,11 +314,11 @@ class ImageDraw:
full_x, full_y = False, False full_x, full_y = False, False
if all(corners): if all(corners):
full_x = d >= x1 - x0 full_x = d >= x1 - x0 - 1
if full_x: if full_x:
# The two left and two right corners are joined # The two left and two right corners are joined
d = x1 - x0 d = x1 - x0
full_y = d >= y1 - y0 full_y = d >= y1 - y0 - 1
if full_y: if full_y:
# The two top and two bottom corners are joined # The two top and two bottom corners are joined
d = y1 - y0 d = y1 - y0

View File

@ -15,6 +15,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
import io
import os import os
import shutil import shutil
import subprocess import subprocess
@ -61,7 +62,17 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=N
left, top, right, bottom = bbox left, top, right, bottom = bbox
im = im.crop((left - x0, top - y0, right - x0, bottom - y0)) im = im.crop((left - x0, top - y0, right - x0, bottom - y0))
return im return im
elif shutil.which("gnome-screenshot"): try:
if not Image.core.HAVE_XCB:
msg = "Pillow was built without XCB support"
raise OSError(msg)
size, data = Image.core.grabscreen_x11(xdisplay)
except OSError:
if (
xdisplay is None
and sys.platform not in ("darwin", "win32")
and shutil.which("gnome-screenshot")
):
fh, filepath = tempfile.mkstemp(".png") fh, filepath = tempfile.mkstemp(".png")
os.close(fh) os.close(fh)
subprocess.call(["gnome-screenshot", "-f", filepath]) subprocess.call(["gnome-screenshot", "-f", filepath])
@ -73,15 +84,13 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=N
im.close() im.close()
return im_cropped return im_cropped
return im return im
# use xdisplay=None for default display on non-win32/macOS systems else:
if not Image.core.HAVE_XCB: raise
msg = "Pillow was built without XCB support" else:
raise OSError(msg) im = Image.frombytes("RGB", size, data, "raw", "BGRX", size[0] * 4, 1)
size, data = Image.core.grabscreen_x11(xdisplay) if bbox:
im = Image.frombytes("RGB", size, data, "raw", "BGRX", size[0] * 4, 1) im = im.crop(bbox)
if bbox: return im
im = im.crop(bbox)
return im
def grabclipboard(): def grabclipboard():
@ -120,8 +129,6 @@ def grabclipboard():
files = data[o:].decode("mbcs").split("\0") files = data[o:].decode("mbcs").split("\0")
return files[: files.index("")] return files[: files.index("")]
if isinstance(data, bytes): if isinstance(data, bytes):
import io
data = io.BytesIO(data) data = io.BytesIO(data)
if fmt == "png": if fmt == "png":
from . import PngImagePlugin from . import PngImagePlugin
@ -134,19 +141,29 @@ def grabclipboard():
return None return None
else: else:
if shutil.which("wl-paste"): if shutil.which("wl-paste"):
output = subprocess.check_output(["wl-paste", "-l"]).decode()
mimetypes = output.splitlines()
if "image/png" in mimetypes:
mimetype = "image/png"
elif mimetypes:
mimetype = mimetypes[0]
else:
mimetype = None
args = ["wl-paste"] args = ["wl-paste"]
if mimetype:
args.extend(["-t", mimetype])
elif shutil.which("xclip"): elif shutil.which("xclip"):
args = ["xclip", "-selection", "clipboard", "-t", "image/png", "-o"] args = ["xclip", "-selection", "clipboard", "-t", "image/png", "-o"]
else: else:
msg = "wl-paste or xclip is required for ImageGrab.grabclipboard() on Linux" msg = "wl-paste or xclip is required for ImageGrab.grabclipboard() on Linux"
raise NotImplementedError(msg) raise NotImplementedError(msg)
fh, filepath = tempfile.mkstemp() p = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
err = subprocess.run(args, stdout=fh, stderr=subprocess.PIPE).stderr err = p.stderr
os.close(fh)
if err: if err:
msg = f"{args[0]} error: {err.strip().decode()}" msg = f"{args[0]} error: {err.strip().decode()}"
raise ChildProcessError(msg) raise ChildProcessError(msg)
im = Image.open(filepath) data = io.BytesIO(p.stdout)
im = Image.open(data)
im.load() im.load()
os.unlink(filepath)
return im return im

View File

@ -17,7 +17,7 @@ import subprocess
import sys import sys
from shlex import quote from shlex import quote
from PIL import Image from . import Image
_viewers = [] _viewers = []

View File

@ -36,7 +36,7 @@ import os
import struct import struct
import sys import sys
from PIL import Image, ImageFile from . import Image, ImageFile
def isInt(f): def isInt(f):
@ -191,7 +191,7 @@ class SpiderImageFile(ImageFile.ImageFile):
# returns a ImageTk.PhotoImage object, after rescaling to 0..255 # returns a ImageTk.PhotoImage object, after rescaling to 0..255
def tkPhotoImage(self): def tkPhotoImage(self):
from PIL import ImageTk from . import ImageTk
return ImageTk.PhotoImage(self.convert2byte(), palette=256) return ImageTk.PhotoImage(self.convert2byte(), palette=256)

View File

@ -132,6 +132,27 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
return NULL; return NULL;
} }
#if PY_MAJOR_VERSION > 3 || PY_MINOR_VERSION > 11
PyConfig config;
PyConfig_InitPythonConfig(&config);
if (!PyArg_ParseTupleAndKeywords(
args,
kw,
"etf|nsy#n",
kwlist,
config.filesystem_encoding,
&filename,
&size,
&index,
&encoding,
&font_bytes,
&font_bytes_size,
&layout_engine)) {
PyConfig_Clear(&config);
return NULL;
}
PyConfig_Clear(&config);
#else
if (!PyArg_ParseTupleAndKeywords( if (!PyArg_ParseTupleAndKeywords(
args, args,
kw, kw,
@ -147,6 +168,7 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
&layout_engine)) { &layout_engine)) {
return NULL; return NULL;
} }
#endif
self = PyObject_New(FontObject, &Font_Type); self = PyObject_New(FontObject, &Font_Type);
if (!self) { if (!self) {

View File

@ -437,8 +437,14 @@ PyImaging_GrabClipboardWin32(PyObject *self, PyObject *args) {
LPCSTR format_names[] = {"DIB", "DIB", "file", "png", NULL}; LPCSTR format_names[] = {"DIB", "DIB", "file", "png", NULL};
if (!OpenClipboard(NULL)) { if (!OpenClipboard(NULL)) {
PyErr_SetString(PyExc_OSError, "failed to open clipboard"); // Maybe the clipboard is temporarily in use by another process.
return NULL; // Wait and try again
Sleep(500);
if (!OpenClipboard(NULL)) {
PyErr_SetString(PyExc_OSError, "failed to open clipboard");
return NULL;
}
} }
// find best format as set by clipboard owner // find best format as set by clipboard owner

View File

@ -337,9 +337,9 @@ deps = {
"libs": [r"imagequant.lib"], "libs": [r"imagequant.lib"],
}, },
"harfbuzz": { "harfbuzz": {
"url": "https://github.com/harfbuzz/harfbuzz/archive/7.2.0.zip", "url": "https://github.com/harfbuzz/harfbuzz/archive/7.3.0.zip",
"filename": "harfbuzz-7.2.0.zip", "filename": "harfbuzz-7.3.0.zip",
"dir": "harfbuzz-7.2.0", "dir": "harfbuzz-7.3.0",
"license": "COPYING", "license": "COPYING",
"build": [ "build": [
*cmds_cmake( *cmds_cmake(
@ -352,12 +352,12 @@ deps = {
"libs": [r"*.lib"], "libs": [r"*.lib"],
}, },
"fribidi": { "fribidi": {
"url": "https://github.com/fribidi/fribidi/archive/v1.0.12.zip", "url": "https://github.com/fribidi/fribidi/archive/v1.0.13.zip",
"filename": "fribidi-1.0.12.zip", "filename": "fribidi-1.0.13.zip",
"dir": "fribidi-1.0.12", "dir": "fribidi-1.0.13",
"license": "COPYING", "license": "COPYING",
"build": [ "build": [
cmd_copy(r"COPYING", r"{bin_dir}\fribidi-1.0.12-COPYING"), cmd_copy(r"COPYING", r"{bin_dir}\fribidi-1.0.13-COPYING"),
cmd_copy(r"{winbuild_dir}\fribidi.cmake", r"CMakeLists.txt"), cmd_copy(r"{winbuild_dir}\fribidi.cmake", r"CMakeLists.txt"),
*cmds_cmake("fribidi"), *cmds_cmake("fribidi"),
], ],