Merge branch 'main' into int_def

This commit is contained in:
Andrew Murray 2023-06-14 09:11:41 +10:00
commit 68edbbca94
40 changed files with 341 additions and 187 deletions

View File

@ -22,7 +22,8 @@ set -e
if [[ $(uname) != CYGWIN* ]]; then
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
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
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
# 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
python3 -m pip install pyqt6
fi

View File

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

View File

@ -39,6 +39,7 @@ jobs:
centos-stream-8-amd64,
centos-stream-9-amd64,
debian-11-bullseye-x86,
debian-12-bookworm-x86,
fedora-37-amd64,
fedora-38-amd64,
gentoo,

View File

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

View File

@ -84,7 +84,9 @@ jobs:
python3 -m pip install pytest-reverse
fi
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
.ci/test.sh
fi

View File

@ -4,9 +4,6 @@ repos:
hooks:
- id: black
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
rev: 5.12.0

View File

@ -5,6 +5,30 @@ Changelog (Pillow)
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
[n3011, radarhere, nulano]

View File

@ -447,6 +447,17 @@ def test_apng_save_duration_loop(tmp_path):
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):
test_file = str(tmp_path / "temp.png")
size = (128, 64)

View File

@ -252,6 +252,19 @@ def test_roundtrip_save_all(tmp_path):
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(
"path, mode",
(

View File

@ -96,10 +96,17 @@ class TestFileTiff:
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:
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):
ifd = TiffImagePlugin.ImageFileDirectory_v2()
with pytest.raises(Exception) as e:

View File

@ -32,6 +32,14 @@ def test_putpalette():
with pytest.raises(ValueError):
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():
im = hopper("P")

View File

@ -463,6 +463,11 @@ def test_default_font():
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):
# issue #2614, should not crash.
assert (0, 0, 0, 0) == font.getbbox("")

View File

@ -98,3 +98,18 @@ $ms = new-object System.IO.MemoryStream(, $bytes)
im = ImageGrab.grabclipboard()
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:
# Avoid "Missing base report" due to committing CHANGES.rst with "[CI skip]"
# 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
comment: false

View File

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

View File

@ -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).
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
example, a rectangle covering all of an 800x600 pixel image is written as (0,
0, 800, 600).
are represented as 4-tuples, (x1, y1, x2, y2), with the upper left corner given
first.
Palette
-------

View File

@ -448,6 +448,8 @@ These platforms are built and tested for every change.
+----------------------------------+----------------------------+---------------------+
| Debian 11 Bullseye | 3.9 | x86 |
+----------------------------------+----------------------------+---------------------+
| Debian 12 Bookworm | 3.11 | x86 |
+----------------------------------+----------------------------+---------------------+
| Fedora 37 | 3.11 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Fedora 38 | 3.11 | x86-64 |

View File

@ -134,6 +134,13 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False):
if gs_windows_binary is not None:
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"
raise OSError(msg)
command[0] = gs_windows_binary
@ -354,7 +361,6 @@ class EpsImageFile(ImageFile.ImageFile):
check_required_header_comments()
if not self._size:
self._size = 1, 1 # errors if this isn't set. why (1,1)?
msg = "cannot determine EPS bounding box"
raise OSError(msg)

View File

@ -879,7 +879,7 @@ def _get_palette_bytes(im):
:param im: Image object
: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):

View File

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

View File

@ -1254,7 +1254,7 @@ class Image:
if ymargin is None:
ymargin = xmargin
self.load()
return self._new(self.im.expand(xmargin, ymargin, 0))
return self._new(self.im.expand(xmargin, ymargin))
def filter(self, filter):
"""

View File

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

View File

@ -43,7 +43,8 @@ class Kernel(BuiltinFilter):
:param size: Kernel size, given as (width, height). In the current
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
divided by this value. The default is the sum of the
kernel weights.

View File

@ -26,7 +26,6 @@
#
import base64
import math
import os
import sys
import warnings
@ -226,10 +225,6 @@ class FreeTypeFont:
path, size, index, encoding, layout_engine = state
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):
"""
: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
gap between the starting coordinate and the first marking
"""
size, offset = self.font.getsize(
text, mode, direction, features, language, anchor
)
if start is None:
start = (0, 0)
size = tuple(math.ceil(size[i] + stroke_width * 2 + start[i]) for i in range(2))
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(
im, size, offset = self.font.render(
text,
im.id,
Image.core.fill,
mode,
direction,
features,
language,
stroke_width,
anchor,
ink,
start[0],
start[1],
Image.MAX_IMAGE_PIXELS,
)
Image._decompression_bomb_check(size)
return im, offset
def font_variant(

View File

@ -15,6 +15,7 @@
# See the README file for information on usage and redistribution.
#
import io
import os
import shutil
import subprocess
@ -128,8 +129,6 @@ def grabclipboard():
files = data[o:].decode("mbcs").split("\0")
return files[: files.index("")]
if isinstance(data, bytes):
import io
data = io.BytesIO(data)
if fmt == "png":
from . import PngImagePlugin
@ -142,19 +141,29 @@ def grabclipboard():
return None
else:
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"]
if mimetype:
args.extend(["-t", mimetype])
elif shutil.which("xclip"):
args = ["xclip", "-selection", "clipboard", "-t", "image/png", "-o"]
else:
msg = "wl-paste or xclip is required for ImageGrab.grabclipboard() on Linux"
raise NotImplementedError(msg)
fh, filepath = tempfile.mkstemp()
err = subprocess.run(args, stdout=fh, stderr=subprocess.PIPE).stderr
os.close(fh)
p = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
err = p.stderr
if err:
msg = f"{args[0]} error: {err.strip().decode()}"
raise ChildProcessError(msg)
im = Image.open(filepath)
data = io.BytesIO(p.stdout)
im = Image.open(data)
im.load()
os.unlink(filepath)
return im

View File

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

View File

@ -457,6 +457,11 @@ class JpegImageFile(ImageFile.ImageFile):
if os.path.exists(self.filename):
subprocess.check_call(["djpeg", "-outfile", path, self.filename])
else:
try:
os.unlink(path)
except OSError:
pass
msg = "Invalid Filename"
raise ValueError(msg)

View File

@ -1146,11 +1146,14 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
and prev_disposal == encoderinfo.get("disposal")
and prev_blend == encoderinfo.get("blend")
):
if isinstance(duration, (list, tuple)):
previous["encoderinfo"]["duration"] += encoderinfo["duration"]
previous["encoderinfo"]["duration"] += encoderinfo.get(
"duration", duration
)
continue
else:
bbox = None
if "duration" not in encoderinfo:
encoderinfo["duration"] = duration
im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo})
# animation control
@ -1175,7 +1178,7 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
im_frame = im_frame.crop(bbox)
size = im_frame.size
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_blend = encoderinfo.get("blend", blend)
# frame control

View File

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

View File

@ -1894,6 +1894,10 @@ class AppendingTiffWriter:
8, # srational
4, # float
8, # double
4, # ifd
2, # unicode
4, # complex
8, # long8
]
# StripOffsets = 273

View File

@ -1027,12 +1027,11 @@ _crop(ImagingObject *self, PyObject *args) {
static PyObject *
_expand_image(ImagingObject *self, PyObject *args) {
int x, y;
int mode = 0;
if (!PyArg_ParseTuple(args, "ii|i", &x, &y, &mode)) {
if (!PyArg_ParseTuple(args, "ii", &x, &y)) {
return NULL;
}
return PyImagingNew(ImagingExpand(self->image, x, y, mode));
return PyImagingNew(ImagingExpand(self->image, x, y));
}
static PyObject *

View File

@ -132,6 +132,27 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
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(
args,
kw,
@ -147,6 +168,7 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
&layout_engine)) {
return NULL;
}
#endif
self = PyObject_New(FontObject, &Font_Type);
if (!self) {
@ -232,9 +254,7 @@ text_layout_raqm(
const char *dir,
PyObject *features,
const char *lang,
GlyphInfo **glyph_info,
int mask,
int color) {
GlyphInfo **glyph_info) {
size_t i = 0, count = 0, start = 0;
raqm_t *rq;
raqm_glyph_t *glyphs = NULL;
@ -471,7 +491,7 @@ text_layout(
#ifdef HAVE_RAQM
if (have_raqm && self->layout_engine == LAYOUT_RAQM) {
count = text_layout_raqm(
string, self, dir, features, lang, glyph_info, mask, color);
string, self, dir, features, lang, glyph_info);
} else
#endif
{
@ -529,73 +549,25 @@ font_getlength(FontObject *self, PyObject *args) {
return PyLong_FromLong(length);
}
static PyObject *
font_getsize(FontObject *self, PyObject *args) {
static int
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 advanced; /* pen position along primary axis, 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_anchor, y_anchor; /* offset of point drawn at (0, 0), in pixels */
int load_flags; /* FreeType load_flags parameter */
int error;
FT_Face face;
FT_Glyph glyph;
FT_BBox bbox; /* glyph bounding box */
GlyphInfo *glyph_info = NULL; /* computed text layout */
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;
}
size_t i; /* glyph_info index */
/*
* text bounds are given by:
* - bounding boxes of individual glyphs
* - pen line, i.e. 0 to `advanced` along primary axis
* this means point (0, 0) is part of the text bounding box
*/
face = NULL;
position = x_min = x_max = y_min = y_max = 0;
for (i = 0; i < count; i++) {
face = self->face;
if (horizontal_dir) {
px = PIXEL(position + glyph_info[i].x_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);
if (error) {
return geterror(error);
geterror(error);
return 1;
}
error = FT_Get_Glyph(face->glyph, &glyph);
if (error) {
return geterror(error);
geterror(error);
return 1;
}
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_PIXELS, &bbox);
@ -647,13 +621,15 @@ font_getsize(FontObject *self, PyObject *args) {
FT_Done_Glyph(glyph);
}
if (glyph_info) {
PyMem_Free(glyph_info);
glyph_info = NULL;
if (anchor == NULL) {
anchor = horizontal_dir ? "la" : "lt";
}
if (strlen(anchor) != 2) {
goto bad_anchor;
}
x_anchor = y_anchor = 0;
if (face) {
if (count) {
if (horizontal_dir) {
switch (anchor[0]) {
case 'l': // left
@ -671,15 +647,15 @@ font_getsize(FontObject *self, PyObject *args) {
}
switch (anchor[1]) {
case 'a': // ascender
y_anchor = PIXEL(self->face->size->metrics.ascender);
y_anchor = PIXEL(face->size->metrics.ascender);
break;
case 't': // top
y_anchor = y_max;
break;
case 'm': // middle (ascender + descender) / 2
y_anchor = PIXEL(
(self->face->size->metrics.ascender +
self->face->size->metrics.descender) /
(face->size->metrics.ascender +
face->size->metrics.descender) /
2);
break;
case 's': // horizontal baseline
@ -689,7 +665,7 @@ font_getsize(FontObject *self, PyObject *args) {
y_anchor = y_min;
break;
case 'd': // descender
y_anchor = PIXEL(self->face->size->metrics.descender);
y_anchor = PIXEL(face->size->metrics.descender);
break;
default:
goto bad_anchor;
@ -729,17 +705,74 @@ font_getsize(FontObject *self, PyObject *args) {
}
}
}
return Py_BuildValue(
"(ii)(ii)",
(x_max - x_min),
(y_max - y_min),
(-x_anchor + x_min),
-(-y_anchor + y_max));
*width = x_max - x_min;
*height = y_max - y_min;
*x_offset = -x_anchor + x_min;
*y_offset = -(-y_anchor + y_max);
return 0;
bad_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;
}
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 *
@ -763,6 +796,7 @@ font_render(FontObject *self, PyObject *args) {
unsigned int bitmap_y; /* glyph bitmap y index */
unsigned char *source; /* glyph bitmap source buffer */
unsigned char convert_scale; /* scale factor for non-8bpp bitmaps */
PyObject *image;
Imaging im;
Py_ssize_t id;
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 *dir = NULL;
const char *lang = NULL;
const char *anchor = NULL;
PyObject *features = Py_None;
PyObject *string;
PyObject *fill;
float x_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
the right size, or this will crash) */
if (!PyArg_ParseTuple(
args,
"On|zzOziLff:render",
"OO|zzOzizLffO:render",
&string,
&id,
&fill,
&mode,
&dir,
&features,
&lang,
&stroke_width,
&anchor,
&foreground_ink_long,
&x_start,
&y_start)) {
&y_start,
&max_image_pixels)) {
return NULL;
}
@ -819,8 +860,41 @@ font_render(FontObject *self, PyObject *args) {
if (PyErr_Occurred()) {
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) {
@ -837,15 +911,6 @@ font_render(FontObject *self, PyObject *args) {
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
* must match font_getsize or there may be clipping!
@ -1042,7 +1107,7 @@ font_render(FontObject *self, PyObject *args) {
}
FT_Stroker_Done(stroker);
PyMem_Del(glyph_info);
Py_RETURN_NONE;
return Py_BuildValue("O(ii)(ii)", image, width, height, x_offset, y_offset);
glyph_error:
if (stroker != NULL) {

View File

@ -436,10 +436,16 @@ PyImaging_GrabClipboardWin32(PyObject *self, PyObject *args) {
UINT formats[] = {CF_DIB, CF_DIBV5, CF_HDROP, RegisterClipboardFormatA("PNG"), 0};
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)) {
PyErr_SetString(PyExc_OSError, "failed to open clipboard");
return NULL;
}
}
// find best format as set by clipboard owner
format = 0;

View File

@ -49,7 +49,7 @@ clip32(float in) {
}
Imaging
ImagingExpand(Imaging imIn, int xmargin, int ymargin, int mode) {
ImagingExpand(Imaging imIn, int xmargin, int ymargin) {
Imaging imOut;
int x, y;
ImagingSectionCookie cookie;

View File

@ -53,10 +53,6 @@
#define UINT16 uint16_t
#define INT32 int32_t
#define UINT32 uint32_t
#ifdef INT64_MAX
#define INT64 int64_t
#define UINT64 uint64_t
#endif
#else /* < C99 */
@ -80,20 +76,9 @@
#error Cannot find required 32-bit integer type
#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 UINT16 unsigned INT16
#define UINT32 unsigned INT32
#ifdef INT64
#define UINT64 unsigned INT64
#endif
#endif /* < C99 */

View File

@ -290,7 +290,7 @@ ImagingConvertTransparent(Imaging im, const char *mode, int r, int g, int b);
extern Imaging
ImagingCrop(Imaging im, int x0, int y0, int x1, int y1);
extern Imaging
ImagingExpand(Imaging im, int x, int y, int mode);
ImagingExpand(Imaging im, int x, int y);
extern Imaging
ImagingFill(Imaging im, const void *ink);
extern int

View File

@ -464,7 +464,7 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) {
}
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;
}
}

View File

@ -37,8 +37,6 @@
#include "Imaging.h"
#include <string.h>
int ImagingNewCount = 0;
/* --------------------------------------------------------------------
* Standard image object.
*/

View File

@ -1552,10 +1552,12 @@ static struct {
{"P", "P;4L", 4, unpackP4L},
{"P", "P", 8, copy1},
{"P", "P;R", 8, unpackLR},
{"P", "L", 8, copy1},
/* palette w. alpha */
{"PA", "PA", 16, unpackLA},
{"PA", "PA;L", 16, unpackLAL},
{"PA", "LA", 16, unpackLA},
/* true colour */
{"RGB", "RGB", 24, ImagingUnpackRGB},

View File

@ -152,9 +152,9 @@ deps = {
"libs": [r"*.lib"],
},
"xz": {
"url": SF_PROJECTS + "/lzmautils/files/xz-5.4.2.tar.gz/download",
"filename": "xz-5.4.2.tar.gz",
"dir": "xz-5.4.2",
"url": SF_PROJECTS + "/lzmautils/files/xz-5.4.3.tar.gz/download",
"filename": "xz-5.4.3.tar.gz",
"dir": "xz-5.4.3",
"license": "COPYING",
"build": [
*cmds_cmake("liblzma", "-DBUILD_SHARED_LIBS:BOOL=OFF"),