Merge branch 'main' into comment_correct_placement

This commit is contained in:
Andrew Murray 2022-05-22 14:56:57 +10:00 committed by GitHub
commit db76eaa12c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 290 additions and 204 deletions

View File

@ -11,9 +11,9 @@ jobs:
matrix:
docker: [
# Run slower jobs first to give them a headstart and reduce waiting time
ubuntu-20.04-focal-arm64v8,
ubuntu-20.04-focal-ppc64le,
ubuntu-20.04-focal-s390x,
ubuntu-22.04-jammy-arm64v8,
ubuntu-22.04-jammy-ppc64le,
ubuntu-22.04-jammy-s390x,
# Then run the remainder
alpine,
amazon-2-amd64,
@ -32,11 +32,11 @@ jobs:
]
dockerTag: [main]
include:
- docker: "ubuntu-20.04-focal-arm64v8"
- docker: "ubuntu-22.04-jammy-arm64v8"
qemu-arch: "aarch64"
- docker: "ubuntu-20.04-focal-ppc64le"
- docker: "ubuntu-22.04-jammy-ppc64le"
qemu-arch: "ppc64le"
- docker: "ubuntu-20.04-focal-s390x"
- docker: "ubuntu-22.04-jammy-s390x"
qemu-arch: "s390x"
name: ${{ matrix.docker }}

View File

@ -5,6 +5,24 @@ Changelog (Pillow)
9.2.0 (unreleased)
------------------
- Separate multiple GIF comment blocks with newlines #6294
[raygard, radarhere]
- Always use GIF89a for comments #6292
[raygard, radarhere]
- Ignore compression value from BMP info dictionary when saving as TIFF #6231
[radarhere]
- If font is file-like object, do not re-read from object to get variant #6234
[radarhere]
- Raise ValueError when trying to access internal fp after close #6213
[radarhere]
- Support more affine expression forms in im.point() #6254
[benrg, radarhere]
- Populate Python palette in fromarray() #6283
[radarhere]
@ -17,9 +35,6 @@ Changelog (Pillow)
- Adjust BITSPERSAMPLE to match SAMPLESPERPIXEL when opening TIFFs #6270
[radarhere]
- Do not open images with zero or negative height #6269
[radarhere]
- Search pkgconf system libs/cflags #6138
[jameshilliard, radarhere]
@ -50,6 +65,15 @@ Changelog (Pillow)
- Deprecated PhotoImage.paste() box parameter #6178
[radarhere]
9.1.1 (2022-05-17)
------------------
- When reading past the end of a TGA scan line, reduce bytes left. CVE-2022-30595
[radarhere]
- Do not open images with zero or negative height #6269
[radarhere]
9.1.0 (2022-04-01)
------------------

View File

@ -85,6 +85,8 @@ release-test:
sdist:
python3 -m build --help > /dev/null 2>&1 || python3 -m pip install build
python3 -m build --sdist
python3 -m twine --help > /dev/null 2>&1 || python3 -m pip install twine
python3 -m twine check --strict dist/*
.PHONY: test
test:

View File

@ -24,7 +24,6 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
* [ ] Create and check source distribution:
```bash
make sdist
python3 -m twine check --strict dist/*
```
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions)
* [ ] Check and upload all binaries and source distributions e.g.:
@ -61,7 +60,6 @@ Released as needed for security, installation or critical bug fixes.
* [ ] Create and check source distribution:
```bash
make sdist
python3 -m twine check --strict dist/*
```
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions)
* [ ] Check and upload all binaries and source distributions e.g.:
@ -91,7 +89,6 @@ Released as needed privately to individual vendors for critical security-related
* [ ] Create and check source distribution:
```bash
make sdist
python3 -m twine check --strict dist/*
```
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions)
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases)

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -637,6 +637,15 @@ def test_apng_save_blend(tmp_path):
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
def test_seek_after_close():
im = Image.open("Tests/images/apng/delay.png")
im.seek(1)
im.close()
with pytest.raises(ValueError):
im.seek(0)
def test_constants_deprecation():
for enum, prefix in {
PngImagePlugin.Disposal: "APNG_DISPOSE_",

View File

@ -46,6 +46,15 @@ def test_closed_file():
im.close()
def test_seek_after_close():
im = Image.open(animated_test_file)
im.seek(1)
im.close()
with pytest.raises(ValueError):
im.seek(0)
def test_context_manager():
with warnings.catch_warnings():
with Image.open(static_test_file) as im:

View File

@ -46,6 +46,19 @@ def test_closed_file():
im.close()
def test_seek_after_close():
im = Image.open("Tests/images/iss634.gif")
im.load()
im.close()
with pytest.raises(ValueError):
im.is_animated
with pytest.raises(ValueError):
im.n_frames
with pytest.raises(ValueError):
im.seek(1)
def test_context_manager():
with warnings.catch_warnings():
with Image.open(TEST_GIF) as im:
@ -794,6 +807,9 @@ def test_comment(tmp_path):
with Image.open(out) as reread:
assert reread.info["comment"] == im.info["comment"].encode()
# Test that GIF89a is used for comments
assert reread.info["version"] == b"GIF89a"
def test_comment_over_255(tmp_path):
out = str(tmp_path / "temp.gif")
@ -804,15 +820,23 @@ def test_comment_over_255(tmp_path):
im.info["comment"] = comment
im.save(out)
with Image.open(out) as reread:
assert reread.info["comment"] == comment
# Test that GIF89a is used for comments
assert reread.info["version"] == b"GIF89a"
def test_zero_comment_subblocks():
with Image.open("Tests/images/hopper_zero_comment_subblocks.gif") as im:
assert_image_equal_tofile(im, TEST_GIF)
def test_read_multiple_comment_blocks():
with Image.open("Tests/images/multiple_comments.gif") as im:
# Multiple comment blocks in a frame are separated not concatenated
assert im.info["comment"] == b"Test comment 1\nTest comment 2"
def test_write_comment(tmp_path):
out = str(tmp_path / "temp.gif")
with Image.open("Tests/images/dispose_prev.gif") as im:

View File

@ -18,6 +18,7 @@ from .helper import (
hopper,
mark_if_feature_version,
skip_unless_feature,
skip_unless_feature_version,
)
@ -991,6 +992,7 @@ class TestFileLibTiff(LibTiffTestCase):
with Image.open(out) as im:
im.load()
@skip_unless_feature_version("libtiff", "4.0.4")
def test_realloc_overflow(self):
TiffImagePlugin.READ_LIBTIFF = True
with Image.open("Tests/images/tiff_overflow_rows_per_strip.tif") as im:

View File

@ -48,6 +48,14 @@ def test_closed_file():
im.close()
def test_seek_after_close():
im = Image.open(test_files[0])
im.close()
with pytest.raises(ValueError):
im.seek(1)
def test_context_manager():
with warnings.catch_warnings():
with Image.open(test_files[0]) as im:

View File

@ -101,6 +101,10 @@ def test_cross_scan_line():
with Image.open("Tests/images/cross_scan_line.tga") as im:
assert_image_equal_tofile(im, "Tests/images/cross_scan_line.png")
with Image.open("Tests/images/cross_scan_line_truncated.tga") as im:
with pytest.raises(OSError):
im.load()
def test_save(tmp_path):
test_file = "Tests/images/tga_id_field.tga"

View File

@ -70,6 +70,15 @@ class TestFileTiff:
im.load()
im.close()
def test_seek_after_close(self):
im = Image.open("Tests/images/multipage.tiff")
im.close()
with pytest.raises(ValueError):
im.n_frames
with pytest.raises(ValueError):
im.seek(1)
def test_context_manager(self):
with warnings.catch_warnings():
with Image.open("Tests/images/multipage.tiff") as im:
@ -706,6 +715,13 @@ class TestFileTiff:
with Image.open(outfile) as reloaded:
assert reloaded.info["icc_profile"] == icc_profile
def test_save_bmp_compression(self, tmp_path):
with Image.open("Tests/images/hopper.bmp") as im:
assert im.info["compression"] == 0
outfile = str(tmp_path / "temp.tif")
im.save(outfile)
def test_discard_icc_profile(self, tmp_path):
outfile = str(tmp_path / "temp.tif")

View File

@ -1,5 +1,7 @@
import pytest
from PIL import Image
from .helper import assert_image_equal, hopper
@ -17,11 +19,24 @@ def test_sanity():
im.point(list(range(256)))
im.point(lambda x: x * 1)
im.point(lambda x: x + 1)
im.point(lambda x: x - 1)
im.point(lambda x: x * 1 + 1)
im.point(lambda x: 0.1 + 0.2 * x)
im.point(lambda x: -x)
im.point(lambda x: x - 0.5)
im.point(lambda x: 1 - x / 2)
im.point(lambda x: (2 + x) / 3)
im.point(lambda x: 0.5)
im.point(lambda x: x / 1)
im.point(lambda x: x + x)
with pytest.raises(TypeError):
im.point(lambda x: x - 1)
im.point(lambda x: x * x)
with pytest.raises(TypeError):
im.point(lambda x: x / 1)
im.point(lambda x: x / x)
with pytest.raises(TypeError):
im.point(lambda x: 1 / x)
with pytest.raises(TypeError):
im.point(lambda x: x // 2)
def test_16bit_lut():
@ -47,3 +62,8 @@ def test_f_mode():
im = hopper("F")
with pytest.raises(ValueError):
im.point(None)
def test_coerce_e_deprecation():
with pytest.warns(DeprecationWarning):
assert Image.coerce_e(2).data == 2

View File

@ -65,9 +65,12 @@ class TestImageFont:
return font_bytes
def test_font_with_filelike(self):
ImageFont.truetype(
ttf = ImageFont.truetype(
self._font_as_bytes(), FONT_SIZE, layout_engine=self.LAYOUT_ENGINE
)
ttf_copy = ttf.font_variant()
assert ttf_copy.font_bytes == ttf.font_bytes
self._render(self._font_as_bytes())
# Usage note: making two fonts from the same buffer fails.
# shared_bytes = self._font_as_bytes()

View File

@ -1,7 +1,7 @@
#!/bin/bash
# install openjpeg
archive=openjpeg-2.4.0
archive=openjpeg-2.5.0
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz

View File

@ -170,6 +170,14 @@ in Pillow 10 (2023-07-01). Upgrade to
`PyQt6 <https://www.riverbankcomputing.com/static/Docs/PyQt6/>`_ or
`PySide6 <https://doc.qt.io/qtforpython/>`_ instead.
Image.coerce_e
~~~~~~~~~~~~~~
.. deprecated:: 9.2.0
This undocumented method has been deprecated and will be removed in Pillow 10
(2023-07-01).
Removed features
----------------

View File

@ -181,7 +181,8 @@ Many of Pillow's features require external libraries:
* **openjpeg** provides JPEG 2000 functionality.
* Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1** and **2.4.0**.
* Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1**,
**2.4.0** and **2.5.0**.
* Pillow does **not** support the earlier **1.5** series which ships
with Debian Jessie.
@ -474,11 +475,9 @@ These platforms are built and tested for every change.
+----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 20.04 LTS (Focal) | 3.7, 3.8, 3.9, 3.10, 3.11, | x86-64 |
| | PyPy3 | |
| +----------------------------+---------------------+
| | 3.8 | arm64v8, ppc64le, |
| | | s390x |
+----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 |
| Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | arm64v8, ppc64le, |
| | | s390x, x86-64 |
+----------------------------------+----------------------------+---------------------+
| Windows Server 2016 | 3.7 | x86-64 |
+----------------------------------+----------------------------+---------------------+

View File

@ -174,7 +174,7 @@ Previously, if a BMP file was too large, an ``OSError`` would be raised. Now,
Dark theme for docs
^^^^^^^^^^^^^^^^^^^
The https://pillow.readthedocs.io documentation will use a dark theme if the the user has requested the system use one. Uses the ``prefers-color-scheme`` CSS media query.
The https://pillow.readthedocs.io documentation will use a dark theme if the user has requested the system use one. Uses the ``prefers-color-scheme`` CSS media query.

View File

@ -0,0 +1,16 @@
9.1.1
-----
Security
========
This release addresses several security problems.
:cve:`CVE-2022-30595`: When reading a TGA file with RLE packets that cross scan lines,
Pillow reads the information past the end of the first line without deducting that
from the length of the remaining file data. This vulnerability was introduced in Pillow
9.1.0, and can cause a heap buffer overflow.
Opening an image with a zero or negative height has been found to bypass a
decompression bomb check. This will now raise a :py:exc:`SyntaxError` instead, in turn
raising a ``PIL.UnidentifiedImageError``.

View File

@ -31,6 +31,14 @@ FreeTypeFont.getmask2 fill parameter
The undocumented ``fill`` parameter of :py:meth:`.FreeTypeFont.getmask2`
has been deprecated and will be removed in Pillow 10 (2023-07-01).
Image.coerce_e
~~~~~~~~~~~~~~
.. deprecated:: 9.2.0
This undocumented method has been deprecated and will be removed in Pillow 10
(2023-07-01).
API Changes
===========

View File

@ -15,6 +15,7 @@ expected to be backported to earlier versions.
:maxdepth: 2
9.2.0
9.1.1
9.1.0
9.0.1
9.0.0

View File

@ -57,7 +57,7 @@ class DcxImageFile(PcxImageFile):
break
self._offset.append(offset)
self.__fp = self.fp
self._fp = self.fp
self.frame = None
self.n_frames = len(self._offset)
self.is_animated = self.n_frames > 1
@ -67,22 +67,13 @@ class DcxImageFile(PcxImageFile):
if not self._seek_check(frame):
return
self.frame = frame
self.fp = self.__fp
self.fp = self._fp
self.fp.seek(self._offset[frame])
PcxImageFile._open(self)
def tell(self):
return self.frame
def _close__fp(self):
try:
if self.__fp != self.fp:
self.__fp.close()
except AttributeError:
pass
finally:
self.__fp = None
Image.register_open(DcxImageFile.format, DcxImageFile, _accept)

View File

@ -91,7 +91,7 @@ class FliImageFile(ImageFile.ImageFile):
# set things up to decode first frame
self.__frame = -1
self.__fp = self.fp
self._fp = self.fp
self.__rewind = self.fp.tell()
self.seek(0)
@ -125,7 +125,7 @@ class FliImageFile(ImageFile.ImageFile):
def _seek(self, frame):
if frame == 0:
self.__frame = -1
self.__fp.seek(self.__rewind)
self._fp.seek(self.__rewind)
self.__offset = 128
else:
# ensure that the previous frame was loaded
@ -136,7 +136,7 @@ class FliImageFile(ImageFile.ImageFile):
self.__frame = frame
# move to next frame
self.fp = self.__fp
self.fp = self._fp
self.fp.seek(self.__offset)
s = self.fp.read(4)
@ -153,15 +153,6 @@ class FliImageFile(ImageFile.ImageFile):
def tell(self):
return self.__frame
def _close__fp(self):
try:
if self.__fp != self.fp:
self.__fp.close()
except AttributeError:
pass
finally:
self.__fp = None
#
# registry

View File

@ -102,7 +102,7 @@ class GifImageFile(ImageFile.ImageFile):
p = ImagePalette.raw("RGB", p)
self.global_palette = self.palette = p
self.__fp = self.fp # FIXME: hack
self._fp = self.fp # FIXME: hack
self.__rewind = self.fp.tell()
self._n_frames = None
self._is_animated = None
@ -161,7 +161,7 @@ class GifImageFile(ImageFile.ImageFile):
self.__offset = 0
self.dispose = None
self.__frame = -1
self.__fp.seek(self.__rewind)
self._fp.seek(self.__rewind)
self.disposal_method = 0
else:
# ensure that the previous frame was loaded
@ -171,7 +171,7 @@ class GifImageFile(ImageFile.ImageFile):
if frame != self.__frame + 1:
raise ValueError(f"cannot seek to frame {frame}")
self.fp = self.__fp
self.fp = self._fp
if self.__offset:
# backup to last frame
self.fp.seek(self.__offset)
@ -228,12 +228,18 @@ class GifImageFile(ImageFile.ImageFile):
#
# comment extension
#
comment = b""
# Collect one comment block
while block:
if "comment" in info:
info["comment"] += block
else:
info["comment"] = block
comment += block
block = self.data()
if "comment" in info:
# If multiple comment blocks in frame, separate with \n
info["comment"] += b"\n" + comment
else:
info["comment"] = comment
s = None
continue
elif s[0] == 255:
@ -281,7 +287,7 @@ class GifImageFile(ImageFile.ImageFile):
s = None
if interlace is None:
# self.__fp = None
# self._fp = None
raise EOFError
if not update_image:
return
@ -443,15 +449,6 @@ class GifImageFile(ImageFile.ImageFile):
def tell(self):
return self.__frame
def _close__fp(self):
try:
if self.__fp != self.fp:
self.__fp.close()
except AttributeError:
pass
finally:
self.__fp = None
# --------------------------------------------------------------------
# Write GIF files
@ -903,17 +900,16 @@ def _get_global_header(im, info):
# https://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
version = b"87a"
for extensionKey in ["transparency", "duration", "loop", "comment"]:
if info and extensionKey in info:
if (extensionKey == "duration" and info[extensionKey] == 0) or (
extensionKey == "comment" and not (1 <= len(info[extensionKey]) <= 255)
):
continue
version = b"89a"
break
else:
if im.info.get("version") == b"89a":
version = b"89a"
if im.info.get("version") == b"89a" or (
info
and (
"transparency" in info
or "loop" in info
or info.get("duration")
or info.get("comment")
)
):
version = b"89a"
background = _get_background(im, info.get("background"))

View File

@ -245,7 +245,7 @@ class ImImageFile(ImageFile.ImageFile):
self.__offset = offs = self.fp.tell()
self.__fp = self.fp # FIXME: hack
self._fp = self.fp # FIXME: hack
if self.rawmode[:2] == "F;":
@ -294,22 +294,13 @@ class ImImageFile(ImageFile.ImageFile):
size = ((self.size[0] * bits + 7) // 8) * self.size[1]
offs = self.__offset + frame * size
self.fp = self.__fp
self.fp = self._fp
self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]
def tell(self):
return self.frame
def _close__fp(self):
try:
if self.__fp != self.fp:
self.__fp.close()
except AttributeError:
pass
finally:
self.__fp = None
#
# --------------------------------------------------------------------

View File

@ -29,7 +29,6 @@ import builtins
import io
import logging
import math
import numbers
import os
import re
import struct
@ -432,44 +431,50 @@ def _getencoder(mode, encoder_name, args, extra=()):
def coerce_e(value):
return value if isinstance(value, _E) else _E(value)
deprecate("coerce_e", 10)
return value if isinstance(value, _E) else _E(1, value)
# _E(scale, offset) represents the affine transformation scale * x + offset.
# The "data" field is named for compatibility with the old implementation,
# and should be renamed once coerce_e is removed.
class _E:
def __init__(self, data):
def __init__(self, scale, data):
self.scale = scale
self.data = data
def __neg__(self):
return _E(-self.scale, -self.data)
def __add__(self, other):
return _E((self.data, "__add__", coerce_e(other).data))
if isinstance(other, _E):
return _E(self.scale + other.scale, self.data + other.data)
return _E(self.scale, self.data + other)
__radd__ = __add__
def __sub__(self, other):
return self + -other
def __rsub__(self, other):
return other + -self
def __mul__(self, other):
return _E((self.data, "__mul__", coerce_e(other).data))
if isinstance(other, _E):
return NotImplemented
return _E(self.scale * other, self.data * other)
__rmul__ = __mul__
def __truediv__(self, other):
if isinstance(other, _E):
return NotImplemented
return _E(self.scale / other, self.data / other)
def _getscaleoffset(expr):
stub = ["stub"]
data = expr(_E(stub)).data
try:
(a, b, c) = data # simplified syntax
if a is stub and b == "__mul__" and isinstance(c, numbers.Number):
return c, 0.0
if a is stub and b == "__add__" and isinstance(c, numbers.Number):
return 1.0, c
except TypeError:
pass
try:
((a, b, c), d, e) = data # full syntax
if (
a is stub
and b == "__mul__"
and isinstance(c, numbers.Number)
and d == "__add__"
and isinstance(e, numbers.Number)
):
return c, e
except TypeError:
pass
raise ValueError("illegal expression")
a = expr(_E(1, 0))
return (a.scale, a.data) if isinstance(a, _E) else (0, a)
# --------------------------------------------------------------------
@ -544,8 +549,10 @@ class Image:
def __exit__(self, *args):
if hasattr(self, "fp") and getattr(self, "_exclusive_fp", False):
if hasattr(self, "_close__fp"):
self._close__fp()
if getattr(self, "_fp", False):
if self._fp != self.fp:
self._fp.close()
self._fp = DeferredError(ValueError("Operation on closed image"))
if self.fp:
self.fp.close()
self.fp = None
@ -563,8 +570,10 @@ class Image:
more information.
"""
try:
if hasattr(self, "_close__fp"):
self._close__fp()
if getattr(self, "_fp", False):
if self._fp != self.fp:
self._fp.close()
self._fp = DeferredError(ValueError("Operation on closed image"))
if self.fp:
self.fp.close()
self.fp = None
@ -1324,7 +1333,7 @@ class Image:
def getextrema(self):
"""
Gets the the minimum and maximum pixel values for each band in
Gets the minimum and maximum pixel values for each band in
the image.
:returns: For a single-band image, a 2-tuple containing the

View File

@ -711,8 +711,13 @@ class FreeTypeFont:
:return: A FreeTypeFont object.
"""
if font is None:
try:
font = BytesIO(self.font_bytes)
except AttributeError:
font = self.path
return FreeTypeFont(
font=self.path if font is None else font,
font=font,
size=self.size if size is None else size,
index=self.index if index is None else index,
encoding=self.encoding if encoding is None else encoding,

View File

@ -62,7 +62,6 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
if not self.images:
raise SyntaxError("not an MIC file; no image entries")
self.__fp = self.fp
self.frame = None
self._n_frames = len(self.images)
self.is_animated = self._n_frames > 1
@ -89,15 +88,6 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
def tell(self):
return self.frame
def _close__fp(self):
try:
if self.__fp != self.fp:
self.__fp.close()
except AttributeError:
pass
finally:
self.__fp = None
#
# --------------------------------------------------------------------

View File

@ -58,20 +58,20 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
assert self.n_frames == len(self.__mpoffsets)
del self.info["mpoffset"] # no longer needed
self.is_animated = self.n_frames > 1
self.__fp = self.fp # FIXME: hack
self.__fp.seek(self.__mpoffsets[0]) # get ready to read first frame
self._fp = self.fp # FIXME: hack
self._fp.seek(self.__mpoffsets[0]) # get ready to read first frame
self.__frame = 0
self.offset = 0
# for now we can only handle reading and individual frame extraction
self.readonly = 1
def load_seek(self, pos):
self.__fp.seek(pos)
self._fp.seek(pos)
def seek(self, frame):
if not self._seek_check(frame):
return
self.fp = self.__fp
self.fp = self._fp
self.offset = self.__mpoffsets[frame]
self.fp.seek(self.offset + 2) # skip SOI marker
@ -97,15 +97,6 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
def tell(self):
return self.__frame
def _close__fp(self):
try:
if self.__fp != self.fp:
self.__fp.close()
except AttributeError:
pass
finally:
self.__fp = None
@staticmethod
def adopt(jpeg_instance, mpheader=None):
"""

View File

@ -710,7 +710,7 @@ class PngImageFile(ImageFile.ImageFile):
if not _accept(self.fp.read(8)):
raise SyntaxError("not a PNG file")
self.__fp = self.fp
self._fp = self.fp
self.__frame = 0
#
@ -767,7 +767,7 @@ class PngImageFile(ImageFile.ImageFile):
self._close_exclusive_fp_after_loading = False
self.png.save_rewind()
self.__rewind_idat = self.__prepare_idat
self.__rewind = self.__fp.tell()
self.__rewind = self._fp.tell()
if self.default_image:
# IDAT chunk contains default image and not first animation frame
self.n_frames += 1
@ -822,7 +822,7 @@ class PngImageFile(ImageFile.ImageFile):
def _seek(self, frame, rewind=False):
if frame == 0:
if rewind:
self.__fp.seek(self.__rewind)
self._fp.seek(self.__rewind)
self.png.rewind()
self.__prepare_idat = self.__rewind_idat
self.im = None
@ -830,7 +830,7 @@ class PngImageFile(ImageFile.ImageFile):
self.pyaccess = None
self.info = self.png.im_info
self.tile = self.png.im_tile
self.fp = self.__fp
self.fp = self._fp
self._prev_im = None
self.dispose = None
self.default_image = self.info.get("default_image", False)
@ -849,7 +849,7 @@ class PngImageFile(ImageFile.ImageFile):
self.im.paste(self.dispose, self.dispose_extent)
self._prev_im = self.im.copy()
self.fp = self.__fp
self.fp = self._fp
# advance to the next frame
if self.__prepare_idat:
@ -1027,15 +1027,6 @@ class PngImageFile(ImageFile.ImageFile):
else {}
)
def _close__fp(self):
try:
if self.__fp != self.fp:
self.__fp.close()
except AttributeError:
pass
finally:
self.__fp = None
# --------------------------------------------------------------------
# PNG writer

View File

@ -132,7 +132,7 @@ class PsdImageFile(ImageFile.ImageFile):
self.tile = _maketile(self.fp, mode, (0, 0) + self.size, channels)
# keep the file open
self.__fp = self.fp
self._fp = self.fp
self.frame = 1
self._min_frame = 1
@ -146,7 +146,7 @@ class PsdImageFile(ImageFile.ImageFile):
self.mode = mode
self.tile = tile
self.frame = layer
self.fp = self.__fp
self.fp = self._fp
return name, bbox
except IndexError as e:
raise EOFError("no such layer") from e
@ -155,15 +155,6 @@ class PsdImageFile(ImageFile.ImageFile):
# return layer number (0=image, 1..max=layers)
return self.frame
def _close__fp(self):
try:
if self.__fp != self.fp:
self.__fp.close()
except AttributeError:
pass
finally:
self.__fp = None
def _layerinfo(fp, ct_bytes):
# read layerinfo block

View File

@ -15,7 +15,7 @@
#
##
# Image plugin for the Spider image format. This format is is used
# Image plugin for the Spider image format. This format is used
# by the SPIDER software, in processing image data from electron
# microscopy and tomography.
##
@ -149,7 +149,7 @@ class SpiderImageFile(ImageFile.ImageFile):
self.mode = "F"
self.tile = [("raw", (0, 0) + self.size, offset, (self.rawmode, 0, 1))]
self.__fp = self.fp # FIXME: hack
self._fp = self.fp # FIXME: hack
@property
def n_frames(self):
@ -172,7 +172,7 @@ class SpiderImageFile(ImageFile.ImageFile):
if not self._seek_check(frame):
return
self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes)
self.fp = self.__fp
self.fp = self._fp
self.fp.seek(self.stkoffset)
self._open()
@ -191,15 +191,6 @@ class SpiderImageFile(ImageFile.ImageFile):
return ImageTk.PhotoImage(self.convert2byte(), palette=256)
def _close__fp(self):
try:
if self.__fp != self.fp:
self.__fp.close()
except AttributeError:
pass
finally:
self.__fp = None
# --------------------------------------------------------------------
# Image series

View File

@ -1073,7 +1073,7 @@ class TiffImageFile(ImageFile.ImageFile):
# setup frame pointers
self.__first = self.__next = self.tag_v2.next
self.__frame = -1
self.__fp = self.fp
self._fp = self.fp
self._frame_pos = []
self._n_frames = None
@ -1106,7 +1106,7 @@ class TiffImageFile(ImageFile.ImageFile):
self.im = Image.core.new(self.mode, self.size)
def _seek(self, frame):
self.fp = self.__fp
self.fp = self._fp
# reset buffered io handle in case fp
# was passed to libtiff, invalidating the buffer
@ -1515,15 +1515,6 @@ class TiffImageFile(ImageFile.ImageFile):
self._tile_orientation = self.tag_v2.get(0x0112)
def _close__fp(self):
try:
if self.__fp != self.fp:
self.__fp.close()
except AttributeError:
pass
finally:
self.__fp = None
#
# --------------------------------------------------------------------
@ -1568,7 +1559,13 @@ def _save(im, fp, filename):
encoderinfo = im.encoderinfo
encoderconfig = im.encoderconfig
compression = encoderinfo.get("compression", im.info.get("compression"))
try:
compression = encoderinfo["compression"]
except KeyError:
compression = im.info.get("compression")
if isinstance(compression, int):
# compression value may be from BMP. Ignore it
compression = None
if compression is None:
compression = "raw"
elif compression == "tiff_jpeg":

View File

@ -125,7 +125,7 @@ ImagingGifDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t
context->blocksize--;
/* New bits are shifted in from from the left. */
/* New bits are shifted in from the left. */
context->bitbuffer |= (INT32)c << context->bitcount;
context->bitcount += 8;

View File

@ -1519,7 +1519,7 @@ error_0:
typedef struct {
Pixel new;
Pixel furthest;
uint32_t furthestV;
uint32_t furthestDistance;
int secondPixel;
} DistanceData;
@ -1536,7 +1536,7 @@ compute_distances(const HashTable *h, const Pixel pixel, uint32_t *dist, void *u
}
if (oldDist > data->furthestDistance) {
data->furthestDistance = oldDist;
data->furthest.v = pixel.v;
data->furthestV = pixel.v;
}
}
@ -1577,10 +1577,11 @@ quantize2(
data.new.c.b = (int)(.5 + (double)mean[2] / (double)nPixels);
for (i = 0; i < nQuantPixels; i++) {
data.furthestDistance = 0;
data.furthestV = pixelData[0].v;
data.secondPixel = (i == 1) ? 1 : 0;
hashtable_foreach_update(h, compute_distances, &data);
p[i].v = data.furthest.v;
data.new.v = data.furthest.v;
p[i].v = data.furthestV;
data.new.v = data.furthestV;
}
hashtable_free(h);

View File

@ -120,6 +120,7 @@ ImagingTgaRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t
}
memcpy(state->buffer + state->x, ptr, n);
ptr += n;
bytes -= n;
extra_bytes -= n;
}
}

View File

@ -246,15 +246,15 @@ deps = {
"libs": [r"Lib\MS\*.lib"],
},
"openjpeg": {
"url": "https://github.com/uclouvain/openjpeg/archive/v2.4.0.tar.gz",
"filename": "openjpeg-2.4.0.tar.gz",
"dir": "openjpeg-2.4.0",
"url": "https://github.com/uclouvain/openjpeg/archive/v2.5.0.tar.gz",
"filename": "openjpeg-2.5.0.tar.gz",
"dir": "openjpeg-2.5.0",
"build": [
cmd_cmake(("-DBUILD_THIRDPARTY:BOOL=OFF", "-DBUILD_SHARED_LIBS:BOOL=OFF")),
cmd_nmake(target="clean"),
cmd_nmake(target="openjp2"),
cmd_mkdir(r"{inc_dir}\openjpeg-2.4.0"),
cmd_copy(r"src\lib\openjp2\*.h", r"{inc_dir}\openjpeg-2.4.0"),
cmd_mkdir(r"{inc_dir}\openjpeg-2.5.0"),
cmd_copy(r"src\lib\openjp2\*.h", r"{inc_dir}\openjpeg-2.5.0"),
],
"libs": [r"bin\*.lib"],
},
@ -280,9 +280,9 @@ deps = {
"libs": [r"imagequant.lib"],
},
"harfbuzz": {
"url": "https://github.com/harfbuzz/harfbuzz/archive/4.2.1.zip",
"filename": "harfbuzz-4.2.1.zip",
"dir": "harfbuzz-4.2.1",
"url": "https://github.com/harfbuzz/harfbuzz/archive/4.3.0.zip",
"filename": "harfbuzz-4.3.0.zip",
"dir": "harfbuzz-4.3.0",
"build": [
cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"),
cmd_nmake(target="clean"),