From ae6520ccd638045d6b627264c5c85e004fc7adb5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 14 Sep 2022 00:05:23 +1000 Subject: [PATCH 01/11] Fixed pasting an L frame onto an RGB(A) GIF --- Tests/images/no_palette_after_rgb.gif | Bin 0 -> 101 bytes Tests/test_file_gif.py | 9 +++++++++ src/PIL/GifImagePlugin.py | 15 ++++++--------- 3 files changed, 15 insertions(+), 9 deletions(-) create mode 100644 Tests/images/no_palette_after_rgb.gif diff --git a/Tests/images/no_palette_after_rgb.gif b/Tests/images/no_palette_after_rgb.gif new file mode 100644 index 0000000000000000000000000000000000000000..8704c464cc4c794e9b25002569b6381c99026148 GIT binary patch literal 101 wcmZ?wbhEHbWMlwA2F0H&3}1k>4g(N?L>d_xfm|ryU}0cnVZl_yKuE$G05SRrZ2$lO literal 0 HcmV?d00001 diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 68cb8a36e..887b6b018 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -83,6 +83,15 @@ def test_l_mode_transparency(): assert im.load()[0, 0] == 128 +def test_l_mode_after_rgb(): + with Image.open("Tests/images/no_palette_after_rgb.gif") as im: + im.seek(1) + assert im.mode == "RGB" + + im.seek(2) + assert im.mode == "RGB" + + def test_strategy(): with Image.open("Tests/images/chi.gif") as im: expected_zero = im.convert("RGB") diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 2e11df54c..1b40b9ad7 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -432,16 +432,13 @@ class GifImageFile(ImageFile.ImageFile): self.mode = "RGB" self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG) return - if self.mode == "P" and self._prev_im: - if self._frame_transparency is not None: - self.im.putpalettealpha(self._frame_transparency, 0) - frame_im = self.im.convert("RGBA") - else: - frame_im = self.im.convert("RGB") + if not self._prev_im: + return + if self._frame_transparency is not None: + self.im.putpalettealpha(self._frame_transparency, 0) + frame_im = self.im.convert("RGBA") else: - if not self._prev_im: - return - frame_im = self.im + frame_im = self.im.convert("RGB") frame_im = self._crop(frame_im, self.dispose_extent) self.im = self._prev_im From b2b3b62be7248043da7716c0ea5e6b56f58e6c90 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 23 Sep 2022 20:06:08 +1000 Subject: [PATCH 02/11] Consider all frames when selecting mode for PNG save_all --- Tests/test_file_apng.py | 10 ++++++++++ src/PIL/PngImagePlugin.py | 41 ++++++++++++++++++++++++++++----------- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index 0ff05f608..bc9b91619 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -648,6 +648,16 @@ def test_seek_after_close(): im.seek(0) +@pytest.mark.parametrize("mode", ("RGBA", "RGB", "P")) +def test_different_modes_in_later_frames(mode, tmp_path): + test_file = str(tmp_path / "temp.png") + + im = Image.new("L", (1, 1)) + im.save(test_file, save_all=True, append_images=[Image.new(mode, (1, 1))]) + with Image.open(test_file) as reloaded: + assert reloaded.mode == mode + + def test_constants_deprecation(): for enum, prefix in { PngImagePlugin.Disposal: "APNG_DISPOSE_", diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 442c65e6f..181002422 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1089,28 +1089,28 @@ class _fdat: self.seq_num += 1 -def _write_multiple_frames(im, fp, chunk, rawmode): - default_image = im.encoderinfo.get("default_image", im.info.get("default_image")) +def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images): duration = im.encoderinfo.get("duration", im.info.get("duration", 0)) loop = im.encoderinfo.get("loop", im.info.get("loop", 0)) disposal = im.encoderinfo.get("disposal", im.info.get("disposal", Disposal.OP_NONE)) blend = im.encoderinfo.get("blend", im.info.get("blend", Blend.OP_SOURCE)) if default_image: - chain = itertools.chain(im.encoderinfo.get("append_images", [])) + chain = itertools.chain(append_images) else: - chain = itertools.chain([im], im.encoderinfo.get("append_images", [])) + chain = itertools.chain([im], append_images) im_frames = [] frame_count = 0 for im_seq in chain: for im_frame in ImageSequence.Iterator(im_seq): - im_frame = im_frame.copy() - if im_frame.mode != im.mode: - if im.mode == "P": - im_frame = im_frame.convert(im.mode, palette=im.palette) + if im_frame.mode == rawmode: + im_frame = im_frame.copy() + else: + if rawmode == "P": + im_frame = im_frame.convert(rawmode, palette=im.palette) else: - im_frame = im_frame.convert(im.mode) + im_frame = im_frame.convert(rawmode) encoderinfo = im.encoderinfo.copy() if isinstance(duration, (list, tuple)): encoderinfo["duration"] = duration[frame_count] @@ -1221,7 +1221,26 @@ def _save_all(im, fp, filename): def _save(im, fp, filename, chunk=putchunk, save_all=False): # save an image to disk (called by the save method) - mode = im.mode + if save_all: + default_image = im.encoderinfo.get( + "default_image", im.info.get("default_image") + ) + modes = set() + append_images = im.encoderinfo.get("append_images", []) + if default_image: + chain = itertools.chain(append_images) + else: + chain = itertools.chain([im], append_images) + for im_seq in chain: + for im_frame in ImageSequence.Iterator(im_seq): + modes.add(im_frame.mode) + for mode in ("RGBA", "RGB", "P"): + if mode in modes: + break + else: + mode = modes.pop() + else: + mode = im.mode if mode == "P": @@ -1373,7 +1392,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): chunk(fp, b"eXIf", exif) if save_all: - _write_multiple_frames(im, fp, chunk, rawmode) + _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) else: ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)]) From e6ffbfd8df075b805550b0d3115192882b1e6d6f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 6 Oct 2022 08:46:31 +1100 Subject: [PATCH 03/11] If palette is present but not needed, do not use global palette --- .../palette_not_needed_for_second_frame.gif | Bin 0 -> 29116 bytes Tests/test_file_gif.py | 6 ++++++ src/PIL/GifImagePlugin.py | 4 +++- 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 Tests/images/palette_not_needed_for_second_frame.gif diff --git a/Tests/images/palette_not_needed_for_second_frame.gif b/Tests/images/palette_not_needed_for_second_frame.gif new file mode 100644 index 0000000000000000000000000000000000000000..0617291d152975acfb63c18e742398bca34e7afd GIT binary patch literal 29116 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�Ad|KobbD>_=amU(R;%* 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 Date: Fri, 7 Oct 2022 09:48:56 +1100 Subject: [PATCH 04/11] Do not attempt normalization if image is already normal --- Tests/test_image_convert.py | 6 ++++++ src/PIL/Image.py | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 1a78f8b4c..e573a06a2 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -38,6 +38,12 @@ def test_sanity(): convert(im, output_mode) +def test_unsupported_conversion(): + im = hopper() + with pytest.raises(ValueError): + im.convert("INVALID") + + def test_default(): im = hopper("P") diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 6611ceb3c..cbf3ff865 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1036,7 +1036,10 @@ class Image: except ValueError: try: # normalize source image and try again - im = self.im.convert(getmodebase(self.mode)) + modebase = getmodebase(self.mode) + if modebase == self.mode: + raise + im = self.im.convert(modebase) im = im.convert(mode, dither) except KeyError as e: raise ValueError("illegal conversion") from e From fcd3eef594bb8eadce8333236440e2f9e9ccbe5f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 7 Oct 2022 22:33:45 +1100 Subject: [PATCH 05/11] Added conversion between RGB/RGBA/RGBX and LAB --- Tests/test_image_convert.py | 11 +++++++++++ src/PIL/Image.py | 13 +++++++++++++ 2 files changed, 24 insertions(+) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 1a78f8b4c..adbd83733 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -242,6 +242,17 @@ def test_p2pa_palette(): assert im_pa.getpalette() == im.getpalette() +@pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX")) +def test_rgb_lab(mode): + im = Image.new(mode, (1, 1)) + converted_im = im.convert("LAB") + assert converted_im.getpixel((0, 0)) == (0, 128, 128) + + im = Image.new("LAB", (1, 1), (255, 0, 0)) + converted_im = im.convert(mode) + assert converted_im.getpixel((0, 0))[:3] == (0, 255, 255) + + def test_matrix_illegal_conversion(): # Arrange im = hopper("CMYK") diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 6611ceb3c..5641879ac 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1027,6 +1027,19 @@ class Image: warnings.warn("Couldn't allocate palette entry for transparency") return new + if "LAB" in (self.mode, mode): + other_mode = mode if self.mode == "LAB" else self.mode + if other_mode in ("RGB", "RGBA", "RGBX"): + from . import ImageCms + + srgb = ImageCms.createProfile("sRGB") + lab = ImageCms.createProfile("LAB") + profiles = [lab, srgb] if self.mode == "LAB" else [srgb, lab] + transform = ImageCms.buildTransform( + profiles[0], profiles[1], self.mode, mode + ) + return transform.apply(self) + # colorspace conversion if dither is None: dither = Dither.FLOYDSTEINBERG From a4b257269e3abaea822405f18f64c3a4634dc61c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 13 Oct 2022 20:21:39 +1100 Subject: [PATCH 06/11] Image channel is used when converting PA with an RGBA palette --- src/PIL/Image.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index d5080a05c..6ba8e11be 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -880,7 +880,7 @@ class Image: and the palette can be represented without a palette. The current version supports all possible conversions between - "L", "RGB" and "CMYK." The ``matrix`` argument only supports "L" + "L", "RGB" and "CMYK". The ``matrix`` argument only supports "L" and "RGB". When translating a color image to greyscale (mode "L"), @@ -899,6 +899,9 @@ class Image: this passes the operation to :py:meth:`~PIL.Image.Image.quantize`, and ``dither`` and ``palette`` are ignored. + When converting from "PA", if an "RGBA" palette is present, the alpha + channel from the image will be used instead of the values from the palette. + :param mode: The requested mode. See: :ref:`concept-modes`. :param matrix: An optional conversion matrix. If given, this should be 4- or 12-tuple containing floating point values. From 340f247672ce4b44cae43b3e566ed3236dd1a506 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 19 Oct 2022 08:23:58 +1100 Subject: [PATCH 07/11] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index eeb733283..ffa0ad9ef 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Fixed seeking to an L frame in a GIF #6576 + [radarhere] + +- Consider all frames when selecting mode for PNG save_all #6610 + [radarhere] + - Don't reassign crc on ChunkStream close #6627 [wiredfool, radarhere] From b2e2559c83550f503dd06791bb60506af5ac9e24 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 19 Oct 2022 19:35:52 +1100 Subject: [PATCH 08/11] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index ffa0ad9ef..5a1095b61 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Added conversion between RGB/RGBA/RGBX and LAB #6647 + [radarhere] + +- Do not attempt normalization if mode is already normal #6644 + [radarhere] + - Fixed seeking to an L frame in a GIF #6576 [radarhere] From a91b1fe415fdf60310603e727f01bccde7613670 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 19 Oct 2022 19:56:11 +1100 Subject: [PATCH 09/11] Added release notes for #6449 [ci skip] --- docs/releasenotes/9.1.0.rst | 2 +- docs/releasenotes/9.3.0.rst | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/releasenotes/9.1.0.rst b/docs/releasenotes/9.1.0.rst index 57646e558..48ce6fef7 100644 --- a/docs/releasenotes/9.1.0.rst +++ b/docs/releasenotes/9.1.0.rst @@ -202,7 +202,7 @@ Pillow now builds binary wheels for musllinux, suitable for Linux distributions (rather than the glibc library used by manylinux wheels). See :pep:`656`. ImageShow temporary files on Unix -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When calling :py:meth:`~PIL.Image.Image.show` or using :py:mod:`~PIL.ImageShow`, a temporary file is created from the image. On Unix, Pillow will no longer delete these diff --git a/docs/releasenotes/9.3.0.rst b/docs/releasenotes/9.3.0.rst index 7109a09f2..d3be270cd 100644 --- a/docs/releasenotes/9.3.0.rst +++ b/docs/releasenotes/9.3.0.rst @@ -63,7 +63,7 @@ TODO Other Changes ============= -Added DDS ATI1 and ATI2 reading -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Added DDS ATI1, ATI2 and BC6H reading +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Support has been added to read the ATI1 and ATI2 formats of DDS images. +Support has been added to read the ATI1, ATI2 and BC6H formats of DDS images. From 1e4b0609c937e07e5a546f14836c24c28d7c3a05 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 19 Oct 2022 19:58:59 +1100 Subject: [PATCH 10/11] Added release notes for #6611 [ci skip] --- docs/releasenotes/9.3.0.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/releasenotes/9.3.0.rst b/docs/releasenotes/9.3.0.rst index d3be270cd..0b8696040 100644 --- a/docs/releasenotes/9.3.0.rst +++ b/docs/releasenotes/9.3.0.rst @@ -67,3 +67,9 @@ Added DDS ATI1, ATI2 and BC6H reading ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Support has been added to read the ATI1, ATI2 and BC6H formats of DDS images. + +Show all frames with ImageShow +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When calling :py:meth:`~PIL.Image.Image.show` or using +:py:mod:`~PIL.ImageShow`, all frames will now be shown. From b67806ac943b06be44fc1be31ac02c38453be224 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 20 Oct 2022 13:12:32 +1100 Subject: [PATCH 11/11] Updated harfbuzz to 5.3.1 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 39b9bcc2c..f4858b630 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -281,9 +281,9 @@ deps = { "libs": [r"imagequant.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/5.3.0.zip", - "filename": "harfbuzz-5.3.0.zip", - "dir": "harfbuzz-5.3.0", + "url": "https://github.com/harfbuzz/harfbuzz/archive/5.3.1.zip", + "filename": "harfbuzz-5.3.1.zip", + "dir": "harfbuzz-5.3.1", "build": [ cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_nmake(target="clean"),