Merge branch 'main' into update-release-notes

This commit is contained in:
Andrew Murray 2024-01-01 19:03:59 +11:00 committed by GitHub
commit ca94dae26b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 222 additions and 78 deletions

View File

@ -2,15 +2,14 @@
[report] [report]
# Regexes for lines to exclude from consideration # Regexes for lines to exclude from consideration
exclude_lines = exclude_also =
# Have to re-enable the standard pragma: # Don't complain if non-runnable code isn't run
pragma: no cover
# Don't complain if non-runnable code isn't run:
if 0: if 0:
if __name__ == .__main__.: if __name__ == .__main__.:
# Don't complain about debug code # Don't complain about debug code
if DEBUG: if DEBUG:
# Don't complain about compatibility code for missing optional dependencies
except ImportError
[run] [run]
omit = omit =

18
.github/problem-matchers/gcc.json vendored Normal file
View 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
}
]
}
]
}

View File

@ -86,6 +86,10 @@ jobs:
env: env:
GHA_PYTHON_VERSION: ${{ matrix.python-version }} 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 - name: Build
run: | run: |
.ci/build.sh .ci/build.sh

View File

@ -5,6 +5,30 @@ Changelog (Pillow)
10.2.0 (unreleased) 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 - Apply ImageFont.MAX_STRING_LENGTH to ImageFont.getmask() #7662
[radarhere] [radarhere]

View File

@ -6,18 +6,21 @@ import pytest
from PIL import Image, IptcImagePlugin from PIL import Image, IptcImagePlugin
from .helper import hopper from .helper import assert_image_equal, hopper
TEST_FILE = "Tests/images/iptc.jpg" TEST_FILE = "Tests/images/iptc.jpg"
def test_open(): def test_open():
expected = Image.new("L", (1, 1))
f = BytesIO( f = BytesIO(
b"\x1c\x03<\x00\x02\x01\x00\x1c\x03x\x00\x01\x01\x1c" b"\x1c\x03<\x00\x02\x01\x00\x1c\x03x\x00\x01\x01\x1c\x03\x14\x00\x01\x01"
b"\x03\x14\x00\x01\x01\x1c\x03\x1e\x00\x01\x01\x1c\x08\n\x00\x00" b"\x1c\x03\x1e\x00\x01\x01\x1c\x08\n\x00\x01\x00"
) )
with Image.open(f) as im: with Image.open(f) as im:
assert im.tile == [("iptc", (0, 0, 1, 1), 25, "raw")] assert im.tile == [("iptc", (0, 0, 1, 1), 25, "raw")]
assert_image_equal(im, expected)
def test_getiptcinfo_jpg_none(): def test_getiptcinfo_jpg_none():
@ -87,24 +90,28 @@ def test_i():
c = b"a" c = b"a"
# Act # Act
ret = IptcImagePlugin.i(c) with pytest.warns(DeprecationWarning):
ret = IptcImagePlugin.i(c)
# Assert # Assert
assert ret == 97 assert ret == 97
def test_dump(): def test_dump(monkeypatch):
# Arrange # Arrange
c = b"abc" c = b"abc"
# Temporarily redirect stdout # Temporarily redirect stdout
old_stdout = sys.stdout mystdout = StringIO()
sys.stdout = mystdout = StringIO() monkeypatch.setattr(sys, "stdout", mystdout)
# Act # Act
IptcImagePlugin.dump(c) with pytest.warns(DeprecationWarning):
IptcImagePlugin.dump(c)
# Reset stdout
sys.stdout = old_stdout
# Assert # Assert
assert mystdout.getvalue() == "61 62 63 \n" assert mystdout.getvalue() == "61 62 63 \n"
def test_pad_deprecation():
with pytest.warns(DeprecationWarning):
assert IptcImagePlugin.PAD == b"\0\0\0\0"

View File

@ -484,13 +484,13 @@ class TestFileTiff:
outfile = str(tmp_path / "temp.tif") outfile = str(tmp_path / "temp.tif")
with Image.open("Tests/images/ifd_tag_type.tiff") as im: with Image.open("Tests/images/ifd_tag_type.tiff") as im:
exif = im.getexif() exif = im.getexif()
exif[256] = 100 exif[264] = 100
im.save(outfile, exif=exif) im.save(outfile, exif=exif)
with Image.open(outfile) as im: with Image.open(outfile) as im:
exif = im.getexif() exif = im.getexif()
assert exif[256] == 100 assert exif[264] == 100
def test_reload_exif_after_seek(self): def test_reload_exif_after_seek(self):
with Image.open("Tests/images/multipage.tiff") as im: with Image.open("Tests/images/multipage.tiff") as im:
@ -781,6 +781,27 @@ class TestFileTiff:
4001, 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): def test_close_on_load_exclusive(self, tmp_path):
# similar to test_fd_leak, but runs on unixlike os # similar to test_fd_leak, but runs on unixlike os
tmpfile = str(tmp_path / "temp.tif") tmpfile = str(tmp_path / "temp.tif")

View File

@ -164,6 +164,7 @@ def test_change_stripbytecounts_tag_type(tmp_path):
# Resize the image so that STRIPBYTECOUNTS will be larger than a SHORT # Resize the image so that STRIPBYTECOUNTS will be larger than a SHORT
im = im.resize((500, 500)) im = im.resize((500, 500))
info[TiffImagePlugin.IMAGEWIDTH] = im.width
# STRIPBYTECOUNTS can be a SHORT or a LONG # STRIPBYTECOUNTS can be a SHORT or a LONG
info.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] = TiffTags.SHORT info.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] = TiffTags.SHORT

View File

@ -1,14 +1,22 @@
from __future__ import annotations from __future__ import annotations
import struct
import pytest 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 from .helper import assert_image_equal_tofile
pytestmark = pytest.mark.skipif( original_core = ImageFont.core
features.check_module("freetype2"),
reason="PILfont superseded if FreeType is supported",
) def setup_module():
if features.check_module("freetype2"):
ImageFont.core = _util.DeferredError(ImportError)
def teardown_module():
ImageFont.core = original_core
def test_default_font(): def test_default_font():
@ -44,3 +52,23 @@ def test_textbbox():
default_font = ImageFont.load_default() default_font = ImageFont.load_default()
assert d.textlength("test", font=default_font) == 24 assert d.textlength("test", font=default_font) == 24
assert d.textbbox((0, 0), "test", font=default_font) == (0, 0, 24, 11) 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)

View File

@ -57,15 +57,6 @@ def test_str_to_img():
assert_image_equal_tofile(A, "Tests/images/morph_a.png") 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( @pytest.mark.parametrize(
"op", ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge") "op", ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge")
) )

View File

@ -44,6 +44,17 @@ ImageFile.raise_oserror
error codes returned by a codec's ``decode()`` method, which ImageFile already does error codes returned by a codec's ``decode()`` method, which ImageFile already does
automatically. 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 Removed features
---------------- ----------------

View File

@ -12,6 +12,15 @@ ImageFile.raise_oserror
error codes returned by a codec's ``decode()`` method, which ImageFile already does error codes returned by a codec's ``decode()`` method, which ImageFile already does
automatically. 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 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 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``. 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 ImageMath.eval: Restricted environment keys
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -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. Opens and identifies the given image file.
@ -3415,7 +3415,7 @@ def merge(mode, bands):
# Plugin registry # 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 Register an image file plugin. This function should not be used
in application code. in application code.
@ -3469,7 +3469,7 @@ def register_save_all(id, driver):
SAVE_ALL[id.upper()] = 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 Registers an image extension. This function should not be
used in application code. used in application code.

View File

@ -396,7 +396,7 @@ class Color3DLUT(MultibandFilter):
if hasattr(table, "shape"): if hasattr(table, "shape"):
try: try:
import numpy import numpy
except ImportError: # pragma: no cover except ImportError:
pass pass
if numpy and isinstance(table, numpy.ndarray): if numpy and isinstance(table, numpy.ndarray):

View File

@ -150,6 +150,7 @@ class ImageFont:
:py:mod:`PIL.Image.core` interface module. :py:mod:`PIL.Image.core` interface module.
""" """
_string_length_check(text) _string_length_check(text)
Image._decompression_bomb_check(self.font.getsize(text))
return self.font.getmask(text, mode) return self.font.getmask(text, mode)
def getbbox(self, text, *args, **kwargs): def getbbox(self, text, *args, **kwargs):

View File

@ -16,31 +16,46 @@
# #
from __future__ import annotations from __future__ import annotations
import os from io import BytesIO
import tempfile from typing import Sequence
from . import Image, ImageFile from . import Image, ImageFile
from ._binary import i16be as i16 from ._binary import i16be as i16
from ._binary import i32be as i32 from ._binary import i32be as i32
from ._deprecate import deprecate
COMPRESSION = {1: "raw", 5: "jpeg"} 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 # Helpers
def _i(c: bytes) -> int:
return i32((b"\0\0\0\0" + c)[-4:])
def _i8(c: int | bytes) -> int: def _i8(c: int | bytes) -> int:
return c if isinstance(c, int) else c[0] return c if isinstance(c, int) else c[0]
def i(c): def i(c: bytes) -> int:
return i32((PAD + c)[-4:]) """.. 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: for i in c:
print("%02x" % _i8(i), end=" ") print("%02x" % _i8(i), end=" ")
print() print()
@ -55,10 +70,10 @@ class IptcImageFile(ImageFile.ImageFile):
format = "IPTC" format = "IPTC"
format_description = "IPTC/NAA" format_description = "IPTC/NAA"
def getint(self, key): def getint(self, key: tuple[int, int]) -> int:
return i(self.info[key]) return _i(self.info[key])
def field(self): def field(self) -> tuple[tuple[int, int] | None, int]:
# #
# get a IPTC field header # get a IPTC field header
s = self.fp.read(5) s = self.fp.read(5)
@ -80,13 +95,13 @@ class IptcImageFile(ImageFile.ImageFile):
elif size == 128: elif size == 128:
size = 0 size = 0
elif size > 128: elif size > 128:
size = i(self.fp.read(size - 128)) size = _i(self.fp.read(size - 128))
else: else:
size = i16(s, 3) size = i16(s, 3)
return tag, size return tag, size
def _open(self): def _open(self) -> None:
# load descriptive fields # load descriptive fields
while True: while True:
offset = self.fp.tell() offset = self.fp.tell()
@ -142,12 +157,11 @@ class IptcImageFile(ImageFile.ImageFile):
self.fp.seek(offset) self.fp.seek(offset)
# Copy image data to temporary file # Copy image data to temporary file
o_fd, outfile = tempfile.mkstemp(text=False) o = BytesIO()
o = os.fdopen(o_fd)
if compression == "raw": if compression == "raw":
# To simplify access to the extracted file, # To simplify access to the extracted file,
# prepend a PPM header # 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: while True:
type, size = self.field() type, size = self.field()
if type != (8, 10): if type != (8, 10):
@ -158,17 +172,10 @@ class IptcImageFile(ImageFile.ImageFile):
break break
o.write(s) o.write(s)
size -= len(s) size -= len(s)
o.close()
try: with Image.open(o) as _im:
with Image.open(outfile) as _im: _im.load()
_im.load() self.im = _im.im
self.im = _im.im
finally:
try:
os.unlink(outfile)
except OSError:
pass
Image.register_open(IptcImageFile.format, IptcImageFile) Image.register_open(IptcImageFile.format, IptcImageFile)
@ -184,8 +191,6 @@ def getiptcinfo(im):
:returns: A dictionary containing IPTC information, or None if :returns: A dictionary containing IPTC information, or None if
no IPTC information block was found. no IPTC information block was found.
""" """
import io
from . import JpegImagePlugin, TiffImagePlugin from . import JpegImagePlugin, TiffImagePlugin
data = None data = None
@ -220,7 +225,7 @@ def getiptcinfo(im):
# parse the IPTC information chunk # parse the IPTC information chunk
im.info = {} im.info = {}
im.fp = io.BytesIO(data) im.fp = BytesIO(data)
try: try:
im._open() im._open()

View File

@ -1704,28 +1704,27 @@ def _save(im, fp, filename):
colormap += [0] * (256 - colors) colormap += [0] * (256 - colors)
ifd[COLORMAP] = colormap ifd[COLORMAP] = colormap
# data orientation # 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: if ROWSPERSTRIP not in ifd:
# aim for given strip size (64 KB by default) when using libtiff writer # aim for given strip size (64 KB by default) when using libtiff writer
if libtiff: if libtiff:
im_strip_size = encoderinfo.get("strip_size", STRIP_SIZE) im_strip_size = encoderinfo.get("strip_size", STRIP_SIZE)
rows_per_strip = ( rows_per_strip = 1 if stride == 0 else min(im_strip_size // stride, h)
1 if stride == 0 else min(im_strip_size // stride, im.size[1])
)
# JPEG encoder expects multiple of 8 rows # JPEG encoder expects multiple of 8 rows
if compression == "jpeg": 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: else:
rows_per_strip = im.size[1] rows_per_strip = h
if rows_per_strip == 0: if rows_per_strip == 0:
rows_per_strip = 1 rows_per_strip = 1
ifd[ROWSPERSTRIP] = rows_per_strip ifd[ROWSPERSTRIP] = rows_per_strip
strip_byte_counts = 1 if stride == 0 else stride * ifd[ROWSPERSTRIP] 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: if strip_byte_counts >= 2**16:
ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG
ifd[STRIPBYTECOUNTS] = (strip_byte_counts,) * (strips_per_image - 1) + ( 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( ifd[STRIPOFFSETS] = tuple(
range(0, strip_byte_counts * strips_per_image, strip_byte_counts) range(0, strip_byte_counts * strips_per_image, strip_byte_counts)

View File

@ -2649,6 +2649,18 @@ _font_new(PyObject *self_, PyObject *args) {
self->glyphs[i].sy0 = S16(B16(glyphdata, 14)); self->glyphs[i].sy0 = S16(B16(glyphdata, 14));
self->glyphs[i].sx1 = S16(B16(glyphdata, 16)); self->glyphs[i].sx1 = S16(B16(glyphdata, 16));
self->glyphs[i].sy1 = S16(B16(glyphdata, 18)); 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) { if (self->glyphs[i].dy0 < y0) {
y0 = self->glyphs[i].dy0; y0 = self->glyphs[i].dy0;
} }
@ -2721,7 +2733,7 @@ _font_text_asBytes(PyObject *encoded_string, unsigned char **text) {
static PyObject * static PyObject *
_font_getmask(ImagingFontObject *self, PyObject *args) { _font_getmask(ImagingFontObject *self, PyObject *args) {
Imaging im; Imaging im;
Imaging bitmap; Imaging bitmap = NULL;
int x, b; int x, b;
int i = 0; int i = 0;
int status; int status;
@ -2730,7 +2742,7 @@ _font_getmask(ImagingFontObject *self, PyObject *args) {
PyObject *encoded_string; PyObject *encoded_string;
unsigned char *text; unsigned char *text;
char *mode = ""; char *mode;
if (!PyArg_ParseTuple(args, "O|s:getmask", &encoded_string, &mode)) { if (!PyArg_ParseTuple(args, "O|s:getmask", &encoded_string, &mode)) {
return NULL; return NULL;
@ -2753,10 +2765,13 @@ _font_getmask(ImagingFontObject *self, PyObject *args) {
b = self->baseline; b = self->baseline;
for (x = 0; text[i]; i++) { for (x = 0; text[i]; i++) {
glyph = &self->glyphs[text[i]]; glyph = &self->glyphs[text[i]];
bitmap = if (i == 0 || text[i] != text[i - 1]) {
ImagingCrop(self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1); ImagingDelete(bitmap);
if (!bitmap) { bitmap =
goto failed; ImagingCrop(self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1);
if (!bitmap) {
goto failed;
}
} }
status = ImagingPaste( status = ImagingPaste(
im, im,
@ -2766,17 +2781,18 @@ _font_getmask(ImagingFontObject *self, PyObject *args) {
glyph->dy0 + b, glyph->dy0 + b,
glyph->dx1 + x, glyph->dx1 + x,
glyph->dy1 + b); glyph->dy1 + b);
ImagingDelete(bitmap);
if (status < 0) { if (status < 0) {
goto failed; goto failed;
} }
x = x + glyph->dx; x = x + glyph->dx;
b = b + glyph->dy; b = b + glyph->dy;
} }
ImagingDelete(bitmap);
free(text); free(text);
return PyImagingNew(im); return PyImagingNew(im);
failed: failed:
ImagingDelete(bitmap);
free(text); free(text);
ImagingDelete(im); ImagingDelete(im);
Py_RETURN_NONE; Py_RETURN_NONE;