From 98b73009cc92575f7b361057265b0443e4234022 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 16 Nov 2023 23:01:26 +1100 Subject: [PATCH 01/20] Correct PDF palette size when saving --- src/PIL/PdfImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py index 09fc0c7e6..b6bb60911 100644 --- a/src/PIL/PdfImagePlugin.py +++ b/src/PIL/PdfImagePlugin.py @@ -96,7 +96,7 @@ def _write_image(im, filename, existing_pdf, image_refs): dict_obj["ColorSpace"] = [ PdfParser.PdfName("Indexed"), PdfParser.PdfName("DeviceRGB"), - 255, + len(palette) // 3 - 1, PdfParser.PdfBinary(palette), ] procset = "ImageI" # indexed color From f23d029d5f356a1636342a0dfb12d1d6083fa5b5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 30 Nov 2023 07:41:02 +1100 Subject: [PATCH 02/20] Moved error from truetype() to FreeTypeFont --- src/PIL/ImageFont.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 0331a5c45..18de10375 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -188,6 +188,10 @@ class FreeTypeFont: def __init__(self, font=None, size=10, index=0, encoding="", layout_engine=None): # FIXME: use service provider instead + if size <= 0: + msg = "font size must be greater than 0" + raise ValueError(msg) + self.path = font self.size = size self.index = index @@ -791,10 +795,6 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None): :exception ValueError: If the font size is not greater than zero. """ - if size <= 0: - msg = "font size must be greater than 0" - raise ValueError(msg) - def freetype(font): return FreeTypeFont(font, size, index, encoding, layout_engine) From bd7874a6f1045d8e3597dd5d2ac2adafe4a83fc3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 1 Dec 2023 12:01:34 +1100 Subject: [PATCH 03/20] Update Windows wheels info --- docs/installation.rst | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 78900aa57..aa78a7a00 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -95,11 +95,10 @@ and :pypi:`olefile` for Pillow to read FPX and MIC images:: .. tab:: Windows - .. warning:: Pillow > 9.5.0 no longer includes 32-bit wheels. - - We provide Pillow binaries for Windows compiled for the matrix of - supported Pythons in 64-bit versions in the wheel format. These binaries include - support for all optional libraries except libimagequant and libxcb. Raqm support + We provide Pillow binaries for Windows compiled for the matrix of supported + Pythons in the wheel format. These include x86, x86-64 and arm64 versions + (with the exception of Python 3.8 on arm64). These binaries include support + for all optional libraries except libimagequant and libxcb. Raqm support requires FriBiDi to be installed separately:: python3 -m pip install --upgrade pip From a04c6a27e8fc6c4c3abccfb69ce892095a908da6 Mon Sep 17 00:00:00 2001 From: Andreas Florath Date: Fri, 1 Dec 2023 16:01:23 +0100 Subject: [PATCH 04/20] Optimization of ImageStat.Stat._getcount method The new implementation uses "sum" instead of the construct "functools.reduce(operator.add, ...)". Test showed that the new function is about three times faster than the original. Also it is shorter and easier to read. Signed-off-by: Andreas Florath --- src/PIL/ImageStat.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/PIL/ImageStat.py b/src/PIL/ImageStat.py index b7ebddf06..10cb08b30 100644 --- a/src/PIL/ImageStat.py +++ b/src/PIL/ImageStat.py @@ -69,10 +69,7 @@ class Stat: def _getcount(self): """Get total number of pixels in each layer""" - v = [] - for i in range(0, len(self.h), 256): - v.append(functools.reduce(operator.add, self.h[i : i + 256])) - return v + return [sum(self.h[i: i + 256]) for i in range(0, len(self.h), 256)] def _getsum(self): """Get sum of all pixels in each layer""" From f7d40ce31cfc7ecfb166b74e45b1e5844b547b86 Mon Sep 17 00:00:00 2001 From: Andreas Florath Date: Fri, 1 Dec 2023 16:15:31 +0100 Subject: [PATCH 05/20] Removed functools and operator import which are not needed anymore Signed-off-by: Andreas Florath --- src/PIL/ImageStat.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/PIL/ImageStat.py b/src/PIL/ImageStat.py index 10cb08b30..8d7dd8615 100644 --- a/src/PIL/ImageStat.py +++ b/src/PIL/ImageStat.py @@ -21,9 +21,7 @@ # See the README file for information on usage and redistribution. # -import functools import math -import operator class Stat: From e01354a2c85c9cdb4ce5dc8a6ad3cd1c6087f16f Mon Sep 17 00:00:00 2001 From: Andreas Florath Date: Fri, 1 Dec 2023 16:19:39 +0100 Subject: [PATCH 06/20] Added space before colon Signed-off-by: Andreas Florath --- src/PIL/ImageStat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageStat.py b/src/PIL/ImageStat.py index 8d7dd8615..c0f647024 100644 --- a/src/PIL/ImageStat.py +++ b/src/PIL/ImageStat.py @@ -67,7 +67,7 @@ class Stat: def _getcount(self): """Get total number of pixels in each layer""" - return [sum(self.h[i: i + 256]) for i in range(0, len(self.h), 256)] + return [sum(self.h[i : i + 256]) for i in range(0, len(self.h), 256)] def _getsum(self): """Get sum of all pixels in each layer""" From 11e226ee9065b9181a6d47d0a5094419aced16d5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 2 Dec 2023 13:18:13 +1100 Subject: [PATCH 07/20] All GitHub Actions wheels are now in the "Wheels" workflow --- RELEASING.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/RELEASING.md b/RELEASING.md index 8b0673203..74f427f03 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -94,7 +94,6 @@ Released as needed privately to individual vendors for critical security-related ## Source and Binary Distributions -### macOS and Linux * [ ] Download sdist and wheels from the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli): ```bash @@ -104,14 +103,6 @@ Released as needed privately to individual vendors for critical security-related * [ ] Download the Linux aarch64 wheels created by Travis CI from [GitHub releases](https://github.com/python-pillow/Pillow/releases) and copy into `dist`. -### Windows -* [ ] Download the artifacts from the [GitHub Actions "Test Windows" workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml) - and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli): - ```bash - gh run download --dir dist - # select dist-x.y.z - ``` - ## Publicize Release * [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) and [Mastodon](https://fosstodon.org/@pillow) e.g. https://twitter.com/PythonPillow/status/1013789184354603010 From 77a96a00ce56dbba3e5809f3b2e643e33c2e39f1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 2 Dec 2023 21:50:11 +1100 Subject: [PATCH 08/20] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 251917654..5f58a2cad 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 10.2.0 (unreleased) ------------------- +- Fixed closing file pointer with olefile 0.47 #7594 + [radarhere] + - Raise ValueError when TrueType font size is not greater than zero #7584 [akx, radarhere] From 27d7bd10c87c24a9babcc3ebec1b78b5a37c02b0 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sun, 3 Dec 2023 21:59:44 +0200 Subject: [PATCH 09/20] Use list comprehensions to create transformed lists --- Tests/test_file_apng.py | 4 +--- src/PIL/FpxImagePlugin.py | 9 ++++---- src/PIL/Image.py | 16 +++++--------- src/PIL/ImageCms.py | 7 ++---- src/PIL/ImageOps.py | 8 ++----- src/PIL/ImagePalette.py | 20 ++++++----------- src/PIL/ImageQt.py | 18 ++++++---------- src/PIL/ImageStat.py | 42 +++++++++++------------------------- src/PIL/JpegImagePlugin.py | 4 +--- src/PIL/MicImagePlugin.py | 9 ++++---- src/PIL/PcfFontFile.py | 16 +++++--------- src/PIL/SpiderImagePlugin.py | 4 +--- 12 files changed, 50 insertions(+), 107 deletions(-) diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index d0c81b5e9..1fb97a789 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -356,9 +356,7 @@ def test_apng_save(tmp_path): assert im.getpixel((64, 32)) == (0, 255, 0, 255) with Image.open("Tests/images/apng/single_frame_default.png") as im: - frames = [] - for frame_im in ImageSequence.Iterator(im): - frames.append(frame_im.copy()) + frames = [frame_im.copy() for frame_im in ImageSequence.Iterator(im)] frames[0].save( test_file, save_all=True, default_image=True, append_images=frames[1:] ) diff --git a/src/PIL/FpxImagePlugin.py b/src/PIL/FpxImagePlugin.py index 3027ef45b..a0999130e 100644 --- a/src/PIL/FpxImagePlugin.py +++ b/src/PIL/FpxImagePlugin.py @@ -97,16 +97,15 @@ class FpxImageFile(ImageFile.ImageFile): s = prop[0x2000002 | id] - colors = [] bands = i32(s, 4) if bands > 4: msg = "Invalid number of bands" raise OSError(msg) - for i in range(bands): - # note: for now, we ignore the "uncalibrated" flag - colors.append(i32(s, 8 + i * 4) & 0x7FFFFFFF) - self._mode, self.rawmode = MODES[tuple(colors)] + # note: for now, we ignore the "uncalibrated" flag + colors = tuple(i32(s, 8 + i * 4) & 0x7FFFFFFF for i in range(bands)) + + self._mode, self.rawmode = MODES[colors] # load JPEG tables, if any self.jpeg = {} diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 2853bd596..8c17292a7 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1288,9 +1288,9 @@ class Image: if self.im.bands == 1 or multiband: return self._new(filter.filter(self.im)) - ims = [] - for c in range(self.im.bands): - ims.append(self._new(filter.filter(self.im.getband(c)))) + ims = [ + self._new(filter.filter(self.im.getband(c))) for c in range(self.im.bands) + ] return merge(self.mode, ims) def getbands(self): @@ -1339,10 +1339,7 @@ class Image: self.load() if self.mode in ("1", "L", "P"): h = self.im.histogram() - out = [] - for i in range(256): - if h[i]: - out.append((h[i], i)) + out = [(h[i], i) for i in range(256) if h[i]] if len(out) > maxcolors: return None return out @@ -1383,10 +1380,7 @@ class Image: self.load() if self.im.bands > 1: - extrema = [] - for i in range(self.im.bands): - extrema.append(self.im.getband(i).getextrema()) - return tuple(extrema) + return tuple(self.im.getband(i).getextrema() for i in range(self.im.bands)) return self.im.getextrema() def _getxmp(self, xmp_tags): diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index 3a337f9f2..0df3a4c6c 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -787,11 +787,8 @@ def getProfileInfo(profile): # info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint description = profile.profile.profile_description cpright = profile.profile.copyright - arr = [] - for elt in (description, cpright): - if elt: - arr.append(elt) - return "\r\n\r\n".join(arr) + "\r\n\r\n" + elements = [element for element in (description, cpright) if element] + return "\r\n\r\n".join(elements) + "\r\n\r\n" except (AttributeError, OSError, TypeError, ValueError) as v: raise PyCMSError(v) from v diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 4f83a4edb..f316a307c 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -557,9 +557,7 @@ def invert(image): :param image: The image to invert. :return: An image. """ - lut = [] - for i in range(256): - lut.append(255 - i) + lut = [255 - i for i in range(256)] return image.point(lut) if image.mode == "1" else _lut(image, lut) @@ -581,10 +579,8 @@ def posterize(image, bits): :param bits: The number of bits to keep for each channel (1-8). :return: An image. """ - lut = [] mask = ~(2 ** (8 - bits) - 1) - for i in range(256): - lut.append(i & mask) + lut = [i & mask for i in range(256)] return _lut(image, lut) diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index cb4f1dba1..f33635a30 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -200,21 +200,15 @@ def raw(rawmode, data): def make_linear_lut(black, white): - lut = [] if black == 0: - for i in range(256): - lut.append(white * i // 255) - else: - msg = "unavailable when black is non-zero" - raise NotImplementedError(msg) # FIXME - return lut + return [white * i // 255 for i in range(256)] + + msg = "unavailable when black is non-zero" + raise NotImplementedError(msg) # FIXME def make_gamma_lut(exp): - lut = [] - for i in range(256): - lut.append(int(((i / 255.0) ** exp) * 255.0 + 0.5)) - return lut + return [int(((i / 255.0) ** exp) * 255.0 + 0.5) for i in range(256)] def negative(mode="RGB"): @@ -226,9 +220,7 @@ def negative(mode="RGB"): def random(mode="RGB"): from random import randint - palette = [] - for i in range(256 * len(mode)): - palette.append(randint(0, 255)) + palette = [randint(0, 255) for i in range(256 * len(mode))] return ImagePalette(mode, palette) diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index d017565a9..56c1aa525 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -103,12 +103,10 @@ def align8to32(bytes, width, mode): if not extra_padding: return bytes - new_data = [] - for i in range(len(bytes) // bytes_per_line): - new_data.append( - bytes[i * bytes_per_line : (i + 1) * bytes_per_line] - + b"\x00" * extra_padding - ) + new_data = [ + bytes[i * bytes_per_line : (i + 1) * bytes_per_line] + b"\x00" * extra_padding + for i in range(len(bytes) // bytes_per_line) + ] return b"".join(new_data) @@ -131,15 +129,11 @@ def _toqclass_helper(im): format = qt_format.Format_Mono elif im.mode == "L": format = qt_format.Format_Indexed8 - colortable = [] - for i in range(256): - colortable.append(rgb(i, i, i)) + colortable = [rgb(i, i, i) for i in range(256)] elif im.mode == "P": format = qt_format.Format_Indexed8 - colortable = [] palette = im.getpalette() - for i in range(0, len(palette), 3): - colortable.append(rgb(*palette[i : i + 3])) + colortable = [rgb(*palette[i : i + 3]) for i in range(0, len(palette), 3)] elif im.mode == "RGB": # Populate the 4th channel with 255 im = im.convert("RGBA") diff --git a/src/PIL/ImageStat.py b/src/PIL/ImageStat.py index b7ebddf06..12747d546 100644 --- a/src/PIL/ImageStat.py +++ b/src/PIL/ImageStat.py @@ -61,18 +61,14 @@ class Stat: x = max(x, i) return n, x # returns (255, 0) if there's no data in the histogram - v = [] - for i in range(0, len(self.h), 256): - v.append(minmax(self.h[i:])) - return v + return [minmax(self.h[i:]) for i in range(0, len(self.h), 256)] def _getcount(self): """Get total number of pixels in each layer""" - - v = [] - for i in range(0, len(self.h), 256): - v.append(functools.reduce(operator.add, self.h[i : i + 256])) - return v + return [ + functools.reduce(operator.add, self.h[i : i + 256]) + for i in range(0, len(self.h), 256) + ] def _getsum(self): """Get sum of all pixels in each layer""" @@ -98,11 +94,7 @@ class Stat: def _getmean(self): """Get average pixel level for each layer""" - - v = [] - for i in self.bands: - v.append(self.sum[i] / self.count[i]) - return v + return [self.sum[i] / self.count[i] for i in self.bands] def _getmedian(self): """Get median pixel level for each layer""" @@ -121,28 +113,18 @@ class Stat: def _getrms(self): """Get RMS for each layer""" - - v = [] - for i in self.bands: - v.append(math.sqrt(self.sum2[i] / self.count[i])) - return v + return [math.sqrt(self.sum2[i] / self.count[i]) for i in self.bands] def _getvar(self): """Get variance for each layer""" - - v = [] - for i in self.bands: - n = self.count[i] - v.append((self.sum2[i] - (self.sum[i] ** 2.0) / n) / n) - return v + return [ + (self.sum2[i] - (self.sum[i] ** 2.0) / self.count[i]) / self.count[i] + for i in self.bands + ] def _getstddev(self): """Get standard deviation for each layer""" - - v = [] - for i in self.bands: - v.append(math.sqrt(self.var[i])) - return v + return [math.sqrt(self.var[i]) for i in self.bands] Global = Stat # compatibility diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index b8a5e7a59..5add65f45 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -233,9 +233,7 @@ def SOF(self, marker): # fixup icc profile self.icclist.sort() # sort by sequence number if self.icclist[0][13] == len(self.icclist): - profile = [] - for p in self.icclist: - profile.append(p[14:]) + profile = [p[14:] for p in self.icclist] icc_profile = b"".join(profile) else: icc_profile = None # wrong number of fragments diff --git a/src/PIL/MicImagePlugin.py b/src/PIL/MicImagePlugin.py index e4154902f..9300d3545 100644 --- a/src/PIL/MicImagePlugin.py +++ b/src/PIL/MicImagePlugin.py @@ -51,10 +51,11 @@ class MicImageFile(TiffImagePlugin.TiffImageFile): # find ACI subfiles with Image members (maybe not the # best way to identify MIC files, but what the... ;-) - self.images = [] - for path in self.ole.listdir(): - if path[1:] and path[0][-4:] == ".ACI" and path[1] == "Image": - self.images.append(path) + self.images = [ + path + for path in self.ole.listdir() + if path[1:] and path[0][-4:] == ".ACI" and path[1] == "Image" + ] # if we didn't find any images, this is probably not # an MIC file. diff --git a/src/PIL/PcfFontFile.py b/src/PIL/PcfFontFile.py index 8db5822fe..1f5727eca 100644 --- a/src/PIL/PcfFontFile.py +++ b/src/PIL/PcfFontFile.py @@ -129,9 +129,8 @@ class PcfFontFile(FontFile.FontFile): nprops = i32(fp.read(4)) # read property description - p = [] - for i in range(nprops): - p.append((i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4)))) + p = [(i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4))) for i in range(nprops)] + if nprops & 3: fp.seek(4 - (nprops & 3), io.SEEK_CUR) # pad @@ -186,8 +185,6 @@ class PcfFontFile(FontFile.FontFile): # # bitmap data - bitmaps = [] - fp, format, i16, i32 = self._getformat(PCF_BITMAPS) nbitmaps = i32(fp.read(4)) @@ -196,13 +193,9 @@ class PcfFontFile(FontFile.FontFile): msg = "Wrong number of bitmaps" raise OSError(msg) - offsets = [] - for i in range(nbitmaps): - offsets.append(i32(fp.read(4))) + offsets = [i32(fp.read(4)) for _ in range(nbitmaps)] - bitmap_sizes = [] - for i in range(4): - bitmap_sizes.append(i32(fp.read(4))) + bitmap_sizes = [i32(fp.read(4)) for _ in range(4)] # byteorder = format & 4 # non-zero => MSB bitorder = format & 8 # non-zero => MSB @@ -218,6 +211,7 @@ class PcfFontFile(FontFile.FontFile): if bitorder: mode = "1" + bitmaps = [] for i in range(nbitmaps): xsize, ysize = metrics[i][:2] b, e = offsets[i : i + 2] diff --git a/src/PIL/SpiderImagePlugin.py b/src/PIL/SpiderImagePlugin.py index 408b982b5..9c06766ff 100644 --- a/src/PIL/SpiderImagePlugin.py +++ b/src/PIL/SpiderImagePlugin.py @@ -238,9 +238,7 @@ def makeSpiderHeader(im): if nvalues < 23: return [] - hdr = [] - for i in range(nvalues): - hdr.append(0.0) + hdr = [0.0 for _ in range(nvalues)] # NB these are Fortran indices hdr[1] = 1.0 # nslice (=1 for an image) From 2b734a33c765d6a9f12c64b534231c8c39761275 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 4 Dec 2023 08:32:39 +1100 Subject: [PATCH 10/20] Updated lcms2 to 2.16 --- .github/workflows/wheels-dependencies.sh | 2 +- docs/installation.rst | 2 +- winbuild/build_prepare.py | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 2605664eb..3ec314873 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -22,7 +22,7 @@ JPEGTURBO_VERSION=3.0.1 OPENJPEG_VERSION=2.5.0 XZ_VERSION=5.4.5 TIFF_VERSION=4.6.0 -LCMS2_VERSION=2.15 +LCMS2_VERSION=2.16 if [[ -n "$IS_MACOS" ]]; then GIFLIB_VERSION=5.1.4 else diff --git a/docs/installation.rst b/docs/installation.rst index aa78a7a00..fbcfbb907 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -175,7 +175,7 @@ Many of Pillow's features require external libraries: * **littlecms** provides color management * Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and - above uses liblcms2. Tested with **1.19** and **2.7-2.15**. + above uses liblcms2. Tested with **1.19** and **2.7-2.16**. * **libwebp** provides the WebP format. diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index c5c9441d4..f7e145fb9 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -279,10 +279,10 @@ DEPS = { "libs": [r"objs\{msbuild_arch}\Release Static\freetype.lib"], }, "lcms2": { - "url": SF_PROJECTS + "/lcms/files/lcms/2.15/lcms2-2.15.tar.gz/download", - "filename": "lcms2-2.15.tar.gz", - "dir": "lcms2-2.15", - "license": "COPYING", + "url": SF_PROJECTS + "/lcms/files/lcms/2.16/lcms2-2.16.tar.gz/download", + "filename": "lcms2-2.16.tar.gz", + "dir": "lcms2-2.16", + "license": "LICENSE", "patch": { r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": { # default is /MD for x86 and /MT for x64, we need /MD always From d7fa0b9d966a14b81c6bcc63ca94668465c0d9e3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 4 Dec 2023 22:20:22 +1100 Subject: [PATCH 11/20] Update CHANGES.rst [ci skip] --- CHANGES.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 5f58a2cad..d1fa91bfc 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,10 +5,13 @@ Changelog (Pillow) 10.2.0 (unreleased) ------------------- +- Correct PDF palette size when saving #7555 + [radarhere] + - Fixed closing file pointer with olefile 0.47 #7594 [radarhere] -- Raise ValueError when TrueType font size is not greater than zero #7584 +- Raise ValueError when TrueType font size is not greater than zero #7584, #7587 [akx, radarhere] - If absent, do not try to close fp when closing image #7557 From dafaa1c512208efcaf8305349b3b7da8219cfbb1 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Mon, 4 Dec 2023 06:56:24 -0600 Subject: [PATCH 12/20] Document JPEG streamtype option streamtype=1 is new in 10.2.0; the other values have existed since Git pre-history. --- docs/handbook/image-file-formats.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 38c00f870..23da312a6 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -521,6 +521,19 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: .. versionadded:: 2.5.0 +**streamtype** + Allows storing images without quantization and Huffman tables, or with + these tables but without image data. This is useful for container formats + or network protocols that handle tables separately and share them between + images. + + * ``0`` (default): interchange datastream, with tables and image data + * ``1``: abbreviated table specification (tables-only) datastream + + .. versionadded:: 10.2.0 + + * ``2``: abbreviated image (image-only) datastream + **comment** A comment about the image. From e92c07f402bc31d187c06e02619133fdc4c60fed Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 4 Dec 2023 16:34:39 +0200 Subject: [PATCH 13/20] Apply suggestions from code review Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> Co-authored-by: Alexander Karpinsky --- src/PIL/ImageOps.py | 2 +- src/PIL/PcfFontFile.py | 2 +- src/PIL/SpiderImagePlugin.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index f316a307c..f183c8f27 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -557,7 +557,7 @@ def invert(image): :param image: The image to invert. :return: An image. """ - lut = [255 - i for i in range(256)] + lut = list(range(255, -1, -1)) return image.point(lut) if image.mode == "1" else _lut(image, lut) diff --git a/src/PIL/PcfFontFile.py b/src/PIL/PcfFontFile.py index 1f5727eca..8b0014f3a 100644 --- a/src/PIL/PcfFontFile.py +++ b/src/PIL/PcfFontFile.py @@ -129,7 +129,7 @@ class PcfFontFile(FontFile.FontFile): nprops = i32(fp.read(4)) # read property description - p = [(i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4))) for i in range(nprops)] + p = [(i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4))) for _ in range(nprops)] if nprops & 3: fp.seek(4 - (nprops & 3), io.SEEK_CUR) # pad diff --git a/src/PIL/SpiderImagePlugin.py b/src/PIL/SpiderImagePlugin.py index 9c06766ff..14cad8f9a 100644 --- a/src/PIL/SpiderImagePlugin.py +++ b/src/PIL/SpiderImagePlugin.py @@ -238,7 +238,7 @@ def makeSpiderHeader(im): if nvalues < 23: return [] - hdr = [0.0 for _ in range(nvalues)] + hdr = [0.0] * nvalues # NB these are Fortran indices hdr[1] = 1.0 # nslice (=1 for an image) From bf9709bc482312fa55f1f3a95980a1a9aba58c0f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 17:18:50 +0000 Subject: [PATCH 14/20] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.4 → v0.1.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.4...v0.1.6) - [github.com/psf/black-pre-commit-mirror: 23.10.1 → 23.11.0](https://github.com/psf/black-pre-commit-mirror/compare/23.10.1...23.11.0) - [github.com/sphinx-contrib/sphinx-lint: v0.8.1 → v0.9.0](https://github.com/sphinx-contrib/sphinx-lint/compare/v0.8.1...v0.9.0) - [github.com/tox-dev/pyproject-fmt: 1.4.1 → 1.5.3](https://github.com/tox-dev/pyproject-fmt/compare/1.4.1...1.5.3) --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8b2dc06ae..f6b8c349c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,12 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.4 + rev: v0.1.6 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/psf/black-pre-commit-mirror - rev: 23.10.1 + rev: 23.11.0 hooks: - id: black @@ -42,12 +42,12 @@ repos: exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/ - repo: https://github.com/sphinx-contrib/sphinx-lint - rev: v0.8.1 + rev: v0.9.0 hooks: - id: sphinx-lint - repo: https://github.com/tox-dev/pyproject-fmt - rev: 1.4.1 + rev: 1.5.3 hooks: - id: pyproject-fmt From 8c5d5c7e6b597cef2b5a39417ddc457f91015f9f Mon Sep 17 00:00:00 2001 From: Sam Bourne Date: Mon, 4 Dec 2023 09:54:10 -0800 Subject: [PATCH 15/20] Add support for BC4 dds files --- Tests/images/bc4_unorm.dds | Bin 0 -> 2892 bytes Tests/test_file_dds.py | 12 ++++++++++++ src/PIL/DdsImagePlugin.py | 8 +++++++- 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 Tests/images/bc4_unorm.dds diff --git a/Tests/images/bc4_unorm.dds b/Tests/images/bc4_unorm.dds new file mode 100644 index 0000000000000000000000000000000000000000..13da711bd13d456406188b445a9668c40fc8604e GIT binary patch literal 2892 zcmc(gZA@EL7{?E7DSct|);cnZYs~zUyX*C#EPkVe!y>w3I2X3C5 z-#P#Dod0u9@42@ILxrXZA(Tc*kRCI3BN0L=7-_Ngf4H95Us)K-*Wg=#Z$q_Ci?Qqy zQTk=tUaau{Qez&_UY3|3r1y<}^SsQ#C_msiJ~`UEQ4!pGnN6jPnS(Ow!swPq<#R~y zta7t!Lw;8U(ijy#TCa8?x?dK+y1(-&ibbZa)~g5Zj}#vnz~hi7pT^^j;emuwNt?<+ zleA=?=b?1AG8eU0!ikjpAwxdr#QV zSr7Argy-^o{+ty2X48k_ez!y&EBxM5++)?;X9Lp(aIprhG*OVEHO`5wQm5;PbTM_a-l9MCM@SqBq!@pR8d$v9+L^9^ znAx*$$TAxe^MyXFXDwJKx{#o|Jc!5rE(PQ*ibl^g;_)aF-6y~S^m@rMfJqm%0ug+H z;0pvD2s&^DrIMzCFA#izpabo8VgpQSx&je=f#3@S9SAyb1*MXvf-ew!fow!}i^4H8 z*3n#y!_!vXyd8(>Sk41uIFgzsh^Ax0gIXM(wxA;e$IMvVPR2&e87z*w`QL3kp;Xf5 z|E(YOk$n%h657fMb%-!&(F9S4$bN0*gu|_bQ6KQ%{a3rbcZjU-!#?ud-7eB^ZzcVf zquu<6an#oky{wu%=iI`R>nCx*dvwoS<@)x+WPgV&gik+7_PIw-Sh9uan^dH)Ttg^E zukiA5z*d7RKKh3^)DN)|E&GOCzyVGF<4l%pPrAn>F;i|yj}2y>Do5c zaGraK3FB-*Mq@q%f;Ij zot9~}DlFZ+k>;Kot{=D0&4)hx{@i?SpPRolPR@D1mg4JhaP#}MZ&4B)OXK9+bMwPa zavm57A$L9_A>V2R zZ|3%yB|5nRbdS*?tP59^H3{**3}$?u(MbbOjuh1%Ze2fl%A0+Wv02h4k$FSD(qCHs e{bMbe%m`z8@8#@A>UYxbG}I{tI8L7?mHq^9EXXbZ literal 0 HcmV?d00001 diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index bb9af7967..85a3ae172 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -12,6 +12,7 @@ TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds" TEST_FILE_DXT5 = "Tests/images/dxt5-argb-8bbp-interpolatedalpha_MipMaps-1.dds" TEST_FILE_ATI1 = "Tests/images/ati1.dds" TEST_FILE_ATI2 = "Tests/images/ati2.dds" +TEST_FILE_DX10_BC4_UNORM = "Tests/images/bc4_unorm.dds" TEST_FILE_DX10_BC5_TYPELESS = "Tests/images/bc5_typeless.dds" TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds" TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds" @@ -82,6 +83,17 @@ def test_sanity_ati1(): assert_image_equal_tofile(im, TEST_FILE_ATI1.replace(".dds", ".png")) +def test_dx10_bc4(): + """Check DX10 BC4 images can be opened""" + + with Image.open(TEST_FILE_DX10_BC4_UNORM) as im: + im.load() + + assert im.format == "DDS" + assert im.mode == "L" + assert im.size == (64, 64) + + @pytest.mark.parametrize( "image_path", ( diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 54f358c7f..faf6a4e43 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -98,6 +98,8 @@ DXT5_FOURCC = 0x35545844 DXGI_FORMAT_R8G8B8A8_TYPELESS = 27 DXGI_FORMAT_R8G8B8A8_UNORM = 28 DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29 +DXGI_FORMAT_BC4_TYPELESS = 79 +DXGI_FORMAT_BC4_UNORM = 80 DXGI_FORMAT_BC5_TYPELESS = 82 DXGI_FORMAT_BC5_UNORM = 83 DXGI_FORMAT_BC5_SNORM = 84 @@ -190,7 +192,11 @@ class DdsImageFile(ImageFile.ImageFile): # ignoring flags which pertain to volume textures and cubemaps (dxgi_format,) = struct.unpack(" Date: Tue, 5 Dec 2023 07:18:07 +1100 Subject: [PATCH 16/20] Added expected test image --- Tests/images/bc4_unorm.png | Bin 0 -> 982 bytes Tests/test_file_dds.py | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 Tests/images/bc4_unorm.png diff --git a/Tests/images/bc4_unorm.png b/Tests/images/bc4_unorm.png new file mode 100644 index 0000000000000000000000000000000000000000..71d536c8453b16874c3ce777555fbbfb55667b60 GIT binary patch literal 982 zcmV;{11bE8P)Nkl{&&0I4LMzU^Z`fk+h$&Ba&uZxQX24%V_Mos1e zp@dH8U8jqbF~ejnGe*|<=z|YFXkw-*E^KpaI|}Vx|30)V+EX_Z%ofw{KbPFY_xHK) z_xDc@s1v134Z2(|q4&~NmUK9Hp64@pHrc!UtI*!w-fpwmQtK?*m&vn9p67W}J>Ueq zv;9|qCcob?|JETe0EcNbG3bBfR0%*c@S-%Op77IX;$ZzS05FwO7n=)S5(L5FU~K8> zrJqGn6a_)(UHpkHK9$Pa>~_0d6h-j|c7AVjJs>gLiyO&z-tcz=MPUBD;cj;TfCh~0 z@?MCH`K9jGns?XdZ@+1%5IlMX`X^%n=>xz7&-hRuzWf#d2fQqO{LzPA_ZQ!lYTidj zk3DOs;Bv8KkW$*V=VF$9*3lsdf{&2)VGzlu%}sDWWKfh6+0|e{yUH zV8;W}-1ttE0mx1@cDzRlu&O2CRAUj@sm8q3rT$qSoIDx`04yM-#*dsq1%OkHMSRl5 zK(SiIU7L@*|5t#$cqL@^14T)Rk9)rd04VX{4oh-sUlBknkW>eHpu~qe_82b_cp=Gg zEeGdsGmFL2))s#*v&s}oDdjls7K?{g^7%P4A%sv$Y5bYxTZROOLozC_?T6w47-k1$ zryBDBU;vcUBQjDGojYpY$z!{X6+-AlWj|7JFTgP9b*iyQ1b`V>K0PA4k`u2_v;fcn z9NCTY1^``n2z=Yw>F^A&1CQgJI?&?=U;-uv9>cj);jVPKmMDmH!T?x!_a&^KoqpBl z12`Zx)xQtE>cF8VupA9{xw`TIDgcm<_8XSy@i6u&$8lWzz}JOBOA8@{gf3^<%z>{6 zA%sYh)c4I*mIz(WGLGXoLjnK`(>{RB4Ed1iL=%8fSyJO+ucryXZ$MafoS7b#B~=Ds zVzraLv&4T^z2Ce=xXauf)%(p`#CrwQ`x^MMx05GL>t)lC?rt9U3rs-w9q-mSwO|4j7UoYJJ5$U@C z?tg^_U_1T?D Date: Tue, 5 Dec 2023 07:39:15 +1100 Subject: [PATCH 17/20] Added test BC4 TYPELESS image --- Tests/images/bc4_typeless.dds | Bin 0 -> 2892 bytes Tests/test_file_dds.py | 13 +++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 Tests/images/bc4_typeless.dds diff --git a/Tests/images/bc4_typeless.dds b/Tests/images/bc4_typeless.dds new file mode 100644 index 0000000000000000000000000000000000000000..27f87889f43d5b5054f3971c412a594dc4dc161e GIT binary patch literal 2892 zcmc(gZA@EL7{?E7DSct|);cnZYs~zUyX*C#EPkVe!y>w3I2X3C5 z-#P#Dod0u9@42@ILxrXZA(Tc*kRCI3BN0L=7-_Ngf4CmoS6LX#*Wg=#Z$q_Ci?Qqy zQTk=t<5=PUrN%s;BHCC>EKvTCeQCH&T3f0FOhSd>W58h6fT#C2cAP zP12IRo(Iy|%3RcbmXWae;_cSYyxH$Fi|9=6OA!VC934G4Ww<&u(N$dEBW7v0(BIx5 zA0Ol)r-u>GHHy2PA797g=M?#-ZIBmQ`@17HFKN*PF(jG~lN8h{vNybdLZB(Ca13048133PkV) zf-ewsAn3pqluDWkzCiE=f)2FXi48ER=?X;f1%fXSbRg)!6_iSv3cf(_1+o#@O$x`% zSVwa)4o_Qk^L8AjqdE7D;YezlAexQ}4`^|C+JcS@95Z8aI~f}>XRtW#E^s;L5oO26Lte?aI@6lazmFwFNk^LRC5I+4R+2C*S~DI_Otd2pJ4jY#b9C4K6*alroX%1@^--+rmNdj z!+Gu{CaCLpv%6D#i#z|Cnto08)4Cmj4O_o`=Ea(v1r1Izbc$H@{BQESUH1pGEf;Q8 zbXum>s<3qPMw)wWxPII|Hy`@&`*ZWTeQy5JI63G2T8gj3!Oid2zC}rJERB}z z$$4NTgxvXzgnS3WPT)WDN8UB_i*WRuCg-gA09ntbD)Rk>_1=!hpCx`f_s>!pICiMX z{B?-hTKa3i;H@04qhHYMu4=znJGVRMn7-d@#~FGa4S7J&Z{$*IzQ(0M zzmeN#mgwXP&^<G9!%Xy_d5es^3Y!(@>`r;5c=PRQeNTV8|^1 literal 0 HcmV?d00001 diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 337f541f6..e9fb04588 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -12,6 +12,7 @@ TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds" TEST_FILE_DXT5 = "Tests/images/dxt5-argb-8bbp-interpolatedalpha_MipMaps-1.dds" TEST_FILE_ATI1 = "Tests/images/ati1.dds" TEST_FILE_ATI2 = "Tests/images/ati2.dds" +TEST_FILE_DX10_BC4_TYPELESS = "Tests/images/bc4_typeless.dds" TEST_FILE_DX10_BC4_UNORM = "Tests/images/bc4_unorm.dds" TEST_FILE_DX10_BC5_TYPELESS = "Tests/images/bc5_typeless.dds" TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds" @@ -83,10 +84,18 @@ def test_sanity_ati1(): assert_image_equal_tofile(im, TEST_FILE_ATI1.replace(".dds", ".png")) -def test_dx10_bc4(): +@pytest.mark.parametrize( + "image_path", + ( + TEST_FILE_DX10_BC4_UNORM, + # hexeditted to be typeless + TEST_FILE_DX10_BC4_TYPELESS, + ), +) +def test_dx10_bc4(image_path): """Check DX10 BC4 images can be opened""" - with Image.open(TEST_FILE_DX10_BC4_UNORM) as im: + with Image.open(image_path) as im: im.load() assert im.format == "DDS" From cd2c3344d7492b54d1621dd158d80e748c222c3c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 5 Dec 2023 08:01:24 +1100 Subject: [PATCH 18/20] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index d1fa91bfc..db54ea279 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 10.2.0 (unreleased) ------------------- +- Optimized ImageStat.Stat.count #7599 + [florath] + - Correct PDF palette size when saving #7555 [radarhere] From f7c3f2a447e2dbdc1589104a7a39d698c5157d3f Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 5 Dec 2023 00:03:38 +0200 Subject: [PATCH 19/20] Use underscore as throwaway variable Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/PIL/ImagePalette.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index f33635a30..f9295e299 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -220,7 +220,7 @@ def negative(mode="RGB"): def random(mode="RGB"): from random import randint - palette = [randint(0, 255) for i in range(256 * len(mode))] + palette = [randint(0, 255) for _ in range(256 * len(mode))] return ImagePalette(mode, palette) From 584fdde178fa6d6ed3dde8c0d4154e0014cb9e8f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 5 Dec 2023 17:25:43 +1100 Subject: [PATCH 20/20] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index db54ea279..b378571c3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 10.2.0 (unreleased) ------------------- +- Added support for reading DX10 BC4 DDS images #7603 + [sambvfx, radarhere] + - Optimized ImageStat.Stat.count #7599 [florath]