mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-24 16:24:11 +03:00
Merge branch 'main' into update-release-notes
This commit is contained in:
commit
ca94dae26b
|
@ -2,15 +2,14 @@
|
|||
|
||||
[report]
|
||||
# Regexes for lines to exclude from consideration
|
||||
exclude_lines =
|
||||
# Have to re-enable the standard pragma:
|
||||
pragma: no cover
|
||||
|
||||
# Don't complain if non-runnable code isn't run:
|
||||
exclude_also =
|
||||
# Don't complain if non-runnable code isn't run
|
||||
if 0:
|
||||
if __name__ == .__main__.:
|
||||
# Don't complain about debug code
|
||||
if DEBUG:
|
||||
# Don't complain about compatibility code for missing optional dependencies
|
||||
except ImportError
|
||||
|
||||
[run]
|
||||
omit =
|
||||
|
|
18
.github/problem-matchers/gcc.json
vendored
Normal file
18
.github/problem-matchers/gcc.json
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"__comment": "Based on vscode-cpptools' Extension/package.json gcc rule",
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "gcc-problem-matcher",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^\\s*(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
"severity": 4,
|
||||
"message": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
|
@ -86,6 +86,10 @@ jobs:
|
|||
env:
|
||||
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
|
||||
|
||||
- name: Register gcc problem matcher
|
||||
if: "matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'"
|
||||
run: echo "::add-matcher::.github/problem-matchers/gcc.json"
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
.ci/build.sh
|
||||
|
|
24
CHANGES.rst
24
CHANGES.rst
|
@ -5,6 +5,30 @@ Changelog (Pillow)
|
|||
10.2.0 (unreleased)
|
||||
-------------------
|
||||
|
||||
- Trim glyph size in ImageFont.getmask() #7669
|
||||
[radarhere]
|
||||
|
||||
- Deprecate IptcImagePlugin helpers #7664
|
||||
[nulano, hugovk, radarhere]
|
||||
|
||||
- Allow uncompressed TIFF images to be saved in chunks #7650
|
||||
[radarhere]
|
||||
|
||||
- Concatenate multiple JPEG EXIF markers #7496
|
||||
[radarhere]
|
||||
|
||||
- Changed IPTC tile tuple to match other plugins #7661
|
||||
[radarhere]
|
||||
|
||||
- Do not assign new fp attribute when exiting context manager #7566
|
||||
[radarhere]
|
||||
|
||||
- Support arbitrary masks for uncompressed RGB DDS images #7589
|
||||
[radarhere, akx]
|
||||
|
||||
- Support setting ROWSPERSTRIP tag #7654
|
||||
[radarhere]
|
||||
|
||||
- Apply ImageFont.MAX_STRING_LENGTH to ImageFont.getmask() #7662
|
||||
[radarhere]
|
||||
|
||||
|
|
|
@ -6,18 +6,21 @@ import pytest
|
|||
|
||||
from PIL import Image, IptcImagePlugin
|
||||
|
||||
from .helper import hopper
|
||||
from .helper import assert_image_equal, hopper
|
||||
|
||||
TEST_FILE = "Tests/images/iptc.jpg"
|
||||
|
||||
|
||||
def test_open():
|
||||
expected = Image.new("L", (1, 1))
|
||||
|
||||
f = BytesIO(
|
||||
b"\x1c\x03<\x00\x02\x01\x00\x1c\x03x\x00\x01\x01\x1c"
|
||||
b"\x03\x14\x00\x01\x01\x1c\x03\x1e\x00\x01\x01\x1c\x08\n\x00\x00"
|
||||
b"\x1c\x03<\x00\x02\x01\x00\x1c\x03x\x00\x01\x01\x1c\x03\x14\x00\x01\x01"
|
||||
b"\x1c\x03\x1e\x00\x01\x01\x1c\x08\n\x00\x01\x00"
|
||||
)
|
||||
with Image.open(f) as im:
|
||||
assert im.tile == [("iptc", (0, 0, 1, 1), 25, "raw")]
|
||||
assert_image_equal(im, expected)
|
||||
|
||||
|
||||
def test_getiptcinfo_jpg_none():
|
||||
|
@ -87,24 +90,28 @@ def test_i():
|
|||
c = b"a"
|
||||
|
||||
# Act
|
||||
ret = IptcImagePlugin.i(c)
|
||||
with pytest.warns(DeprecationWarning):
|
||||
ret = IptcImagePlugin.i(c)
|
||||
|
||||
# Assert
|
||||
assert ret == 97
|
||||
|
||||
|
||||
def test_dump():
|
||||
def test_dump(monkeypatch):
|
||||
# Arrange
|
||||
c = b"abc"
|
||||
# Temporarily redirect stdout
|
||||
old_stdout = sys.stdout
|
||||
sys.stdout = mystdout = StringIO()
|
||||
mystdout = StringIO()
|
||||
monkeypatch.setattr(sys, "stdout", mystdout)
|
||||
|
||||
# Act
|
||||
IptcImagePlugin.dump(c)
|
||||
|
||||
# Reset stdout
|
||||
sys.stdout = old_stdout
|
||||
with pytest.warns(DeprecationWarning):
|
||||
IptcImagePlugin.dump(c)
|
||||
|
||||
# Assert
|
||||
assert mystdout.getvalue() == "61 62 63 \n"
|
||||
|
||||
|
||||
def test_pad_deprecation():
|
||||
with pytest.warns(DeprecationWarning):
|
||||
assert IptcImagePlugin.PAD == b"\0\0\0\0"
|
||||
|
|
|
@ -484,13 +484,13 @@ class TestFileTiff:
|
|||
outfile = str(tmp_path / "temp.tif")
|
||||
with Image.open("Tests/images/ifd_tag_type.tiff") as im:
|
||||
exif = im.getexif()
|
||||
exif[256] = 100
|
||||
exif[264] = 100
|
||||
|
||||
im.save(outfile, exif=exif)
|
||||
|
||||
with Image.open(outfile) as im:
|
||||
exif = im.getexif()
|
||||
assert exif[256] == 100
|
||||
assert exif[264] == 100
|
||||
|
||||
def test_reload_exif_after_seek(self):
|
||||
with Image.open("Tests/images/multipage.tiff") as im:
|
||||
|
@ -781,6 +781,27 @@ class TestFileTiff:
|
|||
4001,
|
||||
]
|
||||
|
||||
def test_tiff_chunks(self, tmp_path):
|
||||
tmpfile = str(tmp_path / "temp.tif")
|
||||
|
||||
im = hopper()
|
||||
with open(tmpfile, "wb") as fp:
|
||||
for y in range(0, 128, 32):
|
||||
chunk = im.crop((0, y, 128, y + 32))
|
||||
if y == 0:
|
||||
chunk.save(
|
||||
fp,
|
||||
"TIFF",
|
||||
tiffinfo={
|
||||
TiffImagePlugin.IMAGEWIDTH: 128,
|
||||
TiffImagePlugin.IMAGELENGTH: 128,
|
||||
},
|
||||
)
|
||||
else:
|
||||
fp.write(chunk.tobytes())
|
||||
|
||||
assert_image_equal_tofile(im, tmpfile)
|
||||
|
||||
def test_close_on_load_exclusive(self, tmp_path):
|
||||
# similar to test_fd_leak, but runs on unixlike os
|
||||
tmpfile = str(tmp_path / "temp.tif")
|
||||
|
|
|
@ -164,6 +164,7 @@ def test_change_stripbytecounts_tag_type(tmp_path):
|
|||
|
||||
# Resize the image so that STRIPBYTECOUNTS will be larger than a SHORT
|
||||
im = im.resize((500, 500))
|
||||
info[TiffImagePlugin.IMAGEWIDTH] = im.width
|
||||
|
||||
# STRIPBYTECOUNTS can be a SHORT or a LONG
|
||||
info.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] = TiffTags.SHORT
|
||||
|
|
|
@ -1,14 +1,22 @@
|
|||
from __future__ import annotations
|
||||
import struct
|
||||
import pytest
|
||||
from io import BytesIO
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont, features
|
||||
from PIL import Image, ImageDraw, ImageFont, features, _util
|
||||
|
||||
from .helper import assert_image_equal_tofile
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
features.check_module("freetype2"),
|
||||
reason="PILfont superseded if FreeType is supported",
|
||||
)
|
||||
original_core = ImageFont.core
|
||||
|
||||
|
||||
def setup_module():
|
||||
if features.check_module("freetype2"):
|
||||
ImageFont.core = _util.DeferredError(ImportError)
|
||||
|
||||
|
||||
def teardown_module():
|
||||
ImageFont.core = original_core
|
||||
|
||||
|
||||
def test_default_font():
|
||||
|
@ -44,3 +52,23 @@ def test_textbbox():
|
|||
default_font = ImageFont.load_default()
|
||||
assert d.textlength("test", font=default_font) == 24
|
||||
assert d.textbbox((0, 0), "test", font=default_font) == (0, 0, 24, 11)
|
||||
|
||||
|
||||
def test_decompression_bomb():
|
||||
glyph = struct.pack(">hhhhhhhhhh", 1, 0, 0, 0, 256, 256, 0, 0, 256, 256)
|
||||
fp = BytesIO(b"PILfont\n\nDATA\n" + glyph * 256)
|
||||
|
||||
font = ImageFont.ImageFont()
|
||||
font._load_pilfont_data(fp, Image.new("L", (256, 256)))
|
||||
with pytest.raises(Image.DecompressionBombError):
|
||||
font.getmask("A" * 1_000_000)
|
||||
|
||||
|
||||
@pytest.mark.timeout(4)
|
||||
def test_oom():
|
||||
glyph = struct.pack(">hhhhhhhhhh", 1, 0, 0, 0, 32767, 32767, 0, 0, 32767, 32767)
|
||||
fp = BytesIO(b"PILfont\n\nDATA\n" + glyph * 256)
|
||||
|
||||
font = ImageFont.ImageFont()
|
||||
font._load_pilfont_data(fp, Image.new("L", (1, 1)))
|
||||
font.getmask("A" * 1_000_000)
|
||||
|
|
|
@ -57,15 +57,6 @@ def test_str_to_img():
|
|||
assert_image_equal_tofile(A, "Tests/images/morph_a.png")
|
||||
|
||||
|
||||
def create_lut():
|
||||
for op in ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge"):
|
||||
lb = ImageMorph.LutBuilder(op_name=op)
|
||||
lut = lb.build_lut()
|
||||
with open(f"Tests/images/{op}.lut", "wb") as f:
|
||||
f.write(lut)
|
||||
|
||||
|
||||
# create_lut()
|
||||
@pytest.mark.parametrize(
|
||||
"op", ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge")
|
||||
)
|
||||
|
|
|
@ -44,6 +44,17 @@ ImageFile.raise_oserror
|
|||
error codes returned by a codec's ``decode()`` method, which ImageFile already does
|
||||
automatically.
|
||||
|
||||
IptcImageFile helper functions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 10.2.0
|
||||
|
||||
The functions ``IptcImageFile.dump`` and ``IptcImageFile.i``, and the constant
|
||||
``IptcImageFile.PAD`` have been deprecated and will be removed in Pillow
|
||||
12.0.0 (2025-10-15). These are undocumented helper functions intended
|
||||
for internal use, so there is no replacement. They can each be replaced
|
||||
by a single line of code using builtin functions in Python.
|
||||
|
||||
Removed features
|
||||
----------------
|
||||
|
||||
|
|
|
@ -12,6 +12,15 @@ ImageFile.raise_oserror
|
|||
error codes returned by a codec's ``decode()`` method, which ImageFile already does
|
||||
automatically.
|
||||
|
||||
IptcImageFile helper functions
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The functions ``IptcImageFile.dump`` and ``IptcImageFile.i``, and the constant
|
||||
``IptcImageFile.PAD`` have been deprecated and will be removed in Pillow
|
||||
12.0.0 (2025-10-15). These are undocumented helper functions intended
|
||||
for internal use, so there is no replacement. They can each be replaced
|
||||
by a single line of code using builtin functions in Python.
|
||||
|
||||
API Changes
|
||||
===========
|
||||
|
||||
|
@ -60,6 +69,16 @@ Pillow will now raise a :py:exc:`ValueError` if the number of characters passed
|
|||
This threshold can be changed by setting :py:data:`PIL.ImageFont.MAX_STRING_LENGTH`. It
|
||||
can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``.
|
||||
|
||||
A decompression bomb check has also been added to
|
||||
:py:meth:`PIL.ImageFont.ImageFont.getmask`.
|
||||
|
||||
ImageFont.getmask: Trim glyph size
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To protect against potential DOS attacks when using PIL fonts,
|
||||
:py:class:`PIL.ImageFont.ImageFont` now trims the size of individual glyphs so that
|
||||
they do not extend beyond the bitmap image.
|
||||
|
||||
ImageMath.eval: Restricted environment keys
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
|
|
@ -3190,7 +3190,7 @@ def _decompression_bomb_check(size):
|
|||
)
|
||||
|
||||
|
||||
def open(fp, mode="r", formats=None):
|
||||
def open(fp, mode="r", formats=None) -> Image:
|
||||
"""
|
||||
Opens and identifies the given image file.
|
||||
|
||||
|
@ -3415,7 +3415,7 @@ def merge(mode, bands):
|
|||
# Plugin registry
|
||||
|
||||
|
||||
def register_open(id, factory, accept=None):
|
||||
def register_open(id, factory, accept=None) -> None:
|
||||
"""
|
||||
Register an image file plugin. This function should not be used
|
||||
in application code.
|
||||
|
@ -3469,7 +3469,7 @@ def register_save_all(id, driver):
|
|||
SAVE_ALL[id.upper()] = driver
|
||||
|
||||
|
||||
def register_extension(id, extension):
|
||||
def register_extension(id, extension) -> None:
|
||||
"""
|
||||
Registers an image extension. This function should not be
|
||||
used in application code.
|
||||
|
|
|
@ -396,7 +396,7 @@ class Color3DLUT(MultibandFilter):
|
|||
if hasattr(table, "shape"):
|
||||
try:
|
||||
import numpy
|
||||
except ImportError: # pragma: no cover
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if numpy and isinstance(table, numpy.ndarray):
|
||||
|
|
|
@ -150,6 +150,7 @@ class ImageFont:
|
|||
:py:mod:`PIL.Image.core` interface module.
|
||||
"""
|
||||
_string_length_check(text)
|
||||
Image._decompression_bomb_check(self.font.getsize(text))
|
||||
return self.font.getmask(text, mode)
|
||||
|
||||
def getbbox(self, text, *args, **kwargs):
|
||||
|
|
|
@ -16,31 +16,46 @@
|
|||
#
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from io import BytesIO
|
||||
from typing import Sequence
|
||||
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i16be as i16
|
||||
from ._binary import i32be as i32
|
||||
from ._deprecate import deprecate
|
||||
|
||||
COMPRESSION = {1: "raw", 5: "jpeg"}
|
||||
|
||||
PAD = b"\0\0\0\0"
|
||||
|
||||
def __getattr__(name: str) -> bytes:
|
||||
if name == "PAD":
|
||||
deprecate("IptcImagePlugin.PAD", 12)
|
||||
return b"\0\0\0\0"
|
||||
msg = f"module '{__name__}' has no attribute '{name}'"
|
||||
raise AttributeError(msg)
|
||||
|
||||
|
||||
#
|
||||
# Helpers
|
||||
|
||||
|
||||
def _i(c: bytes) -> int:
|
||||
return i32((b"\0\0\0\0" + c)[-4:])
|
||||
|
||||
|
||||
def _i8(c: int | bytes) -> int:
|
||||
return c if isinstance(c, int) else c[0]
|
||||
|
||||
|
||||
def i(c):
|
||||
return i32((PAD + c)[-4:])
|
||||
def i(c: bytes) -> int:
|
||||
""".. deprecated:: 10.2.0"""
|
||||
deprecate("IptcImagePlugin.i", 12)
|
||||
return _i(c)
|
||||
|
||||
|
||||
def dump(c):
|
||||
def dump(c: Sequence[int | bytes]) -> None:
|
||||
""".. deprecated:: 10.2.0"""
|
||||
deprecate("IptcImagePlugin.dump", 12)
|
||||
for i in c:
|
||||
print("%02x" % _i8(i), end=" ")
|
||||
print()
|
||||
|
@ -55,10 +70,10 @@ class IptcImageFile(ImageFile.ImageFile):
|
|||
format = "IPTC"
|
||||
format_description = "IPTC/NAA"
|
||||
|
||||
def getint(self, key):
|
||||
return i(self.info[key])
|
||||
def getint(self, key: tuple[int, int]) -> int:
|
||||
return _i(self.info[key])
|
||||
|
||||
def field(self):
|
||||
def field(self) -> tuple[tuple[int, int] | None, int]:
|
||||
#
|
||||
# get a IPTC field header
|
||||
s = self.fp.read(5)
|
||||
|
@ -80,13 +95,13 @@ class IptcImageFile(ImageFile.ImageFile):
|
|||
elif size == 128:
|
||||
size = 0
|
||||
elif size > 128:
|
||||
size = i(self.fp.read(size - 128))
|
||||
size = _i(self.fp.read(size - 128))
|
||||
else:
|
||||
size = i16(s, 3)
|
||||
|
||||
return tag, size
|
||||
|
||||
def _open(self):
|
||||
def _open(self) -> None:
|
||||
# load descriptive fields
|
||||
while True:
|
||||
offset = self.fp.tell()
|
||||
|
@ -142,12 +157,11 @@ class IptcImageFile(ImageFile.ImageFile):
|
|||
self.fp.seek(offset)
|
||||
|
||||
# Copy image data to temporary file
|
||||
o_fd, outfile = tempfile.mkstemp(text=False)
|
||||
o = os.fdopen(o_fd)
|
||||
o = BytesIO()
|
||||
if compression == "raw":
|
||||
# To simplify access to the extracted file,
|
||||
# prepend a PPM header
|
||||
o.write("P5\n%d %d\n255\n" % self.size)
|
||||
o.write(b"P5\n%d %d\n255\n" % self.size)
|
||||
while True:
|
||||
type, size = self.field()
|
||||
if type != (8, 10):
|
||||
|
@ -158,17 +172,10 @@ class IptcImageFile(ImageFile.ImageFile):
|
|||
break
|
||||
o.write(s)
|
||||
size -= len(s)
|
||||
o.close()
|
||||
|
||||
try:
|
||||
with Image.open(outfile) as _im:
|
||||
_im.load()
|
||||
self.im = _im.im
|
||||
finally:
|
||||
try:
|
||||
os.unlink(outfile)
|
||||
except OSError:
|
||||
pass
|
||||
with Image.open(o) as _im:
|
||||
_im.load()
|
||||
self.im = _im.im
|
||||
|
||||
|
||||
Image.register_open(IptcImageFile.format, IptcImageFile)
|
||||
|
@ -184,8 +191,6 @@ def getiptcinfo(im):
|
|||
:returns: A dictionary containing IPTC information, or None if
|
||||
no IPTC information block was found.
|
||||
"""
|
||||
import io
|
||||
|
||||
from . import JpegImagePlugin, TiffImagePlugin
|
||||
|
||||
data = None
|
||||
|
@ -220,7 +225,7 @@ def getiptcinfo(im):
|
|||
|
||||
# parse the IPTC information chunk
|
||||
im.info = {}
|
||||
im.fp = io.BytesIO(data)
|
||||
im.fp = BytesIO(data)
|
||||
|
||||
try:
|
||||
im._open()
|
||||
|
|
|
@ -1704,28 +1704,27 @@ def _save(im, fp, filename):
|
|||
colormap += [0] * (256 - colors)
|
||||
ifd[COLORMAP] = colormap
|
||||
# data orientation
|
||||
stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8)
|
||||
w, h = ifd[IMAGEWIDTH], ifd[IMAGELENGTH]
|
||||
stride = len(bits) * ((w * bits[0] + 7) // 8)
|
||||
if ROWSPERSTRIP not in ifd:
|
||||
# aim for given strip size (64 KB by default) when using libtiff writer
|
||||
if libtiff:
|
||||
im_strip_size = encoderinfo.get("strip_size", STRIP_SIZE)
|
||||
rows_per_strip = (
|
||||
1 if stride == 0 else min(im_strip_size // stride, im.size[1])
|
||||
)
|
||||
rows_per_strip = 1 if stride == 0 else min(im_strip_size // stride, h)
|
||||
# JPEG encoder expects multiple of 8 rows
|
||||
if compression == "jpeg":
|
||||
rows_per_strip = min(((rows_per_strip + 7) // 8) * 8, im.size[1])
|
||||
rows_per_strip = min(((rows_per_strip + 7) // 8) * 8, h)
|
||||
else:
|
||||
rows_per_strip = im.size[1]
|
||||
rows_per_strip = h
|
||||
if rows_per_strip == 0:
|
||||
rows_per_strip = 1
|
||||
ifd[ROWSPERSTRIP] = rows_per_strip
|
||||
strip_byte_counts = 1 if stride == 0 else stride * ifd[ROWSPERSTRIP]
|
||||
strips_per_image = (im.size[1] + ifd[ROWSPERSTRIP] - 1) // ifd[ROWSPERSTRIP]
|
||||
strips_per_image = (h + ifd[ROWSPERSTRIP] - 1) // ifd[ROWSPERSTRIP]
|
||||
if strip_byte_counts >= 2**16:
|
||||
ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG
|
||||
ifd[STRIPBYTECOUNTS] = (strip_byte_counts,) * (strips_per_image - 1) + (
|
||||
stride * im.size[1] - strip_byte_counts * (strips_per_image - 1),
|
||||
stride * h - strip_byte_counts * (strips_per_image - 1),
|
||||
)
|
||||
ifd[STRIPOFFSETS] = tuple(
|
||||
range(0, strip_byte_counts * strips_per_image, strip_byte_counts)
|
||||
|
|
|
@ -2649,6 +2649,18 @@ _font_new(PyObject *self_, PyObject *args) {
|
|||
self->glyphs[i].sy0 = S16(B16(glyphdata, 14));
|
||||
self->glyphs[i].sx1 = S16(B16(glyphdata, 16));
|
||||
self->glyphs[i].sy1 = S16(B16(glyphdata, 18));
|
||||
|
||||
// Do not allow glyphs to extend beyond bitmap image
|
||||
// Helps prevent DOS by stopping cropped images being larger than the original
|
||||
if (self->glyphs[i].sx1 > self->bitmap->xsize) {
|
||||
self->glyphs[i].dx1 -= self->glyphs[i].sx1 - self->bitmap->xsize;
|
||||
self->glyphs[i].sx1 = self->bitmap->xsize;
|
||||
}
|
||||
if (self->glyphs[i].sy1 > self->bitmap->ysize) {
|
||||
self->glyphs[i].dy1 -= self->glyphs[i].sy1 - self->bitmap->ysize;
|
||||
self->glyphs[i].sy1 = self->bitmap->ysize;
|
||||
}
|
||||
|
||||
if (self->glyphs[i].dy0 < y0) {
|
||||
y0 = self->glyphs[i].dy0;
|
||||
}
|
||||
|
@ -2721,7 +2733,7 @@ _font_text_asBytes(PyObject *encoded_string, unsigned char **text) {
|
|||
static PyObject *
|
||||
_font_getmask(ImagingFontObject *self, PyObject *args) {
|
||||
Imaging im;
|
||||
Imaging bitmap;
|
||||
Imaging bitmap = NULL;
|
||||
int x, b;
|
||||
int i = 0;
|
||||
int status;
|
||||
|
@ -2730,7 +2742,7 @@ _font_getmask(ImagingFontObject *self, PyObject *args) {
|
|||
PyObject *encoded_string;
|
||||
|
||||
unsigned char *text;
|
||||
char *mode = "";
|
||||
char *mode;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O|s:getmask", &encoded_string, &mode)) {
|
||||
return NULL;
|
||||
|
@ -2753,10 +2765,13 @@ _font_getmask(ImagingFontObject *self, PyObject *args) {
|
|||
b = self->baseline;
|
||||
for (x = 0; text[i]; i++) {
|
||||
glyph = &self->glyphs[text[i]];
|
||||
bitmap =
|
||||
ImagingCrop(self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1);
|
||||
if (!bitmap) {
|
||||
goto failed;
|
||||
if (i == 0 || text[i] != text[i - 1]) {
|
||||
ImagingDelete(bitmap);
|
||||
bitmap =
|
||||
ImagingCrop(self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1);
|
||||
if (!bitmap) {
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
status = ImagingPaste(
|
||||
im,
|
||||
|
@ -2766,17 +2781,18 @@ _font_getmask(ImagingFontObject *self, PyObject *args) {
|
|||
glyph->dy0 + b,
|
||||
glyph->dx1 + x,
|
||||
glyph->dy1 + b);
|
||||
ImagingDelete(bitmap);
|
||||
if (status < 0) {
|
||||
goto failed;
|
||||
}
|
||||
x = x + glyph->dx;
|
||||
b = b + glyph->dy;
|
||||
}
|
||||
ImagingDelete(bitmap);
|
||||
free(text);
|
||||
return PyImagingNew(im);
|
||||
|
||||
failed:
|
||||
ImagingDelete(bitmap);
|
||||
free(text);
|
||||
ImagingDelete(im);
|
||||
Py_RETURN_NONE;
|
||||
|
|
Loading…
Reference in New Issue
Block a user