mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-26 01:46:18 +03:00
Merge branch 'main' into int_def
This commit is contained in:
commit
68edbbca94
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
1
.github/workflows/test-docker.yml
vendored
1
.github/workflows/test-docker.yml
vendored
|
@ -39,6 +39,7 @@ jobs:
|
||||||
centos-stream-8-amd64,
|
centos-stream-8-amd64,
|
||||||
centos-stream-9-amd64,
|
centos-stream-9-amd64,
|
||||||
debian-11-bullseye-x86,
|
debian-11-bullseye-x86,
|
||||||
|
debian-12-bookworm-x86,
|
||||||
fedora-37-amd64,
|
fedora-37-amd64,
|
||||||
fedora-38-amd64,
|
fedora-38-amd64,
|
||||||
gentoo,
|
gentoo,
|
||||||
|
|
4
.github/workflows/test-windows.yml
vendored
4
.github/workflows/test-windows.yml
vendored
|
@ -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
|
||||||
|
|
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
24
CHANGES.rst
24
CHANGES.rst
|
@ -5,6 +5,30 @@ Changelog (Pillow)
|
||||||
10.0.0 (unreleased)
|
10.0.0 (unreleased)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
- Fixed combining single duration across duplicate APNG frames #7146
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Remove temporary file when error is raised #7148
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Do not use temporary file when grabbing clipboard on Linux #7200
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- If the clipboard fails to open on Windows, wait and try again #7141
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed saving multiple 1 mode frames to GIF #7181
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Replaced absolute PIL import with relative import #7173
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Replaced deprecated Py_FileSystemDefaultEncoding for Python >= 3.12 #7192
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Improved wl-paste mimetype handling in ImageGrab #7094
|
||||||
|
[rrcgat, radarhere]
|
||||||
|
|
||||||
- Added _repr_jpeg_() for IPython display_jpeg #7135
|
- Added _repr_jpeg_() for IPython display_jpeg #7135
|
||||||
[n3011, radarhere, nulano]
|
[n3011, radarhere, nulano]
|
||||||
|
|
||||||
|
|
|
@ -447,6 +447,17 @@ def test_apng_save_duration_loop(tmp_path):
|
||||||
assert im.info.get("duration") == 750
|
assert im.info.get("duration") == 750
|
||||||
|
|
||||||
|
|
||||||
|
def test_apng_save_duplicate_duration(tmp_path):
|
||||||
|
test_file = str(tmp_path / "temp.png")
|
||||||
|
frame = Image.new("RGB", (1, 1))
|
||||||
|
|
||||||
|
# Test a single duration is correctly combined across duplicate frames
|
||||||
|
frame.save(test_file, save_all=True, append_images=[frame, frame], duration=500)
|
||||||
|
with Image.open(test_file) as im:
|
||||||
|
assert im.n_frames == 1
|
||||||
|
assert im.info.get("duration") == 1500
|
||||||
|
|
||||||
|
|
||||||
def test_apng_save_disposal(tmp_path):
|
def test_apng_save_disposal(tmp_path):
|
||||||
test_file = str(tmp_path / "temp.png")
|
test_file = str(tmp_path / "temp.png")
|
||||||
size = (128, 64)
|
size = (128, 64)
|
||||||
|
|
|
@ -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",
|
||||||
(
|
(
|
||||||
|
|
|
@ -96,10 +96,17 @@ class TestFileTiff:
|
||||||
|
|
||||||
assert_image_similar_tofile(im, "Tests/images/pil136.png", 1)
|
assert_image_similar_tofile(im, "Tests/images/pil136.png", 1)
|
||||||
|
|
||||||
def test_bigtiff(self):
|
def test_bigtiff(self, tmp_path):
|
||||||
with Image.open("Tests/images/hopper_bigtiff.tif") as im:
|
with Image.open("Tests/images/hopper_bigtiff.tif") as im:
|
||||||
assert_image_equal_tofile(im, "Tests/images/hopper.tif")
|
assert_image_equal_tofile(im, "Tests/images/hopper.tif")
|
||||||
|
|
||||||
|
with Image.open("Tests/images/hopper_bigtiff.tif") as im:
|
||||||
|
# multistrip support not yet implemented
|
||||||
|
del im.tag_v2[273]
|
||||||
|
|
||||||
|
outfile = str(tmp_path / "temp.tif")
|
||||||
|
im.save(outfile, save_all=True, append_images=[im], tiffinfo=im.tag_v2)
|
||||||
|
|
||||||
def test_set_legacy_api(self):
|
def test_set_legacy_api(self):
|
||||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
with pytest.raises(Exception) as e:
|
with pytest.raises(Exception) as e:
|
||||||
|
|
|
@ -32,6 +32,14 @@ def test_putpalette():
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
palette("YCbCr")
|
palette("YCbCr")
|
||||||
|
|
||||||
|
with Image.open("Tests/images/hopper_gray.jpg") as im:
|
||||||
|
assert im.mode == "L"
|
||||||
|
im.putpalette(list(range(256)) * 3)
|
||||||
|
|
||||||
|
with Image.open("Tests/images/la.tga") as im:
|
||||||
|
assert im.mode == "LA"
|
||||||
|
im.putpalette(list(range(256)) * 3)
|
||||||
|
|
||||||
|
|
||||||
def test_imagepalette():
|
def test_imagepalette():
|
||||||
im = hopper("P")
|
im = hopper("P")
|
||||||
|
|
|
@ -463,6 +463,11 @@ def test_default_font():
|
||||||
assert_image_equal_tofile(im, "Tests/images/default_font.png")
|
assert_image_equal_tofile(im, "Tests/images/default_font.png")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mode", (None, "1", "RGBA"))
|
||||||
|
def test_getbbox(font, mode):
|
||||||
|
assert (0, 4, 12, 16) == font.getbbox("A", mode)
|
||||||
|
|
||||||
|
|
||||||
def test_getbbox_empty(font):
|
def test_getbbox_empty(font):
|
||||||
# issue #2614, should not crash.
|
# issue #2614, should not crash.
|
||||||
assert (0, 0, 0, 0) == font.getbbox("")
|
assert (0, 0, 0, 0) == font.getbbox("")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"))
|
||||||
|
|
|
@ -95,9 +95,8 @@ in the upper left corner. Note that the coordinates refer to the implied pixel
|
||||||
corners; the centre of a pixel addressed as (0, 0) actually lies at (0.5, 0.5).
|
corners; the centre of a pixel addressed as (0, 0) actually lies at (0.5, 0.5).
|
||||||
|
|
||||||
Coordinates are usually passed to the library as 2-tuples (x, y). Rectangles
|
Coordinates are usually passed to the library as 2-tuples (x, y). Rectangles
|
||||||
are represented as 4-tuples, with the upper left corner given first. For
|
are represented as 4-tuples, (x1, y1, x2, y2), with the upper left corner given
|
||||||
example, a rectangle covering all of an 800x600 pixel image is written as (0,
|
first.
|
||||||
0, 800, 600).
|
|
||||||
|
|
||||||
Palette
|
Palette
|
||||||
-------
|
-------
|
||||||
|
|
|
@ -448,6 +448,8 @@ These platforms are built and tested for every change.
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Debian 11 Bullseye | 3.9 | x86 |
|
| Debian 11 Bullseye | 3.9 | x86 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
|
| Debian 12 Bookworm | 3.11 | x86 |
|
||||||
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Fedora 37 | 3.11 | x86-64 |
|
| Fedora 37 | 3.11 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Fedora 38 | 3.11 | x86-64 |
|
| Fedora 38 | 3.11 | x86-64 |
|
||||||
|
|
|
@ -134,6 +134,13 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False):
|
||||||
|
|
||||||
if gs_windows_binary is not None:
|
if gs_windows_binary is not None:
|
||||||
if not gs_windows_binary:
|
if not gs_windows_binary:
|
||||||
|
try:
|
||||||
|
os.unlink(outfile)
|
||||||
|
if infile_temp:
|
||||||
|
os.unlink(infile_temp)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
msg = "Unable to locate Ghostscript on paths"
|
msg = "Unable to locate Ghostscript on paths"
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
command[0] = gs_windows_binary
|
command[0] = gs_windows_binary
|
||||||
|
@ -354,7 +361,6 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
check_required_header_comments()
|
check_required_header_comments()
|
||||||
|
|
||||||
if not self._size:
|
if not self._size:
|
||||||
self._size = 1, 1 # errors if this isn't set. why (1,1)?
|
|
||||||
msg = "cannot determine EPS bounding box"
|
msg = "cannot determine EPS bounding box"
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1254,7 +1254,7 @@ class Image:
|
||||||
if ymargin is None:
|
if ymargin is None:
|
||||||
ymargin = xmargin
|
ymargin = xmargin
|
||||||
self.load()
|
self.load()
|
||||||
return self._new(self.im.expand(xmargin, ymargin, 0))
|
return self._new(self.im.expand(xmargin, ymargin))
|
||||||
|
|
||||||
def filter(self, filter):
|
def filter(self, filter):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -43,7 +43,8 @@ class Kernel(BuiltinFilter):
|
||||||
|
|
||||||
:param size: Kernel size, given as (width, height). In the current
|
:param size: Kernel size, given as (width, height). In the current
|
||||||
version, this must be (3,3) or (5,5).
|
version, this must be (3,3) or (5,5).
|
||||||
:param kernel: A sequence containing kernel weights.
|
:param kernel: A sequence containing kernel weights. The kernel will
|
||||||
|
be flipped vertically before being applied to the image.
|
||||||
:param scale: Scale factor. If given, the result for each pixel is
|
:param scale: Scale factor. If given, the result for each pixel is
|
||||||
divided by this value. The default is the sum of the
|
divided by this value. The default is the sum of the
|
||||||
kernel weights.
|
kernel weights.
|
||||||
|
|
|
@ -26,7 +26,6 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import math
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
|
@ -226,10 +225,6 @@ class FreeTypeFont:
|
||||||
path, size, index, encoding, layout_engine = state
|
path, size, index, encoding, layout_engine = state
|
||||||
self.__init__(path, size, index, encoding, layout_engine)
|
self.__init__(path, size, index, encoding, layout_engine)
|
||||||
|
|
||||||
def _multiline_split(self, text):
|
|
||||||
split_character = "\n" if isinstance(text, str) else b"\n"
|
|
||||||
return text.split(split_character)
|
|
||||||
|
|
||||||
def getname(self):
|
def getname(self):
|
||||||
"""
|
"""
|
||||||
:return: A tuple of the font family (e.g. Helvetica) and the font style
|
:return: A tuple of the font family (e.g. Helvetica) and the font style
|
||||||
|
@ -551,28 +546,23 @@ class FreeTypeFont:
|
||||||
:py:mod:`PIL.Image.core` interface module, and the text offset, the
|
:py:mod:`PIL.Image.core` interface module, and the text offset, the
|
||||||
gap between the starting coordinate and the first marking
|
gap between the starting coordinate and the first marking
|
||||||
"""
|
"""
|
||||||
size, offset = self.font.getsize(
|
|
||||||
text, mode, direction, features, language, anchor
|
|
||||||
)
|
|
||||||
if start is None:
|
if start is None:
|
||||||
start = (0, 0)
|
start = (0, 0)
|
||||||
size = tuple(math.ceil(size[i] + stroke_width * 2 + start[i]) for i in range(2))
|
im, size, offset = self.font.render(
|
||||||
offset = offset[0] - stroke_width, offset[1] - stroke_width
|
|
||||||
Image._decompression_bomb_check(size)
|
|
||||||
im = Image.core.fill("RGBA" if mode == "RGBA" else "L", size, 0)
|
|
||||||
if min(size):
|
|
||||||
self.font.render(
|
|
||||||
text,
|
text,
|
||||||
im.id,
|
Image.core.fill,
|
||||||
mode,
|
mode,
|
||||||
direction,
|
direction,
|
||||||
features,
|
features,
|
||||||
language,
|
language,
|
||||||
stroke_width,
|
stroke_width,
|
||||||
|
anchor,
|
||||||
ink,
|
ink,
|
||||||
start[0],
|
start[0],
|
||||||
start[1],
|
start[1],
|
||||||
|
Image.MAX_IMAGE_PIXELS,
|
||||||
)
|
)
|
||||||
|
Image._decompression_bomb_check(size)
|
||||||
return im, offset
|
return im, offset
|
||||||
|
|
||||||
def font_variant(
|
def font_variant(
|
||||||
|
|
|
@ -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
|
||||||
|
@ -128,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
|
||||||
|
@ -142,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
|
||||||
|
|
|
@ -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 = []
|
||||||
|
|
||||||
|
|
|
@ -457,6 +457,11 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
if os.path.exists(self.filename):
|
if os.path.exists(self.filename):
|
||||||
subprocess.check_call(["djpeg", "-outfile", path, self.filename])
|
subprocess.check_call(["djpeg", "-outfile", path, self.filename])
|
||||||
else:
|
else:
|
||||||
|
try:
|
||||||
|
os.unlink(path)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
msg = "Invalid Filename"
|
msg = "Invalid Filename"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
|
|
@ -1146,11 +1146,14 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
|
||||||
and prev_disposal == encoderinfo.get("disposal")
|
and prev_disposal == encoderinfo.get("disposal")
|
||||||
and prev_blend == encoderinfo.get("blend")
|
and prev_blend == encoderinfo.get("blend")
|
||||||
):
|
):
|
||||||
if isinstance(duration, (list, tuple)):
|
previous["encoderinfo"]["duration"] += encoderinfo.get(
|
||||||
previous["encoderinfo"]["duration"] += encoderinfo["duration"]
|
"duration", duration
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
bbox = None
|
bbox = None
|
||||||
|
if "duration" not in encoderinfo:
|
||||||
|
encoderinfo["duration"] = duration
|
||||||
im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo})
|
im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo})
|
||||||
|
|
||||||
# animation control
|
# animation control
|
||||||
|
@ -1175,7 +1178,7 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
|
||||||
im_frame = im_frame.crop(bbox)
|
im_frame = im_frame.crop(bbox)
|
||||||
size = im_frame.size
|
size = im_frame.size
|
||||||
encoderinfo = frame_data["encoderinfo"]
|
encoderinfo = frame_data["encoderinfo"]
|
||||||
frame_duration = int(round(encoderinfo.get("duration", duration)))
|
frame_duration = int(round(encoderinfo["duration"]))
|
||||||
frame_disposal = encoderinfo.get("disposal", disposal)
|
frame_disposal = encoderinfo.get("disposal", disposal)
|
||||||
frame_blend = encoderinfo.get("blend", blend)
|
frame_blend = encoderinfo.get("blend", blend)
|
||||||
# frame control
|
# frame control
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -1894,6 +1894,10 @@ class AppendingTiffWriter:
|
||||||
8, # srational
|
8, # srational
|
||||||
4, # float
|
4, # float
|
||||||
8, # double
|
8, # double
|
||||||
|
4, # ifd
|
||||||
|
2, # unicode
|
||||||
|
4, # complex
|
||||||
|
8, # long8
|
||||||
]
|
]
|
||||||
|
|
||||||
# StripOffsets = 273
|
# StripOffsets = 273
|
||||||
|
|
|
@ -1027,12 +1027,11 @@ _crop(ImagingObject *self, PyObject *args) {
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_expand_image(ImagingObject *self, PyObject *args) {
|
_expand_image(ImagingObject *self, PyObject *args) {
|
||||||
int x, y;
|
int x, y;
|
||||||
int mode = 0;
|
if (!PyArg_ParseTuple(args, "ii", &x, &y)) {
|
||||||
if (!PyArg_ParseTuple(args, "ii|i", &x, &y, &mode)) {
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return PyImagingNew(ImagingExpand(self->image, x, y, mode));
|
return PyImagingNew(ImagingExpand(self->image, x, y));
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
|
239
src/_imagingft.c
239
src/_imagingft.c
|
@ -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) {
|
||||||
|
@ -232,9 +254,7 @@ text_layout_raqm(
|
||||||
const char *dir,
|
const char *dir,
|
||||||
PyObject *features,
|
PyObject *features,
|
||||||
const char *lang,
|
const char *lang,
|
||||||
GlyphInfo **glyph_info,
|
GlyphInfo **glyph_info) {
|
||||||
int mask,
|
|
||||||
int color) {
|
|
||||||
size_t i = 0, count = 0, start = 0;
|
size_t i = 0, count = 0, start = 0;
|
||||||
raqm_t *rq;
|
raqm_t *rq;
|
||||||
raqm_glyph_t *glyphs = NULL;
|
raqm_glyph_t *glyphs = NULL;
|
||||||
|
@ -471,7 +491,7 @@ text_layout(
|
||||||
#ifdef HAVE_RAQM
|
#ifdef HAVE_RAQM
|
||||||
if (have_raqm && self->layout_engine == LAYOUT_RAQM) {
|
if (have_raqm && self->layout_engine == LAYOUT_RAQM) {
|
||||||
count = text_layout_raqm(
|
count = text_layout_raqm(
|
||||||
string, self, dir, features, lang, glyph_info, mask, color);
|
string, self, dir, features, lang, glyph_info);
|
||||||
} else
|
} else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
|
@ -529,73 +549,25 @@ font_getlength(FontObject *self, PyObject *args) {
|
||||||
return PyLong_FromLong(length);
|
return PyLong_FromLong(length);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static int
|
||||||
font_getsize(FontObject *self, PyObject *args) {
|
bounding_box_and_anchors(FT_Face face, const char *anchor, int horizontal_dir, GlyphInfo *glyph_info, size_t count, int load_flags, int *width, int *height, int *x_offset, int *y_offset) {
|
||||||
int position; /* pen position along primary axis, in 26.6 precision */
|
int position; /* pen position along primary axis, in 26.6 precision */
|
||||||
int advanced; /* pen position along primary axis, in pixels */
|
int advanced; /* pen position along primary axis, in pixels */
|
||||||
int px, py; /* position of current glyph, in pixels */
|
int px, py; /* position of current glyph, in pixels */
|
||||||
int x_min, x_max, y_min, y_max; /* text bounding box, in pixels */
|
int x_min, x_max, y_min, y_max; /* text bounding box, in pixels */
|
||||||
int x_anchor, y_anchor; /* offset of point drawn at (0, 0), in pixels */
|
int x_anchor, y_anchor; /* offset of point drawn at (0, 0), in pixels */
|
||||||
int load_flags; /* FreeType load_flags parameter */
|
|
||||||
int error;
|
int error;
|
||||||
FT_Face face;
|
|
||||||
FT_Glyph glyph;
|
FT_Glyph glyph;
|
||||||
FT_BBox bbox; /* glyph bounding box */
|
FT_BBox bbox; /* glyph bounding box */
|
||||||
GlyphInfo *glyph_info = NULL; /* computed text layout */
|
size_t i; /* glyph_info index */
|
||||||
size_t i, count; /* glyph_info index and length */
|
|
||||||
int horizontal_dir; /* is primary axis horizontal? */
|
|
||||||
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
|
|
||||||
int color = 0; /* is FT_LOAD_COLOR enabled? */
|
|
||||||
const char *mode = NULL;
|
|
||||||
const char *dir = NULL;
|
|
||||||
const char *lang = NULL;
|
|
||||||
const char *anchor = NULL;
|
|
||||||
PyObject *features = Py_None;
|
|
||||||
PyObject *string;
|
|
||||||
|
|
||||||
/* calculate size and bearing for a given string */
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(
|
|
||||||
args, "O|zzOzz:getsize", &string, &mode, &dir, &features, &lang, &anchor)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
|
||||||
|
|
||||||
mask = mode && strcmp(mode, "1") == 0;
|
|
||||||
color = mode && strcmp(mode, "RGBA") == 0;
|
|
||||||
|
|
||||||
if (anchor == NULL) {
|
|
||||||
anchor = horizontal_dir ? "la" : "lt";
|
|
||||||
}
|
|
||||||
if (strlen(anchor) != 2) {
|
|
||||||
goto bad_anchor;
|
|
||||||
}
|
|
||||||
|
|
||||||
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
|
|
||||||
if (PyErr_Occurred()) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
load_flags = FT_LOAD_DEFAULT;
|
|
||||||
if (mask) {
|
|
||||||
load_flags |= FT_LOAD_TARGET_MONO;
|
|
||||||
}
|
|
||||||
if (color) {
|
|
||||||
load_flags |= FT_LOAD_COLOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* text bounds are given by:
|
* text bounds are given by:
|
||||||
* - bounding boxes of individual glyphs
|
* - bounding boxes of individual glyphs
|
||||||
* - pen line, i.e. 0 to `advanced` along primary axis
|
* - pen line, i.e. 0 to `advanced` along primary axis
|
||||||
* this means point (0, 0) is part of the text bounding box
|
* this means point (0, 0) is part of the text bounding box
|
||||||
*/
|
*/
|
||||||
face = NULL;
|
|
||||||
position = x_min = x_max = y_min = y_max = 0;
|
position = x_min = x_max = y_min = y_max = 0;
|
||||||
for (i = 0; i < count; i++) {
|
for (i = 0; i < count; i++) {
|
||||||
face = self->face;
|
|
||||||
|
|
||||||
if (horizontal_dir) {
|
if (horizontal_dir) {
|
||||||
px = PIXEL(position + glyph_info[i].x_offset);
|
px = PIXEL(position + glyph_info[i].x_offset);
|
||||||
py = PIXEL(glyph_info[i].y_offset);
|
py = PIXEL(glyph_info[i].y_offset);
|
||||||
|
@ -618,12 +590,14 @@ font_getsize(FontObject *self, PyObject *args) {
|
||||||
|
|
||||||
error = FT_Load_Glyph(face, glyph_info[i].index, load_flags);
|
error = FT_Load_Glyph(face, glyph_info[i].index, load_flags);
|
||||||
if (error) {
|
if (error) {
|
||||||
return geterror(error);
|
geterror(error);
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
error = FT_Get_Glyph(face->glyph, &glyph);
|
error = FT_Get_Glyph(face->glyph, &glyph);
|
||||||
if (error) {
|
if (error) {
|
||||||
return geterror(error);
|
geterror(error);
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_PIXELS, &bbox);
|
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_PIXELS, &bbox);
|
||||||
|
@ -647,13 +621,15 @@ font_getsize(FontObject *self, PyObject *args) {
|
||||||
FT_Done_Glyph(glyph);
|
FT_Done_Glyph(glyph);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (glyph_info) {
|
if (anchor == NULL) {
|
||||||
PyMem_Free(glyph_info);
|
anchor = horizontal_dir ? "la" : "lt";
|
||||||
glyph_info = NULL;
|
}
|
||||||
|
if (strlen(anchor) != 2) {
|
||||||
|
goto bad_anchor;
|
||||||
}
|
}
|
||||||
|
|
||||||
x_anchor = y_anchor = 0;
|
x_anchor = y_anchor = 0;
|
||||||
if (face) {
|
if (count) {
|
||||||
if (horizontal_dir) {
|
if (horizontal_dir) {
|
||||||
switch (anchor[0]) {
|
switch (anchor[0]) {
|
||||||
case 'l': // left
|
case 'l': // left
|
||||||
|
@ -671,15 +647,15 @@ font_getsize(FontObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
switch (anchor[1]) {
|
switch (anchor[1]) {
|
||||||
case 'a': // ascender
|
case 'a': // ascender
|
||||||
y_anchor = PIXEL(self->face->size->metrics.ascender);
|
y_anchor = PIXEL(face->size->metrics.ascender);
|
||||||
break;
|
break;
|
||||||
case 't': // top
|
case 't': // top
|
||||||
y_anchor = y_max;
|
y_anchor = y_max;
|
||||||
break;
|
break;
|
||||||
case 'm': // middle (ascender + descender) / 2
|
case 'm': // middle (ascender + descender) / 2
|
||||||
y_anchor = PIXEL(
|
y_anchor = PIXEL(
|
||||||
(self->face->size->metrics.ascender +
|
(face->size->metrics.ascender +
|
||||||
self->face->size->metrics.descender) /
|
face->size->metrics.descender) /
|
||||||
2);
|
2);
|
||||||
break;
|
break;
|
||||||
case 's': // horizontal baseline
|
case 's': // horizontal baseline
|
||||||
|
@ -689,7 +665,7 @@ font_getsize(FontObject *self, PyObject *args) {
|
||||||
y_anchor = y_min;
|
y_anchor = y_min;
|
||||||
break;
|
break;
|
||||||
case 'd': // descender
|
case 'd': // descender
|
||||||
y_anchor = PIXEL(self->face->size->metrics.descender);
|
y_anchor = PIXEL(face->size->metrics.descender);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
goto bad_anchor;
|
goto bad_anchor;
|
||||||
|
@ -729,17 +705,74 @@ font_getsize(FontObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*width = x_max - x_min;
|
||||||
return Py_BuildValue(
|
*height = y_max - y_min;
|
||||||
"(ii)(ii)",
|
*x_offset = -x_anchor + x_min;
|
||||||
(x_max - x_min),
|
*y_offset = -(-y_anchor + y_max);
|
||||||
(y_max - y_min),
|
return 0;
|
||||||
(-x_anchor + x_min),
|
|
||||||
-(-y_anchor + y_max));
|
|
||||||
|
|
||||||
bad_anchor:
|
bad_anchor:
|
||||||
PyErr_Format(PyExc_ValueError, "bad anchor specified: %s", anchor);
|
PyErr_Format(PyExc_ValueError, "bad anchor specified: %s", anchor);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
font_getsize(FontObject *self, PyObject *args) {
|
||||||
|
int width, height, x_offset, y_offset;
|
||||||
|
int load_flags; /* FreeType load_flags parameter */
|
||||||
|
int error;
|
||||||
|
GlyphInfo *glyph_info = NULL; /* computed text layout */
|
||||||
|
size_t count; /* glyph_info length */
|
||||||
|
int horizontal_dir; /* is primary axis horizontal? */
|
||||||
|
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
|
||||||
|
int color = 0; /* is FT_LOAD_COLOR enabled? */
|
||||||
|
const char *mode = NULL;
|
||||||
|
const char *dir = NULL;
|
||||||
|
const char *lang = NULL;
|
||||||
|
const char *anchor = NULL;
|
||||||
|
PyObject *features = Py_None;
|
||||||
|
PyObject *string;
|
||||||
|
|
||||||
|
/* calculate size and bearing for a given string */
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(
|
||||||
|
args, "O|zzOzz:getsize", &string, &mode, &dir, &features, &lang, &anchor)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
||||||
|
|
||||||
|
mask = mode && strcmp(mode, "1") == 0;
|
||||||
|
color = mode && strcmp(mode, "RGBA") == 0;
|
||||||
|
|
||||||
|
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
|
||||||
|
if (PyErr_Occurred()) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
load_flags = FT_LOAD_DEFAULT;
|
||||||
|
if (mask) {
|
||||||
|
load_flags |= FT_LOAD_TARGET_MONO;
|
||||||
|
}
|
||||||
|
if (color) {
|
||||||
|
load_flags |= FT_LOAD_COLOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = bounding_box_and_anchors(self->face, anchor, horizontal_dir, glyph_info, count, load_flags, &width, &height, &x_offset, &y_offset);
|
||||||
|
if (glyph_info) {
|
||||||
|
PyMem_Free(glyph_info);
|
||||||
|
glyph_info = NULL;
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Py_BuildValue(
|
||||||
|
"(ii)(ii)",
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
x_offset,
|
||||||
|
y_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -763,6 +796,7 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
unsigned int bitmap_y; /* glyph bitmap y index */
|
unsigned int bitmap_y; /* glyph bitmap y index */
|
||||||
unsigned char *source; /* glyph bitmap source buffer */
|
unsigned char *source; /* glyph bitmap source buffer */
|
||||||
unsigned char convert_scale; /* scale factor for non-8bpp bitmaps */
|
unsigned char convert_scale; /* scale factor for non-8bpp bitmaps */
|
||||||
|
PyObject *image;
|
||||||
Imaging im;
|
Imaging im;
|
||||||
Py_ssize_t id;
|
Py_ssize_t id;
|
||||||
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
|
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
|
||||||
|
@ -773,27 +807,34 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
const char *mode = NULL;
|
const char *mode = NULL;
|
||||||
const char *dir = NULL;
|
const char *dir = NULL;
|
||||||
const char *lang = NULL;
|
const char *lang = NULL;
|
||||||
|
const char *anchor = NULL;
|
||||||
PyObject *features = Py_None;
|
PyObject *features = Py_None;
|
||||||
PyObject *string;
|
PyObject *string;
|
||||||
|
PyObject *fill;
|
||||||
float x_start = 0;
|
float x_start = 0;
|
||||||
float y_start = 0;
|
float y_start = 0;
|
||||||
|
int width, height, x_offset, y_offset;
|
||||||
|
int horizontal_dir; /* is primary axis horizontal? */
|
||||||
|
PyObject *max_image_pixels = Py_None;
|
||||||
|
|
||||||
/* render string into given buffer (the buffer *must* have
|
/* render string into given buffer (the buffer *must* have
|
||||||
the right size, or this will crash) */
|
the right size, or this will crash) */
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args,
|
args,
|
||||||
"On|zzOziLff:render",
|
"OO|zzOzizLffO:render",
|
||||||
&string,
|
&string,
|
||||||
&id,
|
&fill,
|
||||||
&mode,
|
&mode,
|
||||||
&dir,
|
&dir,
|
||||||
&features,
|
&features,
|
||||||
&lang,
|
&lang,
|
||||||
&stroke_width,
|
&stroke_width,
|
||||||
|
&anchor,
|
||||||
&foreground_ink_long,
|
&foreground_ink_long,
|
||||||
&x_start,
|
&x_start,
|
||||||
&y_start)) {
|
&y_start,
|
||||||
|
&max_image_pixels)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -819,8 +860,41 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
if (PyErr_Occurred()) {
|
if (PyErr_Occurred()) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
if (count == 0) {
|
|
||||||
Py_RETURN_NONE;
|
load_flags = stroke_width ? FT_LOAD_NO_BITMAP : FT_LOAD_DEFAULT;
|
||||||
|
if (mask) {
|
||||||
|
load_flags |= FT_LOAD_TARGET_MONO;
|
||||||
|
}
|
||||||
|
if (color) {
|
||||||
|
load_flags |= FT_LOAD_COLOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
||||||
|
|
||||||
|
error = bounding_box_and_anchors(self->face, anchor, horizontal_dir, glyph_info, count, load_flags, &width, &height, &x_offset, &y_offset);
|
||||||
|
if (error) {
|
||||||
|
PyMem_Del(glyph_info);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
width += stroke_width * 2 + ceil(x_start);
|
||||||
|
height += stroke_width * 2 + ceil(y_start);
|
||||||
|
if (max_image_pixels != Py_None) {
|
||||||
|
if (width * height > PyLong_AsLong(max_image_pixels) * 2) {
|
||||||
|
PyMem_Del(glyph_info);
|
||||||
|
return Py_BuildValue("O(ii)(ii)", Py_None, width, height, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
image = PyObject_CallFunction(fill, "s(ii)", strcmp(mode, "RGBA") == 0 ? "RGBA" : "L", width, height);
|
||||||
|
id = PyLong_AsSsize_t(PyObject_GetAttrString(image, "id"));
|
||||||
|
im = (Imaging)id;
|
||||||
|
|
||||||
|
x_offset -= stroke_width;
|
||||||
|
y_offset -= stroke_width;
|
||||||
|
if (count == 0 || width == 0 || height == 0) {
|
||||||
|
PyMem_Del(glyph_info);
|
||||||
|
return Py_BuildValue("O(ii)(ii)", image, width, height, x_offset, y_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stroke_width) {
|
if (stroke_width) {
|
||||||
|
@ -837,15 +911,6 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
0);
|
0);
|
||||||
}
|
}
|
||||||
|
|
||||||
im = (Imaging)id;
|
|
||||||
load_flags = stroke_width ? FT_LOAD_NO_BITMAP : FT_LOAD_DEFAULT;
|
|
||||||
if (mask) {
|
|
||||||
load_flags |= FT_LOAD_TARGET_MONO;
|
|
||||||
}
|
|
||||||
if (color) {
|
|
||||||
load_flags |= FT_LOAD_COLOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* calculate x_min and y_max
|
* calculate x_min and y_max
|
||||||
* must match font_getsize or there may be clipping!
|
* must match font_getsize or there may be clipping!
|
||||||
|
@ -1042,7 +1107,7 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
FT_Stroker_Done(stroker);
|
FT_Stroker_Done(stroker);
|
||||||
PyMem_Del(glyph_info);
|
PyMem_Del(glyph_info);
|
||||||
Py_RETURN_NONE;
|
return Py_BuildValue("O(ii)(ii)", image, width, height, x_offset, y_offset);
|
||||||
|
|
||||||
glyph_error:
|
glyph_error:
|
||||||
if (stroker != NULL) {
|
if (stroker != NULL) {
|
||||||
|
|
|
@ -436,10 +436,16 @@ PyImaging_GrabClipboardWin32(PyObject *self, PyObject *args) {
|
||||||
UINT formats[] = {CF_DIB, CF_DIBV5, CF_HDROP, RegisterClipboardFormatA("PNG"), 0};
|
UINT formats[] = {CF_DIB, CF_DIBV5, CF_HDROP, RegisterClipboardFormatA("PNG"), 0};
|
||||||
LPCSTR format_names[] = {"DIB", "DIB", "file", "png", NULL};
|
LPCSTR format_names[] = {"DIB", "DIB", "file", "png", NULL};
|
||||||
|
|
||||||
|
if (!OpenClipboard(NULL)) {
|
||||||
|
// Maybe the clipboard is temporarily in use by another process.
|
||||||
|
// Wait and try again
|
||||||
|
Sleep(500);
|
||||||
|
|
||||||
if (!OpenClipboard(NULL)) {
|
if (!OpenClipboard(NULL)) {
|
||||||
PyErr_SetString(PyExc_OSError, "failed to open clipboard");
|
PyErr_SetString(PyExc_OSError, "failed to open clipboard");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// find best format as set by clipboard owner
|
// find best format as set by clipboard owner
|
||||||
format = 0;
|
format = 0;
|
||||||
|
|
|
@ -49,7 +49,7 @@ clip32(float in) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Imaging
|
Imaging
|
||||||
ImagingExpand(Imaging imIn, int xmargin, int ymargin, int mode) {
|
ImagingExpand(Imaging imIn, int xmargin, int ymargin) {
|
||||||
Imaging imOut;
|
Imaging imOut;
|
||||||
int x, y;
|
int x, y;
|
||||||
ImagingSectionCookie cookie;
|
ImagingSectionCookie cookie;
|
||||||
|
|
|
@ -53,10 +53,6 @@
|
||||||
#define UINT16 uint16_t
|
#define UINT16 uint16_t
|
||||||
#define INT32 int32_t
|
#define INT32 int32_t
|
||||||
#define UINT32 uint32_t
|
#define UINT32 uint32_t
|
||||||
#ifdef INT64_MAX
|
|
||||||
#define INT64 int64_t
|
|
||||||
#define UINT64 uint64_t
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#else /* < C99 */
|
#else /* < C99 */
|
||||||
|
|
||||||
|
@ -80,20 +76,9 @@
|
||||||
#error Cannot find required 32-bit integer type
|
#error Cannot find required 32-bit integer type
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if SIZEOF_LONG == 8
|
|
||||||
#define INT64 long
|
|
||||||
#elif SIZEOF_LONG_LONG == 8
|
|
||||||
#define INT64 long long
|
|
||||||
#else
|
|
||||||
#warning Cannot find required 64-bit integer type
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define UINT8 unsigned char
|
#define UINT8 unsigned char
|
||||||
#define UINT16 unsigned INT16
|
#define UINT16 unsigned INT16
|
||||||
#define UINT32 unsigned INT32
|
#define UINT32 unsigned INT32
|
||||||
#ifdef INT64
|
|
||||||
#define UINT64 unsigned INT64
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* < C99 */
|
#endif /* < C99 */
|
||||||
|
|
||||||
|
|
|
@ -290,7 +290,7 @@ ImagingConvertTransparent(Imaging im, const char *mode, int r, int g, int b);
|
||||||
extern Imaging
|
extern Imaging
|
||||||
ImagingCrop(Imaging im, int x0, int y0, int x1, int y1);
|
ImagingCrop(Imaging im, int x0, int y0, int x1, int y1);
|
||||||
extern Imaging
|
extern Imaging
|
||||||
ImagingExpand(Imaging im, int x, int y, int mode);
|
ImagingExpand(Imaging im, int x, int y);
|
||||||
extern Imaging
|
extern Imaging
|
||||||
ImagingFill(Imaging im, const void *ink);
|
ImagingFill(Imaging im, const void *ink);
|
||||||
extern int
|
extern int
|
||||||
|
|
|
@ -464,7 +464,7 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!context->num_resolutions) {
|
if (!context->num_resolutions) {
|
||||||
while (tile_width < (1 << (params.numresolution - 1U)) || tile_height < (1 << (params.numresolution - 1U))) {
|
while (tile_width < (1U << (params.numresolution - 1U)) || tile_height < (1U << (params.numresolution - 1U))) {
|
||||||
params.numresolution -= 1;
|
params.numresolution -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,8 +37,6 @@
|
||||||
#include "Imaging.h"
|
#include "Imaging.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
int ImagingNewCount = 0;
|
|
||||||
|
|
||||||
/* --------------------------------------------------------------------
|
/* --------------------------------------------------------------------
|
||||||
* Standard image object.
|
* Standard image object.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1552,10 +1552,12 @@ static struct {
|
||||||
{"P", "P;4L", 4, unpackP4L},
|
{"P", "P;4L", 4, unpackP4L},
|
||||||
{"P", "P", 8, copy1},
|
{"P", "P", 8, copy1},
|
||||||
{"P", "P;R", 8, unpackLR},
|
{"P", "P;R", 8, unpackLR},
|
||||||
|
{"P", "L", 8, copy1},
|
||||||
|
|
||||||
/* palette w. alpha */
|
/* palette w. alpha */
|
||||||
{"PA", "PA", 16, unpackLA},
|
{"PA", "PA", 16, unpackLA},
|
||||||
{"PA", "PA;L", 16, unpackLAL},
|
{"PA", "PA;L", 16, unpackLAL},
|
||||||
|
{"PA", "LA", 16, unpackLA},
|
||||||
|
|
||||||
/* true colour */
|
/* true colour */
|
||||||
{"RGB", "RGB", 24, ImagingUnpackRGB},
|
{"RGB", "RGB", 24, ImagingUnpackRGB},
|
||||||
|
|
|
@ -152,9 +152,9 @@ deps = {
|
||||||
"libs": [r"*.lib"],
|
"libs": [r"*.lib"],
|
||||||
},
|
},
|
||||||
"xz": {
|
"xz": {
|
||||||
"url": SF_PROJECTS + "/lzmautils/files/xz-5.4.2.tar.gz/download",
|
"url": SF_PROJECTS + "/lzmautils/files/xz-5.4.3.tar.gz/download",
|
||||||
"filename": "xz-5.4.2.tar.gz",
|
"filename": "xz-5.4.3.tar.gz",
|
||||||
"dir": "xz-5.4.2",
|
"dir": "xz-5.4.3",
|
||||||
"license": "COPYING",
|
"license": "COPYING",
|
||||||
"build": [
|
"build": [
|
||||||
*cmds_cmake("liblzma", "-DBUILD_SHARED_LIBS:BOOL=OFF"),
|
*cmds_cmake("liblzma", "-DBUILD_SHARED_LIBS:BOOL=OFF"),
|
||||||
|
|
Loading…
Reference in New Issue
Block a user