From f4cd5e750217c8d0c0d5cb5378a49c651d5aa7d8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 3 Apr 2025 18:44:45 +1100 Subject: [PATCH 001/309] Assert image type --- Tests/test_file_avif.py | 9 +++++++++ Tests/test_file_fli.py | 2 ++ Tests/test_file_gif.py | 3 +++ Tests/test_file_jpeg.py | 26 +++++++++++++++++++++----- Tests/test_file_mpo.py | 8 ++++++++ Tests/test_file_png.py | 30 ++++++++++++++++++++++-------- Tests/test_file_webp_metadata.py | 2 ++ 7 files changed, 67 insertions(+), 13 deletions(-) diff --git a/Tests/test_file_avif.py b/Tests/test_file_avif.py index 392a4bbd5..069a48940 100644 --- a/Tests/test_file_avif.py +++ b/Tests/test_file_avif.py @@ -14,6 +14,7 @@ import pytest from PIL import ( AvifImagePlugin, + GifImagePlugin, Image, ImageDraw, ImageFile, @@ -240,6 +241,7 @@ class TestFileAvif: with Image.open("Tests/images/chi.gif") as im: im.save(temp_file) with Image.open(temp_file) as im: + assert isinstance(im, AvifImagePlugin.AvifImageFile) assert im.n_frames == 1 def test_invalid_file(self) -> None: @@ -595,10 +597,12 @@ class TestAvifAnimation: """ with Image.open(TEST_AVIF_FILE) as im: + assert isinstance(im, AvifImagePlugin.AvifImageFile) assert im.n_frames == 1 assert not im.is_animated with Image.open("Tests/images/avif/star.avifs") as im: + assert isinstance(im, AvifImagePlugin.AvifImageFile) assert im.n_frames == 5 assert im.is_animated @@ -609,11 +613,13 @@ class TestAvifAnimation: """ with Image.open("Tests/images/avif/star.gif") as original: + assert isinstance(original, GifImagePlugin.GifImageFile) assert original.n_frames > 1 temp_file = tmp_path / "temp.avif" original.save(temp_file, save_all=True) with Image.open(temp_file) as im: + assert isinstance(im, AvifImagePlugin.AvifImageFile) assert im.n_frames == original.n_frames # Compare first frame in P mode to frame from original GIF @@ -633,6 +639,7 @@ class TestAvifAnimation: def check(temp_file: Path) -> None: with Image.open(temp_file) as im: + assert isinstance(im, AvifImagePlugin.AvifImageFile) assert im.n_frames == 4 # Compare first frame to original @@ -705,6 +712,7 @@ class TestAvifAnimation: ) with Image.open(temp_file) as im: + assert isinstance(im, AvifImagePlugin.AvifImageFile) assert im.n_frames == 5 assert im.is_animated @@ -734,6 +742,7 @@ class TestAvifAnimation: ) with Image.open(temp_file) as im: + assert isinstance(im, AvifImagePlugin.AvifImageFile) assert im.n_frames == 5 assert im.is_animated diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index 81df1ab0b..ff80c92f7 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -43,6 +43,7 @@ def test_sanity() -> None: def test_prefix_chunk(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True) with Image.open(animated_test_file_with_prefix_chunk) as im: + assert isinstance(im, FliImagePlugin.FliImageFile) assert im.mode == "P" assert im.size == (320, 200) assert im.format == "FLI" @@ -50,6 +51,7 @@ def test_prefix_chunk(monkeypatch: pytest.MonkeyPatch) -> None: assert im.is_animated palette = im.getpalette() + assert palette is not None assert palette[3:6] == [255, 255, 255] assert palette[381:384] == [204, 204, 12] assert palette[765:] == [252, 0, 0] diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 20d58a9dd..3d07159f3 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -280,6 +280,7 @@ def test_roundtrip_save_all(tmp_path: Path) -> None: im.save(out, save_all=True) with Image.open(out) as reread: + assert isinstance(reread, GifImagePlugin.GifImageFile) assert reread.n_frames == 5 @@ -1357,8 +1358,10 @@ def test_palette_save_all_P(tmp_path: Path) -> None: with Image.open(out) as im: # Assert that the frames are correct, and each frame has the same palette + assert isinstance(im, GifImagePlugin.GifImageFile) assert_image_equal(im.convert("RGB"), frames[0].convert("RGB")) assert im.palette is not None + assert im.global_palette is not None assert im.palette.palette == im.global_palette.palette im.seek(1) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 79f0ec1a8..9164882d3 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -334,8 +334,10 @@ class TestFileJpeg: # Reading with Image.open("Tests/images/exif_gps.jpg") as im: - exif = im._getexif() - assert exif[gps_index] == expected_exif_gps + assert isinstance(im, JpegImagePlugin.JpegImageFile) + exif_data = im._getexif() + assert exif_data is not None + assert exif_data[gps_index] == expected_exif_gps # Writing f = tmp_path / "temp.jpg" @@ -344,8 +346,10 @@ class TestFileJpeg: hopper().save(f, exif=exif) with Image.open(f) as reloaded: - exif = reloaded._getexif() - assert exif[gps_index] == expected_exif_gps + assert isinstance(reloaded, JpegImagePlugin.JpegImageFile) + exif_data = reloaded._getexif() + assert exif_data is not None + assert exif_data[gps_index] == expected_exif_gps def test_empty_exif_gps(self) -> None: with Image.open("Tests/images/empty_gps_ifd.jpg") as im: @@ -372,6 +376,7 @@ class TestFileJpeg: exifs = [] for i in range(2): with Image.open("Tests/images/exif-200dpcm.jpg") as im: + assert isinstance(im, JpegImagePlugin.JpegImageFile) exifs.append(im._getexif()) assert exifs[0] == exifs[1] @@ -405,13 +410,17 @@ class TestFileJpeg: } with Image.open("Tests/images/exif_gps.jpg") as im: + assert isinstance(im, JpegImagePlugin.JpegImageFile) exif = im._getexif() + assert exif is not None for tag, value in expected_exif.items(): assert value == exif[tag] def test_exif_gps_typeerror(self) -> None: with Image.open("Tests/images/exif_gps_typeerror.jpg") as im: + assert isinstance(im, JpegImagePlugin.JpegImageFile) + # Should not raise a TypeError im._getexif() @@ -491,7 +500,9 @@ class TestFileJpeg: def test_exif(self) -> None: with Image.open("Tests/images/pil_sample_rgb.jpg") as im: + assert isinstance(im, JpegImagePlugin.JpegImageFile) info = im._getexif() + assert info is not None assert info[305] == "Adobe Photoshop CS Macintosh" def test_get_child_images(self) -> None: @@ -676,11 +687,13 @@ class TestFileJpeg: def test_save_multiple_16bit_qtables(self) -> None: with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im: + assert isinstance(im, JpegImagePlugin.JpegImageFile) im2 = self.roundtrip(im, qtables="keep") assert im.quantization == im2.quantization def test_save_single_16bit_qtable(self) -> None: with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im: + assert isinstance(im, JpegImagePlugin.JpegImageFile) im2 = self.roundtrip(im, qtables={0: im.quantization[0]}) assert len(im2.quantization) == 1 assert im2.quantization[0] == im.quantization[0] @@ -889,7 +902,10 @@ class TestFileJpeg: # in contrast to normal 8 with Image.open("Tests/images/exif-ifd-offset.jpg") as im: # Act / Assert - assert im._getexif()[306] == "2017:03:13 23:03:09" + assert isinstance(im, JpegImagePlugin.JpegImageFile) + exif = im._getexif() + assert exif is not None + assert exif[306] == "2017:03:13 23:03:09" def test_multiple_exif(self) -> None: with Image.open("Tests/images/multiple_exif.jpg") as im: diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 73838ef44..c5d3142e8 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -120,9 +120,11 @@ def test_ignore_frame_size() -> None: # Ignore the different size of the second frame # since this is not a "Large Thumbnail" image with Image.open("Tests/images/ignore_frame_size.mpo") as im: + assert isinstance(im, MpoImagePlugin.MpoImageFile) assert im.size == (64, 64) im.seek(1) + assert im.mpinfo is not None assert ( im.mpinfo[0xB002][1]["Attribute"]["MPType"] == "Multi-Frame Image: (Disparity)" @@ -155,7 +157,9 @@ def test_reload_exif_after_seek() -> None: @pytest.mark.parametrize("test_file", test_files) def test_mp(test_file: str) -> None: with Image.open(test_file) as im: + assert isinstance(im, MpoImagePlugin.MpoImageFile) mpinfo = im._getmp() + assert mpinfo is not None assert mpinfo[45056] == b"0100" assert mpinfo[45057] == 2 @@ -164,7 +168,9 @@ def test_mp_offset() -> None: # This image has been manually hexedited to have an IFD offset of 10 # in APP2 data, in contrast to normal 8 with Image.open("Tests/images/sugarshack_ifd_offset.mpo") as im: + assert isinstance(im, MpoImagePlugin.MpoImageFile) mpinfo = im._getmp() + assert mpinfo is not None assert mpinfo[45056] == b"0100" assert mpinfo[45057] == 2 @@ -180,7 +186,9 @@ def test_mp_no_data() -> None: @pytest.mark.parametrize("test_file", test_files) def test_mp_attribute(test_file: str) -> None: with Image.open(test_file) as im: + assert isinstance(im, MpoImagePlugin.MpoImageFile) mpinfo = im._getmp() + assert mpinfo is not None for frame_number, mpentry in enumerate(mpinfo[0xB002]): mpattr = mpentry["Attribute"] if frame_number: diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 0f0886ab8..13e1c80f4 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -671,6 +671,9 @@ class TestFilePng: im.save(out, bits=4, save_all=save_all) with Image.open(out) as reloaded: + assert isinstance(reloaded, PngImagePlugin.PngImageFile) + assert reloaded.png is not None + assert reloaded.png.im_palette is not None assert len(reloaded.png.im_palette[1]) == 48 def test_plte_length(self, tmp_path: Path) -> None: @@ -681,6 +684,9 @@ class TestFilePng: im.save(out) with Image.open(out) as reloaded: + assert isinstance(reloaded, PngImagePlugin.PngImageFile) + assert reloaded.png is not None + assert reloaded.png.im_palette is not None assert len(reloaded.png.im_palette[1]) == 3 def test_getxmp(self) -> None: @@ -702,13 +708,17 @@ class TestFilePng: def test_exif(self) -> None: # With an EXIF chunk with Image.open("Tests/images/exif.png") as im: - exif = im._getexif() - assert exif[274] == 1 + assert isinstance(im, PngImagePlugin.PngImageFile) + exif_data = im._getexif() + assert exif_data is not None + assert exif_data[274] == 1 # With an ImageMagick zTXt chunk with Image.open("Tests/images/exif_imagemagick.png") as im: - exif = im._getexif() - assert exif[274] == 1 + assert isinstance(im, PngImagePlugin.PngImageFile) + exif_data = im._getexif() + assert exif_data is not None + assert exif_data[274] == 1 # Assert that info still can be extracted # when the image is no longer a PngImageFile instance @@ -717,8 +727,10 @@ class TestFilePng: # With a tEXt chunk with Image.open("Tests/images/exif_text.png") as im: - exif = im._getexif() - assert exif[274] == 1 + assert isinstance(im, PngImagePlugin.PngImageFile) + exif_data = im._getexif() + assert exif_data is not None + assert exif_data[274] == 1 # With XMP tags with Image.open("Tests/images/xmp_tags_orientation.png") as im: @@ -740,8 +752,10 @@ class TestFilePng: im.save(test_file, exif=im.getexif()) with Image.open(test_file) as reloaded: - exif = reloaded._getexif() - assert exif[274] == 1 + assert isinstance(reloaded, PngImagePlugin.PngImageFile) + exif_data = reloaded._getexif() + assert exif_data is not None + assert exif_data[274] == 1 @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" diff --git a/Tests/test_file_webp_metadata.py b/Tests/test_file_webp_metadata.py index 7543d22da..3de412b83 100644 --- a/Tests/test_file_webp_metadata.py +++ b/Tests/test_file_webp_metadata.py @@ -22,11 +22,13 @@ except ImportError: def test_read_exif_metadata() -> None: file_path = "Tests/images/flower.webp" with Image.open(file_path) as image: + assert isinstance(image, WebPImagePlugin.WebPImageFile) assert image.format == "WEBP" exif_data = image.info.get("exif", None) assert exif_data exif = image._getexif() + assert exif is not None # Camera make assert exif[271] == "Canon" From 79f834ef6500774f63b49e19f5c150d9ed6f2681 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 11 Apr 2025 22:26:42 +1000 Subject: [PATCH 002/309] If pasting an image onto itself at a lower position, copy from bottom --- Tests/test_image_paste.py | 12 ++++++++++++ src/libImaging/Paste.c | 10 ++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py index 2cff6c893..5fee81729 100644 --- a/Tests/test_image_paste.py +++ b/Tests/test_image_paste.py @@ -124,6 +124,18 @@ class TestImagingPaste: im = im.crop((12, 23, im2.width + 12, im2.height + 23)) assert_image_equal(im, im2) + @pytest.mark.parametrize("y", [10, -10]) + def test_image_self(self, y: int) -> None: + im = self.gradient_RGB + + im_self = im.copy() + im_self.paste(im_self, (0, y)) + + im_copy = im.copy() + im_copy.paste(im_copy.copy(), (0, y)) + + assert_image_equal(im_self, im_copy) + @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) def test_image_mask_1(self, mode: str) -> None: im = Image.new(mode, (200, 200), "white") diff --git a/src/libImaging/Paste.c b/src/libImaging/Paste.c index 86085942a..e2ce00ea6 100644 --- a/src/libImaging/Paste.c +++ b/src/libImaging/Paste.c @@ -44,8 +44,14 @@ paste( xsize *= pixelsize; - for (y = 0; y < ysize; y++) { - memcpy(imOut->image[y + dy] + dx, imIn->image[y + sy] + sx, xsize); + if (imOut == imIn && dy > sy) { + for (y = ysize - 1; y >= 0; y--) { + memcpy(imOut->image[y + dy] + dx, imIn->image[y + sy] + sx, xsize); + } + } else { + for (y = 0; y < ysize; y++) { + memcpy(imOut->image[y + dy] + dx, imIn->image[y + sy] + sx, xsize); + } } } From 81fa4e18c7c11c115c3bed5200b4129f0097b00f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 12 Apr 2025 08:19:18 +1000 Subject: [PATCH 003/309] If pasting image to self at lower position with mask, copy from bottom --- Tests/test_image_paste.py | 11 +++++--- src/libImaging/Paste.c | 53 +++++++++++++++++++++++---------------- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py index 5fee81729..37e4df103 100644 --- a/Tests/test_image_paste.py +++ b/Tests/test_image_paste.py @@ -125,14 +125,17 @@ class TestImagingPaste: assert_image_equal(im, im2) @pytest.mark.parametrize("y", [10, -10]) - def test_image_self(self, y: int) -> None: - im = self.gradient_RGB + @pytest.mark.parametrize("mode", ["L", "RGB"]) + @pytest.mark.parametrize("mask_mode", ["", "1", "L", "LA", "RGBa"]) + def test_image_self(self, y: int, mode: str, mask_mode: str) -> None: + im = getattr(self, "gradient_" + mode) + mask = Image.new(mask_mode, im.size, 0xFFFFFFFF) if mask_mode else None im_self = im.copy() - im_self.paste(im_self, (0, y)) + im_self.paste(im_self, (0, y), mask) im_copy = im.copy() - im_copy.paste(im_copy.copy(), (0, y)) + im_copy.paste(im_copy.copy(), (0, y), mask) assert_image_equal(im_self, im_copy) diff --git a/src/libImaging/Paste.c b/src/libImaging/Paste.c index e2ce00ea6..9942f9c1c 100644 --- a/src/libImaging/Paste.c +++ b/src/libImaging/Paste.c @@ -23,6 +23,18 @@ #include "Imaging.h" +#define PREPARE_PASTE_LOOP() \ + int y, y_end, offset; \ + if (imOut == imIn && dy > sy) { \ + y = ysize - 1; \ + y_end = -1; \ + offset = -1; \ + } else { \ + y = 0; \ + y_end = ysize; \ + offset = 1; \ + } + static inline void paste( Imaging imOut, @@ -37,21 +49,14 @@ paste( ) { /* paste opaque region */ - int y; - dx *= pixelsize; sx *= pixelsize; xsize *= pixelsize; - if (imOut == imIn && dy > sy) { - for (y = ysize - 1; y >= 0; y--) { - memcpy(imOut->image[y + dy] + dx, imIn->image[y + sy] + sx, xsize); - } - } else { - for (y = 0; y < ysize; y++) { - memcpy(imOut->image[y + dy] + dx, imIn->image[y + sy] + sx, xsize); - } + PREPARE_PASTE_LOOP(); + for (; y != y_end; y += offset) { + memcpy(imOut->image[y + dy] + dx, imIn->image[y + sy] + sx, xsize); } } @@ -70,12 +75,13 @@ paste_mask_1( ) { /* paste with mode "1" mask */ - int x, y; + int x; + PREPARE_PASTE_LOOP(); if (imOut->image8) { int in_i16 = strncmp(imIn->mode, "I;16", 4) == 0; int out_i16 = strncmp(imOut->mode, "I;16", 4) == 0; - for (y = 0; y < ysize; y++) { + for (; y != y_end; y += offset) { UINT8 *out = imOut->image8[y + dy] + dx; if (out_i16) { out += dx; @@ -103,7 +109,7 @@ paste_mask_1( } } else { - for (y = 0; y < ysize; y++) { + for (; y != y_end; y += offset) { INT32 *out = imOut->image32[y + dy] + dx; INT32 *in = imIn->image32[y + sy] + sx; UINT8 *mask = imMask->image8[y + sy] + sx; @@ -132,11 +138,12 @@ paste_mask_L( ) { /* paste with mode "L" matte */ - int x, y; + int x; unsigned int tmp1; + PREPARE_PASTE_LOOP(); if (imOut->image8) { - for (y = 0; y < ysize; y++) { + for (; y != y_end; y += offset) { UINT8 *out = imOut->image8[y + dy] + dx; UINT8 *in = imIn->image8[y + sy] + sx; UINT8 *mask = imMask->image8[y + sy] + sx; @@ -147,7 +154,7 @@ paste_mask_L( } } else { - for (y = 0; y < ysize; y++) { + for (; y != y_end; y += offset) { UINT8 *out = (UINT8 *)(imOut->image32[y + dy] + dx); UINT8 *in = (UINT8 *)(imIn->image32[y + sy] + sx); UINT8 *mask = (UINT8 *)(imMask->image8[y + sy] + sx); @@ -180,11 +187,12 @@ paste_mask_RGBA( ) { /* paste with mode "RGBA" matte */ - int x, y; + int x; unsigned int tmp1; + PREPARE_PASTE_LOOP(); if (imOut->image8) { - for (y = 0; y < ysize; y++) { + for (; y != y_end; y += offset) { UINT8 *out = imOut->image8[y + dy] + dx; UINT8 *in = imIn->image8[y + sy] + sx; UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx * 4 + 3; @@ -195,7 +203,7 @@ paste_mask_RGBA( } } else { - for (y = 0; y < ysize; y++) { + for (; y != y_end; y += offset) { UINT8 *out = (UINT8 *)(imOut->image32[y + dy] + dx); UINT8 *in = (UINT8 *)(imIn->image32[y + sy] + sx); UINT8 *mask = (UINT8 *)(imMask->image32[y + sy] + sx); @@ -228,11 +236,12 @@ paste_mask_RGBa( ) { /* paste with mode "RGBa" matte */ - int x, y; + int x; unsigned int tmp1; + PREPARE_PASTE_LOOP(); if (imOut->image8) { - for (y = 0; y < ysize; y++) { + for (; y != y_end; y += offset) { UINT8 *out = imOut->image8[y + dy] + dx; UINT8 *in = imIn->image8[y + sy] + sx; UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx * 4 + 3; @@ -243,7 +252,7 @@ paste_mask_RGBa( } } else { - for (y = 0; y < ysize; y++) { + for (; y != y_end; y += offset) { UINT8 *out = (UINT8 *)(imOut->image32[y + dy] + dx); UINT8 *in = (UINT8 *)(imIn->image32[y + sy] + sx); UINT8 *mask = (UINT8 *)(imMask->image32[y + sy] + sx); From 349cc44fd47427a7cefbf5444dcd4010c8723360 Mon Sep 17 00:00:00 2001 From: Stefan <96178532+stefan6419846@users.noreply.github.com> Date: Wed, 7 May 2025 17:21:22 +0200 Subject: [PATCH 004/309] Add Apache-2.0 notice to IcoImagePlugin Closes #8946. --- src/PIL/IcoImagePlugin.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index 55c57f203..8ddb94b37 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -17,6 +17,20 @@ # . # https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki # +# Copyright 2008 Bryan Davis +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + # Icon format references: # * https://en.wikipedia.org/wiki/ICO_(file_format) # * https://msdn.microsoft.com/en-us/library/ms997538.aspx From a666057989efc669909e85754943f2734c5f2055 Mon Sep 17 00:00:00 2001 From: Stefan <96178532+stefan6419846@users.noreply.github.com> Date: Wed, 21 May 2025 15:40:16 +0200 Subject: [PATCH 005/309] HTTP -> HTTPS Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/PIL/IcoImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index 8ddb94b37..151eef2c9 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -23,7 +23,7 @@ # not use this file except in compliance with the License. You may obtain # a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, From 2059e060051acd2024360834da435a266d9dc665 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 30 May 2025 09:56:47 +0100 Subject: [PATCH 006/309] Add parallel compile from pybind11 --- pyproject.toml | 1 + setup.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 683ab24ef..ae4b70990 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,7 @@ [build-system] build-backend = "backend" requires = [ + "pybind11", "setuptools>=77", ] backend-path = [ diff --git a/setup.py b/setup.py index ab36c6b17..ec6b47b1c 100644 --- a/setup.py +++ b/setup.py @@ -18,9 +18,12 @@ import warnings from collections.abc import Iterator from typing import Any +from pybind11.setup_helpers import ParallelCompile from setuptools import Extension, setup from setuptools.command.build_ext import build_ext +ParallelCompile("MAX_CONCURRENCY", default=0).install() + def get_version() -> str: version_file = "src/PIL/_version.py" @@ -1048,12 +1051,12 @@ ext_modules = [ Extension("PIL._imagingmorph", ["src/_imagingmorph.c"]), ] - # parse configuration from _custom_build/backend.py while sys.argv[-1].startswith("--pillow-configuration="): _, key, value = sys.argv.pop().split("=", 2) configuration.setdefault(key, []).append(value) + try: setup( cmdclass={"build_ext": pil_build_ext}, From b931402046f840bedc09b3c2b0c4039ac28531dc Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sat, 31 May 2025 15:14:17 +0200 Subject: [PATCH 007/309] add pybind11 elsewhere so mypy can find it --- .ci/requirements-mypy.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.ci/requirements-mypy.txt b/.ci/requirements-mypy.txt index 86ac2e0b2..645605aa6 100644 --- a/.ci/requirements-mypy.txt +++ b/.ci/requirements-mypy.txt @@ -5,6 +5,7 @@ ipython numpy packaging pyarrow-stubs +pybind11 pytest sphinx types-atheris From 2316c930f9b2985d27894f043f5f2e4787543dca Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 19 Jun 2025 22:46:09 +1000 Subject: [PATCH 008/309] Removed default argument --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index ec6b47b1c..233811192 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ from pybind11.setup_helpers import ParallelCompile from setuptools import Extension, setup from setuptools.command.build_ext import build_ext -ParallelCompile("MAX_CONCURRENCY", default=0).install() +ParallelCompile("MAX_CONCURRENCY").install() def get_version() -> str: @@ -1051,12 +1051,12 @@ ext_modules = [ Extension("PIL._imagingmorph", ["src/_imagingmorph.c"]), ] + # parse configuration from _custom_build/backend.py while sys.argv[-1].startswith("--pillow-configuration="): _, key, value = sys.argv.pop().split("=", 2) configuration.setdefault(key, []).append(value) - try: setup( cmdclass={"build_ext": pil_build_ext}, From ecd264fffc680ab05da6a71ff4466c774185ab90 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 21 Jun 2025 22:22:46 +1000 Subject: [PATCH 009/309] Use "parallel" config setting and 4 as defaults --- setup.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/setup.py b/setup.py index 1134879be..6c2180ebd 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,15 @@ from pybind11.setup_helpers import ParallelCompile from setuptools import Extension, setup from setuptools.command.build_ext import build_ext -ParallelCompile("MAX_CONCURRENCY").install() +configuration: dict[str, list[str]] = {} + +# parse configuration from _custom_build/backend.py +while sys.argv[-1].startswith("--pillow-configuration="): + _, key, value = sys.argv.pop().split("=", 2) + configuration.setdefault(key, []).append(value) + +default = int(configuration.get("parallel", ["4"])[-1]) +ParallelCompile("MAX_CONCURRENCY", default).install() def get_version() -> str: @@ -30,9 +38,6 @@ def get_version() -> str: return f.read().split('"')[1] -configuration: dict[str, list[str]] = {} - - PILLOW_VERSION = get_version() AVIF_ROOT = None FREETYPE_ROOT = None @@ -1047,11 +1052,6 @@ ext_modules = [ ] -# parse configuration from _custom_build/backend.py -while sys.argv[-1].startswith("--pillow-configuration="): - _, key, value = sys.argv.pop().split("=", 2) - configuration.setdefault(key, []).append(value) - try: setup( cmdclass={"build_ext": pil_build_ext}, From 23ed906b622e25466981fa7c2b80f2a1da612661 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 25 Jun 2025 22:00:36 +1000 Subject: [PATCH 010/309] Removed default limit of 4 --- docs/installation/building-from-source.rst | 5 ++--- setup.py | 6 ++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/installation/building-from-source.rst b/docs/installation/building-from-source.rst index 8988a92ce..4c114a5e2 100644 --- a/docs/installation/building-from-source.rst +++ b/docs/installation/building-from-source.rst @@ -276,10 +276,9 @@ Build options * Config setting: ``-C parallel=n``. Can also be given with environment variable: ``MAX_CONCURRENCY=n``. Pillow can use - multiprocessing to build the extension. Setting ``-C parallel=n`` + multiprocessing to build the extensions. Setting ``-C parallel=n`` sets the number of CPUs to use to ``n``, or can disable parallel building by - using a setting of 1. By default, it uses 4 CPUs, or if 4 are not - available, as many as are present. + using a setting of 1. By default, it uses as many CPUs as are present. * Config settings: ``-C zlib=disable``, ``-C jpeg=disable``, ``-C tiff=disable``, ``-C freetype=disable``, ``-C raqm=disable``, diff --git a/setup.py b/setup.py index 6c2180ebd..aee1b04eb 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ while sys.argv[-1].startswith("--pillow-configuration="): _, key, value = sys.argv.pop().split("=", 2) configuration.setdefault(key, []).append(value) -default = int(configuration.get("parallel", ["4"])[-1]) +default = int(configuration.get("parallel", ["0"])[-1]) ParallelCompile("MAX_CONCURRENCY", default).install() @@ -394,9 +394,7 @@ class pil_build_ext(build_ext): cpu_count = os.cpu_count() if cpu_count is not None: try: - self.parallel = int( - os.environ.get("MAX_CONCURRENCY", min(4, cpu_count)) - ) + self.parallel = int(os.environ.get("MAX_CONCURRENCY", cpu_count)) except TypeError: pass for x in self.feature: From 49efe40f28ce0413ced31d8dcc0c707b0134d6bf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 30 Jun 2025 22:19:14 +1000 Subject: [PATCH 011/309] Escape period --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d5fd964f1..c5276723a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -48,7 +48,7 @@ repos: - id: end-of-file-fixer exclude: ^Tests/images/|\.patch$ - id: trailing-whitespace - exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/|\.patch$ + exclude: ^\.github/.*TEMPLATE|^Tests/(fonts|images)/|\.patch$ - repo: https://github.com/python-jsonschema/check-jsonschema rev: 0.33.1 From 5554e778bba52f2414d0151642a2906aed254ad2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 5 Jul 2025 13:18:48 +1000 Subject: [PATCH 012/309] Removed unnecessary checks --- src/libImaging/AlphaComposite.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/libImaging/AlphaComposite.c b/src/libImaging/AlphaComposite.c index 6d728f908..e14af0dea 100644 --- a/src/libImaging/AlphaComposite.c +++ b/src/libImaging/AlphaComposite.c @@ -25,13 +25,11 @@ ImagingAlphaComposite(Imaging imDst, Imaging imSrc) { int x, y; /* Check arguments */ - if (!imDst || !imSrc || strcmp(imDst->mode, "RGBA") || - imDst->type != IMAGING_TYPE_UINT8 || imDst->bands != 4) { + if (!imDst || !imSrc || strcmp(imDst->mode, "RGBA")) { return ImagingError_ModeError(); } - if (strcmp(imDst->mode, imSrc->mode) || imDst->type != imSrc->type || - imDst->bands != imSrc->bands || imDst->xsize != imSrc->xsize || + if (strcmp(imDst->mode, imSrc->mode) || imDst->xsize != imSrc->xsize || imDst->ysize != imSrc->ysize) { return ImagingError_Mismatch(); } From 3152da47355ed3f9b342dc94de8a2214798842c4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 5 Jul 2025 13:51:18 +1000 Subject: [PATCH 013/309] Allow alpha_composite to use LA images --- Tests/test_image.py | 31 +++++++++++++++++++++++++++++++ src/PIL/Image.py | 5 ++--- src/libImaging/AlphaComposite.c | 3 ++- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 6b8b6d42b..e4c25693a 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -398,6 +398,37 @@ class TestImage: assert img_colors is not None assert sorted(img_colors) == expected_colors + def test_alpha_composite_la(self) -> None: + # Arrange + expected_colors = sorted( + [ + (3300, (255, 255)), + (1156, (170, 192)), + (1122, (128, 255)), + (1089, (0, 0)), + (1122, (255, 128)), + (1122, (0, 128)), + (1089, (0, 255)), + ] + ) + + dst = Image.new("LA", size=(100, 100), color=(0, 255)) + draw = ImageDraw.Draw(dst) + draw.rectangle((0, 33, 100, 66), fill=(0, 128)) + draw.rectangle((0, 67, 100, 100), fill=(0, 0)) + src = Image.new("LA", size=(100, 100), color=(255, 255)) + draw = ImageDraw.Draw(src) + draw.rectangle((33, 0, 66, 100), fill=(255, 128)) + draw.rectangle((67, 0, 100, 100), fill=(255, 0)) + + # Act + img = Image.alpha_composite(dst, src) + + # Assert + img_colors = img.getcolors() + assert img_colors is not None + assert sorted(img_colors) == expected_colors + def test_alpha_inplace(self) -> None: src = Image.new("RGBA", (128, 128), "blue") diff --git a/src/PIL/Image.py b/src/PIL/Image.py index d209405c4..f4f1eea79 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3588,9 +3588,8 @@ def alpha_composite(im1: Image, im2: Image) -> Image: """ Alpha composite im2 over im1. - :param im1: The first image. Must have mode RGBA. - :param im2: The second image. Must have mode RGBA, and the same size as - the first image. + :param im1: The first image. Must have mode RGBA or LA. + :param im2: The second image. Must have the same mode and size as the first image. :returns: An :py:class:`~PIL.Image.Image` object. """ diff --git a/src/libImaging/AlphaComposite.c b/src/libImaging/AlphaComposite.c index e14af0dea..44c451679 100644 --- a/src/libImaging/AlphaComposite.c +++ b/src/libImaging/AlphaComposite.c @@ -25,7 +25,8 @@ ImagingAlphaComposite(Imaging imDst, Imaging imSrc) { int x, y; /* Check arguments */ - if (!imDst || !imSrc || strcmp(imDst->mode, "RGBA")) { + if (!imDst || !imSrc || + (strcmp(imDst->mode, "RGBA") && strcmp(imDst->mode, "LA"))) { return ImagingError_ModeError(); } From 756dd04705be059136a77ba473e1e708a52711fa Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 7 Jul 2025 19:09:39 +1000 Subject: [PATCH 014/309] Removed reference to libtiff 3.x --- docs/installation/building-from-source.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/building-from-source.rst b/docs/installation/building-from-source.rst index 8988a92ce..45cf5295c 100644 --- a/docs/installation/building-from-source.rst +++ b/docs/installation/building-from-source.rst @@ -44,7 +44,7 @@ Many of Pillow's features require external libraries: * **libtiff** provides compressed TIFF functionality - * Pillow has been tested with libtiff versions **3.x** and **4.0-4.7.0** + * Pillow has been tested with libtiff versions **4.0-4.7.0** * **libfreetype** provides type related services From 4cfef00574803a64fbab26d2400fe1f39521cbbc Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 5 Jul 2025 16:33:22 +1000 Subject: [PATCH 015/309] Added "Colors" to concepts --- docs/handbook/concepts.rst | 22 ++++++++++++++++++++++ docs/reference/ImageDraw.rst | 4 +--- docs/reference/PixelAccess.rst | 2 +- src/PIL/Image.py | 24 +++++++++++++----------- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/docs/handbook/concepts.rst b/docs/handbook/concepts.rst index c9d3f5e91..46f612be3 100644 --- a/docs/handbook/concepts.rst +++ b/docs/handbook/concepts.rst @@ -101,6 +101,28 @@ Palette The palette mode (``P``) uses a color palette to define the actual color for each pixel. +.. _colors: + +Colors +------ + +To specify colors, you can use tuples with a value for each channel in the image, e.g. +``Image.new("RGB", (1, 1), (255, 0, 0))``. + +If an image has a single channel, you can use a single number instead, e.g. +``Image.new("L", (1, 1), 255)``. For "F" mode images, floating point values are also +accepted. In the case of "P" mode images, these will be indexes for the color palette. + +If a single value is used for an image with more than one channel, it will still be +parsed:: + + >>> from PIL import Image + >>> im = Image.new("RGBA", (1, 1), 0x04030201) + >>> im.getpixel((0, 0)) + (1, 2, 3, 4) + +Some methods accept other forms, such as color names. See :ref:`color-names`. + Info ---- diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 6e73233a1..4a2223a40 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -45,9 +45,7 @@ Colors ^^^^^^ To specify colors, you can use numbers or tuples just as you would use with -:py:meth:`PIL.Image.new` or :py:meth:`PIL.Image.Image.putpixel`. For “1”, -“L”, and “I” images, use integers. For “RGB” images, use a 3-tuple containing -integer values. For “F” images, use integer or floating point values. +:py:meth:`PIL.Image.new`. See :ref:`colors` for more information. For palette images (mode “P”), use integers as color indexes. In 1.1.4 and later, you can also use RGB 3-tuples or color names (see below). The drawing diff --git a/docs/reference/PixelAccess.rst b/docs/reference/PixelAccess.rst index 9d7cf83b6..e4af94b9f 100644 --- a/docs/reference/PixelAccess.rst +++ b/docs/reference/PixelAccess.rst @@ -59,7 +59,7 @@ Access using negative indexes is also possible. :: Modifies the pixel at x,y. The color is given as a single numerical value for single band images, and a tuple for - multi-band images. + multi-band images. See :ref:`colors` for more information. :param xy: The pixel coordinate, given as (x, y). :param color: The pixel value according to its mode, diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 59168f5e3..262b5478b 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1730,9 +1730,10 @@ class Image: details). Instead of an image, the source can be a integer or tuple - containing pixel values. The method then fills the region - with the given color. When creating RGB images, you can - also use color strings as supported by the ImageColor module. + containing pixel values. The method then fills the region + with the given color. When creating RGB images, you can + also use color strings as supported by the ImageColor module. See + :ref:`colors` for more information. If a mask is given, this method updates only the regions indicated by the mask. You can use either "1", "L", "LA", "RGBA" @@ -1988,7 +1989,8 @@ class Image: sequence ends. The scale and offset values are used to adjust the sequence values: **pixel = value*scale + offset**. - :param data: A flattened sequence object. + :param data: A flattened sequence object. See :ref:`colors` for more + information about values. :param scale: An optional scale value. The default is 1.0. :param offset: An optional offset value. The default is 0.0. """ @@ -2047,7 +2049,7 @@ class Image: Modifies the pixel at the given position. The color is given as a single numerical value for single-band images, and a tuple for multi-band images. In addition to this, RGB and RGBA tuples are - accepted for P and PA images. + accepted for P and PA images. See :ref:`colors` for more information. Note that this method is relatively slow. For more extensive changes, use :py:meth:`~PIL.Image.Image.paste` or the :py:mod:`~PIL.ImageDraw` @@ -3055,12 +3057,12 @@ def new( :param mode: The mode to use for the new image. See: :ref:`concept-modes`. :param size: A 2-tuple, containing (width, height) in pixels. - :param color: What color to use for the image. Default is black. - If given, this should be a single integer or floating point value - for single-band modes, and a tuple for multi-band modes (one value - per band). When creating RGB or HSV images, you can also use color - strings as supported by the ImageColor module. If the color is - None, the image is not initialised. + :param color: What color to use for the image. Default is black. If given, + this should be a single integer or floating point value for single-band + modes, and a tuple for multi-band modes (one value per band). When + creating RGB or HSV images, you can also use color strings as supported + by the ImageColor module. See :ref:`colors` for more information. If the + color is None, the image is not initialised. :returns: An :py:class:`~PIL.Image.Image` object. """ From e88f3120291cc208d8d1b46e3766fdbc1cfada82 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 5 Jul 2025 12:57:07 +1000 Subject: [PATCH 016/309] Fix unclosed file warning --- Tests/test_file_libtiff.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index c245a5a9b..958e2749f 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -873,8 +873,8 @@ class TestFileLibTiff(LibTiffTestCase): assert im.mode == "RGB" assert im.size == (128, 128) assert im.format == "TIFF" - im2 = hopper() - assert_image_similar(im, im2, 5) + with hopper() as im2: + assert_image_similar(im, im2, 5) except OSError: captured = capfd.readouterr() if "LZMA compression support is not configured" in captured.err: From dc7d646db03bb34abd493a79ec2ceb78ec778265 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 1 Jul 2025 22:52:23 +1000 Subject: [PATCH 017/309] Use correct bands for 2 band histograms --- Tests/test_image_histogram.py | 3 +++ src/libImaging/Histo.c | 14 +++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Tests/test_image_histogram.py b/Tests/test_image_histogram.py index dbd55d4c2..436eb78a2 100644 --- a/Tests/test_image_histogram.py +++ b/Tests/test_image_histogram.py @@ -10,9 +10,12 @@ def test_histogram() -> None: assert histogram("1") == (256, 0, 10994) assert histogram("L") == (256, 0, 662) + assert histogram("LA") == (512, 0, 16384) + assert histogram("La") == (512, 0, 16384) assert histogram("I") == (256, 0, 662) assert histogram("F") == (256, 0, 662) assert histogram("P") == (256, 0, 1551) + assert histogram("PA") == (512, 0, 16384) assert histogram("RGB") == (768, 4, 675) assert histogram("RGBA") == (1024, 0, 16384) assert histogram("CMYK") == (1024, 0, 16384) diff --git a/src/libImaging/Histo.c b/src/libImaging/Histo.c index c5a547a64..87c09d3d4 100644 --- a/src/libImaging/Histo.c +++ b/src/libImaging/Histo.c @@ -132,11 +132,15 @@ ImagingGetHistogram(Imaging im, Imaging imMask, void *minmax) { ImagingSectionEnter(&cookie); for (y = 0; y < im->ysize; y++) { UINT8 *in = (UINT8 *)im->image[y]; - for (x = 0; x < im->xsize; x++) { - h->histogram[(*in++)]++; - h->histogram[(*in++) + 256]++; - h->histogram[(*in++) + 512]++; - h->histogram[(*in++) + 768]++; + for (x = 0; x < im->xsize; x++, in += 4) { + h->histogram[*in]++; + if (im->bands == 2) { + h->histogram[*(in + 3) + 256]++; + } else { + h->histogram[*(in + 1) + 256]++; + h->histogram[*(in + 2) + 512]++; + h->histogram[*(in + 3) + 768]++; + } } } ImagingSectionLeave(&cookie); From 99737228c5a65d5291ecdf4d9718a34a815e8b32 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 8 Jul 2025 06:53:22 +1000 Subject: [PATCH 018/309] Only deprecate fromarray mode for changing data types --- Tests/test_image_array.py | 16 +++++--- src/PIL/Image.py | 79 +++++++++++++++++++-------------------- 2 files changed, 49 insertions(+), 46 deletions(-) diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py index ecbce3d6f..abb22f949 100644 --- a/Tests/test_image_array.py +++ b/Tests/test_image_array.py @@ -101,9 +101,8 @@ def test_fromarray_strides_without_tobytes() -> None: self.__array_interface__ = arr_params with pytest.raises(ValueError): - wrapped = Wrapper({"shape": (1, 1), "strides": (1, 1)}) - with pytest.warns(DeprecationWarning, match="'mode' parameter"): - Image.fromarray(wrapped, "L") + wrapped = Wrapper({"shape": (1, 1), "strides": (1, 1), "typestr": "|u1"}) + Image.fromarray(wrapped, "L") def test_fromarray_palette() -> None: @@ -112,9 +111,16 @@ def test_fromarray_palette() -> None: a = numpy.array(i) # Act - with pytest.warns(DeprecationWarning, match="'mode' parameter"): - out = Image.fromarray(a, "P") + out = Image.fromarray(a, "P") # Assert that the Python and C palettes match assert out.palette is not None assert len(out.palette.colors) == len(out.im.getpalette()) / 3 + + +def test_deprecation() -> None: + a = numpy.array(im.convert("L")) + with pytest.warns( + DeprecationWarning, match="'mode' parameter for changing data types" + ): + Image.fromarray(a, "1") diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 59168f5e3..e512da9a1 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3251,19 +3251,9 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image: transferred. This means that P and PA mode images will lose their palette. :param obj: Object with array interface - :param mode: Optional mode to use when reading ``obj``. Will be determined from - type if ``None``. Deprecated. - - This will not be used to convert the data after reading, but will be used to - change how the data is read:: - - from PIL import Image - import numpy as np - a = np.full((1, 1), 300) - im = Image.fromarray(a, mode="L") - im.getpixel((0, 0)) # 44 - im = Image.fromarray(a, mode="RGB") - im.getpixel((0, 0)) # (44, 1, 0) + :param mode: Optional mode to use when reading ``obj``. Since pixel values do not + contain information about palettes or color spaces, this can be used to place + grayscale L mode data within a P mode image, or read RGB data as YCbCr. See: :ref:`concept-modes` for general information about modes. :returns: An image object. @@ -3274,21 +3264,28 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image: shape = arr["shape"] ndim = len(shape) strides = arr.get("strides", None) - if mode is None: - try: - typekey = (1, 1) + shape[2:], arr["typestr"] - except KeyError as e: + try: + typekey = (1, 1) + shape[2:], arr["typestr"] + except KeyError as e: + if mode is not None: + typekey = None + color_modes: list[str] = [] + else: msg = "Cannot handle this data type" raise TypeError(msg) from e + if typekey is not None: try: - mode, rawmode = _fromarray_typemap[typekey] + typemode, rawmode, color_modes = _fromarray_typemap[typekey] except KeyError as e: typekey_shape, typestr = typekey msg = f"Cannot handle this data type: {typekey_shape}, {typestr}" raise TypeError(msg) from e - else: - deprecate("'mode' parameter", 13) + if mode is not None: + if mode != typemode and mode not in color_modes: + deprecate("'mode' parameter for changing data types", 13) rawmode = mode + else: + mode = typemode if mode in ["1", "L", "I", "P", "F"]: ndmax = 2 elif mode == "RGB": @@ -3385,29 +3382,29 @@ def fromqpixmap(im: ImageQt.QPixmap) -> ImageFile.ImageFile: _fromarray_typemap = { - # (shape, typestr) => mode, rawmode + # (shape, typestr) => mode, rawmode, color modes # first two members of shape are set to one - ((1, 1), "|b1"): ("1", "1;8"), - ((1, 1), "|u1"): ("L", "L"), - ((1, 1), "|i1"): ("I", "I;8"), - ((1, 1), "u2"): ("I", "I;16B"), - ((1, 1), "i2"): ("I", "I;16BS"), - ((1, 1), "u4"): ("I", "I;32B"), - ((1, 1), "i4"): ("I", "I;32BS"), - ((1, 1), "f4"): ("F", "F;32BF"), - ((1, 1), "f8"): ("F", "F;64BF"), - ((1, 1, 2), "|u1"): ("LA", "LA"), - ((1, 1, 3), "|u1"): ("RGB", "RGB"), - ((1, 1, 4), "|u1"): ("RGBA", "RGBA"), + ((1, 1), "|b1"): ("1", "1;8", []), + ((1, 1), "|u1"): ("L", "L", ["P"]), + ((1, 1), "|i1"): ("I", "I;8", []), + ((1, 1), "u2"): ("I", "I;16B", []), + ((1, 1), "i2"): ("I", "I;16BS", []), + ((1, 1), "u4"): ("I", "I;32B", []), + ((1, 1), "i4"): ("I", "I;32BS", []), + ((1, 1), "f4"): ("F", "F;32BF", []), + ((1, 1), "f8"): ("F", "F;64BF", []), + ((1, 1, 2), "|u1"): ("LA", "LA", ["La", "PA"]), + ((1, 1, 3), "|u1"): ("RGB", "RGB", ["YCbCr", "LAB", "HSV"]), + ((1, 1, 4), "|u1"): ("RGBA", "RGBA", ["RGBa"]), # shortcuts: - ((1, 1), f"{_ENDIAN}i4"): ("I", "I"), - ((1, 1), f"{_ENDIAN}f4"): ("F", "F"), + ((1, 1), f"{_ENDIAN}i4"): ("I", "I", []), + ((1, 1), f"{_ENDIAN}f4"): ("F", "F", []), } From 06f5cd1ddecea64d44f417bba539dc0b30734ea4 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Tue, 8 Jul 2025 11:31:03 +1000 Subject: [PATCH 019/309] Restored manylinux2014 wheels (#9059) --- .github/workflows/wheels.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 52a3f2cdb..5cc4f0355 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -77,22 +77,22 @@ jobs: platform: linux os: ubuntu-latest cibw_arch: x86_64 + manylinux: "manylinux2014" - name: "manylinux_2_28 x86_64" platform: linux os: ubuntu-latest cibw_arch: x86_64 build: "*manylinux*" - manylinux: "manylinux_2_28" - name: "manylinux2014 and musllinux aarch64" platform: linux os: ubuntu-24.04-arm cibw_arch: aarch64 + manylinux: "manylinux2014" - name: "manylinux_2_28 aarch64" platform: linux os: ubuntu-24.04-arm cibw_arch: aarch64 build: "*manylinux*" - manylinux: "manylinux_2_28" - name: "iOS arm64 device" platform: ios os: macos-latest From 2195faf0dc739f4d46f5d77a4a323b5358f079af Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 13:44:13 +1000 Subject: [PATCH 020/309] Update dependency cibuildwheel to v3.0.1 (#9075) --- .ci/requirements-cibw.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/requirements-cibw.txt b/.ci/requirements-cibw.txt index 520b6e320..e1eb52eb8 100644 --- a/.ci/requirements-cibw.txt +++ b/.ci/requirements-cibw.txt @@ -1 +1 @@ -cibuildwheel==3.0.0 +cibuildwheel==3.0.1 From c9cf688ee7ef50dc1bd4531f19514508ae68a8e5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 8 Jul 2025 21:10:26 +1000 Subject: [PATCH 021/309] Removed ImageDraw.getdraw hints deprecation section --- docs/deprecations.rst | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 236554565..4e65dc807 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -12,13 +12,6 @@ Deprecated features Below are features which are considered deprecated. Where appropriate, a :py:exc:`DeprecationWarning` is issued. -ImageDraw.getdraw hints parameter -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. deprecated:: 10.4.0 - -The ``hints`` parameter in :py:meth:`~PIL.ImageDraw.getdraw()` has been deprecated. - ExifTags.IFD.Makernote ^^^^^^^^^^^^^^^^^^^^^^ @@ -186,6 +179,7 @@ ICNS (width, height, scale) sizes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. deprecated:: 11.0.0 +.. versionremoved:: 12.0.0 Setting an ICNS image size to ``(width, height, scale)`` before loading has been removed. Instead, ``load(scale)`` can be used. From cbd47d8609e3306cb4b20ba2b04b32c176c88e43 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 8 Jul 2025 23:07:07 +1000 Subject: [PATCH 022/309] Removed handling of deprecated WebP features --- src/PIL/features.py | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/PIL/features.py b/src/PIL/features.py index 984f7532c..ff32c2510 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -9,7 +9,6 @@ from typing import IO import PIL from . import Image -from ._deprecate import deprecate modules = { "pil": ("PIL._imaging", "PILLOW_VERSION"), @@ -120,7 +119,7 @@ def get_supported_codecs() -> list[str]: return [f for f in codecs if check_codec(f)] -features: dict[str, tuple[str, str | bool, str | None]] = { +features: dict[str, tuple[str, str, str | None]] = { "raqm": ("PIL._imagingft", "HAVE_RAQM", "raqm_version"), "fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"), "harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"), @@ -146,12 +145,8 @@ def check_feature(feature: str) -> bool | None: module, flag, ver = features[feature] - if isinstance(flag, bool): - deprecate(f'check_feature("{feature}")', 12) try: imported_module = __import__(module, fromlist=["PIL"]) - if isinstance(flag, bool): - return flag return getattr(imported_module, flag) except ModuleNotFoundError: return None @@ -181,17 +176,7 @@ def get_supported_features() -> list[str]: """ :returns: A list of all supported features. """ - supported_features = [] - for f, (module, flag, _) in features.items(): - if flag is True: - for feature, (feature_module, _) in modules.items(): - if feature_module == module: - if check_module(feature): - supported_features.append(f) - break - elif check_feature(f): - supported_features.append(f) - return supported_features + return [f for f in features if check_feature(f)] def check(feature: str) -> bool | None: From 31e6c716ac0141ca03aed750b8b326183a45b0fb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 9 Jul 2025 22:26:25 +1000 Subject: [PATCH 023/309] Improved features test coverage --- Tests/test_features.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Tests/test_features.py b/Tests/test_features.py index 520c25b46..ddca99344 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -112,6 +112,25 @@ def test_unsupported_module() -> None: features.version_module(module) +def test_unsupported_feature() -> None: + # Arrange + feature = "unsupported_feature" + # Act / Assert + with pytest.raises(ValueError): + features.check_feature(feature) + with pytest.raises(ValueError): + features.version_feature(feature) + + +def test_unsupported_version() -> None: + assert features.version("unsupported_version") is None + + +def test_modulenotfound(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(features, "features", {"test": ("PIL._test", "", "")}) + assert features.check_feature("test") is None + + @pytest.mark.parametrize("supported_formats", (True, False)) def test_pilinfo(supported_formats: bool) -> None: buf = io.StringIO() From 2af930b2f72a86f30e79b5abc6f6791362411206 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 10 Jul 2025 12:07:38 +0800 Subject: [PATCH 024/309] Ensure dynamic libjpeg libraries are not linked. --- .github/workflows/wheels-dependencies.sh | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index d761d93b6..2c38dc609 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -60,7 +60,7 @@ if [[ "$CIBW_PLATFORM" == "ios" ]]; then # on using the Xcode builder, which isn't very helpful for most of Pillow's # dependencies. Therefore, we lean on the OSX configurations, plus CC, CFLAGS # etc. to ensure the right sysroot is selected. - HOST_CMAKE_FLAGS="-DCMAKE_SYSTEM_NAME=$CMAKE_SYSTEM_NAME -DCMAKE_SYSTEM_PROCESSOR=$GNU_ARCH -DCMAKE_OSX_DEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET -DCMAKE_OSX_SYSROOT=$IOS_SDK_PATH -DBUILD_SHARED_LIBS=NO" + HOST_CMAKE_FLAGS="-DCMAKE_SYSTEM_NAME=$CMAKE_SYSTEM_NAME -DCMAKE_SYSTEM_PROCESSOR=$GNU_ARCH -DCMAKE_OSX_DEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET -DCMAKE_OSX_SYSROOT=$IOS_SDK_PATH -DBUILD_SHARED_LIBS=NO -DENABLE_SHARED=NO" # Meson needs to be pointed at a cross-platform configuration file # This will be generated once CC etc. have been evaluated. @@ -380,6 +380,15 @@ fi wrap_wheel_builder build +# A safety catch for iOS. iOS can't use dynamic libraries, but clang will prefer +# to link dynamic libraries to static libraries. The only way to reliably +# prevent this is to not have dynamic libraries available in the first place. +# The build process *shouldn't* generate any dylibs... but just in case, purge +# any dylibs that *have* been installed into the build prefix directory. +if [[ -n "$IOS_SDK" ]]; then + find "$BUILD_PREFIX" -name "*.dylib" -exec rm -rf {} \; +fi + # Return to the project root to finish the build popd > /dev/null From 6c12d188db46ea8cfb19024bd55c352a2aaa3a03 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 10 Jul 2025 22:33:31 +1000 Subject: [PATCH 025/309] Updated libwebp to 1.6.0 --- .github/workflows/wheels-dependencies.sh | 4 +-- depends/install_webp.sh | 2 +- patches/iOS/libwebp-1.5.0.tar.gz.patch | 42 ------------------------ winbuild/build_prepare.py | 2 +- 4 files changed, 4 insertions(+), 46 deletions(-) delete mode 100644 patches/iOS/libwebp-1.5.0.tar.gz.patch diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 2c38dc609..6d52ca989 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -103,7 +103,7 @@ TIFF_VERSION=4.7.0 LCMS2_VERSION=2.17 ZLIB_VERSION=1.3.1 ZLIB_NG_VERSION=2.2.4 -LIBWEBP_VERSION=1.5.0 # Patched; next release won't need patching. See patch file. +LIBWEBP_VERSION=1.6.0 BZIP2_VERSION=1.0.8 LIBXCB_VERSION=1.17.0 BROTLI_VERSION=1.1.0 # Patched; next release won't need patching. See patch file. @@ -282,7 +282,7 @@ function build { fi CFLAGS="$CFLAGS $webp_cflags" build_simple libwebp $LIBWEBP_VERSION \ https://storage.googleapis.com/downloads.webmproject.org/releases/webp tar.gz \ - --enable-libwebpmux --enable-libwebpdemux + --enable-libwebpmux --enable-libwebpdemux --disable-libwebpexamples build_brotli diff --git a/depends/install_webp.sh b/depends/install_webp.sh index 9d2977715..d7f3cd2f5 100755 --- a/depends/install_webp.sh +++ b/depends/install_webp.sh @@ -1,7 +1,7 @@ #!/bin/bash # install webp -archive=libwebp-1.5.0 +archive=libwebp-1.6.0 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz diff --git a/patches/iOS/libwebp-1.5.0.tar.gz.patch b/patches/iOS/libwebp-1.5.0.tar.gz.patch deleted file mode 100644 index fefb72b68..000000000 --- a/patches/iOS/libwebp-1.5.0.tar.gz.patch +++ /dev/null @@ -1,42 +0,0 @@ -# libwebp example binaries require dependencies that aren't available for iOS builds. -# There's also no easy way to invoke the build to *exclude* the example builds. -# Since we don't need the examples anyway, remove them from the Makefile. -# -# As a point of reference, libwebp provides an XCFramework build script that involves -# 7 separate invocations of make to avoid building the examples. Patching the Makefile -# to remove the examples is a simpler approach, and one that is more compatible with -# the existing multibuild infrastructure. -# -# In the next release, it should be possible to pass --disable-libwebpexamples -# instead of applying this patch. -# -diff -ur libwebp-1.5.0-orig/Makefile.am libwebp-1.5.0/Makefile.am ---- libwebp-1.5.0-orig/Makefile.am 2024-12-20 09:17:50 -+++ libwebp-1.5.0/Makefile.am 2025-01-09 11:24:17 -@@ -5,5 +5,3 @@ - if BUILD_EXTRAS - SUBDIRS += extras - endif -- --SUBDIRS += examples -diff -ur libwebp-1.5.0-orig/Makefile.in libwebp-1.5.0/Makefile.in ---- libwebp-1.5.0-orig/Makefile.in 2024-12-20 09:52:53 -+++ libwebp-1.5.0/Makefile.in 2025-01-09 11:24:17 -@@ -156,7 +156,7 @@ - unique=`for i in $$list; do \ - if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ - done | $(am__uniquify_input)` --DIST_SUBDIRS = sharpyuv src imageio man extras examples -+DIST_SUBDIRS = sharpyuv src imageio man extras - am__DIST_COMMON = $(srcdir)/Makefile.in \ - $(top_srcdir)/src/webp/config.h.in AUTHORS COPYING ChangeLog \ - NEWS README.md ar-lib compile config.guess config.sub \ -@@ -351,7 +351,7 @@ - top_srcdir = @top_srcdir@ - webp_libname_prefix = @webp_libname_prefix@ - ACLOCAL_AMFLAGS = -I m4 --SUBDIRS = sharpyuv src imageio man $(am__append_1) examples -+SUBDIRS = sharpyuv src imageio man $(am__append_1) - EXTRA_DIST = COPYING autogen.sh - all: all-recursive - diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 187d07b20..6b2d41a7e 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -122,7 +122,7 @@ V = { "LIBAVIF": "1.3.0", "LIBIMAGEQUANT": "4.3.4", "LIBPNG": "1.6.49", - "LIBWEBP": "1.5.0", + "LIBWEBP": "1.6.0", "OPENJPEG": "2.5.3", "TIFF": "4.7.0", "XZ": "5.8.1", From 8b695cc0d36363cd853bd7d0cec7be2e31004537 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 10 Jul 2025 22:50:05 +1000 Subject: [PATCH 026/309] When deleting EXIF IFD tag, clear IFD data --- Tests/test_image.py | 11 +++++++++++ src/PIL/Image.py | 2 ++ 2 files changed, 13 insertions(+) diff --git a/Tests/test_image.py b/Tests/test_image.py index 83b027aa2..e6f21c976 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -922,6 +922,17 @@ class TestImage: reloaded_exif.load(exif.tobytes()) assert reloaded_exif.get_ifd(0x8769) == exif.get_ifd(0x8769) + def test_delete_ifd_tag(self) -> None: + with Image.open("Tests/images/flower.jpg") as im: + exif = im.getexif() + exif.get_ifd(0x8769) + assert 0x8769 in exif + del exif[0x8769] + + reloaded_exif = Image.Exif() + reloaded_exif.load(exif.tobytes()) + assert 0x8769 not in reloaded_exif + def test_exif_load_from_fp(self) -> None: with Image.open("Tests/images/flower.jpg") as im: data = im.info["exif"] diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 262b5478b..8901b3034 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -4215,6 +4215,8 @@ class Exif(_ExifBase): del self._info[tag] else: del self._data[tag] + if tag in self._ifds: + del self._ifds[tag] def __iter__(self) -> Iterator[int]: keys = set(self._data) From 50dde1c125f0f3c1714c64fa6a049b1123e0a0cd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 10 Jul 2025 23:19:16 +1000 Subject: [PATCH 027/309] Remove unused _save_cjpeg --- Tests/helper.py | 10 ---------- Tests/test_file_jpeg.py | 9 --------- Tests/test_shell_injection.py | 7 +------ src/PIL/JpegImagePlugin.py | 10 ---------- winbuild/build_prepare.py | 5 ++--- 5 files changed, 3 insertions(+), 38 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index 34e4d6e75..df99f5f55 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -291,16 +291,6 @@ def djpeg_available() -> bool: return False -def cjpeg_available() -> bool: - if shutil.which("cjpeg"): - try: - subprocess.check_call(["cjpeg", "-version"]) - return True - except subprocess.CalledProcessError: # pragma: no cover - return False - return False - - def netpbm_available() -> bool: return bool(shutil.which("ppmquant") and shutil.which("ppmtogif")) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 08e879807..51d518ae5 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -26,7 +26,6 @@ from .helper import ( assert_image_equal_tofile, assert_image_similar, assert_image_similar_tofile, - cjpeg_available, djpeg_available, hopper, is_win32, @@ -731,14 +730,6 @@ class TestFileJpeg: img.load_djpeg() assert_image_similar_tofile(img, TEST_FILE, 5) - @pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available") - def test_save_cjpeg(self, tmp_path: Path) -> None: - with Image.open(TEST_FILE) as img: - tempfile = str(tmp_path / "temp.jpg") - JpegImagePlugin._save_cjpeg(img, BytesIO(), tempfile) - # Default save quality is 75%, so a tiny bit of difference is alright - assert_image_similar_tofile(img, tempfile, 17) - def test_no_duplicate_0x1001_tag(self) -> None: # Arrange tag_ids = {v: k for k, v in ExifTags.TAGS.items()} diff --git a/Tests/test_shell_injection.py b/Tests/test_shell_injection.py index 03e92b5b9..38d46f312 100644 --- a/Tests/test_shell_injection.py +++ b/Tests/test_shell_injection.py @@ -9,7 +9,7 @@ import pytest from PIL import GifImagePlugin, Image, JpegImagePlugin -from .helper import cjpeg_available, djpeg_available, is_win32, netpbm_available +from .helper import djpeg_available, is_win32, netpbm_available TEST_JPG = "Tests/images/hopper.jpg" TEST_GIF = "Tests/images/hopper.gif" @@ -42,11 +42,6 @@ class TestShellInjection: assert isinstance(im, JpegImagePlugin.JpegImageFile) im.load_djpeg() - @pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available") - def test_save_cjpeg_filename(self, tmp_path: Path) -> None: - with Image.open(TEST_JPG) as im: - self.assert_save_filename_check(tmp_path, im, JpegImagePlugin._save_cjpeg) - @pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") def test_save_netpbm_filename_bmp_mode(self, tmp_path: Path) -> None: with Image.open(TEST_GIF) as im: diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 082f3551a..efe8eff3b 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -845,16 +845,6 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: ) -def _save_cjpeg(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: - # ALTERNATIVE: handle JPEGs via the IJG command line utilities. - tempfile = im._dump() - subprocess.check_call(["cjpeg", "-outfile", filename, tempfile]) - try: - os.unlink(tempfile) - except OSError: - pass - - ## # Factory for making JPEG and MPO instances def jpeg_factory( diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 187d07b20..84d103c08 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -149,18 +149,17 @@ DEPS: dict[str, dict[str, Any]] = { }, "build": [ *cmds_cmake( - ("jpeg-static", "cjpeg-static", "djpeg-static"), + ("jpeg-static", "djpeg-static"), "-DENABLE_SHARED:BOOL=FALSE", "-DWITH_JPEG8:BOOL=TRUE", "-DWITH_CRT_DLL:BOOL=TRUE", ), cmd_copy("jpeg-static.lib", "libjpeg.lib"), - cmd_copy("cjpeg-static.exe", "cjpeg.exe"), cmd_copy("djpeg-static.exe", "djpeg.exe"), ], "headers": ["jconfig.h", r"src\j*.h"], "libs": ["libjpeg.lib"], - "bins": ["cjpeg.exe", "djpeg.exe"], + "bins": ["djpeg.exe"], }, "zlib": { "url": f"https://github.com/zlib-ng/zlib-ng/archive/refs/tags/{V['ZLIBNG']}.tar.gz", From d88986a184ceb32a7eb919e3b21f950c564da35f Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Fri, 11 Jul 2025 12:53:43 +1000 Subject: [PATCH 028/309] Link transitive dependencies Co-authored-by: Russell Keith-Magee --- .github/workflows/wheels-dependencies.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 6d52ca989..4296ba292 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -280,8 +280,11 @@ function build { if [[ -n "$IS_MACOS" ]]; then webp_cflags="$webp_cflags -Wl,-headerpad_max_install_names" fi - CFLAGS="$CFLAGS $webp_cflags" build_simple libwebp $LIBWEBP_VERSION \ - https://storage.googleapis.com/downloads.webmproject.org/releases/webp tar.gz \ + webp_ldflags="" + if [[ -n "$IOS_SDK" ]]; then + webp_ldflags="$webp_ldflags -llzma -lz" + fi + CFLAGS="$CFLAGS $webp_cflags" LDFLAGS="$LDFLAGS $webp_ldflags" build_simple libwebp $LIBWEBP_VERSION \ --enable-libwebpmux --enable-libwebpdemux --disable-libwebpexamples build_brotli From 722c130b316443be7cc561d716711d1d39d704f7 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Fri, 11 Jul 2025 13:12:38 +1000 Subject: [PATCH 029/309] Restored URL Co-authored-by: Russell Keith-Magee --- .github/workflows/wheels-dependencies.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 4296ba292..e83012fd6 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -285,6 +285,7 @@ function build { webp_ldflags="$webp_ldflags -llzma -lz" fi CFLAGS="$CFLAGS $webp_cflags" LDFLAGS="$LDFLAGS $webp_ldflags" build_simple libwebp $LIBWEBP_VERSION \ + https://storage.googleapis.com/downloads.webmproject.org/releases/webp tar.gz \ --enable-libwebpmux --enable-libwebpdemux --disable-libwebpexamples build_brotli From 985544d55715f2a5dfc539fdd09fb9bb7738694c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 11 Jul 2025 13:28:08 +1000 Subject: [PATCH 030/309] Do not disable libwebpexamples --- .github/workflows/wheels-dependencies.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index e83012fd6..6b5aedb69 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -286,7 +286,7 @@ function build { fi CFLAGS="$CFLAGS $webp_cflags" LDFLAGS="$LDFLAGS $webp_ldflags" build_simple libwebp $LIBWEBP_VERSION \ https://storage.googleapis.com/downloads.webmproject.org/releases/webp tar.gz \ - --enable-libwebpmux --enable-libwebpdemux --disable-libwebpexamples + --enable-libwebpmux --enable-libwebpdemux build_brotli From 74e36e0ee5da824132595c2c13dc1fc8416c743f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 11 Jul 2025 16:48:46 +1000 Subject: [PATCH 031/309] Added RGBX and CMYK as alternatives for RGBA array data --- src/PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index e512da9a1..c98630cc2 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3401,7 +3401,7 @@ _fromarray_typemap = { ((1, 1), ">f8"): ("F", "F;64BF", []), ((1, 1, 2), "|u1"): ("LA", "LA", ["La", "PA"]), ((1, 1, 3), "|u1"): ("RGB", "RGB", ["YCbCr", "LAB", "HSV"]), - ((1, 1, 4), "|u1"): ("RGBA", "RGBA", ["RGBa"]), + ((1, 1, 4), "|u1"): ("RGBA", "RGBA", ["RGBa", "RGBX", "CMYK"]), # shortcuts: ((1, 1), f"{_ENDIAN}i4"): ("I", "I", []), ((1, 1), f"{_ENDIAN}f4"): ("F", "F", []), From 561ae3760c8a240d825598c0bd3b0365991586cf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 11 Jul 2025 17:18:47 +1000 Subject: [PATCH 032/309] Set correct size for rotated images after opening --- Tests/test_file_pcd.py | 15 +++++++++++++++ src/PIL/PcdImagePlugin.py | 3 +-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_pcd.py b/Tests/test_file_pcd.py index 81a316fc1..9bf1a75f0 100644 --- a/Tests/test_file_pcd.py +++ b/Tests/test_file_pcd.py @@ -1,10 +1,15 @@ from __future__ import annotations +from io import BytesIO + +import pytest + from PIL import Image def test_load_raw() -> None: with Image.open("Tests/images/hopper.pcd") as im: + assert im.size == (768, 512) im.load() # should not segfault. # Note that this image was created with a resized hopper @@ -15,3 +20,13 @@ def test_load_raw() -> None: # target = hopper().resize((768,512)) # assert_image_similar(im, target, 10) + + +@pytest.mark.parametrize("orientation", (1, 3)) +def test_rotated(orientation: int) -> None: + with open("Tests/images/hopper.pcd", "rb") as fp: + data = bytearray(fp.read()) + data[2048 + 1538] = orientation + f = BytesIO(data) + with Image.open(f) as im: + assert im.size == (512, 768) diff --git a/src/PIL/PcdImagePlugin.py b/src/PIL/PcdImagePlugin.py index 3aa249988..ac53f616e 100644 --- a/src/PIL/PcdImagePlugin.py +++ b/src/PIL/PcdImagePlugin.py @@ -46,14 +46,13 @@ class PcdImageFile(ImageFile.ImageFile): self.tile_post_rotate = -90 self._mode = "RGB" - self._size = 768, 512 # FIXME: not correct for rotated images! + self._size = (512, 768) if orientation in (1, 3) else (768, 512) self.tile = [ImageFile._Tile("pcd", (0, 0) + self.size, 96 * 2048)] def load_end(self) -> None: if self.tile_post_rotate: # Handle rotated PCDs self.im = self.im.rotate(self.tile_post_rotate) - self._size = self.im.size # From 7328cf2e5e9da9bbf2f2b20859c09707de8b1e4d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 11 Jul 2025 17:19:56 +1000 Subject: [PATCH 033/309] Reduced number of bytes read --- src/PIL/PcdImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/PcdImagePlugin.py b/src/PIL/PcdImagePlugin.py index ac53f616e..7f9ab525c 100644 --- a/src/PIL/PcdImagePlugin.py +++ b/src/PIL/PcdImagePlugin.py @@ -32,7 +32,7 @@ class PcdImageFile(ImageFile.ImageFile): assert self.fp is not None self.fp.seek(2048) - s = self.fp.read(2048) + s = self.fp.read(1539) if not s.startswith(b"PCD_"): msg = "not a PCD file" From a8bb7579dc3dd5c24dcecc17832dc1ea5b2249a8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 11 Jul 2025 21:06:30 +1000 Subject: [PATCH 034/309] Improved ImageMath test coverage --- Tests/test_imagemath_lambda_eval.py | 32 ++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/Tests/test_imagemath_lambda_eval.py b/Tests/test_imagemath_lambda_eval.py index 26c04b9a0..ce2a32ae8 100644 --- a/Tests/test_imagemath_lambda_eval.py +++ b/Tests/test_imagemath_lambda_eval.py @@ -2,7 +2,9 @@ from __future__ import annotations from typing import Any -from PIL import Image, ImageMath +import pytest + +from PIL import Image, ImageMath, _imagingmath def pixel(im: Image.Image | int) -> str | int: @@ -498,3 +500,31 @@ def test_logical_not_equal() -> None: ) == "I 1" ) + + +def test_reflected_operands() -> None: + assert pixel(ImageMath.lambda_eval(lambda args: 1 + args["A"], **images)) == "I 2" + assert pixel(ImageMath.lambda_eval(lambda args: 1 - args["A"], **images)) == "I 0" + assert pixel(ImageMath.lambda_eval(lambda args: 1 * args["A"], **images)) == "I 1" + assert pixel(ImageMath.lambda_eval(lambda args: 1 / args["A"], **images)) == "I 1" + assert pixel(ImageMath.lambda_eval(lambda args: 1 % args["A"], **images)) == "I 0" + assert pixel(ImageMath.lambda_eval(lambda args: 1 ** args["A"], **images)) == "I 1" + assert pixel(ImageMath.lambda_eval(lambda args: 1 & args["A"], **images)) == "I 1" + assert pixel(ImageMath.lambda_eval(lambda args: 1 | args["A"], **images)) == "I 1" + assert pixel(ImageMath.lambda_eval(lambda args: 1 ^ args["A"], **images)) == "I 0" + + +def test_unsupported_mode() -> None: + im = Image.new("RGB", (1, 1)) + with pytest.raises(ValueError, match="unsupported mode: RGB"): + ImageMath.lambda_eval(lambda args: args["im"] + 1, im=im) + + +def test_bad_operand_type(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delattr(_imagingmath, "abs_I") + with pytest.raises(TypeError, match="bad operand type for 'abs'"): + ImageMath.lambda_eval(lambda args: abs(args["I"]), I=I) + + monkeypatch.delattr(_imagingmath, "max_F") + with pytest.raises(TypeError, match="bad operand type for 'max'"): + ImageMath.lambda_eval(lambda args: args["max"](args["I"], args["F"]), I=I, F=F) From bc2519abf10e6a2a095be82d7a268870afaaba71 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 11 Jul 2025 22:45:22 +1000 Subject: [PATCH 035/309] Removed helper method _i8, unused since dump() was removed --- src/PIL/IptcImagePlugin.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py index b1fbb1bf1..e5a52aa8f 100644 --- a/src/PIL/IptcImagePlugin.py +++ b/src/PIL/IptcImagePlugin.py @@ -34,10 +34,6 @@ def _i(c: bytes) -> int: return i32((b"\0\0\0\0" + c)[-4:]) -def _i8(c: int | bytes) -> int: - return c if isinstance(c, int) else c[0] - - ## # Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields # from TIFF and JPEG files, use the getiptcinfo function. From 68ac3375c68a3798d9f964a2ec704c0465ea4566 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 12 Jul 2025 12:47:54 +1000 Subject: [PATCH 036/309] Codec is always "iptc" --- src/PIL/IptcImagePlugin.py | 48 ++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py index e5a52aa8f..85a13fe2c 100644 --- a/src/PIL/IptcImagePlugin.py +++ b/src/PIL/IptcImagePlugin.py @@ -124,35 +124,33 @@ class IptcImageFile(ImageFile.ImageFile): ] def load(self) -> Image.core.PixelAccess | None: - if len(self.tile) != 1 or self.tile[0][0] != "iptc": - return ImageFile.ImageFile.load(self) + if self.tile: + offset, compression = self.tile[0][2:] - offset, compression = self.tile[0][2:] + self.fp.seek(offset) - self.fp.seek(offset) - - # Copy image data to temporary file - o = BytesIO() - if compression == "raw": - # To simplify access to the extracted file, - # prepend a PPM header - o.write(b"P5\n%d %d\n255\n" % self.size) - while True: - type, size = self.field() - if type != (8, 10): - break - while size > 0: - s = self.fp.read(min(size, 8192)) - if not s: + # Copy image data to temporary file + o = BytesIO() + if compression == "raw": + # To simplify access to the extracted file, + # prepend a PPM header + o.write(b"P5\n%d %d\n255\n" % self.size) + while True: + type, size = self.field() + if type != (8, 10): break - o.write(s) - size -= len(s) + while size > 0: + s = self.fp.read(min(size, 8192)) + if not s: + break + o.write(s) + size -= len(s) - with Image.open(o) as _im: - _im.load() - self.im = _im.im - self.tile = [] - return Image.Image.load(self) + with Image.open(o) as _im: + _im.load() + self.im = _im.im + self.tile = [] + return ImageFile.ImageFile.load(self) Image.register_open(IptcImageFile.format, IptcImageFile) From cfa51ad4ada953c1a32d4cf9e1504de1cfec40b8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 12 Jul 2025 15:09:07 +1000 Subject: [PATCH 037/309] Populate single band --- Tests/test_file_iptc.py | 69 +++++++++++++++++++++++++++++++++++--- src/PIL/IptcImagePlugin.py | 33 +++++++++++------- 2 files changed, 85 insertions(+), 17 deletions(-) diff --git a/Tests/test_file_iptc.py b/Tests/test_file_iptc.py index 3c4c892c8..5a8aaa3ef 100644 --- a/Tests/test_file_iptc.py +++ b/Tests/test_file_iptc.py @@ -2,6 +2,8 @@ from __future__ import annotations from io import BytesIO +import pytest + from PIL import Image, IptcImagePlugin, TiffImagePlugin, TiffTags from .helper import assert_image_equal, hopper @@ -9,21 +11,78 @@ from .helper import assert_image_equal, hopper TEST_FILE = "Tests/images/iptc.jpg" +def create_iptc_image(info: dict[str, int] = {}) -> BytesIO: + def field(tag, value): + return bytes((0x1C,) + tag + (0, len(value))) + value + + data = field((3, 60), bytes((info.get("layers", 1), info.get("component", 0)))) + data += field((3, 120), bytes((info.get("compression", 1),))) + if "band" in info: + data += field((3, 65), bytes((info["band"] + 1,))) + data += field((3, 20), b"\x01") # width + data += field((3, 30), b"\x01") # height + data += field( + (8, 10), + bytes((info.get("data", 0),)), + ) + + return BytesIO(data) + + def test_open() -> None: expected = Image.new("L", (1, 1)) - f = BytesIO( - b"\x1c\x03<\x00\x02\x01\x00\x1c\x03x\x00\x01\x01\x1c\x03\x14\x00\x01\x01" - b"\x1c\x03\x1e\x00\x01\x01\x1c\x08\n\x00\x01\x00" - ) + f = create_iptc_image() 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", None))] assert_image_equal(im, expected) with Image.open(f) as im: assert im.load() is not None +def test_field_length() -> None: + f = create_iptc_image() + f.seek(28) + f.write(b"\xff") + with pytest.raises(OSError, match="illegal field length in IPTC/NAA file"): + with Image.open(f): + pass + + +@pytest.mark.parametrize("layers, mode", ((3, "RGB"), (4, "CMYK"))) +def test_layers(layers: int, mode: str) -> None: + for band in range(-1, layers): + info = {"layers": layers, "component": 1, "data": 5} + if band != -1: + info["band"] = band + f = create_iptc_image(info) + with Image.open(f) as im: + assert im.mode == mode + + data = [0] * layers + data[max(band, 0)] = 5 + assert im.getpixel((0, 0)) == tuple(data) + + +def test_unknown_compression() -> None: + f = create_iptc_image({"compression": 2}) + with pytest.raises(OSError, match="Unknown IPTC image compression"): + with Image.open(f): + pass + + +def test_getiptcinfo() -> None: + f = create_iptc_image() + with Image.open(f) as im: + assert IptcImagePlugin.getiptcinfo(im) == { + (3, 60): b"\x01\x00", + (3, 120): b"\x01", + (3, 20): b"\x01", + (3, 30): b"\x01", + } + + def test_getiptcinfo_jpg_none() -> None: # Arrange with hopper() as im: diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py index 85a13fe2c..c28f4dcc7 100644 --- a/src/PIL/IptcImagePlugin.py +++ b/src/PIL/IptcImagePlugin.py @@ -96,16 +96,18 @@ class IptcImageFile(ImageFile.ImageFile): # mode layers = self.info[(3, 60)][0] component = self.info[(3, 60)][1] - if (3, 65) in self.info: - id = self.info[(3, 65)][0] - 1 - else: - id = 0 if layers == 1 and not component: self._mode = "L" - elif layers == 3 and component: - self._mode = "RGB"[id] - elif layers == 4 and component: - self._mode = "CMYK"[id] + band = None + else: + if layers == 3 and component: + self._mode = "RGB" + elif layers == 4 and component: + self._mode = "CMYK" + if (3, 65) in self.info: + band = self.info[(3, 65)][0] - 1 + else: + band = 0 # size self._size = self.getint((3, 20)), self.getint((3, 30)) @@ -120,14 +122,16 @@ class IptcImageFile(ImageFile.ImageFile): # tile if tag == (8, 10): self.tile = [ - ImageFile._Tile("iptc", (0, 0) + self.size, offset, compression) + ImageFile._Tile("iptc", (0, 0) + self.size, offset, (compression, band)) ] def load(self) -> Image.core.PixelAccess | None: if self.tile: - offset, compression = self.tile[0][2:] + args = self.tile[0].args + assert isinstance(args, tuple) + compression, band = args - self.fp.seek(offset) + self.fp.seek(self.tile[0].offset) # Copy image data to temporary file o = BytesIO() @@ -147,7 +151,12 @@ class IptcImageFile(ImageFile.ImageFile): size -= len(s) with Image.open(o) as _im: - _im.load() + if band is not None: + bands = [Image.new("L", _im.size)] * Image.getmodebands(self.mode) + bands[band] = _im + _im = Image.merge(self.mode, bands) + else: + _im.load() self.im = _im.im self.tile = [] return ImageFile.ImageFile.load(self) From 6fdbf5433108454f8085b5d01c803367f97535a7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 12 Jul 2025 19:50:19 +1000 Subject: [PATCH 038/309] Width and height are unsigned --- src/PIL/GbrImagePlugin.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/PIL/GbrImagePlugin.py b/src/PIL/GbrImagePlugin.py index f319d7e84..d69295363 100644 --- a/src/PIL/GbrImagePlugin.py +++ b/src/PIL/GbrImagePlugin.py @@ -54,7 +54,7 @@ class GbrImageFile(ImageFile.ImageFile): width = i32(self.fp.read(4)) height = i32(self.fp.read(4)) color_depth = i32(self.fp.read(4)) - if width <= 0 or height <= 0: + if width == 0 or height == 0: msg = "not a GIMP brush" raise SyntaxError(msg) if color_depth not in (1, 4): @@ -71,7 +71,7 @@ class GbrImageFile(ImageFile.ImageFile): raise SyntaxError(msg) self.info["spacing"] = i32(self.fp.read(4)) - comment = self.fp.read(comment_length)[:-1] + self.info["comment"] = self.fp.read(comment_length)[:-1] if color_depth == 1: self._mode = "L" @@ -80,8 +80,6 @@ class GbrImageFile(ImageFile.ImageFile): self._size = width, height - self.info["comment"] = comment - # Image might not be small Image._decompression_bomb_check(self.size) From 4adff39bfd7a04c875b41bd879f513cde09c4604 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 12 Jul 2025 19:55:58 +1000 Subject: [PATCH 039/309] Improved test coverage --- Tests/test_file_gbr.py | 51 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/Tests/test_file_gbr.py b/Tests/test_file_gbr.py index 1b834cd3c..b8851d82b 100644 --- a/Tests/test_file_gbr.py +++ b/Tests/test_file_gbr.py @@ -1,8 +1,10 @@ from __future__ import annotations +from io import BytesIO + import pytest -from PIL import GbrImagePlugin, Image +from PIL import GbrImagePlugin, Image, _binary from .helper import assert_image_equal_tofile @@ -31,8 +33,49 @@ def test_multiple_load_operations() -> None: assert_image_equal_tofile(im, "Tests/images/gbr.png") -def test_invalid_file() -> None: - invalid_file = "Tests/images/flower.jpg" +def create_gbr_image(info: dict[str, int] = {}, magic_number=b"") -> BytesIO: + return BytesIO( + b"".join( + _binary.o32be(i) + for i in [ + info.get("header_size", 20), + info.get("version", 1), + info.get("width", 1), + info.get("height", 1), + info.get("color_depth", 1), + ] + ) + + magic_number + ) - with pytest.raises(SyntaxError): + +def test_invalid_file() -> None: + for f in [ + create_gbr_image({"header_size": 0}), + create_gbr_image({"width": 0}), + create_gbr_image({"height": 0}), + ]: + with pytest.raises(SyntaxError, match="not a GIMP brush"): + GbrImagePlugin.GbrImageFile(f) + + invalid_file = "Tests/images/flower.jpg" + with pytest.raises(SyntaxError, match="Unsupported GIMP brush version"): GbrImagePlugin.GbrImageFile(invalid_file) + + +def test_unsupported_gimp_brush() -> None: + f = create_gbr_image({"color_depth": 2}) + with pytest.raises(SyntaxError, match="Unsupported GIMP brush color depth: 2"): + GbrImagePlugin.GbrImageFile(f) + + +def test_bad_magic_number() -> None: + f = create_gbr_image({"version": 2}, magic_number=b"badm") + with pytest.raises(SyntaxError, match="not a GIMP brush, bad magic number"): + GbrImagePlugin.GbrImageFile(f) + + +def test_L() -> None: + f = create_gbr_image() + with Image.open(f) as im: + assert im.mode == "L" From d85fa7a2471b16bc547a46542f18f218b19a1c6b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 13 Jul 2025 16:13:44 +1000 Subject: [PATCH 040/309] Improved WmfImagePlugin test coverage --- Tests/test_file_wmf.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py index dcf5f000f..906080d15 100644 --- a/Tests/test_file_wmf.py +++ b/Tests/test_file_wmf.py @@ -44,6 +44,18 @@ def test_load_zero_inch() -> None: pass +def test_load_unsupported_wmf() -> None: + b = BytesIO(b"\xd7\xcd\xc6\x9a\x00\x00" + b"\x01" * 10) + with pytest.raises(SyntaxError, match="Unsupported WMF file format"): + WmfImagePlugin.WmfStubImageFile(b) + + +def test_load_unsupported() -> None: + b = BytesIO(b"\x01\x00\x00\x00") + with pytest.raises(SyntaxError, match="Unsupported file format"): + WmfImagePlugin.WmfStubImageFile(b) + + def test_render() -> None: with open("Tests/images/drawing.emf", "rb") as fp: data = fp.read() From 5ce88dbe53a3d46e20d464fbd6ef06031645bfda Mon Sep 17 00:00:00 2001 From: GUO YANKE Date: Mon, 7 Jul 2025 13:57:11 +0800 Subject: [PATCH 041/309] feat(ImageGrab): enhance grab function to support window-based screenshot capturing on macOS --- src/PIL/ImageGrab.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index 1eb450734..ba2c9b141 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -43,7 +43,10 @@ def grab( fh, filepath = tempfile.mkstemp(".png") os.close(fh) args = ["screencapture"] - if bbox: + if window: + args += ["-l", str(window)] + # -R is not working with -l + if bbox and not window: left, top, right, bottom = bbox args += ["-R", f"{left},{top},{right-left},{bottom-top}"] subprocess.call(args + ["-x", filepath]) @@ -51,9 +54,16 @@ def grab( im.load() os.unlink(filepath) if bbox: - im_resized = im.resize((right - left, bottom - top)) - im.close() - return im_resized + # manual crop for windowed mode + if window: + left, top, right, bottom = bbox + im_cropped = im.crop((left, top, right, bottom)) + im.close() + return im_cropped + else: + im_resized = im.resize((right - left, bottom - top)) + im.close() + return im_resized return im elif sys.platform == "win32": if window is not None: From 1f7e9c3b51db100de0164eab45ca16ec4e24bd78 Mon Sep 17 00:00:00 2001 From: Yan-Ke Guo Date: Mon, 7 Jul 2025 16:29:38 +0800 Subject: [PATCH 042/309] Apply suggestions from code review Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/PIL/ImageGrab.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index ba2c9b141..c18874581 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -56,8 +56,7 @@ def grab( if bbox: # manual crop for windowed mode if window: - left, top, right, bottom = bbox - im_cropped = im.crop((left, top, right, bottom)) + im_cropped = im.crop(bbox) im.close() return im_cropped else: From 7eaac3fcf0cd0fa54ba91784727f1d1a7654b31b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 7 Jul 2025 18:13:07 +1000 Subject: [PATCH 043/309] Updated documentation --- docs/reference/ImageGrab.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/ImageGrab.rst b/docs/reference/ImageGrab.rst index f6a2ec5bc..25afc9926 100644 --- a/docs/reference/ImageGrab.rst +++ b/docs/reference/ImageGrab.rst @@ -42,9 +42,9 @@ or the clipboard to a PIL image memory. .. versionadded:: 7.1.0 :param window: - HWND, to capture a single window. Windows only. + Capture a single window. On Windows, this is a HWND. On macOS, it uses windowid. - .. versionadded:: 11.2.1 + .. versionadded:: 11.2.1 (Windows), 12.0.0 (macOS) :return: An image .. py:function:: grabclipboard() From 79914ec8a57e1beeb2a5c62809044c116828962b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 9 Jul 2025 20:00:20 +1000 Subject: [PATCH 044/309] Check for scaling in macOS windows --- src/PIL/ImageGrab.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index c18874581..b82a2ff3a 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -45,8 +45,7 @@ def grab( args = ["screencapture"] if window: args += ["-l", str(window)] - # -R is not working with -l - if bbox and not window: + elif bbox: left, top, right, bottom = bbox args += ["-R", f"{left},{top},{right-left},{bottom-top}"] subprocess.call(args + ["-x", filepath]) @@ -54,9 +53,29 @@ def grab( im.load() os.unlink(filepath) if bbox: - # manual crop for windowed mode if window: - im_cropped = im.crop(bbox) + # Determine if the window was in retina mode or not + # by capturing it without the shadow, + # and checking how different the width is + fh, filepath = tempfile.mkstemp(".png") + os.close(fh) + subprocess.call( + ["screencapture", "-l", str(window), "-o", "-x", filepath] + ) + with Image.open(filepath) as im_no_shadow: + retina = im.width - im_no_shadow.width > 100 + os.unlink(filepath) + + # Since screencapture's -R does not work with -l, + # crop the image manually + if retina: + left, top, right, bottom = bbox + im_cropped = im.resize( + (right - left, bottom - top), + box=tuple(coord * 2 for coord in bbox), + ) + else: + im_cropped = im.crop(bbox) im.close() return im_cropped else: From 7516805121cc1da1d00cd6f0a22f64e0232c3541 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 14 Jul 2025 19:29:27 +1000 Subject: [PATCH 045/309] Improved DDS test coverage --- Tests/images/unimplemented_pixel_format.dds | Bin 0 -> 132 bytes Tests/test_file_dds.py | 19 +++++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 Tests/images/unimplemented_pixel_format.dds diff --git a/Tests/images/unimplemented_pixel_format.dds b/Tests/images/unimplemented_pixel_format.dds new file mode 100644 index 0000000000000000000000000000000000000000..9092df8b1b5acda7e115b9ceaf6241d0f294dd1b GIT binary patch literal 132 rcmZ>930A0KU|?Vu;9_841TsJvNWhsOE|EY1sE!4nS^-SSSfCI9+g<`M literal 0 HcmV?d00001 diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 5c7a943b1..116dfa59c 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -380,21 +380,28 @@ def test_palette() -> None: assert_image_equal_tofile(im, "Tests/images/transparent.gif") +def test_unsupported_header_size() -> None: + with pytest.raises(OSError, match="Unsupported header size 0"): + with Image.open(BytesIO(b"DDS " + b"\x00" * 4)): + pass + + def test_unsupported_bitcount() -> None: - with pytest.raises(OSError): + with pytest.raises(OSError, match="Unsupported bitcount 24 for 131072"): with Image.open("Tests/images/unsupported_bitcount.dds"): pass @pytest.mark.parametrize( - "test_file", + "test_file, message", ( - "Tests/images/unimplemented_dxgi_format.dds", - "Tests/images/unimplemented_pfflags.dds", + ("Tests/images/unimplemented_dxgi_format.dds", "Unimplemented DXGI format 93"), + ("Tests/images/unimplemented_pixel_format.dds", "Unimplemented pixel format 0"), + ("Tests/images/unimplemented_pfflags.dds", "Unknown pixel format flags 8"), ), ) -def test_not_implemented(test_file: str) -> None: - with pytest.raises(NotImplementedError): +def test_not_implemented(test_file: str, message: str) -> None: + with pytest.raises(NotImplementedError, match=message): with Image.open(test_file): pass From 638eb1b9992804ca21577b5933d476b7bdbeb5d6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 13:23:40 +1000 Subject: [PATCH 046/309] Update dependency mypy to v1.17.0 (#9092) --- .ci/requirements-mypy.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/requirements-mypy.txt b/.ci/requirements-mypy.txt index 44b5badab..e81f527b8 100644 --- a/.ci/requirements-mypy.txt +++ b/.ci/requirements-mypy.txt @@ -1,4 +1,4 @@ -mypy==1.16.1 +mypy==1.17.0 IceSpringPySideStubs-PyQt6 IceSpringPySideStubs-PySide6 ipython From 91bbeb5dcb47ce6d3b5b1c9969c982910ebee56b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 16 Jul 2025 13:54:13 +1000 Subject: [PATCH 047/309] Revert iOS change until the test runs again --- Tests/test_pyroma.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Tests/test_pyroma.py b/Tests/test_pyroma.py index a161d3f05..c2f7fe22e 100644 --- a/Tests/test_pyroma.py +++ b/Tests/test_pyroma.py @@ -1,7 +1,5 @@ from __future__ import annotations -from importlib.metadata import metadata - import pytest from PIL import __version__ @@ -11,7 +9,7 @@ pyroma = pytest.importorskip("pyroma", reason="Pyroma not installed") def test_pyroma() -> None: # Arrange - data = pyroma.projectdata.map_metadata_keys(metadata("Pillow")) + data = pyroma.projectdata.get_data(".") # Act rating = pyroma.ratings.rate(data) From a426eb55afcb9e8a069d6aba21405c0d89f69bec Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 16 Jul 2025 13:40:22 +1000 Subject: [PATCH 048/309] Remove file after test completion --- Tests/test_image_access.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index b3de5c13d..2609b1e34 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -276,10 +276,11 @@ class TestEmbeddable: except Exception: pytest.skip("Compiler could not be initialized") - with open("embed_pil.c", "w", encoding="utf-8") as fh: - home = sys.prefix.replace("\\", "\\\\") - fh.write( - f""" + try: + with open("embed_pil.c", "w", encoding="utf-8") as fh: + home = sys.prefix.replace("\\", "\\\\") + fh.write( + f""" #include "Python.h" int main(int argc, char* argv[]) @@ -301,17 +302,19 @@ int main(int argc, char* argv[]) return 0; }} """ - ) + ) - objects = compiler.compile(["embed_pil.c"]) - compiler.link_executable(objects, "embed_pil") + objects = compiler.compile(["embed_pil.c"]) + compiler.link_executable(objects, "embed_pil") - env = os.environ.copy() - env["PATH"] = sys.prefix + ";" + env["PATH"] + env = os.environ.copy() + env["PATH"] = sys.prefix + ";" + env["PATH"] - # Do not display the Windows Error Reporting dialog - getattr(ctypes, "windll").kernel32.SetErrorMode(0x0002) + # Do not display the Windows Error Reporting dialog + getattr(ctypes, "windll").kernel32.SetErrorMode(0x0002) - process = subprocess.Popen(["embed_pil.exe"], env=env) - process.communicate() - assert process.returncode == 0 + process = subprocess.Popen(["embed_pil.exe"], env=env) + process.communicate() + assert process.returncode == 0 + finally: + os.remove("embed_pil.c") From a39d14648bdbd6638e7097167c4b8c2964ce3752 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 16 Jul 2025 13:39:19 +1000 Subject: [PATCH 049/309] Updated manifest --- MANIFEST.in | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/MANIFEST.in b/MANIFEST.in index 95a6b1b92..6623f227d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -13,6 +13,7 @@ include LICENSE include Makefile include tox.ini graft Tests +graft Tests/images graft checks graft patches graft src @@ -28,8 +29,19 @@ exclude .editorconfig exclude .readthedocs.yml exclude codecov.yml exclude renovate.json +exclude Tests/images/README.md +exclude Tests/images/crash*.tif +exclude Tests/images/string_dimension.tiff global-exclude .git* global-exclude *.pyc global-exclude *.so prune .ci prune wheels +prune winbuild/build +prune winbuild/depends +prune Tests/errors +prune Tests/images/jpeg2000 +prune Tests/images/msp +prune Tests/images/picins +prune Tests/images/sunraster +prune Tests/test-images From cd93629a5c23a20c9a7dc13d13236e164bf2909f Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sat, 20 Apr 2024 16:40:43 -0500 Subject: [PATCH 050/309] use a struct for mode names instead of just a string --- setup.py | 1 + src/libImaging/Imaging.h | 23 ++++---- src/libImaging/Mode.c | 115 +++++++++++++++++++++++++++++++++++++++ src/libImaging/Mode.h | 60 ++++++++++++++++++++ 4 files changed, 186 insertions(+), 13 deletions(-) create mode 100644 src/libImaging/Mode.c create mode 100644 src/libImaging/Mode.h diff --git a/setup.py b/setup.py index df584f8df..93b5bcc78 100644 --- a/setup.py +++ b/setup.py @@ -103,6 +103,7 @@ _LIB_IMAGING = ( "JpegDecode", "JpegEncode", "Matrix", + "Mode", "ModeFilter", "Negative", "Offset", diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index bfe67d462..1eaabd8e5 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -11,6 +11,7 @@ */ #include "ImPlatform.h" +#include "Mode.h" #if defined(__cplusplus) extern "C" { @@ -71,9 +72,6 @@ typedef struct ImagingPaletteInstance *ImagingPalette; #define IMAGING_TYPE_FLOAT32 2 #define IMAGING_TYPE_SPECIAL 3 /* check mode for details */ -#define IMAGING_MODE_LENGTH \ - 6 + 1 /* Band names ("1", "L", "P", "RGB", "RGBA", "CMYK", "YCbCr", "BGR;xy") */ - typedef struct { char *ptr; int size; @@ -81,12 +79,11 @@ typedef struct { struct ImagingMemoryInstance { /* Format */ - char mode[IMAGING_MODE_LENGTH]; /* Band names ("1", "L", "P", "RGB", "RGBA", "CMYK", - "YCbCr", "BGR;xy") */ - int type; /* Data type (IMAGING_TYPE_*) */ - int depth; /* Depth (ignored in this version) */ - int bands; /* Number of bands (1, 2, 3, or 4) */ - int xsize; /* Image dimension. */ + const Mode *mode; /* Image mode (IMAGING_MODE_*) */ + int type; /* Data type (IMAGING_TYPE_*) */ + int depth; /* Depth (ignored in this version) */ + int bands; /* Number of bands (1, 2, 3, or 4) */ + int xsize; /* Image dimension. */ int ysize; /* Colour palette (for "P" images only) */ @@ -140,15 +137,15 @@ struct ImagingMemoryInstance { #define IMAGING_PIXEL_FLOAT32(im, x, y) (((FLOAT32 *)(im)->image32[y])[x]) struct ImagingAccessInstance { - const char *mode; + const Mode *mode; void (*get_pixel)(Imaging im, int x, int y, void *pixel); void (*put_pixel)(Imaging im, int x, int y, const void *pixel); }; struct ImagingHistogramInstance { /* Format */ - char mode[IMAGING_MODE_LENGTH]; /* Band names (of corresponding source image) */ - int bands; /* Number of bands (1, 3, or 4) */ + const Mode *mode; /* Mode of corresponding source image */ + int bands; /* Number of bands (1, 3, or 4) */ /* Data */ long *histogram; /* Histogram (bands*256 longs) */ @@ -156,7 +153,7 @@ struct ImagingHistogramInstance { struct ImagingPaletteInstance { /* Format */ - char mode[IMAGING_MODE_LENGTH]; /* Band names */ + const Mode *mode; /* Data */ int size; diff --git a/src/libImaging/Mode.c b/src/libImaging/Mode.c new file mode 100644 index 000000000..5b9c9dae1 --- /dev/null +++ b/src/libImaging/Mode.c @@ -0,0 +1,115 @@ +#include "Mode.h" +#include + + +#define CREATE_MODE(TYPE, NAME, INIT) \ +const TYPE IMAGING_##NAME##_VAL = INIT;\ +const TYPE * const IMAGING_##NAME = &IMAGING_##NAME##_VAL; + + +CREATE_MODE(Mode, MODE_1, {"1"}) +CREATE_MODE(Mode, MODE_CMYK, {"CMYK"}) +CREATE_MODE(Mode, MODE_F, {"F"}) +CREATE_MODE(Mode, MODE_HSV, {"HSV"}) +CREATE_MODE(Mode, MODE_I, {"I"}) +CREATE_MODE(Mode, MODE_L, {"L"}) +CREATE_MODE(Mode, MODE_LA, {"LA"}) +CREATE_MODE(Mode, MODE_LAB, {"LAB"}) +CREATE_MODE(Mode, MODE_La, {"La"}) +CREATE_MODE(Mode, MODE_P, {"P"}) +CREATE_MODE(Mode, MODE_PA, {"PA"}) +CREATE_MODE(Mode, MODE_RGB, {"RGB"}) +CREATE_MODE(Mode, MODE_RGBA, {"RGBA"}) +CREATE_MODE(Mode, MODE_RGBX, {"RGBX"}) +CREATE_MODE(Mode, MODE_RGBa, {"RGBa"}) +CREATE_MODE(Mode, MODE_YCbCr, {"YCbCr"}) + +const Mode * const MODES[] = { + IMAGING_MODE_1, + IMAGING_MODE_CMYK, + IMAGING_MODE_F, + IMAGING_MODE_HSV, + IMAGING_MODE_I, + IMAGING_MODE_L, + IMAGING_MODE_LA, + IMAGING_MODE_LAB, + IMAGING_MODE_La, + IMAGING_MODE_P, + IMAGING_MODE_PA, + IMAGING_MODE_RGB, + IMAGING_MODE_RGBA, + IMAGING_MODE_RGBX, + IMAGING_MODE_RGBa, + IMAGING_MODE_YCbCr, + NULL +}; + +const Mode * findMode(const char * const name) { + int i = 0; + const Mode * mode; + while ((mode = MODES[i++]) != NULL) { + if (!strcmp(mode->name, name)) { + return mode; + } + } + return NULL; +} + + +// Alias all of the modes as rawmodes so that the addresses are the same. +#define ALIAS_MODE_AS_RAWMODE(NAME) const RawMode * const IMAGING_RAWMODE_##NAME = (const RawMode * const)IMAGING_MODE_##NAME; +ALIAS_MODE_AS_RAWMODE(1) +ALIAS_MODE_AS_RAWMODE(CMYK) +ALIAS_MODE_AS_RAWMODE(F) +ALIAS_MODE_AS_RAWMODE(HSV) +ALIAS_MODE_AS_RAWMODE(I) +ALIAS_MODE_AS_RAWMODE(L) +ALIAS_MODE_AS_RAWMODE(LA) +ALIAS_MODE_AS_RAWMODE(LAB) +ALIAS_MODE_AS_RAWMODE(La) +ALIAS_MODE_AS_RAWMODE(P) +ALIAS_MODE_AS_RAWMODE(PA) +ALIAS_MODE_AS_RAWMODE(RGB) +ALIAS_MODE_AS_RAWMODE(RGBA) +ALIAS_MODE_AS_RAWMODE(RGBX) +ALIAS_MODE_AS_RAWMODE(RGBa) +ALIAS_MODE_AS_RAWMODE(YCbCr) + +CREATE_MODE(RawMode, RAWMODE_BGR_15, {"BGR;15"}) +CREATE_MODE(RawMode, RAWMODE_BGR_16, {"BGR;16"}) + +const RawMode * const RAWMODES[] = { + IMAGING_RAWMODE_1, + IMAGING_RAWMODE_CMYK, + IMAGING_RAWMODE_F, + IMAGING_RAWMODE_HSV, + IMAGING_RAWMODE_I, + IMAGING_RAWMODE_L, + IMAGING_RAWMODE_LA, + IMAGING_RAWMODE_LAB, + IMAGING_RAWMODE_La, + IMAGING_RAWMODE_P, + IMAGING_RAWMODE_PA, + IMAGING_RAWMODE_RGB, + IMAGING_RAWMODE_RGBA, + IMAGING_RAWMODE_RGBX, + IMAGING_RAWMODE_RGBa, + IMAGING_RAWMODE_YCbCr, + + IMAGING_RAWMODE_BGR_15, + IMAGING_RAWMODE_BGR_16, + + NULL +}; + +const RawMode * findRawMode(const char * const name) { + int i = 0; + const RawMode * rawmode; + while ((rawmode = RAWMODES[i++]) != NULL) { + const RawMode * const rawmode = RAWMODES[i]; + if (!strcmp(rawmode->name, name)) { + return rawmode; + } + } + return NULL; +} diff --git a/src/libImaging/Mode.h b/src/libImaging/Mode.h new file mode 100644 index 000000000..2d4d27c31 --- /dev/null +++ b/src/libImaging/Mode.h @@ -0,0 +1,60 @@ +#ifndef __MODE_H__ +#define __MODE_H__ + + +// Maximum length (including null terminator) for both mode and rawmode names. +#define IMAGING_MODE_LENGTH 6+1 + + +typedef struct { + const char * const name; +} Mode; + +extern const Mode * const IMAGING_MODE_1; +extern const Mode * const IMAGING_MODE_CMYK; +extern const Mode * const IMAGING_MODE_F; +extern const Mode * const IMAGING_MODE_HSV; +extern const Mode * const IMAGING_MODE_I; +extern const Mode * const IMAGING_MODE_L; +extern const Mode * const IMAGING_MODE_LA; +extern const Mode * const IMAGING_MODE_LAB; +extern const Mode * const IMAGING_MODE_La; +extern const Mode * const IMAGING_MODE_P; +extern const Mode * const IMAGING_MODE_PA; +extern const Mode * const IMAGING_MODE_RGB; +extern const Mode * const IMAGING_MODE_RGBA; +extern const Mode * const IMAGING_MODE_RGBX; +extern const Mode * const IMAGING_MODE_RGBa; +extern const Mode * const IMAGING_MODE_YCbCr; + +const Mode * findMode(const char * const name); + + +typedef struct { + const char * const name; +} RawMode; + +extern const RawMode * const IMAGING_RAWMODE_1; +extern const RawMode * const IMAGING_RAWMODE_CMYK; +extern const RawMode * const IMAGING_RAWMODE_F; +extern const RawMode * const IMAGING_RAWMODE_HSV; +extern const RawMode * const IMAGING_RAWMODE_I; +extern const RawMode * const IMAGING_RAWMODE_L; +extern const RawMode * const IMAGING_RAWMODE_LA; +extern const RawMode * const IMAGING_RAWMODE_LAB; +extern const RawMode * const IMAGING_RAWMODE_La; +extern const RawMode * const IMAGING_RAWMODE_P; +extern const RawMode * const IMAGING_RAWMODE_PA; +extern const RawMode * const IMAGING_RAWMODE_RGB; +extern const RawMode * const IMAGING_RAWMODE_RGBA; +extern const RawMode * const IMAGING_RAWMODE_RGBX; +extern const RawMode * const IMAGING_RAWMODE_RGBa; +extern const RawMode * const IMAGING_RAWMODE_YCbCr; + +extern const RawMode * const IMAGING_RAWMODE_BGR_15; +extern const RawMode * const IMAGING_RAWMODE_BGR_16; + +const RawMode * findRawMode(const char * const name); + + +#endif // __MODE_H__ From 63a45ad8d0d876cd2c32c43ff1641a93db7307b5 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sat, 20 Apr 2024 20:11:17 -0500 Subject: [PATCH 051/309] add special modes --- src/libImaging/Mode.c | 35 +++++++++++++++++++++++++++++++++-- src/libImaging/Mode.h | 16 ++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/libImaging/Mode.c b/src/libImaging/Mode.c index 5b9c9dae1..2bd09bda5 100644 --- a/src/libImaging/Mode.c +++ b/src/libImaging/Mode.c @@ -24,6 +24,15 @@ CREATE_MODE(Mode, MODE_RGBX, {"RGBX"}) CREATE_MODE(Mode, MODE_RGBa, {"RGBa"}) CREATE_MODE(Mode, MODE_YCbCr, {"YCbCr"}) +CREATE_MODE(Mode, MODE_BGR_15, {"BGR;15"}) +CREATE_MODE(Mode, MODE_BGR_16, {"BGR;16"}) +CREATE_MODE(Mode, MODE_BGR_24, {"BGR;24"}) + +CREATE_MODE(Mode, MODE_I_16, {"I;16"}) +CREATE_MODE(Mode, MODE_I_16L, {"I;16L"}) +CREATE_MODE(Mode, MODE_I_16B, {"I;16B"}) +CREATE_MODE(Mode, MODE_I_16N, {"I;16N"}) + const Mode * const MODES[] = { IMAGING_MODE_1, IMAGING_MODE_CMYK, @@ -41,6 +50,16 @@ const Mode * const MODES[] = { IMAGING_MODE_RGBX, IMAGING_MODE_RGBa, IMAGING_MODE_YCbCr, + + IMAGING_MODE_BGR_15, + IMAGING_MODE_BGR_16, + IMAGING_MODE_BGR_24, + + IMAGING_MODE_I_16, + IMAGING_MODE_I_16L, + IMAGING_MODE_I_16B, + IMAGING_MODE_I_16N, + NULL }; @@ -75,8 +94,14 @@ ALIAS_MODE_AS_RAWMODE(RGBX) ALIAS_MODE_AS_RAWMODE(RGBa) ALIAS_MODE_AS_RAWMODE(YCbCr) -CREATE_MODE(RawMode, RAWMODE_BGR_15, {"BGR;15"}) -CREATE_MODE(RawMode, RAWMODE_BGR_16, {"BGR;16"}) +ALIAS_MODE_AS_RAWMODE(BGR_15) +ALIAS_MODE_AS_RAWMODE(BGR_16) +ALIAS_MODE_AS_RAWMODE(BGR_24) + +ALIAS_MODE_AS_RAWMODE(I_16) +ALIAS_MODE_AS_RAWMODE(I_16L) +ALIAS_MODE_AS_RAWMODE(I_16B) +ALIAS_MODE_AS_RAWMODE(I_16N) const RawMode * const RAWMODES[] = { IMAGING_RAWMODE_1, @@ -98,6 +123,12 @@ const RawMode * const RAWMODES[] = { IMAGING_RAWMODE_BGR_15, IMAGING_RAWMODE_BGR_16, + IMAGING_RAWMODE_BGR_24, + + IMAGING_RAWMODE_I_16, + IMAGING_RAWMODE_I_16L, + IMAGING_RAWMODE_I_16B, + IMAGING_RAWMODE_I_16N, NULL }; diff --git a/src/libImaging/Mode.h b/src/libImaging/Mode.h index 2d4d27c31..6491beb81 100644 --- a/src/libImaging/Mode.h +++ b/src/libImaging/Mode.h @@ -27,6 +27,15 @@ extern const Mode * const IMAGING_MODE_RGBX; extern const Mode * const IMAGING_MODE_RGBa; extern const Mode * const IMAGING_MODE_YCbCr; +extern const Mode * const IMAGING_MODE_BGR_15; +extern const Mode * const IMAGING_MODE_BGR_16; +extern const Mode * const IMAGING_MODE_BGR_24; + +extern const Mode * const IMAGING_MODE_I_16; +extern const Mode * const IMAGING_MODE_I_16L; +extern const Mode * const IMAGING_MODE_I_16B; +extern const Mode * const IMAGING_MODE_I_16N; + const Mode * findMode(const char * const name); @@ -53,6 +62,13 @@ extern const RawMode * const IMAGING_RAWMODE_YCbCr; extern const RawMode * const IMAGING_RAWMODE_BGR_15; extern const RawMode * const IMAGING_RAWMODE_BGR_16; +extern const RawMode * const IMAGING_RAWMODE_BGR_24; +extern const RawMode * const IMAGING_RAWMODE_BGR_32; + +extern const RawMode * const IMAGING_RAWMODE_I_16; +extern const RawMode * const IMAGING_RAWMODE_I_16L; +extern const RawMode * const IMAGING_RAWMODE_I_16B; +extern const RawMode * const IMAGING_RAWMODE_I_16N; const RawMode * findRawMode(const char * const name); From 12409e4574ef1eb2df52b286a46912268a0e1de5 Mon Sep 17 00:00:00 2001 From: eyedav <88885346+eyedav@users.noreply.github.com> Date: Sat, 19 Jul 2025 14:44:41 +0200 Subject: [PATCH 052/309] use mode structs in _imaging.c --- src/_imaging.c | 178 +++++++++++++++++++++++++++------------ src/libImaging/Imaging.h | 34 ++++---- 2 files changed, 139 insertions(+), 73 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index fbfc0e41a..d0648540a 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -368,7 +368,7 @@ ImagingError_ValueError(const char *message) { /* -------------------------------------------------------------------- */ static int -getbands(const char *mode) { +getbands(const Mode *mode) { Imaging im; int bands; @@ -662,7 +662,11 @@ getink(PyObject *color, Imaging im, char *ink) { memcpy(ink, &ftmp, sizeof(ftmp)); return ink; case IMAGING_TYPE_SPECIAL: - if (strncmp(im->mode, "I;16", 4) == 0) { + if (im->mode == IMAGING_MODE_I_16 + || im->mode == IMAGING_MODE_I_16L + || im->mode == IMAGING_MODE_I_16B + || im->mode == IMAGING_MODE_I_16N + ) { ink[0] = (UINT8)r; ink[1] = (UINT8)(r >> 8); ink[2] = ink[3] = 0; @@ -681,6 +685,30 @@ getink(PyObject *color, Imaging im, char *ink) { } else if (!PyArg_ParseTuple(color, "iiL", &b, &g, &r)) { return NULL; } + if (im->mode == IMAGING_MODE_BGR_15) { + UINT16 v = ((((UINT16)r) << 7) & 0x7c00) + + ((((UINT16)g) << 2) & 0x03e0) + + ((((UINT16)b) >> 3) & 0x001f); + + ink[0] = (UINT8)v; + ink[1] = (UINT8)(v >> 8); + ink[2] = ink[3] = 0; + return ink; + } else if (im->mode == IMAGING_MODE_BGR_16) { + UINT16 v = ((((UINT16)r) << 8) & 0xf800) + + ((((UINT16)g) << 3) & 0x07e0) + + ((((UINT16)b) >> 3) & 0x001f); + ink[0] = (UINT8)v; + ink[1] = (UINT8)(v >> 8); + ink[2] = ink[3] = 0; + return ink; + } else if (im->mode == IMAGING_MODE_BGR_24) { + ink[0] = (UINT8)b; + ink[1] = (UINT8)g; + ink[2] = (UINT8)r; + ink[3] = 0; + return ink; + } } } @@ -694,7 +722,7 @@ getink(PyObject *color, Imaging im, char *ink) { static PyObject * _fill(PyObject *self, PyObject *args) { - char *mode; + char *mode_name; int xsize, ysize; PyObject *color; char buffer[4]; @@ -703,10 +731,12 @@ _fill(PyObject *self, PyObject *args) { xsize = ysize = 256; color = NULL; - if (!PyArg_ParseTuple(args, "s|(ii)O", &mode, &xsize, &ysize, &color)) { + if (!PyArg_ParseTuple(args, "s|(ii)O", &mode_name, &xsize, &ysize, &color)) { return NULL; } + const Mode * const mode = findMode(mode_name); + im = ImagingNewDirty(mode, xsize, ysize); if (!im) { return NULL; @@ -727,47 +757,55 @@ _fill(PyObject *self, PyObject *args) { static PyObject * _new(PyObject *self, PyObject *args) { - char *mode; + char *mode_name; int xsize, ysize; - if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) { + if (!PyArg_ParseTuple(args, "s(ii)", &mode_name, &xsize, &ysize)) { return NULL; } + const Mode * const mode = findMode(mode_name); + return PyImagingNew(ImagingNew(mode, xsize, ysize)); } static PyObject * _new_block(PyObject *self, PyObject *args) { - char *mode; + char *mode_name; int xsize, ysize; - if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) { + if (!PyArg_ParseTuple(args, "s(ii)", &mode_name, &xsize, &ysize)) { return NULL; } + const Mode * const mode = findMode(mode_name); + return PyImagingNew(ImagingNewBlock(mode, xsize, ysize)); } static PyObject * _linear_gradient(PyObject *self, PyObject *args) { - char *mode; + char *mode_name; - if (!PyArg_ParseTuple(args, "s", &mode)) { + if (!PyArg_ParseTuple(args, "s", &mode_name)) { return NULL; } + const Mode * const mode = findMode(mode_name); + return PyImagingNew(ImagingFillLinearGradient(mode)); } static PyObject * _radial_gradient(PyObject *self, PyObject *args) { - char *mode; + char *mode_name; - if (!PyArg_ParseTuple(args, "s", &mode)) { + if (!PyArg_ParseTuple(args, "s", &mode_name)) { return NULL; } + const Mode * const mode = findMode(mode_name); + return PyImagingNew(ImagingFillRadialGradient(mode)); } @@ -907,7 +945,7 @@ _prepare_lut_table(PyObject *table, Py_ssize_t table_size) { static PyObject * _color_lut_3d(ImagingObject *self, PyObject *args) { - char *mode; + char *mode_name; int filter; int table_channels; int size1D, size2D, size3D; @@ -919,7 +957,7 @@ _color_lut_3d(ImagingObject *self, PyObject *args) { if (!PyArg_ParseTuple( args, "sii(iii)O:color_lut_3d", - &mode, + &mode_name, &filter, &table_channels, &size1D, @@ -930,6 +968,8 @@ _color_lut_3d(ImagingObject *self, PyObject *args) { return NULL; } + const Mode * const mode = findMode(mode_name); + /* actually, it is trilinear */ if (filter != IMAGING_TRANSFORM_BILINEAR) { PyErr_SetString(PyExc_ValueError, "Only LINEAR filter is supported."); @@ -976,11 +1016,11 @@ _color_lut_3d(ImagingObject *self, PyObject *args) { static PyObject * _convert(ImagingObject *self, PyObject *args) { - char *mode; + char *mode_name; int dither = 0; ImagingObject *paletteimage = NULL; - if (!PyArg_ParseTuple(args, "s|iO", &mode, &dither, &paletteimage)) { + if (!PyArg_ParseTuple(args, "s|iO", &mode_name, &dither, &paletteimage)) { return NULL; } if (paletteimage != NULL) { @@ -997,6 +1037,8 @@ _convert(ImagingObject *self, PyObject *args) { } } + const Mode * const mode = findMode(mode_name); + return PyImagingNew(ImagingConvert( self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither )); @@ -1021,14 +1063,14 @@ _convert2(ImagingObject *self, PyObject *args) { static PyObject * _convert_matrix(ImagingObject *self, PyObject *args) { - char *mode; + char *mode_name; float m[12]; - if (!PyArg_ParseTuple(args, "s(ffff)", &mode, m + 0, m + 1, m + 2, m + 3)) { + if (!PyArg_ParseTuple(args, "s(ffff)", &mode_name, m + 0, m + 1, m + 2, m + 3)) { PyErr_Clear(); if (!PyArg_ParseTuple( args, "s(ffffffffffff)", - &mode, + &mode_name, m + 0, m + 1, m + 2, @@ -1046,18 +1088,22 @@ _convert_matrix(ImagingObject *self, PyObject *args) { } } + const Mode * const mode = findMode(mode_name); + return PyImagingNew(ImagingConvertMatrix(self->image, mode, m)); } static PyObject * _convert_transparent(ImagingObject *self, PyObject *args) { - char *mode; + char *mode_name; int r, g, b; - if (PyArg_ParseTuple(args, "s(iii)", &mode, &r, &g, &b)) { + if (PyArg_ParseTuple(args, "s(iii)", &mode_name, &r, &g, &b)) { + const Mode * const mode = findMode(mode_name); return PyImagingNew(ImagingConvertTransparent(self->image, mode, r, g, b)); } PyErr_Clear(); - if (PyArg_ParseTuple(args, "si", &mode, &r)) { + if (PyArg_ParseTuple(args, "si", &mode_name, &r)) { + const Mode * const mode = findMode(mode_name); return PyImagingNew(ImagingConvertTransparent(self->image, mode, r, 0, 0)); } return NULL; @@ -1156,9 +1202,9 @@ _getpalette(ImagingObject *self, PyObject *args) { int bits; ImagingShuffler pack; - char *mode = "RGB"; - char *rawmode = "RGB"; - if (!PyArg_ParseTuple(args, "|ss", &mode, &rawmode)) { + char *mode_name = "RGB"; + char *rawmode_name = "RGB"; + if (!PyArg_ParseTuple(args, "|ss", &mode_name, &rawmode_name)) { return NULL; } @@ -1167,6 +1213,9 @@ _getpalette(ImagingObject *self, PyObject *args) { return NULL; } + const Mode * const mode = findMode(mode_name); + const RawMode * const rawmode = findRawMode(rawmode_name); + pack = ImagingFindPacker(mode, rawmode, &bits); if (!pack) { PyErr_SetString(PyExc_ValueError, wrong_raw_mode); @@ -1193,7 +1242,7 @@ _getpalettemode(ImagingObject *self) { return NULL; } - return PyUnicode_FromString(self->image->palette->mode); + return PyUnicode_FromString(self->image->palette->mode->name); } static inline int @@ -1474,12 +1523,14 @@ _point(ImagingObject *self, PyObject *args) { Imaging im; PyObject *list; - char *mode; - if (!PyArg_ParseTuple(args, "Oz", &list, &mode)) { + char *mode_name; + if (!PyArg_ParseTuple(args, "Oz", &list, &mode_name)) { return NULL; } - if (mode && !strcmp(mode, "F")) { + const Mode * const mode = findMode(mode_name); + + if (mode == IMAGING_MODE_F) { FLOAT32 *data; /* map from 8-bit data to floating point */ @@ -1490,8 +1541,7 @@ _point(ImagingObject *self, PyObject *args) { } im = ImagingPoint(self->image, mode, (void *)data); free(data); - - } else if (!strcmp(self->image->mode, "I") && mode && !strcmp(mode, "L")) { + } else if (self->image->mode == IMAGING_MODE_I && mode == IMAGING_MODE_L) { UINT8 *data; /* map from 16-bit subset of 32-bit data to 8-bit */ @@ -1503,7 +1553,6 @@ _point(ImagingObject *self, PyObject *args) { } im = ImagingPoint(self->image, mode, (void *)data); free(data); - } else { INT32 *data; UINT8 lut[1024]; @@ -1524,7 +1573,7 @@ _point(ImagingObject *self, PyObject *args) { return NULL; } - if (mode && !strcmp(mode, "I")) { + if (mode == IMAGING_MODE_I) { im = ImagingPoint(self->image, mode, (void *)data); } else if (mode && bands > 1) { for (i = 0; i < 256; i++) { @@ -1629,10 +1678,9 @@ _putdata(ImagingObject *self, PyObject *args) { int bigendian = 0; if (image->type == IMAGING_TYPE_SPECIAL) { // I;16* - if ( - strcmp(image->mode, "I;16B") == 0 + if (image->mode == IMAGING_MODE_I_16B #ifdef WORDS_BIGENDIAN - || strcmp(image->mode, "I;16N") == 0 + || image->mode == IMAGING_MODE_I_16N #endif ) { bigendian = 1; @@ -1729,7 +1777,7 @@ _quantize(ImagingObject *self, PyObject *args) { if (!self->image->xsize || !self->image->ysize) { /* no content; return an empty image */ - return PyImagingNew(ImagingNew("P", self->image->xsize, self->image->ysize)); + return PyImagingNew(ImagingNew(IMAGING_MODE_P, self->image->xsize, self->image->ysize)); } return PyImagingNew(ImagingQuantize(self->image, colours, method, kmeans)); @@ -1740,21 +1788,33 @@ _putpalette(ImagingObject *self, PyObject *args) { ImagingShuffler unpack; int bits; - char *palette_mode, *rawmode; + char *palette_mode_name, *rawmode_name; UINT8 *palette; Py_ssize_t palettesize; if (!PyArg_ParseTuple( - args, "ssy#", &palette_mode, &rawmode, &palette, &palettesize + args, "ssy#", &palette_mode_name, &rawmode_name, &palette, &palettesize )) { return NULL; } - if (strcmp(self->image->mode, "L") && strcmp(self->image->mode, "LA") && - strcmp(self->image->mode, "P") && strcmp(self->image->mode, "PA")) { + if (self->image->mode != IMAGING_MODE_L && self->image->mode != IMAGING_MODE_LA && + self->image->mode != IMAGING_MODE_P && self->image->mode != IMAGING_MODE_PA) { PyErr_SetString(PyExc_ValueError, wrong_mode); return NULL; } + const Mode * const palette_mode = findMode(palette_mode_name); + if (palette_mode == NULL) { + PyErr_SetString(PyExc_ValueError, wrong_mode); + return NULL; + } + + const RawMode * const rawmode = findRawMode(rawmode_name); + if (rawmode == NULL) { + PyErr_SetString(PyExc_ValueError, wrong_raw_mode); + return NULL; + } + unpack = ImagingFindUnpacker(palette_mode, rawmode, &bits); if (!unpack) { PyErr_SetString(PyExc_ValueError, wrong_raw_mode); @@ -1768,7 +1828,7 @@ _putpalette(ImagingObject *self, PyObject *args) { ImagingPaletteDelete(self->image->palette); - strcpy(self->image->mode, strlen(self->image->mode) == 2 ? "PA" : "P"); + self->image->mode = strlen(self->image->mode->name) == 2 ? IMAGING_MODE_PA : IMAGING_MODE_P; self->image->palette = ImagingPaletteNew(palette_mode); @@ -1796,7 +1856,7 @@ _putpalettealpha(ImagingObject *self, PyObject *args) { return NULL; } - strcpy(self->image->palette->mode, "RGBA"); + self->image->palette->mode = IMAGING_MODE_RGBA; self->image->palette->palette[index * 4 + 3] = (UINT8)alpha; Py_RETURN_NONE; @@ -1821,7 +1881,7 @@ _putpalettealphas(ImagingObject *self, PyObject *args) { return NULL; } - strcpy(self->image->palette->mode, "RGBA"); + self->image->palette->mode = IMAGING_MODE_RGBA; for (i = 0; i < length; i++) { self->image->palette->palette[i * 4 + 3] = (UINT8)values[i]; } @@ -1989,8 +2049,10 @@ _reduce(ImagingObject *self, PyObject *args) { return PyImagingNew(imOut); } -#define IS_RGB(mode) \ - (!strcmp(mode, "RGB") || !strcmp(mode, "RGBA") || !strcmp(mode, "RGBX")) +static int +isRGB(const Mode * const mode) { + return mode == IMAGING_MODE_RGB || mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_RGBX; +} static PyObject * im_setmode(ImagingObject *self, PyObject *args) { @@ -1998,23 +2060,25 @@ im_setmode(ImagingObject *self, PyObject *args) { Imaging im; - char *mode; + char *mode_name; Py_ssize_t modelen; - if (!PyArg_ParseTuple(args, "s#:setmode", &mode, &modelen)) { + if (!PyArg_ParseTuple(args, "s#:setmode", &mode_name, &modelen)) { return NULL; } + const Mode * const mode = findMode(mode_name); + im = self->image; /* move all logic in here to the libImaging primitive */ - if (!strcmp(im->mode, mode)) { + if (im->mode == mode) { ; /* same mode; always succeeds */ - } else if (IS_RGB(im->mode) && IS_RGB(mode)) { + } else if (isRGB(im->mode) && isRGB(mode)) { /* color to color */ - strcpy(im->mode, mode); + im->mode = mode; im->bands = modelen; - if (!strcmp(mode, "RGBA")) { + if (mode == IMAGING_MODE_RGBA) { (void)ImagingFillBand(im, 3, 255); } } else { @@ -2294,7 +2358,7 @@ _getextrema(ImagingObject *self) { case IMAGING_TYPE_FLOAT32: return Py_BuildValue("dd", extrema.f[0], extrema.f[1]); case IMAGING_TYPE_SPECIAL: - if (strcmp(self->image->mode, "I;16") == 0) { + if (self->image->mode == IMAGING_MODE_I_16) { return Py_BuildValue("HH", extrema.s[0], extrema.s[1]); } } @@ -2383,7 +2447,7 @@ _putband(ImagingObject *self, PyObject *args) { static PyObject * _merge(PyObject *self, PyObject *args) { - char *mode; + char *mode_name; ImagingObject *band0 = NULL; ImagingObject *band1 = NULL; ImagingObject *band2 = NULL; @@ -2393,7 +2457,7 @@ _merge(PyObject *self, PyObject *args) { if (!PyArg_ParseTuple( args, "sO!|O!O!O!", - &mode, + &mode_name, &Imaging_Type, &band0, &Imaging_Type, @@ -2406,6 +2470,8 @@ _merge(PyObject *self, PyObject *args) { return NULL; } + const Mode * const mode = findMode(mode_name); + if (band0) { bands[0] = band0->image; } @@ -3711,7 +3777,7 @@ static struct PyMethodDef methods[] = { static PyObject * _getattr_mode(ImagingObject *self, void *closure) { - return PyUnicode_FromString(self->image->mode); + return PyUnicode_FromString(self->image->mode->name); } static PyObject * diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index 1eaabd8e5..49f17f0da 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -193,16 +193,16 @@ extern void ImagingMemorySetBlockAllocator(ImagingMemoryArena arena, int use_block_allocator); extern Imaging -ImagingNew(const char *mode, int xsize, int ysize); +ImagingNew(const Mode *mode, int xsize, int ysize); extern Imaging -ImagingNewDirty(const char *mode, int xsize, int ysize); +ImagingNewDirty(const Mode *mode, int xsize, int ysize); extern Imaging -ImagingNew2Dirty(const char *mode, Imaging imOut, Imaging imIn); +ImagingNew2Dirty(const Mode *mode, Imaging imOut, Imaging imIn); extern void ImagingDelete(Imaging im); extern Imaging -ImagingNewBlock(const char *mode, int xsize, int ysize); +ImagingNewBlock(const Mode *mode, int xsize, int ysize); extern Imaging ImagingNewArrow( @@ -214,9 +214,9 @@ ImagingNewArrow( ); extern Imaging -ImagingNewPrologue(const char *mode, int xsize, int ysize); +ImagingNewPrologue(const Mode *mode, int xsize, int ysize); extern Imaging -ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int structure_size); +ImagingNewPrologueSubtype(const Mode *mode, int xsize, int ysize, int structure_size); extern void ImagingCopyPalette(Imaging destination, Imaging source); @@ -233,7 +233,7 @@ _ImagingAccessDelete(Imaging im, ImagingAccess access); #define ImagingAccessDelete(im, access) /* nop, for now */ extern ImagingPalette -ImagingPaletteNew(const char *mode); +ImagingPaletteNew(const Mode *mode); extern ImagingPalette ImagingPaletteNewBrowser(void); extern ImagingPalette @@ -305,13 +305,13 @@ ImagingBlend(Imaging imIn1, Imaging imIn2, float alpha); extern Imaging ImagingCopy(Imaging im); extern Imaging -ImagingConvert(Imaging im, const char *mode, ImagingPalette palette, int dither); +ImagingConvert(Imaging im, const Mode *mode, ImagingPalette palette, int dither); extern Imaging -ImagingConvertInPlace(Imaging im, const char *mode); +ImagingConvertInPlace(Imaging im, const Mode *mode); extern Imaging -ImagingConvertMatrix(Imaging im, const char *mode, float m[]); +ImagingConvertMatrix(Imaging im, const Mode *mode, float m[]); extern Imaging -ImagingConvertTransparent(Imaging im, const char *mode, int r, int g, int b); +ImagingConvertTransparent(Imaging im, const Mode *mode, int r, int g, int b); extern Imaging ImagingCrop(Imaging im, int x0, int y0, int x1, int y1); extern Imaging @@ -325,9 +325,9 @@ ImagingFill2( extern Imaging ImagingFillBand(Imaging im, int band, int color); extern Imaging -ImagingFillLinearGradient(const char *mode); +ImagingFillLinearGradient(const Mode *mode); extern Imaging -ImagingFillRadialGradient(const char *mode); +ImagingFillRadialGradient(const Mode *mode); extern Imaging ImagingFilter(Imaging im, int xsize, int ysize, const FLOAT32 *kernel, FLOAT32 offset); extern Imaging @@ -341,7 +341,7 @@ ImagingGaussianBlur( extern Imaging ImagingGetBand(Imaging im, int band); extern Imaging -ImagingMerge(const char *mode, Imaging bands[4]); +ImagingMerge(const Mode *mode, Imaging bands[4]); extern int ImagingSplit(Imaging im, Imaging bands[4]); extern int @@ -368,7 +368,7 @@ ImagingOffset(Imaging im, int xoffset, int yoffset); extern int ImagingPaste(Imaging into, Imaging im, Imaging mask, int x0, int y0, int x1, int y1); extern Imaging -ImagingPoint(Imaging im, const char *tablemode, const void *table); +ImagingPoint(Imaging im, const Mode *tablemode, const void *table); extern Imaging ImagingPointTransform(Imaging imIn, double scale, double offset); extern Imaging @@ -709,9 +709,9 @@ extern void ImagingConvertYCbCr2RGB(UINT8 *out, const UINT8 *in, int pixels); extern ImagingShuffler -ImagingFindUnpacker(const char *mode, const char *rawmode, int *bits_out); +ImagingFindUnpacker(const Mode *mode, const RawMode *rawmode, int *bits_out); extern ImagingShuffler -ImagingFindPacker(const char *mode, const char *rawmode, int *bits_out); +ImagingFindPacker(const Mode *mode, const RawMode *rawmode, int *bits_out); struct ImagingCodecStateInstance { int count; From a37f53c94974232c3d239ca9194d93296638d3ad Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 21 Apr 2024 00:58:11 -0500 Subject: [PATCH 053/309] use mode structs in tkImaging.c --- src/Tk/tkImaging.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Tk/tkImaging.c b/src/Tk/tkImaging.c index a36c3e0bd..3e35f885f 100644 --- a/src/Tk/tkImaging.c +++ b/src/Tk/tkImaging.c @@ -121,15 +121,18 @@ PyImagingPhotoPut( /* Mode */ - if (strcmp(im->mode, "1") == 0 || strcmp(im->mode, "L") == 0) { + if (im->mode == IMAGING_MODE_1 || im->mode == IMAGING_MODE_L) { block.pixelSize = 1; block.offset[0] = block.offset[1] = block.offset[2] = block.offset[3] = 0; - } else if (strncmp(im->mode, "RGB", 3) == 0) { + } else if ( + im->mode == IMAGING_MODE_RGB || im->mode == IMAGING_MODE_RGBA || + im->mode == IMAGING_MODE_RGBX || im->mode == IMAGING_MODE_RGBa + ) { block.pixelSize = 4; block.offset[0] = 0; block.offset[1] = 1; block.offset[2] = 2; - if (strcmp(im->mode, "RGBA") == 0) { + if (im->mode == IMAGING_MODE_RGBA) { block.offset[3] = 3; /* alpha (or reserved, under Tk 8.2) */ } else { block.offset[3] = 0; /* no alpha */ From a12dc30dc0f4a9b16c49fef2597a8d2f052983fe Mon Sep 17 00:00:00 2001 From: eyedav <88885346+eyedav@users.noreply.github.com> Date: Sat, 19 Jul 2025 14:46:29 +0200 Subject: [PATCH 054/309] use mode structs in encode.c and decode.c --- src/decode.c | 105 ++++++++++++++++++++++++++---------------- src/encode.c | 94 +++++++++++++++++++++++++------------ src/libImaging/Jpeg.h | 8 ++-- src/libImaging/Mode.h | 7 +++ 4 files changed, 140 insertions(+), 74 deletions(-) diff --git a/src/decode.c b/src/decode.c index 03db1ce35..9f4de28a8 100644 --- a/src/decode.c +++ b/src/decode.c @@ -266,7 +266,7 @@ static PyTypeObject ImagingDecoderType = { /* -------------------------------------------------------------------- */ int -get_unpacker(ImagingDecoderObject *decoder, const char *mode, const char *rawmode) { +get_unpacker(ImagingDecoderObject *decoder, const Mode *mode, const RawMode *rawmode) { int bits; ImagingShuffler unpack; @@ -436,12 +436,14 @@ PyObject * PyImaging_HexDecoderNew(PyObject *self, PyObject *args) { ImagingDecoderObject *decoder; - char *mode; - char *rawmode; - if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) { + char *mode_name, *rawmode_name; + if (!PyArg_ParseTuple(args, "ss", &mode_name, &rawmode_name)) { return NULL; } + const Mode * const mode = findMode(mode_name); + const RawMode * const rawmode = findRawMode(rawmode_name); + decoder = PyImaging_DecoderNew(0); if (decoder == NULL) { return NULL; @@ -469,16 +471,19 @@ PyImaging_HexDecoderNew(PyObject *self, PyObject *args) { PyObject * PyImaging_LibTiffDecoderNew(PyObject *self, PyObject *args) { ImagingDecoderObject *decoder; - char *mode; - char *rawmode; + char *mode_name; + char *rawmode_name; char *compname; int fp; uint32_t ifdoffset; - if (!PyArg_ParseTuple(args, "sssiI", &mode, &rawmode, &compname, &fp, &ifdoffset)) { + if (!PyArg_ParseTuple(args, "sssiI", &mode_name, &rawmode_name, &compname, &fp, &ifdoffset)) { return NULL; } + const Mode * const mode = findMode(mode_name); + const RawMode * const rawmode = findRawMode(rawmode_name); + TRACE(("new tiff decoder %s\n", compname)); decoder = PyImaging_DecoderNew(sizeof(TIFFSTATE)); @@ -511,12 +516,15 @@ PyObject * PyImaging_PackbitsDecoderNew(PyObject *self, PyObject *args) { ImagingDecoderObject *decoder; - char *mode; - char *rawmode; - if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) { + char *mode_name; + char *rawmode_name; + if (!PyArg_ParseTuple(args, "ss", &mode_name, &rawmode_name)) { return NULL; } + const Mode * const mode = findMode(mode_name); + const RawMode * const rawmode = findRawMode(rawmode_name); + decoder = PyImaging_DecoderNew(0); if (decoder == NULL) { return NULL; @@ -545,7 +553,7 @@ PyImaging_PcdDecoderNew(PyObject *self, PyObject *args) { } /* Unpack from PhotoYCC to RGB */ - if (get_unpacker(decoder, "RGB", "YCC;P") < 0) { + if (get_unpacker(decoder, IMAGING_MODE_RGB, IMAGING_RAWMODE_YCC_P) < 0) { return NULL; } @@ -562,13 +570,15 @@ PyObject * PyImaging_PcxDecoderNew(PyObject *self, PyObject *args) { ImagingDecoderObject *decoder; - char *mode; - char *rawmode; + char *mode_name, *rawmode_name; int stride; - if (!PyArg_ParseTuple(args, "ssi", &mode, &rawmode, &stride)) { + if (!PyArg_ParseTuple(args, "ssi", &mode_name, &rawmode_name, &stride)) { return NULL; } + const Mode * const mode = findMode(mode_name); + const RawMode * const rawmode = findRawMode(rawmode_name); + decoder = PyImaging_DecoderNew(0); if (decoder == NULL) { return NULL; @@ -593,14 +603,16 @@ PyObject * PyImaging_RawDecoderNew(PyObject *self, PyObject *args) { ImagingDecoderObject *decoder; - char *mode; - char *rawmode; + char *mode_name, *rawmode_name; int stride = 0; int ystep = 1; - if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &stride, &ystep)) { + if (!PyArg_ParseTuple(args, "ss|ii", &mode_name, &rawmode_name, &stride, &ystep)) { return NULL; } + const Mode * const mode = findMode(mode_name); + const RawMode * const rawmode = findRawMode(rawmode_name); + decoder = PyImaging_DecoderNew(sizeof(RAWSTATE)); if (decoder == NULL) { return NULL; @@ -627,14 +639,16 @@ PyObject * PyImaging_SgiRleDecoderNew(PyObject *self, PyObject *args) { ImagingDecoderObject *decoder; - char *mode; - char *rawmode; + char *mode_name, *rawmode_name; int ystep = 1; int bpc = 1; - if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &ystep, &bpc)) { + if (!PyArg_ParseTuple(args, "ss|ii", &mode_name, &rawmode_name, &ystep, &bpc)) { return NULL; } + const Mode * const mode = findMode(mode_name); + const RawMode * const rawmode = findRawMode(rawmode_name); + decoder = PyImaging_DecoderNew(sizeof(SGISTATE)); if (decoder == NULL) { return NULL; @@ -661,12 +675,14 @@ PyObject * PyImaging_SunRleDecoderNew(PyObject *self, PyObject *args) { ImagingDecoderObject *decoder; - char *mode; - char *rawmode; - if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) { + char *mode_name, *rawmode_name; + if (!PyArg_ParseTuple(args, "ss", &mode_name, &rawmode_name)) { return NULL; } + const Mode * const mode = findMode(mode_name); + const RawMode * const rawmode = findRawMode(rawmode_name); + decoder = PyImaging_DecoderNew(0); if (decoder == NULL) { return NULL; @@ -689,14 +705,16 @@ PyObject * PyImaging_TgaRleDecoderNew(PyObject *self, PyObject *args) { ImagingDecoderObject *decoder; - char *mode; - char *rawmode; + char *mode_name, *rawmode_name; int ystep = 1; int depth = 8; - if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &ystep, &depth)) { + if (!PyArg_ParseTuple(args, "ss|ii", &mode_name, &rawmode_name, &ystep, &depth)) { return NULL; } + const Mode * const mode = findMode(mode_name); + const RawMode * const rawmode = findRawMode(rawmode_name); + decoder = PyImaging_DecoderNew(0); if (decoder == NULL) { return NULL; @@ -727,7 +745,7 @@ PyImaging_XbmDecoderNew(PyObject *self, PyObject *args) { return NULL; } - if (get_unpacker(decoder, "1", "1;R") < 0) { + if (get_unpacker(decoder, IMAGING_MODE_1, IMAGING_RAWMODE_1_R) < 0) { return NULL; } @@ -748,13 +766,15 @@ PyObject * PyImaging_ZipDecoderNew(PyObject *self, PyObject *args) { ImagingDecoderObject *decoder; - char *mode; - char *rawmode; + char *mode_name, *rawmode_name; int interlaced = 0; - if (!PyArg_ParseTuple(args, "ss|i", &mode, &rawmode, &interlaced)) { + if (!PyArg_ParseTuple(args, "ss|i", &mode_name, &rawmode_name, &interlaced)) { return NULL; } + const Mode * const mode = findMode(mode_name); + const RawMode * const rawmode = findRawMode(rawmode_name); + decoder = PyImaging_DecoderNew(sizeof(ZIPSTATE)); if (decoder == NULL) { return NULL; @@ -798,16 +818,19 @@ PyObject * PyImaging_JpegDecoderNew(PyObject *self, PyObject *args) { ImagingDecoderObject *decoder; - char *mode; - char *rawmode; /* what we want from the decoder */ - char *jpegmode; /* what's in the file */ + char *mode_name; + char *rawmode_name; /* what we want from the decoder */ + char *jpegmode; /* what's in the file */ int scale = 1; int draft = 0; - if (!PyArg_ParseTuple(args, "ssz|ii", &mode, &rawmode, &jpegmode, &scale, &draft)) { + if (!PyArg_ParseTuple(args, "ssz|ii", &mode_name, &rawmode_name, &jpegmode, &scale, &draft)) { return NULL; } + const Mode * const mode = findMode(mode_name); + const RawMode * rawmode = findRawMode(rawmode_name); + if (!jpegmode) { jpegmode = ""; } @@ -820,8 +843,8 @@ PyImaging_JpegDecoderNew(PyObject *self, PyObject *args) { // libjpeg-turbo supports different output formats. // We are choosing Pillow's native format (3 color bytes + 1 padding) // to avoid extra conversion in Unpack.c. - if (ImagingJpegUseJCSExtensions() && strcmp(rawmode, "RGB") == 0) { - rawmode = "RGBX"; + if (ImagingJpegUseJCSExtensions() && rawmode == IMAGING_RAWMODE_RGB) { + rawmode = IMAGING_RAWMODE_RGBX; } if (get_unpacker(decoder, mode, rawmode) < 0) { @@ -831,11 +854,13 @@ PyImaging_JpegDecoderNew(PyObject *self, PyObject *args) { decoder->decode = ImagingJpegDecode; decoder->cleanup = ImagingJpegDecodeCleanup; - strncpy(((JPEGSTATE *)decoder->state.context)->rawmode, rawmode, 8); - strncpy(((JPEGSTATE *)decoder->state.context)->jpegmode, jpegmode, 8); + JPEGSTATE *jpeg_decoder_state_context = (JPEGSTATE *)decoder->state.context; - ((JPEGSTATE *)decoder->state.context)->scale = scale; - ((JPEGSTATE *)decoder->state.context)->draft = draft; + jpeg_decoder_state_context->rawmode = rawmode; + strncpy(jpeg_decoder_state_context->jpegmode, jpegmode, 8); + + jpeg_decoder_state_context->scale = scale; + jpeg_decoder_state_context->draft = draft; return (PyObject *)decoder; } diff --git a/src/encode.c b/src/encode.c index e56494036..311ffa4ee 100644 --- a/src/encode.c +++ b/src/encode.c @@ -334,14 +334,19 @@ static PyTypeObject ImagingEncoderType = { /* -------------------------------------------------------------------- */ int -get_packer(ImagingEncoderObject *encoder, const char *mode, const char *rawmode) { +get_packer(ImagingEncoderObject *encoder, const Mode *mode, const RawMode *rawmode) { int bits; ImagingShuffler pack; pack = ImagingFindPacker(mode, rawmode, &bits); if (!pack) { Py_DECREF(encoder); - PyErr_Format(PyExc_ValueError, "No packer found from %s to %s", mode, rawmode); + PyErr_Format( + PyExc_ValueError, + "No packer found from %s to %s", + mode->name, + rawmode->name + ); return -1; } @@ -402,11 +407,11 @@ PyObject * PyImaging_GifEncoderNew(PyObject *self, PyObject *args) { ImagingEncoderObject *encoder; - char *mode; - char *rawmode; + char *mode_name; + char *rawmode_name; Py_ssize_t bits = 8; Py_ssize_t interlace = 0; - if (!PyArg_ParseTuple(args, "ss|nn", &mode, &rawmode, &bits, &interlace)) { + if (!PyArg_ParseTuple(args, "ss|nn", &mode_name, &rawmode_name, &bits, &interlace)) { return NULL; } @@ -415,6 +420,9 @@ PyImaging_GifEncoderNew(PyObject *self, PyObject *args) { return NULL; } + const Mode * const mode = findMode(mode_name); + const RawMode * const rawmode = findRawMode(rawmode_name); + if (get_packer(encoder, mode, rawmode) < 0) { return NULL; } @@ -435,11 +443,11 @@ PyObject * PyImaging_PcxEncoderNew(PyObject *self, PyObject *args) { ImagingEncoderObject *encoder; - char *mode; - char *rawmode; + char *mode_name; + char *rawmode_name; Py_ssize_t bits = 8; - if (!PyArg_ParseTuple(args, "ss|n", &mode, &rawmode, &bits)) { + if (!PyArg_ParseTuple(args, "ss|n", &mode_name, &rawmode_name, &bits)) { return NULL; } @@ -448,6 +456,9 @@ PyImaging_PcxEncoderNew(PyObject *self, PyObject *args) { return NULL; } + const Mode * const mode = findMode(mode_name); + const RawMode * const rawmode = findRawMode(rawmode_name); + if (get_packer(encoder, mode, rawmode) < 0) { return NULL; } @@ -465,12 +476,12 @@ PyObject * PyImaging_RawEncoderNew(PyObject *self, PyObject *args) { ImagingEncoderObject *encoder; - char *mode; - char *rawmode; + char *mode_name; + char *rawmode_name; Py_ssize_t stride = 0; Py_ssize_t ystep = 1; - if (!PyArg_ParseTuple(args, "ss|nn", &mode, &rawmode, &stride, &ystep)) { + if (!PyArg_ParseTuple(args, "ss|nn", &mode_name, &rawmode_name, &stride, &ystep)) { return NULL; } @@ -479,6 +490,9 @@ PyImaging_RawEncoderNew(PyObject *self, PyObject *args) { return NULL; } + const Mode * const mode = findMode(mode_name); + const RawMode * const rawmode = findRawMode(rawmode_name); + if (get_packer(encoder, mode, rawmode) < 0) { return NULL; } @@ -499,11 +513,11 @@ PyObject * PyImaging_TgaRleEncoderNew(PyObject *self, PyObject *args) { ImagingEncoderObject *encoder; - char *mode; - char *rawmode; + char *mode_name; + char *rawmode_name; Py_ssize_t ystep = 1; - if (!PyArg_ParseTuple(args, "ss|n", &mode, &rawmode, &ystep)) { + if (!PyArg_ParseTuple(args, "ss|n", &mode_name, &rawmode_name, &ystep)) { return NULL; } @@ -512,6 +526,9 @@ PyImaging_TgaRleEncoderNew(PyObject *self, PyObject *args) { return NULL; } + const Mode * const mode = findMode(mode_name); + const RawMode * const rawmode = findRawMode(rawmode_name); + if (get_packer(encoder, mode, rawmode) < 0) { return NULL; } @@ -536,7 +553,7 @@ PyImaging_XbmEncoderNew(PyObject *self, PyObject *args) { return NULL; } - if (get_packer(encoder, "1", "1;R") < 0) { + if (get_packer(encoder, IMAGING_MODE_1, IMAGING_RAWMODE_1_R) < 0) { return NULL; } @@ -557,8 +574,8 @@ PyObject * PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) { ImagingEncoderObject *encoder; - char *mode; - char *rawmode; + char *mode_name; + char *rawmode_name; Py_ssize_t optimize = 0; Py_ssize_t compress_level = -1; Py_ssize_t compress_type = -1; @@ -567,8 +584,8 @@ PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) { if (!PyArg_ParseTuple( args, "ss|nnny#", - &mode, - &rawmode, + &mode_name, + &rawmode_name, &optimize, &compress_level, &compress_type, @@ -597,6 +614,9 @@ PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) { return NULL; } + const Mode * const mode = findMode(mode_name); + const RawMode * const rawmode = findRawMode(rawmode_name); + if (get_packer(encoder, mode, rawmode) < 0) { free(dictionary); return NULL; @@ -605,7 +625,7 @@ PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) { encoder->encode = ImagingZipEncode; encoder->cleanup = ImagingZipEncodeCleanup; - if (rawmode[0] == 'P') { + if (rawmode == IMAGING_RAWMODE_P || rawmode == IMAGING_RAWMODE_PA) { /* disable filtering */ ((ZIPSTATE *)encoder->state.context)->mode = ZIP_PNG_PALETTE; } @@ -634,8 +654,8 @@ PyObject * PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { ImagingEncoderObject *encoder; - char *mode; - char *rawmode; + char *mode_name; + char *rawmode_name; char *compname; char *filename; Py_ssize_t fp; @@ -655,7 +675,15 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { PyObject *item; if (!PyArg_ParseTuple( - args, "sssnsOO", &mode, &rawmode, &compname, &fp, &filename, &tags, &types + args, + "sssnsOO", + &mode_name, + &rawmode_name, + &compname, + &fp, + &filename, + &tags, + &types )) { return NULL; } @@ -693,6 +721,9 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { return NULL; } + const Mode * const mode = findMode(mode_name); + const RawMode * const rawmode = findRawMode(rawmode_name); + if (get_packer(encoder, mode, rawmode) < 0) { return NULL; } @@ -1076,8 +1107,8 @@ PyObject * PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) { ImagingEncoderObject *encoder; - char *mode; - char *rawmode; + char *mode_name; + char *rawmode_name; Py_ssize_t quality = 0; Py_ssize_t progressive = 0; Py_ssize_t smooth = 0; @@ -1101,8 +1132,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) { if (!PyArg_ParseTuple( args, "ss|nnnnpn(nn)nnnOz#y#y#", - &mode, - &rawmode, + &mode_name, + &rawmode_name, &quality, &progressive, &smooth, @@ -1130,11 +1161,14 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) { return NULL; } + const Mode * const mode = findMode(mode_name); + const RawMode * rawmode = findRawMode(rawmode_name); + // libjpeg-turbo supports different output formats. // We are choosing Pillow's native format (3 color bytes + 1 padding) // to avoid extra conversion in Pack.c. - if (ImagingJpegUseJCSExtensions() && strcmp(rawmode, "RGB") == 0) { - rawmode = "RGBX"; + if (ImagingJpegUseJCSExtensions() && rawmode == IMAGING_RAWMODE_RGB) { + rawmode = IMAGING_RAWMODE_RGBX; } if (get_packer(encoder, mode, rawmode) < 0) { @@ -1192,7 +1226,7 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) { encoder->encode = ImagingJpegEncode; JPEGENCODERSTATE *jpeg_encoder_state = (JPEGENCODERSTATE *)encoder->state.context; - strncpy(jpeg_encoder_state->rawmode, rawmode, 8); + jpeg_encoder_state->rawmode = rawmode; jpeg_encoder_state->keep_rgb = keep_rgb; jpeg_encoder_state->quality = quality; jpeg_encoder_state->qtables = qarrays; diff --git a/src/libImaging/Jpeg.h b/src/libImaging/Jpeg.h index 7cdba9022..35df91d7f 100644 --- a/src/libImaging/Jpeg.h +++ b/src/libImaging/Jpeg.h @@ -31,9 +31,9 @@ typedef struct { /* Jpeg file mode (empty if not known) */ char jpegmode[8 + 1]; - /* Converter output mode (input to the shuffler). If empty, - convert conversions are disabled */ - char rawmode[8 + 1]; + /* Converter output mode (input to the shuffler) */ + /* If NULL, convert conversions are disabled */ + const RawMode *rawmode; /* If set, trade quality for speed */ int draft; @@ -91,7 +91,7 @@ typedef struct { unsigned int restart_marker_rows; /* Converter input mode (input to the shuffler) */ - char rawmode[8 + 1]; + const RawMode *rawmode; /* Custom quantization tables () */ unsigned int *qtables; diff --git a/src/libImaging/Mode.h b/src/libImaging/Mode.h index 6491beb81..a99103667 100644 --- a/src/libImaging/Mode.h +++ b/src/libImaging/Mode.h @@ -43,6 +43,7 @@ typedef struct { const char * const name; } RawMode; +// Non-rawmode aliases. extern const RawMode * const IMAGING_RAWMODE_1; extern const RawMode * const IMAGING_RAWMODE_CMYK; extern const RawMode * const IMAGING_RAWMODE_F; @@ -60,16 +61,22 @@ extern const RawMode * const IMAGING_RAWMODE_RGBX; extern const RawMode * const IMAGING_RAWMODE_RGBa; extern const RawMode * const IMAGING_RAWMODE_YCbCr; +// BGR modes. extern const RawMode * const IMAGING_RAWMODE_BGR_15; extern const RawMode * const IMAGING_RAWMODE_BGR_16; extern const RawMode * const IMAGING_RAWMODE_BGR_24; extern const RawMode * const IMAGING_RAWMODE_BGR_32; +// I;16 modes. extern const RawMode * const IMAGING_RAWMODE_I_16; extern const RawMode * const IMAGING_RAWMODE_I_16L; extern const RawMode * const IMAGING_RAWMODE_I_16B; extern const RawMode * const IMAGING_RAWMODE_I_16N; +// Rawmodes +extern const RawMode * const IMAGING_RAWMODE_1_R; +extern const RawMode * const IMAGING_RAWMODE_YCC_P; + const RawMode * findRawMode(const char * const name); From 0df2ed0640b4be12fb7066d39868e1c77adfa52e Mon Sep 17 00:00:00 2001 From: eyedav <88885346+eyedav@users.noreply.github.com> Date: Sat, 19 Jul 2025 14:53:22 +0200 Subject: [PATCH 055/309] use mode structs in Access.c --- src/libImaging/Access.c | 123 ++++++++++++++++++---------------------- src/libImaging/Mode.c | 8 +++ src/libImaging/Mode.h | 6 +- 3 files changed, 69 insertions(+), 68 deletions(-) diff --git a/src/libImaging/Access.c b/src/libImaging/Access.c index 3db52377e..850399b14 100644 --- a/src/libImaging/Access.c +++ b/src/libImaging/Access.c @@ -11,38 +11,9 @@ #include "Imaging.h" -/* use make_hash.py from the pillow-scripts repository to calculate these values */ -#define ACCESS_TABLE_SIZE 35 -#define ACCESS_TABLE_HASH 8940 +#define ACCESS_TABLE_SIZE 24 +static struct ImagingAccessInstance ACCESS_TABLE[ACCESS_TABLE_SIZE]; -static struct ImagingAccessInstance access_table[ACCESS_TABLE_SIZE]; - -static inline UINT32 -hash(const char *mode) { - UINT32 i = ACCESS_TABLE_HASH; - while (*mode) { - i = ((i << 5) + i) ^ (UINT8)*mode++; - } - return i % ACCESS_TABLE_SIZE; -} - -static ImagingAccess -add_item(const char *mode) { - UINT32 i = hash(mode); - /* printf("hash %s => %d\n", mode, i); */ - if (access_table[i].mode && strcmp(access_table[i].mode, mode) != 0) { - fprintf( - stderr, - "AccessInit: hash collision: %d for both %s and %s\n", - i, - mode, - access_table[i].mode - ); - exit(1); - } - access_table[i].mode = mode; - return &access_table[i]; -} /* fetch individual pixel */ @@ -149,51 +120,69 @@ put_pixel_32(Imaging im, int x, int y, const void *color) { memcpy(&im->image32[y][x], color, sizeof(INT32)); } + +static void +set_access_table_item( + const int index, + const Mode * const mode, + void (*get_pixel)(Imaging im, int x, int y, void *pixel), + void (*put_pixel)(Imaging im, int x, int y, const void *pixel) +) { + ACCESS_TABLE[index].mode = mode; + ACCESS_TABLE[index].get_pixel = get_pixel; + ACCESS_TABLE[index].put_pixel = put_pixel; +} + void ImagingAccessInit(void) { -#define ADD(mode_, get_pixel_, put_pixel_) \ - { \ - ImagingAccess access = add_item(mode_); \ - access->get_pixel = get_pixel_; \ - access->put_pixel = put_pixel_; \ - } - - /* populate access table */ - ADD("1", get_pixel_8, put_pixel_8); - ADD("L", get_pixel_8, put_pixel_8); - ADD("LA", get_pixel_32_2bands, put_pixel_32); - ADD("La", get_pixel_32_2bands, put_pixel_32); - ADD("I", get_pixel_32, put_pixel_32); - ADD("I;16", get_pixel_16L, put_pixel_16L); - ADD("I;16L", get_pixel_16L, put_pixel_16L); - ADD("I;16B", get_pixel_16B, put_pixel_16B); + int i = 0; + set_access_table_item(i++, IMAGING_MODE_1, get_pixel_8, put_pixel_8); + set_access_table_item(i++, IMAGING_MODE_L, get_pixel_8, put_pixel_8); + set_access_table_item(i++, IMAGING_MODE_LA, get_pixel_32_2bands, put_pixel_32); + set_access_table_item(i++, IMAGING_MODE_La, get_pixel_32_2bands, put_pixel_32); + set_access_table_item(i++, IMAGING_MODE_I, get_pixel_32, put_pixel_32); + set_access_table_item(i++, IMAGING_MODE_I_16, get_pixel_16L, put_pixel_16L); + set_access_table_item(i++, IMAGING_MODE_I_16L, get_pixel_16L, put_pixel_16L); + set_access_table_item(i++, IMAGING_MODE_I_16B, get_pixel_16B, put_pixel_16B); #ifdef WORDS_BIGENDIAN - ADD("I;16N", get_pixel_16B, put_pixel_16B); + set_access_table_item(i++, IMAGING_MODE_I_16N, get_pixel_16B, put_pixel_16B); #else - ADD("I;16N", get_pixel_16L, put_pixel_16L); + set_access_table_item(i++, IMAGING_MODE_I_16N, get_pixel_16L, put_pixel_16L); #endif - ADD("I;32L", get_pixel_32L, put_pixel_32L); - ADD("I;32B", get_pixel_32B, put_pixel_32B); - ADD("F", get_pixel_32, put_pixel_32); - ADD("P", get_pixel_8, put_pixel_8); - ADD("PA", get_pixel_32_2bands, put_pixel_32); - ADD("RGB", get_pixel_32, put_pixel_32); - ADD("RGBA", get_pixel_32, put_pixel_32); - ADD("RGBa", get_pixel_32, put_pixel_32); - ADD("RGBX", get_pixel_32, put_pixel_32); - ADD("CMYK", get_pixel_32, put_pixel_32); - ADD("YCbCr", get_pixel_32, put_pixel_32); - ADD("LAB", get_pixel_32, put_pixel_32); - ADD("HSV", get_pixel_32, put_pixel_32); + set_access_table_item(i++, IMAGING_MODE_I_32L, get_pixel_32L, put_pixel_32L); + set_access_table_item(i++, IMAGING_MODE_I_32B, get_pixel_32B, put_pixel_32B); + set_access_table_item(i++, IMAGING_MODE_F, get_pixel_32, put_pixel_32); + set_access_table_item(i++, IMAGING_MODE_P, get_pixel_8, put_pixel_8); + set_access_table_item(i++, IMAGING_MODE_PA, get_pixel_32_2bands, put_pixel_32); + set_access_table_item(i++, IMAGING_MODE_RGB, get_pixel_32, put_pixel_32); + set_access_table_item(i++, IMAGING_MODE_RGBA, get_pixel_32, put_pixel_32); + set_access_table_item(i++, IMAGING_MODE_RGBa, get_pixel_32, put_pixel_32); + set_access_table_item(i++, IMAGING_MODE_RGBX, get_pixel_32, put_pixel_32); + set_access_table_item(i++, IMAGING_MODE_CMYK, get_pixel_32, put_pixel_32); + set_access_table_item(i++, IMAGING_MODE_YCbCr, get_pixel_32, put_pixel_32); + set_access_table_item(i++, IMAGING_MODE_LAB, get_pixel_32, put_pixel_32); + set_access_table_item(i++, IMAGING_MODE_HSV, get_pixel_32, put_pixel_32); + + + if (i != ACCESS_TABLE_SIZE) { + fprintf( + stderr, + "AccessInit: incorrect number of items added to ACCESS_TABLE; expected %i but got %i\n", + ACCESS_TABLE_SIZE, + i); + exit(1); + } } ImagingAccess -ImagingAccessNew(Imaging im) { - ImagingAccess access = &access_table[hash(im->mode)]; - if (im->mode[0] != access->mode[0] || strcmp(im->mode, access->mode) != 0) { - return NULL; +ImagingAccessNew(const Imaging im) { + int i; + for (i = 0; i < ACCESS_TABLE_SIZE; i++) { + if (im->mode == ACCESS_TABLE[i].mode) { + return &ACCESS_TABLE[i]; + } } - return access; + return NULL; } void diff --git a/src/libImaging/Mode.c b/src/libImaging/Mode.c index 2bd09bda5..85ba50e3f 100644 --- a/src/libImaging/Mode.c +++ b/src/libImaging/Mode.c @@ -32,6 +32,8 @@ CREATE_MODE(Mode, MODE_I_16, {"I;16"}) CREATE_MODE(Mode, MODE_I_16L, {"I;16L"}) CREATE_MODE(Mode, MODE_I_16B, {"I;16B"}) CREATE_MODE(Mode, MODE_I_16N, {"I;16N"}) +CREATE_MODE(Mode, MODE_I_32L, {"I;32L"}) +CREATE_MODE(Mode, MODE_I_32B, {"I;32B"}) const Mode * const MODES[] = { IMAGING_MODE_1, @@ -59,6 +61,8 @@ const Mode * const MODES[] = { IMAGING_MODE_I_16L, IMAGING_MODE_I_16B, IMAGING_MODE_I_16N, + IMAGING_MODE_I_32L, + IMAGING_MODE_I_32B, NULL }; @@ -102,6 +106,8 @@ ALIAS_MODE_AS_RAWMODE(I_16) ALIAS_MODE_AS_RAWMODE(I_16L) ALIAS_MODE_AS_RAWMODE(I_16B) ALIAS_MODE_AS_RAWMODE(I_16N) +ALIAS_MODE_AS_RAWMODE(I_32L) +ALIAS_MODE_AS_RAWMODE(I_32B) const RawMode * const RAWMODES[] = { IMAGING_RAWMODE_1, @@ -129,6 +135,8 @@ const RawMode * const RAWMODES[] = { IMAGING_RAWMODE_I_16L, IMAGING_RAWMODE_I_16B, IMAGING_RAWMODE_I_16N, + IMAGING_RAWMODE_I_32L, + IMAGING_RAWMODE_I_32B, NULL }; diff --git a/src/libImaging/Mode.h b/src/libImaging/Mode.h index a99103667..bd184808d 100644 --- a/src/libImaging/Mode.h +++ b/src/libImaging/Mode.h @@ -35,6 +35,8 @@ extern const Mode * const IMAGING_MODE_I_16; extern const Mode * const IMAGING_MODE_I_16L; extern const Mode * const IMAGING_MODE_I_16B; extern const Mode * const IMAGING_MODE_I_16N; +extern const Mode * const IMAGING_MODE_I_32L; +extern const Mode * const IMAGING_MODE_I_32B; const Mode * findMode(const char * const name); @@ -67,11 +69,13 @@ extern const RawMode * const IMAGING_RAWMODE_BGR_16; extern const RawMode * const IMAGING_RAWMODE_BGR_24; extern const RawMode * const IMAGING_RAWMODE_BGR_32; -// I;16 modes. +// I;* modes. extern const RawMode * const IMAGING_RAWMODE_I_16; extern const RawMode * const IMAGING_RAWMODE_I_16L; extern const RawMode * const IMAGING_RAWMODE_I_16B; extern const RawMode * const IMAGING_RAWMODE_I_16N; +extern const RawMode * const IMAGING_RAWMODE_I_32L; +extern const RawMode * const IMAGING_RAWMODE_I_32B; // Rawmodes extern const RawMode * const IMAGING_RAWMODE_1_R; From 82182ba5489e8406c963a88a2187c622d4ee90f2 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 21 Apr 2024 13:09:47 -0500 Subject: [PATCH 056/309] use mode structs in AlphaComposite.c --- src/libImaging/AlphaComposite.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libImaging/AlphaComposite.c b/src/libImaging/AlphaComposite.c index 6d728f908..8d6ee8862 100644 --- a/src/libImaging/AlphaComposite.c +++ b/src/libImaging/AlphaComposite.c @@ -25,12 +25,12 @@ ImagingAlphaComposite(Imaging imDst, Imaging imSrc) { int x, y; /* Check arguments */ - if (!imDst || !imSrc || strcmp(imDst->mode, "RGBA") || + if (!imDst || !imSrc || imDst->mode != IMAGING_MODE_RGBA || imDst->type != IMAGING_TYPE_UINT8 || imDst->bands != 4) { return ImagingError_ModeError(); } - if (strcmp(imDst->mode, imSrc->mode) || imDst->type != imSrc->type || + if (imDst->mode != imSrc->mode || imDst->type != imSrc->type || imDst->bands != imSrc->bands || imDst->xsize != imSrc->xsize || imDst->ysize != imSrc->ysize) { return ImagingError_Mismatch(); From d0541a73b94f1b8b5ba8a9e42718288e6f02feb1 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 21 Apr 2024 13:10:12 -0500 Subject: [PATCH 057/309] use mode structs in Bands.c --- src/libImaging/Bands.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libImaging/Bands.c b/src/libImaging/Bands.c index e1b16b34a..501b4625f 100644 --- a/src/libImaging/Bands.c +++ b/src/libImaging/Bands.c @@ -41,7 +41,7 @@ ImagingGetBand(Imaging imIn, int band) { band = 3; } - imOut = ImagingNewDirty("L", imIn->xsize, imIn->ysize); + imOut = ImagingNewDirty(IMAGING_MODE_L, imIn->xsize, imIn->ysize); if (!imOut) { return NULL; } @@ -82,7 +82,7 @@ ImagingSplit(Imaging imIn, Imaging bands[4]) { } for (i = 0; i < imIn->bands; i++) { - bands[i] = ImagingNewDirty("L", imIn->xsize, imIn->ysize); + bands[i] = ImagingNewDirty(IMAGING_MODE_L, imIn->xsize, imIn->ysize); if (!bands[i]) { for (j = 0; j < i; ++j) { ImagingDelete(bands[j]); @@ -240,7 +240,7 @@ ImagingFillBand(Imaging imOut, int band, int color) { } Imaging -ImagingMerge(const char *mode, Imaging bands[4]) { +ImagingMerge(const Mode *mode, Imaging bands[4]) { int i, x, y; int bandsCount = 0; Imaging imOut; From 38c75b9449c1bd2fbf5911ac683798f9f3a45fa5 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 21 Apr 2024 13:16:01 -0500 Subject: [PATCH 058/309] use mode structs in Blend.c --- src/libImaging/Blend.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libImaging/Blend.c b/src/libImaging/Blend.c index a53ae0fad..df94920f6 100644 --- a/src/libImaging/Blend.c +++ b/src/libImaging/Blend.c @@ -24,8 +24,8 @@ ImagingBlend(Imaging imIn1, Imaging imIn2, float alpha) { /* Check arguments */ if (!imIn1 || !imIn2 || imIn1->type != IMAGING_TYPE_UINT8 || imIn1->palette || - strcmp(imIn1->mode, "1") == 0 || imIn2->palette || - strcmp(imIn2->mode, "1") == 0) { + imIn1->mode == IMAGING_MODE_1 || imIn2->palette || + imIn2->mode == IMAGING_MODE_1) { return ImagingError_ModeError(); } From 6f6e1f99fc6fb94743e0f9281565b330fbbfaad0 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 21 Apr 2024 14:00:44 -0500 Subject: [PATCH 059/309] use mode structs in BoxBlur.c --- src/libImaging/BoxBlur.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libImaging/BoxBlur.c b/src/libImaging/BoxBlur.c index ed91541fe..4fea4fe44 100644 --- a/src/libImaging/BoxBlur.c +++ b/src/libImaging/BoxBlur.c @@ -248,7 +248,7 @@ ImagingBoxBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int n) return ImagingError_ValueError("radius must be >= 0"); } - if (strcmp(imIn->mode, imOut->mode) || imIn->type != imOut->type || + if (imIn->mode != imOut->mode || imIn->type != imOut->type || imIn->bands != imOut->bands || imIn->xsize != imOut->xsize || imIn->ysize != imOut->ysize) { return ImagingError_Mismatch(); @@ -258,10 +258,10 @@ ImagingBoxBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int n) return ImagingError_ModeError(); } - if (!(strcmp(imIn->mode, "RGB") == 0 || strcmp(imIn->mode, "RGBA") == 0 || - strcmp(imIn->mode, "RGBa") == 0 || strcmp(imIn->mode, "RGBX") == 0 || - strcmp(imIn->mode, "CMYK") == 0 || strcmp(imIn->mode, "L") == 0 || - strcmp(imIn->mode, "LA") == 0 || strcmp(imIn->mode, "La") == 0)) { + if (imIn->mode != IMAGING_MODE_RGB && imIn->mode != IMAGING_MODE_RGBA && + imIn->mode != IMAGING_MODE_RGBa && imIn->mode != IMAGING_MODE_RGBX && + imIn->mode != IMAGING_MODE_CMYK && imIn->mode != IMAGING_MODE_L && + imIn->mode != IMAGING_MODE_LA && imIn->mode != IMAGING_MODE_La) { return ImagingError_ModeError(); } From ecf1fce82baf2a4ca2deb37b58419e06a927e1eb Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 21 Apr 2024 14:05:13 -0500 Subject: [PATCH 060/309] use mode structs in Chops.c --- src/libImaging/Chops.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libImaging/Chops.c b/src/libImaging/Chops.c index f326d402f..66d0b4f97 100644 --- a/src/libImaging/Chops.c +++ b/src/libImaging/Chops.c @@ -60,11 +60,11 @@ return imOut; static Imaging -create(Imaging im1, Imaging im2, char *mode) { +create(Imaging im1, Imaging im2, const Mode *mode) { int xsize, ysize; if (!im1 || !im2 || im1->type != IMAGING_TYPE_UINT8 || - (mode != NULL && (strcmp(im1->mode, "1") || strcmp(im2->mode, "1")))) { + (mode != NULL && (im1->mode != mode || im2->mode != mode))) { return (Imaging)ImagingError_ModeError(); } if (im1->type != im2->type || im1->bands != im2->bands) { @@ -114,17 +114,17 @@ ImagingChopSubtract(Imaging imIn1, Imaging imIn2, float scale, int offset) { Imaging ImagingChopAnd(Imaging imIn1, Imaging imIn2) { - CHOP2((in1[x] && in2[x]) ? 255 : 0, "1"); + CHOP2((in1[x] && in2[x]) ? 255 : 0, IMAGING_MODE_1); } Imaging ImagingChopOr(Imaging imIn1, Imaging imIn2) { - CHOP2((in1[x] || in2[x]) ? 255 : 0, "1"); + CHOP2((in1[x] || in2[x]) ? 255 : 0, IMAGING_MODE_1); } Imaging ImagingChopXor(Imaging imIn1, Imaging imIn2) { - CHOP2(((in1[x] != 0) ^ (in2[x] != 0)) ? 255 : 0, "1"); + CHOP2(((in1[x] != 0) ^ (in2[x] != 0)) ? 255 : 0, IMAGING_MODE_1); } Imaging From 9bf3495898169879e75a8783310d4798741d3729 Mon Sep 17 00:00:00 2001 From: eyedav <88885346+eyedav@users.noreply.github.com> Date: Sat, 19 Jul 2025 15:12:51 +0200 Subject: [PATCH 061/309] use mode structs in Convert.c --- src/_imaging.c | 1 + src/libImaging/Access.c | 3 + src/libImaging/Convert.c | 406 +++++++++++++++++++++------------------ src/libImaging/Imaging.h | 15 +- 4 files changed, 235 insertions(+), 190 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index d0648540a..271a16dbb 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -4322,6 +4322,7 @@ setup_module(PyObject *m) { } ImagingAccessInit(); + ImagingConvertInit(); #ifdef HAVE_LIBJPEG { diff --git a/src/libImaging/Access.c b/src/libImaging/Access.c index 850399b14..6c41fc091 100644 --- a/src/libImaging/Access.c +++ b/src/libImaging/Access.c @@ -187,3 +187,6 @@ ImagingAccessNew(const Imaging im) { void _ImagingAccessDelete(Imaging im, ImagingAccess access) {} + +void +ImagingAccessFree() {} diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index 9a2c9ff16..2bc054616 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -877,147 +877,12 @@ I16_RGB(UINT8 *out, const UINT8 *in, int xsize) { } } -static struct { - const char *from; - const char *to; - ImagingShuffler convert; -} converters[] = { - - {"1", "L", bit2l}, - {"1", "I", bit2i}, - {"1", "F", bit2f}, - {"1", "RGB", bit2rgb}, - {"1", "RGBA", bit2rgb}, - {"1", "RGBX", bit2rgb}, - {"1", "CMYK", bit2cmyk}, - {"1", "YCbCr", bit2ycbcr}, - {"1", "HSV", bit2hsv}, - - {"L", "1", l2bit}, - {"L", "LA", l2la}, - {"L", "I", l2i}, - {"L", "F", l2f}, - {"L", "RGB", l2rgb}, - {"L", "RGBA", l2rgb}, - {"L", "RGBX", l2rgb}, - {"L", "CMYK", l2cmyk}, - {"L", "YCbCr", l2ycbcr}, - {"L", "HSV", l2hsv}, - - {"LA", "L", la2l}, - {"LA", "La", lA2la}, - {"LA", "RGB", la2rgb}, - {"LA", "RGBA", la2rgb}, - {"LA", "RGBX", la2rgb}, - {"LA", "CMYK", la2cmyk}, - {"LA", "YCbCr", la2ycbcr}, - {"LA", "HSV", la2hsv}, - - {"La", "LA", la2lA}, - - {"I", "L", i2l}, - {"I", "F", i2f}, - {"I", "RGB", i2rgb}, - {"I", "RGBA", i2rgb}, - {"I", "RGBX", i2rgb}, - {"I", "HSV", i2hsv}, - - {"F", "L", f2l}, - {"F", "I", f2i}, - - {"RGB", "1", rgb2bit}, - {"RGB", "L", rgb2l}, - {"RGB", "LA", rgb2la}, - {"RGB", "La", rgb2la}, - {"RGB", "I", rgb2i}, - {"RGB", "I;16", rgb2i16l}, - {"RGB", "I;16L", rgb2i16l}, - {"RGB", "I;16B", rgb2i16b}, -#ifdef WORDS_BIGENDIAN - {"RGB", "I;16N", rgb2i16b}, -#else - {"RGB", "I;16N", rgb2i16l}, -#endif - {"RGB", "F", rgb2f}, - {"RGB", "RGBA", rgb2rgba}, - {"RGB", "RGBa", rgb2rgba}, - {"RGB", "RGBX", rgb2rgba}, - {"RGB", "CMYK", rgb2cmyk}, - {"RGB", "YCbCr", ImagingConvertRGB2YCbCr}, - {"RGB", "HSV", rgb2hsv}, - - {"RGBA", "1", rgb2bit}, - {"RGBA", "L", rgb2l}, - {"RGBA", "LA", rgba2la}, - {"RGBA", "I", rgb2i}, - {"RGBA", "F", rgb2f}, - {"RGBA", "RGB", rgba2rgb}, - {"RGBA", "RGBa", rgbA2rgba}, - {"RGBA", "RGBX", rgb2rgba}, - {"RGBA", "CMYK", rgb2cmyk}, - {"RGBA", "YCbCr", ImagingConvertRGB2YCbCr}, - {"RGBA", "HSV", rgb2hsv}, - - {"RGBa", "RGBA", rgba2rgbA}, - {"RGBa", "RGB", rgba2rgb_}, - - {"RGBX", "1", rgb2bit}, - {"RGBX", "L", rgb2l}, - {"RGBX", "LA", rgb2la}, - {"RGBX", "I", rgb2i}, - {"RGBX", "F", rgb2f}, - {"RGBX", "RGB", rgba2rgb}, - {"RGBX", "CMYK", rgb2cmyk}, - {"RGBX", "YCbCr", ImagingConvertRGB2YCbCr}, - {"RGBX", "HSV", rgb2hsv}, - - {"CMYK", "RGB", cmyk2rgb}, - {"CMYK", "RGBA", cmyk2rgb}, - {"CMYK", "RGBX", cmyk2rgb}, - {"CMYK", "HSV", cmyk2hsv}, - - {"YCbCr", "L", ycbcr2l}, - {"YCbCr", "LA", ycbcr2la}, - {"YCbCr", "RGB", ImagingConvertYCbCr2RGB}, - - {"HSV", "RGB", hsv2rgb}, - - {"I", "I;16", I_I16L}, - {"I;16", "I", I16L_I}, - {"I;16", "RGB", I16_RGB}, - {"L", "I;16", L_I16L}, - {"I;16", "L", I16L_L}, - - {"I", "I;16L", I_I16L}, - {"I;16L", "I", I16L_I}, - {"I", "I;16B", I_I16B}, - {"I;16B", "I", I16B_I}, - - {"L", "I;16L", L_I16L}, - {"I;16L", "L", I16L_L}, - {"L", "I;16B", L_I16B}, - {"I;16B", "L", I16B_L}, -#ifdef WORDS_BIGENDIAN - {"L", "I;16N", L_I16B}, - {"I;16N", "L", I16B_L}, -#else - {"L", "I;16N", L_I16L}, - {"I;16N", "L", I16L_L}, -#endif - - {"I;16", "F", I16L_F}, - {"I;16L", "F", I16L_F}, - {"I;16B", "F", I16B_F}, - - {NULL} -}; - -/* FIXME: translate indexed versions to pointer versions below this line */ - /* ------------------- */ /* Palette conversions */ /* ------------------- */ +/* FIXME: translate indexed versions to pointer versions below this line */ + static void p2bit(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { int x; @@ -1065,13 +930,13 @@ pa2p(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { static void p2pa(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { int x; - int rgb = strcmp(palette->mode, "RGB"); + const int rgb = palette->mode == IMAGING_MODE_RGB; for (x = 0; x < xsize; x++, in++) { const UINT8 *rgba = &palette->palette[in[0] * 4]; *out++ = in[0]; *out++ = in[0]; *out++ = in[0]; - *out++ = rgb == 0 ? 255 : rgba[3]; + *out++ = rgb ? 255 : rgba[3]; } } @@ -1225,7 +1090,7 @@ pa2ycbcr(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { } static Imaging -frompalette(Imaging imOut, Imaging imIn, const char *mode) { +frompalette(Imaging imOut, Imaging imIn, const Mode *mode) { ImagingSectionCookie cookie; int alpha; int y; @@ -1237,31 +1102,31 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) { return (Imaging)ImagingError_ValueError("no palette"); } - alpha = !strcmp(imIn->mode, "PA"); + alpha = imIn->mode == IMAGING_MODE_PA; - if (strcmp(mode, "1") == 0) { + if (mode == IMAGING_MODE_1) { convert = alpha ? pa2bit : p2bit; - } else if (strcmp(mode, "L") == 0) { + } else if (mode == IMAGING_MODE_L) { convert = alpha ? pa2l : p2l; - } else if (strcmp(mode, "LA") == 0) { + } else if (mode == IMAGING_MODE_LA) { convert = alpha ? pa2la : p2la; - } else if (strcmp(mode, "P") == 0) { + } else if (mode == IMAGING_MODE_P) { convert = pa2p; - } else if (strcmp(mode, "PA") == 0) { + } else if (mode == IMAGING_MODE_PA) { convert = p2pa; - } else if (strcmp(mode, "I") == 0) { + } else if (mode == IMAGING_MODE_I) { convert = alpha ? pa2i : p2i; - } else if (strcmp(mode, "F") == 0) { + } else if (mode == IMAGING_MODE_F) { convert = alpha ? pa2f : p2f; - } else if (strcmp(mode, "RGB") == 0) { + } else if (mode == IMAGING_MODE_RGB) { convert = alpha ? pa2rgb : p2rgb; - } else if (strcmp(mode, "RGBA") == 0 || strcmp(mode, "RGBX") == 0) { + } else if (mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_RGBX) { convert = alpha ? pa2rgba : p2rgba; - } else if (strcmp(mode, "CMYK") == 0) { + } else if (mode == IMAGING_MODE_CMYK) { convert = alpha ? pa2cmyk : p2cmyk; - } else if (strcmp(mode, "YCbCr") == 0) { + } else if (mode == IMAGING_MODE_YCbCr) { convert = alpha ? pa2ycbcr : p2ycbcr; - } else if (strcmp(mode, "HSV") == 0) { + } else if (mode == IMAGING_MODE_HSV) { convert = alpha ? pa2hsv : p2hsv; } else { return (Imaging)ImagingError_ValueError("conversion not supported"); @@ -1271,7 +1136,7 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) { if (!imOut) { return NULL; } - if (strcmp(mode, "P") == 0 || strcmp(mode, "PA") == 0) { + if (mode == IMAGING_MODE_P || mode == IMAGING_MODE_PA) { ImagingPaletteDelete(imOut->palette); imOut->palette = ImagingPaletteDuplicate(imIn->palette); } @@ -1295,24 +1160,26 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) { #endif static Imaging topalette( - Imaging imOut, Imaging imIn, const char *mode, ImagingPalette inpalette, int dither + Imaging imOut, Imaging imIn, const Mode *mode, ImagingPalette inpalette, int dither ) { ImagingSectionCookie cookie; int alpha; int x, y; ImagingPalette palette = inpalette; - /* Map L or RGB/RGBX/RGBA to palette image */ - if (strcmp(imIn->mode, "L") != 0 && strncmp(imIn->mode, "RGB", 3) != 0) { + /* Map L or RGB/RGBX/RGBA/RGBa to palette image */ + if (imIn->mode != IMAGING_MODE_L && imIn->mode != IMAGING_MODE_RGB && + imIn->mode != IMAGING_MODE_RGBX && imIn->mode != IMAGING_MODE_RGBA && + imIn->mode != IMAGING_MODE_RGBa) { return (Imaging)ImagingError_ValueError("conversion not supported"); } - alpha = !strcmp(mode, "PA"); + alpha = mode == IMAGING_MODE_PA; if (palette == NULL) { /* FIXME: make user configurable */ if (imIn->bands == 1) { - palette = ImagingPaletteNew("RGB"); + palette = ImagingPaletteNew(IMAGING_MODE_RGB); palette->size = 256; int i; @@ -1499,11 +1366,11 @@ tobilevel(Imaging imOut, Imaging imIn) { int *errors; /* Map L or RGB to dithered 1 image */ - if (strcmp(imIn->mode, "L") != 0 && strcmp(imIn->mode, "RGB") != 0) { + if (imIn->mode != IMAGING_MODE_L && imIn->mode != IMAGING_MODE_RGB) { return (Imaging)ImagingError_ValueError("conversion not supported"); } - imOut = ImagingNew2Dirty("1", imOut, imIn); + imOut = ImagingNew2Dirty(IMAGING_MODE_1, imOut, imIn); if (!imOut) { return NULL; } @@ -1585,9 +1452,19 @@ tobilevel(Imaging imOut, Imaging imIn) { #pragma optimize("", on) #endif +/* ------------------- */ +/* Conversion handlers */ +/* ------------------- */ + +static struct Converter { + const Mode *from; + const Mode *to; + ImagingShuffler convert; +} *converters = NULL; + static Imaging convert( - Imaging imOut, Imaging imIn, const char *mode, ImagingPalette palette, int dither + Imaging imOut, Imaging imIn, const Mode *mode, ImagingPalette palette, int dither ) { ImagingSectionCookie cookie; ImagingShuffler convert; @@ -1605,22 +1482,22 @@ convert( mode = imIn->palette->mode; } else { /* Same mode? */ - if (!strcmp(imIn->mode, mode)) { + if (imIn->mode == mode) { return ImagingCopy2(imOut, imIn); } } /* test for special conversions */ - if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "PA") == 0) { + if (imIn->mode == IMAGING_MODE_P || imIn->mode == IMAGING_MODE_PA) { return frompalette(imOut, imIn, mode); } - if (strcmp(mode, "P") == 0 || strcmp(mode, "PA") == 0) { + if (mode == IMAGING_MODE_P || mode == IMAGING_MODE_PA) { return topalette(imOut, imIn, mode, palette, dither); } - if (dither && strcmp(mode, "1") == 0) { + if (dither && mode == IMAGING_MODE_1) { return tobilevel(imOut, imIn); } @@ -1629,8 +1506,7 @@ convert( convert = NULL; for (y = 0; converters[y].from; y++) { - if (!strcmp(imIn->mode, converters[y].from) && - !strcmp(mode, converters[y].to)) { + if (imIn->mode == converters[y].from && mode == converters[y].to) { convert = converters[y].convert; break; } @@ -1642,7 +1518,7 @@ convert( #else static char buf[100]; snprintf( - buf, 100, "conversion from %.10s to %.10s not supported", imIn->mode, mode + buf, 100, "conversion from %.10s to %.10s not supported", imIn->mode->name, mode->name ); return (Imaging)ImagingError_ValueError(buf); #endif @@ -1663,7 +1539,7 @@ convert( } Imaging -ImagingConvert(Imaging imIn, const char *mode, ImagingPalette palette, int dither) { +ImagingConvert(Imaging imIn, const Mode *mode, ImagingPalette palette, int dither) { return convert(NULL, imIn, mode, palette, dither); } @@ -1673,7 +1549,7 @@ ImagingConvert2(Imaging imOut, Imaging imIn) { } Imaging -ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) { +ImagingConvertTransparent(Imaging imIn, const Mode *mode, int r, int g, int b) { ImagingSectionCookie cookie; ImagingShuffler convert; Imaging imOut = NULL; @@ -1687,27 +1563,30 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) { return (Imaging)ImagingError_ModeError(); } - if (strcmp(imIn->mode, "RGB") == 0 && - (strcmp(mode, "RGBA") == 0 || strcmp(mode, "RGBa") == 0)) { + if (imIn->mode == IMAGING_MODE_RGB && (mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_RGBa)) { convert = rgb2rgba; - if (strcmp(mode, "RGBa") == 0) { + if (mode == IMAGING_MODE_RGBa) { premultiplied = 1; } - } else if (strcmp(imIn->mode, "RGB") == 0 && - (strcmp(mode, "LA") == 0 || strcmp(mode, "La") == 0)) { + } else if (imIn->mode == IMAGING_MODE_RGB && (mode == IMAGING_MODE_LA || mode == IMAGING_MODE_La)) { convert = rgb2la; source_transparency = 1; - if (strcmp(mode, "La") == 0) { + if (mode == IMAGING_MODE_La) { premultiplied = 1; } - } else if ((strcmp(imIn->mode, "1") == 0 || strcmp(imIn->mode, "I") == 0 || - strcmp(imIn->mode, "I;16") == 0 || strcmp(imIn->mode, "L") == 0) && - (strcmp(mode, "RGBA") == 0 || strcmp(mode, "LA") == 0)) { - if (strcmp(imIn->mode, "1") == 0) { + } else if ((imIn->mode == IMAGING_MODE_1 || + imIn->mode == IMAGING_MODE_I || + imIn->mode == IMAGING_MODE_I_16 || + imIn->mode == IMAGING_MODE_L + ) && ( + mode == IMAGING_MODE_RGBA || + mode == IMAGING_MODE_LA + )) { + if (imIn->mode == IMAGING_MODE_1) { convert = bit2rgb; - } else if (strcmp(imIn->mode, "I") == 0) { + } else if (imIn->mode == IMAGING_MODE_I) { convert = i2rgb; - } else if (strcmp(imIn->mode, "I;16") == 0) { + } else if (imIn->mode == IMAGING_MODE_I_16) { convert = I16_RGB; } else { convert = l2rgb; @@ -1719,8 +1598,8 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) { buf, 100, "conversion from %.10s to %.10s not supported in convert_transparent", - imIn->mode, - mode + imIn->mode->name, + mode->name ); return (Imaging)ImagingError_ValueError(buf); } @@ -1743,15 +1622,15 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) { } Imaging -ImagingConvertInPlace(Imaging imIn, const char *mode) { +ImagingConvertInPlace(Imaging imIn, const Mode *mode) { ImagingSectionCookie cookie; ImagingShuffler convert; int y; /* limited support for inplace conversion */ - if (strcmp(imIn->mode, "L") == 0 && strcmp(mode, "1") == 0) { + if (imIn->mode == IMAGING_MODE_L && mode == IMAGING_MODE_1) { convert = l2bit; - } else if (strcmp(imIn->mode, "1") == 0 && strcmp(mode, "L") == 0) { + } else if (imIn->mode == IMAGING_MODE_1 && mode == IMAGING_MODE_L) { convert = bit2l; } else { return ImagingError_ModeError(); @@ -1765,3 +1644,154 @@ ImagingConvertInPlace(Imaging imIn, const char *mode) { return imIn; } + +/* ------------------ */ +/* Converter mappings */ +/* ------------------ */ + +void +ImagingConvertInit() { + const struct Converter temp[] = { + {IMAGING_MODE_1, IMAGING_MODE_L, bit2l}, + {IMAGING_MODE_1, IMAGING_MODE_I, bit2i}, + {IMAGING_MODE_1, IMAGING_MODE_F, bit2f}, + {IMAGING_MODE_1, IMAGING_MODE_RGB, bit2rgb}, + {IMAGING_MODE_1, IMAGING_MODE_RGBA, bit2rgb}, + {IMAGING_MODE_1, IMAGING_MODE_RGBX, bit2rgb}, + {IMAGING_MODE_1, IMAGING_MODE_CMYK, bit2cmyk}, + {IMAGING_MODE_1, IMAGING_MODE_YCbCr, bit2ycbcr}, + {IMAGING_MODE_1, IMAGING_MODE_HSV, bit2hsv}, + + {IMAGING_MODE_L, IMAGING_MODE_1, l2bit}, + {IMAGING_MODE_L, IMAGING_MODE_LA, l2la}, + {IMAGING_MODE_L, IMAGING_MODE_I, l2i}, + {IMAGING_MODE_L, IMAGING_MODE_F, l2f}, + {IMAGING_MODE_L, IMAGING_MODE_RGB, l2rgb}, + {IMAGING_MODE_L, IMAGING_MODE_RGBA, l2rgb}, + {IMAGING_MODE_L, IMAGING_MODE_RGBX, l2rgb}, + {IMAGING_MODE_L, IMAGING_MODE_CMYK, l2cmyk}, + {IMAGING_MODE_L, IMAGING_MODE_YCbCr, l2ycbcr}, + {IMAGING_MODE_L, IMAGING_MODE_HSV, l2hsv}, + + {IMAGING_MODE_LA, IMAGING_MODE_L, la2l}, + {IMAGING_MODE_LA, IMAGING_MODE_La, lA2la}, + {IMAGING_MODE_LA, IMAGING_MODE_RGB, la2rgb}, + {IMAGING_MODE_LA, IMAGING_MODE_RGBA, la2rgb}, + {IMAGING_MODE_LA, IMAGING_MODE_RGBX, la2rgb}, + {IMAGING_MODE_LA, IMAGING_MODE_CMYK, la2cmyk}, + {IMAGING_MODE_LA, IMAGING_MODE_YCbCr, la2ycbcr}, + {IMAGING_MODE_LA, IMAGING_MODE_HSV, la2hsv}, + + {IMAGING_MODE_La, IMAGING_MODE_LA, la2lA}, + + {IMAGING_MODE_I, IMAGING_MODE_L, i2l}, + {IMAGING_MODE_I, IMAGING_MODE_F, i2f}, + {IMAGING_MODE_I, IMAGING_MODE_RGB, i2rgb}, + {IMAGING_MODE_I, IMAGING_MODE_RGBA, i2rgb}, + {IMAGING_MODE_I, IMAGING_MODE_RGBX, i2rgb}, + {IMAGING_MODE_I, IMAGING_MODE_HSV, i2hsv}, + + {IMAGING_MODE_F, IMAGING_MODE_L, f2l}, + {IMAGING_MODE_F, IMAGING_MODE_I, f2i}, + + {IMAGING_MODE_RGB, IMAGING_MODE_1, rgb2bit}, + {IMAGING_MODE_RGB, IMAGING_MODE_L, rgb2l}, + {IMAGING_MODE_RGB, IMAGING_MODE_LA, rgb2la}, + {IMAGING_MODE_RGB, IMAGING_MODE_La, rgb2la}, + {IMAGING_MODE_RGB, IMAGING_MODE_I, rgb2i}, + {IMAGING_MODE_RGB, IMAGING_MODE_I_16, rgb2i16l}, + {IMAGING_MODE_RGB, IMAGING_MODE_I_16L, rgb2i16l}, + {IMAGING_MODE_RGB, IMAGING_MODE_I_16B, rgb2i16b}, +#ifdef WORDS_BIGENDIAN + {IMAGING_MODE_RGB, IMAGING_MODE_I_16N, rgb2i16b}, +#else + {IMAGING_MODE_RGB, IMAGING_MODE_I_16N, rgb2i16l}, +#endif + {IMAGING_MODE_RGB, IMAGING_MODE_F, rgb2f}, + {IMAGING_MODE_RGB, IMAGING_MODE_BGR_15, rgb2bgr15}, + {IMAGING_MODE_RGB, IMAGING_MODE_BGR_16, rgb2bgr16}, + {IMAGING_MODE_RGB, IMAGING_MODE_BGR_24, rgb2bgr24}, + {IMAGING_MODE_RGB, IMAGING_MODE_RGBA, rgb2rgba}, + {IMAGING_MODE_RGB, IMAGING_MODE_RGBa, rgb2rgba}, + {IMAGING_MODE_RGB, IMAGING_MODE_RGBX, rgb2rgba}, + {IMAGING_MODE_RGB, IMAGING_MODE_CMYK, rgb2cmyk}, + {IMAGING_MODE_RGB, IMAGING_MODE_YCbCr, ImagingConvertRGB2YCbCr}, + {IMAGING_MODE_RGB, IMAGING_MODE_HSV, rgb2hsv}, + + {IMAGING_MODE_RGBA, IMAGING_MODE_1, rgb2bit}, + {IMAGING_MODE_RGBA, IMAGING_MODE_L, rgb2l}, + {IMAGING_MODE_RGBA, IMAGING_MODE_LA, rgba2la}, + {IMAGING_MODE_RGBA, IMAGING_MODE_I, rgb2i}, + {IMAGING_MODE_RGBA, IMAGING_MODE_F, rgb2f}, + {IMAGING_MODE_RGBA, IMAGING_MODE_RGB, rgba2rgb}, + {IMAGING_MODE_RGBA, IMAGING_MODE_RGBa, rgbA2rgba}, + {IMAGING_MODE_RGBA, IMAGING_MODE_RGBX, rgb2rgba}, + {IMAGING_MODE_RGBA, IMAGING_MODE_CMYK, rgb2cmyk}, + {IMAGING_MODE_RGBA, IMAGING_MODE_YCbCr, ImagingConvertRGB2YCbCr}, + {IMAGING_MODE_RGBA, IMAGING_MODE_HSV, rgb2hsv}, + + {IMAGING_MODE_RGBa, IMAGING_MODE_RGBA, rgba2rgbA}, + {IMAGING_MODE_RGBa, IMAGING_MODE_RGB, rgba2rgb_}, + + {IMAGING_MODE_RGBX, IMAGING_MODE_1, rgb2bit}, + {IMAGING_MODE_RGBX, IMAGING_MODE_L, rgb2l}, + {IMAGING_MODE_RGBX, IMAGING_MODE_LA, rgb2la}, + {IMAGING_MODE_RGBX, IMAGING_MODE_I, rgb2i}, + {IMAGING_MODE_RGBX, IMAGING_MODE_F, rgb2f}, + {IMAGING_MODE_RGBX, IMAGING_MODE_RGB, rgba2rgb}, + {IMAGING_MODE_RGBX, IMAGING_MODE_CMYK, rgb2cmyk}, + {IMAGING_MODE_RGBX, IMAGING_MODE_YCbCr, ImagingConvertRGB2YCbCr}, + {IMAGING_MODE_RGBX, IMAGING_MODE_HSV, rgb2hsv}, + + {IMAGING_MODE_CMYK, IMAGING_MODE_RGB, cmyk2rgb}, + {IMAGING_MODE_CMYK, IMAGING_MODE_RGBA, cmyk2rgb}, + {IMAGING_MODE_CMYK, IMAGING_MODE_RGBX, cmyk2rgb}, + {IMAGING_MODE_CMYK, IMAGING_MODE_HSV, cmyk2hsv}, + + {IMAGING_MODE_YCbCr, IMAGING_MODE_L, ycbcr2l}, + {IMAGING_MODE_YCbCr, IMAGING_MODE_LA, ycbcr2la}, + {IMAGING_MODE_YCbCr, IMAGING_MODE_RGB, ImagingConvertYCbCr2RGB}, + + {IMAGING_MODE_HSV, IMAGING_MODE_RGB, hsv2rgb}, + + {IMAGING_MODE_I, IMAGING_MODE_I_16, I_I16L}, + {IMAGING_MODE_I_16, IMAGING_MODE_I, I16L_I}, + {IMAGING_MODE_I_16, IMAGING_MODE_RGB, I16_RGB}, + {IMAGING_MODE_L, IMAGING_MODE_I_16, L_I16L}, + {IMAGING_MODE_I_16, IMAGING_MODE_L, I16L_L}, + + {IMAGING_MODE_I, IMAGING_MODE_I_16L, I_I16L}, + {IMAGING_MODE_I_16L, IMAGING_MODE_I, I16L_I}, + {IMAGING_MODE_I, IMAGING_MODE_I_16B, I_I16B}, + {IMAGING_MODE_I_16B, IMAGING_MODE_I, I16B_I}, + + {IMAGING_MODE_L, IMAGING_MODE_I_16L, L_I16L}, + {IMAGING_MODE_I_16L, IMAGING_MODE_L, I16L_L}, + {IMAGING_MODE_L, IMAGING_MODE_I_16B, L_I16B}, + {IMAGING_MODE_I_16B, IMAGING_MODE_L, I16B_L}, +#ifdef WORDS_BIGENDIAN + {IMAGING_MODE_L, IMAGING_MODE_I_16N, L_I16B}, + {IMAGING_MODE_I_16N, IMAGING_MODE_L, I16B_L}, +#else + {IMAGING_MODE_L, IMAGING_MODE_I_16N, L_I16L}, + {IMAGING_MODE_I_16N, IMAGING_MODE_L, I16L_L}, +#endif + + {IMAGING_MODE_I_16, IMAGING_MODE_F, I16L_F}, + {IMAGING_MODE_I_16L, IMAGING_MODE_F, I16L_F}, + {IMAGING_MODE_I_16B, IMAGING_MODE_F, I16B_F}, + + {NULL} + }; + converters = malloc(sizeof(temp)); + if (converters == NULL) { + fprintf(stderr, "ConvertInit: failed to allocate memory for converter table\n"); + exit(1); + } + memcpy(converters, temp, sizeof(temp)); +} + +void +ImagingConvertFree() { + free(converters); +} diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index 49f17f0da..c7e069313 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -181,6 +181,19 @@ typedef struct ImagingMemoryArena { #endif } *ImagingMemoryArena; +/* Memory Management */ +/* ----------------- */ + +extern void +ImagingAccessInit(void); +extern void +ImagingAccessFree(void); + +extern void +ImagingConvertInit(void); +extern void +ImagingConvertFree(void); + /* Objects */ /* ------- */ @@ -224,8 +237,6 @@ ImagingCopyPalette(Imaging destination, Imaging source); extern void ImagingHistogramDelete(ImagingHistogram histogram); -extern void -ImagingAccessInit(void); extern ImagingAccess ImagingAccessNew(Imaging im); extern void From bcfe5f21729a895388ac1385120dd7a60a6876c4 Mon Sep 17 00:00:00 2001 From: eyedav <88885346+eyedav@users.noreply.github.com> Date: Sat, 19 Jul 2025 15:37:03 +0200 Subject: [PATCH 062/309] use mode structs in Draw.c --- src/libImaging/Draw.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index 27cac687e..b94630473 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -68,7 +68,7 @@ typedef void (*hline_handler)(Imaging, int, int, int, int, Imaging); static inline void point8(Imaging im, int x, int y, int ink) { if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize) { - if (strncmp(im->mode, "I;16", 4) == 0) { + if (strncmp(im->mode->name, "I;16", 4) == 0) { #ifdef WORDS_BIGENDIAN im->image8[y][x * 2] = (UINT8)(ink >> 8); im->image8[y][x * 2 + 1] = (UINT8)ink; @@ -117,13 +117,13 @@ hline8(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) { } if (x0 <= x1) { int bigendian = -1; - if (strncmp(im->mode, "I;16", 4) == 0) { + if (isModeI16(im->mode)) { bigendian = ( #ifdef WORDS_BIGENDIAN - strcmp(im->mode, "I;16") == 0 || strcmp(im->mode, "I;16L") == 0 + im->mode == IMAGING_MODE_I_16 || im->mode == IMAGING_MODE_I_16L #else - strcmp(im->mode, "I;16B") == 0 + im->mode == IMAGING_MODE_I_16B #endif ) ? 1 @@ -672,17 +672,17 @@ DRAW draw32rgba = {point32rgba, hline32rgba, line32rgba}; /* Interface */ /* -------------------------------------------------------------------- */ -#define DRAWINIT() \ - if (im->image8) { \ - draw = &draw8; \ - if (strncmp(im->mode, "I;16", 4) == 0) { \ - ink = INK16(ink_); \ - } else { \ - ink = INK8(ink_); \ - } \ - } else { \ - draw = (op) ? &draw32rgba : &draw32; \ - memcpy(&ink, ink_, sizeof(ink)); \ +#define DRAWINIT() \ + if (im->image8) { \ + draw = &draw8; \ + if (strncmp(im->mode->name, "I;16", 4) == 0) { \ + ink = INK16(ink_); \ + } else { \ + ink = INK8(ink_); \ + } \ + } else { \ + draw = (op) ? &draw32rgba : &draw32; \ + memcpy(&ink, ink_, sizeof(ink)); \ } int From b5c4b821bca4373cfb60da07d35bfd4f7f4ff4a5 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 21 Apr 2024 19:22:21 -0500 Subject: [PATCH 063/309] use mode structs in Effects.c --- src/libImaging/Effects.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libImaging/Effects.c b/src/libImaging/Effects.c index 93e7af0bc..c05c5764e 100644 --- a/src/libImaging/Effects.c +++ b/src/libImaging/Effects.c @@ -36,7 +36,7 @@ ImagingEffectMandelbrot(int xsize, int ysize, double extent[4], int quality) { return (Imaging)ImagingError_ValueError(NULL); } - im = ImagingNewDirty("L", xsize, ysize); + im = ImagingNewDirty(IMAGING_MODE_L, xsize, ysize); if (!im) { return NULL; } @@ -80,7 +80,7 @@ ImagingEffectNoise(int xsize, int ysize, float sigma) { int nextok; double this, next; - imOut = ImagingNewDirty("L", xsize, ysize); + imOut = ImagingNewDirty(IMAGING_MODE_L, xsize, ysize); if (!imOut) { return NULL; } From 19c0d1da769f153fc91e5f3da2d23b4cc9cf4fc5 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 21 Apr 2024 19:25:46 -0500 Subject: [PATCH 064/309] use mode structs in File.c --- src/libImaging/File.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libImaging/File.c b/src/libImaging/File.c index 901fe83ad..435dbeca0 100644 --- a/src/libImaging/File.c +++ b/src/libImaging/File.c @@ -23,14 +23,13 @@ int ImagingSaveRaw(Imaging im, FILE *fp) { int x, y, i; - if (strcmp(im->mode, "1") == 0 || strcmp(im->mode, "L") == 0) { + if (im->mode == IMAGING_MODE_1 || im->mode == IMAGING_MODE_L) { /* @PIL227: FIXME: for mode "1", map != 0 to 255 */ /* PGM "L" */ for (y = 0; y < im->ysize; y++) { fwrite(im->image[y], 1, im->xsize, fp); } - } else { /* PPM "RGB" or other internal format */ for (y = 0; y < im->ysize; y++) { @@ -58,10 +57,10 @@ ImagingSavePPM(Imaging im, const char *outfile) { return 0; } - if (strcmp(im->mode, "1") == 0 || strcmp(im->mode, "L") == 0) { + if (im->mode == IMAGING_MODE_1 || im->mode == IMAGING_MODE_L) { /* Write "PGM" */ fprintf(fp, "P5\n%d %d\n255\n", im->xsize, im->ysize); - } else if (strcmp(im->mode, "RGB") == 0) { + } else if (im->mode == IMAGING_MODE_RGB) { /* Write "PPM" */ fprintf(fp, "P6\n%d %d\n255\n", im->xsize, im->ysize); } else { From 6202eefcff942fea2773c02b6605f27d5c4cf87e Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 21 Apr 2024 19:27:30 -0500 Subject: [PATCH 065/309] use mode structs in Fill.c --- src/libImaging/Fill.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/libImaging/Fill.c b/src/libImaging/Fill.c index 28f427370..854cdb9fe 100644 --- a/src/libImaging/Fill.c +++ b/src/libImaging/Fill.c @@ -68,11 +68,14 @@ ImagingFill(Imaging im, const void *colour) { } Imaging -ImagingFillLinearGradient(const char *mode) { +ImagingFillLinearGradient(const Mode *mode) { Imaging im; int y; - if (strlen(mode) != 1) { + if (mode != IMAGING_MODE_1 && mode != IMAGING_MODE_F && + mode != IMAGING_MODE_I && mode != IMAGING_MODE_L && + mode != IMAGING_MODE_P + ) { return (Imaging)ImagingError_ModeError(); } @@ -102,12 +105,15 @@ ImagingFillLinearGradient(const char *mode) { } Imaging -ImagingFillRadialGradient(const char *mode) { +ImagingFillRadialGradient(const Mode *mode) { Imaging im; int x, y; int d; - if (strlen(mode) != 1) { + if (mode != IMAGING_MODE_1 && mode != IMAGING_MODE_F && + mode != IMAGING_MODE_I && mode != IMAGING_MODE_L && + mode != IMAGING_MODE_P + ) { return (Imaging)ImagingError_ModeError(); } From af22363327dd0d9f5684b12834f2558f8a3acdce Mon Sep 17 00:00:00 2001 From: eyedav <88885346+eyedav@users.noreply.github.com> Date: Sat, 19 Jul 2025 15:39:16 +0200 Subject: [PATCH 066/309] use mode structs in Filter.c --- src/libImaging/Filter.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/libImaging/Filter.c b/src/libImaging/Filter.c index c46dd3cd1..48f210809 100644 --- a/src/libImaging/Filter.c +++ b/src/libImaging/Filter.c @@ -155,10 +155,9 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) { } else { int bigendian = 0; if (im->type == IMAGING_TYPE_SPECIAL) { - if ( - strcmp(im->mode, "I;16B") == 0 + if (im->mode == IMAGING_MODE_I_16B #ifdef WORDS_BIGENDIAN - || strcmp(im->mode, "I;16N") == 0 + || im->mode == IMAGING_MODE_I_16N #endif ) { bigendian = 1; @@ -309,10 +308,9 @@ ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) { } else { int bigendian = 0; if (im->type == IMAGING_TYPE_SPECIAL) { - if ( - strcmp(im->mode, "I;16B") == 0 + if (im->mode == IMAGING_MODE_I_16B #ifdef WORDS_BIGENDIAN - || strcmp(im->mode, "I;16N") == 0 + || im->mode == IMAGING_MODE_I_16N #endif ) { bigendian = 1; From cfe9155a0b9b7bd4435d9abfbc61b127a4d415b5 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sat, 12 Oct 2024 21:10:41 -0500 Subject: [PATCH 067/309] use mode structs in Geometry.c --- src/libImaging/Geometry.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/libImaging/Geometry.c b/src/libImaging/Geometry.c index 1e2abd7e7..c141ad2a1 100644 --- a/src/libImaging/Geometry.c +++ b/src/libImaging/Geometry.c @@ -19,7 +19,7 @@ ImagingFlipLeftRight(Imaging imOut, Imaging imIn) { ImagingSectionCookie cookie; int x, y, xr; - if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) { + if (!imOut || !imIn || imIn->mode != imOut->mode) { return (Imaging)ImagingError_ModeError(); } if (imIn->xsize != imOut->xsize || imIn->ysize != imOut->ysize) { @@ -41,7 +41,7 @@ ImagingFlipLeftRight(Imaging imOut, Imaging imIn) { ImagingSectionEnter(&cookie); if (imIn->image8) { - if (strncmp(imIn->mode, "I;16", 4) == 0) { + if (strncmp(imIn->mode->name, "I;16", 4) == 0) { FLIP_LEFT_RIGHT(UINT16, image8) } else { FLIP_LEFT_RIGHT(UINT8, image8) @@ -62,7 +62,7 @@ ImagingFlipTopBottom(Imaging imOut, Imaging imIn) { ImagingSectionCookie cookie; int y, yr; - if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) { + if (!imOut || !imIn || imIn->mode != imOut->mode) { return (Imaging)ImagingError_ModeError(); } if (imIn->xsize != imOut->xsize || imIn->ysize != imOut->ysize) { @@ -89,7 +89,7 @@ ImagingRotate90(Imaging imOut, Imaging imIn) { int x, y, xx, yy, xr, xxsize, yysize; int xxx, yyy, xxxsize, yyysize; - if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) { + if (!imOut || !imIn || imIn->mode != imOut->mode) { return (Imaging)ImagingError_ModeError(); } if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) { @@ -127,7 +127,7 @@ ImagingRotate90(Imaging imOut, Imaging imIn) { ImagingSectionEnter(&cookie); if (imIn->image8) { - if (strncmp(imIn->mode, "I;16", 4) == 0) { + if (strncmp(imIn->mode->name, "I;16", 4) == 0) { ROTATE_90(UINT16, image8); } else { ROTATE_90(UINT8, image8); @@ -149,7 +149,7 @@ ImagingTranspose(Imaging imOut, Imaging imIn) { int x, y, xx, yy, xxsize, yysize; int xxx, yyy, xxxsize, yyysize; - if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) { + if (!imOut || !imIn || imIn->mode != imOut->mode) { return (Imaging)ImagingError_ModeError(); } if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) { @@ -186,7 +186,7 @@ ImagingTranspose(Imaging imOut, Imaging imIn) { ImagingSectionEnter(&cookie); if (imIn->image8) { - if (strncmp(imIn->mode, "I;16", 4) == 0) { + if (strncmp(imIn->mode->name, "I;16", 4) == 0) { TRANSPOSE(UINT16, image8); } else { TRANSPOSE(UINT8, image8); @@ -208,7 +208,7 @@ ImagingTransverse(Imaging imOut, Imaging imIn) { int x, y, xr, yr, xx, yy, xxsize, yysize; int xxx, yyy, xxxsize, yyysize; - if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) { + if (!imOut || !imIn || imIn->mode != imOut->mode) { return (Imaging)ImagingError_ModeError(); } if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) { @@ -247,7 +247,7 @@ ImagingTransverse(Imaging imOut, Imaging imIn) { ImagingSectionEnter(&cookie); if (imIn->image8) { - if (strncmp(imIn->mode, "I;16", 4) == 0) { + if (strncmp(imIn->mode->name, "I;16", 4) == 0) { TRANSVERSE(UINT16, image8); } else { TRANSVERSE(UINT8, image8); @@ -268,7 +268,7 @@ ImagingRotate180(Imaging imOut, Imaging imIn) { ImagingSectionCookie cookie; int x, y, xr, yr; - if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) { + if (!imOut || !imIn || imIn->mode != imOut->mode) { return (Imaging)ImagingError_ModeError(); } if (imIn->xsize != imOut->xsize || imIn->ysize != imOut->ysize) { @@ -291,7 +291,7 @@ ImagingRotate180(Imaging imOut, Imaging imIn) { yr = imIn->ysize - 1; if (imIn->image8) { - if (strncmp(imIn->mode, "I;16", 4) == 0) { + if (strncmp(imIn->mode->name, "I;16", 4) == 0) { ROTATE_180(UINT16, image8) } else { ROTATE_180(UINT8, image8) @@ -313,7 +313,7 @@ ImagingRotate270(Imaging imOut, Imaging imIn) { int x, y, xx, yy, yr, xxsize, yysize; int xxx, yyy, xxxsize, yyysize; - if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) { + if (!imOut || !imIn || imIn->mode != imOut->mode) { return (Imaging)ImagingError_ModeError(); } if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) { @@ -351,7 +351,7 @@ ImagingRotate270(Imaging imOut, Imaging imIn) { ImagingSectionEnter(&cookie); if (imIn->image8) { - if (strncmp(imIn->mode, "I;16", 4) == 0) { + if (strncmp(imIn->mode->name, "I;16", 4) == 0) { ROTATE_270(UINT16, image8); } else { ROTATE_270(UINT8, image8); @@ -791,7 +791,7 @@ ImagingGenericTransform( char *out; double xx, yy; - if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) { + if (!imOut || !imIn || imIn->mode != imOut->mode) { return (Imaging)ImagingError_ModeError(); } @@ -848,7 +848,7 @@ ImagingScaleAffine( int xmin, xmax; int *xintab; - if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) { + if (!imOut || !imIn || imIn->mode != imOut->mode) { return (Imaging)ImagingError_ModeError(); } @@ -1035,7 +1035,7 @@ ImagingTransformAffine( double xx, yy; double xo, yo; - if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) { + if (!imOut || !imIn || imIn->mode != imOut->mode) { return (Imaging)ImagingError_ModeError(); } From 2668338583ed765f5e2843650e9784c32652e271 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 21 Apr 2024 19:35:06 -0500 Subject: [PATCH 068/309] use mode structs in GetBBox.c --- src/libImaging/GetBBox.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/libImaging/GetBBox.c b/src/libImaging/GetBBox.c index d430893dd..3719a9f15 100644 --- a/src/libImaging/GetBBox.c +++ b/src/libImaging/GetBBox.c @@ -89,10 +89,11 @@ ImagingGetBBox(Imaging im, int bbox[4], int alpha_only) { INT32 mask = 0xffffffff; if (im->bands == 3) { ((UINT8 *)&mask)[3] = 0; - } else if (alpha_only && - (strcmp(im->mode, "RGBa") == 0 || strcmp(im->mode, "RGBA") == 0 || - strcmp(im->mode, "La") == 0 || strcmp(im->mode, "LA") == 0 || - strcmp(im->mode, "PA") == 0)) { + } else if (alpha_only && ( + im->mode == IMAGING_MODE_RGBa || im->mode == IMAGING_MODE_RGBA || + im->mode == IMAGING_MODE_La || im->mode == IMAGING_MODE_LA || + im->mode == IMAGING_MODE_PA + )) { #ifdef WORDS_BIGENDIAN mask = 0x000000ff; #else @@ -208,7 +209,7 @@ ImagingGetExtrema(Imaging im, void *extrema) { memcpy(((char *)extrema) + sizeof(fmin), &fmax, sizeof(fmax)); break; case IMAGING_TYPE_SPECIAL: - if (strcmp(im->mode, "I;16") == 0) { + if (im->mode == IMAGING_MODE_I_16) { UINT16 v; UINT8 *pixel = *im->image8; #ifdef WORDS_BIGENDIAN From 27497700ee55e6c42cf4428f3a9dfb34ccdecbe2 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 21 Apr 2024 19:38:07 -0500 Subject: [PATCH 069/309] use mode structs in Histo.c --- src/libImaging/Histo.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libImaging/Histo.c b/src/libImaging/Histo.c index c5a547a64..cfbf8333d 100644 --- a/src/libImaging/Histo.c +++ b/src/libImaging/Histo.c @@ -43,10 +43,10 @@ ImagingHistogramNew(Imaging im) { if (!h) { return (ImagingHistogram)ImagingError_MemoryError(); } - strncpy(h->mode, im->mode, IMAGING_MODE_LENGTH - 1); - h->mode[IMAGING_MODE_LENGTH - 1] = 0; + h->mode = im->mode; h->bands = im->bands; + h->histogram = calloc(im->pixelsize, 256 * sizeof(long)); if (!h->histogram) { free(h); @@ -73,7 +73,7 @@ ImagingGetHistogram(Imaging im, Imaging imMask, void *minmax) { if (im->xsize != imMask->xsize || im->ysize != imMask->ysize) { return ImagingError_Mismatch(); } - if (strcmp(imMask->mode, "1") != 0 && strcmp(imMask->mode, "L") != 0) { + if (imMask->mode != IMAGING_MODE_1 && imMask->mode != IMAGING_MODE_L) { return ImagingError_ValueError("bad transparency mask"); } } From 33272580d020ea7eed6f5b735fcceb5b36b7ec80 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 21 Apr 2024 19:45:28 -0500 Subject: [PATCH 070/309] use mode structs in Jpeg2KDecode.c --- src/libImaging/Jpeg2KDecode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/Jpeg2KDecode.c b/src/libImaging/Jpeg2KDecode.c index cc6955ca5..3cbe2965d 100644 --- a/src/libImaging/Jpeg2KDecode.c +++ b/src/libImaging/Jpeg2KDecode.c @@ -771,7 +771,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) { if (color_space == j2k_unpackers[n].color_space && image->numcomps == j2k_unpackers[n].components && (j2k_unpackers[n].subsampling || (subsampling == -1)) && - strcmp(im->mode, j2k_unpackers[n].mode) == 0) { + strcmp(im->mode->name, j2k_unpackers[n].mode) == 0) { unpack = j2k_unpackers[n].unpacker; break; } From 98a2c63326b3c64ea9aafd1f25ea33bfb37b935d Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 21 Apr 2024 19:52:05 -0500 Subject: [PATCH 071/309] use mode structs in Jpeg2KEncode.c --- src/libImaging/Jpeg2KEncode.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libImaging/Jpeg2KEncode.c b/src/libImaging/Jpeg2KEncode.c index 61e095ad6..67290f674 100644 --- a/src/libImaging/Jpeg2KEncode.c +++ b/src/libImaging/Jpeg2KEncode.c @@ -305,28 +305,28 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) { #endif /* Setup an opj_image */ - if (strcmp(im->mode, "L") == 0) { + if (im->mode == IMAGING_MODE_L) { components = 1; color_space = OPJ_CLRSPC_GRAY; pack = j2k_pack_l; - } else if (strcmp(im->mode, "I;16") == 0 || strcmp(im->mode, "I;16B") == 0) { + } else if (im->mode == IMAGING_MODE_I_16 || im->mode == IMAGING_MODE_I_16B) { components = 1; color_space = OPJ_CLRSPC_GRAY; pack = j2k_pack_i16; prec = 16; - } else if (strcmp(im->mode, "LA") == 0) { + } else if (im->mode == IMAGING_MODE_LA) { components = 2; color_space = OPJ_CLRSPC_GRAY; pack = j2k_pack_la; - } else if (strcmp(im->mode, "RGB") == 0) { + } else if (im->mode == IMAGING_MODE_RGB) { components = 3; color_space = OPJ_CLRSPC_SRGB; pack = j2k_pack_rgb; - } else if (strcmp(im->mode, "YCbCr") == 0) { + } else if (im->mode == IMAGING_MODE_YCbCr) { components = 3; color_space = OPJ_CLRSPC_SYCC; pack = j2k_pack_rgb; - } else if (strcmp(im->mode, "RGBA") == 0) { + } else if (im->mode == IMAGING_MODE_RGBA) { components = 4; color_space = OPJ_CLRSPC_SRGB; pack = j2k_pack_rgba; @@ -497,9 +497,9 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) { goto quick_exit; } - if (strcmp(im->mode, "RGBA") == 0) { + if (im->mode == IMAGING_MODE_RGBA) { image->comps[3].alpha = 1; - } else if (strcmp(im->mode, "LA") == 0) { + } else if (im->mode == IMAGING_MODE_LA) { image->comps[1].alpha = 1; } From 30d4cd02296eec22d5acb19c74c02597f65991ab Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 21 Apr 2024 19:58:58 -0500 Subject: [PATCH 072/309] use mode structs in JpegDecode.c --- src/libImaging/JpegDecode.c | 14 +++++++------- src/libImaging/Mode.h | 2 ++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/libImaging/JpegDecode.c b/src/libImaging/JpegDecode.c index 2970f56d1..36eb7835a 100644 --- a/src/libImaging/JpegDecode.c +++ b/src/libImaging/JpegDecode.c @@ -196,22 +196,22 @@ ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t by /* rawmode indicates what we want from the decoder. if not set, conversions are disabled */ - if (strcmp(context->rawmode, "L") == 0) { + if (context->rawmode == IMAGING_RAWMODE_L) { context->cinfo.out_color_space = JCS_GRAYSCALE; - } else if (strcmp(context->rawmode, "RGB") == 0) { + } else if (context->rawmode == IMAGING_RAWMODE_RGB) { context->cinfo.out_color_space = JCS_RGB; } #ifdef JCS_EXTENSIONS - else if (strcmp(context->rawmode, "RGBX") == 0) { + else if (context->rawmode == IMAGING_RAWMODE_RGBX) { context->cinfo.out_color_space = JCS_EXT_RGBX; } #endif - else if (strcmp(context->rawmode, "CMYK") == 0 || - strcmp(context->rawmode, "CMYK;I") == 0) { + else if (context->rawmode == IMAGING_RAWMODE_CMYK || + context->rawmode == IMAGING_RAWMODE_CMYK_I) { context->cinfo.out_color_space = JCS_CMYK; - } else if (strcmp(context->rawmode, "YCbCr") == 0) { + } else if (context->rawmode == IMAGING_RAWMODE_YCbCr) { context->cinfo.out_color_space = JCS_YCbCr; - } else if (strcmp(context->rawmode, "YCbCrK") == 0) { + } else if (context->rawmode == IMAGING_RAWMODE_YCbCrK) { context->cinfo.out_color_space = JCS_YCCK; } else { /* Disable decoder conversions */ diff --git a/src/libImaging/Mode.h b/src/libImaging/Mode.h index bd184808d..36deddd02 100644 --- a/src/libImaging/Mode.h +++ b/src/libImaging/Mode.h @@ -79,7 +79,9 @@ extern const RawMode * const IMAGING_RAWMODE_I_32B; // Rawmodes extern const RawMode * const IMAGING_RAWMODE_1_R; +extern const RawMode * const IMAGING_RAWMODE_CMYK_I; extern const RawMode * const IMAGING_RAWMODE_YCC_P; +extern const RawMode * const IMAGING_RAWMODE_YCbCrK; const RawMode * findRawMode(const char * const name); From 0abfdd25b1dddbc0ca4fddf816dff9cbba837d14 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 21 Apr 2024 20:00:42 -0500 Subject: [PATCH 073/309] use mode structs in JpegEncode.c --- src/libImaging/JpegEncode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libImaging/JpegEncode.c b/src/libImaging/JpegEncode.c index 972435ee1..098e431fc 100644 --- a/src/libImaging/JpegEncode.c +++ b/src/libImaging/JpegEncode.c @@ -114,7 +114,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { break; case 24: context->cinfo.input_components = 3; - if (strcmp(im->mode, "YCbCr") == 0) { + if (im->mode == IMAGING_MODE_YCbCr) { context->cinfo.in_color_space = JCS_YCbCr; } else { context->cinfo.in_color_space = JCS_RGB; @@ -124,7 +124,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { context->cinfo.input_components = 4; context->cinfo.in_color_space = JCS_CMYK; #ifdef JCS_EXTENSIONS - if (strcmp(context->rawmode, "RGBX") == 0) { + if (context->rawmode == IMAGING_RAWMODE_RGBX) { context->cinfo.in_color_space = JCS_EXT_RGBX; } #endif From 378c3bd23d88e713fe5148d1cec35cf7126f6530 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 21 Apr 2024 20:03:46 -0500 Subject: [PATCH 074/309] use mode structs in Matrix.c --- src/libImaging/Matrix.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/libImaging/Matrix.c b/src/libImaging/Matrix.c index ec7f4d93e..fd5584611 100644 --- a/src/libImaging/Matrix.c +++ b/src/libImaging/Matrix.c @@ -18,7 +18,7 @@ #define CLIPF(v) ((v <= 0.0) ? 0 : (v >= 255.0F) ? 255 : (UINT8)v) Imaging -ImagingConvertMatrix(Imaging im, const char *mode, float m[]) { +ImagingConvertMatrix(Imaging im, const Mode *mode, float m[]) { Imaging imOut; int x, y; ImagingSectionCookie cookie; @@ -28,8 +28,8 @@ ImagingConvertMatrix(Imaging im, const char *mode, float m[]) { return (Imaging)ImagingError_ModeError(); } - if (strcmp(mode, "L") == 0) { - imOut = ImagingNewDirty("L", im->xsize, im->ysize); + if (mode == IMAGING_MODE_L) { + imOut = ImagingNewDirty(IMAGING_MODE_L, im->xsize, im->ysize); if (!imOut) { return NULL; } @@ -46,8 +46,7 @@ ImagingConvertMatrix(Imaging im, const char *mode, float m[]) { } } ImagingSectionLeave(&cookie); - - } else if (strlen(mode) == 3) { + } else if (strlen(mode->name) == 3) { imOut = ImagingNewDirty(mode, im->xsize, im->ysize); if (!imOut) { return NULL; From 49062856196ce78bbf9269aa41f65126b04de706 Mon Sep 17 00:00:00 2001 From: eyedav <88885346+eyedav@users.noreply.github.com> Date: Sat, 19 Jul 2025 15:41:13 +0200 Subject: [PATCH 075/309] add function isModeI16() to check if a mode is an I;16 mode --- src/_imaging.c | 6 +----- src/libImaging/Draw.c | 24 ++++++++++++------------ src/libImaging/Geometry.c | 12 ++++++------ src/libImaging/Mode.c | 7 +++++++ src/libImaging/Mode.h | 2 ++ src/libImaging/Paste.c | 6 +++--- src/map.c | 2 +- 7 files changed, 32 insertions(+), 27 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index 271a16dbb..bd7cc2233 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -662,11 +662,7 @@ getink(PyObject *color, Imaging im, char *ink) { memcpy(ink, &ftmp, sizeof(ftmp)); return ink; case IMAGING_TYPE_SPECIAL: - if (im->mode == IMAGING_MODE_I_16 - || im->mode == IMAGING_MODE_I_16L - || im->mode == IMAGING_MODE_I_16B - || im->mode == IMAGING_MODE_I_16N - ) { + if (isModeI16(im->mode)) { ink[0] = (UINT8)r; ink[1] = (UINT8)(r >> 8); ink[2] = ink[3] = 0; diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index b94630473..d28980432 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -68,7 +68,7 @@ typedef void (*hline_handler)(Imaging, int, int, int, int, Imaging); static inline void point8(Imaging im, int x, int y, int ink) { if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize) { - if (strncmp(im->mode->name, "I;16", 4) == 0) { + if (isModeI16(im->mode)) { #ifdef WORDS_BIGENDIAN im->image8[y][x * 2] = (UINT8)(ink >> 8); im->image8[y][x * 2 + 1] = (UINT8)ink; @@ -672,17 +672,17 @@ DRAW draw32rgba = {point32rgba, hline32rgba, line32rgba}; /* Interface */ /* -------------------------------------------------------------------- */ -#define DRAWINIT() \ - if (im->image8) { \ - draw = &draw8; \ - if (strncmp(im->mode->name, "I;16", 4) == 0) { \ - ink = INK16(ink_); \ - } else { \ - ink = INK8(ink_); \ - } \ - } else { \ - draw = (op) ? &draw32rgba : &draw32; \ - memcpy(&ink, ink_, sizeof(ink)); \ +#define DRAWINIT() \ + if (im->image8) { \ + draw = &draw8; \ + if (isModeI16(im->mode)) { \ + ink = INK16(ink_); \ + } else { \ + ink = INK8(ink_); \ + } \ + } else { \ + draw = (op) ? &draw32rgba : &draw32; \ + memcpy(&ink, ink_, sizeof(ink)); \ } int diff --git a/src/libImaging/Geometry.c b/src/libImaging/Geometry.c index c141ad2a1..80ecd7cb6 100644 --- a/src/libImaging/Geometry.c +++ b/src/libImaging/Geometry.c @@ -41,7 +41,7 @@ ImagingFlipLeftRight(Imaging imOut, Imaging imIn) { ImagingSectionEnter(&cookie); if (imIn->image8) { - if (strncmp(imIn->mode->name, "I;16", 4) == 0) { + if (isModeI16(imIn->mode)) { FLIP_LEFT_RIGHT(UINT16, image8) } else { FLIP_LEFT_RIGHT(UINT8, image8) @@ -127,7 +127,7 @@ ImagingRotate90(Imaging imOut, Imaging imIn) { ImagingSectionEnter(&cookie); if (imIn->image8) { - if (strncmp(imIn->mode->name, "I;16", 4) == 0) { + if (isModeI16(imIn->mode)) { ROTATE_90(UINT16, image8); } else { ROTATE_90(UINT8, image8); @@ -186,7 +186,7 @@ ImagingTranspose(Imaging imOut, Imaging imIn) { ImagingSectionEnter(&cookie); if (imIn->image8) { - if (strncmp(imIn->mode->name, "I;16", 4) == 0) { + if (isModeI16(imIn->mode)) { TRANSPOSE(UINT16, image8); } else { TRANSPOSE(UINT8, image8); @@ -247,7 +247,7 @@ ImagingTransverse(Imaging imOut, Imaging imIn) { ImagingSectionEnter(&cookie); if (imIn->image8) { - if (strncmp(imIn->mode->name, "I;16", 4) == 0) { + if (isModeI16(imIn->mode)) { TRANSVERSE(UINT16, image8); } else { TRANSVERSE(UINT8, image8); @@ -291,7 +291,7 @@ ImagingRotate180(Imaging imOut, Imaging imIn) { yr = imIn->ysize - 1; if (imIn->image8) { - if (strncmp(imIn->mode->name, "I;16", 4) == 0) { + if (isModeI16(imIn->mode)) { ROTATE_180(UINT16, image8) } else { ROTATE_180(UINT8, image8) @@ -351,7 +351,7 @@ ImagingRotate270(Imaging imOut, Imaging imIn) { ImagingSectionEnter(&cookie); if (imIn->image8) { - if (strncmp(imIn->mode->name, "I;16", 4) == 0) { + if (isModeI16(imIn->mode)) { ROTATE_270(UINT16, image8); } else { ROTATE_270(UINT8, image8); diff --git a/src/libImaging/Mode.c b/src/libImaging/Mode.c index 85ba50e3f..a11b65905 100644 --- a/src/libImaging/Mode.c +++ b/src/libImaging/Mode.c @@ -152,3 +152,10 @@ const RawMode * findRawMode(const char * const name) { } return NULL; } + +int isModeI16(const Mode * const mode) { + return mode == IMAGING_MODE_I_16 + || mode == IMAGING_MODE_I_16L + || mode == IMAGING_MODE_I_16B + || mode == IMAGING_MODE_I_16N; +} diff --git a/src/libImaging/Mode.h b/src/libImaging/Mode.h index 36deddd02..d1035efe8 100644 --- a/src/libImaging/Mode.h +++ b/src/libImaging/Mode.h @@ -86,4 +86,6 @@ extern const RawMode * const IMAGING_RAWMODE_YCbCrK; const RawMode * findRawMode(const char * const name); +int isModeI16(const Mode * const mode); + #endif // __MODE_H__ diff --git a/src/libImaging/Paste.c b/src/libImaging/Paste.c index 86085942a..5d745d0c1 100644 --- a/src/libImaging/Paste.c +++ b/src/libImaging/Paste.c @@ -67,8 +67,8 @@ paste_mask_1( int x, y; if (imOut->image8) { - int in_i16 = strncmp(imIn->mode, "I;16", 4) == 0; - int out_i16 = strncmp(imOut->mode, "I;16", 4) == 0; + int in_i16 = isModeI16(imIn->mode); + int out_i16 = isModeI16(imOut->mode); for (y = 0; y < ysize; y++) { UINT8 *out = imOut->image8[y + dy] + dx; if (out_i16) { @@ -437,7 +437,7 @@ fill_mask_L( unsigned int tmp1; if (imOut->image8) { - int i16 = strncmp(imOut->mode, "I;16", 4) == 0; + int i16 = isModeI16(imOut->mode); for (y = 0; y < ysize; y++) { UINT8 *out = imOut->image8[y + dy] + dx; if (i16) { diff --git a/src/map.c b/src/map.c index 9a3144ab9..f4933e45e 100644 --- a/src/map.c +++ b/src/map.c @@ -85,7 +85,7 @@ PyImaging_MapBuffer(PyObject *self, PyObject *args) { if (stride <= 0) { if (!strcmp(mode, "L") || !strcmp(mode, "P")) { stride = xsize; - } else if (!strncmp(mode, "I;16", 4)) { + } else if (isModeI16(mode)) { stride = xsize * 2; } else { stride = xsize * 4; From e5bc5b4ffacb00c7793e65b381f7c5dbfabfb86c Mon Sep 17 00:00:00 2001 From: eyedav <88885346+eyedav@users.noreply.github.com> Date: Sat, 19 Jul 2025 15:47:07 +0200 Subject: [PATCH 076/309] use mode structs in Pack.c --- src/_imaging.c | 1 + src/libImaging/Imaging.h | 5 ++ src/libImaging/Mode.h | 41 ++++++++++ src/libImaging/Pack.c | 163 ++++++++++++++++++++++++++++++++++++--- 4 files changed, 201 insertions(+), 9 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index bd7cc2233..22276590a 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -4319,6 +4319,7 @@ setup_module(PyObject *m) { ImagingAccessInit(); ImagingConvertInit(); + ImagingPackInit(); #ifdef HAVE_LIBJPEG { diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index c7e069313..c3efb2cda 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -194,6 +194,11 @@ ImagingConvertInit(void); extern void ImagingConvertFree(void); +extern void +ImagingPackInit(void); +extern void +ImagingPackFree(void); + /* Objects */ /* ------- */ diff --git a/src/libImaging/Mode.h b/src/libImaging/Mode.h index d1035efe8..cedad1e03 100644 --- a/src/libImaging/Mode.h +++ b/src/libImaging/Mode.h @@ -78,10 +78,51 @@ extern const RawMode * const IMAGING_RAWMODE_I_32L; extern const RawMode * const IMAGING_RAWMODE_I_32B; // Rawmodes +extern const RawMode * const IMAGING_RAWMODE_1_I; +extern const RawMode * const IMAGING_RAWMODE_1_IR; extern const RawMode * const IMAGING_RAWMODE_1_R; +extern const RawMode * const IMAGING_RAWMODE_A; +extern const RawMode * const IMAGING_RAWMODE_ABGR; +extern const RawMode * const IMAGING_RAWMODE_B; +extern const RawMode * const IMAGING_RAWMODE_BGR; +extern const RawMode * const IMAGING_RAWMODE_BGRA; +extern const RawMode * const IMAGING_RAWMODE_BGRX; +extern const RawMode * const IMAGING_RAWMODE_BGRa; +extern const RawMode * const IMAGING_RAWMODE_C; extern const RawMode * const IMAGING_RAWMODE_CMYK_I; +extern const RawMode * const IMAGING_RAWMODE_CMYK_L; +extern const RawMode * const IMAGING_RAWMODE_Cb; +extern const RawMode * const IMAGING_RAWMODE_Cr; +extern const RawMode * const IMAGING_RAWMODE_F_32F; +extern const RawMode * const IMAGING_RAWMODE_F_32NF; +extern const RawMode * const IMAGING_RAWMODE_G; +extern const RawMode * const IMAGING_RAWMODE_H; +extern const RawMode * const IMAGING_RAWMODE_I_32NS; +extern const RawMode * const IMAGING_RAWMODE_I_32S; +extern const RawMode * const IMAGING_RAWMODE_K; +extern const RawMode * const IMAGING_RAWMODE_LA_L; +extern const RawMode * const IMAGING_RAWMODE_L_16; +extern const RawMode * const IMAGING_RAWMODE_L_16B; +extern const RawMode * const IMAGING_RAWMODE_M; +extern const RawMode * const IMAGING_RAWMODE_PA_L; +extern const RawMode * const IMAGING_RAWMODE_P_1; +extern const RawMode * const IMAGING_RAWMODE_P_2; +extern const RawMode * const IMAGING_RAWMODE_P_4; +extern const RawMode * const IMAGING_RAWMODE_R; +extern const RawMode * const IMAGING_RAWMODE_RGBA_L; +extern const RawMode * const IMAGING_RAWMODE_RGBX_L; +extern const RawMode * const IMAGING_RAWMODE_RGB_L; +extern const RawMode * const IMAGING_RAWMODE_S; +extern const RawMode * const IMAGING_RAWMODE_V; +extern const RawMode * const IMAGING_RAWMODE_X; +extern const RawMode * const IMAGING_RAWMODE_XBGR; +extern const RawMode * const IMAGING_RAWMODE_XRGB; +extern const RawMode * const IMAGING_RAWMODE_Y; extern const RawMode * const IMAGING_RAWMODE_YCC_P; extern const RawMode * const IMAGING_RAWMODE_YCbCrK; +extern const RawMode * const IMAGING_RAWMODE_YCbCrX; +extern const RawMode * const IMAGING_RAWMODE_YCbCr_L; +extern const RawMode * const IMAGING_RAWMODE_aBGR; const RawMode * findRawMode(const char * const name); diff --git a/src/libImaging/Pack.c b/src/libImaging/Pack.c index 7f8a50d19..da714dacc 100644 --- a/src/libImaging/Pack.c +++ b/src/libImaging/Pack.c @@ -518,9 +518,9 @@ band3(UINT8 *out, const UINT8 *in, int pixels) { } } -static struct { - const char *mode; - const char *rawmode; +static struct Packer { + const Mode *mode; + const RawMode *rawmode; int bits; ImagingShuffler pack; } packers[] = { @@ -656,13 +656,10 @@ static struct { }; ImagingShuffler -ImagingFindPacker(const char *mode, const char *rawmode, int *bits_out) { +ImagingFindPacker(const Mode *mode, const RawMode *rawmode, int *bits_out) { int i; - - /* find a suitable pixel packer */ - for (i = 0; packers[i].rawmode; i++) { - if (strcmp(packers[i].mode, mode) == 0 && - strcmp(packers[i].rawmode, rawmode) == 0) { + for (i = 0; packers[i].mode; i++) { + if (packers[i].mode == mode && packers[i].rawmode == rawmode) { if (bits_out) { *bits_out = packers[i].bits; } @@ -671,3 +668,151 @@ ImagingFindPacker(const char *mode, const char *rawmode, int *bits_out) { } return NULL; } + +void +ImagingPackInit(void) { + const struct Packer temp[] = { + /* bilevel */ + {IMAGING_MODE_1, IMAGING_RAWMODE_1, 1, pack1}, + {IMAGING_MODE_1, IMAGING_RAWMODE_1_I, 1, pack1I}, + {IMAGING_MODE_1, IMAGING_RAWMODE_1_R, 1, pack1R}, + {IMAGING_MODE_1, IMAGING_RAWMODE_1_IR, 1, pack1IR}, + {IMAGING_MODE_1, IMAGING_RAWMODE_L, 8, pack1L}, + + /* grayscale */ + {IMAGING_MODE_L, IMAGING_RAWMODE_L, 8, copy1}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_16, 16, packL16}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_16B, 16, packL16B}, + + /* grayscale w. alpha */ + {IMAGING_MODE_LA, IMAGING_RAWMODE_LA, 16, packLA}, + {IMAGING_MODE_LA, IMAGING_RAWMODE_LA_L, 16, packLAL}, + + /* grayscale w. alpha premultiplied */ + {IMAGING_MODE_La, IMAGING_RAWMODE_La, 16, packLA}, + + /* palette */ + {IMAGING_MODE_P, IMAGING_RAWMODE_P_1, 1, pack1}, + {IMAGING_MODE_P, IMAGING_RAWMODE_P_2, 2, packP2}, + {IMAGING_MODE_P, IMAGING_RAWMODE_P_4, 4, packP4}, + {IMAGING_MODE_P, IMAGING_RAWMODE_P, 8, copy1}, + + /* palette w. alpha */ + {IMAGING_MODE_PA, IMAGING_RAWMODE_PA, 16, packLA}, + {IMAGING_MODE_PA, IMAGING_RAWMODE_PA_L, 16, packLAL}, + + /* true colour */ + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB, 24, ImagingPackRGB}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX, 32, copy4}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBA, 32, copy4}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_XRGB, 32, ImagingPackXRGB}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGR, 24, ImagingPackBGR}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGRX, 32, ImagingPackBGRX}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_XBGR, 32, ImagingPackXBGR}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_L, 24, packRGBL}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_R, 8, band0}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_G, 8, band1}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_B, 8, band2}, + + /* true colour w. alpha */ + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA, 32, copy4}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_L, 32, packRGBXL}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGB, 24, ImagingPackRGB}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGR, 24, ImagingPackBGR}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA, 32, ImagingPackBGRA}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_ABGR, 32, ImagingPackABGR}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRa, 32, ImagingPackBGRa}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R, 8, band0}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G, 8, band1}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B, 8, band2}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A, 8, band3}, + + /* true colour w. alpha premultiplied */ + {IMAGING_MODE_RGBa, IMAGING_RAWMODE_RGBa, 32, copy4}, + {IMAGING_MODE_RGBa, IMAGING_RAWMODE_BGRa, 32, ImagingPackBGRA}, + {IMAGING_MODE_RGBa, IMAGING_RAWMODE_aBGR, 32, ImagingPackABGR}, + + /* true colour w. padding */ + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX, 32, copy4}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_L, 32, packRGBXL}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB, 24, ImagingPackRGB}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGR, 24, ImagingPackBGR}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGRX, 32, ImagingPackBGRX}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_XBGR, 32, ImagingPackXBGR}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_R, 8, band0}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_G, 8, band1}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_B, 8, band2}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_X, 8, band3}, + + /* colour separation */ + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK, 32, copy4}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_I, 32, copy4I}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_L, 32, packRGBXL}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_C, 8, band0}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_M, 8, band1}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_Y, 8, band2}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_K, 8, band3}, + + /* video (YCbCr) */ + {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCr, 24, ImagingPackRGB}, + {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCr_L, 24, packRGBL}, + {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCrX, 32, copy4}, + {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCrK, 32, copy4}, + {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_Y, 8, band0}, + {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_Cb, 8, band1}, + {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_Cr, 8, band2}, + + /* LAB Color */ + {IMAGING_MODE_LAB, IMAGING_RAWMODE_LAB, 24, ImagingPackLAB}, + {IMAGING_MODE_LAB, IMAGING_RAWMODE_L, 8, band0}, + {IMAGING_MODE_LAB, IMAGING_RAWMODE_A, 8, band1}, + {IMAGING_MODE_LAB, IMAGING_RAWMODE_B, 8, band2}, + + /* HSV */ + {IMAGING_MODE_HSV, IMAGING_RAWMODE_HSV, 24, ImagingPackRGB}, + {IMAGING_MODE_HSV, IMAGING_RAWMODE_H, 8, band0}, + {IMAGING_MODE_HSV, IMAGING_RAWMODE_S, 8, band1}, + {IMAGING_MODE_HSV, IMAGING_RAWMODE_V, 8, band2}, + + /* integer */ + {IMAGING_MODE_I, IMAGING_RAWMODE_I, 32, copy4}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_16B, 16, packI16B}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_32S, 32, packI32S}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_32NS, 32, copy4}, + + /* floating point */ + {IMAGING_MODE_F, IMAGING_RAWMODE_F, 32, copy4}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_32F, 32, packI32S}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_32NF, 32, copy4}, + + /* storage modes */ + {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16, 16, copy2}, +#ifdef WORDS_BIGENDIAN + {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16B, 16, packI16N_I16}, +#else + {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16B, 16, packI16N_I16B}, +#endif + {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16B, 16, copy2}, + {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16L, 16, copy2}, + {IMAGING_MODE_I_16N, IMAGING_RAWMODE_I_16N, 16, copy2}, + {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16N, 16, packI16N_I16}, // LibTiff native->image endian. + {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16N, 16, packI16N_I16}, + {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16N, 16, packI16N_I16B}, + {IMAGING_MODE_BGR_15, IMAGING_RAWMODE_BGR_15, 16, copy2}, + {IMAGING_MODE_BGR_16, IMAGING_RAWMODE_BGR_16, 16, copy2}, + {IMAGING_MODE_BGR_24, IMAGING_RAWMODE_BGR_24, 24, copy3}, + + {NULL} /* sentinel */ + }; + packers = malloc(sizeof(temp)); + if (packers == NULL) { + fprintf(stderr, "PackInit: failed to allocate memory for packers table\n"); + exit(1); + } + memcpy(packers, temp, sizeof(temp)); +} + +void +ImagingPackFree(void) { + free(packers); +} From af3c24e12b2982a4713f80931164422acef0433b Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 21 Apr 2024 22:29:12 -0500 Subject: [PATCH 077/309] use mode structs in Palette.c --- src/libImaging/Mode.h | 4 ---- src/libImaging/Palette.c | 9 ++++----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/libImaging/Mode.h b/src/libImaging/Mode.h index cedad1e03..09810b584 100644 --- a/src/libImaging/Mode.h +++ b/src/libImaging/Mode.h @@ -2,10 +2,6 @@ #define __MODE_H__ -// Maximum length (including null terminator) for both mode and rawmode names. -#define IMAGING_MODE_LENGTH 6+1 - - typedef struct { const char * const name; } Mode; diff --git a/src/libImaging/Palette.c b/src/libImaging/Palette.c index 78916bca5..6b4fea6a5 100644 --- a/src/libImaging/Palette.c +++ b/src/libImaging/Palette.c @@ -21,13 +21,13 @@ #include ImagingPalette -ImagingPaletteNew(const char *mode) { +ImagingPaletteNew(const Mode *mode) { /* Create a palette object */ int i; ImagingPalette palette; - if (strcmp(mode, "RGB") && strcmp(mode, "RGBA")) { + if (mode != IMAGING_MODE_RGB && mode != IMAGING_MODE_RGBA) { return (ImagingPalette)ImagingError_ModeError(); } @@ -36,8 +36,7 @@ ImagingPaletteNew(const char *mode) { return (ImagingPalette)ImagingError_MemoryError(); } - strncpy(palette->mode, mode, IMAGING_MODE_LENGTH - 1); - palette->mode[IMAGING_MODE_LENGTH - 1] = 0; + palette->mode = mode; palette->size = 0; for (i = 0; i < 256; i++) { @@ -54,7 +53,7 @@ ImagingPaletteNewBrowser(void) { int i, r, g, b; ImagingPalette palette; - palette = ImagingPaletteNew("RGB"); + palette = ImagingPaletteNew(IMAGING_MODE_RGB); if (!palette) { return NULL; } From 2a9d712ceb71b7e14dc73782076280aae1dbc362 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 21 Apr 2024 22:32:54 -0500 Subject: [PATCH 078/309] use mode structs in Paste.c --- src/libImaging/Paste.c | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/src/libImaging/Paste.c b/src/libImaging/Paste.c index 5d745d0c1..54dd270e6 100644 --- a/src/libImaging/Paste.c +++ b/src/libImaging/Paste.c @@ -307,31 +307,26 @@ ImagingPaste( ImagingSectionEnter(&cookie); paste(imOut, imIn, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize); ImagingSectionLeave(&cookie); - - } else if (strcmp(imMask->mode, "1") == 0) { + } else if (imMask->mode == IMAGING_MODE_1) { ImagingSectionEnter(&cookie); paste_mask_1(imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize); ImagingSectionLeave(&cookie); - - } else if (strcmp(imMask->mode, "L") == 0) { + } else if (imMask->mode == IMAGING_MODE_L) { ImagingSectionEnter(&cookie); paste_mask_L(imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize); ImagingSectionLeave(&cookie); - - } else if (strcmp(imMask->mode, "LA") == 0 || strcmp(imMask->mode, "RGBA") == 0) { + } else if (imMask->mode == IMAGING_MODE_LA || imMask->mode == IMAGING_MODE_RGBA) { ImagingSectionEnter(&cookie); paste_mask_RGBA( imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize ); ImagingSectionLeave(&cookie); - - } else if (strcmp(imMask->mode, "RGBa") == 0) { + } else if (imMask->mode == IMAGING_MODE_RGBa) { ImagingSectionEnter(&cookie); paste_mask_RGBa( imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize ); ImagingSectionLeave(&cookie); - } else { (void)ImagingError_ValueError("bad transparency mask"); return -1; @@ -455,10 +450,11 @@ fill_mask_L( } } else { - int alpha_channel = - strcmp(imOut->mode, "RGBa") == 0 || strcmp(imOut->mode, "RGBA") == 0 || - strcmp(imOut->mode, "La") == 0 || strcmp(imOut->mode, "LA") == 0 || - strcmp(imOut->mode, "PA") == 0; + int alpha_channel = imOut->mode == IMAGING_MODE_RGBa || + imOut->mode == IMAGING_MODE_RGBA || + imOut->mode == IMAGING_MODE_La || + imOut->mode == IMAGING_MODE_LA || + imOut->mode == IMAGING_MODE_PA; for (y = 0; y < ysize; y++) { UINT8 *out = (UINT8 *)imOut->image[y + dy] + dx * pixelsize; UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx; @@ -617,27 +613,22 @@ ImagingFill2( ImagingSectionEnter(&cookie); fill(imOut, ink, dx0, dy0, xsize, ysize, pixelsize); ImagingSectionLeave(&cookie); - - } else if (strcmp(imMask->mode, "1") == 0) { + } else if (imMask->mode == IMAGING_MODE_1) { ImagingSectionEnter(&cookie); fill_mask_1(imOut, ink, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize); ImagingSectionLeave(&cookie); - - } else if (strcmp(imMask->mode, "L") == 0) { + } else if (imMask->mode == IMAGING_MODE_L) { ImagingSectionEnter(&cookie); fill_mask_L(imOut, ink, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize); ImagingSectionLeave(&cookie); - - } else if (strcmp(imMask->mode, "RGBA") == 0) { + } else if (imMask->mode == IMAGING_MODE_RGBA) { ImagingSectionEnter(&cookie); fill_mask_RGBA(imOut, ink, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize); ImagingSectionLeave(&cookie); - - } else if (strcmp(imMask->mode, "RGBa") == 0) { + } else if (imMask->mode == IMAGING_MODE_RGBa) { ImagingSectionEnter(&cookie); fill_mask_RGBa(imOut, ink, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize); ImagingSectionLeave(&cookie); - } else { (void)ImagingError_ValueError("bad transparency mask"); return -1; From 7e48697f8269d1d9caefd0516951cd52b31a154d Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 21 Apr 2024 22:36:23 -0500 Subject: [PATCH 079/309] use mode structs in Point.c --- src/libImaging/Point.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libImaging/Point.c b/src/libImaging/Point.c index b11ea62ed..e33f38508 100644 --- a/src/libImaging/Point.c +++ b/src/libImaging/Point.c @@ -128,7 +128,7 @@ im_point_32_8(Imaging imOut, Imaging imIn, im_point_context *context) { } Imaging -ImagingPoint(Imaging imIn, const char *mode, const void *table) { +ImagingPoint(Imaging imIn, const Mode *mode, const void *table) { /* lookup table transform */ ImagingSectionCookie cookie; @@ -145,10 +145,10 @@ ImagingPoint(Imaging imIn, const char *mode, const void *table) { } if (imIn->type != IMAGING_TYPE_UINT8) { - if (imIn->type != IMAGING_TYPE_INT32 || strcmp(mode, "L") != 0) { + if (imIn->type != IMAGING_TYPE_INT32 || mode != IMAGING_MODE_L) { goto mode_mismatch; } - } else if (!imIn->image8 && strcmp(imIn->mode, mode) != 0) { + } else if (!imIn->image8 && imIn->mode != mode) { goto mode_mismatch; } @@ -210,8 +210,8 @@ ImagingPointTransform(Imaging imIn, double scale, double offset) { Imaging imOut; int x, y; - if (!imIn || (strcmp(imIn->mode, "I") != 0 && strcmp(imIn->mode, "I;16") != 0 && - strcmp(imIn->mode, "F") != 0)) { + if (!imIn || (imIn->mode != IMAGING_MODE_I && + imIn->mode != IMAGING_MODE_I_16 && imIn->mode != IMAGING_MODE_F)) { return (Imaging)ImagingError_ModeError(); } @@ -245,7 +245,7 @@ ImagingPointTransform(Imaging imIn, double scale, double offset) { ImagingSectionLeave(&cookie); break; case IMAGING_TYPE_SPECIAL: - if (strcmp(imIn->mode, "I;16") == 0) { + if (imIn->mode == IMAGING_MODE_I_16) { ImagingSectionEnter(&cookie); for (y = 0; y < imIn->ysize; y++) { char *in = (char *)imIn->image[y]; From fb73d9003e62d2023176d39c84e0ff3f258debb8 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 21 Apr 2024 22:38:11 -0500 Subject: [PATCH 080/309] use mode structs in Quant.c --- src/libImaging/Quant.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/libImaging/Quant.c b/src/libImaging/Quant.c index a489a882d..b1397c5f0 100644 --- a/src/libImaging/Quant.c +++ b/src/libImaging/Quant.c @@ -1684,13 +1684,13 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) { return (Imaging)ImagingError_ValueError("bad number of colors"); } - if (strcmp(im->mode, "L") != 0 && strcmp(im->mode, "P") != 0 && - strcmp(im->mode, "RGB") != 0 && strcmp(im->mode, "RGBA") != 0) { + if (im->mode != IMAGING_MODE_L && im->mode != IMAGING_MODE_P && + im->mode != IMAGING_MODE_RGB && im->mode != IMAGING_MODE_RGBA) { return ImagingError_ModeError(); } /* only octree and imagequant supports RGBA */ - if (!strcmp(im->mode, "RGBA") && mode != 2 && mode != 3) { + if (im->mode == IMAGING_MODE_RGBA && mode != 2 && mode != 3) { return ImagingError_ModeError(); } @@ -1708,7 +1708,7 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) { /* FIXME: maybe we could load the hash tables directly from the image data? */ - if (!strcmp(im->mode, "L")) { + if (im->mode == IMAGING_MODE_L) { /* grayscale */ /* FIXME: converting a "L" image to "P" with 256 colors @@ -1721,7 +1721,7 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) { } } - } else if (!strcmp(im->mode, "P")) { + } else if (im->mode == IMAGING_MODE_P) { /* palette */ pp = im->palette->palette; @@ -1736,10 +1736,10 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) { } } - } else if (!strcmp(im->mode, "RGB") || !strcmp(im->mode, "RGBA")) { + } else if (im->mode == IMAGING_MODE_RGB || im->mode == IMAGING_MODE_RGBA) { /* true colour */ - withAlpha = !strcmp(im->mode, "RGBA"); + withAlpha = im->mode == IMAGING_MODE_RGBA; int transparency = 0; unsigned char r = 0, g = 0, b = 0; for (i = y = 0; y < im->ysize; y++) { @@ -1830,7 +1830,7 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) { ImagingSectionLeave(&cookie); if (result > 0) { - imOut = ImagingNewDirty("P", im->xsize, im->ysize); + imOut = ImagingNewDirty(IMAGING_MODE_P, im->xsize, im->ysize); ImagingSectionEnter(&cookie); for (i = y = 0; y < im->ysize; y++) { @@ -1855,7 +1855,7 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) { } if (withAlpha) { - strcpy(imOut->palette->mode, "RGBA"); + imOut->palette->mode = IMAGING_MODE_RGBA; } free(palette); From c80fba3045893918f1e51c4df3934e314b2c5f8d Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 21 Apr 2024 22:40:40 -0500 Subject: [PATCH 081/309] use mode structs in Reduce.c --- src/libImaging/Reduce.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/Reduce.c b/src/libImaging/Reduce.c index 022daa000..a4e58ced8 100644 --- a/src/libImaging/Reduce.c +++ b/src/libImaging/Reduce.c @@ -1452,7 +1452,7 @@ ImagingReduce(Imaging imIn, int xscale, int yscale, int box[4]) { ImagingSectionCookie cookie; Imaging imOut = NULL; - if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "1") == 0) { + if (imIn->mode == IMAGING_MODE_P || imIn->mode == IMAGING_MODE_1) { return (Imaging)ImagingError_ModeError(); } From 858b0b38059a6162c3317c2a5081d4ee7b2ff9b1 Mon Sep 17 00:00:00 2001 From: eyedav <88885346+eyedav@users.noreply.github.com> Date: Sat, 19 Jul 2025 15:47:47 +0200 Subject: [PATCH 082/309] use mode structs in Resample.c --- src/libImaging/Resample.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/libImaging/Resample.c b/src/libImaging/Resample.c index b114e0023..3ab43a895 100644 --- a/src/libImaging/Resample.c +++ b/src/libImaging/Resample.c @@ -470,10 +470,9 @@ ImagingResampleHorizontal_16bpc( double *k; int bigendian = 0; - if ( - strcmp(imIn->mode, "I;16N") == 0 + if (imIn->mode == IMAGING_MODE_I_16N #ifdef WORDS_BIGENDIAN - || strcmp(imIn->mode, "I;16B") == 0 + || imIn->mode == IMAGING_MODE_I_16B #endif ) { bigendian = 1; @@ -510,10 +509,9 @@ ImagingResampleVertical_16bpc( double *k; int bigendian = 0; - if ( - strcmp(imIn->mode, "I;16N") == 0 + if (imIn->mode == IMAGING_MODE_I_16N #ifdef WORDS_BIGENDIAN - || strcmp(imIn->mode, "I;16B") == 0 + || imIn->mode == IMAGING_MODE_I_16B #endif ) { bigendian = 1; @@ -648,12 +646,12 @@ ImagingResample(Imaging imIn, int xsize, int ysize, int filter, float box[4]) { ResampleFunction ResampleHorizontal; ResampleFunction ResampleVertical; - if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "1") == 0) { + if (imIn->mode == IMAGING_MODE_P || imIn->mode == IMAGING_MODE_1) { return (Imaging)ImagingError_ModeError(); } if (imIn->type == IMAGING_TYPE_SPECIAL) { - if (strncmp(imIn->mode, "I;16", 4) == 0) { + if (isModeI16(imIn->mode)) { ResampleHorizontal = ImagingResampleHorizontal_16bpc; ResampleVertical = ImagingResampleVertical_16bpc; } else { From e75a0a9c39ab16da44482be77c21b2f08fea9923 Mon Sep 17 00:00:00 2001 From: eyedav <88885346+eyedav@users.noreply.github.com> Date: Sat, 19 Jul 2025 15:54:11 +0200 Subject: [PATCH 083/309] use mode structs in Storage.c --- src/libImaging/Storage.c | 59 ++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/src/libImaging/Storage.c b/src/libImaging/Storage.c index 4640f078a..38142b7c5 100644 --- a/src/libImaging/Storage.c +++ b/src/libImaging/Storage.c @@ -42,7 +42,7 @@ */ Imaging -ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { +ImagingNewPrologueSubtype(const Mode *mode, int xsize, int ysize, int size) { Imaging im; /* linesize overflow check, roughly the current largest space req'd */ @@ -62,37 +62,37 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { im->type = IMAGING_TYPE_UINT8; strcpy(im->arrow_band_format, "C"); - if (strcmp(mode, "1") == 0) { + if (mode == IMAGING_MODE_1) { /* 1-bit images */ im->bands = im->pixelsize = 1; im->linesize = xsize; strcpy(im->band_names[0], "1"); - } else if (strcmp(mode, "P") == 0) { + } else if (mode == IMAGING_MODE_P) { /* 8-bit palette mapped images */ im->bands = im->pixelsize = 1; im->linesize = xsize; - im->palette = ImagingPaletteNew("RGB"); + im->palette = ImagingPaletteNew(IMAGING_MODE_RGB); strcpy(im->band_names[0], "P"); - } else if (strcmp(mode, "PA") == 0) { + } else if (mode == IMAGING_MODE_PA) { /* 8-bit palette with alpha */ im->bands = 2; im->pixelsize = 4; /* store in image32 memory */ im->linesize = xsize * 4; - im->palette = ImagingPaletteNew("RGB"); + im->palette = ImagingPaletteNew(IMAGING_MODE_RGB); strcpy(im->band_names[0], "P"); strcpy(im->band_names[1], "X"); strcpy(im->band_names[2], "X"); strcpy(im->band_names[3], "A"); - } else if (strcmp(mode, "L") == 0) { + } else if (mode == IMAGING_MODE_L) { /* 8-bit grayscale (luminance) images */ im->bands = im->pixelsize = 1; im->linesize = xsize; strcpy(im->band_names[0], "L"); - } else if (strcmp(mode, "LA") == 0) { + } else if (mode == IMAGING_MODE_LA) { /* 8-bit grayscale (luminance) with alpha */ im->bands = 2; im->pixelsize = 4; /* store in image32 memory */ @@ -102,7 +102,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { strcpy(im->band_names[2], "X"); strcpy(im->band_names[3], "A"); - } else if (strcmp(mode, "La") == 0) { + } else if (mode == IMAGING_MODE_La) { /* 8-bit grayscale (luminance) with premultiplied alpha */ im->bands = 2; im->pixelsize = 4; /* store in image32 memory */ @@ -112,7 +112,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { strcpy(im->band_names[2], "X"); strcpy(im->band_names[3], "a"); - } else if (strcmp(mode, "F") == 0) { + } else if (mode == IMAGING_MODE_F) { /* 32-bit floating point images */ im->bands = 1; im->pixelsize = 4; @@ -121,7 +121,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { strcpy(im->arrow_band_format, "f"); strcpy(im->band_names[0], "F"); - } else if (strcmp(mode, "I") == 0) { + } else if (mode == IMAGING_MODE_I) { /* 32-bit integer images */ im->bands = 1; im->pixelsize = 4; @@ -130,8 +130,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { strcpy(im->arrow_band_format, "i"); strcpy(im->band_names[0], "I"); - } else if (strcmp(mode, "I;16") == 0 || strcmp(mode, "I;16L") == 0 || - strcmp(mode, "I;16B") == 0 || strcmp(mode, "I;16N") == 0) { + } else if (isModeI16(mode)) { /* EXPERIMENTAL */ /* 16-bit raw integer images */ im->bands = 1; @@ -141,7 +140,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { strcpy(im->arrow_band_format, "s"); strcpy(im->band_names[0], "I"); - } else if (strcmp(mode, "RGB") == 0) { + } else if (mode == IMAGING_MODE_RGB) { /* 24-bit true colour images */ im->bands = 3; im->pixelsize = 4; @@ -151,7 +150,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { strcpy(im->band_names[2], "B"); strcpy(im->band_names[3], "X"); - } else if (strcmp(mode, "RGBX") == 0) { + } else if (mode == IMAGING_MODE_RGBX) { /* 32-bit true colour images with padding */ im->bands = im->pixelsize = 4; im->linesize = xsize * 4; @@ -160,7 +159,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { strcpy(im->band_names[2], "B"); strcpy(im->band_names[3], "X"); - } else if (strcmp(mode, "RGBA") == 0) { + } else if (mode == IMAGING_MODE_RGBA) { /* 32-bit true colour images with alpha */ im->bands = im->pixelsize = 4; im->linesize = xsize * 4; @@ -169,7 +168,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { strcpy(im->band_names[2], "B"); strcpy(im->band_names[3], "A"); - } else if (strcmp(mode, "RGBa") == 0) { + } else if (mode == IMAGING_MODE_RGBa) { /* 32-bit true colour images with premultiplied alpha */ im->bands = im->pixelsize = 4; im->linesize = xsize * 4; @@ -178,7 +177,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { strcpy(im->band_names[2], "B"); strcpy(im->band_names[3], "a"); - } else if (strcmp(mode, "CMYK") == 0) { + } else if (mode == IMAGING_MODE_CMYK) { /* 32-bit colour separation */ im->bands = im->pixelsize = 4; im->linesize = xsize * 4; @@ -187,7 +186,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { strcpy(im->band_names[2], "Y"); strcpy(im->band_names[3], "K"); - } else if (strcmp(mode, "YCbCr") == 0) { + } else if (mode == IMAGING_MODE_YCbCr) { /* 24-bit video format */ im->bands = 3; im->pixelsize = 4; @@ -197,7 +196,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { strcpy(im->band_names[2], "Cr"); strcpy(im->band_names[3], "X"); - } else if (strcmp(mode, "LAB") == 0) { + } else if (mode == IMAGING_MODE_LAB) { /* 24-bit color, luminance, + 2 color channels */ /* L is uint8, a,b are int8 */ im->bands = 3; @@ -208,7 +207,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { strcpy(im->band_names[2], "b"); strcpy(im->band_names[3], "X"); - } else if (strcmp(mode, "HSV") == 0) { + } else if (mode == IMAGING_MODE_HSV) { /* 24-bit color, luminance, + 2 color channels */ /* L is uint8, a,b are int8 */ im->bands = 3; @@ -225,7 +224,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { } /* Setup image descriptor */ - strcpy(im->mode, mode); + im->mode = mode; /* Pointer array (allocate at least one line, to avoid MemoryError exceptions on platforms where calloc(0, x) returns NULL) */ @@ -257,7 +256,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { } Imaging -ImagingNewPrologue(const char *mode, int xsize, int ysize) { +ImagingNewPrologue(const Mode *mode, int xsize, int ysize) { return ImagingNewPrologueSubtype( mode, xsize, ysize, sizeof(struct ImagingMemoryInstance) ); @@ -594,7 +593,7 @@ ImagingBorrowArrow( */ Imaging -ImagingNewInternal(const char *mode, int xsize, int ysize, int dirty) { +ImagingNewInternal(const Mode *mode, int xsize, int ysize, int dirty) { Imaging im; if (xsize < 0 || ysize < 0) { @@ -630,7 +629,7 @@ ImagingNewInternal(const char *mode, int xsize, int ysize, int dirty) { } Imaging -ImagingNew(const char *mode, int xsize, int ysize) { +ImagingNew(const Mode *mode, int xsize, int ysize) { if (ImagingDefaultArena.use_block_allocator) { return ImagingNewBlock(mode, xsize, ysize); } @@ -638,7 +637,7 @@ ImagingNew(const char *mode, int xsize, int ysize) { } Imaging -ImagingNewDirty(const char *mode, int xsize, int ysize) { +ImagingNewDirty(const Mode *mode, int xsize, int ysize) { if (ImagingDefaultArena.use_block_allocator) { return ImagingNewBlock(mode, xsize, ysize); } @@ -646,7 +645,7 @@ ImagingNewDirty(const char *mode, int xsize, int ysize) { } Imaging -ImagingNewBlock(const char *mode, int xsize, int ysize) { +ImagingNewBlock(const Mode *mode, int xsize, int ysize) { Imaging im; if (xsize < 0 || ysize < 0) { @@ -668,7 +667,7 @@ ImagingNewBlock(const char *mode, int xsize, int ysize) { Imaging ImagingNewArrow( - const char *mode, + const Mode *mode, int xsize, int ysize, PyObject *schema_capsule, @@ -741,12 +740,12 @@ ImagingNewArrow( } Imaging -ImagingNew2Dirty(const char *mode, Imaging imOut, Imaging imIn) { +ImagingNew2Dirty(const Mode *mode, Imaging imOut, Imaging imIn) { /* allocate or validate output image */ if (imOut) { /* make sure images match */ - if (strcmp(imOut->mode, mode) != 0 || imOut->xsize != imIn->xsize || + if (imOut->mode != mode || imOut->xsize != imIn->xsize || imOut->ysize != imIn->ysize) { return ImagingError_Mismatch(); } From 141c95df9a56ed177b07fb82acfa6087d338d4be Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 21 Apr 2024 22:54:58 -0500 Subject: [PATCH 084/309] use mode structs in TiffDecode.c --- src/libImaging/Mode.h | 4 ++++ src/libImaging/TiffDecode.c | 20 ++++++++------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/libImaging/Mode.h b/src/libImaging/Mode.h index 09810b584..f953d586f 100644 --- a/src/libImaging/Mode.h +++ b/src/libImaging/Mode.h @@ -79,11 +79,13 @@ extern const RawMode * const IMAGING_RAWMODE_1_IR; extern const RawMode * const IMAGING_RAWMODE_1_R; extern const RawMode * const IMAGING_RAWMODE_A; extern const RawMode * const IMAGING_RAWMODE_ABGR; +extern const RawMode * const IMAGING_RAWMODE_A_16N; extern const RawMode * const IMAGING_RAWMODE_B; extern const RawMode * const IMAGING_RAWMODE_BGR; extern const RawMode * const IMAGING_RAWMODE_BGRA; extern const RawMode * const IMAGING_RAWMODE_BGRX; extern const RawMode * const IMAGING_RAWMODE_BGRa; +extern const RawMode * const IMAGING_RAWMODE_B_16N; extern const RawMode * const IMAGING_RAWMODE_C; extern const RawMode * const IMAGING_RAWMODE_CMYK_I; extern const RawMode * const IMAGING_RAWMODE_CMYK_L; @@ -92,6 +94,7 @@ extern const RawMode * const IMAGING_RAWMODE_Cr; extern const RawMode * const IMAGING_RAWMODE_F_32F; extern const RawMode * const IMAGING_RAWMODE_F_32NF; extern const RawMode * const IMAGING_RAWMODE_G; +extern const RawMode * const IMAGING_RAWMODE_G_16N; extern const RawMode * const IMAGING_RAWMODE_H; extern const RawMode * const IMAGING_RAWMODE_I_32NS; extern const RawMode * const IMAGING_RAWMODE_I_32S; @@ -108,6 +111,7 @@ extern const RawMode * const IMAGING_RAWMODE_R; extern const RawMode * const IMAGING_RAWMODE_RGBA_L; extern const RawMode * const IMAGING_RAWMODE_RGBX_L; extern const RawMode * const IMAGING_RAWMODE_RGB_L; +extern const RawMode * const IMAGING_RAWMODE_R_16N; extern const RawMode * const IMAGING_RAWMODE_S; extern const RawMode * const IMAGING_RAWMODE_V; extern const RawMode * const IMAGING_RAWMODE_X; diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 71516fd1b..40e8fba50 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -246,14 +246,10 @@ _pickUnpackers( // We'll pick appropriate set of unpackers depending on planar_configuration // It does not matter if data is RGB(A), CMYK or LUV really, // we just copy it plane by plane - unpackers[0] = - ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "R;16N" : "R", NULL); - unpackers[1] = - ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "G;16N" : "G", NULL); - unpackers[2] = - ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "B;16N" : "B", NULL); - unpackers[3] = - ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "A;16N" : "A", NULL); + unpackers[0] = ImagingFindUnpacker(IMAGING_MODE_RGBA, bits_per_sample == 16 ? IMAGING_RAWMODE_R_16N : IMAGING_RAWMODE_R, NULL); + unpackers[1] = ImagingFindUnpacker(IMAGING_MODE_RGBA, bits_per_sample == 16 ? IMAGING_RAWMODE_G_16N : IMAGING_RAWMODE_G, NULL); + unpackers[2] = ImagingFindUnpacker(IMAGING_MODE_RGBA, bits_per_sample == 16 ? IMAGING_RAWMODE_B_16N : IMAGING_RAWMODE_B, NULL); + unpackers[3] = ImagingFindUnpacker(IMAGING_MODE_RGBA, bits_per_sample == 16 ? IMAGING_RAWMODE_A_16N : IMAGING_RAWMODE_A, NULL); return im->bands; } else { @@ -644,7 +640,7 @@ ImagingLibTiffDecode( ); TRACE( ("Image: mode %s, type %d, bands: %d, xsize %d, ysize %d \n", - im->mode, + im->mode->name, im->type, im->bands, im->xsize, @@ -755,7 +751,7 @@ ImagingLibTiffDecode( if (!state->errcode) { // Check if raw mode was RGBa and it was stored on separate planes // so we have to convert it to RGBA - if (planes > 3 && strcmp(im->mode, "RGBA") == 0) { + if (planes > 3 && im->mode == IMAGING_MODE_RGBA) { uint16_t extrasamples; uint16_t *sampleinfo; ImagingShuffler shuffle; @@ -767,7 +763,7 @@ ImagingLibTiffDecode( if (extrasamples >= 1 && (sampleinfo[0] == EXTRASAMPLE_UNSPECIFIED || sampleinfo[0] == EXTRASAMPLE_ASSOCALPHA)) { - shuffle = ImagingFindUnpacker("RGBA", "RGBa", NULL); + shuffle = ImagingFindUnpacker(IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa, NULL); for (y = state->yoff; y < state->ysize; y++) { UINT8 *ptr = (UINT8 *)im->image[y + state->yoff] + @@ -991,7 +987,7 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt ); TRACE( ("Image: mode %s, type %d, bands: %d, xsize %d, ysize %d \n", - im->mode, + im->mode->name, im->type, im->bands, im->xsize, From 39d434b39dc736287604673720532b89c70eb260 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Mon, 22 Apr 2024 12:47:51 -0500 Subject: [PATCH 085/309] use (void) for empty function parameters --- src/libImaging/Access.c | 2 +- src/libImaging/Convert.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libImaging/Access.c b/src/libImaging/Access.c index 6c41fc091..95586d7ed 100644 --- a/src/libImaging/Access.c +++ b/src/libImaging/Access.c @@ -189,4 +189,4 @@ void _ImagingAccessDelete(Imaging im, ImagingAccess access) {} void -ImagingAccessFree() {} +ImagingAccessFree(void) {} diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index 2bc054616..48259dcdc 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -1650,7 +1650,7 @@ ImagingConvertInPlace(Imaging imIn, const Mode *mode) { /* ------------------ */ void -ImagingConvertInit() { +ImagingConvertInit(void) { const struct Converter temp[] = { {IMAGING_MODE_1, IMAGING_MODE_L, bit2l}, {IMAGING_MODE_1, IMAGING_MODE_I, bit2i}, @@ -1792,6 +1792,6 @@ ImagingConvertInit() { } void -ImagingConvertFree() { +ImagingConvertFree(void) { free(converters); } From 31118b0019ddd553dfd0b841b6b42c4b33ab1ef9 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Mon, 22 Apr 2024 12:48:26 -0500 Subject: [PATCH 086/309] set pointer to NULL after free --- src/libImaging/Convert.c | 1 + src/libImaging/Pack.c | 1 + 2 files changed, 2 insertions(+) diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index 48259dcdc..8f580c294 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -1794,4 +1794,5 @@ ImagingConvertInit(void) { void ImagingConvertFree(void) { free(converters); + converters = NULL; } diff --git a/src/libImaging/Pack.c b/src/libImaging/Pack.c index da714dacc..aaa074c92 100644 --- a/src/libImaging/Pack.c +++ b/src/libImaging/Pack.c @@ -815,4 +815,5 @@ ImagingPackInit(void) { void ImagingPackFree(void) { free(packers); + packers = NULL; } From d11819ca6bdebc50c94d84aad17514cf4a42365f Mon Sep 17 00:00:00 2001 From: eyedav <88885346+eyedav@users.noreply.github.com> Date: Sat, 19 Jul 2025 15:55:44 +0200 Subject: [PATCH 087/309] use mode structs in Unpack.c --- src/_imaging.c | 1 + src/libImaging/Imaging.h | 5 + src/libImaging/Mode.h | 96 ++++++++++++ src/libImaging/Unpack.c | 325 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 421 insertions(+), 6 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index 22276590a..6c98c42ea 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -4320,6 +4320,7 @@ setup_module(PyObject *m) { ImagingAccessInit(); ImagingConvertInit(); ImagingPackInit(); + ImagingUnpackInit(); #ifdef HAVE_LIBJPEG { diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index c3efb2cda..9f450dd3a 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -199,6 +199,11 @@ ImagingPackInit(void); extern void ImagingPackFree(void); +extern void +ImagingUnpackInit(void); +extern void +ImagingUnpackFree(void); + /* Objects */ /* ------- */ diff --git a/src/libImaging/Mode.h b/src/libImaging/Mode.h index f953d586f..663f2f468 100644 --- a/src/libImaging/Mode.h +++ b/src/libImaging/Mode.h @@ -74,43 +74,136 @@ extern const RawMode * const IMAGING_RAWMODE_I_32L; extern const RawMode * const IMAGING_RAWMODE_I_32B; // Rawmodes +extern const RawMode * const IMAGING_RAWMODE_1_8; extern const RawMode * const IMAGING_RAWMODE_1_I; extern const RawMode * const IMAGING_RAWMODE_1_IR; extern const RawMode * const IMAGING_RAWMODE_1_R; extern const RawMode * const IMAGING_RAWMODE_A; extern const RawMode * const IMAGING_RAWMODE_ABGR; +extern const RawMode * const IMAGING_RAWMODE_ARGB; +extern const RawMode * const IMAGING_RAWMODE_A_16B; +extern const RawMode * const IMAGING_RAWMODE_A_16L; extern const RawMode * const IMAGING_RAWMODE_A_16N; extern const RawMode * const IMAGING_RAWMODE_B; +extern const RawMode * const IMAGING_RAWMODE_BGAR; extern const RawMode * const IMAGING_RAWMODE_BGR; extern const RawMode * const IMAGING_RAWMODE_BGRA; +extern const RawMode * const IMAGING_RAWMODE_BGRA_15; +extern const RawMode * const IMAGING_RAWMODE_BGRA_15Z; +extern const RawMode * const IMAGING_RAWMODE_BGRA_16B; +extern const RawMode * const IMAGING_RAWMODE_BGRA_16L; extern const RawMode * const IMAGING_RAWMODE_BGRX; +extern const RawMode * const IMAGING_RAWMODE_BGR_5; extern const RawMode * const IMAGING_RAWMODE_BGRa; +extern const RawMode * const IMAGING_RAWMODE_BGXR; +extern const RawMode * const IMAGING_RAWMODE_B_16B; +extern const RawMode * const IMAGING_RAWMODE_B_16L; extern const RawMode * const IMAGING_RAWMODE_B_16N; extern const RawMode * const IMAGING_RAWMODE_C; +extern const RawMode * const IMAGING_RAWMODE_CMYKX; +extern const RawMode * const IMAGING_RAWMODE_CMYKXX; +extern const RawMode * const IMAGING_RAWMODE_CMYK_16B; +extern const RawMode * const IMAGING_RAWMODE_CMYK_16L; +extern const RawMode * const IMAGING_RAWMODE_CMYK_16N; extern const RawMode * const IMAGING_RAWMODE_CMYK_I; extern const RawMode * const IMAGING_RAWMODE_CMYK_L; +extern const RawMode * const IMAGING_RAWMODE_C_I; extern const RawMode * const IMAGING_RAWMODE_Cb; extern const RawMode * const IMAGING_RAWMODE_Cr; +extern const RawMode * const IMAGING_RAWMODE_F_16; +extern const RawMode * const IMAGING_RAWMODE_F_16B; +extern const RawMode * const IMAGING_RAWMODE_F_16BS; +extern const RawMode * const IMAGING_RAWMODE_F_16N; +extern const RawMode * const IMAGING_RAWMODE_F_16NS; +extern const RawMode * const IMAGING_RAWMODE_F_16S; +extern const RawMode * const IMAGING_RAWMODE_F_32; +extern const RawMode * const IMAGING_RAWMODE_F_32B; +extern const RawMode * const IMAGING_RAWMODE_F_32BF; +extern const RawMode * const IMAGING_RAWMODE_F_32BS; extern const RawMode * const IMAGING_RAWMODE_F_32F; +extern const RawMode * const IMAGING_RAWMODE_F_32N; extern const RawMode * const IMAGING_RAWMODE_F_32NF; +extern const RawMode * const IMAGING_RAWMODE_F_32NS; +extern const RawMode * const IMAGING_RAWMODE_F_32S; +extern const RawMode * const IMAGING_RAWMODE_F_64BF; +extern const RawMode * const IMAGING_RAWMODE_F_64F; +extern const RawMode * const IMAGING_RAWMODE_F_64NF; +extern const RawMode * const IMAGING_RAWMODE_F_8; +extern const RawMode * const IMAGING_RAWMODE_F_8S; extern const RawMode * const IMAGING_RAWMODE_G; +extern const RawMode * const IMAGING_RAWMODE_G_16B; +extern const RawMode * const IMAGING_RAWMODE_G_16L; extern const RawMode * const IMAGING_RAWMODE_G_16N; extern const RawMode * const IMAGING_RAWMODE_H; +extern const RawMode * const IMAGING_RAWMODE_I_12; +extern const RawMode * const IMAGING_RAWMODE_I_16BS; +extern const RawMode * const IMAGING_RAWMODE_I_16NS; +extern const RawMode * const IMAGING_RAWMODE_I_16R; +extern const RawMode * const IMAGING_RAWMODE_I_16S; +extern const RawMode * const IMAGING_RAWMODE_I_32; +extern const RawMode * const IMAGING_RAWMODE_I_32BS; +extern const RawMode * const IMAGING_RAWMODE_I_32N; extern const RawMode * const IMAGING_RAWMODE_I_32NS; extern const RawMode * const IMAGING_RAWMODE_I_32S; +extern const RawMode * const IMAGING_RAWMODE_I_8; +extern const RawMode * const IMAGING_RAWMODE_I_8S; extern const RawMode * const IMAGING_RAWMODE_K; +extern const RawMode * const IMAGING_RAWMODE_K_I; +extern const RawMode * const IMAGING_RAWMODE_LA_16B; extern const RawMode * const IMAGING_RAWMODE_LA_L; extern const RawMode * const IMAGING_RAWMODE_L_16; extern const RawMode * const IMAGING_RAWMODE_L_16B; +extern const RawMode * const IMAGING_RAWMODE_L_2; +extern const RawMode * const IMAGING_RAWMODE_L_2I; +extern const RawMode * const IMAGING_RAWMODE_L_2IR; +extern const RawMode * const IMAGING_RAWMODE_L_2R; +extern const RawMode * const IMAGING_RAWMODE_L_4; +extern const RawMode * const IMAGING_RAWMODE_L_4I; +extern const RawMode * const IMAGING_RAWMODE_L_4IR; +extern const RawMode * const IMAGING_RAWMODE_L_4R; +extern const RawMode * const IMAGING_RAWMODE_L_I; +extern const RawMode * const IMAGING_RAWMODE_L_R; extern const RawMode * const IMAGING_RAWMODE_M; +extern const RawMode * const IMAGING_RAWMODE_M_I; extern const RawMode * const IMAGING_RAWMODE_PA_L; +extern const RawMode * const IMAGING_RAWMODE_PX; extern const RawMode * const IMAGING_RAWMODE_P_1; extern const RawMode * const IMAGING_RAWMODE_P_2; +extern const RawMode * const IMAGING_RAWMODE_P_2L; extern const RawMode * const IMAGING_RAWMODE_P_4; +extern const RawMode * const IMAGING_RAWMODE_P_4L; +extern const RawMode * const IMAGING_RAWMODE_P_R; extern const RawMode * const IMAGING_RAWMODE_R; +extern const RawMode * const IMAGING_RAWMODE_RGBAX; +extern const RawMode * const IMAGING_RAWMODE_RGBAXX; +extern const RawMode * const IMAGING_RAWMODE_RGBA_15; +extern const RawMode * const IMAGING_RAWMODE_RGBA_16B; +extern const RawMode * const IMAGING_RAWMODE_RGBA_16L; +extern const RawMode * const IMAGING_RAWMODE_RGBA_16N; +extern const RawMode * const IMAGING_RAWMODE_RGBA_4B; +extern const RawMode * const IMAGING_RAWMODE_RGBA_I; extern const RawMode * const IMAGING_RAWMODE_RGBA_L; +extern const RawMode * const IMAGING_RAWMODE_RGBXX; +extern const RawMode * const IMAGING_RAWMODE_RGBXXX; +extern const RawMode * const IMAGING_RAWMODE_RGBX_16B; +extern const RawMode * const IMAGING_RAWMODE_RGBX_16L; +extern const RawMode * const IMAGING_RAWMODE_RGBX_16N; extern const RawMode * const IMAGING_RAWMODE_RGBX_L; +extern const RawMode * const IMAGING_RAWMODE_RGB_15; +extern const RawMode * const IMAGING_RAWMODE_RGB_16; +extern const RawMode * const IMAGING_RAWMODE_RGB_16B; +extern const RawMode * const IMAGING_RAWMODE_RGB_16L; +extern const RawMode * const IMAGING_RAWMODE_RGB_16N; +extern const RawMode * const IMAGING_RAWMODE_RGB_4B; extern const RawMode * const IMAGING_RAWMODE_RGB_L; +extern const RawMode * const IMAGING_RAWMODE_RGB_R; +extern const RawMode * const IMAGING_RAWMODE_RGBaX; +extern const RawMode * const IMAGING_RAWMODE_RGBaXX; +extern const RawMode * const IMAGING_RAWMODE_RGBa_16B; +extern const RawMode * const IMAGING_RAWMODE_RGBa_16L; +extern const RawMode * const IMAGING_RAWMODE_RGBa_16N; +extern const RawMode * const IMAGING_RAWMODE_R_16B; +extern const RawMode * const IMAGING_RAWMODE_R_16L; extern const RawMode * const IMAGING_RAWMODE_R_16N; extern const RawMode * const IMAGING_RAWMODE_S; extern const RawMode * const IMAGING_RAWMODE_V; @@ -118,11 +211,14 @@ extern const RawMode * const IMAGING_RAWMODE_X; extern const RawMode * const IMAGING_RAWMODE_XBGR; extern const RawMode * const IMAGING_RAWMODE_XRGB; extern const RawMode * const IMAGING_RAWMODE_Y; +extern const RawMode * const IMAGING_RAWMODE_YCCA_P; extern const RawMode * const IMAGING_RAWMODE_YCC_P; extern const RawMode * const IMAGING_RAWMODE_YCbCrK; extern const RawMode * const IMAGING_RAWMODE_YCbCrX; extern const RawMode * const IMAGING_RAWMODE_YCbCr_L; +extern const RawMode * const IMAGING_RAWMODE_Y_I; extern const RawMode * const IMAGING_RAWMODE_aBGR; +extern const RawMode * const IMAGING_RAWMODE_aRGB; const RawMode * findRawMode(const char * const name); diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index 976baa726..787602151 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -1541,9 +1541,9 @@ band316L(UINT8 *out, const UINT8 *in, int pixels) { } } -static struct { - const char *mode; - const char *rawmode; +static struct Unpacker { + const Mode *mode; + const RawMode *rawmode; int bits; ImagingShuffler unpack; } unpackers[] = { @@ -1846,13 +1846,12 @@ static struct { }; ImagingShuffler -ImagingFindUnpacker(const char *mode, const char *rawmode, int *bits_out) { +ImagingFindUnpacker(const Mode *mode, const RawMode *rawmode, int *bits_out) { int i; /* find a suitable pixel unpacker */ for (i = 0; unpackers[i].rawmode; i++) { - if (strcmp(unpackers[i].mode, mode) == 0 && - strcmp(unpackers[i].rawmode, rawmode) == 0) { + if (unpackers[i].mode == mode && unpackers[i].rawmode == rawmode) { if (bits_out) { *bits_out = unpackers[i].bits; } @@ -1864,3 +1863,317 @@ ImagingFindUnpacker(const char *mode, const char *rawmode, int *bits_out) { return NULL; } + +void +ImagingUnpackInit(void) { + const struct Unpacker temp[] = { + /* raw mode syntax is ";" where "bits" defaults + depending on mode (1 for "1", 8 for "P" and "L", etc), and + "flags" should be given in alphabetical order. if both bits + and flags have their default values, the ; should be left out */ + + /* flags: "I" inverted data; "R" reversed bit order; "B" big + endian byte order (default is little endian); "L" line + interleave, "S" signed, "F" floating point, "Z" inverted alpha */ + + /* exception: rawmodes "I" and "F" are always native endian byte order */ + + /* bilevel */ + {IMAGING_MODE_1, IMAGING_RAWMODE_1, 1, unpack1}, + {IMAGING_MODE_1, IMAGING_RAWMODE_1_I, 1, unpack1I}, + {IMAGING_MODE_1, IMAGING_RAWMODE_1_R, 1, unpack1R}, + {IMAGING_MODE_1, IMAGING_RAWMODE_1_IR, 1, unpack1IR}, + {IMAGING_MODE_1, IMAGING_RAWMODE_1_8, 8, unpack18}, + + /* grayscale */ + {IMAGING_MODE_L, IMAGING_RAWMODE_L_2, 2, unpackL2}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_2I, 2, unpackL2I}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_2R, 2, unpackL2R}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_2IR, 2, unpackL2IR}, + + {IMAGING_MODE_L, IMAGING_RAWMODE_L_4, 4, unpackL4}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_4I, 4, unpackL4I}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_4R, 4, unpackL4R}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_4IR, 4, unpackL4IR}, + + {IMAGING_MODE_L, IMAGING_RAWMODE_L, 8, copy1}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_I, 8, unpackLI}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_R, 8, unpackLR}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_16, 16, unpackL16}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_16B, 16, unpackL16B}, + + /* grayscale w. alpha */ + {IMAGING_MODE_LA, IMAGING_RAWMODE_LA, 16, unpackLA}, + {IMAGING_MODE_LA, IMAGING_RAWMODE_LA_L, 16, unpackLAL}, + + /* grayscale w. alpha premultiplied */ + {IMAGING_MODE_La, IMAGING_RAWMODE_La, 16, unpackLA}, + + /* palette */ + {IMAGING_MODE_P, IMAGING_RAWMODE_P_1, 1, unpackP1}, + {IMAGING_MODE_P, IMAGING_RAWMODE_P_2, 2, unpackP2}, + {IMAGING_MODE_P, IMAGING_RAWMODE_P_2L, 2, unpackP2L}, + {IMAGING_MODE_P, IMAGING_RAWMODE_P_4, 4, unpackP4}, + {IMAGING_MODE_P, IMAGING_RAWMODE_P_4L, 4, unpackP4L}, + {IMAGING_MODE_P, IMAGING_RAWMODE_P, 8, copy1}, + {IMAGING_MODE_P, IMAGING_RAWMODE_P_R, 8, unpackLR}, + {IMAGING_MODE_P, IMAGING_RAWMODE_L, 8, copy1}, + {IMAGING_MODE_P, IMAGING_RAWMODE_PX, 16, unpackL16B}, + + /* palette w. alpha */ + {IMAGING_MODE_PA, IMAGING_RAWMODE_PA, 16, unpackLA}, + {IMAGING_MODE_PA, IMAGING_RAWMODE_PA_L, 16, unpackLAL}, + {IMAGING_MODE_PA, IMAGING_RAWMODE_LA, 16, unpackLA}, + + /* true colour */ + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB, 24, ImagingUnpackRGB}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_L, 24, unpackRGBL}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_R, 24, unpackRGBR}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_16L, 48, unpackRGB16L}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_16B, 48, unpackRGB16B}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGR, 24, ImagingUnpackBGR}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_15, 16, ImagingUnpackRGB15}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGR_15, 16, ImagingUnpackBGR15}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_16, 16, ImagingUnpackRGB16}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGR_16, 16, ImagingUnpackBGR16}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX_16L, 64, unpackRGBA16L}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX_16B, 64, unpackRGBA16B}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_4B, 16, ImagingUnpackRGB4B}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGR_5, 16, ImagingUnpackBGR15}, /* compat */ + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX, 32, copy4}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX_L, 32, unpackRGBAL}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBXX, 40, copy4skip1}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBXXX, 48, copy4skip2}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBA_L, 32, unpackRGBAL}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBA_15, 16, ImagingUnpackRGBA15}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGRX, 32, ImagingUnpackBGRX}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGXR, 32, ImagingUnpackBGXR}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_XRGB, 32, ImagingUnpackXRGB}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_XBGR, 32, ImagingUnpackXBGR}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_YCC_P, 24, ImagingUnpackYCC}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_R, 8, band0}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_G, 8, band1}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_B, 8, band2}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_R_16L, 16, band016L}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_G_16L, 16, band116L}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_B_16L, 16, band216L}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_R_16B, 16, band016B}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_G_16B, 16, band116B}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_B_16B, 16, band216B}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_CMYK, 32, cmyk2rgb}, + + {IMAGING_MODE_BGR_15, IMAGING_RAWMODE_BGR_15, 16, copy2}, + {IMAGING_MODE_BGR_16, IMAGING_RAWMODE_BGR_16, 16, copy2}, + {IMAGING_MODE_BGR_24, IMAGING_RAWMODE_BGR_24, 24, copy3}, + + /* true colour w. alpha */ + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_LA, 16, unpackRGBALA}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_LA_16B, 32, unpackRGBALA16B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA, 32, copy4}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBAX, 40, copy4skip1}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBAXX, 48, copy4skip2}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa, 32, unpackRGBa}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBaX, 40, unpackRGBaskip1}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBaXX, 48, unpackRGBaskip2}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa_16L, 64, unpackRGBa16L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa_16B, 64, unpackRGBa16B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRa, 32, unpackBGRa}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_I, 32, unpackRGBAI}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_L, 32, unpackRGBAL}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_15, 16, ImagingUnpackRGBA15}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA_15, 16, ImagingUnpackBGRA15}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA_15Z, 16, ImagingUnpackBGRA15Z}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_4B, 16, ImagingUnpackRGBA4B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_16L, 64, unpackRGBA16L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_16B, 64, unpackRGBA16B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA, 32, unpackBGRA}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA_16L, 64, unpackBGRA16L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA_16B, 64, unpackBGRA16B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGAR, 32, unpackBGAR}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_ARGB, 32, unpackARGB}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_ABGR, 32, unpackABGR}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_YCCA_P, 32, ImagingUnpackYCCA}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R, 8, band0}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G, 8, band1}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B, 8, band2}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A, 8, band3}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R_16L, 16, band016L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G_16L, 16, band116L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B_16L, 16, band216L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A_16L, 16, band316L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R_16B, 16, band016B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G_16B, 16, band116B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B_16B, 16, band216B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A_16B, 16, band316B}, + +#ifdef WORDS_BIGENDIAN + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_16N, 48, unpackRGB16B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa_16N, 64, unpackRGBa16B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_16N, 64, unpackRGBA16B}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_16N, 64, unpackRGBA16B}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_R_16N, 16, band016B}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_G_16N, 16, band116B}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_B_16N, 16, band216B}, + + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R_16N, 16, band016B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G_16N, 16, band116B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B_16N, 16, band216B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A_16N, 16, band316B}, +#else + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_16N, 48, unpackRGB16L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa_16N, 64, unpackRGBa16L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_16N, 64, unpackRGBA16L}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_16N, 64, unpackRGBA16L}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_R_16N, 16, band016L}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_G_16N, 16, band116L}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_B_16N, 16, band216L}, + + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R_16N, 16, band016L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G_16N, 16, band116L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B_16N, 16, band216L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A_16N, 16, band316L}, +#endif + + /* true colour w. alpha premultiplied */ + {IMAGING_MODE_RGBa, IMAGING_RAWMODE_RGBa, 32, copy4}, + {IMAGING_MODE_RGBa, IMAGING_RAWMODE_BGRa, 32, unpackBGRA}, + {IMAGING_MODE_RGBa, IMAGING_RAWMODE_aRGB, 32, unpackARGB}, + {IMAGING_MODE_RGBa, IMAGING_RAWMODE_aBGR, 32, unpackABGR}, + + /* true colour w. padding */ + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB, 24, ImagingUnpackRGB}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB_L, 24, unpackRGBL}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB_16B, 48, unpackRGB16B}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGR, 24, ImagingUnpackBGR}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB_15, 16, ImagingUnpackRGB15}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGR_15, 16, ImagingUnpackBGR15}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB_4B, 16, ImagingUnpackRGB4B}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGR_5, 16, ImagingUnpackBGR15}, /* compat */ + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX, 32, copy4}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBXX, 40, copy4skip1}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBXXX, 48, copy4skip2}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_L, 32, unpackRGBAL}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_16L, 64, unpackRGBA16L}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_16B, 64, unpackRGBA16B}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGRX, 32, ImagingUnpackBGRX}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_XRGB, 32, ImagingUnpackXRGB}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_XBGR, 32, ImagingUnpackXBGR}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_YCC_P, 24, ImagingUnpackYCC}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_R, 8, band0}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_G, 8, band1}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_B, 8, band2}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_X, 8, band3}, + + /* colour separation */ + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK, 32, copy4}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYKX, 40, copy4skip1}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYKXX, 48, copy4skip2}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_I, 32, unpackCMYKI}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_L, 32, unpackRGBAL}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_16L, 64, unpackRGBA16L}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_16B, 64, unpackRGBA16B}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_C, 8, band0}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_M, 8, band1}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_Y, 8, band2}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_K, 8, band3}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_C_I, 8, band0I}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_M_I, 8, band1I}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_Y_I, 8, band2I}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_K_I, 8, band3I}, + +#ifdef WORDS_BIGENDIAN + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_16N, 64, unpackRGBA16B}, +#else + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_16N, 64, unpackRGBA16L}, +#endif + + /* video (YCbCr) */ + {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCr, 24, ImagingUnpackRGB}, + {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCr_L, 24, unpackRGBL}, + {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCrX, 32, copy4}, + {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCrK, 32, copy4}, + + /* LAB Color */ + {IMAGING_MODE_LAB, IMAGING_RAWMODE_LAB, 24, ImagingUnpackLAB}, + {IMAGING_MODE_LAB, IMAGING_RAWMODE_L, 8, band0}, + {IMAGING_MODE_LAB, IMAGING_RAWMODE_A, 8, band1}, + {IMAGING_MODE_LAB, IMAGING_RAWMODE_B, 8, band2}, + + /* HSV Color */ + {IMAGING_MODE_HSV, IMAGING_RAWMODE_HSV, 24, ImagingUnpackRGB}, + {IMAGING_MODE_HSV, IMAGING_RAWMODE_H, 8, band0}, + {IMAGING_MODE_HSV, IMAGING_RAWMODE_S, 8, band1}, + {IMAGING_MODE_HSV, IMAGING_RAWMODE_V, 8, band2}, + + /* integer variations */ + {IMAGING_MODE_I, IMAGING_RAWMODE_I, 32, copy4}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_8, 8, unpackI8}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_8S, 8, unpackI8S}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_16, 16, unpackI16}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_16S, 16, unpackI16S}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_16B, 16, unpackI16B}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_16BS, 16, unpackI16BS}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_16N, 16, unpackI16N}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_16NS, 16, unpackI16NS}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_32, 32, unpackI32}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_32S, 32, unpackI32S}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_32B, 32, unpackI32B}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_32BS, 32, unpackI32BS}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_32N, 32, unpackI32N}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_32NS, 32, unpackI32NS}, + + /* floating point variations */ + {IMAGING_MODE_F, IMAGING_RAWMODE_F, 32, copy4}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_8, 8, unpackF8}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_8S, 8, unpackF8S}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_16, 16, unpackF16}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_16S, 16, unpackF16S}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_16B, 16, unpackF16B}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_16BS, 16, unpackF16BS}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_16N, 16, unpackF16N}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_16NS, 16, unpackF16NS}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_32, 32, unpackF32}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_32S, 32, unpackF32S}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_32B, 32, unpackF32B}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_32BS, 32, unpackF32BS}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_32N, 32, unpackF32N}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_32NS, 32, unpackF32NS}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_32F, 32, unpackF32F}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_32BF, 32, unpackF32BF}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_32NF, 32, unpackF32NF}, +#ifdef FLOAT64 + {IMAGING_MODE_F, IMAGING_RAWMODE_F_64F, 64, unpackF64F}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_64BF, 64, unpackF64BF}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_64NF, 64, unpackF64NF}, +#endif + + /* storage modes */ + {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16, 16, copy2}, + {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16B, 16, copy2}, + {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16L, 16, copy2}, + {IMAGING_MODE_I_16N, IMAGING_RAWMODE_I_16N, 16, copy2}, + + {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16B, 16, unpackI16B_I16}, + {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16N, 16, unpackI16N_I16}, // LibTiff native->image endian. + {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16N, 16, unpackI16N_I16}, // LibTiff native->image endian. + {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16N, 16, unpackI16N_I16B}, + + {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16R, 16, unpackI16R_I16}, + + {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_12, 12, unpackI12_I16}, // 12 bit Tiffs stored in 16bits. + + {NULL} /* sentinel */ + }; + unpackers = malloc(sizeof(temp)); + if (unpackers == NULL) { + fprintf(stderr, "UnpackInit: failed to allocate memory for unpackers table\n"); + exit(1); + } + memcpy(unpackers, temp, sizeof(temp)); +} + +void +ImagingUnpackFree(void) { + free(unpackers); + unpackers = NULL; +} From feb7e6ef2d269bf481ee070ceebb0a00f7f9123d Mon Sep 17 00:00:00 2001 From: Yay295 Date: Mon, 22 Apr 2024 12:59:17 -0500 Subject: [PATCH 088/309] use mode structs in map.c --- src/map.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/map.c b/src/map.c index f4933e45e..451cca589 100644 --- a/src/map.c +++ b/src/map.c @@ -55,7 +55,7 @@ PyImaging_MapBuffer(PyObject *self, PyObject *args) { PyObject *target; Py_buffer view; - char *mode; + char *mode_name; char *codec; Py_ssize_t offset; int xsize, ysize; @@ -70,7 +70,7 @@ PyImaging_MapBuffer(PyObject *self, PyObject *args) { &ysize, &codec, &offset, - &mode, + &mode_name, &stride, &ystep )) { @@ -82,8 +82,10 @@ PyImaging_MapBuffer(PyObject *self, PyObject *args) { return NULL; } + const Mode * const mode = findMode(mode_name); + if (stride <= 0) { - if (!strcmp(mode, "L") || !strcmp(mode, "P")) { + if (mode == IMAGING_MODE_L || mode == IMAGING_MODE_P) { stride = xsize; } else if (isModeI16(mode)) { stride = xsize * 2; From c9c50ac678ca88da18c8b2695380065fdd637533 Mon Sep 17 00:00:00 2001 From: eyedav <88885346+eyedav@users.noreply.github.com> Date: Sat, 19 Jul 2025 16:00:26 +0200 Subject: [PATCH 089/309] initialize accessors similar to converters/packers/unpackers --- src/libImaging/Access.c | 90 +++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 52 deletions(-) diff --git a/src/libImaging/Access.c b/src/libImaging/Access.c index 95586d7ed..59a776fe0 100644 --- a/src/libImaging/Access.c +++ b/src/libImaging/Access.c @@ -11,10 +11,6 @@ #include "Imaging.h" -#define ACCESS_TABLE_SIZE 24 -static struct ImagingAccessInstance ACCESS_TABLE[ACCESS_TABLE_SIZE]; - - /* fetch individual pixel */ static void @@ -120,66 +116,53 @@ put_pixel_32(Imaging im, int x, int y, const void *color) { memcpy(&im->image32[y][x], color, sizeof(INT32)); } - -static void -set_access_table_item( - const int index, - const Mode * const mode, - void (*get_pixel)(Imaging im, int x, int y, void *pixel), - void (*put_pixel)(Imaging im, int x, int y, const void *pixel) -) { - ACCESS_TABLE[index].mode = mode; - ACCESS_TABLE[index].get_pixel = get_pixel; - ACCESS_TABLE[index].put_pixel = put_pixel; -} +static struct ImagingAccessInstance *accessors = NULL; void ImagingAccessInit(void) { - int i = 0; - set_access_table_item(i++, IMAGING_MODE_1, get_pixel_8, put_pixel_8); - set_access_table_item(i++, IMAGING_MODE_L, get_pixel_8, put_pixel_8); - set_access_table_item(i++, IMAGING_MODE_LA, get_pixel_32_2bands, put_pixel_32); - set_access_table_item(i++, IMAGING_MODE_La, get_pixel_32_2bands, put_pixel_32); - set_access_table_item(i++, IMAGING_MODE_I, get_pixel_32, put_pixel_32); - set_access_table_item(i++, IMAGING_MODE_I_16, get_pixel_16L, put_pixel_16L); - set_access_table_item(i++, IMAGING_MODE_I_16L, get_pixel_16L, put_pixel_16L); - set_access_table_item(i++, IMAGING_MODE_I_16B, get_pixel_16B, put_pixel_16B); + const struct ImagingAccessInstance temp[] = { + {IMAGING_MODE_1, get_pixel_8, put_pixel_8}, + {IMAGING_MODE_L, get_pixel_8, put_pixel_8}, + {IMAGING_MODE_LA, get_pixel_32_2bands, put_pixel_32}, + {IMAGING_MODE_La, get_pixel_32_2bands, put_pixel_32}, + {IMAGING_MODE_I, get_pixel_32, put_pixel_32}, + {IMAGING_MODE_I_16, get_pixel_16L, put_pixel_16L}, + {IMAGING_MODE_I_16L, get_pixel_16L, put_pixel_16L}, + {IMAGING_MODE_I_16B, get_pixel_16B, put_pixel_16B}, #ifdef WORDS_BIGENDIAN - set_access_table_item(i++, IMAGING_MODE_I_16N, get_pixel_16B, put_pixel_16B); + {IMAGING_MODE_I_16N, get_pixel_16B, put_pixel_16B}, #else - set_access_table_item(i++, IMAGING_MODE_I_16N, get_pixel_16L, put_pixel_16L); + {IMAGING_MODE_I_16N, get_pixel_16L, put_pixel_16L}, #endif - set_access_table_item(i++, IMAGING_MODE_I_32L, get_pixel_32L, put_pixel_32L); - set_access_table_item(i++, IMAGING_MODE_I_32B, get_pixel_32B, put_pixel_32B); - set_access_table_item(i++, IMAGING_MODE_F, get_pixel_32, put_pixel_32); - set_access_table_item(i++, IMAGING_MODE_P, get_pixel_8, put_pixel_8); - set_access_table_item(i++, IMAGING_MODE_PA, get_pixel_32_2bands, put_pixel_32); - set_access_table_item(i++, IMAGING_MODE_RGB, get_pixel_32, put_pixel_32); - set_access_table_item(i++, IMAGING_MODE_RGBA, get_pixel_32, put_pixel_32); - set_access_table_item(i++, IMAGING_MODE_RGBa, get_pixel_32, put_pixel_32); - set_access_table_item(i++, IMAGING_MODE_RGBX, get_pixel_32, put_pixel_32); - set_access_table_item(i++, IMAGING_MODE_CMYK, get_pixel_32, put_pixel_32); - set_access_table_item(i++, IMAGING_MODE_YCbCr, get_pixel_32, put_pixel_32); - set_access_table_item(i++, IMAGING_MODE_LAB, get_pixel_32, put_pixel_32); - set_access_table_item(i++, IMAGING_MODE_HSV, get_pixel_32, put_pixel_32); - - - if (i != ACCESS_TABLE_SIZE) { - fprintf( - stderr, - "AccessInit: incorrect number of items added to ACCESS_TABLE; expected %i but got %i\n", - ACCESS_TABLE_SIZE, - i); + {IMAGING_MODE_I_32L, get_pixel_32L, put_pixel_32L}, + {IMAGING_MODE_I_32B, get_pixel_32B, put_pixel_32B}, + {IMAGING_MODE_F, get_pixel_32, put_pixel_32}, + {IMAGING_MODE_P, get_pixel_8, put_pixel_8}, + {IMAGING_MODE_PA, get_pixel_32_2bands, put_pixel_32}, + {IMAGING_MODE_RGB, get_pixel_32, put_pixel_32}, + {IMAGING_MODE_RGBA, get_pixel_32, put_pixel_32}, + {IMAGING_MODE_RGBa, get_pixel_32, put_pixel_32}, + {IMAGING_MODE_RGBX, get_pixel_32, put_pixel_32}, + {IMAGING_MODE_CMYK, get_pixel_32, put_pixel_32}, + {IMAGING_MODE_YCbCr, get_pixel_32, put_pixel_32}, + {IMAGING_MODE_LAB, get_pixel_32, put_pixel_32}, + {IMAGING_MODE_HSV, get_pixel_32, put_pixel_32}, + {NULL} + }; + accessors = malloc(sizeof(temp)); + if (accessors == NULL) { + fprintf(stderr, "AccessInit: failed to allocate memory for accessors table\n"); exit(1); } + memcpy(accessors, temp, sizeof(temp)); } ImagingAccess ImagingAccessNew(const Imaging im) { int i; - for (i = 0; i < ACCESS_TABLE_SIZE; i++) { - if (im->mode == ACCESS_TABLE[i].mode) { - return &ACCESS_TABLE[i]; + for (i = 0; accessors[i].mode; i++) { + if (im->mode == accessors[i].mode) { + return &accessors[i]; } } return NULL; @@ -189,4 +172,7 @@ void _ImagingAccessDelete(Imaging im, ImagingAccess access) {} void -ImagingAccessFree(void) {} +ImagingAccessFree(void) { + free(accessors); + accessors = NULL; +} From cacb8b3ce70e73dd0b0f338410510cfbbd22709d Mon Sep 17 00:00:00 2001 From: Yay295 Date: Mon, 22 Apr 2024 14:01:53 -0500 Subject: [PATCH 090/309] define rawmodes --- src/libImaging/Mode.c | 292 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 292 insertions(+) diff --git a/src/libImaging/Mode.c b/src/libImaging/Mode.c index a11b65905..8d651b06d 100644 --- a/src/libImaging/Mode.c +++ b/src/libImaging/Mode.c @@ -109,6 +109,152 @@ ALIAS_MODE_AS_RAWMODE(I_16N) ALIAS_MODE_AS_RAWMODE(I_32L) ALIAS_MODE_AS_RAWMODE(I_32B) +CREATE_MODE(RawMode, RAWMODE_1_8, {"1;8"}) +CREATE_MODE(RawMode, RAWMODE_1_I, {"1;I"}) +CREATE_MODE(RawMode, RAWMODE_1_IR, {"1;IR"}) +CREATE_MODE(RawMode, RAWMODE_1_R, {"1;R"}) +CREATE_MODE(RawMode, RAWMODE_A, {"A"}) +CREATE_MODE(RawMode, RAWMODE_ABGR, {"ABGR"}) +CREATE_MODE(RawMode, RAWMODE_ARGB, {"ARGB"}) +CREATE_MODE(RawMode, RAWMODE_A_16B, {"A;16B"}) +CREATE_MODE(RawMode, RAWMODE_A_16L, {"A;16L"}) +CREATE_MODE(RawMode, RAWMODE_A_16N, {"A;16N"}) +CREATE_MODE(RawMode, RAWMODE_B, {"B"}) +CREATE_MODE(RawMode, RAWMODE_BGAR, {"BGAR"}) +CREATE_MODE(RawMode, RAWMODE_BGR, {"BGR"}) +CREATE_MODE(RawMode, RAWMODE_BGRA, {"BGRA"}) +CREATE_MODE(RawMode, RAWMODE_BGRA_15, {"BGRA;15"}) +CREATE_MODE(RawMode, RAWMODE_BGRA_15Z, {"BGRA;15Z"}) +CREATE_MODE(RawMode, RAWMODE_BGRA_16B, {"BGRA;16B"}) +CREATE_MODE(RawMode, RAWMODE_BGRA_16L, {"BGRA;16L"}) +CREATE_MODE(RawMode, RAWMODE_BGRX, {"BGRX"}) +CREATE_MODE(RawMode, RAWMODE_BGR_5, {"BGR;5"}) +CREATE_MODE(RawMode, RAWMODE_BGRa, {"BGRa"}) +CREATE_MODE(RawMode, RAWMODE_BGXR, {"BGXR"}) +CREATE_MODE(RawMode, RAWMODE_B_16B, {"B;16B"}) +CREATE_MODE(RawMode, RAWMODE_B_16L, {"B;16L"}) +CREATE_MODE(RawMode, RAWMODE_B_16N, {"B;16N"}) +CREATE_MODE(RawMode, RAWMODE_C, {"C"}) +CREATE_MODE(RawMode, RAWMODE_CMYKX, {"CMYKX"}) +CREATE_MODE(RawMode, RAWMODE_CMYKXX, {"CMYKXX"}) +CREATE_MODE(RawMode, RAWMODE_CMYK_16B, {"CMYK;16B"}) +CREATE_MODE(RawMode, RAWMODE_CMYK_16L, {"CMYK;16L"}) +CREATE_MODE(RawMode, RAWMODE_CMYK_16N, {"CMYK;16N"}) +CREATE_MODE(RawMode, RAWMODE_CMYK_I, {"CMYK;I"}) +CREATE_MODE(RawMode, RAWMODE_CMYK_L, {"CMYK;L"}) +CREATE_MODE(RawMode, RAWMODE_C_I, {"C;I"}) +CREATE_MODE(RawMode, RAWMODE_Cb, {"Cb"}) +CREATE_MODE(RawMode, RAWMODE_Cr, {"Cr"}) +CREATE_MODE(RawMode, RAWMODE_F_16, {"F;16"}) +CREATE_MODE(RawMode, RAWMODE_F_16B, {"F;16B"}) +CREATE_MODE(RawMode, RAWMODE_F_16BS, {"F;16BS"}) +CREATE_MODE(RawMode, RAWMODE_F_16N, {"F;16N"}) +CREATE_MODE(RawMode, RAWMODE_F_16NS, {"F;16NS"}) +CREATE_MODE(RawMode, RAWMODE_F_16S, {"F;16S"}) +CREATE_MODE(RawMode, RAWMODE_F_32, {"F;32"}) +CREATE_MODE(RawMode, RAWMODE_F_32B, {"F;32B"}) +CREATE_MODE(RawMode, RAWMODE_F_32BF, {"F;32BF"}) +CREATE_MODE(RawMode, RAWMODE_F_32BS, {"F;32BS"}) +CREATE_MODE(RawMode, RAWMODE_F_32F, {"F;32F"}) +CREATE_MODE(RawMode, RAWMODE_F_32N, {"F;32N"}) +CREATE_MODE(RawMode, RAWMODE_F_32NF, {"F;32NF"}) +CREATE_MODE(RawMode, RAWMODE_F_32NS, {"F;32NS"}) +CREATE_MODE(RawMode, RAWMODE_F_32S, {"F;32S"}) +CREATE_MODE(RawMode, RAWMODE_F_64BF, {"F;64BF"}) +CREATE_MODE(RawMode, RAWMODE_F_64F, {"F;64F"}) +CREATE_MODE(RawMode, RAWMODE_F_64NF, {"F;64NF"}) +CREATE_MODE(RawMode, RAWMODE_F_8, {"F;8"}) +CREATE_MODE(RawMode, RAWMODE_F_8S, {"F;8S"}) +CREATE_MODE(RawMode, RAWMODE_G, {"G"}) +CREATE_MODE(RawMode, RAWMODE_G_16B, {"G;16B"}) +CREATE_MODE(RawMode, RAWMODE_G_16L, {"G;16L"}) +CREATE_MODE(RawMode, RAWMODE_G_16N, {"G;16N"}) +CREATE_MODE(RawMode, RAWMODE_H, {"H"}) +CREATE_MODE(RawMode, RAWMODE_I_12, {"I;12"}) +CREATE_MODE(RawMode, RAWMODE_I_16BS, {"I;16BS"}) +CREATE_MODE(RawMode, RAWMODE_I_16NS, {"I;16NS"}) +CREATE_MODE(RawMode, RAWMODE_I_16R, {"I;16R"}) +CREATE_MODE(RawMode, RAWMODE_I_16S, {"I;16S"}) +CREATE_MODE(RawMode, RAWMODE_I_32, {"I;32"}) +CREATE_MODE(RawMode, RAWMODE_I_32BS, {"I;32BS"}) +CREATE_MODE(RawMode, RAWMODE_I_32N, {"I;32N"}) +CREATE_MODE(RawMode, RAWMODE_I_32NS, {"I;32NS"}) +CREATE_MODE(RawMode, RAWMODE_I_32S, {"I;32S"}) +CREATE_MODE(RawMode, RAWMODE_I_8, {"I;8"}) +CREATE_MODE(RawMode, RAWMODE_I_8S, {"I;8S"}) +CREATE_MODE(RawMode, RAWMODE_K, {"K"}) +CREATE_MODE(RawMode, RAWMODE_K_I, {"K;I"}) +CREATE_MODE(RawMode, RAWMODE_LA_16B, {"LA;16B"}) +CREATE_MODE(RawMode, RAWMODE_LA_L, {"LA;L"}) +CREATE_MODE(RawMode, RAWMODE_L_16, {"L;16"}) +CREATE_MODE(RawMode, RAWMODE_L_16B, {"L;16B"}) +CREATE_MODE(RawMode, RAWMODE_L_2, {"L;2"}) +CREATE_MODE(RawMode, RAWMODE_L_2I, {"L;2I"}) +CREATE_MODE(RawMode, RAWMODE_L_2IR, {"L;2IR"}) +CREATE_MODE(RawMode, RAWMODE_L_2R, {"L;2R"}) +CREATE_MODE(RawMode, RAWMODE_L_4, {"L;4"}) +CREATE_MODE(RawMode, RAWMODE_L_4I, {"L;4I"}) +CREATE_MODE(RawMode, RAWMODE_L_4IR, {"L;4IR"}) +CREATE_MODE(RawMode, RAWMODE_L_4R, {"L;4R"}) +CREATE_MODE(RawMode, RAWMODE_L_I, {"L;I"}) +CREATE_MODE(RawMode, RAWMODE_L_R, {"L;R"}) +CREATE_MODE(RawMode, RAWMODE_M, {"M"}) +CREATE_MODE(RawMode, RAWMODE_M_I, {"M;I"}) +CREATE_MODE(RawMode, RAWMODE_PA_L, {"PA;L"}) +CREATE_MODE(RawMode, RAWMODE_PX, {"PX"}) +CREATE_MODE(RawMode, RAWMODE_P_1, {"P;1"}) +CREATE_MODE(RawMode, RAWMODE_P_2, {"P;2"}) +CREATE_MODE(RawMode, RAWMODE_P_2L, {"P;2L"}) +CREATE_MODE(RawMode, RAWMODE_P_4, {"P;4"}) +CREATE_MODE(RawMode, RAWMODE_P_4L, {"P;4L"}) +CREATE_MODE(RawMode, RAWMODE_P_R, {"P;R"}) +CREATE_MODE(RawMode, RAWMODE_R, {"R"}) +CREATE_MODE(RawMode, RAWMODE_RGBAX, {"RGBAX"}) +CREATE_MODE(RawMode, RAWMODE_RGBAXX, {"RGBAXX"}) +CREATE_MODE(RawMode, RAWMODE_RGBA_15, {"RGBA;15"}) +CREATE_MODE(RawMode, RAWMODE_RGBA_16B, {"RGBA;16B"}) +CREATE_MODE(RawMode, RAWMODE_RGBA_16L, {"RGBA;16L"}) +CREATE_MODE(RawMode, RAWMODE_RGBA_16N, {"RGBA;16N"}) +CREATE_MODE(RawMode, RAWMODE_RGBA_4B, {"RGBA;4B"}) +CREATE_MODE(RawMode, RAWMODE_RGBA_I, {"RGBA;I"}) +CREATE_MODE(RawMode, RAWMODE_RGBA_L, {"RGBA;L"}) +CREATE_MODE(RawMode, RAWMODE_RGBXX, {"RGBXX"}) +CREATE_MODE(RawMode, RAWMODE_RGBXXX, {"RGBXXX"}) +CREATE_MODE(RawMode, RAWMODE_RGBX_16B, {"RGBX;16B"}) +CREATE_MODE(RawMode, RAWMODE_RGBX_16L, {"RGBX;16L"}) +CREATE_MODE(RawMode, RAWMODE_RGBX_16N, {"RGBX;16N"}) +CREATE_MODE(RawMode, RAWMODE_RGBX_L, {"RGBX;L"}) +CREATE_MODE(RawMode, RAWMODE_RGB_15, {"RGB;15"}) +CREATE_MODE(RawMode, RAWMODE_RGB_16, {"RGB;16"}) +CREATE_MODE(RawMode, RAWMODE_RGB_16B, {"RGB;16B"}) +CREATE_MODE(RawMode, RAWMODE_RGB_16L, {"RGB;16L"}) +CREATE_MODE(RawMode, RAWMODE_RGB_16N, {"RGB;16N"}) +CREATE_MODE(RawMode, RAWMODE_RGB_4B, {"RGB;4B"}) +CREATE_MODE(RawMode, RAWMODE_RGB_L, {"RGB;L"}) +CREATE_MODE(RawMode, RAWMODE_RGB_R, {"RGB;R"}) +CREATE_MODE(RawMode, RAWMODE_RGBaX, {"RGBaX"}) +CREATE_MODE(RawMode, RAWMODE_RGBaXX, {"RGBaXX"}) +CREATE_MODE(RawMode, RAWMODE_RGBa_16B, {"RGBa;16B"}) +CREATE_MODE(RawMode, RAWMODE_RGBa_16L, {"RGBa;16L"}) +CREATE_MODE(RawMode, RAWMODE_RGBa_16N, {"RGBa;16N"}) +CREATE_MODE(RawMode, RAWMODE_R_16B, {"R;16B"}) +CREATE_MODE(RawMode, RAWMODE_R_16L, {"R;16L"}) +CREATE_MODE(RawMode, RAWMODE_R_16N, {"R;16N"}) +CREATE_MODE(RawMode, RAWMODE_S, {"S"}) +CREATE_MODE(RawMode, RAWMODE_V, {"V"}) +CREATE_MODE(RawMode, RAWMODE_X, {"X"}) +CREATE_MODE(RawMode, RAWMODE_XBGR, {"XBGR"}) +CREATE_MODE(RawMode, RAWMODE_XRGB, {"XRGB"}) +CREATE_MODE(RawMode, RAWMODE_Y, {"Y"}) +CREATE_MODE(RawMode, RAWMODE_YCCA_P, {"YCCA;P"}) +CREATE_MODE(RawMode, RAWMODE_YCC_P, {"YCC;P"}) +CREATE_MODE(RawMode, RAWMODE_YCbCrK, {"YCbCrK"}) +CREATE_MODE(RawMode, RAWMODE_YCbCrX, {"YCbCrX"}) +CREATE_MODE(RawMode, RAWMODE_YCbCr_L, {"YCbCr;L"}) +CREATE_MODE(RawMode, RAWMODE_Y_I, {"Y;I"}) +CREATE_MODE(RawMode, RAWMODE_aBGR, {"aBGR"}) +CREATE_MODE(RawMode, RAWMODE_aRGB, {"aRGB"}) + const RawMode * const RAWMODES[] = { IMAGING_RAWMODE_1, IMAGING_RAWMODE_CMYK, @@ -138,6 +284,152 @@ const RawMode * const RAWMODES[] = { IMAGING_RAWMODE_I_32L, IMAGING_RAWMODE_I_32B, + IMAGING_RAWMODE_1_8, + IMAGING_RAWMODE_1_I, + IMAGING_RAWMODE_1_IR, + IMAGING_RAWMODE_1_R, + IMAGING_RAWMODE_A, + IMAGING_RAWMODE_ABGR, + IMAGING_RAWMODE_ARGB, + IMAGING_RAWMODE_A_16B, + IMAGING_RAWMODE_A_16L, + IMAGING_RAWMODE_A_16N, + IMAGING_RAWMODE_B, + IMAGING_RAWMODE_BGAR, + IMAGING_RAWMODE_BGR, + IMAGING_RAWMODE_BGRA, + IMAGING_RAWMODE_BGRA_15, + IMAGING_RAWMODE_BGRA_15Z, + IMAGING_RAWMODE_BGRA_16B, + IMAGING_RAWMODE_BGRA_16L, + IMAGING_RAWMODE_BGRX, + IMAGING_RAWMODE_BGR_5, + IMAGING_RAWMODE_BGRa, + IMAGING_RAWMODE_BGXR, + IMAGING_RAWMODE_B_16B, + IMAGING_RAWMODE_B_16L, + IMAGING_RAWMODE_B_16N, + IMAGING_RAWMODE_C, + IMAGING_RAWMODE_CMYKX, + IMAGING_RAWMODE_CMYKXX, + IMAGING_RAWMODE_CMYK_16B, + IMAGING_RAWMODE_CMYK_16L, + IMAGING_RAWMODE_CMYK_16N, + IMAGING_RAWMODE_CMYK_I, + IMAGING_RAWMODE_CMYK_L, + IMAGING_RAWMODE_C_I, + IMAGING_RAWMODE_Cb, + IMAGING_RAWMODE_Cr, + IMAGING_RAWMODE_F_16, + IMAGING_RAWMODE_F_16B, + IMAGING_RAWMODE_F_16BS, + IMAGING_RAWMODE_F_16N, + IMAGING_RAWMODE_F_16NS, + IMAGING_RAWMODE_F_16S, + IMAGING_RAWMODE_F_32, + IMAGING_RAWMODE_F_32B, + IMAGING_RAWMODE_F_32BF, + IMAGING_RAWMODE_F_32BS, + IMAGING_RAWMODE_F_32F, + IMAGING_RAWMODE_F_32N, + IMAGING_RAWMODE_F_32NF, + IMAGING_RAWMODE_F_32NS, + IMAGING_RAWMODE_F_32S, + IMAGING_RAWMODE_F_64BF, + IMAGING_RAWMODE_F_64F, + IMAGING_RAWMODE_F_64NF, + IMAGING_RAWMODE_F_8, + IMAGING_RAWMODE_F_8S, + IMAGING_RAWMODE_G, + IMAGING_RAWMODE_G_16B, + IMAGING_RAWMODE_G_16L, + IMAGING_RAWMODE_G_16N, + IMAGING_RAWMODE_H, + IMAGING_RAWMODE_I_12, + IMAGING_RAWMODE_I_16BS, + IMAGING_RAWMODE_I_16NS, + IMAGING_RAWMODE_I_16R, + IMAGING_RAWMODE_I_16S, + IMAGING_RAWMODE_I_32, + IMAGING_RAWMODE_I_32BS, + IMAGING_RAWMODE_I_32N, + IMAGING_RAWMODE_I_32NS, + IMAGING_RAWMODE_I_32S, + IMAGING_RAWMODE_I_8, + IMAGING_RAWMODE_I_8S, + IMAGING_RAWMODE_K, + IMAGING_RAWMODE_K_I, + IMAGING_RAWMODE_LA_16B, + IMAGING_RAWMODE_LA_L, + IMAGING_RAWMODE_L_16, + IMAGING_RAWMODE_L_16B, + IMAGING_RAWMODE_L_2, + IMAGING_RAWMODE_L_2I, + IMAGING_RAWMODE_L_2IR, + IMAGING_RAWMODE_L_2R, + IMAGING_RAWMODE_L_4, + IMAGING_RAWMODE_L_4I, + IMAGING_RAWMODE_L_4IR, + IMAGING_RAWMODE_L_4R, + IMAGING_RAWMODE_L_I, + IMAGING_RAWMODE_L_R, + IMAGING_RAWMODE_M, + IMAGING_RAWMODE_M_I, + IMAGING_RAWMODE_PA_L, + IMAGING_RAWMODE_PX, + IMAGING_RAWMODE_P_1, + IMAGING_RAWMODE_P_2, + IMAGING_RAWMODE_P_2L, + IMAGING_RAWMODE_P_4, + IMAGING_RAWMODE_P_4L, + IMAGING_RAWMODE_P_R, + IMAGING_RAWMODE_R, + IMAGING_RAWMODE_RGBAX, + IMAGING_RAWMODE_RGBAXX, + IMAGING_RAWMODE_RGBA_15, + IMAGING_RAWMODE_RGBA_16B, + IMAGING_RAWMODE_RGBA_16L, + IMAGING_RAWMODE_RGBA_16N, + IMAGING_RAWMODE_RGBA_4B, + IMAGING_RAWMODE_RGBA_I, + IMAGING_RAWMODE_RGBA_L, + IMAGING_RAWMODE_RGBXX, + IMAGING_RAWMODE_RGBXXX, + IMAGING_RAWMODE_RGBX_16B, + IMAGING_RAWMODE_RGBX_16L, + IMAGING_RAWMODE_RGBX_16N, + IMAGING_RAWMODE_RGBX_L, + IMAGING_RAWMODE_RGB_15, + IMAGING_RAWMODE_RGB_16, + IMAGING_RAWMODE_RGB_16B, + IMAGING_RAWMODE_RGB_16L, + IMAGING_RAWMODE_RGB_16N, + IMAGING_RAWMODE_RGB_4B, + IMAGING_RAWMODE_RGB_L, + IMAGING_RAWMODE_RGB_R, + IMAGING_RAWMODE_RGBaX, + IMAGING_RAWMODE_RGBaXX, + IMAGING_RAWMODE_RGBa_16B, + IMAGING_RAWMODE_RGBa_16L, + IMAGING_RAWMODE_RGBa_16N, + IMAGING_RAWMODE_R_16B, + IMAGING_RAWMODE_R_16L, + IMAGING_RAWMODE_R_16N, + IMAGING_RAWMODE_S, + IMAGING_RAWMODE_V, + IMAGING_RAWMODE_X, + IMAGING_RAWMODE_XBGR, + IMAGING_RAWMODE_XRGB, + IMAGING_RAWMODE_Y, + IMAGING_RAWMODE_YCCA_P, + IMAGING_RAWMODE_YCC_P, + IMAGING_RAWMODE_YCbCrK, + IMAGING_RAWMODE_YCbCrX, + IMAGING_RAWMODE_YCbCr_L, + IMAGING_RAWMODE_Y_I, + IMAGING_RAWMODE_aBGR, + IMAGING_RAWMODE_aRGB, + NULL }; From 20a5aeac84ea29a41699337f3e16acd1a667e85f Mon Sep 17 00:00:00 2001 From: Yay295 Date: Mon, 22 Apr 2024 15:25:40 -0500 Subject: [PATCH 091/309] fix findRawMode() and change findMode() to match --- src/libImaging/Mode.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/libImaging/Mode.c b/src/libImaging/Mode.c index 8d651b06d..04e843d32 100644 --- a/src/libImaging/Mode.c +++ b/src/libImaging/Mode.c @@ -68,10 +68,9 @@ const Mode * const MODES[] = { }; const Mode * findMode(const char * const name) { - int i = 0; const Mode * mode; - while ((mode = MODES[i++]) != NULL) { - if (!strcmp(mode->name, name)) { + for (int i = 0; (mode = MODES[i]); i++) { + if (strcmp(mode->name, name) == 0) { return mode; } } @@ -434,11 +433,9 @@ const RawMode * const RAWMODES[] = { }; const RawMode * findRawMode(const char * const name) { - int i = 0; const RawMode * rawmode; - while ((rawmode = RAWMODES[i++]) != NULL) { - const RawMode * const rawmode = RAWMODES[i]; - if (!strcmp(rawmode->name, name)) { + for (int i = 0; (rawmode = RAWMODES[i]); i++) { + if (strcmp(rawmode->name, name) == 0) { return rawmode; } } From 579c55ea86680f7bc22a0a9852146eeccafe4189 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Mon, 22 Apr 2024 17:16:56 -0500 Subject: [PATCH 092/309] check for null input in findMode() and findRawMode() --- src/libImaging/Mode.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libImaging/Mode.c b/src/libImaging/Mode.c index 04e843d32..01b2b5501 100644 --- a/src/libImaging/Mode.c +++ b/src/libImaging/Mode.c @@ -68,6 +68,9 @@ const Mode * const MODES[] = { }; const Mode * findMode(const char * const name) { + if (name == NULL) { + return NULL; + } const Mode * mode; for (int i = 0; (mode = MODES[i]); i++) { if (strcmp(mode->name, name) == 0) { @@ -433,6 +436,9 @@ const RawMode * const RAWMODES[] = { }; const RawMode * findRawMode(const char * const name) { + if (name == NULL) { + return NULL; + } const RawMode * rawmode; for (int i = 0; (rawmode = RAWMODES[i]); i++) { if (strcmp(rawmode->name, name) == 0) { From 422eb1ebc4384c44eef5e1c3f084abbeaf6cb010 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Mon, 22 Apr 2024 19:47:58 -0500 Subject: [PATCH 093/309] replace some string function usage with imaging mode checks --- src/_imaging.c | 8 +++++++- src/libImaging/Matrix.c | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index 6c98c42ea..f2d396140 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -1824,7 +1824,13 @@ _putpalette(ImagingObject *self, PyObject *args) { ImagingPaletteDelete(self->image->palette); - self->image->mode = strlen(self->image->mode->name) == 2 ? IMAGING_MODE_PA : IMAGING_MODE_P; + if (self->image->mode == IMAGING_MODE_LA) { + self->image->mode = IMAGING_MODE_PA; + } else if (self->image->mode == IMAGING_MODE_L) { + self->image->mode = IMAGING_MODE_P; + } else { + // The image already has a palette mode so we don't need to change it. + } self->image->palette = ImagingPaletteNew(palette_mode); diff --git a/src/libImaging/Matrix.c b/src/libImaging/Matrix.c index fd5584611..f848b870d 100644 --- a/src/libImaging/Matrix.c +++ b/src/libImaging/Matrix.c @@ -46,7 +46,11 @@ ImagingConvertMatrix(Imaging im, const Mode *mode, float m[]) { } } ImagingSectionLeave(&cookie); - } else if (strlen(mode->name) == 3) { + } else if ( + mode == IMAGING_MODE_HSV || + mode == IMAGING_MODE_LAB || + mode == IMAGING_MODE_RGB + ) { imOut = ImagingNewDirty(mode, im->xsize, im->ysize); if (!imOut) { return NULL; From 16fc61ee657f4a5df67993a6845940cd1f35612d Mon Sep 17 00:00:00 2001 From: Yay295 Date: Mon, 22 Apr 2024 20:16:28 -0500 Subject: [PATCH 094/309] use RawMode struct for jpegmode --- src/decode.c | 11 ++++------- src/libImaging/Jpeg.h | 4 ++-- src/libImaging/JpegDecode.c | 10 +++++----- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/decode.c b/src/decode.c index 9f4de28a8..e48dbbc3d 100644 --- a/src/decode.c +++ b/src/decode.c @@ -820,20 +820,17 @@ PyImaging_JpegDecoderNew(PyObject *self, PyObject *args) { char *mode_name; char *rawmode_name; /* what we want from the decoder */ - char *jpegmode; /* what's in the file */ + char *jpegmode_name; /* what's in the file */ int scale = 1; int draft = 0; - if (!PyArg_ParseTuple(args, "ssz|ii", &mode_name, &rawmode_name, &jpegmode, &scale, &draft)) { + if (!PyArg_ParseTuple(args, "ssz|ii", &mode_name, &rawmode_name, &jpegmode_name, &scale, &draft)) { return NULL; } const Mode * const mode = findMode(mode_name); const RawMode * rawmode = findRawMode(rawmode_name); - - if (!jpegmode) { - jpegmode = ""; - } + const RawMode * const jpegmode = findRawMode(jpegmode_name); decoder = PyImaging_DecoderNew(sizeof(JPEGSTATE)); if (decoder == NULL) { @@ -857,7 +854,7 @@ PyImaging_JpegDecoderNew(PyObject *self, PyObject *args) { JPEGSTATE *jpeg_decoder_state_context = (JPEGSTATE *)decoder->state.context; jpeg_decoder_state_context->rawmode = rawmode; - strncpy(jpeg_decoder_state_context->jpegmode, jpegmode, 8); + jpeg_decoder_state_context->jpegmode = jpegmode; jpeg_decoder_state_context->scale = scale; jpeg_decoder_state_context->draft = draft; diff --git a/src/libImaging/Jpeg.h b/src/libImaging/Jpeg.h index 35df91d7f..48c6c6184 100644 --- a/src/libImaging/Jpeg.h +++ b/src/libImaging/Jpeg.h @@ -28,8 +28,8 @@ typedef struct { typedef struct { /* CONFIGURATION */ - /* Jpeg file mode (empty if not known) */ - char jpegmode[8 + 1]; + /* Jpeg file mode (NULL if not known) */ + const RawMode *jpegmode; /* Converter output mode (input to the shuffler) */ /* If NULL, convert conversions are disabled */ diff --git a/src/libImaging/JpegDecode.c b/src/libImaging/JpegDecode.c index 36eb7835a..49d4fcb2f 100644 --- a/src/libImaging/JpegDecode.c +++ b/src/libImaging/JpegDecode.c @@ -182,15 +182,15 @@ ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t by /* jpegmode indicates what's in the file; if not set, we'll trust the decoder */ - if (strcmp(context->jpegmode, "L") == 0) { + if (context->jpegmode == IMAGING_RAWMODE_L) { context->cinfo.jpeg_color_space = JCS_GRAYSCALE; - } else if (strcmp(context->jpegmode, "RGB") == 0) { + } else if (context->jpegmode == IMAGING_RAWMODE_RGB) { context->cinfo.jpeg_color_space = JCS_RGB; - } else if (strcmp(context->jpegmode, "CMYK") == 0) { + } else if (context->jpegmode == IMAGING_RAWMODE_CMYK) { context->cinfo.jpeg_color_space = JCS_CMYK; - } else if (strcmp(context->jpegmode, "YCbCr") == 0) { + } else if (context->jpegmode == IMAGING_RAWMODE_YCbCr) { context->cinfo.jpeg_color_space = JCS_YCbCr; - } else if (strcmp(context->jpegmode, "YCbCrK") == 0) { + } else if (context->jpegmode == IMAGING_RAWMODE_YCbCrK) { context->cinfo.jpeg_color_space = JCS_YCCK; } From 4b07ed52fd22e5a3407f98fc999d0a8d6ef4546e Mon Sep 17 00:00:00 2001 From: Yay295 Date: Mon, 22 Apr 2024 20:43:49 -0500 Subject: [PATCH 095/309] use Mode struct for windows display code --- src/display.c | 16 +++++++--------- src/libImaging/Dib.c | 34 ++++++++++++++++------------------ src/libImaging/ImDib.h | 6 +++--- 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/src/display.c b/src/display.c index 3215f6691..0650b8866 100644 --- a/src/display.c +++ b/src/display.c @@ -47,7 +47,7 @@ typedef struct { static PyTypeObject ImagingDisplayType; static ImagingDisplayObject * -_new(const char *mode, int xsize, int ysize) { +_new(const Mode * const mode, int xsize, int ysize) { ImagingDisplayObject *display; if (PyType_Ready(&ImagingDisplayType) < 0) { @@ -235,7 +235,7 @@ static struct PyMethodDef methods[] = { static PyObject * _getattr_mode(ImagingDisplayObject *self, void *closure) { - return Py_BuildValue("s", self->dib->mode); + return Py_BuildValue("s", self->dib->mode->name); } static PyObject * @@ -258,13 +258,14 @@ static PyTypeObject ImagingDisplayType = { PyObject * PyImaging_DisplayWin32(PyObject *self, PyObject *args) { ImagingDisplayObject *display; - char *mode; + char *mode_name; int xsize, ysize; - if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) { + if (!PyArg_ParseTuple(args, "s(ii)", &mode_name, &xsize, &ysize)) { return NULL; } + const Mode * const mode = findMode(mode_name); display = _new(mode, xsize, ysize); if (display == NULL) { return NULL; @@ -275,12 +276,9 @@ PyImaging_DisplayWin32(PyObject *self, PyObject *args) { PyObject * PyImaging_DisplayModeWin32(PyObject *self, PyObject *args) { - char *mode; int size[2]; - - mode = ImagingGetModeDIB(size); - - return Py_BuildValue("s(ii)", mode, size[0], size[1]); + const Mode * const mode = ImagingGetModeDIB(size); + return Py_BuildValue("s(ii)", mode->name, size[0], size[1]); } /* -------------------------------------------------------------------- */ diff --git a/src/libImaging/Dib.c b/src/libImaging/Dib.c index c69e9e552..154c610ec 100644 --- a/src/libImaging/Dib.c +++ b/src/libImaging/Dib.c @@ -25,20 +25,17 @@ #include "ImDib.h" -char * +const Mode * ImagingGetModeDIB(int size_out[2]) { /* Get device characteristics */ - HDC dc; - char *mode; + const HDC dc = CreateCompatibleDC(NULL); - dc = CreateCompatibleDC(NULL); - - mode = "P"; + const Mode *mode = IMAGING_MODE_P; if (!(GetDeviceCaps(dc, RASTERCAPS) & RC_PALETTE)) { - mode = "RGB"; + mode = IMAGING_MODE_RGB; if (GetDeviceCaps(dc, BITSPIXEL) == 1) { - mode = "1"; + mode = IMAGING_MODE_1; } } @@ -53,7 +50,7 @@ ImagingGetModeDIB(int size_out[2]) { } ImagingDIB -ImagingNewDIB(const char *mode, int xsize, int ysize) { +ImagingNewDIB(const Mode * const mode, int xsize, int ysize) { /* Create a Windows bitmap */ ImagingDIB dib; @@ -61,10 +58,12 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) { int i; /* Check mode */ - if (strcmp(mode, "1") != 0 && strcmp(mode, "L") != 0 && strcmp(mode, "RGB") != 0) { + if (mode != IMAGING_MODE_1 && mode != IMAGING_MODE_L && mode != IMAGING_MODE_RGB) { return (ImagingDIB)ImagingError_ModeError(); } + const int pixelsize = mode == IMAGING_MODE_RGB ? 3 : 1; + /* Create DIB context and info header */ /* malloc check ok, small constant allocation */ dib = (ImagingDIB)malloc(sizeof(*dib)); @@ -83,7 +82,7 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) { dib->info->bmiHeader.biWidth = xsize; dib->info->bmiHeader.biHeight = ysize; dib->info->bmiHeader.biPlanes = 1; - dib->info->bmiHeader.biBitCount = strlen(mode) * 8; + dib->info->bmiHeader.biBitCount = pixelsize * 8; dib->info->bmiHeader.biCompression = BI_RGB; /* Create DIB */ @@ -103,12 +102,12 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) { return (ImagingDIB)ImagingError_MemoryError(); } - strcpy(dib->mode, mode); + dib->mode = mode; dib->xsize = xsize; dib->ysize = ysize; - dib->pixelsize = strlen(mode); - dib->linesize = (xsize * dib->pixelsize + 3) & -4; + dib->pixelsize = pixelsize; + dib->linesize = (xsize * pixelsize + 3) & -4; if (dib->pixelsize == 1) { dib->pack = dib->unpack = (ImagingShuffler)memcpy; @@ -132,7 +131,7 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) { } /* Create an associated palette (for 8-bit displays only) */ - if (strcmp(ImagingGetModeDIB(NULL), "P") == 0) { + if (ImagingGetModeDIB(NULL) == IMAGING_MODE_P) { char palbuf[sizeof(LOGPALETTE) + 256 * sizeof(PALETTEENTRY)]; LPLOGPALETTE pal = (LPLOGPALETTE)palbuf; int i, r, g, b; @@ -142,7 +141,7 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) { pal->palNumEntries = 256; GetSystemPaletteEntries(dib->dc, 0, 256, pal->palPalEntry); - if (strcmp(mode, "L") == 0) { + if (mode == IMAGING_MODE_L) { /* Grayscale DIB. Fill all 236 slots with a grayscale ramp * (this is usually overkill on Windows since VGA only offers * 6 bits grayscale resolution). Ignore the slots already @@ -156,8 +155,7 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) { } dib->palette = CreatePalette(pal); - - } else if (strcmp(mode, "RGB") == 0) { + } else if (mode == IMAGING_MODE_RGB) { #ifdef CUBE216 /* Colour DIB. Create a 6x6x6 colour cube (216 entries) and diff --git a/src/libImaging/ImDib.h b/src/libImaging/ImDib.h index 91ff3f322..6d8f420cb 100644 --- a/src/libImaging/ImDib.h +++ b/src/libImaging/ImDib.h @@ -27,7 +27,7 @@ struct ImagingDIBInstance { UINT8 *bits; HPALETTE palette; /* Used by cut and paste */ - char mode[4]; + const Mode *mode; int xsize, ysize; int pixelsize; int linesize; @@ -37,11 +37,11 @@ struct ImagingDIBInstance { typedef struct ImagingDIBInstance *ImagingDIB; -extern char * +extern const Mode * ImagingGetModeDIB(int size_out[2]); extern ImagingDIB -ImagingNewDIB(const char *mode, int xsize, int ysize); +ImagingNewDIB(const Mode * const mode, int xsize, int ysize); extern void ImagingDeleteDIB(ImagingDIB im); From 9527ce7f8c925e30e8b5535e8b115366743495e4 Mon Sep 17 00:00:00 2001 From: eyedav <88885346+eyedav@users.noreply.github.com> Date: Sat, 19 Jul 2025 16:54:32 +0200 Subject: [PATCH 096/309] change mode structs to enums Structs have better type safety, but they make allocation more difficult, especially when we have multiple Python modules trying to share the same code. --- src/_imaging.c | 51 +-- src/decode.c | 44 +- src/display.c | 10 +- src/encode.c | 34 +- src/libImaging/Access.c | 71 ++- src/libImaging/Bands.c | 2 +- src/libImaging/Chops.c | 58 +-- src/libImaging/Convert.c | 325 +++++++------- src/libImaging/Dib.c | 6 +- src/libImaging/Fill.c | 4 +- src/libImaging/ImDib.h | 6 +- src/libImaging/Imaging.h | 77 ++-- src/libImaging/Jpeg.h | 10 +- src/libImaging/Jpeg2KDecode.c | 2 +- src/libImaging/JpegDecode.c | 10 +- src/libImaging/Matrix.c | 2 +- src/libImaging/Mode.c | 651 ++++++++++----------------- src/libImaging/Mode.h | 436 ++++++++++--------- src/libImaging/Pack.c | 340 ++++----------- src/libImaging/Palette.c | 2 +- src/libImaging/Point.c | 4 +- src/libImaging/Storage.c | 16 +- src/libImaging/TiffDecode.c | 4 +- src/libImaging/Unpack.c | 796 ++++++++++------------------------ src/map.c | 2 +- 25 files changed, 1120 insertions(+), 1843 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index f2d396140..a940bb974 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -368,7 +368,7 @@ ImagingError_ValueError(const char *message) { /* -------------------------------------------------------------------- */ static int -getbands(const Mode *mode) { +getbands(const ModeID mode) { Imaging im; int bands; @@ -731,7 +731,7 @@ _fill(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); + const ModeID mode = findModeID(mode_name); im = ImagingNewDirty(mode, xsize, ysize); if (!im) { @@ -760,7 +760,7 @@ _new(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); + const ModeID mode = findModeID(mode_name); return PyImagingNew(ImagingNew(mode, xsize, ysize)); } @@ -774,7 +774,7 @@ _new_block(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); + const ModeID mode = findModeID(mode_name); return PyImagingNew(ImagingNewBlock(mode, xsize, ysize)); } @@ -787,7 +787,7 @@ _linear_gradient(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); + const ModeID mode = findModeID(mode_name); return PyImagingNew(ImagingFillLinearGradient(mode)); } @@ -800,7 +800,7 @@ _radial_gradient(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); + const ModeID mode = findModeID(mode_name); return PyImagingNew(ImagingFillRadialGradient(mode)); } @@ -964,7 +964,7 @@ _color_lut_3d(ImagingObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); + const ModeID mode = findModeID(mode_name); /* actually, it is trilinear */ if (filter != IMAGING_TRANSFORM_BILINEAR) { @@ -1033,7 +1033,7 @@ _convert(ImagingObject *self, PyObject *args) { } } - const Mode * const mode = findMode(mode_name); + const ModeID mode = findModeID(mode_name); return PyImagingNew(ImagingConvert( self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither @@ -1084,7 +1084,7 @@ _convert_matrix(ImagingObject *self, PyObject *args) { } } - const Mode * const mode = findMode(mode_name); + const ModeID mode = findModeID(mode_name); return PyImagingNew(ImagingConvertMatrix(self->image, mode, m)); } @@ -1094,12 +1094,12 @@ _convert_transparent(ImagingObject *self, PyObject *args) { char *mode_name; int r, g, b; if (PyArg_ParseTuple(args, "s(iii)", &mode_name, &r, &g, &b)) { - const Mode * const mode = findMode(mode_name); + const ModeID mode = findModeID(mode_name); return PyImagingNew(ImagingConvertTransparent(self->image, mode, r, g, b)); } PyErr_Clear(); if (PyArg_ParseTuple(args, "si", &mode_name, &r)) { - const Mode * const mode = findMode(mode_name); + const ModeID mode = findModeID(mode_name); return PyImagingNew(ImagingConvertTransparent(self->image, mode, r, 0, 0)); } return NULL; @@ -1209,8 +1209,8 @@ _getpalette(ImagingObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); - const RawMode * const rawmode = findRawMode(rawmode_name); + const ModeID mode = findModeID(mode_name); + const RawModeID rawmode = findRawModeID(rawmode_name); pack = ImagingFindPacker(mode, rawmode, &bits); if (!pack) { @@ -1238,7 +1238,7 @@ _getpalettemode(ImagingObject *self) { return NULL; } - return PyUnicode_FromString(self->image->palette->mode->name); + return PyUnicode_FromString(getModeData(self->image->palette->mode)->name); } static inline int @@ -1524,7 +1524,7 @@ _point(ImagingObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); + const ModeID mode = findModeID(mode_name); if (mode == IMAGING_MODE_F) { FLOAT32 *data; @@ -1799,14 +1799,14 @@ _putpalette(ImagingObject *self, PyObject *args) { return NULL; } - const Mode * const palette_mode = findMode(palette_mode_name); - if (palette_mode == NULL) { + const ModeID palette_mode = findModeID(palette_mode_name); + if (palette_mode == IMAGING_MODE_UNKNOWN) { PyErr_SetString(PyExc_ValueError, wrong_mode); return NULL; } - const RawMode * const rawmode = findRawMode(rawmode_name); - if (rawmode == NULL) { + const RawModeID rawmode = findRawModeID(rawmode_name); + if (rawmode == IMAGING_RAWMODE_UNKNOWN) { PyErr_SetString(PyExc_ValueError, wrong_raw_mode); return NULL; } @@ -2052,7 +2052,7 @@ _reduce(ImagingObject *self, PyObject *args) { } static int -isRGB(const Mode * const mode) { +isRGB(const ModeID mode) { return mode == IMAGING_MODE_RGB || mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_RGBX; } @@ -2068,7 +2068,7 @@ im_setmode(ImagingObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); + const ModeID mode = findModeID(mode_name); im = self->image; @@ -2472,7 +2472,7 @@ _merge(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); + const ModeID mode = findModeID(mode_name); if (band0) { bands[0] = band0->image; @@ -3779,7 +3779,7 @@ static struct PyMethodDef methods[] = { static PyObject * _getattr_mode(ImagingObject *self, void *closure) { - return PyUnicode_FromString(self->image->mode->name); + return PyUnicode_FromString(getModeData(self->image->mode)->name); } static PyObject * @@ -4323,11 +4323,6 @@ setup_module(PyObject *m) { return -1; } - ImagingAccessInit(); - ImagingConvertInit(); - ImagingPackInit(); - ImagingUnpackInit(); - #ifdef HAVE_LIBJPEG { extern const char *ImagingJpegVersion(void); diff --git a/src/decode.c b/src/decode.c index e48dbbc3d..41b2f6f31 100644 --- a/src/decode.c +++ b/src/decode.c @@ -266,7 +266,7 @@ static PyTypeObject ImagingDecoderType = { /* -------------------------------------------------------------------- */ int -get_unpacker(ImagingDecoderObject *decoder, const Mode *mode, const RawMode *rawmode) { +get_unpacker(ImagingDecoderObject *decoder, const ModeID mode, const RawModeID rawmode) { int bits; ImagingShuffler unpack; @@ -441,8 +441,8 @@ PyImaging_HexDecoderNew(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); - const RawMode * const rawmode = findRawMode(rawmode_name); + const ModeID mode = findModeID(mode_name); + const RawModeID rawmode = findRawModeID(rawmode_name); decoder = PyImaging_DecoderNew(0); if (decoder == NULL) { @@ -481,8 +481,8 @@ PyImaging_LibTiffDecoderNew(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); - const RawMode * const rawmode = findRawMode(rawmode_name); + const ModeID mode = findModeID(mode_name); + const RawModeID rawmode = findRawModeID(rawmode_name); TRACE(("new tiff decoder %s\n", compname)); @@ -522,8 +522,8 @@ PyImaging_PackbitsDecoderNew(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); - const RawMode * const rawmode = findRawMode(rawmode_name); + const ModeID mode = findModeID(mode_name); + const RawModeID rawmode = findRawModeID(rawmode_name); decoder = PyImaging_DecoderNew(0); if (decoder == NULL) { @@ -576,8 +576,8 @@ PyImaging_PcxDecoderNew(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); - const RawMode * const rawmode = findRawMode(rawmode_name); + const ModeID mode = findModeID(mode_name); + const RawModeID rawmode = findRawModeID(rawmode_name); decoder = PyImaging_DecoderNew(0); if (decoder == NULL) { @@ -610,8 +610,8 @@ PyImaging_RawDecoderNew(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); - const RawMode * const rawmode = findRawMode(rawmode_name); + const ModeID mode = findModeID(mode_name); + const RawModeID rawmode = findRawModeID(rawmode_name); decoder = PyImaging_DecoderNew(sizeof(RAWSTATE)); if (decoder == NULL) { @@ -646,8 +646,8 @@ PyImaging_SgiRleDecoderNew(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); - const RawMode * const rawmode = findRawMode(rawmode_name); + const ModeID mode = findModeID(mode_name); + const RawModeID rawmode = findRawModeID(rawmode_name); decoder = PyImaging_DecoderNew(sizeof(SGISTATE)); if (decoder == NULL) { @@ -680,8 +680,8 @@ PyImaging_SunRleDecoderNew(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); - const RawMode * const rawmode = findRawMode(rawmode_name); + const ModeID mode = findModeID(mode_name); + const RawModeID rawmode = findRawModeID(rawmode_name); decoder = PyImaging_DecoderNew(0); if (decoder == NULL) { @@ -712,8 +712,8 @@ PyImaging_TgaRleDecoderNew(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); - const RawMode * const rawmode = findRawMode(rawmode_name); + const ModeID mode = findModeID(mode_name); + const RawModeID rawmode = findRawModeID(rawmode_name); decoder = PyImaging_DecoderNew(0); if (decoder == NULL) { @@ -772,8 +772,8 @@ PyImaging_ZipDecoderNew(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); - const RawMode * const rawmode = findRawMode(rawmode_name); + const ModeID mode = findModeID(mode_name); + const RawModeID rawmode = findRawModeID(rawmode_name); decoder = PyImaging_DecoderNew(sizeof(ZIPSTATE)); if (decoder == NULL) { @@ -828,9 +828,9 @@ PyImaging_JpegDecoderNew(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); - const RawMode * rawmode = findRawMode(rawmode_name); - const RawMode * const jpegmode = findRawMode(jpegmode_name); + const ModeID mode = findModeID(mode_name); + RawModeID rawmode = findRawModeID(rawmode_name); + const RawModeID jpegmode = findRawModeID(jpegmode_name); decoder = PyImaging_DecoderNew(sizeof(JPEGSTATE)); if (decoder == NULL) { diff --git a/src/display.c b/src/display.c index 0650b8866..5b5853a3c 100644 --- a/src/display.c +++ b/src/display.c @@ -47,7 +47,7 @@ typedef struct { static PyTypeObject ImagingDisplayType; static ImagingDisplayObject * -_new(const Mode * const mode, int xsize, int ysize) { +_new(const ModeID mode, int xsize, int ysize) { ImagingDisplayObject *display; if (PyType_Ready(&ImagingDisplayType) < 0) { @@ -235,7 +235,7 @@ static struct PyMethodDef methods[] = { static PyObject * _getattr_mode(ImagingDisplayObject *self, void *closure) { - return Py_BuildValue("s", self->dib->mode->name); + return Py_BuildValue("s", getModeData(self->dib->mode)->name); } static PyObject * @@ -265,7 +265,7 @@ PyImaging_DisplayWin32(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); + const ModeID mode = findModeID(mode_name); display = _new(mode, xsize, ysize); if (display == NULL) { return NULL; @@ -277,8 +277,8 @@ PyImaging_DisplayWin32(PyObject *self, PyObject *args) { PyObject * PyImaging_DisplayModeWin32(PyObject *self, PyObject *args) { int size[2]; - const Mode * const mode = ImagingGetModeDIB(size); - return Py_BuildValue("s(ii)", mode->name, size[0], size[1]); + const ModeID mode = ImagingGetModeDIB(size); + return Py_BuildValue("s(ii)", getModeData(mode)->name, size[0], size[1]); } /* -------------------------------------------------------------------- */ diff --git a/src/encode.c b/src/encode.c index 311ffa4ee..3a6b6d6d0 100644 --- a/src/encode.c +++ b/src/encode.c @@ -334,7 +334,7 @@ static PyTypeObject ImagingEncoderType = { /* -------------------------------------------------------------------- */ int -get_packer(ImagingEncoderObject *encoder, const Mode *mode, const RawMode *rawmode) { +get_packer(ImagingEncoderObject *encoder, const ModeID mode, const RawModeID rawmode) { int bits; ImagingShuffler pack; @@ -344,8 +344,8 @@ get_packer(ImagingEncoderObject *encoder, const Mode *mode, const RawMode *rawmo PyErr_Format( PyExc_ValueError, "No packer found from %s to %s", - mode->name, - rawmode->name + getModeData(mode)->name, + getRawModeData(rawmode)->name ); return -1; } @@ -420,8 +420,8 @@ PyImaging_GifEncoderNew(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); - const RawMode * const rawmode = findRawMode(rawmode_name); + const ModeID mode = findModeID(mode_name); + const RawModeID rawmode = findRawModeID(rawmode_name); if (get_packer(encoder, mode, rawmode) < 0) { return NULL; @@ -456,8 +456,8 @@ PyImaging_PcxEncoderNew(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); - const RawMode * const rawmode = findRawMode(rawmode_name); + const ModeID mode = findModeID(mode_name); + const RawModeID rawmode = findRawModeID(rawmode_name); if (get_packer(encoder, mode, rawmode) < 0) { return NULL; @@ -490,8 +490,8 @@ PyImaging_RawEncoderNew(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); - const RawMode * const rawmode = findRawMode(rawmode_name); + const ModeID mode = findModeID(mode_name); + const RawModeID rawmode = findRawModeID(rawmode_name); if (get_packer(encoder, mode, rawmode) < 0) { return NULL; @@ -526,8 +526,8 @@ PyImaging_TgaRleEncoderNew(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); - const RawMode * const rawmode = findRawMode(rawmode_name); + const ModeID mode = findModeID(mode_name); + const RawModeID rawmode = findRawModeID(rawmode_name); if (get_packer(encoder, mode, rawmode) < 0) { return NULL; @@ -614,8 +614,8 @@ PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); - const RawMode * const rawmode = findRawMode(rawmode_name); + const ModeID mode = findModeID(mode_name); + const RawModeID rawmode = findRawModeID(rawmode_name); if (get_packer(encoder, mode, rawmode) < 0) { free(dictionary); @@ -721,8 +721,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); - const RawMode * const rawmode = findRawMode(rawmode_name); + const ModeID mode = findModeID(mode_name); + const RawModeID rawmode = findRawModeID(rawmode_name); if (get_packer(encoder, mode, rawmode) < 0) { return NULL; @@ -1161,8 +1161,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); - const RawMode * rawmode = findRawMode(rawmode_name); + const ModeID mode = findModeID(mode_name); + RawModeID rawmode = findRawModeID(rawmode_name); // libjpeg-turbo supports different output formats. // We are choosing Pillow's native format (3 color bytes + 1 padding) diff --git a/src/libImaging/Access.c b/src/libImaging/Access.c index 59a776fe0..6360e9147 100644 --- a/src/libImaging/Access.c +++ b/src/libImaging/Access.c @@ -116,51 +116,38 @@ put_pixel_32(Imaging im, int x, int y, const void *color) { memcpy(&im->image32[y][x], color, sizeof(INT32)); } -static struct ImagingAccessInstance *accessors = NULL; - -void -ImagingAccessInit(void) { - const struct ImagingAccessInstance temp[] = { - {IMAGING_MODE_1, get_pixel_8, put_pixel_8}, - {IMAGING_MODE_L, get_pixel_8, put_pixel_8}, - {IMAGING_MODE_LA, get_pixel_32_2bands, put_pixel_32}, - {IMAGING_MODE_La, get_pixel_32_2bands, put_pixel_32}, - {IMAGING_MODE_I, get_pixel_32, put_pixel_32}, - {IMAGING_MODE_I_16, get_pixel_16L, put_pixel_16L}, - {IMAGING_MODE_I_16L, get_pixel_16L, put_pixel_16L}, - {IMAGING_MODE_I_16B, get_pixel_16B, put_pixel_16B}, +static struct ImagingAccessInstance accessors[] = { + {IMAGING_MODE_1, get_pixel_8, put_pixel_8}, + {IMAGING_MODE_L, get_pixel_8, put_pixel_8}, + {IMAGING_MODE_LA, get_pixel_32_2bands, put_pixel_32}, + {IMAGING_MODE_La, get_pixel_32_2bands, put_pixel_32}, + {IMAGING_MODE_I, get_pixel_32, put_pixel_32}, + {IMAGING_MODE_I_16, get_pixel_16L, put_pixel_16L}, + {IMAGING_MODE_I_16L, get_pixel_16L, put_pixel_16L}, + {IMAGING_MODE_I_16B, get_pixel_16B, put_pixel_16B}, #ifdef WORDS_BIGENDIAN - {IMAGING_MODE_I_16N, get_pixel_16B, put_pixel_16B}, + {IMAGING_MODE_I_16N, get_pixel_16B, put_pixel_16B}, #else - {IMAGING_MODE_I_16N, get_pixel_16L, put_pixel_16L}, + {IMAGING_MODE_I_16N, get_pixel_16L, put_pixel_16L}, #endif - {IMAGING_MODE_I_32L, get_pixel_32L, put_pixel_32L}, - {IMAGING_MODE_I_32B, get_pixel_32B, put_pixel_32B}, - {IMAGING_MODE_F, get_pixel_32, put_pixel_32}, - {IMAGING_MODE_P, get_pixel_8, put_pixel_8}, - {IMAGING_MODE_PA, get_pixel_32_2bands, put_pixel_32}, - {IMAGING_MODE_RGB, get_pixel_32, put_pixel_32}, - {IMAGING_MODE_RGBA, get_pixel_32, put_pixel_32}, - {IMAGING_MODE_RGBa, get_pixel_32, put_pixel_32}, - {IMAGING_MODE_RGBX, get_pixel_32, put_pixel_32}, - {IMAGING_MODE_CMYK, get_pixel_32, put_pixel_32}, - {IMAGING_MODE_YCbCr, get_pixel_32, put_pixel_32}, - {IMAGING_MODE_LAB, get_pixel_32, put_pixel_32}, - {IMAGING_MODE_HSV, get_pixel_32, put_pixel_32}, - {NULL} - }; - accessors = malloc(sizeof(temp)); - if (accessors == NULL) { - fprintf(stderr, "AccessInit: failed to allocate memory for accessors table\n"); - exit(1); - } - memcpy(accessors, temp, sizeof(temp)); -} + {IMAGING_MODE_I_32L, get_pixel_32L, put_pixel_32L}, + {IMAGING_MODE_I_32B, get_pixel_32B, put_pixel_32B}, + {IMAGING_MODE_F, get_pixel_32, put_pixel_32}, + {IMAGING_MODE_P, get_pixel_8, put_pixel_8}, + {IMAGING_MODE_PA, get_pixel_32_2bands, put_pixel_32}, + {IMAGING_MODE_RGB, get_pixel_32, put_pixel_32}, + {IMAGING_MODE_RGBA, get_pixel_32, put_pixel_32}, + {IMAGING_MODE_RGBa, get_pixel_32, put_pixel_32}, + {IMAGING_MODE_RGBX, get_pixel_32, put_pixel_32}, + {IMAGING_MODE_CMYK, get_pixel_32, put_pixel_32}, + {IMAGING_MODE_YCbCr, get_pixel_32, put_pixel_32}, + {IMAGING_MODE_LAB, get_pixel_32, put_pixel_32}, + {IMAGING_MODE_HSV, get_pixel_32, put_pixel_32}, +}; ImagingAccess ImagingAccessNew(const Imaging im) { - int i; - for (i = 0; accessors[i].mode; i++) { + for (size_t i = 0; i < sizeof(accessors) / sizeof(*accessors); i++) { if (im->mode == accessors[i].mode) { return &accessors[i]; } @@ -170,9 +157,3 @@ ImagingAccessNew(const Imaging im) { void _ImagingAccessDelete(Imaging im, ImagingAccess access) {} - -void -ImagingAccessFree(void) { - free(accessors); - accessors = NULL; -} diff --git a/src/libImaging/Bands.c b/src/libImaging/Bands.c index 501b4625f..d1b0ebc4e 100644 --- a/src/libImaging/Bands.c +++ b/src/libImaging/Bands.c @@ -240,7 +240,7 @@ ImagingFillBand(Imaging imOut, int band, int color) { } Imaging -ImagingMerge(const Mode *mode, Imaging bands[4]) { +ImagingMerge(const ModeID mode, Imaging bands[4]) { int i, x, y; int bandsCount = 0; Imaging imOut; diff --git a/src/libImaging/Chops.c b/src/libImaging/Chops.c index 66d0b4f97..331f2dfe6 100644 --- a/src/libImaging/Chops.c +++ b/src/libImaging/Chops.c @@ -18,28 +18,28 @@ #include "Imaging.h" -#define CHOP(operation) \ - int x, y; \ - Imaging imOut; \ - imOut = create(imIn1, imIn2, NULL); \ - if (!imOut) { \ - return NULL; \ - } \ - for (y = 0; y < imOut->ysize; y++) { \ - UINT8 *out = (UINT8 *)imOut->image[y]; \ - UINT8 *in1 = (UINT8 *)imIn1->image[y]; \ - UINT8 *in2 = (UINT8 *)imIn2->image[y]; \ - for (x = 0; x < imOut->linesize; x++) { \ - int temp = operation; \ - if (temp <= 0) { \ - out[x] = 0; \ - } else if (temp >= 255) { \ - out[x] = 255; \ - } else { \ - out[x] = temp; \ - } \ - } \ - } \ +#define CHOP(operation) \ + int x, y; \ + Imaging imOut; \ + imOut = create(imIn1, imIn2, IMAGING_MODE_UNKNOWN); \ + if (!imOut) { \ + return NULL; \ + } \ + for (y = 0; y < imOut->ysize; y++) { \ + UINT8 *out = (UINT8 *)imOut->image[y]; \ + UINT8 *in1 = (UINT8 *)imIn1->image[y]; \ + UINT8 *in2 = (UINT8 *)imIn2->image[y]; \ + for (x = 0; x < imOut->linesize; x++) { \ + int temp = operation; \ + if (temp <= 0) { \ + out[x] = 0; \ + } else if (temp >= 255) { \ + out[x] = 255; \ + } else { \ + out[x] = temp; \ + } \ + } \ + } \ return imOut; #define CHOP2(operation, mode) \ @@ -60,11 +60,11 @@ return imOut; static Imaging -create(Imaging im1, Imaging im2, const Mode *mode) { +create(Imaging im1, Imaging im2, const ModeID mode) { int xsize, ysize; if (!im1 || !im2 || im1->type != IMAGING_TYPE_UINT8 || - (mode != NULL && (im1->mode != mode || im2->mode != mode))) { + (mode != IMAGING_MODE_UNKNOWN && (im1->mode != mode || im2->mode != mode))) { return (Imaging)ImagingError_ModeError(); } if (im1->type != im2->type || im1->bands != im2->bands) { @@ -129,12 +129,12 @@ ImagingChopXor(Imaging imIn1, Imaging imIn2) { Imaging ImagingChopAddModulo(Imaging imIn1, Imaging imIn2) { - CHOP2(in1[x] + in2[x], NULL); + CHOP2(in1[x] + in2[x], IMAGING_MODE_UNKNOWN); } Imaging ImagingChopSubtractModulo(Imaging imIn1, Imaging imIn2) { - CHOP2(in1[x] - in2[x], NULL); + CHOP2(in1[x] - in2[x], IMAGING_MODE_UNKNOWN); } Imaging @@ -142,7 +142,7 @@ ImagingChopSoftLight(Imaging imIn1, Imaging imIn2) { CHOP2( (((255 - in1[x]) * (in1[x] * in2[x])) / 65536) + (in1[x] * (255 - ((255 - in1[x]) * (255 - in2[x]) / 255))) / 255, - NULL + IMAGING_MODE_UNKNOWN ); } @@ -151,7 +151,7 @@ ImagingChopHardLight(Imaging imIn1, Imaging imIn2) { CHOP2( (in2[x] < 128) ? ((in1[x] * in2[x]) / 127) : 255 - (((255 - in2[x]) * (255 - in1[x])) / 127), - NULL + IMAGING_MODE_UNKNOWN ); } @@ -160,6 +160,6 @@ ImagingOverlay(Imaging imIn1, Imaging imIn2) { CHOP2( (in1[x] < 128) ? ((in1[x] * in2[x]) / 127) : 255 - (((255 - in1[x]) * (255 - in2[x])) / 127), - NULL + IMAGING_MODE_UNKNOWN ); } diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index 8f580c294..862f228e5 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -1090,7 +1090,7 @@ pa2ycbcr(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { } static Imaging -frompalette(Imaging imOut, Imaging imIn, const Mode *mode) { +frompalette(Imaging imOut, Imaging imIn, const ModeID mode) { ImagingSectionCookie cookie; int alpha; int y; @@ -1160,7 +1160,7 @@ frompalette(Imaging imOut, Imaging imIn, const Mode *mode) { #endif static Imaging topalette( - Imaging imOut, Imaging imIn, const Mode *mode, ImagingPalette inpalette, int dither + Imaging imOut, Imaging imIn, const ModeID mode, ImagingPalette inpalette, int dither ) { ImagingSectionCookie cookie; int alpha; @@ -1456,25 +1456,151 @@ tobilevel(Imaging imOut, Imaging imIn) { /* Conversion handlers */ /* ------------------- */ -static struct Converter { - const Mode *from; - const Mode *to; +static struct { + const ModeID from; + const ModeID to; ImagingShuffler convert; -} *converters = NULL; +} converters[] = { + {IMAGING_MODE_1, IMAGING_MODE_L, bit2l}, + {IMAGING_MODE_1, IMAGING_MODE_I, bit2i}, + {IMAGING_MODE_1, IMAGING_MODE_F, bit2f}, + {IMAGING_MODE_1, IMAGING_MODE_RGB, bit2rgb}, + {IMAGING_MODE_1, IMAGING_MODE_RGBA, bit2rgb}, + {IMAGING_MODE_1, IMAGING_MODE_RGBX, bit2rgb}, + {IMAGING_MODE_1, IMAGING_MODE_CMYK, bit2cmyk}, + {IMAGING_MODE_1, IMAGING_MODE_YCbCr, bit2ycbcr}, + {IMAGING_MODE_1, IMAGING_MODE_HSV, bit2hsv}, + + {IMAGING_MODE_L, IMAGING_MODE_1, l2bit}, + {IMAGING_MODE_L, IMAGING_MODE_LA, l2la}, + {IMAGING_MODE_L, IMAGING_MODE_I, l2i}, + {IMAGING_MODE_L, IMAGING_MODE_F, l2f}, + {IMAGING_MODE_L, IMAGING_MODE_RGB, l2rgb}, + {IMAGING_MODE_L, IMAGING_MODE_RGBA, l2rgb}, + {IMAGING_MODE_L, IMAGING_MODE_RGBX, l2rgb}, + {IMAGING_MODE_L, IMAGING_MODE_CMYK, l2cmyk}, + {IMAGING_MODE_L, IMAGING_MODE_YCbCr, l2ycbcr}, + {IMAGING_MODE_L, IMAGING_MODE_HSV, l2hsv}, + + {IMAGING_MODE_LA, IMAGING_MODE_L, la2l}, + {IMAGING_MODE_LA, IMAGING_MODE_La, lA2la}, + {IMAGING_MODE_LA, IMAGING_MODE_RGB, la2rgb}, + {IMAGING_MODE_LA, IMAGING_MODE_RGBA, la2rgb}, + {IMAGING_MODE_LA, IMAGING_MODE_RGBX, la2rgb}, + {IMAGING_MODE_LA, IMAGING_MODE_CMYK, la2cmyk}, + {IMAGING_MODE_LA, IMAGING_MODE_YCbCr, la2ycbcr}, + {IMAGING_MODE_LA, IMAGING_MODE_HSV, la2hsv}, + + {IMAGING_MODE_La, IMAGING_MODE_LA, la2lA}, + + {IMAGING_MODE_I, IMAGING_MODE_L, i2l}, + {IMAGING_MODE_I, IMAGING_MODE_F, i2f}, + {IMAGING_MODE_I, IMAGING_MODE_RGB, i2rgb}, + {IMAGING_MODE_I, IMAGING_MODE_RGBA, i2rgb}, + {IMAGING_MODE_I, IMAGING_MODE_RGBX, i2rgb}, + {IMAGING_MODE_I, IMAGING_MODE_HSV, i2hsv}, + + {IMAGING_MODE_F, IMAGING_MODE_L, f2l}, + {IMAGING_MODE_F, IMAGING_MODE_I, f2i}, + + {IMAGING_MODE_RGB, IMAGING_MODE_1, rgb2bit}, + {IMAGING_MODE_RGB, IMAGING_MODE_L, rgb2l}, + {IMAGING_MODE_RGB, IMAGING_MODE_LA, rgb2la}, + {IMAGING_MODE_RGB, IMAGING_MODE_La, rgb2la}, + {IMAGING_MODE_RGB, IMAGING_MODE_I, rgb2i}, + {IMAGING_MODE_RGB, IMAGING_MODE_I_16, rgb2i16l}, + {IMAGING_MODE_RGB, IMAGING_MODE_I_16L, rgb2i16l}, + {IMAGING_MODE_RGB, IMAGING_MODE_I_16B, rgb2i16b}, +#ifdef WORDS_BIGENDIAN + {IMAGING_MODE_RGB, IMAGING_MODE_I_16N, rgb2i16b}, +#else + {IMAGING_MODE_RGB, IMAGING_MODE_I_16N, rgb2i16l}, +#endif + {IMAGING_MODE_RGB, IMAGING_MODE_F, rgb2f}, + {IMAGING_MODE_RGB, IMAGING_MODE_BGR_15, rgb2bgr15}, + {IMAGING_MODE_RGB, IMAGING_MODE_BGR_16, rgb2bgr16}, + {IMAGING_MODE_RGB, IMAGING_MODE_BGR_24, rgb2bgr24}, + {IMAGING_MODE_RGB, IMAGING_MODE_RGBA, rgb2rgba}, + {IMAGING_MODE_RGB, IMAGING_MODE_RGBa, rgb2rgba}, + {IMAGING_MODE_RGB, IMAGING_MODE_RGBX, rgb2rgba}, + {IMAGING_MODE_RGB, IMAGING_MODE_CMYK, rgb2cmyk}, + {IMAGING_MODE_RGB, IMAGING_MODE_YCbCr, ImagingConvertRGB2YCbCr}, + {IMAGING_MODE_RGB, IMAGING_MODE_HSV, rgb2hsv}, + + {IMAGING_MODE_RGBA, IMAGING_MODE_1, rgb2bit}, + {IMAGING_MODE_RGBA, IMAGING_MODE_L, rgb2l}, + {IMAGING_MODE_RGBA, IMAGING_MODE_LA, rgba2la}, + {IMAGING_MODE_RGBA, IMAGING_MODE_I, rgb2i}, + {IMAGING_MODE_RGBA, IMAGING_MODE_F, rgb2f}, + {IMAGING_MODE_RGBA, IMAGING_MODE_RGB, rgba2rgb}, + {IMAGING_MODE_RGBA, IMAGING_MODE_RGBa, rgbA2rgba}, + {IMAGING_MODE_RGBA, IMAGING_MODE_RGBX, rgb2rgba}, + {IMAGING_MODE_RGBA, IMAGING_MODE_CMYK, rgb2cmyk}, + {IMAGING_MODE_RGBA, IMAGING_MODE_YCbCr, ImagingConvertRGB2YCbCr}, + {IMAGING_MODE_RGBA, IMAGING_MODE_HSV, rgb2hsv}, + + {IMAGING_MODE_RGBa, IMAGING_MODE_RGBA, rgba2rgbA}, + {IMAGING_MODE_RGBa, IMAGING_MODE_RGB, rgba2rgb_}, + + {IMAGING_MODE_RGBX, IMAGING_MODE_1, rgb2bit}, + {IMAGING_MODE_RGBX, IMAGING_MODE_L, rgb2l}, + {IMAGING_MODE_RGBX, IMAGING_MODE_LA, rgb2la}, + {IMAGING_MODE_RGBX, IMAGING_MODE_I, rgb2i}, + {IMAGING_MODE_RGBX, IMAGING_MODE_F, rgb2f}, + {IMAGING_MODE_RGBX, IMAGING_MODE_RGB, rgba2rgb}, + {IMAGING_MODE_RGBX, IMAGING_MODE_CMYK, rgb2cmyk}, + {IMAGING_MODE_RGBX, IMAGING_MODE_YCbCr, ImagingConvertRGB2YCbCr}, + {IMAGING_MODE_RGBX, IMAGING_MODE_HSV, rgb2hsv}, + + {IMAGING_MODE_CMYK, IMAGING_MODE_RGB, cmyk2rgb}, + {IMAGING_MODE_CMYK, IMAGING_MODE_RGBA, cmyk2rgb}, + {IMAGING_MODE_CMYK, IMAGING_MODE_RGBX, cmyk2rgb}, + {IMAGING_MODE_CMYK, IMAGING_MODE_HSV, cmyk2hsv}, + + {IMAGING_MODE_YCbCr, IMAGING_MODE_L, ycbcr2l}, + {IMAGING_MODE_YCbCr, IMAGING_MODE_LA, ycbcr2la}, + {IMAGING_MODE_YCbCr, IMAGING_MODE_RGB, ImagingConvertYCbCr2RGB}, + + {IMAGING_MODE_HSV, IMAGING_MODE_RGB, hsv2rgb}, + + {IMAGING_MODE_I, IMAGING_MODE_I_16, I_I16L}, + {IMAGING_MODE_I_16, IMAGING_MODE_I, I16L_I}, + {IMAGING_MODE_I_16, IMAGING_MODE_RGB, I16_RGB}, + {IMAGING_MODE_L, IMAGING_MODE_I_16, L_I16L}, + {IMAGING_MODE_I_16, IMAGING_MODE_L, I16L_L}, + + {IMAGING_MODE_I, IMAGING_MODE_I_16L, I_I16L}, + {IMAGING_MODE_I_16L, IMAGING_MODE_I, I16L_I}, + {IMAGING_MODE_I, IMAGING_MODE_I_16B, I_I16B}, + {IMAGING_MODE_I_16B, IMAGING_MODE_I, I16B_I}, + + {IMAGING_MODE_L, IMAGING_MODE_I_16L, L_I16L}, + {IMAGING_MODE_I_16L, IMAGING_MODE_L, I16L_L}, + {IMAGING_MODE_L, IMAGING_MODE_I_16B, L_I16B}, + {IMAGING_MODE_I_16B, IMAGING_MODE_L, I16B_L}, +#ifdef WORDS_BIGENDIAN + {IMAGING_MODE_L, IMAGING_MODE_I_16N, L_I16B}, + {IMAGING_MODE_I_16N, IMAGING_MODE_L, I16B_L}, +#else + {IMAGING_MODE_L, IMAGING_MODE_I_16N, L_I16L}, + {IMAGING_MODE_I_16N, IMAGING_MODE_L, I16L_L}, +#endif + + {IMAGING_MODE_I_16, IMAGING_MODE_F, I16L_F}, + {IMAGING_MODE_I_16L, IMAGING_MODE_F, I16L_F}, + {IMAGING_MODE_I_16B, IMAGING_MODE_F, I16B_F} +}; static Imaging -convert( - Imaging imOut, Imaging imIn, const Mode *mode, ImagingPalette palette, int dither -) { +convert(Imaging imOut, Imaging imIn, ModeID mode, ImagingPalette palette, int dither) { ImagingSectionCookie cookie; ImagingShuffler convert; - int y; if (!imIn) { return (Imaging)ImagingError_ModeError(); } - if (!mode) { + if (mode == IMAGING_MODE_UNKNOWN) { /* Map palette image to full depth */ if (!imIn->palette) { return (Imaging)ImagingError_ModeError(); @@ -1504,10 +1630,9 @@ convert( /* standard conversion machinery */ convert = NULL; - - for (y = 0; converters[y].from; y++) { - if (imIn->mode == converters[y].from && mode == converters[y].to) { - convert = converters[y].convert; + for (size_t i = 0; i < sizeof(converters) / sizeof(*converters); i++) { + if (imIn->mode == converters[i].from && mode == converters[i].to) { + convert = converters[i].convert; break; } } @@ -1518,7 +1643,11 @@ convert( #else static char buf[100]; snprintf( - buf, 100, "conversion from %.10s to %.10s not supported", imIn->mode->name, mode->name + buf, + 100, + "conversion from %.10s to %.10s not supported", + getModeData(imIn->mode)->name, + getModeData(mode)->name ); return (Imaging)ImagingError_ValueError(buf); #endif @@ -1530,7 +1659,7 @@ convert( } ImagingSectionEnter(&cookie); - for (y = 0; y < imIn->ysize; y++) { + for (int y = 0; y < imIn->ysize; y++) { (*convert)((UINT8 *)imOut->image[y], (UINT8 *)imIn->image[y], imIn->xsize); } ImagingSectionLeave(&cookie); @@ -1539,7 +1668,7 @@ convert( } Imaging -ImagingConvert(Imaging imIn, const Mode *mode, ImagingPalette palette, int dither) { +ImagingConvert(Imaging imIn, const ModeID mode, ImagingPalette palette, int dither) { return convert(NULL, imIn, mode, palette, dither); } @@ -1549,7 +1678,7 @@ ImagingConvert2(Imaging imOut, Imaging imIn) { } Imaging -ImagingConvertTransparent(Imaging imIn, const Mode *mode, int r, int g, int b) { +ImagingConvertTransparent(Imaging imIn, const ModeID mode, int r, int g, int b) { ImagingSectionCookie cookie; ImagingShuffler convert; Imaging imOut = NULL; @@ -1598,8 +1727,8 @@ ImagingConvertTransparent(Imaging imIn, const Mode *mode, int r, int g, int b) { buf, 100, "conversion from %.10s to %.10s not supported in convert_transparent", - imIn->mode->name, - mode->name + getModeData(imIn->mode)->name, + getModeData(mode)->name ); return (Imaging)ImagingError_ValueError(buf); } @@ -1622,7 +1751,7 @@ ImagingConvertTransparent(Imaging imIn, const Mode *mode, int r, int g, int b) { } Imaging -ImagingConvertInPlace(Imaging imIn, const Mode *mode) { +ImagingConvertInPlace(Imaging imIn, const ModeID mode) { ImagingSectionCookie cookie; ImagingShuffler convert; int y; @@ -1644,155 +1773,3 @@ ImagingConvertInPlace(Imaging imIn, const Mode *mode) { return imIn; } - -/* ------------------ */ -/* Converter mappings */ -/* ------------------ */ - -void -ImagingConvertInit(void) { - const struct Converter temp[] = { - {IMAGING_MODE_1, IMAGING_MODE_L, bit2l}, - {IMAGING_MODE_1, IMAGING_MODE_I, bit2i}, - {IMAGING_MODE_1, IMAGING_MODE_F, bit2f}, - {IMAGING_MODE_1, IMAGING_MODE_RGB, bit2rgb}, - {IMAGING_MODE_1, IMAGING_MODE_RGBA, bit2rgb}, - {IMAGING_MODE_1, IMAGING_MODE_RGBX, bit2rgb}, - {IMAGING_MODE_1, IMAGING_MODE_CMYK, bit2cmyk}, - {IMAGING_MODE_1, IMAGING_MODE_YCbCr, bit2ycbcr}, - {IMAGING_MODE_1, IMAGING_MODE_HSV, bit2hsv}, - - {IMAGING_MODE_L, IMAGING_MODE_1, l2bit}, - {IMAGING_MODE_L, IMAGING_MODE_LA, l2la}, - {IMAGING_MODE_L, IMAGING_MODE_I, l2i}, - {IMAGING_MODE_L, IMAGING_MODE_F, l2f}, - {IMAGING_MODE_L, IMAGING_MODE_RGB, l2rgb}, - {IMAGING_MODE_L, IMAGING_MODE_RGBA, l2rgb}, - {IMAGING_MODE_L, IMAGING_MODE_RGBX, l2rgb}, - {IMAGING_MODE_L, IMAGING_MODE_CMYK, l2cmyk}, - {IMAGING_MODE_L, IMAGING_MODE_YCbCr, l2ycbcr}, - {IMAGING_MODE_L, IMAGING_MODE_HSV, l2hsv}, - - {IMAGING_MODE_LA, IMAGING_MODE_L, la2l}, - {IMAGING_MODE_LA, IMAGING_MODE_La, lA2la}, - {IMAGING_MODE_LA, IMAGING_MODE_RGB, la2rgb}, - {IMAGING_MODE_LA, IMAGING_MODE_RGBA, la2rgb}, - {IMAGING_MODE_LA, IMAGING_MODE_RGBX, la2rgb}, - {IMAGING_MODE_LA, IMAGING_MODE_CMYK, la2cmyk}, - {IMAGING_MODE_LA, IMAGING_MODE_YCbCr, la2ycbcr}, - {IMAGING_MODE_LA, IMAGING_MODE_HSV, la2hsv}, - - {IMAGING_MODE_La, IMAGING_MODE_LA, la2lA}, - - {IMAGING_MODE_I, IMAGING_MODE_L, i2l}, - {IMAGING_MODE_I, IMAGING_MODE_F, i2f}, - {IMAGING_MODE_I, IMAGING_MODE_RGB, i2rgb}, - {IMAGING_MODE_I, IMAGING_MODE_RGBA, i2rgb}, - {IMAGING_MODE_I, IMAGING_MODE_RGBX, i2rgb}, - {IMAGING_MODE_I, IMAGING_MODE_HSV, i2hsv}, - - {IMAGING_MODE_F, IMAGING_MODE_L, f2l}, - {IMAGING_MODE_F, IMAGING_MODE_I, f2i}, - - {IMAGING_MODE_RGB, IMAGING_MODE_1, rgb2bit}, - {IMAGING_MODE_RGB, IMAGING_MODE_L, rgb2l}, - {IMAGING_MODE_RGB, IMAGING_MODE_LA, rgb2la}, - {IMAGING_MODE_RGB, IMAGING_MODE_La, rgb2la}, - {IMAGING_MODE_RGB, IMAGING_MODE_I, rgb2i}, - {IMAGING_MODE_RGB, IMAGING_MODE_I_16, rgb2i16l}, - {IMAGING_MODE_RGB, IMAGING_MODE_I_16L, rgb2i16l}, - {IMAGING_MODE_RGB, IMAGING_MODE_I_16B, rgb2i16b}, -#ifdef WORDS_BIGENDIAN - {IMAGING_MODE_RGB, IMAGING_MODE_I_16N, rgb2i16b}, -#else - {IMAGING_MODE_RGB, IMAGING_MODE_I_16N, rgb2i16l}, -#endif - {IMAGING_MODE_RGB, IMAGING_MODE_F, rgb2f}, - {IMAGING_MODE_RGB, IMAGING_MODE_BGR_15, rgb2bgr15}, - {IMAGING_MODE_RGB, IMAGING_MODE_BGR_16, rgb2bgr16}, - {IMAGING_MODE_RGB, IMAGING_MODE_BGR_24, rgb2bgr24}, - {IMAGING_MODE_RGB, IMAGING_MODE_RGBA, rgb2rgba}, - {IMAGING_MODE_RGB, IMAGING_MODE_RGBa, rgb2rgba}, - {IMAGING_MODE_RGB, IMAGING_MODE_RGBX, rgb2rgba}, - {IMAGING_MODE_RGB, IMAGING_MODE_CMYK, rgb2cmyk}, - {IMAGING_MODE_RGB, IMAGING_MODE_YCbCr, ImagingConvertRGB2YCbCr}, - {IMAGING_MODE_RGB, IMAGING_MODE_HSV, rgb2hsv}, - - {IMAGING_MODE_RGBA, IMAGING_MODE_1, rgb2bit}, - {IMAGING_MODE_RGBA, IMAGING_MODE_L, rgb2l}, - {IMAGING_MODE_RGBA, IMAGING_MODE_LA, rgba2la}, - {IMAGING_MODE_RGBA, IMAGING_MODE_I, rgb2i}, - {IMAGING_MODE_RGBA, IMAGING_MODE_F, rgb2f}, - {IMAGING_MODE_RGBA, IMAGING_MODE_RGB, rgba2rgb}, - {IMAGING_MODE_RGBA, IMAGING_MODE_RGBa, rgbA2rgba}, - {IMAGING_MODE_RGBA, IMAGING_MODE_RGBX, rgb2rgba}, - {IMAGING_MODE_RGBA, IMAGING_MODE_CMYK, rgb2cmyk}, - {IMAGING_MODE_RGBA, IMAGING_MODE_YCbCr, ImagingConvertRGB2YCbCr}, - {IMAGING_MODE_RGBA, IMAGING_MODE_HSV, rgb2hsv}, - - {IMAGING_MODE_RGBa, IMAGING_MODE_RGBA, rgba2rgbA}, - {IMAGING_MODE_RGBa, IMAGING_MODE_RGB, rgba2rgb_}, - - {IMAGING_MODE_RGBX, IMAGING_MODE_1, rgb2bit}, - {IMAGING_MODE_RGBX, IMAGING_MODE_L, rgb2l}, - {IMAGING_MODE_RGBX, IMAGING_MODE_LA, rgb2la}, - {IMAGING_MODE_RGBX, IMAGING_MODE_I, rgb2i}, - {IMAGING_MODE_RGBX, IMAGING_MODE_F, rgb2f}, - {IMAGING_MODE_RGBX, IMAGING_MODE_RGB, rgba2rgb}, - {IMAGING_MODE_RGBX, IMAGING_MODE_CMYK, rgb2cmyk}, - {IMAGING_MODE_RGBX, IMAGING_MODE_YCbCr, ImagingConvertRGB2YCbCr}, - {IMAGING_MODE_RGBX, IMAGING_MODE_HSV, rgb2hsv}, - - {IMAGING_MODE_CMYK, IMAGING_MODE_RGB, cmyk2rgb}, - {IMAGING_MODE_CMYK, IMAGING_MODE_RGBA, cmyk2rgb}, - {IMAGING_MODE_CMYK, IMAGING_MODE_RGBX, cmyk2rgb}, - {IMAGING_MODE_CMYK, IMAGING_MODE_HSV, cmyk2hsv}, - - {IMAGING_MODE_YCbCr, IMAGING_MODE_L, ycbcr2l}, - {IMAGING_MODE_YCbCr, IMAGING_MODE_LA, ycbcr2la}, - {IMAGING_MODE_YCbCr, IMAGING_MODE_RGB, ImagingConvertYCbCr2RGB}, - - {IMAGING_MODE_HSV, IMAGING_MODE_RGB, hsv2rgb}, - - {IMAGING_MODE_I, IMAGING_MODE_I_16, I_I16L}, - {IMAGING_MODE_I_16, IMAGING_MODE_I, I16L_I}, - {IMAGING_MODE_I_16, IMAGING_MODE_RGB, I16_RGB}, - {IMAGING_MODE_L, IMAGING_MODE_I_16, L_I16L}, - {IMAGING_MODE_I_16, IMAGING_MODE_L, I16L_L}, - - {IMAGING_MODE_I, IMAGING_MODE_I_16L, I_I16L}, - {IMAGING_MODE_I_16L, IMAGING_MODE_I, I16L_I}, - {IMAGING_MODE_I, IMAGING_MODE_I_16B, I_I16B}, - {IMAGING_MODE_I_16B, IMAGING_MODE_I, I16B_I}, - - {IMAGING_MODE_L, IMAGING_MODE_I_16L, L_I16L}, - {IMAGING_MODE_I_16L, IMAGING_MODE_L, I16L_L}, - {IMAGING_MODE_L, IMAGING_MODE_I_16B, L_I16B}, - {IMAGING_MODE_I_16B, IMAGING_MODE_L, I16B_L}, -#ifdef WORDS_BIGENDIAN - {IMAGING_MODE_L, IMAGING_MODE_I_16N, L_I16B}, - {IMAGING_MODE_I_16N, IMAGING_MODE_L, I16B_L}, -#else - {IMAGING_MODE_L, IMAGING_MODE_I_16N, L_I16L}, - {IMAGING_MODE_I_16N, IMAGING_MODE_L, I16L_L}, -#endif - - {IMAGING_MODE_I_16, IMAGING_MODE_F, I16L_F}, - {IMAGING_MODE_I_16L, IMAGING_MODE_F, I16L_F}, - {IMAGING_MODE_I_16B, IMAGING_MODE_F, I16B_F}, - - {NULL} - }; - converters = malloc(sizeof(temp)); - if (converters == NULL) { - fprintf(stderr, "ConvertInit: failed to allocate memory for converter table\n"); - exit(1); - } - memcpy(converters, temp, sizeof(temp)); -} - -void -ImagingConvertFree(void) { - free(converters); - converters = NULL; -} diff --git a/src/libImaging/Dib.c b/src/libImaging/Dib.c index 154c610ec..2afe71d4a 100644 --- a/src/libImaging/Dib.c +++ b/src/libImaging/Dib.c @@ -25,13 +25,13 @@ #include "ImDib.h" -const Mode * +ModeID ImagingGetModeDIB(int size_out[2]) { /* Get device characteristics */ const HDC dc = CreateCompatibleDC(NULL); - const Mode *mode = IMAGING_MODE_P; + ModeID mode = IMAGING_MODE_P; if (!(GetDeviceCaps(dc, RASTERCAPS) & RC_PALETTE)) { mode = IMAGING_MODE_RGB; if (GetDeviceCaps(dc, BITSPIXEL) == 1) { @@ -50,7 +50,7 @@ ImagingGetModeDIB(int size_out[2]) { } ImagingDIB -ImagingNewDIB(const Mode * const mode, int xsize, int ysize) { +ImagingNewDIB(const ModeID mode, int xsize, int ysize) { /* Create a Windows bitmap */ ImagingDIB dib; diff --git a/src/libImaging/Fill.c b/src/libImaging/Fill.c index 854cdb9fe..0224d1ba9 100644 --- a/src/libImaging/Fill.c +++ b/src/libImaging/Fill.c @@ -68,7 +68,7 @@ ImagingFill(Imaging im, const void *colour) { } Imaging -ImagingFillLinearGradient(const Mode *mode) { +ImagingFillLinearGradient(const ModeID mode) { Imaging im; int y; @@ -105,7 +105,7 @@ ImagingFillLinearGradient(const Mode *mode) { } Imaging -ImagingFillRadialGradient(const Mode *mode) { +ImagingFillRadialGradient(const ModeID mode) { Imaging im; int x, y; int d; diff --git a/src/libImaging/ImDib.h b/src/libImaging/ImDib.h index 6d8f420cb..65f090f92 100644 --- a/src/libImaging/ImDib.h +++ b/src/libImaging/ImDib.h @@ -27,7 +27,7 @@ struct ImagingDIBInstance { UINT8 *bits; HPALETTE palette; /* Used by cut and paste */ - const Mode *mode; + ModeID mode; int xsize, ysize; int pixelsize; int linesize; @@ -37,11 +37,11 @@ struct ImagingDIBInstance { typedef struct ImagingDIBInstance *ImagingDIB; -extern const Mode * +extern ModeID ImagingGetModeDIB(int size_out[2]); extern ImagingDIB -ImagingNewDIB(const Mode * const mode, int xsize, int ysize); +ImagingNewDIB(ModeID mode, int xsize, int ysize); extern void ImagingDeleteDIB(ImagingDIB im); diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index 9f450dd3a..290a76c8e 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -79,11 +79,11 @@ typedef struct { struct ImagingMemoryInstance { /* Format */ - const Mode *mode; /* Image mode (IMAGING_MODE_*) */ - int type; /* Data type (IMAGING_TYPE_*) */ - int depth; /* Depth (ignored in this version) */ - int bands; /* Number of bands (1, 2, 3, or 4) */ - int xsize; /* Image dimension. */ + ModeID mode; /* Image mode (IMAGING_MODE_*) */ + int type; /* Data type (IMAGING_TYPE_*) */ + int depth; /* Depth (ignored in this version) */ + int bands; /* Number of bands (1, 2, 3, or 4) */ + int xsize; /* Image dimension. */ int ysize; /* Colour palette (for "P" images only) */ @@ -137,15 +137,15 @@ struct ImagingMemoryInstance { #define IMAGING_PIXEL_FLOAT32(im, x, y) (((FLOAT32 *)(im)->image32[y])[x]) struct ImagingAccessInstance { - const Mode *mode; + ModeID mode; void (*get_pixel)(Imaging im, int x, int y, void *pixel); void (*put_pixel)(Imaging im, int x, int y, const void *pixel); }; struct ImagingHistogramInstance { /* Format */ - const Mode *mode; /* Mode of corresponding source image */ - int bands; /* Number of bands (1, 3, or 4) */ + ModeID mode; /* Mode ID of corresponding source image */ + int bands; /* Number of bands (1, 3, or 4) */ /* Data */ long *histogram; /* Histogram (bands*256 longs) */ @@ -153,7 +153,7 @@ struct ImagingHistogramInstance { struct ImagingPaletteInstance { /* Format */ - const Mode *mode; + ModeID mode; /* Data */ int size; @@ -181,29 +181,6 @@ typedef struct ImagingMemoryArena { #endif } *ImagingMemoryArena; -/* Memory Management */ -/* ----------------- */ - -extern void -ImagingAccessInit(void); -extern void -ImagingAccessFree(void); - -extern void -ImagingConvertInit(void); -extern void -ImagingConvertFree(void); - -extern void -ImagingPackInit(void); -extern void -ImagingPackFree(void); - -extern void -ImagingUnpackInit(void); -extern void -ImagingUnpackFree(void); - /* Objects */ /* ------- */ @@ -216,20 +193,20 @@ extern void ImagingMemorySetBlockAllocator(ImagingMemoryArena arena, int use_block_allocator); extern Imaging -ImagingNew(const Mode *mode, int xsize, int ysize); +ImagingNew(ModeID mode, int xsize, int ysize); extern Imaging -ImagingNewDirty(const Mode *mode, int xsize, int ysize); +ImagingNewDirty(ModeID mode, int xsize, int ysize); extern Imaging -ImagingNew2Dirty(const Mode *mode, Imaging imOut, Imaging imIn); +ImagingNew2Dirty(ModeID mode, Imaging imOut, Imaging imIn); extern void ImagingDelete(Imaging im); extern Imaging -ImagingNewBlock(const Mode *mode, int xsize, int ysize); +ImagingNewBlock(ModeID mode, int xsize, int ysize); extern Imaging ImagingNewArrow( - const char *mode, + const ModeID mode, int xsize, int ysize, PyObject *schema_capsule, @@ -237,9 +214,9 @@ ImagingNewArrow( ); extern Imaging -ImagingNewPrologue(const Mode *mode, int xsize, int ysize); +ImagingNewPrologue(ModeID mode, int xsize, int ysize); extern Imaging -ImagingNewPrologueSubtype(const Mode *mode, int xsize, int ysize, int structure_size); +ImagingNewPrologueSubtype(ModeID mode, int xsize, int ysize, int structure_size); extern void ImagingCopyPalette(Imaging destination, Imaging source); @@ -254,7 +231,7 @@ _ImagingAccessDelete(Imaging im, ImagingAccess access); #define ImagingAccessDelete(im, access) /* nop, for now */ extern ImagingPalette -ImagingPaletteNew(const Mode *mode); +ImagingPaletteNew(ModeID mode); extern ImagingPalette ImagingPaletteNewBrowser(void); extern ImagingPalette @@ -326,13 +303,13 @@ ImagingBlend(Imaging imIn1, Imaging imIn2, float alpha); extern Imaging ImagingCopy(Imaging im); extern Imaging -ImagingConvert(Imaging im, const Mode *mode, ImagingPalette palette, int dither); +ImagingConvert(Imaging im, ModeID mode, ImagingPalette palette, int dither); extern Imaging -ImagingConvertInPlace(Imaging im, const Mode *mode); +ImagingConvertInPlace(Imaging im, ModeID mode); extern Imaging -ImagingConvertMatrix(Imaging im, const Mode *mode, float m[]); +ImagingConvertMatrix(Imaging im, ModeID mode, float m[]); extern Imaging -ImagingConvertTransparent(Imaging im, const Mode *mode, int r, int g, int b); +ImagingConvertTransparent(Imaging im, ModeID mode, int r, int g, int b); extern Imaging ImagingCrop(Imaging im, int x0, int y0, int x1, int y1); extern Imaging @@ -346,9 +323,9 @@ ImagingFill2( extern Imaging ImagingFillBand(Imaging im, int band, int color); extern Imaging -ImagingFillLinearGradient(const Mode *mode); +ImagingFillLinearGradient(ModeID mode); extern Imaging -ImagingFillRadialGradient(const Mode *mode); +ImagingFillRadialGradient(ModeID mode); extern Imaging ImagingFilter(Imaging im, int xsize, int ysize, const FLOAT32 *kernel, FLOAT32 offset); extern Imaging @@ -362,7 +339,7 @@ ImagingGaussianBlur( extern Imaging ImagingGetBand(Imaging im, int band); extern Imaging -ImagingMerge(const Mode *mode, Imaging bands[4]); +ImagingMerge(ModeID mode, Imaging bands[4]); extern int ImagingSplit(Imaging im, Imaging bands[4]); extern int @@ -389,7 +366,7 @@ ImagingOffset(Imaging im, int xoffset, int yoffset); extern int ImagingPaste(Imaging into, Imaging im, Imaging mask, int x0, int y0, int x1, int y1); extern Imaging -ImagingPoint(Imaging im, const Mode *tablemode, const void *table); +ImagingPoint(Imaging im, ModeID tablemode, const void *table); extern Imaging ImagingPointTransform(Imaging imIn, double scale, double offset); extern Imaging @@ -730,9 +707,9 @@ extern void ImagingConvertYCbCr2RGB(UINT8 *out, const UINT8 *in, int pixels); extern ImagingShuffler -ImagingFindUnpacker(const Mode *mode, const RawMode *rawmode, int *bits_out); +ImagingFindUnpacker(ModeID mode, RawModeID rawmode, int *bits_out); extern ImagingShuffler -ImagingFindPacker(const Mode *mode, const RawMode *rawmode, int *bits_out); +ImagingFindPacker(ModeID mode, RawModeID rawmode, int *bits_out); struct ImagingCodecStateInstance { int count; diff --git a/src/libImaging/Jpeg.h b/src/libImaging/Jpeg.h index 48c6c6184..e07904fc7 100644 --- a/src/libImaging/Jpeg.h +++ b/src/libImaging/Jpeg.h @@ -28,12 +28,12 @@ typedef struct { typedef struct { /* CONFIGURATION */ - /* Jpeg file mode (NULL if not known) */ - const RawMode *jpegmode; + /* Jpeg file mode */ + RawModeID jpegmode; /* Converter output mode (input to the shuffler) */ - /* If NULL, convert conversions are disabled */ - const RawMode *rawmode; + /* If not a valid mode, convert conversions are disabled */ + RawModeID rawmode; /* If set, trade quality for speed */ int draft; @@ -91,7 +91,7 @@ typedef struct { unsigned int restart_marker_rows; /* Converter input mode (input to the shuffler) */ - const RawMode *rawmode; + RawModeID rawmode; /* Custom quantization tables () */ unsigned int *qtables; diff --git a/src/libImaging/Jpeg2KDecode.c b/src/libImaging/Jpeg2KDecode.c index 3cbe2965d..67f705ddd 100644 --- a/src/libImaging/Jpeg2KDecode.c +++ b/src/libImaging/Jpeg2KDecode.c @@ -771,7 +771,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) { if (color_space == j2k_unpackers[n].color_space && image->numcomps == j2k_unpackers[n].components && (j2k_unpackers[n].subsampling || (subsampling == -1)) && - strcmp(im->mode->name, j2k_unpackers[n].mode) == 0) { + strcmp(getModeData(im->mode)->name, j2k_unpackers[n].mode) == 0) { unpack = j2k_unpackers[n].unpacker; break; } diff --git a/src/libImaging/JpegDecode.c b/src/libImaging/JpegDecode.c index 49d4fcb2f..ae3274456 100644 --- a/src/libImaging/JpegDecode.c +++ b/src/libImaging/JpegDecode.c @@ -180,8 +180,8 @@ ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t by /* Decoder settings */ - /* jpegmode indicates what's in the file; if not set, we'll - trust the decoder */ + /* jpegmode indicates what's in the file. */ + /* If not valid, we'll trust the decoder. */ if (context->jpegmode == IMAGING_RAWMODE_L) { context->cinfo.jpeg_color_space = JCS_GRAYSCALE; } else if (context->jpegmode == IMAGING_RAWMODE_RGB) { @@ -194,8 +194,8 @@ ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t by context->cinfo.jpeg_color_space = JCS_YCCK; } - /* rawmode indicates what we want from the decoder. if not - set, conversions are disabled */ + /* rawmode indicates what we want from the decoder. */ + /* If not valid, conversions are disabled. */ if (context->rawmode == IMAGING_RAWMODE_L) { context->cinfo.out_color_space = JCS_GRAYSCALE; } else if (context->rawmode == IMAGING_RAWMODE_RGB) { @@ -214,7 +214,7 @@ ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t by } else if (context->rawmode == IMAGING_RAWMODE_YCbCrK) { context->cinfo.out_color_space = JCS_YCCK; } else { - /* Disable decoder conversions */ + /* Disable decoder conversions. */ context->cinfo.jpeg_color_space = JCS_UNKNOWN; context->cinfo.out_color_space = JCS_UNKNOWN; } diff --git a/src/libImaging/Matrix.c b/src/libImaging/Matrix.c index f848b870d..6bc9fbc1d 100644 --- a/src/libImaging/Matrix.c +++ b/src/libImaging/Matrix.c @@ -18,7 +18,7 @@ #define CLIPF(v) ((v <= 0.0) ? 0 : (v >= 255.0F) ? 255 : (UINT8)v) Imaging -ImagingConvertMatrix(Imaging im, const Mode *mode, float m[]) { +ImagingConvertMatrix(Imaging im, const ModeID mode, float m[]) { Imaging imOut; int x, y; ImagingSectionCookie cookie; diff --git a/src/libImaging/Mode.c b/src/libImaging/Mode.c index 01b2b5501..659e7aada 100644 --- a/src/libImaging/Mode.c +++ b/src/libImaging/Mode.c @@ -2,453 +2,258 @@ #include -#define CREATE_MODE(TYPE, NAME, INIT) \ -const TYPE IMAGING_##NAME##_VAL = INIT;\ -const TYPE * const IMAGING_##NAME = &IMAGING_##NAME##_VAL; +const ModeData MODES[] = { + [IMAGING_MODE_UNKNOWN] = {""}, + [IMAGING_MODE_1] = {"1"}, + [IMAGING_MODE_CMYK] = {"CMYK"}, + [IMAGING_MODE_F] = {"F"}, + [IMAGING_MODE_HSV] = {"HSV"}, + [IMAGING_MODE_I] = {"I"}, + [IMAGING_MODE_L] = {"L"}, + [IMAGING_MODE_LA] = {"LA"}, + [IMAGING_MODE_LAB] = {"LAB"}, + [IMAGING_MODE_La] = {"La"}, + [IMAGING_MODE_P] = {"P"}, + [IMAGING_MODE_PA] = {"PA"}, + [IMAGING_MODE_RGB] = {"RGB"}, + [IMAGING_MODE_RGBA] = {"RGBA"}, + [IMAGING_MODE_RGBX] = {"RGBX"}, + [IMAGING_MODE_RGBa] = {"RGBa"}, + [IMAGING_MODE_YCbCr] = {"YCbCr"}, -CREATE_MODE(Mode, MODE_1, {"1"}) -CREATE_MODE(Mode, MODE_CMYK, {"CMYK"}) -CREATE_MODE(Mode, MODE_F, {"F"}) -CREATE_MODE(Mode, MODE_HSV, {"HSV"}) -CREATE_MODE(Mode, MODE_I, {"I"}) -CREATE_MODE(Mode, MODE_L, {"L"}) -CREATE_MODE(Mode, MODE_LA, {"LA"}) -CREATE_MODE(Mode, MODE_LAB, {"LAB"}) -CREATE_MODE(Mode, MODE_La, {"La"}) -CREATE_MODE(Mode, MODE_P, {"P"}) -CREATE_MODE(Mode, MODE_PA, {"PA"}) -CREATE_MODE(Mode, MODE_RGB, {"RGB"}) -CREATE_MODE(Mode, MODE_RGBA, {"RGBA"}) -CREATE_MODE(Mode, MODE_RGBX, {"RGBX"}) -CREATE_MODE(Mode, MODE_RGBa, {"RGBa"}) -CREATE_MODE(Mode, MODE_YCbCr, {"YCbCr"}) + [IMAGING_MODE_BGR_15] = {"BGR;15"}, + [IMAGING_MODE_BGR_16] = {"BGR;16"}, + [IMAGING_MODE_BGR_24] = {"BGR;24"}, -CREATE_MODE(Mode, MODE_BGR_15, {"BGR;15"}) -CREATE_MODE(Mode, MODE_BGR_16, {"BGR;16"}) -CREATE_MODE(Mode, MODE_BGR_24, {"BGR;24"}) - -CREATE_MODE(Mode, MODE_I_16, {"I;16"}) -CREATE_MODE(Mode, MODE_I_16L, {"I;16L"}) -CREATE_MODE(Mode, MODE_I_16B, {"I;16B"}) -CREATE_MODE(Mode, MODE_I_16N, {"I;16N"}) -CREATE_MODE(Mode, MODE_I_32L, {"I;32L"}) -CREATE_MODE(Mode, MODE_I_32B, {"I;32B"}) - -const Mode * const MODES[] = { - IMAGING_MODE_1, - IMAGING_MODE_CMYK, - IMAGING_MODE_F, - IMAGING_MODE_HSV, - IMAGING_MODE_I, - IMAGING_MODE_L, - IMAGING_MODE_LA, - IMAGING_MODE_LAB, - IMAGING_MODE_La, - IMAGING_MODE_P, - IMAGING_MODE_PA, - IMAGING_MODE_RGB, - IMAGING_MODE_RGBA, - IMAGING_MODE_RGBX, - IMAGING_MODE_RGBa, - IMAGING_MODE_YCbCr, - - IMAGING_MODE_BGR_15, - IMAGING_MODE_BGR_16, - IMAGING_MODE_BGR_24, - - IMAGING_MODE_I_16, - IMAGING_MODE_I_16L, - IMAGING_MODE_I_16B, - IMAGING_MODE_I_16N, - IMAGING_MODE_I_32L, - IMAGING_MODE_I_32B, - - NULL + [IMAGING_MODE_I_16] = {"I;16"}, + [IMAGING_MODE_I_16L] = {"I;16L"}, + [IMAGING_MODE_I_16B] = {"I;16B"}, + [IMAGING_MODE_I_16N] = {"I;16N"}, + [IMAGING_MODE_I_32L] = {"I;32L"}, + [IMAGING_MODE_I_32B] = {"I;32B"}, }; -const Mode * findMode(const char * const name) { +const ModeID findModeID(const char * const name) { if (name == NULL) { - return NULL; + return IMAGING_MODE_UNKNOWN; } - const Mode * mode; - for (int i = 0; (mode = MODES[i]); i++) { - if (strcmp(mode->name, name) == 0) { - return mode; + for (size_t i = 0; i < sizeof(MODES) / sizeof(*MODES); i++) { + if (strcmp(MODES[i].name, name) == 0) { + return (ModeID)i; } } - return NULL; + return IMAGING_MODE_UNKNOWN; +} + +const ModeData * const getModeData(const ModeID id) { + if (id < 0 || id > sizeof(MODES) / sizeof(*MODES)) { + return &MODES[IMAGING_MODE_UNKNOWN]; + } + return &MODES[id]; } -// Alias all of the modes as rawmodes so that the addresses are the same. -#define ALIAS_MODE_AS_RAWMODE(NAME) const RawMode * const IMAGING_RAWMODE_##NAME = (const RawMode * const)IMAGING_MODE_##NAME; -ALIAS_MODE_AS_RAWMODE(1) -ALIAS_MODE_AS_RAWMODE(CMYK) -ALIAS_MODE_AS_RAWMODE(F) -ALIAS_MODE_AS_RAWMODE(HSV) -ALIAS_MODE_AS_RAWMODE(I) -ALIAS_MODE_AS_RAWMODE(L) -ALIAS_MODE_AS_RAWMODE(LA) -ALIAS_MODE_AS_RAWMODE(LAB) -ALIAS_MODE_AS_RAWMODE(La) -ALIAS_MODE_AS_RAWMODE(P) -ALIAS_MODE_AS_RAWMODE(PA) -ALIAS_MODE_AS_RAWMODE(RGB) -ALIAS_MODE_AS_RAWMODE(RGBA) -ALIAS_MODE_AS_RAWMODE(RGBX) -ALIAS_MODE_AS_RAWMODE(RGBa) -ALIAS_MODE_AS_RAWMODE(YCbCr) +const RawModeData RAWMODES[] = { + [IMAGING_RAWMODE_UNKNOWN] = {""}, -ALIAS_MODE_AS_RAWMODE(BGR_15) -ALIAS_MODE_AS_RAWMODE(BGR_16) -ALIAS_MODE_AS_RAWMODE(BGR_24) + [IMAGING_RAWMODE_1] = {"1"}, + [IMAGING_RAWMODE_CMYK] = {"CMYK"}, + [IMAGING_RAWMODE_F] = {"F"}, + [IMAGING_RAWMODE_HSV] = {"HSV"}, + [IMAGING_RAWMODE_I] = {"I"}, + [IMAGING_RAWMODE_L] = {"L"}, + [IMAGING_RAWMODE_LA] = {"LA"}, + [IMAGING_RAWMODE_LAB] = {"LAB"}, + [IMAGING_RAWMODE_La] = {"La"}, + [IMAGING_RAWMODE_P] = {"P"}, + [IMAGING_RAWMODE_PA] = {"PA"}, + [IMAGING_RAWMODE_RGB] = {"RGB"}, + [IMAGING_RAWMODE_RGBA] = {"RGBA"}, + [IMAGING_RAWMODE_RGBX] = {"RGBX"}, + [IMAGING_RAWMODE_RGBa] = {"RGBa"}, + [IMAGING_RAWMODE_YCbCr] = {"YCbCr"}, -ALIAS_MODE_AS_RAWMODE(I_16) -ALIAS_MODE_AS_RAWMODE(I_16L) -ALIAS_MODE_AS_RAWMODE(I_16B) -ALIAS_MODE_AS_RAWMODE(I_16N) -ALIAS_MODE_AS_RAWMODE(I_32L) -ALIAS_MODE_AS_RAWMODE(I_32B) + [IMAGING_RAWMODE_BGR_15] = {"BGR;15"}, + [IMAGING_RAWMODE_BGR_16] = {"BGR;16"}, + [IMAGING_RAWMODE_BGR_24] = {"BGR;24"}, + [IMAGING_RAWMODE_BGR_32] = {"BGR;32"}, -CREATE_MODE(RawMode, RAWMODE_1_8, {"1;8"}) -CREATE_MODE(RawMode, RAWMODE_1_I, {"1;I"}) -CREATE_MODE(RawMode, RAWMODE_1_IR, {"1;IR"}) -CREATE_MODE(RawMode, RAWMODE_1_R, {"1;R"}) -CREATE_MODE(RawMode, RAWMODE_A, {"A"}) -CREATE_MODE(RawMode, RAWMODE_ABGR, {"ABGR"}) -CREATE_MODE(RawMode, RAWMODE_ARGB, {"ARGB"}) -CREATE_MODE(RawMode, RAWMODE_A_16B, {"A;16B"}) -CREATE_MODE(RawMode, RAWMODE_A_16L, {"A;16L"}) -CREATE_MODE(RawMode, RAWMODE_A_16N, {"A;16N"}) -CREATE_MODE(RawMode, RAWMODE_B, {"B"}) -CREATE_MODE(RawMode, RAWMODE_BGAR, {"BGAR"}) -CREATE_MODE(RawMode, RAWMODE_BGR, {"BGR"}) -CREATE_MODE(RawMode, RAWMODE_BGRA, {"BGRA"}) -CREATE_MODE(RawMode, RAWMODE_BGRA_15, {"BGRA;15"}) -CREATE_MODE(RawMode, RAWMODE_BGRA_15Z, {"BGRA;15Z"}) -CREATE_MODE(RawMode, RAWMODE_BGRA_16B, {"BGRA;16B"}) -CREATE_MODE(RawMode, RAWMODE_BGRA_16L, {"BGRA;16L"}) -CREATE_MODE(RawMode, RAWMODE_BGRX, {"BGRX"}) -CREATE_MODE(RawMode, RAWMODE_BGR_5, {"BGR;5"}) -CREATE_MODE(RawMode, RAWMODE_BGRa, {"BGRa"}) -CREATE_MODE(RawMode, RAWMODE_BGXR, {"BGXR"}) -CREATE_MODE(RawMode, RAWMODE_B_16B, {"B;16B"}) -CREATE_MODE(RawMode, RAWMODE_B_16L, {"B;16L"}) -CREATE_MODE(RawMode, RAWMODE_B_16N, {"B;16N"}) -CREATE_MODE(RawMode, RAWMODE_C, {"C"}) -CREATE_MODE(RawMode, RAWMODE_CMYKX, {"CMYKX"}) -CREATE_MODE(RawMode, RAWMODE_CMYKXX, {"CMYKXX"}) -CREATE_MODE(RawMode, RAWMODE_CMYK_16B, {"CMYK;16B"}) -CREATE_MODE(RawMode, RAWMODE_CMYK_16L, {"CMYK;16L"}) -CREATE_MODE(RawMode, RAWMODE_CMYK_16N, {"CMYK;16N"}) -CREATE_MODE(RawMode, RAWMODE_CMYK_I, {"CMYK;I"}) -CREATE_MODE(RawMode, RAWMODE_CMYK_L, {"CMYK;L"}) -CREATE_MODE(RawMode, RAWMODE_C_I, {"C;I"}) -CREATE_MODE(RawMode, RAWMODE_Cb, {"Cb"}) -CREATE_MODE(RawMode, RAWMODE_Cr, {"Cr"}) -CREATE_MODE(RawMode, RAWMODE_F_16, {"F;16"}) -CREATE_MODE(RawMode, RAWMODE_F_16B, {"F;16B"}) -CREATE_MODE(RawMode, RAWMODE_F_16BS, {"F;16BS"}) -CREATE_MODE(RawMode, RAWMODE_F_16N, {"F;16N"}) -CREATE_MODE(RawMode, RAWMODE_F_16NS, {"F;16NS"}) -CREATE_MODE(RawMode, RAWMODE_F_16S, {"F;16S"}) -CREATE_MODE(RawMode, RAWMODE_F_32, {"F;32"}) -CREATE_MODE(RawMode, RAWMODE_F_32B, {"F;32B"}) -CREATE_MODE(RawMode, RAWMODE_F_32BF, {"F;32BF"}) -CREATE_MODE(RawMode, RAWMODE_F_32BS, {"F;32BS"}) -CREATE_MODE(RawMode, RAWMODE_F_32F, {"F;32F"}) -CREATE_MODE(RawMode, RAWMODE_F_32N, {"F;32N"}) -CREATE_MODE(RawMode, RAWMODE_F_32NF, {"F;32NF"}) -CREATE_MODE(RawMode, RAWMODE_F_32NS, {"F;32NS"}) -CREATE_MODE(RawMode, RAWMODE_F_32S, {"F;32S"}) -CREATE_MODE(RawMode, RAWMODE_F_64BF, {"F;64BF"}) -CREATE_MODE(RawMode, RAWMODE_F_64F, {"F;64F"}) -CREATE_MODE(RawMode, RAWMODE_F_64NF, {"F;64NF"}) -CREATE_MODE(RawMode, RAWMODE_F_8, {"F;8"}) -CREATE_MODE(RawMode, RAWMODE_F_8S, {"F;8S"}) -CREATE_MODE(RawMode, RAWMODE_G, {"G"}) -CREATE_MODE(RawMode, RAWMODE_G_16B, {"G;16B"}) -CREATE_MODE(RawMode, RAWMODE_G_16L, {"G;16L"}) -CREATE_MODE(RawMode, RAWMODE_G_16N, {"G;16N"}) -CREATE_MODE(RawMode, RAWMODE_H, {"H"}) -CREATE_MODE(RawMode, RAWMODE_I_12, {"I;12"}) -CREATE_MODE(RawMode, RAWMODE_I_16BS, {"I;16BS"}) -CREATE_MODE(RawMode, RAWMODE_I_16NS, {"I;16NS"}) -CREATE_MODE(RawMode, RAWMODE_I_16R, {"I;16R"}) -CREATE_MODE(RawMode, RAWMODE_I_16S, {"I;16S"}) -CREATE_MODE(RawMode, RAWMODE_I_32, {"I;32"}) -CREATE_MODE(RawMode, RAWMODE_I_32BS, {"I;32BS"}) -CREATE_MODE(RawMode, RAWMODE_I_32N, {"I;32N"}) -CREATE_MODE(RawMode, RAWMODE_I_32NS, {"I;32NS"}) -CREATE_MODE(RawMode, RAWMODE_I_32S, {"I;32S"}) -CREATE_MODE(RawMode, RAWMODE_I_8, {"I;8"}) -CREATE_MODE(RawMode, RAWMODE_I_8S, {"I;8S"}) -CREATE_MODE(RawMode, RAWMODE_K, {"K"}) -CREATE_MODE(RawMode, RAWMODE_K_I, {"K;I"}) -CREATE_MODE(RawMode, RAWMODE_LA_16B, {"LA;16B"}) -CREATE_MODE(RawMode, RAWMODE_LA_L, {"LA;L"}) -CREATE_MODE(RawMode, RAWMODE_L_16, {"L;16"}) -CREATE_MODE(RawMode, RAWMODE_L_16B, {"L;16B"}) -CREATE_MODE(RawMode, RAWMODE_L_2, {"L;2"}) -CREATE_MODE(RawMode, RAWMODE_L_2I, {"L;2I"}) -CREATE_MODE(RawMode, RAWMODE_L_2IR, {"L;2IR"}) -CREATE_MODE(RawMode, RAWMODE_L_2R, {"L;2R"}) -CREATE_MODE(RawMode, RAWMODE_L_4, {"L;4"}) -CREATE_MODE(RawMode, RAWMODE_L_4I, {"L;4I"}) -CREATE_MODE(RawMode, RAWMODE_L_4IR, {"L;4IR"}) -CREATE_MODE(RawMode, RAWMODE_L_4R, {"L;4R"}) -CREATE_MODE(RawMode, RAWMODE_L_I, {"L;I"}) -CREATE_MODE(RawMode, RAWMODE_L_R, {"L;R"}) -CREATE_MODE(RawMode, RAWMODE_M, {"M"}) -CREATE_MODE(RawMode, RAWMODE_M_I, {"M;I"}) -CREATE_MODE(RawMode, RAWMODE_PA_L, {"PA;L"}) -CREATE_MODE(RawMode, RAWMODE_PX, {"PX"}) -CREATE_MODE(RawMode, RAWMODE_P_1, {"P;1"}) -CREATE_MODE(RawMode, RAWMODE_P_2, {"P;2"}) -CREATE_MODE(RawMode, RAWMODE_P_2L, {"P;2L"}) -CREATE_MODE(RawMode, RAWMODE_P_4, {"P;4"}) -CREATE_MODE(RawMode, RAWMODE_P_4L, {"P;4L"}) -CREATE_MODE(RawMode, RAWMODE_P_R, {"P;R"}) -CREATE_MODE(RawMode, RAWMODE_R, {"R"}) -CREATE_MODE(RawMode, RAWMODE_RGBAX, {"RGBAX"}) -CREATE_MODE(RawMode, RAWMODE_RGBAXX, {"RGBAXX"}) -CREATE_MODE(RawMode, RAWMODE_RGBA_15, {"RGBA;15"}) -CREATE_MODE(RawMode, RAWMODE_RGBA_16B, {"RGBA;16B"}) -CREATE_MODE(RawMode, RAWMODE_RGBA_16L, {"RGBA;16L"}) -CREATE_MODE(RawMode, RAWMODE_RGBA_16N, {"RGBA;16N"}) -CREATE_MODE(RawMode, RAWMODE_RGBA_4B, {"RGBA;4B"}) -CREATE_MODE(RawMode, RAWMODE_RGBA_I, {"RGBA;I"}) -CREATE_MODE(RawMode, RAWMODE_RGBA_L, {"RGBA;L"}) -CREATE_MODE(RawMode, RAWMODE_RGBXX, {"RGBXX"}) -CREATE_MODE(RawMode, RAWMODE_RGBXXX, {"RGBXXX"}) -CREATE_MODE(RawMode, RAWMODE_RGBX_16B, {"RGBX;16B"}) -CREATE_MODE(RawMode, RAWMODE_RGBX_16L, {"RGBX;16L"}) -CREATE_MODE(RawMode, RAWMODE_RGBX_16N, {"RGBX;16N"}) -CREATE_MODE(RawMode, RAWMODE_RGBX_L, {"RGBX;L"}) -CREATE_MODE(RawMode, RAWMODE_RGB_15, {"RGB;15"}) -CREATE_MODE(RawMode, RAWMODE_RGB_16, {"RGB;16"}) -CREATE_MODE(RawMode, RAWMODE_RGB_16B, {"RGB;16B"}) -CREATE_MODE(RawMode, RAWMODE_RGB_16L, {"RGB;16L"}) -CREATE_MODE(RawMode, RAWMODE_RGB_16N, {"RGB;16N"}) -CREATE_MODE(RawMode, RAWMODE_RGB_4B, {"RGB;4B"}) -CREATE_MODE(RawMode, RAWMODE_RGB_L, {"RGB;L"}) -CREATE_MODE(RawMode, RAWMODE_RGB_R, {"RGB;R"}) -CREATE_MODE(RawMode, RAWMODE_RGBaX, {"RGBaX"}) -CREATE_MODE(RawMode, RAWMODE_RGBaXX, {"RGBaXX"}) -CREATE_MODE(RawMode, RAWMODE_RGBa_16B, {"RGBa;16B"}) -CREATE_MODE(RawMode, RAWMODE_RGBa_16L, {"RGBa;16L"}) -CREATE_MODE(RawMode, RAWMODE_RGBa_16N, {"RGBa;16N"}) -CREATE_MODE(RawMode, RAWMODE_R_16B, {"R;16B"}) -CREATE_MODE(RawMode, RAWMODE_R_16L, {"R;16L"}) -CREATE_MODE(RawMode, RAWMODE_R_16N, {"R;16N"}) -CREATE_MODE(RawMode, RAWMODE_S, {"S"}) -CREATE_MODE(RawMode, RAWMODE_V, {"V"}) -CREATE_MODE(RawMode, RAWMODE_X, {"X"}) -CREATE_MODE(RawMode, RAWMODE_XBGR, {"XBGR"}) -CREATE_MODE(RawMode, RAWMODE_XRGB, {"XRGB"}) -CREATE_MODE(RawMode, RAWMODE_Y, {"Y"}) -CREATE_MODE(RawMode, RAWMODE_YCCA_P, {"YCCA;P"}) -CREATE_MODE(RawMode, RAWMODE_YCC_P, {"YCC;P"}) -CREATE_MODE(RawMode, RAWMODE_YCbCrK, {"YCbCrK"}) -CREATE_MODE(RawMode, RAWMODE_YCbCrX, {"YCbCrX"}) -CREATE_MODE(RawMode, RAWMODE_YCbCr_L, {"YCbCr;L"}) -CREATE_MODE(RawMode, RAWMODE_Y_I, {"Y;I"}) -CREATE_MODE(RawMode, RAWMODE_aBGR, {"aBGR"}) -CREATE_MODE(RawMode, RAWMODE_aRGB, {"aRGB"}) + [IMAGING_RAWMODE_I_16] = {"I;16"}, + [IMAGING_RAWMODE_I_16L] = {"I;16L"}, + [IMAGING_RAWMODE_I_16B] = {"I;16B"}, + [IMAGING_RAWMODE_I_16N] = {"I;16N"}, + [IMAGING_RAWMODE_I_32L] = {"I;32L"}, + [IMAGING_RAWMODE_I_32B] = {"I;32B"}, -const RawMode * const RAWMODES[] = { - IMAGING_RAWMODE_1, - IMAGING_RAWMODE_CMYK, - IMAGING_RAWMODE_F, - IMAGING_RAWMODE_HSV, - IMAGING_RAWMODE_I, - IMAGING_RAWMODE_L, - IMAGING_RAWMODE_LA, - IMAGING_RAWMODE_LAB, - IMAGING_RAWMODE_La, - IMAGING_RAWMODE_P, - IMAGING_RAWMODE_PA, - IMAGING_RAWMODE_RGB, - IMAGING_RAWMODE_RGBA, - IMAGING_RAWMODE_RGBX, - IMAGING_RAWMODE_RGBa, - IMAGING_RAWMODE_YCbCr, - - IMAGING_RAWMODE_BGR_15, - IMAGING_RAWMODE_BGR_16, - IMAGING_RAWMODE_BGR_24, - - IMAGING_RAWMODE_I_16, - IMAGING_RAWMODE_I_16L, - IMAGING_RAWMODE_I_16B, - IMAGING_RAWMODE_I_16N, - IMAGING_RAWMODE_I_32L, - IMAGING_RAWMODE_I_32B, - - IMAGING_RAWMODE_1_8, - IMAGING_RAWMODE_1_I, - IMAGING_RAWMODE_1_IR, - IMAGING_RAWMODE_1_R, - IMAGING_RAWMODE_A, - IMAGING_RAWMODE_ABGR, - IMAGING_RAWMODE_ARGB, - IMAGING_RAWMODE_A_16B, - IMAGING_RAWMODE_A_16L, - IMAGING_RAWMODE_A_16N, - IMAGING_RAWMODE_B, - IMAGING_RAWMODE_BGAR, - IMAGING_RAWMODE_BGR, - IMAGING_RAWMODE_BGRA, - IMAGING_RAWMODE_BGRA_15, - IMAGING_RAWMODE_BGRA_15Z, - IMAGING_RAWMODE_BGRA_16B, - IMAGING_RAWMODE_BGRA_16L, - IMAGING_RAWMODE_BGRX, - IMAGING_RAWMODE_BGR_5, - IMAGING_RAWMODE_BGRa, - IMAGING_RAWMODE_BGXR, - IMAGING_RAWMODE_B_16B, - IMAGING_RAWMODE_B_16L, - IMAGING_RAWMODE_B_16N, - IMAGING_RAWMODE_C, - IMAGING_RAWMODE_CMYKX, - IMAGING_RAWMODE_CMYKXX, - IMAGING_RAWMODE_CMYK_16B, - IMAGING_RAWMODE_CMYK_16L, - IMAGING_RAWMODE_CMYK_16N, - IMAGING_RAWMODE_CMYK_I, - IMAGING_RAWMODE_CMYK_L, - IMAGING_RAWMODE_C_I, - IMAGING_RAWMODE_Cb, - IMAGING_RAWMODE_Cr, - IMAGING_RAWMODE_F_16, - IMAGING_RAWMODE_F_16B, - IMAGING_RAWMODE_F_16BS, - IMAGING_RAWMODE_F_16N, - IMAGING_RAWMODE_F_16NS, - IMAGING_RAWMODE_F_16S, - IMAGING_RAWMODE_F_32, - IMAGING_RAWMODE_F_32B, - IMAGING_RAWMODE_F_32BF, - IMAGING_RAWMODE_F_32BS, - IMAGING_RAWMODE_F_32F, - IMAGING_RAWMODE_F_32N, - IMAGING_RAWMODE_F_32NF, - IMAGING_RAWMODE_F_32NS, - IMAGING_RAWMODE_F_32S, - IMAGING_RAWMODE_F_64BF, - IMAGING_RAWMODE_F_64F, - IMAGING_RAWMODE_F_64NF, - IMAGING_RAWMODE_F_8, - IMAGING_RAWMODE_F_8S, - IMAGING_RAWMODE_G, - IMAGING_RAWMODE_G_16B, - IMAGING_RAWMODE_G_16L, - IMAGING_RAWMODE_G_16N, - IMAGING_RAWMODE_H, - IMAGING_RAWMODE_I_12, - IMAGING_RAWMODE_I_16BS, - IMAGING_RAWMODE_I_16NS, - IMAGING_RAWMODE_I_16R, - IMAGING_RAWMODE_I_16S, - IMAGING_RAWMODE_I_32, - IMAGING_RAWMODE_I_32BS, - IMAGING_RAWMODE_I_32N, - IMAGING_RAWMODE_I_32NS, - IMAGING_RAWMODE_I_32S, - IMAGING_RAWMODE_I_8, - IMAGING_RAWMODE_I_8S, - IMAGING_RAWMODE_K, - IMAGING_RAWMODE_K_I, - IMAGING_RAWMODE_LA_16B, - IMAGING_RAWMODE_LA_L, - IMAGING_RAWMODE_L_16, - IMAGING_RAWMODE_L_16B, - IMAGING_RAWMODE_L_2, - IMAGING_RAWMODE_L_2I, - IMAGING_RAWMODE_L_2IR, - IMAGING_RAWMODE_L_2R, - IMAGING_RAWMODE_L_4, - IMAGING_RAWMODE_L_4I, - IMAGING_RAWMODE_L_4IR, - IMAGING_RAWMODE_L_4R, - IMAGING_RAWMODE_L_I, - IMAGING_RAWMODE_L_R, - IMAGING_RAWMODE_M, - IMAGING_RAWMODE_M_I, - IMAGING_RAWMODE_PA_L, - IMAGING_RAWMODE_PX, - IMAGING_RAWMODE_P_1, - IMAGING_RAWMODE_P_2, - IMAGING_RAWMODE_P_2L, - IMAGING_RAWMODE_P_4, - IMAGING_RAWMODE_P_4L, - IMAGING_RAWMODE_P_R, - IMAGING_RAWMODE_R, - IMAGING_RAWMODE_RGBAX, - IMAGING_RAWMODE_RGBAXX, - IMAGING_RAWMODE_RGBA_15, - IMAGING_RAWMODE_RGBA_16B, - IMAGING_RAWMODE_RGBA_16L, - IMAGING_RAWMODE_RGBA_16N, - IMAGING_RAWMODE_RGBA_4B, - IMAGING_RAWMODE_RGBA_I, - IMAGING_RAWMODE_RGBA_L, - IMAGING_RAWMODE_RGBXX, - IMAGING_RAWMODE_RGBXXX, - IMAGING_RAWMODE_RGBX_16B, - IMAGING_RAWMODE_RGBX_16L, - IMAGING_RAWMODE_RGBX_16N, - IMAGING_RAWMODE_RGBX_L, - IMAGING_RAWMODE_RGB_15, - IMAGING_RAWMODE_RGB_16, - IMAGING_RAWMODE_RGB_16B, - IMAGING_RAWMODE_RGB_16L, - IMAGING_RAWMODE_RGB_16N, - IMAGING_RAWMODE_RGB_4B, - IMAGING_RAWMODE_RGB_L, - IMAGING_RAWMODE_RGB_R, - IMAGING_RAWMODE_RGBaX, - IMAGING_RAWMODE_RGBaXX, - IMAGING_RAWMODE_RGBa_16B, - IMAGING_RAWMODE_RGBa_16L, - IMAGING_RAWMODE_RGBa_16N, - IMAGING_RAWMODE_R_16B, - IMAGING_RAWMODE_R_16L, - IMAGING_RAWMODE_R_16N, - IMAGING_RAWMODE_S, - IMAGING_RAWMODE_V, - IMAGING_RAWMODE_X, - IMAGING_RAWMODE_XBGR, - IMAGING_RAWMODE_XRGB, - IMAGING_RAWMODE_Y, - IMAGING_RAWMODE_YCCA_P, - IMAGING_RAWMODE_YCC_P, - IMAGING_RAWMODE_YCbCrK, - IMAGING_RAWMODE_YCbCrX, - IMAGING_RAWMODE_YCbCr_L, - IMAGING_RAWMODE_Y_I, - IMAGING_RAWMODE_aBGR, - IMAGING_RAWMODE_aRGB, - - NULL + [IMAGING_RAWMODE_1_8] = {"1;8"}, + [IMAGING_RAWMODE_1_I] = {"1;I"}, + [IMAGING_RAWMODE_1_IR] = {"1;IR"}, + [IMAGING_RAWMODE_1_R] = {"1;R"}, + [IMAGING_RAWMODE_A] = {"A"}, + [IMAGING_RAWMODE_ABGR] = {"ABGR"}, + [IMAGING_RAWMODE_ARGB] = {"ARGB"}, + [IMAGING_RAWMODE_A_16B] = {"A;16B"}, + [IMAGING_RAWMODE_A_16L] = {"A;16L"}, + [IMAGING_RAWMODE_A_16N] = {"A;16N"}, + [IMAGING_RAWMODE_B] = {"B"}, + [IMAGING_RAWMODE_BGAR] = {"BGAR"}, + [IMAGING_RAWMODE_BGR] = {"BGR"}, + [IMAGING_RAWMODE_BGRA] = {"BGRA"}, + [IMAGING_RAWMODE_BGRA_15] = {"BGRA;15"}, + [IMAGING_RAWMODE_BGRA_15Z] = {"BGRA;15Z"}, + [IMAGING_RAWMODE_BGRA_16B] = {"BGRA;16B"}, + [IMAGING_RAWMODE_BGRA_16L] = {"BGRA;16L"}, + [IMAGING_RAWMODE_BGRX] = {"BGRX"}, + [IMAGING_RAWMODE_BGR_5] = {"BGR;5"}, + [IMAGING_RAWMODE_BGRa] = {"BGRa"}, + [IMAGING_RAWMODE_BGXR] = {"BGXR"}, + [IMAGING_RAWMODE_B_16B] = {"B;16B"}, + [IMAGING_RAWMODE_B_16L] = {"B;16L"}, + [IMAGING_RAWMODE_B_16N] = {"B;16N"}, + [IMAGING_RAWMODE_C] = {"C"}, + [IMAGING_RAWMODE_CMYKX] = {"CMYKX"}, + [IMAGING_RAWMODE_CMYKXX] = {"CMYKXX"}, + [IMAGING_RAWMODE_CMYK_16B] = {"CMYK;16B"}, + [IMAGING_RAWMODE_CMYK_16L] = {"CMYK;16L"}, + [IMAGING_RAWMODE_CMYK_16N] = {"CMYK;16N"}, + [IMAGING_RAWMODE_CMYK_I] = {"CMYK;I"}, + [IMAGING_RAWMODE_CMYK_L] = {"CMYK;L"}, + [IMAGING_RAWMODE_C_I] = {"C;I"}, + [IMAGING_RAWMODE_Cb] = {"Cb"}, + [IMAGING_RAWMODE_Cr] = {"Cr"}, + [IMAGING_RAWMODE_F_16] = {"F;16"}, + [IMAGING_RAWMODE_F_16B] = {"F;16B"}, + [IMAGING_RAWMODE_F_16BS] = {"F;16BS"}, + [IMAGING_RAWMODE_F_16N] = {"F;16N"}, + [IMAGING_RAWMODE_F_16NS] = {"F;16NS"}, + [IMAGING_RAWMODE_F_16S] = {"F;16S"}, + [IMAGING_RAWMODE_F_32] = {"F;32"}, + [IMAGING_RAWMODE_F_32B] = {"F;32B"}, + [IMAGING_RAWMODE_F_32BF] = {"F;32BF"}, + [IMAGING_RAWMODE_F_32BS] = {"F;32BS"}, + [IMAGING_RAWMODE_F_32F] = {"F;32F"}, + [IMAGING_RAWMODE_F_32N] = {"F;32N"}, + [IMAGING_RAWMODE_F_32NF] = {"F;32NF"}, + [IMAGING_RAWMODE_F_32NS] = {"F;32NS"}, + [IMAGING_RAWMODE_F_32S] = {"F;32S"}, + [IMAGING_RAWMODE_F_64BF] = {"F;64BF"}, + [IMAGING_RAWMODE_F_64F] = {"F;64F"}, + [IMAGING_RAWMODE_F_64NF] = {"F;64NF"}, + [IMAGING_RAWMODE_F_8] = {"F;8"}, + [IMAGING_RAWMODE_F_8S] = {"F;8S"}, + [IMAGING_RAWMODE_G] = {"G"}, + [IMAGING_RAWMODE_G_16B] = {"G;16B"}, + [IMAGING_RAWMODE_G_16L] = {"G;16L"}, + [IMAGING_RAWMODE_G_16N] = {"G;16N"}, + [IMAGING_RAWMODE_H] = {"H"}, + [IMAGING_RAWMODE_I_12] = {"I;12"}, + [IMAGING_RAWMODE_I_16BS] = {"I;16BS"}, + [IMAGING_RAWMODE_I_16NS] = {"I;16NS"}, + [IMAGING_RAWMODE_I_16R] = {"I;16R"}, + [IMAGING_RAWMODE_I_16S] = {"I;16S"}, + [IMAGING_RAWMODE_I_32] = {"I;32"}, + [IMAGING_RAWMODE_I_32BS] = {"I;32BS"}, + [IMAGING_RAWMODE_I_32N] = {"I;32N"}, + [IMAGING_RAWMODE_I_32NS] = {"I;32NS"}, + [IMAGING_RAWMODE_I_32S] = {"I;32S"}, + [IMAGING_RAWMODE_I_8] = {"I;8"}, + [IMAGING_RAWMODE_I_8S] = {"I;8S"}, + [IMAGING_RAWMODE_K] = {"K"}, + [IMAGING_RAWMODE_K_I] = {"K;I"}, + [IMAGING_RAWMODE_LA_16B] = {"LA;16B"}, + [IMAGING_RAWMODE_LA_L] = {"LA;L"}, + [IMAGING_RAWMODE_L_16] = {"L;16"}, + [IMAGING_RAWMODE_L_16B] = {"L;16B"}, + [IMAGING_RAWMODE_L_2] = {"L;2"}, + [IMAGING_RAWMODE_L_2I] = {"L;2I"}, + [IMAGING_RAWMODE_L_2IR] = {"L;2IR"}, + [IMAGING_RAWMODE_L_2R] = {"L;2R"}, + [IMAGING_RAWMODE_L_4] = {"L;4"}, + [IMAGING_RAWMODE_L_4I] = {"L;4I"}, + [IMAGING_RAWMODE_L_4IR] = {"L;4IR"}, + [IMAGING_RAWMODE_L_4R] = {"L;4R"}, + [IMAGING_RAWMODE_L_I] = {"L;I"}, + [IMAGING_RAWMODE_L_R] = {"L;R"}, + [IMAGING_RAWMODE_M] = {"M"}, + [IMAGING_RAWMODE_M_I] = {"M;I"}, + [IMAGING_RAWMODE_PA_L] = {"PA;L"}, + [IMAGING_RAWMODE_PX] = {"PX"}, + [IMAGING_RAWMODE_P_1] = {"P;1"}, + [IMAGING_RAWMODE_P_2] = {"P;2"}, + [IMAGING_RAWMODE_P_2L] = {"P;2L"}, + [IMAGING_RAWMODE_P_4] = {"P;4"}, + [IMAGING_RAWMODE_P_4L] = {"P;4L"}, + [IMAGING_RAWMODE_P_R] = {"P;R"}, + [IMAGING_RAWMODE_R] = {"R"}, + [IMAGING_RAWMODE_RGBAX] = {"RGBAX"}, + [IMAGING_RAWMODE_RGBAXX] = {"RGBAXX"}, + [IMAGING_RAWMODE_RGBA_15] = {"RGBA;15"}, + [IMAGING_RAWMODE_RGBA_16B] = {"RGBA;16B"}, + [IMAGING_RAWMODE_RGBA_16L] = {"RGBA;16L"}, + [IMAGING_RAWMODE_RGBA_16N] = {"RGBA;16N"}, + [IMAGING_RAWMODE_RGBA_4B] = {"RGBA;4B"}, + [IMAGING_RAWMODE_RGBA_I] = {"RGBA;I"}, + [IMAGING_RAWMODE_RGBA_L] = {"RGBA;L"}, + [IMAGING_RAWMODE_RGBXX] = {"RGBXX"}, + [IMAGING_RAWMODE_RGBXXX] = {"RGBXXX"}, + [IMAGING_RAWMODE_RGBX_16B] = {"RGBX;16B"}, + [IMAGING_RAWMODE_RGBX_16L] = {"RGBX;16L"}, + [IMAGING_RAWMODE_RGBX_16N] = {"RGBX;16N"}, + [IMAGING_RAWMODE_RGBX_L] = {"RGBX;L"}, + [IMAGING_RAWMODE_RGB_15] = {"RGB;15"}, + [IMAGING_RAWMODE_RGB_16] = {"RGB;16"}, + [IMAGING_RAWMODE_RGB_16B] = {"RGB;16B"}, + [IMAGING_RAWMODE_RGB_16L] = {"RGB;16L"}, + [IMAGING_RAWMODE_RGB_16N] = {"RGB;16N"}, + [IMAGING_RAWMODE_RGB_4B] = {"RGB;4B"}, + [IMAGING_RAWMODE_RGB_L] = {"RGB;L"}, + [IMAGING_RAWMODE_RGB_R] = {"RGB;R"}, + [IMAGING_RAWMODE_RGBaX] = {"RGBaX"}, + [IMAGING_RAWMODE_RGBaXX] = {"RGBaXX"}, + [IMAGING_RAWMODE_RGBa_16B] = {"RGBa;16B"}, + [IMAGING_RAWMODE_RGBa_16L] = {"RGBa;16L"}, + [IMAGING_RAWMODE_RGBa_16N] = {"RGBa;16N"}, + [IMAGING_RAWMODE_R_16B] = {"R;16B"}, + [IMAGING_RAWMODE_R_16L] = {"R;16L"}, + [IMAGING_RAWMODE_R_16N] = {"R;16N"}, + [IMAGING_RAWMODE_S] = {"S"}, + [IMAGING_RAWMODE_V] = {"V"}, + [IMAGING_RAWMODE_X] = {"X"}, + [IMAGING_RAWMODE_XBGR] = {"XBGR"}, + [IMAGING_RAWMODE_XRGB] = {"XRGB"}, + [IMAGING_RAWMODE_Y] = {"Y"}, + [IMAGING_RAWMODE_YCCA_P] = {"YCCA;P"}, + [IMAGING_RAWMODE_YCC_P] = {"YCC;P"}, + [IMAGING_RAWMODE_YCbCrK] = {"YCbCrK"}, + [IMAGING_RAWMODE_YCbCrX] = {"YCbCrX"}, + [IMAGING_RAWMODE_YCbCr_L] = {"YCbCr;L"}, + [IMAGING_RAWMODE_Y_I] = {"Y;I"}, + [IMAGING_RAWMODE_aBGR] = {"aBGR"}, + [IMAGING_RAWMODE_aRGB] = {"aRGB"}, }; -const RawMode * findRawMode(const char * const name) { +const RawModeID findRawModeID(const char * const name) { if (name == NULL) { - return NULL; + return IMAGING_RAWMODE_UNKNOWN; } - const RawMode * rawmode; - for (int i = 0; (rawmode = RAWMODES[i]); i++) { - if (strcmp(rawmode->name, name) == 0) { - return rawmode; + for (size_t i = 0; i < sizeof(RAWMODES) / sizeof(*RAWMODES); i++) { + if (strcmp(RAWMODES[i].name, name) == 0) { + return (RawModeID)i; } } - return NULL; + return IMAGING_RAWMODE_UNKNOWN; } -int isModeI16(const Mode * const mode) { +const RawModeData * const getRawModeData(const RawModeID id) { + if (id < 0 || id > sizeof(RAWMODES) / sizeof(*RAWMODES)) { + return &RAWMODES[IMAGING_RAWMODE_UNKNOWN]; + } + return &RAWMODES[id]; +} + + +int isModeI16(const ModeID mode) { return mode == IMAGING_MODE_I_16 || mode == IMAGING_MODE_I_16L || mode == IMAGING_MODE_I_16B diff --git a/src/libImaging/Mode.h b/src/libImaging/Mode.h index 663f2f468..e21ad941a 100644 --- a/src/libImaging/Mode.h +++ b/src/libImaging/Mode.h @@ -2,227 +2,237 @@ #define __MODE_H__ -typedef struct { - const char * const name; -} Mode; +typedef enum { + IMAGING_MODE_UNKNOWN, -extern const Mode * const IMAGING_MODE_1; -extern const Mode * const IMAGING_MODE_CMYK; -extern const Mode * const IMAGING_MODE_F; -extern const Mode * const IMAGING_MODE_HSV; -extern const Mode * const IMAGING_MODE_I; -extern const Mode * const IMAGING_MODE_L; -extern const Mode * const IMAGING_MODE_LA; -extern const Mode * const IMAGING_MODE_LAB; -extern const Mode * const IMAGING_MODE_La; -extern const Mode * const IMAGING_MODE_P; -extern const Mode * const IMAGING_MODE_PA; -extern const Mode * const IMAGING_MODE_RGB; -extern const Mode * const IMAGING_MODE_RGBA; -extern const Mode * const IMAGING_MODE_RGBX; -extern const Mode * const IMAGING_MODE_RGBa; -extern const Mode * const IMAGING_MODE_YCbCr; + IMAGING_MODE_1, + IMAGING_MODE_CMYK, + IMAGING_MODE_F, + IMAGING_MODE_HSV, + IMAGING_MODE_I, + IMAGING_MODE_L, + IMAGING_MODE_LA, + IMAGING_MODE_LAB, + IMAGING_MODE_La, + IMAGING_MODE_P, + IMAGING_MODE_PA, + IMAGING_MODE_RGB, + IMAGING_MODE_RGBA, + IMAGING_MODE_RGBX, + IMAGING_MODE_RGBa, + IMAGING_MODE_YCbCr, -extern const Mode * const IMAGING_MODE_BGR_15; -extern const Mode * const IMAGING_MODE_BGR_16; -extern const Mode * const IMAGING_MODE_BGR_24; - -extern const Mode * const IMAGING_MODE_I_16; -extern const Mode * const IMAGING_MODE_I_16L; -extern const Mode * const IMAGING_MODE_I_16B; -extern const Mode * const IMAGING_MODE_I_16N; -extern const Mode * const IMAGING_MODE_I_32L; -extern const Mode * const IMAGING_MODE_I_32B; - -const Mode * findMode(const char * const name); + IMAGING_MODE_BGR_15, + IMAGING_MODE_BGR_16, + IMAGING_MODE_BGR_24, + IMAGING_MODE_I_16, + IMAGING_MODE_I_16L, + IMAGING_MODE_I_16B, + IMAGING_MODE_I_16N, + IMAGING_MODE_I_32L, + IMAGING_MODE_I_32B, +} ModeID; typedef struct { const char * const name; -} RawMode; +} ModeData; -// Non-rawmode aliases. -extern const RawMode * const IMAGING_RAWMODE_1; -extern const RawMode * const IMAGING_RAWMODE_CMYK; -extern const RawMode * const IMAGING_RAWMODE_F; -extern const RawMode * const IMAGING_RAWMODE_HSV; -extern const RawMode * const IMAGING_RAWMODE_I; -extern const RawMode * const IMAGING_RAWMODE_L; -extern const RawMode * const IMAGING_RAWMODE_LA; -extern const RawMode * const IMAGING_RAWMODE_LAB; -extern const RawMode * const IMAGING_RAWMODE_La; -extern const RawMode * const IMAGING_RAWMODE_P; -extern const RawMode * const IMAGING_RAWMODE_PA; -extern const RawMode * const IMAGING_RAWMODE_RGB; -extern const RawMode * const IMAGING_RAWMODE_RGBA; -extern const RawMode * const IMAGING_RAWMODE_RGBX; -extern const RawMode * const IMAGING_RAWMODE_RGBa; -extern const RawMode * const IMAGING_RAWMODE_YCbCr; - -// BGR modes. -extern const RawMode * const IMAGING_RAWMODE_BGR_15; -extern const RawMode * const IMAGING_RAWMODE_BGR_16; -extern const RawMode * const IMAGING_RAWMODE_BGR_24; -extern const RawMode * const IMAGING_RAWMODE_BGR_32; - -// I;* modes. -extern const RawMode * const IMAGING_RAWMODE_I_16; -extern const RawMode * const IMAGING_RAWMODE_I_16L; -extern const RawMode * const IMAGING_RAWMODE_I_16B; -extern const RawMode * const IMAGING_RAWMODE_I_16N; -extern const RawMode * const IMAGING_RAWMODE_I_32L; -extern const RawMode * const IMAGING_RAWMODE_I_32B; - -// Rawmodes -extern const RawMode * const IMAGING_RAWMODE_1_8; -extern const RawMode * const IMAGING_RAWMODE_1_I; -extern const RawMode * const IMAGING_RAWMODE_1_IR; -extern const RawMode * const IMAGING_RAWMODE_1_R; -extern const RawMode * const IMAGING_RAWMODE_A; -extern const RawMode * const IMAGING_RAWMODE_ABGR; -extern const RawMode * const IMAGING_RAWMODE_ARGB; -extern const RawMode * const IMAGING_RAWMODE_A_16B; -extern const RawMode * const IMAGING_RAWMODE_A_16L; -extern const RawMode * const IMAGING_RAWMODE_A_16N; -extern const RawMode * const IMAGING_RAWMODE_B; -extern const RawMode * const IMAGING_RAWMODE_BGAR; -extern const RawMode * const IMAGING_RAWMODE_BGR; -extern const RawMode * const IMAGING_RAWMODE_BGRA; -extern const RawMode * const IMAGING_RAWMODE_BGRA_15; -extern const RawMode * const IMAGING_RAWMODE_BGRA_15Z; -extern const RawMode * const IMAGING_RAWMODE_BGRA_16B; -extern const RawMode * const IMAGING_RAWMODE_BGRA_16L; -extern const RawMode * const IMAGING_RAWMODE_BGRX; -extern const RawMode * const IMAGING_RAWMODE_BGR_5; -extern const RawMode * const IMAGING_RAWMODE_BGRa; -extern const RawMode * const IMAGING_RAWMODE_BGXR; -extern const RawMode * const IMAGING_RAWMODE_B_16B; -extern const RawMode * const IMAGING_RAWMODE_B_16L; -extern const RawMode * const IMAGING_RAWMODE_B_16N; -extern const RawMode * const IMAGING_RAWMODE_C; -extern const RawMode * const IMAGING_RAWMODE_CMYKX; -extern const RawMode * const IMAGING_RAWMODE_CMYKXX; -extern const RawMode * const IMAGING_RAWMODE_CMYK_16B; -extern const RawMode * const IMAGING_RAWMODE_CMYK_16L; -extern const RawMode * const IMAGING_RAWMODE_CMYK_16N; -extern const RawMode * const IMAGING_RAWMODE_CMYK_I; -extern const RawMode * const IMAGING_RAWMODE_CMYK_L; -extern const RawMode * const IMAGING_RAWMODE_C_I; -extern const RawMode * const IMAGING_RAWMODE_Cb; -extern const RawMode * const IMAGING_RAWMODE_Cr; -extern const RawMode * const IMAGING_RAWMODE_F_16; -extern const RawMode * const IMAGING_RAWMODE_F_16B; -extern const RawMode * const IMAGING_RAWMODE_F_16BS; -extern const RawMode * const IMAGING_RAWMODE_F_16N; -extern const RawMode * const IMAGING_RAWMODE_F_16NS; -extern const RawMode * const IMAGING_RAWMODE_F_16S; -extern const RawMode * const IMAGING_RAWMODE_F_32; -extern const RawMode * const IMAGING_RAWMODE_F_32B; -extern const RawMode * const IMAGING_RAWMODE_F_32BF; -extern const RawMode * const IMAGING_RAWMODE_F_32BS; -extern const RawMode * const IMAGING_RAWMODE_F_32F; -extern const RawMode * const IMAGING_RAWMODE_F_32N; -extern const RawMode * const IMAGING_RAWMODE_F_32NF; -extern const RawMode * const IMAGING_RAWMODE_F_32NS; -extern const RawMode * const IMAGING_RAWMODE_F_32S; -extern const RawMode * const IMAGING_RAWMODE_F_64BF; -extern const RawMode * const IMAGING_RAWMODE_F_64F; -extern const RawMode * const IMAGING_RAWMODE_F_64NF; -extern const RawMode * const IMAGING_RAWMODE_F_8; -extern const RawMode * const IMAGING_RAWMODE_F_8S; -extern const RawMode * const IMAGING_RAWMODE_G; -extern const RawMode * const IMAGING_RAWMODE_G_16B; -extern const RawMode * const IMAGING_RAWMODE_G_16L; -extern const RawMode * const IMAGING_RAWMODE_G_16N; -extern const RawMode * const IMAGING_RAWMODE_H; -extern const RawMode * const IMAGING_RAWMODE_I_12; -extern const RawMode * const IMAGING_RAWMODE_I_16BS; -extern const RawMode * const IMAGING_RAWMODE_I_16NS; -extern const RawMode * const IMAGING_RAWMODE_I_16R; -extern const RawMode * const IMAGING_RAWMODE_I_16S; -extern const RawMode * const IMAGING_RAWMODE_I_32; -extern const RawMode * const IMAGING_RAWMODE_I_32BS; -extern const RawMode * const IMAGING_RAWMODE_I_32N; -extern const RawMode * const IMAGING_RAWMODE_I_32NS; -extern const RawMode * const IMAGING_RAWMODE_I_32S; -extern const RawMode * const IMAGING_RAWMODE_I_8; -extern const RawMode * const IMAGING_RAWMODE_I_8S; -extern const RawMode * const IMAGING_RAWMODE_K; -extern const RawMode * const IMAGING_RAWMODE_K_I; -extern const RawMode * const IMAGING_RAWMODE_LA_16B; -extern const RawMode * const IMAGING_RAWMODE_LA_L; -extern const RawMode * const IMAGING_RAWMODE_L_16; -extern const RawMode * const IMAGING_RAWMODE_L_16B; -extern const RawMode * const IMAGING_RAWMODE_L_2; -extern const RawMode * const IMAGING_RAWMODE_L_2I; -extern const RawMode * const IMAGING_RAWMODE_L_2IR; -extern const RawMode * const IMAGING_RAWMODE_L_2R; -extern const RawMode * const IMAGING_RAWMODE_L_4; -extern const RawMode * const IMAGING_RAWMODE_L_4I; -extern const RawMode * const IMAGING_RAWMODE_L_4IR; -extern const RawMode * const IMAGING_RAWMODE_L_4R; -extern const RawMode * const IMAGING_RAWMODE_L_I; -extern const RawMode * const IMAGING_RAWMODE_L_R; -extern const RawMode * const IMAGING_RAWMODE_M; -extern const RawMode * const IMAGING_RAWMODE_M_I; -extern const RawMode * const IMAGING_RAWMODE_PA_L; -extern const RawMode * const IMAGING_RAWMODE_PX; -extern const RawMode * const IMAGING_RAWMODE_P_1; -extern const RawMode * const IMAGING_RAWMODE_P_2; -extern const RawMode * const IMAGING_RAWMODE_P_2L; -extern const RawMode * const IMAGING_RAWMODE_P_4; -extern const RawMode * const IMAGING_RAWMODE_P_4L; -extern const RawMode * const IMAGING_RAWMODE_P_R; -extern const RawMode * const IMAGING_RAWMODE_R; -extern const RawMode * const IMAGING_RAWMODE_RGBAX; -extern const RawMode * const IMAGING_RAWMODE_RGBAXX; -extern const RawMode * const IMAGING_RAWMODE_RGBA_15; -extern const RawMode * const IMAGING_RAWMODE_RGBA_16B; -extern const RawMode * const IMAGING_RAWMODE_RGBA_16L; -extern const RawMode * const IMAGING_RAWMODE_RGBA_16N; -extern const RawMode * const IMAGING_RAWMODE_RGBA_4B; -extern const RawMode * const IMAGING_RAWMODE_RGBA_I; -extern const RawMode * const IMAGING_RAWMODE_RGBA_L; -extern const RawMode * const IMAGING_RAWMODE_RGBXX; -extern const RawMode * const IMAGING_RAWMODE_RGBXXX; -extern const RawMode * const IMAGING_RAWMODE_RGBX_16B; -extern const RawMode * const IMAGING_RAWMODE_RGBX_16L; -extern const RawMode * const IMAGING_RAWMODE_RGBX_16N; -extern const RawMode * const IMAGING_RAWMODE_RGBX_L; -extern const RawMode * const IMAGING_RAWMODE_RGB_15; -extern const RawMode * const IMAGING_RAWMODE_RGB_16; -extern const RawMode * const IMAGING_RAWMODE_RGB_16B; -extern const RawMode * const IMAGING_RAWMODE_RGB_16L; -extern const RawMode * const IMAGING_RAWMODE_RGB_16N; -extern const RawMode * const IMAGING_RAWMODE_RGB_4B; -extern const RawMode * const IMAGING_RAWMODE_RGB_L; -extern const RawMode * const IMAGING_RAWMODE_RGB_R; -extern const RawMode * const IMAGING_RAWMODE_RGBaX; -extern const RawMode * const IMAGING_RAWMODE_RGBaXX; -extern const RawMode * const IMAGING_RAWMODE_RGBa_16B; -extern const RawMode * const IMAGING_RAWMODE_RGBa_16L; -extern const RawMode * const IMAGING_RAWMODE_RGBa_16N; -extern const RawMode * const IMAGING_RAWMODE_R_16B; -extern const RawMode * const IMAGING_RAWMODE_R_16L; -extern const RawMode * const IMAGING_RAWMODE_R_16N; -extern const RawMode * const IMAGING_RAWMODE_S; -extern const RawMode * const IMAGING_RAWMODE_V; -extern const RawMode * const IMAGING_RAWMODE_X; -extern const RawMode * const IMAGING_RAWMODE_XBGR; -extern const RawMode * const IMAGING_RAWMODE_XRGB; -extern const RawMode * const IMAGING_RAWMODE_Y; -extern const RawMode * const IMAGING_RAWMODE_YCCA_P; -extern const RawMode * const IMAGING_RAWMODE_YCC_P; -extern const RawMode * const IMAGING_RAWMODE_YCbCrK; -extern const RawMode * const IMAGING_RAWMODE_YCbCrX; -extern const RawMode * const IMAGING_RAWMODE_YCbCr_L; -extern const RawMode * const IMAGING_RAWMODE_Y_I; -extern const RawMode * const IMAGING_RAWMODE_aBGR; -extern const RawMode * const IMAGING_RAWMODE_aRGB; - -const RawMode * findRawMode(const char * const name); +const ModeID findModeID(const char * const name); +const ModeData * const getModeData(const ModeID id); -int isModeI16(const Mode * const mode); +typedef enum { + IMAGING_RAWMODE_UNKNOWN, + + // Non-rawmode aliases. + IMAGING_RAWMODE_1, + IMAGING_RAWMODE_CMYK, + IMAGING_RAWMODE_F, + IMAGING_RAWMODE_HSV, + IMAGING_RAWMODE_I, + IMAGING_RAWMODE_L, + IMAGING_RAWMODE_LA, + IMAGING_RAWMODE_LAB, + IMAGING_RAWMODE_La, + IMAGING_RAWMODE_P, + IMAGING_RAWMODE_PA, + IMAGING_RAWMODE_RGB, + IMAGING_RAWMODE_RGBA, + IMAGING_RAWMODE_RGBX, + IMAGING_RAWMODE_RGBa, + IMAGING_RAWMODE_YCbCr, + + // BGR modes. + IMAGING_RAWMODE_BGR_15, + IMAGING_RAWMODE_BGR_16, + IMAGING_RAWMODE_BGR_24, + IMAGING_RAWMODE_BGR_32, + + // I;* modes. + IMAGING_RAWMODE_I_16, + IMAGING_RAWMODE_I_16L, + IMAGING_RAWMODE_I_16B, + IMAGING_RAWMODE_I_16N, + IMAGING_RAWMODE_I_32L, + IMAGING_RAWMODE_I_32B, + + // Rawmodes + IMAGING_RAWMODE_1_8, + IMAGING_RAWMODE_1_I, + IMAGING_RAWMODE_1_IR, + IMAGING_RAWMODE_1_R, + IMAGING_RAWMODE_A, + IMAGING_RAWMODE_ABGR, + IMAGING_RAWMODE_ARGB, + IMAGING_RAWMODE_A_16B, + IMAGING_RAWMODE_A_16L, + IMAGING_RAWMODE_A_16N, + IMAGING_RAWMODE_B, + IMAGING_RAWMODE_BGAR, + IMAGING_RAWMODE_BGR, + IMAGING_RAWMODE_BGRA, + IMAGING_RAWMODE_BGRA_15, + IMAGING_RAWMODE_BGRA_15Z, + IMAGING_RAWMODE_BGRA_16B, + IMAGING_RAWMODE_BGRA_16L, + IMAGING_RAWMODE_BGRX, + IMAGING_RAWMODE_BGR_5, + IMAGING_RAWMODE_BGRa, + IMAGING_RAWMODE_BGXR, + IMAGING_RAWMODE_B_16B, + IMAGING_RAWMODE_B_16L, + IMAGING_RAWMODE_B_16N, + IMAGING_RAWMODE_C, + IMAGING_RAWMODE_CMYKX, + IMAGING_RAWMODE_CMYKXX, + IMAGING_RAWMODE_CMYK_16B, + IMAGING_RAWMODE_CMYK_16L, + IMAGING_RAWMODE_CMYK_16N, + IMAGING_RAWMODE_CMYK_I, + IMAGING_RAWMODE_CMYK_L, + IMAGING_RAWMODE_C_I, + IMAGING_RAWMODE_Cb, + IMAGING_RAWMODE_Cr, + IMAGING_RAWMODE_F_16, + IMAGING_RAWMODE_F_16B, + IMAGING_RAWMODE_F_16BS, + IMAGING_RAWMODE_F_16N, + IMAGING_RAWMODE_F_16NS, + IMAGING_RAWMODE_F_16S, + IMAGING_RAWMODE_F_32, + IMAGING_RAWMODE_F_32B, + IMAGING_RAWMODE_F_32BF, + IMAGING_RAWMODE_F_32BS, + IMAGING_RAWMODE_F_32F, + IMAGING_RAWMODE_F_32N, + IMAGING_RAWMODE_F_32NF, + IMAGING_RAWMODE_F_32NS, + IMAGING_RAWMODE_F_32S, + IMAGING_RAWMODE_F_64BF, + IMAGING_RAWMODE_F_64F, + IMAGING_RAWMODE_F_64NF, + IMAGING_RAWMODE_F_8, + IMAGING_RAWMODE_F_8S, + IMAGING_RAWMODE_G, + IMAGING_RAWMODE_G_16B, + IMAGING_RAWMODE_G_16L, + IMAGING_RAWMODE_G_16N, + IMAGING_RAWMODE_H, + IMAGING_RAWMODE_I_12, + IMAGING_RAWMODE_I_16BS, + IMAGING_RAWMODE_I_16NS, + IMAGING_RAWMODE_I_16R, + IMAGING_RAWMODE_I_16S, + IMAGING_RAWMODE_I_32, + IMAGING_RAWMODE_I_32BS, + IMAGING_RAWMODE_I_32N, + IMAGING_RAWMODE_I_32NS, + IMAGING_RAWMODE_I_32S, + IMAGING_RAWMODE_I_8, + IMAGING_RAWMODE_I_8S, + IMAGING_RAWMODE_K, + IMAGING_RAWMODE_K_I, + IMAGING_RAWMODE_LA_16B, + IMAGING_RAWMODE_LA_L, + IMAGING_RAWMODE_L_16, + IMAGING_RAWMODE_L_16B, + IMAGING_RAWMODE_L_2, + IMAGING_RAWMODE_L_2I, + IMAGING_RAWMODE_L_2IR, + IMAGING_RAWMODE_L_2R, + IMAGING_RAWMODE_L_4, + IMAGING_RAWMODE_L_4I, + IMAGING_RAWMODE_L_4IR, + IMAGING_RAWMODE_L_4R, + IMAGING_RAWMODE_L_I, + IMAGING_RAWMODE_L_R, + IMAGING_RAWMODE_M, + IMAGING_RAWMODE_M_I, + IMAGING_RAWMODE_PA_L, + IMAGING_RAWMODE_PX, + IMAGING_RAWMODE_P_1, + IMAGING_RAWMODE_P_2, + IMAGING_RAWMODE_P_2L, + IMAGING_RAWMODE_P_4, + IMAGING_RAWMODE_P_4L, + IMAGING_RAWMODE_P_R, + IMAGING_RAWMODE_R, + IMAGING_RAWMODE_RGBAX, + IMAGING_RAWMODE_RGBAXX, + IMAGING_RAWMODE_RGBA_15, + IMAGING_RAWMODE_RGBA_16B, + IMAGING_RAWMODE_RGBA_16L, + IMAGING_RAWMODE_RGBA_16N, + IMAGING_RAWMODE_RGBA_4B, + IMAGING_RAWMODE_RGBA_I, + IMAGING_RAWMODE_RGBA_L, + IMAGING_RAWMODE_RGBXX, + IMAGING_RAWMODE_RGBXXX, + IMAGING_RAWMODE_RGBX_16B, + IMAGING_RAWMODE_RGBX_16L, + IMAGING_RAWMODE_RGBX_16N, + IMAGING_RAWMODE_RGBX_L, + IMAGING_RAWMODE_RGB_15, + IMAGING_RAWMODE_RGB_16, + IMAGING_RAWMODE_RGB_16B, + IMAGING_RAWMODE_RGB_16L, + IMAGING_RAWMODE_RGB_16N, + IMAGING_RAWMODE_RGB_4B, + IMAGING_RAWMODE_RGB_L, + IMAGING_RAWMODE_RGB_R, + IMAGING_RAWMODE_RGBaX, + IMAGING_RAWMODE_RGBaXX, + IMAGING_RAWMODE_RGBa_16B, + IMAGING_RAWMODE_RGBa_16L, + IMAGING_RAWMODE_RGBa_16N, + IMAGING_RAWMODE_R_16B, + IMAGING_RAWMODE_R_16L, + IMAGING_RAWMODE_R_16N, + IMAGING_RAWMODE_S, + IMAGING_RAWMODE_V, + IMAGING_RAWMODE_X, + IMAGING_RAWMODE_XBGR, + IMAGING_RAWMODE_XRGB, + IMAGING_RAWMODE_Y, + IMAGING_RAWMODE_YCCA_P, + IMAGING_RAWMODE_YCC_P, + IMAGING_RAWMODE_YCbCrK, + IMAGING_RAWMODE_YCbCrX, + IMAGING_RAWMODE_YCbCr_L, + IMAGING_RAWMODE_Y_I, + IMAGING_RAWMODE_aBGR, + IMAGING_RAWMODE_aRGB, +} RawModeID; + +typedef struct { + const char * const name; +} RawModeData; + +const RawModeID findRawModeID(const char * const name); +const RawModeData * const getRawModeData(const RawModeID id); + + +int isModeI16(const ModeID mode); #endif // __MODE_H__ diff --git a/src/libImaging/Pack.c b/src/libImaging/Pack.c index aaa074c92..63bbc8acb 100644 --- a/src/libImaging/Pack.c +++ b/src/libImaging/Pack.c @@ -518,147 +518,146 @@ band3(UINT8 *out, const UINT8 *in, int pixels) { } } -static struct Packer { - const Mode *mode; - const RawMode *rawmode; +static struct { + const ModeID mode; + const RawModeID rawmode; int bits; ImagingShuffler pack; } packers[] = { /* bilevel */ - {"1", "1", 1, pack1}, - {"1", "1;I", 1, pack1I}, - {"1", "1;R", 1, pack1R}, - {"1", "1;IR", 1, pack1IR}, - {"1", "L", 8, pack1L}, + {IMAGING_MODE_1, IMAGING_RAWMODE_1, 1, pack1}, + {IMAGING_MODE_1, IMAGING_RAWMODE_1_I, 1, pack1I}, + {IMAGING_MODE_1, IMAGING_RAWMODE_1_R, 1, pack1R}, + {IMAGING_MODE_1, IMAGING_RAWMODE_1_IR, 1, pack1IR}, + {IMAGING_MODE_1, IMAGING_RAWMODE_L, 8, pack1L}, /* grayscale */ - {"L", "L", 8, copy1}, - {"L", "L;16", 16, packL16}, - {"L", "L;16B", 16, packL16B}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L, 8, copy1}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_16, 16, packL16}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_16B, 16, packL16B}, /* grayscale w. alpha */ - {"LA", "LA", 16, packLA}, - {"LA", "LA;L", 16, packLAL}, + {IMAGING_MODE_LA, IMAGING_RAWMODE_LA, 16, packLA}, + {IMAGING_MODE_LA, IMAGING_RAWMODE_LA_L, 16, packLAL}, /* grayscale w. alpha premultiplied */ - {"La", "La", 16, packLA}, + {IMAGING_MODE_La, IMAGING_RAWMODE_La, 16, packLA}, /* palette */ - {"P", "P;1", 1, pack1}, - {"P", "P;2", 2, packP2}, - {"P", "P;4", 4, packP4}, - {"P", "P", 8, copy1}, + {IMAGING_MODE_P, IMAGING_RAWMODE_P_1, 1, pack1}, + {IMAGING_MODE_P, IMAGING_RAWMODE_P_2, 2, packP2}, + {IMAGING_MODE_P, IMAGING_RAWMODE_P_4, 4, packP4}, + {IMAGING_MODE_P, IMAGING_RAWMODE_P, 8, copy1}, /* palette w. alpha */ - {"PA", "PA", 16, packLA}, - {"PA", "PA;L", 16, packLAL}, + {IMAGING_MODE_PA, IMAGING_RAWMODE_PA, 16, packLA}, + {IMAGING_MODE_PA, IMAGING_RAWMODE_PA_L, 16, packLAL}, /* true colour */ - {"RGB", "RGB", 24, ImagingPackRGB}, - {"RGB", "RGBX", 32, copy4}, - {"RGB", "RGBA", 32, copy4}, - {"RGB", "XRGB", 32, ImagingPackXRGB}, - {"RGB", "BGR", 24, ImagingPackBGR}, - {"RGB", "BGRX", 32, ImagingPackBGRX}, - {"RGB", "XBGR", 32, ImagingPackXBGR}, - {"RGB", "RGB;L", 24, packRGBL}, - {"RGB", "R", 8, band0}, - {"RGB", "G", 8, band1}, - {"RGB", "B", 8, band2}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB, 24, ImagingPackRGB}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX, 32, copy4}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBA, 32, copy4}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_XRGB, 32, ImagingPackXRGB}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGR, 24, ImagingPackBGR}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGRX, 32, ImagingPackBGRX}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_XBGR, 32, ImagingPackXBGR}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_L, 24, packRGBL}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_R, 8, band0}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_G, 8, band1}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_B, 8, band2}, /* true colour w. alpha */ - {"RGBA", "RGBA", 32, copy4}, - {"RGBA", "RGBA;L", 32, packRGBXL}, - {"RGBA", "RGB", 24, ImagingPackRGB}, - {"RGBA", "BGR", 24, ImagingPackBGR}, - {"RGBA", "BGRA", 32, ImagingPackBGRA}, - {"RGBA", "ABGR", 32, ImagingPackABGR}, - {"RGBA", "BGRa", 32, ImagingPackBGRa}, - {"RGBA", "R", 8, band0}, - {"RGBA", "G", 8, band1}, - {"RGBA", "B", 8, band2}, - {"RGBA", "A", 8, band3}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA, 32, copy4}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_L, 32, packRGBXL}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGB, 24, ImagingPackRGB}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGR, 24, ImagingPackBGR}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA, 32, ImagingPackBGRA}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_ABGR, 32, ImagingPackABGR}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRa, 32, ImagingPackBGRa}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R, 8, band0}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G, 8, band1}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B, 8, band2}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A, 8, band3}, /* true colour w. alpha premultiplied */ - {"RGBa", "RGBa", 32, copy4}, - {"RGBa", "BGRa", 32, ImagingPackBGRA}, - {"RGBa", "aBGR", 32, ImagingPackABGR}, + {IMAGING_MODE_RGBa, IMAGING_RAWMODE_RGBa, 32, copy4}, + {IMAGING_MODE_RGBa, IMAGING_RAWMODE_BGRa, 32, ImagingPackBGRA}, + {IMAGING_MODE_RGBa, IMAGING_RAWMODE_aBGR, 32, ImagingPackABGR}, /* true colour w. padding */ - {"RGBX", "RGBX", 32, copy4}, - {"RGBX", "RGBX;L", 32, packRGBXL}, - {"RGBX", "RGB", 24, ImagingPackRGB}, - {"RGBX", "BGR", 24, ImagingPackBGR}, - {"RGBX", "BGRX", 32, ImagingPackBGRX}, - {"RGBX", "XBGR", 32, ImagingPackXBGR}, - {"RGBX", "R", 8, band0}, - {"RGBX", "G", 8, band1}, - {"RGBX", "B", 8, band2}, - {"RGBX", "X", 8, band3}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX, 32, copy4}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_L, 32, packRGBXL}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB, 24, ImagingPackRGB}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGR, 24, ImagingPackBGR}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGRX, 32, ImagingPackBGRX}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_XBGR, 32, ImagingPackXBGR}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_R, 8, band0}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_G, 8, band1}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_B, 8, band2}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_X, 8, band3}, /* colour separation */ - {"CMYK", "CMYK", 32, copy4}, - {"CMYK", "CMYK;I", 32, copy4I}, - {"CMYK", "CMYK;L", 32, packRGBXL}, - {"CMYK", "C", 8, band0}, - {"CMYK", "M", 8, band1}, - {"CMYK", "Y", 8, band2}, - {"CMYK", "K", 8, band3}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK, 32, copy4}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_I, 32, copy4I}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_L, 32, packRGBXL}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_C, 8, band0}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_M, 8, band1}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_Y, 8, band2}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_K, 8, band3}, /* video (YCbCr) */ - {"YCbCr", "YCbCr", 24, ImagingPackRGB}, - {"YCbCr", "YCbCr;L", 24, packRGBL}, - {"YCbCr", "YCbCrX", 32, copy4}, - {"YCbCr", "YCbCrK", 32, copy4}, - {"YCbCr", "Y", 8, band0}, - {"YCbCr", "Cb", 8, band1}, - {"YCbCr", "Cr", 8, band2}, + {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCr, 24, ImagingPackRGB}, + {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCr_L, 24, packRGBL}, + {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCrX, 32, copy4}, + {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCrK, 32, copy4}, + {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_Y, 8, band0}, + {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_Cb, 8, band1}, + {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_Cr, 8, band2}, /* LAB Color */ - {"LAB", "LAB", 24, ImagingPackLAB}, - {"LAB", "L", 8, band0}, - {"LAB", "A", 8, band1}, - {"LAB", "B", 8, band2}, + {IMAGING_MODE_LAB, IMAGING_RAWMODE_LAB, 24, ImagingPackLAB}, + {IMAGING_MODE_LAB, IMAGING_RAWMODE_L, 8, band0}, + {IMAGING_MODE_LAB, IMAGING_RAWMODE_A, 8, band1}, + {IMAGING_MODE_LAB, IMAGING_RAWMODE_B, 8, band2}, /* HSV */ - {"HSV", "HSV", 24, ImagingPackRGB}, - {"HSV", "H", 8, band0}, - {"HSV", "S", 8, band1}, - {"HSV", "V", 8, band2}, + {IMAGING_MODE_HSV, IMAGING_RAWMODE_HSV, 24, ImagingPackRGB}, + {IMAGING_MODE_HSV, IMAGING_RAWMODE_H, 8, band0}, + {IMAGING_MODE_HSV, IMAGING_RAWMODE_S, 8, band1}, + {IMAGING_MODE_HSV, IMAGING_RAWMODE_V, 8, band2}, /* integer */ - {"I", "I", 32, copy4}, - {"I", "I;16B", 16, packI16B}, - {"I", "I;32S", 32, packI32S}, - {"I", "I;32NS", 32, copy4}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I, 32, copy4}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_16B, 16, packI16B}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_32S, 32, packI32S}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_32NS, 32, copy4}, /* floating point */ - {"F", "F", 32, copy4}, - {"F", "F;32F", 32, packI32S}, - {"F", "F;32NF", 32, copy4}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F, 32, copy4}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_32F, 32, packI32S}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_32NF, 32, copy4}, /* storage modes */ - {"I;16", "I;16", 16, copy2}, + {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16, 16, copy2}, #ifdef WORDS_BIGENDIAN - {"I;16", "I;16B", 16, packI16N_I16}, + {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16B, 16, packI16N_I16}, #else - {"I;16", "I;16B", 16, packI16N_I16B}, + {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16B, 16, packI16N_I16B}, #endif - {"I;16B", "I;16B", 16, copy2}, - {"I;16L", "I;16L", 16, copy2}, - {"I;16N", "I;16N", 16, copy2}, - {"I;16", "I;16N", 16, packI16N_I16}, // LibTiff native->image endian. - {"I;16L", "I;16N", 16, packI16N_I16}, - {"I;16B", "I;16N", 16, packI16N_I16B}, + {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16B, 16, copy2}, + {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16L, 16, copy2}, + {IMAGING_MODE_I_16N, IMAGING_RAWMODE_I_16N, 16, copy2}, + {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16N, 16, packI16N_I16}, // LibTiff native->image endian. + {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16N, 16, packI16N_I16}, + {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16N, 16, packI16N_I16B}, {NULL} /* sentinel */ }; ImagingShuffler -ImagingFindPacker(const Mode *mode, const RawMode *rawmode, int *bits_out) { - int i; - for (i = 0; packers[i].mode; i++) { +ImagingFindPacker(const ModeID mode, const RawModeID rawmode, int *bits_out) { + for (size_t i = 0; i < sizeof(packers) / sizeof(*packers); i++) { if (packers[i].mode == mode && packers[i].rawmode == rawmode) { if (bits_out) { *bits_out = packers[i].bits; @@ -668,152 +667,3 @@ ImagingFindPacker(const Mode *mode, const RawMode *rawmode, int *bits_out) { } return NULL; } - -void -ImagingPackInit(void) { - const struct Packer temp[] = { - /* bilevel */ - {IMAGING_MODE_1, IMAGING_RAWMODE_1, 1, pack1}, - {IMAGING_MODE_1, IMAGING_RAWMODE_1_I, 1, pack1I}, - {IMAGING_MODE_1, IMAGING_RAWMODE_1_R, 1, pack1R}, - {IMAGING_MODE_1, IMAGING_RAWMODE_1_IR, 1, pack1IR}, - {IMAGING_MODE_1, IMAGING_RAWMODE_L, 8, pack1L}, - - /* grayscale */ - {IMAGING_MODE_L, IMAGING_RAWMODE_L, 8, copy1}, - {IMAGING_MODE_L, IMAGING_RAWMODE_L_16, 16, packL16}, - {IMAGING_MODE_L, IMAGING_RAWMODE_L_16B, 16, packL16B}, - - /* grayscale w. alpha */ - {IMAGING_MODE_LA, IMAGING_RAWMODE_LA, 16, packLA}, - {IMAGING_MODE_LA, IMAGING_RAWMODE_LA_L, 16, packLAL}, - - /* grayscale w. alpha premultiplied */ - {IMAGING_MODE_La, IMAGING_RAWMODE_La, 16, packLA}, - - /* palette */ - {IMAGING_MODE_P, IMAGING_RAWMODE_P_1, 1, pack1}, - {IMAGING_MODE_P, IMAGING_RAWMODE_P_2, 2, packP2}, - {IMAGING_MODE_P, IMAGING_RAWMODE_P_4, 4, packP4}, - {IMAGING_MODE_P, IMAGING_RAWMODE_P, 8, copy1}, - - /* palette w. alpha */ - {IMAGING_MODE_PA, IMAGING_RAWMODE_PA, 16, packLA}, - {IMAGING_MODE_PA, IMAGING_RAWMODE_PA_L, 16, packLAL}, - - /* true colour */ - {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB, 24, ImagingPackRGB}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX, 32, copy4}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBA, 32, copy4}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_XRGB, 32, ImagingPackXRGB}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGR, 24, ImagingPackBGR}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGRX, 32, ImagingPackBGRX}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_XBGR, 32, ImagingPackXBGR}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_L, 24, packRGBL}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_R, 8, band0}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_G, 8, band1}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_B, 8, band2}, - - /* true colour w. alpha */ - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA, 32, copy4}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_L, 32, packRGBXL}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGB, 24, ImagingPackRGB}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGR, 24, ImagingPackBGR}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA, 32, ImagingPackBGRA}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_ABGR, 32, ImagingPackABGR}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRa, 32, ImagingPackBGRa}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R, 8, band0}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G, 8, band1}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B, 8, band2}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A, 8, band3}, - - /* true colour w. alpha premultiplied */ - {IMAGING_MODE_RGBa, IMAGING_RAWMODE_RGBa, 32, copy4}, - {IMAGING_MODE_RGBa, IMAGING_RAWMODE_BGRa, 32, ImagingPackBGRA}, - {IMAGING_MODE_RGBa, IMAGING_RAWMODE_aBGR, 32, ImagingPackABGR}, - - /* true colour w. padding */ - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX, 32, copy4}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_L, 32, packRGBXL}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB, 24, ImagingPackRGB}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGR, 24, ImagingPackBGR}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGRX, 32, ImagingPackBGRX}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_XBGR, 32, ImagingPackXBGR}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_R, 8, band0}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_G, 8, band1}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_B, 8, band2}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_X, 8, band3}, - - /* colour separation */ - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK, 32, copy4}, - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_I, 32, copy4I}, - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_L, 32, packRGBXL}, - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_C, 8, band0}, - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_M, 8, band1}, - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_Y, 8, band2}, - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_K, 8, band3}, - - /* video (YCbCr) */ - {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCr, 24, ImagingPackRGB}, - {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCr_L, 24, packRGBL}, - {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCrX, 32, copy4}, - {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCrK, 32, copy4}, - {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_Y, 8, band0}, - {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_Cb, 8, band1}, - {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_Cr, 8, band2}, - - /* LAB Color */ - {IMAGING_MODE_LAB, IMAGING_RAWMODE_LAB, 24, ImagingPackLAB}, - {IMAGING_MODE_LAB, IMAGING_RAWMODE_L, 8, band0}, - {IMAGING_MODE_LAB, IMAGING_RAWMODE_A, 8, band1}, - {IMAGING_MODE_LAB, IMAGING_RAWMODE_B, 8, band2}, - - /* HSV */ - {IMAGING_MODE_HSV, IMAGING_RAWMODE_HSV, 24, ImagingPackRGB}, - {IMAGING_MODE_HSV, IMAGING_RAWMODE_H, 8, band0}, - {IMAGING_MODE_HSV, IMAGING_RAWMODE_S, 8, band1}, - {IMAGING_MODE_HSV, IMAGING_RAWMODE_V, 8, band2}, - - /* integer */ - {IMAGING_MODE_I, IMAGING_RAWMODE_I, 32, copy4}, - {IMAGING_MODE_I, IMAGING_RAWMODE_I_16B, 16, packI16B}, - {IMAGING_MODE_I, IMAGING_RAWMODE_I_32S, 32, packI32S}, - {IMAGING_MODE_I, IMAGING_RAWMODE_I_32NS, 32, copy4}, - - /* floating point */ - {IMAGING_MODE_F, IMAGING_RAWMODE_F, 32, copy4}, - {IMAGING_MODE_F, IMAGING_RAWMODE_F_32F, 32, packI32S}, - {IMAGING_MODE_F, IMAGING_RAWMODE_F_32NF, 32, copy4}, - - /* storage modes */ - {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16, 16, copy2}, -#ifdef WORDS_BIGENDIAN - {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16B, 16, packI16N_I16}, -#else - {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16B, 16, packI16N_I16B}, -#endif - {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16B, 16, copy2}, - {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16L, 16, copy2}, - {IMAGING_MODE_I_16N, IMAGING_RAWMODE_I_16N, 16, copy2}, - {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16N, 16, packI16N_I16}, // LibTiff native->image endian. - {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16N, 16, packI16N_I16}, - {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16N, 16, packI16N_I16B}, - {IMAGING_MODE_BGR_15, IMAGING_RAWMODE_BGR_15, 16, copy2}, - {IMAGING_MODE_BGR_16, IMAGING_RAWMODE_BGR_16, 16, copy2}, - {IMAGING_MODE_BGR_24, IMAGING_RAWMODE_BGR_24, 24, copy3}, - - {NULL} /* sentinel */ - }; - packers = malloc(sizeof(temp)); - if (packers == NULL) { - fprintf(stderr, "PackInit: failed to allocate memory for packers table\n"); - exit(1); - } - memcpy(packers, temp, sizeof(temp)); -} - -void -ImagingPackFree(void) { - free(packers); - packers = NULL; -} diff --git a/src/libImaging/Palette.c b/src/libImaging/Palette.c index 6b4fea6a5..2bbdb69ef 100644 --- a/src/libImaging/Palette.c +++ b/src/libImaging/Palette.c @@ -21,7 +21,7 @@ #include ImagingPalette -ImagingPaletteNew(const Mode *mode) { +ImagingPaletteNew(const ModeID mode) { /* Create a palette object */ int i; diff --git a/src/libImaging/Point.c b/src/libImaging/Point.c index e33f38508..fa0b1027c 100644 --- a/src/libImaging/Point.c +++ b/src/libImaging/Point.c @@ -128,7 +128,7 @@ im_point_32_8(Imaging imOut, Imaging imIn, im_point_context *context) { } Imaging -ImagingPoint(Imaging imIn, const Mode *mode, const void *table) { +ImagingPoint(Imaging imIn, ModeID mode, const void *table) { /* lookup table transform */ ImagingSectionCookie cookie; @@ -140,7 +140,7 @@ ImagingPoint(Imaging imIn, const Mode *mode, const void *table) { return (Imaging)ImagingError_ModeError(); } - if (!mode) { + if (mode == IMAGING_MODE_UNKNOWN) { mode = imIn->mode; } diff --git a/src/libImaging/Storage.c b/src/libImaging/Storage.c index 38142b7c5..c09062c92 100644 --- a/src/libImaging/Storage.c +++ b/src/libImaging/Storage.c @@ -42,7 +42,7 @@ */ Imaging -ImagingNewPrologueSubtype(const Mode *mode, int xsize, int ysize, int size) { +ImagingNewPrologueSubtype(const ModeID mode, int xsize, int ysize, int size) { Imaging im; /* linesize overflow check, roughly the current largest space req'd */ @@ -256,7 +256,7 @@ ImagingNewPrologueSubtype(const Mode *mode, int xsize, int ysize, int size) { } Imaging -ImagingNewPrologue(const Mode *mode, int xsize, int ysize) { +ImagingNewPrologue(const ModeID mode, int xsize, int ysize) { return ImagingNewPrologueSubtype( mode, xsize, ysize, sizeof(struct ImagingMemoryInstance) ); @@ -593,7 +593,7 @@ ImagingBorrowArrow( */ Imaging -ImagingNewInternal(const Mode *mode, int xsize, int ysize, int dirty) { +ImagingNewInternal(const ModeID mode, int xsize, int ysize, int dirty) { Imaging im; if (xsize < 0 || ysize < 0) { @@ -629,7 +629,7 @@ ImagingNewInternal(const Mode *mode, int xsize, int ysize, int dirty) { } Imaging -ImagingNew(const Mode *mode, int xsize, int ysize) { +ImagingNew(const ModeID mode, int xsize, int ysize) { if (ImagingDefaultArena.use_block_allocator) { return ImagingNewBlock(mode, xsize, ysize); } @@ -637,7 +637,7 @@ ImagingNew(const Mode *mode, int xsize, int ysize) { } Imaging -ImagingNewDirty(const Mode *mode, int xsize, int ysize) { +ImagingNewDirty(const ModeID mode, int xsize, int ysize) { if (ImagingDefaultArena.use_block_allocator) { return ImagingNewBlock(mode, xsize, ysize); } @@ -645,7 +645,7 @@ ImagingNewDirty(const Mode *mode, int xsize, int ysize) { } Imaging -ImagingNewBlock(const Mode *mode, int xsize, int ysize) { +ImagingNewBlock(const ModeID mode, int xsize, int ysize) { Imaging im; if (xsize < 0 || ysize < 0) { @@ -667,7 +667,7 @@ ImagingNewBlock(const Mode *mode, int xsize, int ysize) { Imaging ImagingNewArrow( - const Mode *mode, + const ModeID mode, int xsize, int ysize, PyObject *schema_capsule, @@ -740,7 +740,7 @@ ImagingNewArrow( } Imaging -ImagingNew2Dirty(const Mode *mode, Imaging imOut, Imaging imIn) { +ImagingNew2Dirty(const ModeID mode, Imaging imOut, Imaging imIn) { /* allocate or validate output image */ if (imOut) { diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 40e8fba50..f987c608f 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -640,7 +640,7 @@ ImagingLibTiffDecode( ); TRACE( ("Image: mode %s, type %d, bands: %d, xsize %d, ysize %d \n", - im->mode->name, + getModeData(im->mode)->name, im->type, im->bands, im->xsize, @@ -987,7 +987,7 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt ); TRACE( ("Image: mode %s, type %d, bands: %d, xsize %d, ysize %d \n", - im->mode->name, + getModeData(im->mode)->name, im->type, im->bands, im->xsize, diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index 787602151..8d4bb8619 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -1313,7 +1313,7 @@ copy4skip2(UINT8 *_out, const UINT8 *in, int pixels) { /* Unpack to "I" and "F" images */ #define UNPACK_RAW(NAME, GET, INTYPE, OUTTYPE) \ - static void NAME(UINT8 *out_, const UINT8 *in, int pixels) { \ + static void NAME(UINT8 *out, const UINT8 *in, int pixels) { \ int i; \ OUTTYPE *out = (OUTTYPE *)out_; \ for (i = 0; i < pixels; i++, in += sizeof(INTYPE)) { \ @@ -1322,7 +1322,7 @@ copy4skip2(UINT8 *_out, const UINT8 *in, int pixels) { } #define UNPACK(NAME, COPY, INTYPE, OUTTYPE) \ - static void NAME(UINT8 *out_, const UINT8 *in, int pixels) { \ + static void NAME(UINT8 *out, const UINT8 *in, int pixels) { \ int i; \ OUTTYPE *out = (OUTTYPE *)out_; \ INTYPE tmp_; \ @@ -1541,13 +1541,12 @@ band316L(UINT8 *out, const UINT8 *in, int pixels) { } } -static struct Unpacker { - const Mode *mode; - const RawMode *rawmode; +static struct { + const ModeID mode; + const RawModeID rawmode; int bits; ImagingShuffler unpack; } unpackers[] = { - /* raw mode syntax is ";" where "bits" defaults depending on mode (1 for "1", 8 for "P" and "L", etc), and "flags" should be given in alphabetical order. if both bits @@ -1560,297 +1559,294 @@ static struct Unpacker { /* exception: rawmodes "I" and "F" are always native endian byte order */ /* bilevel */ - {"1", "1", 1, unpack1}, - {"1", "1;I", 1, unpack1I}, - {"1", "1;R", 1, unpack1R}, - {"1", "1;IR", 1, unpack1IR}, - {"1", "1;8", 8, unpack18}, + {IMAGING_MODE_1, IMAGING_RAWMODE_1, 1, unpack1}, + {IMAGING_MODE_1, IMAGING_RAWMODE_1_I, 1, unpack1I}, + {IMAGING_MODE_1, IMAGING_RAWMODE_1_R, 1, unpack1R}, + {IMAGING_MODE_1, IMAGING_RAWMODE_1_IR, 1, unpack1IR}, + {IMAGING_MODE_1, IMAGING_RAWMODE_1_8, 8, unpack18}, /* grayscale */ - {"L", "L;2", 2, unpackL2}, - {"L", "L;2I", 2, unpackL2I}, - {"L", "L;2R", 2, unpackL2R}, - {"L", "L;2IR", 2, unpackL2IR}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_2, 2, unpackL2}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_2I, 2, unpackL2I}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_2R, 2, unpackL2R}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_2IR, 2, unpackL2IR}, - {"L", "L;4", 4, unpackL4}, - {"L", "L;4I", 4, unpackL4I}, - {"L", "L;4R", 4, unpackL4R}, - {"L", "L;4IR", 4, unpackL4IR}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_4, 4, unpackL4}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_4I, 4, unpackL4I}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_4R, 4, unpackL4R}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_4IR, 4, unpackL4IR}, - {"L", "L", 8, copy1}, - {"L", "L;I", 8, unpackLI}, - {"L", "L;R", 8, unpackLR}, - {"L", "L;16", 16, unpackL16}, - {"L", "L;16B", 16, unpackL16B}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L, 8, copy1}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_I, 8, unpackLI}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_R, 8, unpackLR}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_16, 16, unpackL16}, + {IMAGING_MODE_L, IMAGING_RAWMODE_L_16B, 16, unpackL16B}, /* grayscale w. alpha */ - {"LA", "LA", 16, unpackLA}, - {"LA", "LA;L", 16, unpackLAL}, + {IMAGING_MODE_LA, IMAGING_RAWMODE_LA, 16, unpackLA}, + {IMAGING_MODE_LA, IMAGING_RAWMODE_LA_L, 16, unpackLAL}, /* grayscale w. alpha premultiplied */ - {"La", "La", 16, unpackLA}, + {IMAGING_MODE_La, IMAGING_RAWMODE_La, 16, unpackLA}, /* palette */ - {"P", "P;1", 1, unpackP1}, - {"P", "P;2", 2, unpackP2}, - {"P", "P;2L", 2, unpackP2L}, - {"P", "P;4", 4, unpackP4}, - {"P", "P;4L", 4, unpackP4L}, - {"P", "P", 8, copy1}, - {"P", "P;R", 8, unpackLR}, - {"P", "L", 8, copy1}, - {"P", "PX", 16, unpackL16B}, + {IMAGING_MODE_P, IMAGING_RAWMODE_P_1, 1, unpackP1}, + {IMAGING_MODE_P, IMAGING_RAWMODE_P_2, 2, unpackP2}, + {IMAGING_MODE_P, IMAGING_RAWMODE_P_2L, 2, unpackP2L}, + {IMAGING_MODE_P, IMAGING_RAWMODE_P_4, 4, unpackP4}, + {IMAGING_MODE_P, IMAGING_RAWMODE_P_4L, 4, unpackP4L}, + {IMAGING_MODE_P, IMAGING_RAWMODE_P, 8, copy1}, + {IMAGING_MODE_P, IMAGING_RAWMODE_P_R, 8, unpackLR}, + {IMAGING_MODE_P, IMAGING_RAWMODE_L, 8, copy1}, + {IMAGING_MODE_P, IMAGING_RAWMODE_PX, 16, unpackL16B}, /* palette w. alpha */ - {"PA", "PA", 16, unpackLA}, - {"PA", "PA;L", 16, unpackLAL}, - {"PA", "LA", 16, unpackLA}, + {IMAGING_MODE_PA, IMAGING_RAWMODE_PA, 16, unpackLA}, + {IMAGING_MODE_PA, IMAGING_RAWMODE_PA_L, 16, unpackLAL}, + {IMAGING_MODE_PA, IMAGING_RAWMODE_LA, 16, unpackLA}, /* true colour */ - {"RGB", "RGB", 24, ImagingUnpackRGB}, - {"RGB", "RGB;L", 24, unpackRGBL}, - {"RGB", "RGB;R", 24, unpackRGBR}, - {"RGB", "RGB;16L", 48, unpackRGB16L}, - {"RGB", "RGB;16B", 48, unpackRGB16B}, - {"RGB", "BGR", 24, ImagingUnpackBGR}, - {"RGB", "RGB;15", 16, ImagingUnpackRGB15}, - {"RGB", "BGR;15", 16, ImagingUnpackBGR15}, - {"RGB", "RGB;16", 16, ImagingUnpackRGB16}, - {"RGB", "BGR;16", 16, ImagingUnpackBGR16}, - {"RGB", "RGBX;16L", 64, unpackRGBA16L}, - {"RGB", "RGBX;16B", 64, unpackRGBA16B}, - {"RGB", "RGB;4B", 16, ImagingUnpackRGB4B}, - {"RGB", "BGR;5", 16, ImagingUnpackBGR15}, /* compat */ - {"RGB", "RGBX", 32, copy4}, - {"RGB", "RGBX;L", 32, unpackRGBAL}, - {"RGB", "RGBXX", 40, copy4skip1}, - {"RGB", "RGBXXX", 48, copy4skip2}, - {"RGB", "RGBA;L", 32, unpackRGBAL}, - {"RGB", "RGBA;15", 16, ImagingUnpackRGBA15}, - {"RGB", "BGRX", 32, ImagingUnpackBGRX}, - {"RGB", "BGXR", 32, ImagingUnpackBGXR}, - {"RGB", "XRGB", 32, ImagingUnpackXRGB}, - {"RGB", "XBGR", 32, ImagingUnpackXBGR}, - {"RGB", "YCC;P", 24, ImagingUnpackYCC}, - {"RGB", "R", 8, band0}, - {"RGB", "G", 8, band1}, - {"RGB", "B", 8, band2}, - {"RGB", "R;16L", 16, band016L}, - {"RGB", "G;16L", 16, band116L}, - {"RGB", "B;16L", 16, band216L}, - {"RGB", "R;16B", 16, band016B}, - {"RGB", "G;16B", 16, band116B}, - {"RGB", "B;16B", 16, band216B}, - {"RGB", "CMYK", 32, cmyk2rgb}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB, 24, ImagingUnpackRGB}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_L, 24, unpackRGBL}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_R, 24, unpackRGBR}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_16L, 48, unpackRGB16L}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_16B, 48, unpackRGB16B}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGR, 24, ImagingUnpackBGR}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_15, 16, ImagingUnpackRGB15}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGR_15, 16, ImagingUnpackBGR15}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_16, 16, ImagingUnpackRGB16}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGR_16, 16, ImagingUnpackBGR16}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX_16L, 64, unpackRGBA16L}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX_16B, 64, unpackRGBA16B}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_4B, 16, ImagingUnpackRGB4B}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGR_5, 16, ImagingUnpackBGR15}, /* compat */ + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX, 32, copy4}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX_L, 32, unpackRGBAL}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBXX, 40, copy4skip1}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBXXX, 48, copy4skip2}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBA_L, 32, unpackRGBAL}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBA_15, 16, ImagingUnpackRGBA15}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGRX, 32, ImagingUnpackBGRX}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGXR, 32, ImagingUnpackBGXR}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_XRGB, 32, ImagingUnpackXRGB}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_XBGR, 32, ImagingUnpackXBGR}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_YCC_P, 24, ImagingUnpackYCC}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_R, 8, band0}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_G, 8, band1}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_B, 8, band2}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_R_16L, 16, band016L}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_G_16L, 16, band116L}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_B_16L, 16, band216L}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_R_16B, 16, band016B}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_G_16B, 16, band116B}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_B_16B, 16, band216B}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_CMYK, 32, cmyk2rgb}, /* true colour w. alpha */ - {"RGBA", "LA", 16, unpackRGBALA}, - {"RGBA", "LA;16B", 32, unpackRGBALA16B}, - {"RGBA", "RGBA", 32, copy4}, - {"RGBA", "RGBAX", 40, copy4skip1}, - {"RGBA", "RGBAXX", 48, copy4skip2}, - {"RGBA", "RGBa", 32, unpackRGBa}, - {"RGBA", "RGBaX", 40, unpackRGBaskip1}, - {"RGBA", "RGBaXX", 48, unpackRGBaskip2}, - {"RGBA", "RGBa;16L", 64, unpackRGBa16L}, - {"RGBA", "RGBa;16B", 64, unpackRGBa16B}, - {"RGBA", "BGR", 24, ImagingUnpackBGR}, - {"RGBA", "BGRa", 32, unpackBGRa}, - {"RGBA", "RGBA;I", 32, unpackRGBAI}, - {"RGBA", "RGBA;L", 32, unpackRGBAL}, - {"RGBA", "RGBA;15", 16, ImagingUnpackRGBA15}, - {"RGBA", "BGRA;15", 16, ImagingUnpackBGRA15}, - {"RGBA", "BGRA;15Z", 16, ImagingUnpackBGRA15Z}, - {"RGBA", "RGBA;4B", 16, ImagingUnpackRGBA4B}, - {"RGBA", "RGBA;16L", 64, unpackRGBA16L}, - {"RGBA", "RGBA;16B", 64, unpackRGBA16B}, - {"RGBA", "BGRA", 32, unpackBGRA}, - {"RGBA", "BGRA;16L", 64, unpackBGRA16L}, - {"RGBA", "BGRA;16B", 64, unpackBGRA16B}, - {"RGBA", "BGAR", 32, unpackBGAR}, - {"RGBA", "ARGB", 32, unpackARGB}, - {"RGBA", "ABGR", 32, unpackABGR}, - {"RGBA", "YCCA;P", 32, ImagingUnpackYCCA}, - {"RGBA", "R", 8, band0}, - {"RGBA", "G", 8, band1}, - {"RGBA", "B", 8, band2}, - {"RGBA", "A", 8, band3}, - {"RGBA", "R;16L", 16, band016L}, - {"RGBA", "G;16L", 16, band116L}, - {"RGBA", "B;16L", 16, band216L}, - {"RGBA", "A;16L", 16, band316L}, - {"RGBA", "R;16B", 16, band016B}, - {"RGBA", "G;16B", 16, band116B}, - {"RGBA", "B;16B", 16, band216B}, - {"RGBA", "A;16B", 16, band316B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_LA, 16, unpackRGBALA}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_LA_16B, 32, unpackRGBALA16B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA, 32, copy4}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBAX, 40, copy4skip1}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBAXX, 48, copy4skip2}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa, 32, unpackRGBa}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBaX, 40, unpackRGBaskip1}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBaXX, 48, unpackRGBaskip2}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa_16L, 64, unpackRGBa16L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa_16B, 64, unpackRGBa16B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGR, 24, ImagingUnpackBGR}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRa, 32, unpackBGRa}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_I, 32, unpackRGBAI}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_L, 32, unpackRGBAL}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_15, 16, ImagingUnpackRGBA15}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA_15, 16, ImagingUnpackBGRA15}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA_15Z, 16, ImagingUnpackBGRA15Z}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_4B, 16, ImagingUnpackRGBA4B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_16L, 64, unpackRGBA16L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_16B, 64, unpackRGBA16B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA, 32, unpackBGRA}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA_16L, 64, unpackBGRA16L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA_16B, 64, unpackBGRA16B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGAR, 32, unpackBGAR}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_ARGB, 32, unpackARGB}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_ABGR, 32, unpackABGR}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_YCCA_P, 32, ImagingUnpackYCCA}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R, 8, band0}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G, 8, band1}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B, 8, band2}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A, 8, band3}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R_16L, 16, band016L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G_16L, 16, band116L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B_16L, 16, band216L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A_16L, 16, band316L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R_16B, 16, band016B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G_16B, 16, band116B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B_16B, 16, band216B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A_16B, 16, band316B}, #ifdef WORDS_BIGENDIAN - {"RGB", "RGB;16N", 48, unpackRGB16B}, - {"RGB", "RGBX;16N", 64, unpackRGBA16B}, - {"RGBA", "RGBa;16N", 64, unpackRGBa16B}, - {"RGBA", "RGBA;16N", 64, unpackRGBA16B}, - {"RGBX", "RGBX;16N", 64, unpackRGBA16B}, - {"RGB", "R;16N", 16, band016B}, - {"RGB", "G;16N", 16, band116B}, - {"RGB", "B;16N", 16, band216B}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_16N, 48, unpackRGB16B}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX_16N, 64, unpackRGBA16B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa_16N, 64, unpackRGBa16B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_16N, 64, unpackRGBA16B}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_16N, 64, unpackRGBA16B}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_R_16N, 16, band016B}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_G_16N, 16, band116B}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_B_16N, 16, band216B}, - {"RGBA", "R;16N", 16, band016B}, - {"RGBA", "G;16N", 16, band116B}, - {"RGBA", "B;16N", 16, band216B}, - {"RGBA", "A;16N", 16, band316B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R_16N, 16, band016B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G_16N, 16, band116B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B_16N, 16, band216B}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A_16N, 16, band316B}, #else - {"RGB", "RGB;16N", 48, unpackRGB16L}, - {"RGB", "RGBX;16N", 64, unpackRGBA16L}, - {"RGBA", "RGBa;16N", 64, unpackRGBa16L}, - {"RGBA", "RGBA;16N", 64, unpackRGBA16L}, - {"RGBX", "RGBX;16N", 64, unpackRGBA16L}, - {"RGB", "R;16N", 16, band016L}, - {"RGB", "G;16N", 16, band116L}, - {"RGB", "B;16N", 16, band216L}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_16N, 48, unpackRGB16L}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX_16N, 64, unpackRGBA16L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa_16N, 64, unpackRGBa16L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_16N, 64, unpackRGBA16L}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_16N, 64, unpackRGBA16L}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_R_16N, 16, band016L}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_G_16N, 16, band116L}, + {IMAGING_MODE_RGB, IMAGING_RAWMODE_B_16N, 16, band216L}, - {"RGBA", "R;16N", 16, band016L}, - {"RGBA", "G;16N", 16, band116L}, - {"RGBA", "B;16N", 16, band216L}, - {"RGBA", "A;16N", 16, band316L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R_16N, 16, band016L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G_16N, 16, band116L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B_16N, 16, band216L}, + {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A_16N, 16, band316L}, #endif /* true colour w. alpha premultiplied */ - {"RGBa", "RGBa", 32, copy4}, - {"RGBa", "BGRa", 32, unpackBGRA}, - {"RGBa", "aRGB", 32, unpackARGB}, - {"RGBa", "aBGR", 32, unpackABGR}, + {IMAGING_MODE_RGBa, IMAGING_RAWMODE_RGBa, 32, copy4}, + {IMAGING_MODE_RGBa, IMAGING_RAWMODE_BGRa, 32, unpackBGRA}, + {IMAGING_MODE_RGBa, IMAGING_RAWMODE_aRGB, 32, unpackARGB}, + {IMAGING_MODE_RGBa, IMAGING_RAWMODE_aBGR, 32, unpackABGR}, /* true colour w. padding */ - {"RGBX", "RGB", 24, ImagingUnpackRGB}, - {"RGBX", "RGB;L", 24, unpackRGBL}, - {"RGBX", "RGB;16B", 48, unpackRGB16B}, - {"RGBX", "BGR", 24, ImagingUnpackBGR}, - {"RGBX", "RGB;15", 16, ImagingUnpackRGB15}, - {"RGBX", "BGR;15", 16, ImagingUnpackBGR15}, - {"RGBX", "RGB;4B", 16, ImagingUnpackRGB4B}, - {"RGBX", "BGR;5", 16, ImagingUnpackBGR15}, /* compat */ - {"RGBX", "RGBX", 32, copy4}, - {"RGBX", "RGBXX", 40, copy4skip1}, - {"RGBX", "RGBXXX", 48, copy4skip2}, - {"RGBX", "RGBX;L", 32, unpackRGBAL}, - {"RGBX", "RGBX;16L", 64, unpackRGBA16L}, - {"RGBX", "RGBX;16B", 64, unpackRGBA16B}, - {"RGBX", "BGRX", 32, ImagingUnpackBGRX}, - {"RGBX", "XRGB", 32, ImagingUnpackXRGB}, - {"RGBX", "XBGR", 32, ImagingUnpackXBGR}, - {"RGBX", "YCC;P", 24, ImagingUnpackYCC}, - {"RGBX", "R", 8, band0}, - {"RGBX", "G", 8, band1}, - {"RGBX", "B", 8, band2}, - {"RGBX", "X", 8, band3}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB, 24, ImagingUnpackRGB}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB_L, 24, unpackRGBL}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB_16B, 48, unpackRGB16B}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGR, 24, ImagingUnpackBGR}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB_15, 16, ImagingUnpackRGB15}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGR_15, 16, ImagingUnpackBGR15}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB_4B, 16, ImagingUnpackRGB4B}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGR_5, 16, ImagingUnpackBGR15}, /* compat */ + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX, 32, copy4}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBXX, 40, copy4skip1}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBXXX, 48, copy4skip2}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_L, 32, unpackRGBAL}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_16L, 64, unpackRGBA16L}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_16B, 64, unpackRGBA16B}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGRX, 32, ImagingUnpackBGRX}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_XRGB, 32, ImagingUnpackXRGB}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_XBGR, 32, ImagingUnpackXBGR}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_YCC_P, 24, ImagingUnpackYCC}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_R, 8, band0}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_G, 8, band1}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_B, 8, band2}, + {IMAGING_MODE_RGBX, IMAGING_RAWMODE_X, 8, band3}, /* colour separation */ - {"CMYK", "CMYK", 32, copy4}, - {"CMYK", "CMYKX", 40, copy4skip1}, - {"CMYK", "CMYKXX", 48, copy4skip2}, - {"CMYK", "CMYK;I", 32, unpackCMYKI}, - {"CMYK", "CMYK;L", 32, unpackRGBAL}, - {"CMYK", "CMYK;16L", 64, unpackRGBA16L}, - {"CMYK", "CMYK;16B", 64, unpackRGBA16B}, - {"CMYK", "C", 8, band0}, - {"CMYK", "M", 8, band1}, - {"CMYK", "Y", 8, band2}, - {"CMYK", "K", 8, band3}, - {"CMYK", "C;I", 8, band0I}, - {"CMYK", "M;I", 8, band1I}, - {"CMYK", "Y;I", 8, band2I}, - {"CMYK", "K;I", 8, band3I}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK, 32, copy4}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYKX, 40, copy4skip1}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYKXX, 48, copy4skip2}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_I, 32, unpackCMYKI}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_L, 32, unpackRGBAL}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_16L, 64, unpackRGBA16L}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_16B, 64, unpackRGBA16B}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_C, 8, band0}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_M, 8, band1}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_Y, 8, band2}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_K, 8, band3}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_C_I, 8, band0I}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_M_I, 8, band1I}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_Y_I, 8, band2I}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_K_I, 8, band3I}, #ifdef WORDS_BIGENDIAN - {"CMYK", "CMYK;16N", 64, unpackRGBA16B}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_16N, 64, unpackRGBA16B}, #else - {"CMYK", "CMYK;16N", 64, unpackRGBA16L}, + {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_16N, 64, unpackRGBA16L}, #endif /* video (YCbCr) */ - {"YCbCr", "YCbCr", 24, ImagingUnpackRGB}, - {"YCbCr", "YCbCr;L", 24, unpackRGBL}, - {"YCbCr", "YCbCrX", 32, copy4}, - {"YCbCr", "YCbCrK", 32, copy4}, + {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCr, 24, ImagingUnpackRGB}, + {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCr_L, 24, unpackRGBL}, + {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCrX, 32, copy4}, + {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCrK, 32, copy4}, /* LAB Color */ - {"LAB", "LAB", 24, ImagingUnpackLAB}, - {"LAB", "L", 8, band0}, - {"LAB", "A", 8, band1}, - {"LAB", "B", 8, band2}, + {IMAGING_MODE_LAB, IMAGING_RAWMODE_LAB, 24, ImagingUnpackLAB}, + {IMAGING_MODE_LAB, IMAGING_RAWMODE_L, 8, band0}, + {IMAGING_MODE_LAB, IMAGING_RAWMODE_A, 8, band1}, + {IMAGING_MODE_LAB, IMAGING_RAWMODE_B, 8, band2}, /* HSV Color */ - {"HSV", "HSV", 24, ImagingUnpackRGB}, - {"HSV", "H", 8, band0}, - {"HSV", "S", 8, band1}, - {"HSV", "V", 8, band2}, + {IMAGING_MODE_HSV, IMAGING_RAWMODE_HSV, 24, ImagingUnpackRGB}, + {IMAGING_MODE_HSV, IMAGING_RAWMODE_H, 8, band0}, + {IMAGING_MODE_HSV, IMAGING_RAWMODE_S, 8, band1}, + {IMAGING_MODE_HSV, IMAGING_RAWMODE_V, 8, band2}, /* integer variations */ - {"I", "I", 32, copy4}, - {"I", "I;8", 8, unpackI8}, - {"I", "I;8S", 8, unpackI8S}, - {"I", "I;16", 16, unpackI16}, - {"I", "I;16S", 16, unpackI16S}, - {"I", "I;16B", 16, unpackI16B}, - {"I", "I;16BS", 16, unpackI16BS}, - {"I", "I;16N", 16, unpackI16N}, - {"I", "I;16NS", 16, unpackI16NS}, - {"I", "I;32", 32, unpackI32}, - {"I", "I;32S", 32, unpackI32S}, - {"I", "I;32B", 32, unpackI32B}, - {"I", "I;32BS", 32, unpackI32BS}, - {"I", "I;32N", 32, unpackI32N}, - {"I", "I;32NS", 32, unpackI32NS}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I, 32, copy4}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_8, 8, unpackI8}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_8S, 8, unpackI8S}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_16, 16, unpackI16}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_16S, 16, unpackI16S}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_16B, 16, unpackI16B}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_16BS, 16, unpackI16BS}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_16N, 16, unpackI16N}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_16NS, 16, unpackI16NS}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_32, 32, unpackI32}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_32S, 32, unpackI32S}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_32B, 32, unpackI32B}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_32BS, 32, unpackI32BS}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_32N, 32, unpackI32N}, + {IMAGING_MODE_I, IMAGING_RAWMODE_I_32NS, 32, unpackI32NS}, /* floating point variations */ - {"F", "F", 32, copy4}, - {"F", "F;8", 8, unpackF8}, - {"F", "F;8S", 8, unpackF8S}, - {"F", "F;16", 16, unpackF16}, - {"F", "F;16S", 16, unpackF16S}, - {"F", "F;16B", 16, unpackF16B}, - {"F", "F;16BS", 16, unpackF16BS}, - {"F", "F;16N", 16, unpackF16N}, - {"F", "F;16NS", 16, unpackF16NS}, - {"F", "F;32", 32, unpackF32}, - {"F", "F;32S", 32, unpackF32S}, - {"F", "F;32B", 32, unpackF32B}, - {"F", "F;32BS", 32, unpackF32BS}, - {"F", "F;32N", 32, unpackF32N}, - {"F", "F;32NS", 32, unpackF32NS}, - {"F", "F;32F", 32, unpackF32F}, - {"F", "F;32BF", 32, unpackF32BF}, - {"F", "F;32NF", 32, unpackF32NF}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F, 32, copy4}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_8, 8, unpackF8}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_8S, 8, unpackF8S}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_16, 16, unpackF16}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_16S, 16, unpackF16S}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_16B, 16, unpackF16B}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_16BS, 16, unpackF16BS}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_16N, 16, unpackF16N}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_16NS, 16, unpackF16NS}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_32, 32, unpackF32}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_32S, 32, unpackF32S}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_32B, 32, unpackF32B}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_32BS, 32, unpackF32BS}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_32N, 32, unpackF32N}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_32NS, 32, unpackF32NS}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_32F, 32, unpackF32F}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_32BF, 32, unpackF32BF}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_32NF, 32, unpackF32NF}, #ifdef FLOAT64 - {"F", "F;64F", 64, unpackF64F}, - {"F", "F;64BF", 64, unpackF64BF}, - {"F", "F;64NF", 64, unpackF64NF}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_64F, 64, unpackF64F}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_64BF, 64, unpackF64BF}, + {IMAGING_MODE_F, IMAGING_RAWMODE_F_64NF, 64, unpackF64NF}, #endif /* storage modes */ - {"I;16", "I;16", 16, copy2}, - {"I;16B", "I;16B", 16, copy2}, - {"I;16L", "I;16L", 16, copy2}, - {"I;16N", "I;16N", 16, copy2}, + {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16, 16, copy2}, + {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16B, 16, copy2}, + {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16L, 16, copy2}, + {IMAGING_MODE_I_16N, IMAGING_RAWMODE_I_16N, 16, copy2}, - {"I;16", "I;16B", 16, unpackI16B_I16}, - {"I;16", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian. - {"I;16L", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian. - {"I;16B", "I;16N", 16, unpackI16N_I16B}, + {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16B, 16, unpackI16B_I16}, + {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16N, 16, unpackI16N_I16}, // LibTiff native->image endian. + {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16N, 16, unpackI16N_I16}, // LibTiff native->image endian. + {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16N, 16, unpackI16N_I16B}, - {"I;16", "I;16R", 16, unpackI16R_I16}, + {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16R, 16, unpackI16R_I16}, - {"I;16", "I;12", 12, unpackI12_I16}, // 12 bit Tiffs stored in 16bits. + {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_12, 12, unpackI12_I16}, // 12 bit Tiffs stored in 16bits. {NULL} /* sentinel */ }; ImagingShuffler -ImagingFindUnpacker(const Mode *mode, const RawMode *rawmode, int *bits_out) { - int i; - - /* find a suitable pixel unpacker */ - for (i = 0; unpackers[i].rawmode; i++) { +ImagingFindUnpacker(const ModeID mode, const RawModeID rawmode, int *bits_out) { + for (size_t i = 0; i < sizeof(unpackers) / sizeof(*unpackers); i++) { if (unpackers[i].mode == mode && unpackers[i].rawmode == rawmode) { if (bits_out) { *bits_out = unpackers[i].bits; @@ -1863,317 +1859,3 @@ ImagingFindUnpacker(const Mode *mode, const RawMode *rawmode, int *bits_out) { return NULL; } - -void -ImagingUnpackInit(void) { - const struct Unpacker temp[] = { - /* raw mode syntax is ";" where "bits" defaults - depending on mode (1 for "1", 8 for "P" and "L", etc), and - "flags" should be given in alphabetical order. if both bits - and flags have their default values, the ; should be left out */ - - /* flags: "I" inverted data; "R" reversed bit order; "B" big - endian byte order (default is little endian); "L" line - interleave, "S" signed, "F" floating point, "Z" inverted alpha */ - - /* exception: rawmodes "I" and "F" are always native endian byte order */ - - /* bilevel */ - {IMAGING_MODE_1, IMAGING_RAWMODE_1, 1, unpack1}, - {IMAGING_MODE_1, IMAGING_RAWMODE_1_I, 1, unpack1I}, - {IMAGING_MODE_1, IMAGING_RAWMODE_1_R, 1, unpack1R}, - {IMAGING_MODE_1, IMAGING_RAWMODE_1_IR, 1, unpack1IR}, - {IMAGING_MODE_1, IMAGING_RAWMODE_1_8, 8, unpack18}, - - /* grayscale */ - {IMAGING_MODE_L, IMAGING_RAWMODE_L_2, 2, unpackL2}, - {IMAGING_MODE_L, IMAGING_RAWMODE_L_2I, 2, unpackL2I}, - {IMAGING_MODE_L, IMAGING_RAWMODE_L_2R, 2, unpackL2R}, - {IMAGING_MODE_L, IMAGING_RAWMODE_L_2IR, 2, unpackL2IR}, - - {IMAGING_MODE_L, IMAGING_RAWMODE_L_4, 4, unpackL4}, - {IMAGING_MODE_L, IMAGING_RAWMODE_L_4I, 4, unpackL4I}, - {IMAGING_MODE_L, IMAGING_RAWMODE_L_4R, 4, unpackL4R}, - {IMAGING_MODE_L, IMAGING_RAWMODE_L_4IR, 4, unpackL4IR}, - - {IMAGING_MODE_L, IMAGING_RAWMODE_L, 8, copy1}, - {IMAGING_MODE_L, IMAGING_RAWMODE_L_I, 8, unpackLI}, - {IMAGING_MODE_L, IMAGING_RAWMODE_L_R, 8, unpackLR}, - {IMAGING_MODE_L, IMAGING_RAWMODE_L_16, 16, unpackL16}, - {IMAGING_MODE_L, IMAGING_RAWMODE_L_16B, 16, unpackL16B}, - - /* grayscale w. alpha */ - {IMAGING_MODE_LA, IMAGING_RAWMODE_LA, 16, unpackLA}, - {IMAGING_MODE_LA, IMAGING_RAWMODE_LA_L, 16, unpackLAL}, - - /* grayscale w. alpha premultiplied */ - {IMAGING_MODE_La, IMAGING_RAWMODE_La, 16, unpackLA}, - - /* palette */ - {IMAGING_MODE_P, IMAGING_RAWMODE_P_1, 1, unpackP1}, - {IMAGING_MODE_P, IMAGING_RAWMODE_P_2, 2, unpackP2}, - {IMAGING_MODE_P, IMAGING_RAWMODE_P_2L, 2, unpackP2L}, - {IMAGING_MODE_P, IMAGING_RAWMODE_P_4, 4, unpackP4}, - {IMAGING_MODE_P, IMAGING_RAWMODE_P_4L, 4, unpackP4L}, - {IMAGING_MODE_P, IMAGING_RAWMODE_P, 8, copy1}, - {IMAGING_MODE_P, IMAGING_RAWMODE_P_R, 8, unpackLR}, - {IMAGING_MODE_P, IMAGING_RAWMODE_L, 8, copy1}, - {IMAGING_MODE_P, IMAGING_RAWMODE_PX, 16, unpackL16B}, - - /* palette w. alpha */ - {IMAGING_MODE_PA, IMAGING_RAWMODE_PA, 16, unpackLA}, - {IMAGING_MODE_PA, IMAGING_RAWMODE_PA_L, 16, unpackLAL}, - {IMAGING_MODE_PA, IMAGING_RAWMODE_LA, 16, unpackLA}, - - /* true colour */ - {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB, 24, ImagingUnpackRGB}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_L, 24, unpackRGBL}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_R, 24, unpackRGBR}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_16L, 48, unpackRGB16L}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_16B, 48, unpackRGB16B}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGR, 24, ImagingUnpackBGR}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_15, 16, ImagingUnpackRGB15}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGR_15, 16, ImagingUnpackBGR15}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_16, 16, ImagingUnpackRGB16}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGR_16, 16, ImagingUnpackBGR16}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX_16L, 64, unpackRGBA16L}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX_16B, 64, unpackRGBA16B}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_4B, 16, ImagingUnpackRGB4B}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGR_5, 16, ImagingUnpackBGR15}, /* compat */ - {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX, 32, copy4}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX_L, 32, unpackRGBAL}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBXX, 40, copy4skip1}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBXXX, 48, copy4skip2}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBA_L, 32, unpackRGBAL}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBA_15, 16, ImagingUnpackRGBA15}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGRX, 32, ImagingUnpackBGRX}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGXR, 32, ImagingUnpackBGXR}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_XRGB, 32, ImagingUnpackXRGB}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_XBGR, 32, ImagingUnpackXBGR}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_YCC_P, 24, ImagingUnpackYCC}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_R, 8, band0}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_G, 8, band1}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_B, 8, band2}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_R_16L, 16, band016L}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_G_16L, 16, band116L}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_B_16L, 16, band216L}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_R_16B, 16, band016B}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_G_16B, 16, band116B}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_B_16B, 16, band216B}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_CMYK, 32, cmyk2rgb}, - - {IMAGING_MODE_BGR_15, IMAGING_RAWMODE_BGR_15, 16, copy2}, - {IMAGING_MODE_BGR_16, IMAGING_RAWMODE_BGR_16, 16, copy2}, - {IMAGING_MODE_BGR_24, IMAGING_RAWMODE_BGR_24, 24, copy3}, - - /* true colour w. alpha */ - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_LA, 16, unpackRGBALA}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_LA_16B, 32, unpackRGBALA16B}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA, 32, copy4}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBAX, 40, copy4skip1}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBAXX, 48, copy4skip2}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa, 32, unpackRGBa}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBaX, 40, unpackRGBaskip1}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBaXX, 48, unpackRGBaskip2}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa_16L, 64, unpackRGBa16L}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa_16B, 64, unpackRGBa16B}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRa, 32, unpackBGRa}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_I, 32, unpackRGBAI}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_L, 32, unpackRGBAL}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_15, 16, ImagingUnpackRGBA15}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA_15, 16, ImagingUnpackBGRA15}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA_15Z, 16, ImagingUnpackBGRA15Z}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_4B, 16, ImagingUnpackRGBA4B}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_16L, 64, unpackRGBA16L}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_16B, 64, unpackRGBA16B}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA, 32, unpackBGRA}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA_16L, 64, unpackBGRA16L}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA_16B, 64, unpackBGRA16B}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGAR, 32, unpackBGAR}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_ARGB, 32, unpackARGB}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_ABGR, 32, unpackABGR}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_YCCA_P, 32, ImagingUnpackYCCA}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R, 8, band0}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G, 8, band1}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B, 8, band2}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A, 8, band3}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R_16L, 16, band016L}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G_16L, 16, band116L}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B_16L, 16, band216L}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A_16L, 16, band316L}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R_16B, 16, band016B}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G_16B, 16, band116B}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B_16B, 16, band216B}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A_16B, 16, band316B}, - -#ifdef WORDS_BIGENDIAN - {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_16N, 48, unpackRGB16B}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa_16N, 64, unpackRGBa16B}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_16N, 64, unpackRGBA16B}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_16N, 64, unpackRGBA16B}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_R_16N, 16, band016B}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_G_16N, 16, band116B}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_B_16N, 16, band216B}, - - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R_16N, 16, band016B}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G_16N, 16, band116B}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B_16N, 16, band216B}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A_16N, 16, band316B}, -#else - {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_16N, 48, unpackRGB16L}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa_16N, 64, unpackRGBa16L}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_16N, 64, unpackRGBA16L}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_16N, 64, unpackRGBA16L}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_R_16N, 16, band016L}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_G_16N, 16, band116L}, - {IMAGING_MODE_RGB, IMAGING_RAWMODE_B_16N, 16, band216L}, - - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R_16N, 16, band016L}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G_16N, 16, band116L}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B_16N, 16, band216L}, - {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A_16N, 16, band316L}, -#endif - - /* true colour w. alpha premultiplied */ - {IMAGING_MODE_RGBa, IMAGING_RAWMODE_RGBa, 32, copy4}, - {IMAGING_MODE_RGBa, IMAGING_RAWMODE_BGRa, 32, unpackBGRA}, - {IMAGING_MODE_RGBa, IMAGING_RAWMODE_aRGB, 32, unpackARGB}, - {IMAGING_MODE_RGBa, IMAGING_RAWMODE_aBGR, 32, unpackABGR}, - - /* true colour w. padding */ - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB, 24, ImagingUnpackRGB}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB_L, 24, unpackRGBL}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB_16B, 48, unpackRGB16B}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGR, 24, ImagingUnpackBGR}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB_15, 16, ImagingUnpackRGB15}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGR_15, 16, ImagingUnpackBGR15}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB_4B, 16, ImagingUnpackRGB4B}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGR_5, 16, ImagingUnpackBGR15}, /* compat */ - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX, 32, copy4}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBXX, 40, copy4skip1}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBXXX, 48, copy4skip2}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_L, 32, unpackRGBAL}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_16L, 64, unpackRGBA16L}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_16B, 64, unpackRGBA16B}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGRX, 32, ImagingUnpackBGRX}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_XRGB, 32, ImagingUnpackXRGB}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_XBGR, 32, ImagingUnpackXBGR}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_YCC_P, 24, ImagingUnpackYCC}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_R, 8, band0}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_G, 8, band1}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_B, 8, band2}, - {IMAGING_MODE_RGBX, IMAGING_RAWMODE_X, 8, band3}, - - /* colour separation */ - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK, 32, copy4}, - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYKX, 40, copy4skip1}, - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYKXX, 48, copy4skip2}, - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_I, 32, unpackCMYKI}, - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_L, 32, unpackRGBAL}, - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_16L, 64, unpackRGBA16L}, - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_16B, 64, unpackRGBA16B}, - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_C, 8, band0}, - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_M, 8, band1}, - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_Y, 8, band2}, - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_K, 8, band3}, - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_C_I, 8, band0I}, - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_M_I, 8, band1I}, - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_Y_I, 8, band2I}, - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_K_I, 8, band3I}, - -#ifdef WORDS_BIGENDIAN - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_16N, 64, unpackRGBA16B}, -#else - {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_16N, 64, unpackRGBA16L}, -#endif - - /* video (YCbCr) */ - {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCr, 24, ImagingUnpackRGB}, - {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCr_L, 24, unpackRGBL}, - {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCrX, 32, copy4}, - {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCrK, 32, copy4}, - - /* LAB Color */ - {IMAGING_MODE_LAB, IMAGING_RAWMODE_LAB, 24, ImagingUnpackLAB}, - {IMAGING_MODE_LAB, IMAGING_RAWMODE_L, 8, band0}, - {IMAGING_MODE_LAB, IMAGING_RAWMODE_A, 8, band1}, - {IMAGING_MODE_LAB, IMAGING_RAWMODE_B, 8, band2}, - - /* HSV Color */ - {IMAGING_MODE_HSV, IMAGING_RAWMODE_HSV, 24, ImagingUnpackRGB}, - {IMAGING_MODE_HSV, IMAGING_RAWMODE_H, 8, band0}, - {IMAGING_MODE_HSV, IMAGING_RAWMODE_S, 8, band1}, - {IMAGING_MODE_HSV, IMAGING_RAWMODE_V, 8, band2}, - - /* integer variations */ - {IMAGING_MODE_I, IMAGING_RAWMODE_I, 32, copy4}, - {IMAGING_MODE_I, IMAGING_RAWMODE_I_8, 8, unpackI8}, - {IMAGING_MODE_I, IMAGING_RAWMODE_I_8S, 8, unpackI8S}, - {IMAGING_MODE_I, IMAGING_RAWMODE_I_16, 16, unpackI16}, - {IMAGING_MODE_I, IMAGING_RAWMODE_I_16S, 16, unpackI16S}, - {IMAGING_MODE_I, IMAGING_RAWMODE_I_16B, 16, unpackI16B}, - {IMAGING_MODE_I, IMAGING_RAWMODE_I_16BS, 16, unpackI16BS}, - {IMAGING_MODE_I, IMAGING_RAWMODE_I_16N, 16, unpackI16N}, - {IMAGING_MODE_I, IMAGING_RAWMODE_I_16NS, 16, unpackI16NS}, - {IMAGING_MODE_I, IMAGING_RAWMODE_I_32, 32, unpackI32}, - {IMAGING_MODE_I, IMAGING_RAWMODE_I_32S, 32, unpackI32S}, - {IMAGING_MODE_I, IMAGING_RAWMODE_I_32B, 32, unpackI32B}, - {IMAGING_MODE_I, IMAGING_RAWMODE_I_32BS, 32, unpackI32BS}, - {IMAGING_MODE_I, IMAGING_RAWMODE_I_32N, 32, unpackI32N}, - {IMAGING_MODE_I, IMAGING_RAWMODE_I_32NS, 32, unpackI32NS}, - - /* floating point variations */ - {IMAGING_MODE_F, IMAGING_RAWMODE_F, 32, copy4}, - {IMAGING_MODE_F, IMAGING_RAWMODE_F_8, 8, unpackF8}, - {IMAGING_MODE_F, IMAGING_RAWMODE_F_8S, 8, unpackF8S}, - {IMAGING_MODE_F, IMAGING_RAWMODE_F_16, 16, unpackF16}, - {IMAGING_MODE_F, IMAGING_RAWMODE_F_16S, 16, unpackF16S}, - {IMAGING_MODE_F, IMAGING_RAWMODE_F_16B, 16, unpackF16B}, - {IMAGING_MODE_F, IMAGING_RAWMODE_F_16BS, 16, unpackF16BS}, - {IMAGING_MODE_F, IMAGING_RAWMODE_F_16N, 16, unpackF16N}, - {IMAGING_MODE_F, IMAGING_RAWMODE_F_16NS, 16, unpackF16NS}, - {IMAGING_MODE_F, IMAGING_RAWMODE_F_32, 32, unpackF32}, - {IMAGING_MODE_F, IMAGING_RAWMODE_F_32S, 32, unpackF32S}, - {IMAGING_MODE_F, IMAGING_RAWMODE_F_32B, 32, unpackF32B}, - {IMAGING_MODE_F, IMAGING_RAWMODE_F_32BS, 32, unpackF32BS}, - {IMAGING_MODE_F, IMAGING_RAWMODE_F_32N, 32, unpackF32N}, - {IMAGING_MODE_F, IMAGING_RAWMODE_F_32NS, 32, unpackF32NS}, - {IMAGING_MODE_F, IMAGING_RAWMODE_F_32F, 32, unpackF32F}, - {IMAGING_MODE_F, IMAGING_RAWMODE_F_32BF, 32, unpackF32BF}, - {IMAGING_MODE_F, IMAGING_RAWMODE_F_32NF, 32, unpackF32NF}, -#ifdef FLOAT64 - {IMAGING_MODE_F, IMAGING_RAWMODE_F_64F, 64, unpackF64F}, - {IMAGING_MODE_F, IMAGING_RAWMODE_F_64BF, 64, unpackF64BF}, - {IMAGING_MODE_F, IMAGING_RAWMODE_F_64NF, 64, unpackF64NF}, -#endif - - /* storage modes */ - {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16, 16, copy2}, - {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16B, 16, copy2}, - {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16L, 16, copy2}, - {IMAGING_MODE_I_16N, IMAGING_RAWMODE_I_16N, 16, copy2}, - - {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16B, 16, unpackI16B_I16}, - {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16N, 16, unpackI16N_I16}, // LibTiff native->image endian. - {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16N, 16, unpackI16N_I16}, // LibTiff native->image endian. - {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16N, 16, unpackI16N_I16B}, - - {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16R, 16, unpackI16R_I16}, - - {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_12, 12, unpackI12_I16}, // 12 bit Tiffs stored in 16bits. - - {NULL} /* sentinel */ - }; - unpackers = malloc(sizeof(temp)); - if (unpackers == NULL) { - fprintf(stderr, "UnpackInit: failed to allocate memory for unpackers table\n"); - exit(1); - } - memcpy(unpackers, temp, sizeof(temp)); -} - -void -ImagingUnpackFree(void) { - free(unpackers); - unpackers = NULL; -} diff --git a/src/map.c b/src/map.c index 451cca589..6f66b0cc5 100644 --- a/src/map.c +++ b/src/map.c @@ -82,7 +82,7 @@ PyImaging_MapBuffer(PyObject *self, PyObject *args) { return NULL; } - const Mode * const mode = findMode(mode_name); + const ModeID mode = findModeID(mode_name); if (stride <= 0) { if (mode == IMAGING_MODE_L || mode == IMAGING_MODE_P) { From 4d721bc5913986b31f451decb03f094c9f21a908 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Tue, 23 Apr 2024 12:41:49 -0500 Subject: [PATCH 097/309] use mode enums in _webp.c --- src/_webp.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/_webp.c b/src/_webp.c index e84e786ed..d065e329c 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -89,8 +89,8 @@ HandleMuxError(WebPMuxError err, char *chunk) { static int import_frame_libwebp(WebPPicture *frame, Imaging im) { - if (strcmp(im->mode, "RGBA") && strcmp(im->mode, "RGB") && - strcmp(im->mode, "RGBX")) { + if (im->mode != IMAGING_MODE_RGBA && im->mode != IMAGING_MODE_RGB && + im->mode != IMAGING_MODE_RGBX) { PyErr_SetString(PyExc_ValueError, "unsupported image mode"); return -1; } @@ -104,7 +104,7 @@ import_frame_libwebp(WebPPicture *frame, Imaging im) { return -2; } - int ignore_fourth_channel = strcmp(im->mode, "RGBA"); + int ignore_fourth_channel = im->mode != IMAGING_MODE_RGBA; for (int y = 0; y < im->ysize; ++y) { UINT8 *src = (UINT8 *)im->image32[y]; UINT32 *dst = frame->argb + frame->argb_stride * y; @@ -143,7 +143,7 @@ typedef struct { PyObject_HEAD WebPAnimDecoder *dec; WebPAnimInfo info; WebPData data; - char *mode; + ModeID mode; } WebPAnimDecoderObject; static PyTypeObject WebPAnimDecoder_Type; @@ -396,7 +396,7 @@ _anim_decoder_new(PyObject *self, PyObject *args) { const uint8_t *webp; Py_ssize_t size; WebPData webp_src; - char *mode; + ModeID mode; WebPDecoderConfig config; WebPAnimDecoderObject *decp = NULL; WebPAnimDecoder *dec = NULL; @@ -409,10 +409,10 @@ _anim_decoder_new(PyObject *self, PyObject *args) { webp_src.size = size; // Sniff the mode, since the decoder API doesn't tell us - mode = "RGBA"; + mode = IMAGING_MODE_RGBA; if (WebPGetFeatures(webp, size, &config.input) == VP8_STATUS_OK) { if (!config.input.has_alpha) { - mode = "RGBX"; + mode = IMAGING_MODE_RGBX; } } @@ -455,7 +455,7 @@ _anim_decoder_get_info(PyObject *self) { info->loop_count, info->bgcolor, info->frame_count, - decp->mode + getModeData(decp->mode)->name ); } From 85212dbbb6400255a0522a99ce7fa18a40ea3f81 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 19 Jul 2025 16:55:52 +0200 Subject: [PATCH 098/309] Add image band metadata for the 4 channel images --- Tests/test_pyarrow.py | 27 +++++++++++++ src/libImaging/Arrow.c | 86 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 112 insertions(+), 1 deletion(-) diff --git a/Tests/test_pyarrow.py b/Tests/test_pyarrow.py index 8dad94fe0..a69504e78 100644 --- a/Tests/test_pyarrow.py +++ b/Tests/test_pyarrow.py @@ -1,5 +1,6 @@ from __future__ import annotations +import json from typing import Any, NamedTuple import pytest @@ -244,3 +245,29 @@ def test_from_int32array(mode: str, data_tp: DataShape, mask: list[int] | None) img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE) _test_img_equals_int32_pyarray(img, arr, mask, elts_per_pixel) + + +@pytest.mark.parametrize( + "mode, metadata", + ( + ("LA", ["L", "X", "X", "A"]), + ("RGB", ["R", "G", "B", "X"]), + ("RGBX", ["R", "G", "B", "X"]), + ("RGBA", ["R", "G", "B", "A"]), + ("CMYK", ["C", "M", "Y", "K"]), + ("YCbCr", ["Y", "Cb", "Cr", "X"]), + ("HSV", ["H", "S", "V", "X"]), + ), +) +def test_image_metadata(mode: str, metadata: list[str]) -> None: + img = hopper(mode) + + arr = pyarrow.array(img) # type: ignore[call-overload] + + assert arr.type.field(0).metadata + assert arr.type.field(0).metadata[b"image"] + + parsed_metadata = json.loads(arr.type.field(0).metadata[b"image"].decode("utf8")) + + assert "bands" in parsed_metadata + assert parsed_metadata["bands"] == metadata diff --git a/src/libImaging/Arrow.c b/src/libImaging/Arrow.c index ccafe33b9..2ecec9b29 100644 --- a/src/libImaging/Arrow.c +++ b/src/libImaging/Arrow.c @@ -55,6 +55,77 @@ ReleaseExportedSchema(struct ArrowSchema *array) { // Mark array released array->release = NULL; } +char * +image_band_json(Imaging im) { + char *format = "{\"bands\": [\"%s\", \"%s\", \"%s\", \"%s\"]}"; + char *json; + // Bands can be 4 bands * 2 characters each + int len = strlen(format) + 8 + 1; + int err; + + json = calloc(1, len); + + if (!json) { + return NULL; + } + + err = PyOS_snprintf( + json, + len, + format, + im->band_names[0], + im->band_names[1], + im->band_names[2], + im->band_names[3] + ); + if (err < 0) { + return NULL; + } + return json; +} + +char * +assemble_metadata(const char *band_json) { + /* format is + int32: number of key/value pairs (noted N below) + int32: byte length of key 0 + key 0 (not null-terminated) + int32: byte length of value 0 + value 0 (not null-terminated) + ... + int32: byte length of key N - 1 + key N - 1 (not null-terminated) + int32: byte length of value N - 1 + value N - 1 (not null-terminated) + */ + const char *key = "image"; + INT32 key_len = strlen(key); + INT32 band_json_len = strlen(band_json); + + char *buf; + INT32 *dest_int; + char *dest; + + buf = calloc(1, key_len + band_json_len + 4 + 1 * 8); + if (!buf) { + return NULL; + } + + dest_int = (void *)buf; + + dest_int[0] = 1; + dest_int[1] = key_len; + dest_int += 2; + dest = (void *)dest_int; + memcpy(dest, key, key_len); + dest += key_len; + dest_int = (void *)dest; + dest_int[0] = band_json_len; + dest_int += 1; + memcpy(dest_int, band_json, band_json_len); + + return buf; +} int export_named_type(struct ArrowSchema *schema, char *format, char *name) { @@ -95,6 +166,8 @@ export_named_type(struct ArrowSchema *schema, char *format, char *name) { int export_imaging_schema(Imaging im, struct ArrowSchema *schema) { int retval = 0; + char *metadata; + char *band_json; if (strcmp(im->arrow_band_format, "") == 0) { return IMAGING_ARROW_INCOMPATIBLE_MODE; @@ -117,13 +190,24 @@ export_imaging_schema(Imaging im, struct ArrowSchema *schema) { schema->n_children = 1; schema->children = calloc(1, sizeof(struct ArrowSchema *)); schema->children[0] = (struct ArrowSchema *)calloc(1, sizeof(struct ArrowSchema)); - retval = export_named_type(schema->children[0], im->arrow_band_format, "pixel"); + retval = export_named_type(schema->children[0], im->arrow_band_format, im->mode); if (retval != 0) { free(schema->children[0]); free(schema->children); schema->release(schema); return retval; } + + // band related metadata + band_json = image_band_json(im); + if (band_json) { + // adding the metadata to the child array. + // Accessible in pyarrow via pa.array(img).type.field(0).metadata + // adding it to the top level is not accessible. + schema->children[0]->metadata = assemble_metadata(band_json); + free(band_json); + } + return 0; } From aa39e84f7a465c91035d5ea0a3d522faf9f9159d Mon Sep 17 00:00:00 2001 From: eyedav <88885346+eyedav@users.noreply.github.com> Date: Sat, 19 Jul 2025 16:58:08 +0200 Subject: [PATCH 099/309] use mode enums in Jpeg2KDecode.c --- src/libImaging/Jpeg2KDecode.c | 44 +++++++++++++++++------------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/libImaging/Jpeg2KDecode.c b/src/libImaging/Jpeg2KDecode.c index 67f705ddd..1b496f45e 100644 --- a/src/libImaging/Jpeg2KDecode.c +++ b/src/libImaging/Jpeg2KDecode.c @@ -71,7 +71,7 @@ typedef void (*j2k_unpacker_t)( ); struct j2k_decode_unpacker { - const char *mode; + const ModeID mode; OPJ_COLOR_SPACE color_space; unsigned components; /* bool indicating if unpacker supports subsampling */ @@ -599,26 +599,26 @@ j2ku_sycca_rgba( } static const struct j2k_decode_unpacker j2k_unpackers[] = { - {"L", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_l}, - {"P", OPJ_CLRSPC_SRGB, 1, 0, j2ku_gray_l}, - {"PA", OPJ_CLRSPC_SRGB, 2, 0, j2ku_graya_la}, - {"I;16", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i}, - {"I;16B", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i}, - {"LA", OPJ_CLRSPC_GRAY, 2, 0, j2ku_graya_la}, - {"RGB", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_rgb}, - {"RGB", OPJ_CLRSPC_GRAY, 2, 0, j2ku_gray_rgb}, - {"RGB", OPJ_CLRSPC_SRGB, 3, 1, j2ku_srgb_rgb}, - {"RGB", OPJ_CLRSPC_SYCC, 3, 1, j2ku_sycc_rgb}, - {"RGB", OPJ_CLRSPC_SRGB, 4, 1, j2ku_srgb_rgb}, - {"RGB", OPJ_CLRSPC_SYCC, 4, 1, j2ku_sycc_rgb}, - {"RGBA", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_rgb}, - {"RGBA", OPJ_CLRSPC_GRAY, 2, 0, j2ku_graya_la}, - {"RGBA", OPJ_CLRSPC_SRGB, 3, 1, j2ku_srgb_rgb}, - {"RGBA", OPJ_CLRSPC_SYCC, 3, 1, j2ku_sycc_rgb}, - {"RGBA", OPJ_CLRSPC_GRAY, 4, 1, j2ku_srgba_rgba}, - {"RGBA", OPJ_CLRSPC_SRGB, 4, 1, j2ku_srgba_rgba}, - {"RGBA", OPJ_CLRSPC_SYCC, 4, 1, j2ku_sycca_rgba}, - {"CMYK", OPJ_CLRSPC_CMYK, 4, 1, j2ku_srgba_rgba}, + {IMAGING_MODE_L, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_l}, + {IMAGING_MODE_P, OPJ_CLRSPC_SRGB, 1, 0, j2ku_gray_l}, + {IMAGING_MODE_PA, OPJ_CLRSPC_SRGB, 2, 0, j2ku_graya_la}, + {IMAGING_MODE_I_16, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i}, + {IMAGING_MODE_I_16B, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i}, + {IMAGING_MODE_LA, OPJ_CLRSPC_GRAY, 2, 0, j2ku_graya_la}, + {IMAGING_MODE_RGB, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_rgb}, + {IMAGING_MODE_RGB, OPJ_CLRSPC_GRAY, 2, 0, j2ku_gray_rgb}, + {IMAGING_MODE_RGB, OPJ_CLRSPC_SRGB, 3, 1, j2ku_srgb_rgb}, + {IMAGING_MODE_RGB, OPJ_CLRSPC_SYCC, 3, 1, j2ku_sycc_rgb}, + {IMAGING_MODE_RGB, OPJ_CLRSPC_SRGB, 4, 1, j2ku_srgb_rgb}, + {IMAGING_MODE_RGB, OPJ_CLRSPC_SYCC, 4, 1, j2ku_sycc_rgb}, + {IMAGING_MODE_RGBA, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_rgb}, + {IMAGING_MODE_RGBA, OPJ_CLRSPC_GRAY, 2, 0, j2ku_graya_la}, + {IMAGING_MODE_RGBA, OPJ_CLRSPC_SRGB, 3, 1, j2ku_srgb_rgb}, + {IMAGING_MODE_RGBA, OPJ_CLRSPC_SYCC, 3, 1, j2ku_sycc_rgb}, + {IMAGING_MODE_RGBA, OPJ_CLRSPC_GRAY, 4, 1, j2ku_srgba_rgba}, + {IMAGING_MODE_RGBA, OPJ_CLRSPC_SRGB, 4, 1, j2ku_srgba_rgba}, + {IMAGING_MODE_RGBA, OPJ_CLRSPC_SYCC, 4, 1, j2ku_sycca_rgba}, + {IMAGING_MODE_CMYK, OPJ_CLRSPC_CMYK, 4, 1, j2ku_srgba_rgba}, }; /* -------------------------------------------------------------------- */ @@ -771,7 +771,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) { if (color_space == j2k_unpackers[n].color_space && image->numcomps == j2k_unpackers[n].components && (j2k_unpackers[n].subsampling || (subsampling == -1)) && - strcmp(getModeData(im->mode)->name, j2k_unpackers[n].mode) == 0) { + im->mode == j2k_unpackers[n].mode) { unpack = j2k_unpackers[n].unpacker; break; } From a53f83f023d3a4a166cc1107cb16aa5f0cc0f2c9 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Tue, 23 Apr 2024 13:13:19 -0500 Subject: [PATCH 100/309] use mode enums in _imagingft.c --- src/_imagingft.c | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index 29d8e9e71..a38ea507a 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -525,7 +525,7 @@ font_getlength(FontObject *self, PyObject *args) { int horizontal_dir; /* is primary axis horizontal? */ int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */ int color = 0; /* is FT_LOAD_COLOR enabled? */ - const char *mode = NULL; + const char *mode_name = NULL; const char *dir = NULL; const char *lang = NULL; PyObject *features = Py_None; @@ -534,15 +534,16 @@ font_getlength(FontObject *self, PyObject *args) { /* calculate size and bearing for a given string */ if (!PyArg_ParseTuple( - args, "O|zzOz:getlength", &string, &mode, &dir, &features, &lang + args, "O|zzOz:getlength", &string, &mode_name, &dir, &features, &lang )) { return NULL; } horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1; - mask = mode && strcmp(mode, "1") == 0; - color = mode && strcmp(mode, "RGBA") == 0; + const ModeID mode = findModeID(mode_name); + mask = mode == IMAGING_MODE_1; + color = mode == IMAGING_MODE_RGBA; count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color); if (PyErr_Occurred()) { @@ -754,7 +755,7 @@ font_getsize(FontObject *self, PyObject *args) { int horizontal_dir; /* is primary axis horizontal? */ int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */ int color = 0; /* is FT_LOAD_COLOR enabled? */ - const char *mode = NULL; + const char *mode_name = NULL; const char *dir = NULL; const char *lang = NULL; const char *anchor = NULL; @@ -764,15 +765,23 @@ font_getsize(FontObject *self, PyObject *args) { /* calculate size and bearing for a given string */ if (!PyArg_ParseTuple( - args, "O|zzOzz:getsize", &string, &mode, &dir, &features, &lang, &anchor + args, + "O|zzOzz:getsize", + &string, + &mode_name, + &dir, + &features, + &lang, + &anchor )) { return NULL; } horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1; - mask = mode && strcmp(mode, "1") == 0; - color = mode && strcmp(mode, "RGBA") == 0; + const ModeID mode = findModeID(mode_name); + mask = mode == IMAGING_MODE_1; + color = mode == IMAGING_MODE_RGBA; count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color); if (PyErr_Occurred()) { @@ -839,7 +848,7 @@ font_render(FontObject *self, PyObject *args) { int stroke_filled = 0; PY_LONG_LONG foreground_ink_long = 0; unsigned int foreground_ink; - const char *mode = NULL; + const char *mode_name = NULL; const char *dir = NULL; const char *lang = NULL; const char *anchor = NULL; @@ -859,7 +868,7 @@ font_render(FontObject *self, PyObject *args) { "OO|zzOzfpzL(ff):render", &string, &fill, - &mode, + &mode_name, &dir, &features, &lang, @@ -873,8 +882,9 @@ font_render(FontObject *self, PyObject *args) { return NULL; } - mask = mode && strcmp(mode, "1") == 0; - color = mode && strcmp(mode, "RGBA") == 0; + const ModeID mode = findModeID(mode_name); + mask = mode == IMAGING_MODE_1; + color = mode == IMAGING_MODE_RGBA; foreground_ink = foreground_ink_long; From f8bfa2fe4e78fc5ea32844a0bd2ded59d2ef398d Mon Sep 17 00:00:00 2001 From: Yay295 Date: Tue, 23 Apr 2024 13:19:49 -0500 Subject: [PATCH 101/309] use more mode enums in decode.c --- src/decode.c | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/decode.c b/src/decode.c index 41b2f6f31..f95637fa6 100644 --- a/src/decode.c +++ b/src/decode.c @@ -291,17 +291,18 @@ PyObject * PyImaging_BitDecoderNew(PyObject *self, PyObject *args) { ImagingDecoderObject *decoder; - char *mode; + const char *mode_name; int bits = 8; int pad = 8; int fill = 0; int sign = 0; int ystep = 1; - if (!PyArg_ParseTuple(args, "s|iiiii", &mode, &bits, &pad, &fill, &sign, &ystep)) { + if (!PyArg_ParseTuple(args, "s|iiiii", &mode_name, &bits, &pad, &fill, &sign, &ystep)) { return NULL; } - if (strcmp(mode, "F") != 0) { + const ModeID mode = findModeID(mode_name); + if (mode != IMAGING_MODE_F) { PyErr_SetString(PyExc_ValueError, "bad image mode"); return NULL; } @@ -331,34 +332,36 @@ PyObject * PyImaging_BcnDecoderNew(PyObject *self, PyObject *args) { ImagingDecoderObject *decoder; - char *mode; - char *actual; + char *mode_name; int n = 0; char *pixel_format = ""; - if (!PyArg_ParseTuple(args, "si|s", &mode, &n, &pixel_format)) { + if (!PyArg_ParseTuple(args, "si|s", &mode_name, &n, &pixel_format)) { return NULL; } + const ModeID mode = findModeID(mode_name); + ModeID actual; + switch (n) { case 1: /* BC1: 565 color, 1-bit alpha */ case 2: /* BC2: 565 color, 4-bit alpha */ case 3: /* BC3: 565 color, 2-endpoint 8-bit interpolated alpha */ case 7: /* BC7: 4-channel 8-bit via everything */ - actual = "RGBA"; + actual = IMAGING_MODE_RGBA; break; case 4: /* BC4: 1-channel 8-bit via 1 BC3 alpha block */ - actual = "L"; + actual = IMAGING_MODE_L; break; case 5: /* BC5: 2-channel 8-bit via 2 BC3 alpha blocks */ case 6: /* BC6: 3-channel 16-bit float */ - actual = "RGB"; + actual = IMAGING_MODE_RGB; break; default: PyErr_SetString(PyExc_ValueError, "block compression type unknown"); return NULL; } - if (strcmp(mode, actual) != 0) { + if (mode != actual) { PyErr_SetString(PyExc_ValueError, "bad image mode"); return NULL; } @@ -401,15 +404,16 @@ PyObject * PyImaging_GifDecoderNew(PyObject *self, PyObject *args) { ImagingDecoderObject *decoder; - char *mode; + const char *mode_name; int bits = 8; int interlace = 0; int transparency = -1; - if (!PyArg_ParseTuple(args, "s|iii", &mode, &bits, &interlace, &transparency)) { + if (!PyArg_ParseTuple(args, "s|iii", &mode_name, &bits, &interlace, &transparency)) { return NULL; } - if (strcmp(mode, "L") != 0 && strcmp(mode, "P") != 0) { + const ModeID mode = findModeID(mode_name); + if (mode != IMAGING_MODE_L && mode != IMAGING_MODE_P) { PyErr_SetString(PyExc_ValueError, "bad image mode"); return NULL; } From 47503477d49922c085d6062035f2a7da68b3fd2d Mon Sep 17 00:00:00 2001 From: eyedav <88885346+eyedav@users.noreply.github.com> Date: Sat, 19 Jul 2025 17:00:35 +0200 Subject: [PATCH 102/309] add Mode.c as a dependency for _imagingft.c and _webp.c --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 93b5bcc78..b5769b191 100644 --- a/setup.py +++ b/setup.py @@ -1080,9 +1080,9 @@ for src_file in _LIB_IMAGING: files.append(os.path.join("src/libImaging", src_file + ".c")) ext_modules = [ Extension("PIL._imaging", files), - Extension("PIL._imagingft", ["src/_imagingft.c"]), + Extension("PIL._imagingft", ["src/_imagingft.c", "src/libImaging/Mode.c"]), Extension("PIL._imagingcms", ["src/_imagingcms.c"]), - Extension("PIL._webp", ["src/_webp.c"]), + Extension("PIL._webp", ["src/_webp.c", "src/libImaging/Mode.c"]), Extension("PIL._avif", ["src/_avif.c"]), Extension("PIL._imagingtk", ["src/_imagingtk.c", "src/Tk/tkImaging.c"]), Extension("PIL._imagingmath", ["src/_imagingmath.c"]), From e483a976d218ceb1120f00b624a4d0f30c0cd71e Mon Sep 17 00:00:00 2001 From: Yay295 Date: Wed, 24 Apr 2024 17:33:09 -0500 Subject: [PATCH 103/309] use a different temp build dir for each module --- setup.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/setup.py b/setup.py index b5769b191..2dca5e380 100644 --- a/setup.py +++ b/setup.py @@ -1008,6 +1008,17 @@ class pil_build_ext(build_ext): self.summary_report(feature) + def build_extension(self, ext): + # Append the extension name (not including "PIL.") to the temp build directory + # so that each module builds to its own directory. We need to make a (shallow) + # copy of 'self' here so that we don't overwrite this value when running in + # parallel. + import copy + + self_copy = copy.copy(self) + self_copy.build_temp = os.path.join(self.build_temp, ext.name[4:]) + build_ext.build_extension(self_copy, ext) + def summary_report(self, feature: ext_feature) -> None: print("-" * 68) print("PIL SETUP SUMMARY") From 28adda9299daac9cd4ee349474d7618c16152d2d Mon Sep 17 00:00:00 2001 From: eyedav <88885346+eyedav@users.noreply.github.com> Date: Sat, 19 Jul 2025 17:02:00 +0200 Subject: [PATCH 104/309] build Mode.c as a common library --- setup.py | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/setup.py b/setup.py index 2dca5e380..3098a9ec6 100644 --- a/setup.py +++ b/setup.py @@ -103,7 +103,6 @@ _LIB_IMAGING = ( "JpegDecode", "JpegEncode", "Matrix", - "Mode", "ModeFilter", "Negative", "Offset", @@ -1008,17 +1007,6 @@ class pil_build_ext(build_ext): self.summary_report(feature) - def build_extension(self, ext): - # Append the extension name (not including "PIL.") to the temp build directory - # so that each module builds to its own directory. We need to make a (shallow) - # copy of 'self' here so that we don't overwrite this value when running in - # parallel. - import copy - - self_copy = copy.copy(self) - self_copy.build_temp = os.path.join(self.build_temp, ext.name[4:]) - build_ext.build_extension(self_copy, ext) - def summary_report(self, feature: ext_feature) -> None: print("-" * 68) print("PIL SETUP SUMMARY") @@ -1084,16 +1072,20 @@ def debug_build() -> bool: return hasattr(sys, "gettotalrefcount") or FUZZING_BUILD +libraries = [ + ("pil_imaging_mode", {"sources": ["src/libImaging/Mode.c"]}), +] + files: list[str | os.PathLike[str]] = ["src/_imaging.c"] for src_file in _IMAGING: files.append("src/" + src_file + ".c") for src_file in _LIB_IMAGING: files.append(os.path.join("src/libImaging", src_file + ".c")) ext_modules = [ - Extension("PIL._imaging", files), - Extension("PIL._imagingft", ["src/_imagingft.c", "src/libImaging/Mode.c"]), + Extension("PIL._imaging", files, libraries=["pil_imaging_mode"]), + Extension("PIL._imagingft", ["src/_imagingft.c"], libraries=["pil_imaging_mode"]), Extension("PIL._imagingcms", ["src/_imagingcms.c"]), - Extension("PIL._webp", ["src/_webp.c", "src/libImaging/Mode.c"]), + Extension("PIL._webp", ["src/_webp.c"], libraries=["pil_imaging_mode"]), Extension("PIL._avif", ["src/_avif.c"]), Extension("PIL._imagingtk", ["src/_imagingtk.c", "src/Tk/tkImaging.c"]), Extension("PIL._imagingmath", ["src/_imagingmath.c"]), @@ -1105,6 +1097,7 @@ try: setup( cmdclass={"build_ext": pil_build_ext}, ext_modules=ext_modules, + libraries=libraries, zip_safe=not (debug_build() or PLATFORM_MINGW), ) except RequiredDependencyException as err: From 0567f064e4616a7e816a2fda493fc00c4a6ae23d Mon Sep 17 00:00:00 2001 From: Yay295 Date: Thu, 25 Apr 2024 19:29:02 -0500 Subject: [PATCH 105/309] add debug check that all modes and rawmodes are defined --- src/libImaging/Mode.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/libImaging/Mode.c b/src/libImaging/Mode.c index 659e7aada..1ec24aae8 100644 --- a/src/libImaging/Mode.c +++ b/src/libImaging/Mode.c @@ -1,6 +1,10 @@ #include "Mode.h" #include +#ifdef NDEBUG +#include +#include +#endif const ModeData MODES[] = { [IMAGING_MODE_UNKNOWN] = {""}, @@ -39,6 +43,11 @@ const ModeID findModeID(const char * const name) { return IMAGING_MODE_UNKNOWN; } for (size_t i = 0; i < sizeof(MODES) / sizeof(*MODES); i++) { +#ifdef NDEBUG + if (MODES[i].name == NULL) { + fprintf(stderr, "Mode ID %zu is not defined.\n", (size_t)i); + } else +#endif if (strcmp(MODES[i].name, name) == 0) { return (ModeID)i; } @@ -238,6 +247,11 @@ const RawModeID findRawModeID(const char * const name) { return IMAGING_RAWMODE_UNKNOWN; } for (size_t i = 0; i < sizeof(RAWMODES) / sizeof(*RAWMODES); i++) { +#ifdef NDEBUG + if (RAWMODES[i].name == NULL) { + fprintf(stderr, "Rawmode ID %zu is not defined.\n", (size_t)i); + } else +#endif if (strcmp(RAWMODES[i].name, name) == 0) { return (RawModeID)i; } From 2f169fa121dcb9e1319c8741075396e55aea378c Mon Sep 17 00:00:00 2001 From: Yay295 Date: Thu, 25 Apr 2024 19:54:47 -0500 Subject: [PATCH 106/309] use mode enums in _imagingcms.c --- setup.py | 2 +- src/_imagingcms.c | 46 +++++++++++++++++++++++++++++----------------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/setup.py b/setup.py index 3098a9ec6..82986f140 100644 --- a/setup.py +++ b/setup.py @@ -1084,7 +1084,7 @@ for src_file in _LIB_IMAGING: ext_modules = [ Extension("PIL._imaging", files, libraries=["pil_imaging_mode"]), Extension("PIL._imagingft", ["src/_imagingft.c"], libraries=["pil_imaging_mode"]), - Extension("PIL._imagingcms", ["src/_imagingcms.c"]), + Extension("PIL._imagingcms", ["src/_imagingcms.c"], libraries=["pil_imaging_mode"]), Extension("PIL._webp", ["src/_webp.c"], libraries=["pil_imaging_mode"]), Extension("PIL._avif", ["src/_avif.c"]), Extension("PIL._imagingtk", ["src/_imagingtk.c", "src/Tk/tkImaging.c"]), diff --git a/src/_imagingcms.c b/src/_imagingcms.c index e2f29d1b7..ad3b27896 100644 --- a/src/_imagingcms.c +++ b/src/_imagingcms.c @@ -212,32 +212,44 @@ cms_transform_dealloc(CmsTransformObject *self) { /* internal functions */ static cmsUInt32Number -findLCMStype(char *PILmode) { - if (strcmp(PILmode, "RGB") == 0 || strcmp(PILmode, "RGBA") == 0 || - strcmp(PILmode, "RGBX") == 0) { - return TYPE_RGBA_8; +findLCMStype(const char *const mode_name) { + const ModeID mode = findModeID(mode_name); + switch (mode) { + case IMAGING_MODE_RGB: + case IMAGING_MODE_RGBA: + case IMAGING_MODE_RGBX: + return TYPE_RGBA_8; + case IMAGING_MODE_CMYK: + return TYPE_CMYK_8; + case IMAGING_MODE_I_16: + case IMAGING_MODE_I_16L: + return TYPE_GRAY_16; + case IMAGING_MODE_I_16B: + return TYPE_GRAY_16_SE; + case IMAGING_MODE_YCbCr: + return TYPE_YCbCr_8; + case IMAGING_MODE_LAB: + // LabX equivalent like ALab, but not reversed -- no #define in lcms2 + return ( + COLORSPACE_SH(PT_LabV2) | CHANNELS_SH(3) | BYTES_SH(1) | EXTRA_SH(1) + ); + default: + // This function only accepts a subset of the imaging modes Pillow has. + break; } - if (strcmp(PILmode, "RGBA;16B") == 0) { + // The following modes are not valid PIL Image modes. + if (strcmp(mode_name, "RGBA;16B") == 0) { return TYPE_RGBA_16; } - if (strcmp(PILmode, "CMYK") == 0) { - return TYPE_CMYK_8; - } - if (strcmp(PILmode, "I;16") == 0 || strcmp(PILmode, "I;16L") == 0 || - strcmp(PILmode, "L;16") == 0) { + if (strcmp(mode_name, "L;16") == 0) { return TYPE_GRAY_16; } - if (strcmp(PILmode, "I;16B") == 0 || strcmp(PILmode, "L;16B") == 0) { + if (strcmp(mode_name, "L;16B") == 0) { return TYPE_GRAY_16_SE; } - if (strcmp(PILmode, "YCbCr") == 0 || strcmp(PILmode, "YCCA") == 0 || - strcmp(PILmode, "YCC") == 0) { + if (strcmp(mode_name, "YCCA") == 0 || strcmp(mode_name, "YCC") == 0) { return TYPE_YCbCr_8; } - if (strcmp(PILmode, "LAB") == 0) { - // LabX equivalent like ALab, but not reversed -- no #define in lcms2 - return (COLORSPACE_SH(PT_LabV2) | CHANNELS_SH(3) | BYTES_SH(1) | EXTRA_SH(1)); - } /* presume "1" or "L" by default */ return TYPE_GRAY_8; } From d82576ff3801b6e9bd87ebaf93b357768dfc08b6 Mon Sep 17 00:00:00 2001 From: eyedav <88885346+eyedav@users.noreply.github.com> Date: Sat, 19 Jul 2025 17:03:31 +0200 Subject: [PATCH 107/309] require types-setuptools>=75.2.0 this is necessary to have https://github.com/python/typeshed/pull/12791 --- .ci/requirements-mypy.txt | 2 +- setup.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.ci/requirements-mypy.txt b/.ci/requirements-mypy.txt index 99eac6027..3519707f1 100644 --- a/.ci/requirements-mypy.txt +++ b/.ci/requirements-mypy.txt @@ -11,4 +11,4 @@ sphinx types-atheris types-defusedxml types-olefile -types-setuptools +types-setuptools>=75.2.0 diff --git a/setup.py b/setup.py index 82986f140..dcc07eaf6 100644 --- a/setup.py +++ b/setup.py @@ -16,11 +16,15 @@ import subprocess import sys import warnings from collections.abc import Iterator +from typing import TYPE_CHECKING, Any from pybind11.setup_helpers import ParallelCompile from setuptools import Extension, setup from setuptools.command.build_ext import build_ext +if TYPE_CHECKING: + from setuptools import _BuildInfo + configuration: dict[str, list[str]] = {} # parse configuration from _custom_build/backend.py @@ -1072,7 +1076,7 @@ def debug_build() -> bool: return hasattr(sys, "gettotalrefcount") or FUZZING_BUILD -libraries = [ +libraries: list[tuple[str, _BuildInfo]] = [ ("pil_imaging_mode", {"sources": ["src/libImaging/Mode.c"]}), ] From 84aa4372fd38069edbc792c235169422c0a799d6 Mon Sep 17 00:00:00 2001 From: eyedav <88885346+eyedav@users.noreply.github.com> Date: Sat, 19 Jul 2025 17:06:44 +0200 Subject: [PATCH 108/309] linter changes --- src/Tk/tkImaging.c | 6 ++-- src/_imaging.c | 7 +++-- src/decode.c | 22 +++++++++---- src/encode.c | 4 ++- src/libImaging/Convert.c | 17 +++++----- src/libImaging/Fill.c | 12 +++---- src/libImaging/GetBBox.c | 9 +++--- src/libImaging/Matrix.c | 7 ++--- src/libImaging/Mode.c | 63 +++++++++++++++---------------------- src/libImaging/Mode.h | 24 +++++++------- src/libImaging/Pack.c | 3 +- src/libImaging/Paste.c | 9 +++--- src/libImaging/Point.c | 4 +-- src/libImaging/TiffDecode.c | 28 ++++++++++++++--- 14 files changed, 113 insertions(+), 102 deletions(-) diff --git a/src/Tk/tkImaging.c b/src/Tk/tkImaging.c index 3e35f885f..834634bd7 100644 --- a/src/Tk/tkImaging.c +++ b/src/Tk/tkImaging.c @@ -124,10 +124,8 @@ PyImagingPhotoPut( if (im->mode == IMAGING_MODE_1 || im->mode == IMAGING_MODE_L) { block.pixelSize = 1; block.offset[0] = block.offset[1] = block.offset[2] = block.offset[3] = 0; - } else if ( - im->mode == IMAGING_MODE_RGB || im->mode == IMAGING_MODE_RGBA || - im->mode == IMAGING_MODE_RGBX || im->mode == IMAGING_MODE_RGBa - ) { + } else if (im->mode == IMAGING_MODE_RGB || im->mode == IMAGING_MODE_RGBA || + im->mode == IMAGING_MODE_RGBX || im->mode == IMAGING_MODE_RGBa) { block.pixelSize = 4; block.offset[0] = 0; block.offset[1] = 1; diff --git a/src/_imaging.c b/src/_imaging.c index a940bb974..4264cdb87 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -1773,7 +1773,9 @@ _quantize(ImagingObject *self, PyObject *args) { if (!self->image->xsize || !self->image->ysize) { /* no content; return an empty image */ - return PyImagingNew(ImagingNew(IMAGING_MODE_P, self->image->xsize, self->image->ysize)); + return PyImagingNew( + ImagingNew(IMAGING_MODE_P, self->image->xsize, self->image->ysize) + ); } return PyImagingNew(ImagingQuantize(self->image, colours, method, kmeans)); @@ -2053,7 +2055,8 @@ _reduce(ImagingObject *self, PyObject *args) { static int isRGB(const ModeID mode) { - return mode == IMAGING_MODE_RGB || mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_RGBX; + return mode == IMAGING_MODE_RGB || mode == IMAGING_MODE_RGBA || + mode == IMAGING_MODE_RGBX; } static PyObject * diff --git a/src/decode.c b/src/decode.c index f95637fa6..b7deee228 100644 --- a/src/decode.c +++ b/src/decode.c @@ -266,7 +266,9 @@ static PyTypeObject ImagingDecoderType = { /* -------------------------------------------------------------------- */ int -get_unpacker(ImagingDecoderObject *decoder, const ModeID mode, const RawModeID rawmode) { +get_unpacker( + ImagingDecoderObject *decoder, const ModeID mode, const RawModeID rawmode +) { int bits; ImagingShuffler unpack; @@ -297,7 +299,9 @@ PyImaging_BitDecoderNew(PyObject *self, PyObject *args) { int fill = 0; int sign = 0; int ystep = 1; - if (!PyArg_ParseTuple(args, "s|iiiii", &mode_name, &bits, &pad, &fill, &sign, &ystep)) { + if (!PyArg_ParseTuple( + args, "s|iiiii", &mode_name, &bits, &pad, &fill, &sign, &ystep + )) { return NULL; } @@ -408,7 +412,9 @@ PyImaging_GifDecoderNew(PyObject *self, PyObject *args) { int bits = 8; int interlace = 0; int transparency = -1; - if (!PyArg_ParseTuple(args, "s|iii", &mode_name, &bits, &interlace, &transparency)) { + if (!PyArg_ParseTuple( + args, "s|iii", &mode_name, &bits, &interlace, &transparency + )) { return NULL; } @@ -481,7 +487,9 @@ PyImaging_LibTiffDecoderNew(PyObject *self, PyObject *args) { int fp; uint32_t ifdoffset; - if (!PyArg_ParseTuple(args, "sssiI", &mode_name, &rawmode_name, &compname, &fp, &ifdoffset)) { + if (!PyArg_ParseTuple( + args, "sssiI", &mode_name, &rawmode_name, &compname, &fp, &ifdoffset + )) { return NULL; } @@ -823,12 +831,14 @@ PyImaging_JpegDecoderNew(PyObject *self, PyObject *args) { ImagingDecoderObject *decoder; char *mode_name; - char *rawmode_name; /* what we want from the decoder */ + char *rawmode_name; /* what we want from the decoder */ char *jpegmode_name; /* what's in the file */ int scale = 1; int draft = 0; - if (!PyArg_ParseTuple(args, "ssz|ii", &mode_name, &rawmode_name, &jpegmode_name, &scale, &draft)) { + if (!PyArg_ParseTuple( + args, "ssz|ii", &mode_name, &rawmode_name, &jpegmode_name, &scale, &draft + )) { return NULL; } diff --git a/src/encode.c b/src/encode.c index 3a6b6d6d0..6a75a2fcc 100644 --- a/src/encode.c +++ b/src/encode.c @@ -411,7 +411,9 @@ PyImaging_GifEncoderNew(PyObject *self, PyObject *args) { char *rawmode_name; Py_ssize_t bits = 8; Py_ssize_t interlace = 0; - if (!PyArg_ParseTuple(args, "ss|nn", &mode_name, &rawmode_name, &bits, &interlace)) { + if (!PyArg_ParseTuple( + args, "ss|nn", &mode_name, &rawmode_name, &bits, &interlace + )) { return NULL; } diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index 862f228e5..0c36b8449 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -1692,25 +1692,22 @@ ImagingConvertTransparent(Imaging imIn, const ModeID mode, int r, int g, int b) return (Imaging)ImagingError_ModeError(); } - if (imIn->mode == IMAGING_MODE_RGB && (mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_RGBa)) { + if (imIn->mode == IMAGING_MODE_RGB && + (mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_RGBa)) { convert = rgb2rgba; if (mode == IMAGING_MODE_RGBa) { premultiplied = 1; } - } else if (imIn->mode == IMAGING_MODE_RGB && (mode == IMAGING_MODE_LA || mode == IMAGING_MODE_La)) { + } else if (imIn->mode == IMAGING_MODE_RGB && + (mode == IMAGING_MODE_LA || mode == IMAGING_MODE_La)) { convert = rgb2la; source_transparency = 1; if (mode == IMAGING_MODE_La) { premultiplied = 1; } - } else if ((imIn->mode == IMAGING_MODE_1 || - imIn->mode == IMAGING_MODE_I || - imIn->mode == IMAGING_MODE_I_16 || - imIn->mode == IMAGING_MODE_L - ) && ( - mode == IMAGING_MODE_RGBA || - mode == IMAGING_MODE_LA - )) { + } else if ((imIn->mode == IMAGING_MODE_1 || imIn->mode == IMAGING_MODE_I || + imIn->mode == IMAGING_MODE_I_16 || imIn->mode == IMAGING_MODE_L) && + (mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_LA)) { if (imIn->mode == IMAGING_MODE_1) { convert = bit2rgb; } else if (imIn->mode == IMAGING_MODE_I) { diff --git a/src/libImaging/Fill.c b/src/libImaging/Fill.c index 0224d1ba9..cbd303204 100644 --- a/src/libImaging/Fill.c +++ b/src/libImaging/Fill.c @@ -72,10 +72,8 @@ ImagingFillLinearGradient(const ModeID mode) { Imaging im; int y; - if (mode != IMAGING_MODE_1 && mode != IMAGING_MODE_F && - mode != IMAGING_MODE_I && mode != IMAGING_MODE_L && - mode != IMAGING_MODE_P - ) { + if (mode != IMAGING_MODE_1 && mode != IMAGING_MODE_F && mode != IMAGING_MODE_I && + mode != IMAGING_MODE_L && mode != IMAGING_MODE_P) { return (Imaging)ImagingError_ModeError(); } @@ -110,10 +108,8 @@ ImagingFillRadialGradient(const ModeID mode) { int x, y; int d; - if (mode != IMAGING_MODE_1 && mode != IMAGING_MODE_F && - mode != IMAGING_MODE_I && mode != IMAGING_MODE_L && - mode != IMAGING_MODE_P - ) { + if (mode != IMAGING_MODE_1 && mode != IMAGING_MODE_F && mode != IMAGING_MODE_I && + mode != IMAGING_MODE_L && mode != IMAGING_MODE_P) { return (Imaging)ImagingError_ModeError(); } diff --git a/src/libImaging/GetBBox.c b/src/libImaging/GetBBox.c index 3719a9f15..f94cf2a0e 100644 --- a/src/libImaging/GetBBox.c +++ b/src/libImaging/GetBBox.c @@ -89,11 +89,10 @@ ImagingGetBBox(Imaging im, int bbox[4], int alpha_only) { INT32 mask = 0xffffffff; if (im->bands == 3) { ((UINT8 *)&mask)[3] = 0; - } else if (alpha_only && ( - im->mode == IMAGING_MODE_RGBa || im->mode == IMAGING_MODE_RGBA || - im->mode == IMAGING_MODE_La || im->mode == IMAGING_MODE_LA || - im->mode == IMAGING_MODE_PA - )) { + } else if (alpha_only && + (im->mode == IMAGING_MODE_RGBa || im->mode == IMAGING_MODE_RGBA || + im->mode == IMAGING_MODE_La || im->mode == IMAGING_MODE_LA || + im->mode == IMAGING_MODE_PA)) { #ifdef WORDS_BIGENDIAN mask = 0x000000ff; #else diff --git a/src/libImaging/Matrix.c b/src/libImaging/Matrix.c index 6bc9fbc1d..d28e04edf 100644 --- a/src/libImaging/Matrix.c +++ b/src/libImaging/Matrix.c @@ -46,11 +46,8 @@ ImagingConvertMatrix(Imaging im, const ModeID mode, float m[]) { } } ImagingSectionLeave(&cookie); - } else if ( - mode == IMAGING_MODE_HSV || - mode == IMAGING_MODE_LAB || - mode == IMAGING_MODE_RGB - ) { + } else if (mode == IMAGING_MODE_HSV || mode == IMAGING_MODE_LAB || + mode == IMAGING_MODE_RGB) { imOut = ImagingNewDirty(mode, im->xsize, im->ysize); if (!imOut) { return NULL; diff --git a/src/libImaging/Mode.c b/src/libImaging/Mode.c index 1ec24aae8..8222c585b 100644 --- a/src/libImaging/Mode.c +++ b/src/libImaging/Mode.c @@ -9,36 +9,25 @@ const ModeData MODES[] = { [IMAGING_MODE_UNKNOWN] = {""}, - [IMAGING_MODE_1] = {"1"}, - [IMAGING_MODE_CMYK] = {"CMYK"}, - [IMAGING_MODE_F] = {"F"}, - [IMAGING_MODE_HSV] = {"HSV"}, - [IMAGING_MODE_I] = {"I"}, - [IMAGING_MODE_L] = {"L"}, - [IMAGING_MODE_LA] = {"LA"}, - [IMAGING_MODE_LAB] = {"LAB"}, - [IMAGING_MODE_La] = {"La"}, - [IMAGING_MODE_P] = {"P"}, - [IMAGING_MODE_PA] = {"PA"}, - [IMAGING_MODE_RGB] = {"RGB"}, - [IMAGING_MODE_RGBA] = {"RGBA"}, - [IMAGING_MODE_RGBX] = {"RGBX"}, - [IMAGING_MODE_RGBa] = {"RGBa"}, - [IMAGING_MODE_YCbCr] = {"YCbCr"}, + [IMAGING_MODE_1] = {"1"}, [IMAGING_MODE_CMYK] = {"CMYK"}, + [IMAGING_MODE_F] = {"F"}, [IMAGING_MODE_HSV] = {"HSV"}, + [IMAGING_MODE_I] = {"I"}, [IMAGING_MODE_L] = {"L"}, + [IMAGING_MODE_LA] = {"LA"}, [IMAGING_MODE_LAB] = {"LAB"}, + [IMAGING_MODE_La] = {"La"}, [IMAGING_MODE_P] = {"P"}, + [IMAGING_MODE_PA] = {"PA"}, [IMAGING_MODE_RGB] = {"RGB"}, + [IMAGING_MODE_RGBA] = {"RGBA"}, [IMAGING_MODE_RGBX] = {"RGBX"}, + [IMAGING_MODE_RGBa] = {"RGBa"}, [IMAGING_MODE_YCbCr] = {"YCbCr"}, - [IMAGING_MODE_BGR_15] = {"BGR;15"}, - [IMAGING_MODE_BGR_16] = {"BGR;16"}, + [IMAGING_MODE_BGR_15] = {"BGR;15"}, [IMAGING_MODE_BGR_16] = {"BGR;16"}, [IMAGING_MODE_BGR_24] = {"BGR;24"}, - [IMAGING_MODE_I_16] = {"I;16"}, - [IMAGING_MODE_I_16L] = {"I;16L"}, - [IMAGING_MODE_I_16B] = {"I;16B"}, - [IMAGING_MODE_I_16N] = {"I;16N"}, - [IMAGING_MODE_I_32L] = {"I;32L"}, - [IMAGING_MODE_I_32B] = {"I;32B"}, + [IMAGING_MODE_I_16] = {"I;16"}, [IMAGING_MODE_I_16L] = {"I;16L"}, + [IMAGING_MODE_I_16B] = {"I;16B"}, [IMAGING_MODE_I_16N] = {"I;16N"}, + [IMAGING_MODE_I_32L] = {"I;32L"}, [IMAGING_MODE_I_32B] = {"I;32B"}, }; -const ModeID findModeID(const char * const name) { +const ModeID +findModeID(const char *const name) { if (name == NULL) { return IMAGING_MODE_UNKNOWN; } @@ -48,21 +37,21 @@ const ModeID findModeID(const char * const name) { fprintf(stderr, "Mode ID %zu is not defined.\n", (size_t)i); } else #endif - if (strcmp(MODES[i].name, name) == 0) { + if (strcmp(MODES[i].name, name) == 0) { return (ModeID)i; } } return IMAGING_MODE_UNKNOWN; } -const ModeData * const getModeData(const ModeID id) { +const ModeData *const +getModeData(const ModeID id) { if (id < 0 || id > sizeof(MODES) / sizeof(*MODES)) { return &MODES[IMAGING_MODE_UNKNOWN]; } return &MODES[id]; } - const RawModeData RAWMODES[] = { [IMAGING_RAWMODE_UNKNOWN] = {""}, @@ -242,7 +231,8 @@ const RawModeData RAWMODES[] = { [IMAGING_RAWMODE_aRGB] = {"aRGB"}, }; -const RawModeID findRawModeID(const char * const name) { +const RawModeID +findRawModeID(const char *const name) { if (name == NULL) { return IMAGING_RAWMODE_UNKNOWN; } @@ -252,24 +242,23 @@ const RawModeID findRawModeID(const char * const name) { fprintf(stderr, "Rawmode ID %zu is not defined.\n", (size_t)i); } else #endif - if (strcmp(RAWMODES[i].name, name) == 0) { + if (strcmp(RAWMODES[i].name, name) == 0) { return (RawModeID)i; } } return IMAGING_RAWMODE_UNKNOWN; } -const RawModeData * const getRawModeData(const RawModeID id) { +const RawModeData *const +getRawModeData(const RawModeID id) { if (id < 0 || id > sizeof(RAWMODES) / sizeof(*RAWMODES)) { return &RAWMODES[IMAGING_RAWMODE_UNKNOWN]; } return &RAWMODES[id]; } - -int isModeI16(const ModeID mode) { - return mode == IMAGING_MODE_I_16 - || mode == IMAGING_MODE_I_16L - || mode == IMAGING_MODE_I_16B - || mode == IMAGING_MODE_I_16N; +int +isModeI16(const ModeID mode) { + return mode == IMAGING_MODE_I_16 || mode == IMAGING_MODE_I_16L || + mode == IMAGING_MODE_I_16B || mode == IMAGING_MODE_I_16N; } diff --git a/src/libImaging/Mode.h b/src/libImaging/Mode.h index e21ad941a..a20ad0cb6 100644 --- a/src/libImaging/Mode.h +++ b/src/libImaging/Mode.h @@ -1,7 +1,6 @@ #ifndef __MODE_H__ #define __MODE_H__ - typedef enum { IMAGING_MODE_UNKNOWN, @@ -35,12 +34,13 @@ typedef enum { } ModeID; typedef struct { - const char * const name; + const char *const name; } ModeData; -const ModeID findModeID(const char * const name); -const ModeData * const getModeData(const ModeID id); - +const ModeID +findModeID(const char *const name); +const ModeData *const +getModeData(const ModeID id); typedef enum { IMAGING_RAWMODE_UNKNOWN, @@ -226,13 +226,15 @@ typedef enum { } RawModeID; typedef struct { - const char * const name; + const char *const name; } RawModeData; -const RawModeID findRawModeID(const char * const name); -const RawModeData * const getRawModeData(const RawModeID id); +const RawModeID +findRawModeID(const char *const name); +const RawModeData *const +getRawModeData(const RawModeID id); +int +isModeI16(const ModeID mode); -int isModeI16(const ModeID mode); - -#endif // __MODE_H__ +#endif // __MODE_H__ diff --git a/src/libImaging/Pack.c b/src/libImaging/Pack.c index 63bbc8acb..a0652e0ca 100644 --- a/src/libImaging/Pack.c +++ b/src/libImaging/Pack.c @@ -648,7 +648,8 @@ static struct { {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16B, 16, copy2}, {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16L, 16, copy2}, {IMAGING_MODE_I_16N, IMAGING_RAWMODE_I_16N, 16, copy2}, - {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16N, 16, packI16N_I16}, // LibTiff native->image endian. + {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16N, 16, packI16N_I16 + }, // LibTiff native->image endian. {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16N, 16, packI16N_I16}, {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16N, 16, packI16N_I16B}, diff --git a/src/libImaging/Paste.c b/src/libImaging/Paste.c index 54dd270e6..d4b973abc 100644 --- a/src/libImaging/Paste.c +++ b/src/libImaging/Paste.c @@ -450,11 +450,10 @@ fill_mask_L( } } else { - int alpha_channel = imOut->mode == IMAGING_MODE_RGBa || - imOut->mode == IMAGING_MODE_RGBA || - imOut->mode == IMAGING_MODE_La || - imOut->mode == IMAGING_MODE_LA || - imOut->mode == IMAGING_MODE_PA; + int alpha_channel = + imOut->mode == IMAGING_MODE_RGBa || imOut->mode == IMAGING_MODE_RGBA || + imOut->mode == IMAGING_MODE_La || imOut->mode == IMAGING_MODE_LA || + imOut->mode == IMAGING_MODE_PA; for (y = 0; y < ysize; y++) { UINT8 *out = (UINT8 *)imOut->image[y + dy] + dx * pixelsize; UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx; diff --git a/src/libImaging/Point.c b/src/libImaging/Point.c index fa0b1027c..8f6d47c77 100644 --- a/src/libImaging/Point.c +++ b/src/libImaging/Point.c @@ -210,8 +210,8 @@ ImagingPointTransform(Imaging imIn, double scale, double offset) { Imaging imOut; int x, y; - if (!imIn || (imIn->mode != IMAGING_MODE_I && - imIn->mode != IMAGING_MODE_I_16 && imIn->mode != IMAGING_MODE_F)) { + if (!imIn || (imIn->mode != IMAGING_MODE_I && imIn->mode != IMAGING_MODE_I_16 && + imIn->mode != IMAGING_MODE_F)) { return (Imaging)ImagingError_ModeError(); } diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index f987c608f..72e0d7b30 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -246,10 +246,26 @@ _pickUnpackers( // We'll pick appropriate set of unpackers depending on planar_configuration // It does not matter if data is RGB(A), CMYK or LUV really, // we just copy it plane by plane - unpackers[0] = ImagingFindUnpacker(IMAGING_MODE_RGBA, bits_per_sample == 16 ? IMAGING_RAWMODE_R_16N : IMAGING_RAWMODE_R, NULL); - unpackers[1] = ImagingFindUnpacker(IMAGING_MODE_RGBA, bits_per_sample == 16 ? IMAGING_RAWMODE_G_16N : IMAGING_RAWMODE_G, NULL); - unpackers[2] = ImagingFindUnpacker(IMAGING_MODE_RGBA, bits_per_sample == 16 ? IMAGING_RAWMODE_B_16N : IMAGING_RAWMODE_B, NULL); - unpackers[3] = ImagingFindUnpacker(IMAGING_MODE_RGBA, bits_per_sample == 16 ? IMAGING_RAWMODE_A_16N : IMAGING_RAWMODE_A, NULL); + unpackers[0] = ImagingFindUnpacker( + IMAGING_MODE_RGBA, + bits_per_sample == 16 ? IMAGING_RAWMODE_R_16N : IMAGING_RAWMODE_R, + NULL + ); + unpackers[1] = ImagingFindUnpacker( + IMAGING_MODE_RGBA, + bits_per_sample == 16 ? IMAGING_RAWMODE_G_16N : IMAGING_RAWMODE_G, + NULL + ); + unpackers[2] = ImagingFindUnpacker( + IMAGING_MODE_RGBA, + bits_per_sample == 16 ? IMAGING_RAWMODE_B_16N : IMAGING_RAWMODE_B, + NULL + ); + unpackers[3] = ImagingFindUnpacker( + IMAGING_MODE_RGBA, + bits_per_sample == 16 ? IMAGING_RAWMODE_A_16N : IMAGING_RAWMODE_A, + NULL + ); return im->bands; } else { @@ -763,7 +779,9 @@ ImagingLibTiffDecode( if (extrasamples >= 1 && (sampleinfo[0] == EXTRASAMPLE_UNSPECIFIED || sampleinfo[0] == EXTRASAMPLE_ASSOCALPHA)) { - shuffle = ImagingFindUnpacker(IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa, NULL); + shuffle = ImagingFindUnpacker( + IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa, NULL + ); for (y = state->yoff; y < state->ysize; y++) { UINT8 *ptr = (UINT8 *)im->image[y + state->yoff] + From 64556405e29d076d95d284d3d146cecff7476b7d Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 19 Jul 2025 17:34:39 +0200 Subject: [PATCH 109/309] WIP - Not working in pyarrow --- src/libImaging/Arrow.c | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/libImaging/Arrow.c b/src/libImaging/Arrow.c index 2ecec9b29..ff98dfb51 100644 --- a/src/libImaging/Arrow.c +++ b/src/libImaging/Arrow.c @@ -84,6 +84,33 @@ image_band_json(Imaging im) { return json; } +char * +single_band_json(Imaging im) { + char *format = "{\"bands\": [\"%s\"]}"; + char *json; + // Bands can be 1 band * (maybe but probably not) 2 characters each + int len = strlen(format) + 2 + 1; + int err; + + json = calloc(1, len); + + if (!json) { + return NULL; + } + + err = PyOS_snprintf( + json, + len, + format, + im->band_names[0] + ); + if (err < 0) { + return NULL; + } + return json; +} + + char * assemble_metadata(const char *band_json) { /* format is @@ -179,7 +206,17 @@ export_imaging_schema(Imaging im, struct ArrowSchema *schema) { } if (im->bands == 1) { - return export_named_type(schema, im->arrow_band_format, im->band_names[0]); + retval = export_named_type(schema, im->arrow_band_format, im->band_names[0]); + if (retval != 0) { + return retval; + } + // band related metadata + band_json = single_band_json(im); + if (band_json) { + schema->metadata = assemble_metadata(band_json); + free(band_json); + } + return retval; } retval = export_named_type(schema, "+w:4", ""); From adfb66f1d6b61494f0c111ffb21a38dbfc720b4c Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 20 Jul 2025 10:18:59 +0200 Subject: [PATCH 110/309] Fix Compliation errors from rebase --- src/_imaging.c | 5 ++++- src/libImaging/BcnEncode.c | 2 +- src/libImaging/Convert.c | 3 --- src/libImaging/Jpeg2KEncode.c | 2 +- src/libImaging/Pack.c | 4 +--- src/libImaging/Unpack.c | 8 +++----- 6 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index 4264cdb87..7823745f0 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -297,6 +297,7 @@ ExportArrowArrayPyCapsule(ImagingObject *self) { static PyObject * _new_arrow(PyObject *self, PyObject *args) { char *mode; + ModeID mode_id; int xsize, ysize; PyObject *schema_capsule, *array_capsule; PyObject *ret; @@ -307,9 +308,11 @@ _new_arrow(PyObject *self, PyObject *args) { return NULL; } + mode_id = findModeID(mode); + // ImagingBorrowArrow is responsible for retaining the array_capsule ret = PyImagingNew( - ImagingNewArrow(mode, xsize, ysize, schema_capsule, array_capsule) + ImagingNewArrow(mode_id, xsize, ysize, schema_capsule, array_capsule) ); if (!ret) { return ImagingError_ValueError("Invalid Arrow array mode or size mismatch"); diff --git a/src/libImaging/BcnEncode.c b/src/libImaging/BcnEncode.c index 7a5072dde..2101383fd 100644 --- a/src/libImaging/BcnEncode.c +++ b/src/libImaging/BcnEncode.c @@ -253,7 +253,7 @@ int ImagingBcnEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { int n = state->state; int has_alpha_channel = - strcmp(im->mode, "RGBA") == 0 || strcmp(im->mode, "LA") == 0; + im->mode == IMAGING_MODE_RGBA || im->mode == IMAGING_MODE_LA; UINT8 *dst = buf; diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index 0c36b8449..330e5325c 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -1517,9 +1517,6 @@ static struct { {IMAGING_MODE_RGB, IMAGING_MODE_I_16N, rgb2i16l}, #endif {IMAGING_MODE_RGB, IMAGING_MODE_F, rgb2f}, - {IMAGING_MODE_RGB, IMAGING_MODE_BGR_15, rgb2bgr15}, - {IMAGING_MODE_RGB, IMAGING_MODE_BGR_16, rgb2bgr16}, - {IMAGING_MODE_RGB, IMAGING_MODE_BGR_24, rgb2bgr24}, {IMAGING_MODE_RGB, IMAGING_MODE_RGBA, rgb2rgba}, {IMAGING_MODE_RGB, IMAGING_MODE_RGBa, rgb2rgba}, {IMAGING_MODE_RGB, IMAGING_MODE_RGBX, rgb2rgba}, diff --git a/src/libImaging/Jpeg2KEncode.c b/src/libImaging/Jpeg2KEncode.c index 67290f674..fdfbde2d7 100644 --- a/src/libImaging/Jpeg2KEncode.c +++ b/src/libImaging/Jpeg2KEncode.c @@ -332,7 +332,7 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) { pack = j2k_pack_rgba; #if ((OPJ_VERSION_MAJOR == 2 && OPJ_VERSION_MINOR == 5 && OPJ_VERSION_BUILD >= 3) || \ (OPJ_VERSION_MAJOR == 2 && OPJ_VERSION_MINOR > 5) || OPJ_VERSION_MAJOR > 2) - } else if (strcmp(im->mode, "CMYK") == 0) { + } else if (im->mode == IMAGING_MODE_CMYK) { components = 4; color_space = OPJ_CLRSPC_CMYK; pack = j2k_pack_rgba; diff --git a/src/libImaging/Pack.c b/src/libImaging/Pack.c index a0652e0ca..0a97c4872 100644 --- a/src/libImaging/Pack.c +++ b/src/libImaging/Pack.c @@ -651,9 +651,7 @@ static struct { {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16N, 16, packI16N_I16 }, // LibTiff native->image endian. {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16N, 16, packI16N_I16}, - {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16N, 16, packI16N_I16B}, - - {NULL} /* sentinel */ + {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16N, 16, packI16N_I16B} }; ImagingShuffler diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index 8d4bb8619..075ec5b95 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -1313,7 +1313,7 @@ copy4skip2(UINT8 *_out, const UINT8 *in, int pixels) { /* Unpack to "I" and "F" images */ #define UNPACK_RAW(NAME, GET, INTYPE, OUTTYPE) \ - static void NAME(UINT8 *out, const UINT8 *in, int pixels) { \ + static void NAME(UINT8 *out_, const UINT8 *in, int pixels) { \ int i; \ OUTTYPE *out = (OUTTYPE *)out_; \ for (i = 0; i < pixels; i++, in += sizeof(INTYPE)) { \ @@ -1322,7 +1322,7 @@ copy4skip2(UINT8 *_out, const UINT8 *in, int pixels) { } #define UNPACK(NAME, COPY, INTYPE, OUTTYPE) \ - static void NAME(UINT8 *out, const UINT8 *in, int pixels) { \ + static void NAME(UINT8 *out_, const UINT8 *in, int pixels) { \ int i; \ OUTTYPE *out = (OUTTYPE *)out_; \ INTYPE tmp_; \ @@ -1839,9 +1839,7 @@ static struct { {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16R, 16, unpackI16R_I16}, - {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_12, 12, unpackI12_I16}, // 12 bit Tiffs stored in 16bits. - - {NULL} /* sentinel */ + {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_12, 12, unpackI12_I16} // 12 bit Tiffs stored in 16bits. }; ImagingShuffler From 1159e65b4f60013f93c5b043743c407dbcf74777 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 20 Jul 2025 12:58:54 +0200 Subject: [PATCH 111/309] Added integration tests for Arro3, comparable to PyArrow tests --- Tests/test_arro3.py | 276 ++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 2 + 2 files changed, 278 insertions(+) create mode 100644 Tests/test_arro3.py diff --git a/Tests/test_arro3.py b/Tests/test_arro3.py new file mode 100644 index 000000000..ddc7ecd0a --- /dev/null +++ b/Tests/test_arro3.py @@ -0,0 +1,276 @@ +from __future__ import annotations + +import json +from typing import Any, NamedTuple +from itertools import repeat, chain + +import pytest + +from PIL import Image + +from .helper import ( + assert_deep_equal, + assert_image_equal, + hopper, + is_big_endian, +) + +TYPE_CHECKING = False +if TYPE_CHECKING: + from arro3.core import Array, DataType, Field, fixed_size_list_array + from arro3 import compute +else: + arro3 = pytest.importorskip("arro3", reason="Arro3 not installed") + from arro3.core import Array, DataType, Field, fixed_size_list_array + from arro3 import compute + +TEST_IMAGE_SIZE = (10, 10) + + +def _test_img_equals_pyarray( + img: Image.Image, arr: Any, mask: list[int] | None, elts_per_pixel: int = 1 +) -> None: + assert img.height * img.width * elts_per_pixel == len(arr) + px = img.load() + assert px is not None + if elts_per_pixel > 1 and mask is None: + # have to do element-wise comparison when we're comparing + # flattened r,g,b,a to a pixel. + mask = list(range(elts_per_pixel)) + for x in range(0, img.size[0], int(img.size[0] / 10)): + for y in range(0, img.size[1], int(img.size[1] / 10)): + if mask: + pixel = px[x, y] + assert isinstance(pixel, tuple) + for ix, elt in enumerate(mask): + if elts_per_pixel == 1: + assert pixel[ix] == arr[y * img.width + x].as_py()[elt] + else: + assert ( + pixel[ix] + == arr[(y * img.width + x) * elts_per_pixel + elt].as_py() + ) + else: + assert_deep_equal(px[x, y], arr[y * img.width + x].as_py()) + + +def _test_img_equals_int32_pyarray( + img: Image.Image, arr: Any, mask: list[int] | None, elts_per_pixel: int = 1 +) -> None: + assert img.height * img.width * elts_per_pixel == len(arr) + px = img.load() + assert px is not None + if mask is None: + # have to do element-wise comparison when we're comparing + # flattened rgba in an uint32 to a pixel. + mask = list(range(elts_per_pixel)) + for x in range(0, img.size[0], int(img.size[0] / 10)): + for y in range(0, img.size[1], int(img.size[1] / 10)): + pixel = px[x, y] + assert isinstance(pixel, tuple) + arr_pixel_int = arr[y * img.width + x].as_py() + arr_pixel_tuple = ( + arr_pixel_int % 256, + (arr_pixel_int // 256) % 256, + (arr_pixel_int // 256**2) % 256, + (arr_pixel_int // 256**3), + ) + if is_big_endian(): + arr_pixel_tuple = arr_pixel_tuple[::-1] + + for ix, elt in enumerate(mask): + assert pixel[ix] == arr_pixel_tuple[elt] + +fl_uint8_4_type = DataType.list(Field("_", DataType.uint8()).with_nullable(False), 4) + +@pytest.mark.parametrize( + "mode, dtype, mask", + ( + ("L", DataType.uint8(), None), + ("I", DataType.int32(), None), + ("F", DataType.float32(), None), + ("LA", fl_uint8_4_type, [0, 3]), + ("RGB", fl_uint8_4_type, [0, 1, 2]), + ("RGBA", fl_uint8_4_type, None), + ("RGBX", fl_uint8_4_type, None), + ("CMYK", fl_uint8_4_type, None), + ("YCbCr", fl_uint8_4_type, [0, 1, 2]), + ("HSV", fl_uint8_4_type, [0, 1, 2]), + ), +) +def test_to_array(mode: str, dtype: DataType, mask: list[int] | None) -> None: + img = hopper(mode) + + # Resize to non-square + img = img.crop((3, 0, 124, 127)) + assert img.size == (121, 127) + + arr = Array(img) # type: ignore[call-overload] + _test_img_equals_pyarray(img, arr, mask) + assert arr.type == dtype + + reloaded = Image.fromarrow(arr, mode, img.size) + + assert reloaded + + assert_image_equal(img, reloaded) + + +def test_lifetime() -> None: + # valgrind shouldn't error out here. + # arrays should be accessible after the image is deleted. + + img = hopper("L") + + arr_1 = Array(img) # type: ignore[call-overload] + arr_2 = Array(img) # type: ignore[call-overload] + + del img + + assert compute.sum(arr_1).as_py() > 0 + del arr_1 + + assert compute.sum(arr_2).as_py() > 0 + del arr_2 + + +def test_lifetime2() -> None: + # valgrind shouldn't error out here. + # img should remain after the arrays are collected. + + img = hopper("L") + + arr_1 = Array(img) # type: ignore[call-overload] + arr_2 = Array(img) # type: ignore[call-overload] + + assert compute.sum(arr_1).as_py() > 0 + del arr_1 + + assert compute.sum(arr_2).as_py() > 0 + del arr_2 + + img2 = img.copy() + px = img2.load() + assert px # make mypy happy + assert isinstance(px[0, 0], int) + + +class DataShape(NamedTuple): + dtype: DataType + # Strictly speaking, elt should be a pixel or pixel component, so + # list[uint8][4], float, int, uint32, uint8, etc. But more + # correctly, it should be exactly the dtype from the line above. + elt: Any + elts_per_pixel: int + + +UINT_ARR = DataShape( + dtype=fl_uint8_4_type, + elt=[1, 2, 3, 4], # array of 4 uint8 per pixel + elts_per_pixel=1, # only one array per pixel +) + +UINT = DataShape( + dtype=DataType.uint8(), + elt=3, # one uint8, + elts_per_pixel=4, # but repeated 4x per pixel +) + +UINT32 = DataShape( + dtype=DataType.uint32(), + elt=0xABCDEF45, # one packed int, doesn't fit in a int32 > 0x80000000 + elts_per_pixel=1, # one per pixel +) + +INT32 = DataShape( + dtype=DataType.uint32(), + elt=0x12CDEF45, # one packed int + elts_per_pixel=1, # one per pixel +) + + +@pytest.mark.parametrize( + "mode, data_tp, mask", + ( + ("L", DataShape(DataType.uint8(), 3, 1), None), + ("I", DataShape(DataType.int32(), 1 << 24, 1), None), + ("F", DataShape(DataType.float32(), 3.14159, 1), None), + ("LA", UINT_ARR, [0, 3]), + ("LA", UINT, [0, 3]), + ("RGB", UINT_ARR, [0, 1, 2]), + ("RGBA", UINT_ARR, None), + ("CMYK", UINT_ARR, None), + ("YCbCr", UINT_ARR, [0, 1, 2]), + ("HSV", UINT_ARR, [0, 1, 2]), + ("RGB", UINT, [0, 1, 2]), + ("RGBA", UINT, None), + ("CMYK", UINT, None), + ("YCbCr", UINT, [0, 1, 2]), + ("HSV", UINT, [0, 1, 2]), + ), +) +def test_fromarray(mode: str, data_tp: DataShape, mask: list[int] | None) -> None: + (dtype, elt, elts_per_pixel) = data_tp + + ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1] + if dtype == fl_uint8_4_type: + tmp_arr = Array(elt * (ct_pixels * elts_per_pixel), type=DataType.uint8()) + arr = fixed_size_list_array(tmp_arr, 4) + else: + arr = Array([elt] * (ct_pixels * elts_per_pixel), type=dtype) + img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE) + + _test_img_equals_pyarray(img, arr, mask, elts_per_pixel) + + +@pytest.mark.parametrize( + "mode, data_tp, mask", + ( + ("LA", UINT32, [0, 3]), + ("RGB", UINT32, [0, 1, 2]), + ("RGBA", UINT32, None), + ("CMYK", UINT32, None), + ("YCbCr", UINT32, [0, 1, 2]), + ("HSV", UINT32, [0, 1, 2]), + ("LA", INT32, [0, 3]), + ("RGB", INT32, [0, 1, 2]), + ("RGBA", INT32, None), + ("CMYK", INT32, None), + ("YCbCr", INT32, [0, 1, 2]), + ("HSV", INT32, [0, 1, 2]), + ), +) +def test_from_int32array(mode: str, data_tp: DataShape, mask: list[int] | None) -> None: + (dtype, elt, elts_per_pixel) = data_tp + + ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1] + arr = Array([elt] * (ct_pixels * elts_per_pixel), type=dtype) + img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE) + + _test_img_equals_int32_pyarray(img, arr, mask, elts_per_pixel) + + +@pytest.mark.parametrize( + "mode, metadata", + ( + ("LA", ["L", "X", "X", "A"]), + ("RGB", ["R", "G", "B", "X"]), + ("RGBX", ["R", "G", "B", "X"]), + ("RGBA", ["R", "G", "B", "A"]), + ("CMYK", ["C", "M", "Y", "K"]), + ("YCbCr", ["Y", "Cb", "Cr", "X"]), + ("HSV", ["H", "S", "V", "X"]), + ), +) +def test_image_metadata(mode: str, metadata: list[str]) -> None: + img = hopper(mode) + + arr = Array(img) # type: ignore[call-overload] + + assert arr.type.value_field.metadata + assert arr.type.value_field.metadata[b"image"] + + parsed_metadata = json.loads(arr.type.value_field.metadata[b"image"].decode("utf8")) + + assert "bands" in parsed_metadata + assert parsed_metadata["bands"] == metadata diff --git a/pyproject.toml b/pyproject.toml index 4e8623118..b1765e82c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,6 +58,8 @@ optional-dependencies.mic = [ ] optional-dependencies.test-arrow = [ "pyarrow", + "arro3-core", + "arro3-compute", ] optional-dependencies.tests = [ From 1a02d4ed5a1672218249ed129f43d4109b32e183 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 20 Jul 2025 13:01:39 +0200 Subject: [PATCH 112/309] lint fixes --- Tests/test_arro3.py | 7 ++++--- pyproject.toml | 4 ++-- src/libImaging/Arrow.c | 8 +------- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/Tests/test_arro3.py b/Tests/test_arro3.py index ddc7ecd0a..a7c755fc2 100644 --- a/Tests/test_arro3.py +++ b/Tests/test_arro3.py @@ -2,7 +2,6 @@ from __future__ import annotations import json from typing import Any, NamedTuple -from itertools import repeat, chain import pytest @@ -17,12 +16,12 @@ from .helper import ( TYPE_CHECKING = False if TYPE_CHECKING: - from arro3.core import Array, DataType, Field, fixed_size_list_array from arro3 import compute + from arro3.core import Array, DataType, Field, fixed_size_list_array else: arro3 = pytest.importorskip("arro3", reason="Arro3 not installed") - from arro3.core import Array, DataType, Field, fixed_size_list_array from arro3 import compute + from arro3.core import Array, DataType, Field, fixed_size_list_array TEST_IMAGE_SIZE = (10, 10) @@ -81,8 +80,10 @@ def _test_img_equals_int32_pyarray( for ix, elt in enumerate(mask): assert pixel[ix] == arr_pixel_tuple[elt] + fl_uint8_4_type = DataType.list(Field("_", DataType.uint8()).with_nullable(False), 4) + @pytest.mark.parametrize( "mode, dtype, mask", ( diff --git a/pyproject.toml b/pyproject.toml index b1765e82c..899e833e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,9 +57,9 @@ optional-dependencies.mic = [ "olefile", ] optional-dependencies.test-arrow = [ - "pyarrow", - "arro3-core", "arro3-compute", + "arro3-core", + "pyarrow", ] optional-dependencies.tests = [ diff --git a/src/libImaging/Arrow.c b/src/libImaging/Arrow.c index ff98dfb51..4519243ae 100644 --- a/src/libImaging/Arrow.c +++ b/src/libImaging/Arrow.c @@ -98,19 +98,13 @@ single_band_json(Imaging im) { return NULL; } - err = PyOS_snprintf( - json, - len, - format, - im->band_names[0] - ); + err = PyOS_snprintf(json, len, format, im->band_names[0]); if (err < 0) { return NULL; } return json; } - char * assemble_metadata(const char *band_json) { /* format is From 28c7645d8bab0a746f60e37cbfde1c79b8f69ca9 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 21 Jul 2025 11:19:45 +0200 Subject: [PATCH 113/309] Added tests for integration with nanoarrow --- Tests/test_nanoarrow.py | 281 ++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 1 + 2 files changed, 282 insertions(+) create mode 100644 Tests/test_nanoarrow.py diff --git a/Tests/test_nanoarrow.py b/Tests/test_nanoarrow.py new file mode 100644 index 000000000..e0ae11baa --- /dev/null +++ b/Tests/test_nanoarrow.py @@ -0,0 +1,281 @@ +from __future__ import annotations + +import json +from typing import Any, NamedTuple + +import pytest + +from PIL import Image + +from .helper import ( + assert_deep_equal, + assert_image_equal, + hopper, + is_big_endian, +) + +TYPE_CHECKING = False +if TYPE_CHECKING: + import nanoarrow +else: + nanoarrow = pytest.importorskip("nanoarrow", reason="Nanoarrow not installed") + +TEST_IMAGE_SIZE = (10, 10) + + +def _test_img_equals_pyarray( + img: Image.Image, arr: Any, mask: list[int] | None, elts_per_pixel: int = 1 +) -> None: + assert img.height * img.width * elts_per_pixel == len(arr) + px = img.load() + assert px is not None + if elts_per_pixel > 1 and mask is None: + # have to do element-wise comparison when we're comparing + # flattened r,g,b,a to a pixel. + mask = list(range(elts_per_pixel)) + for x in range(0, img.size[0], int(img.size[0] / 10)): + for y in range(0, img.size[1], int(img.size[1] / 10)): + if mask: + pixel = px[x, y] + assert isinstance(pixel, tuple) + for ix, elt in enumerate(mask): + if elts_per_pixel == 1: + assert pixel[ix] == arr[y * img.width + x].as_py()[elt] + else: + assert ( + pixel[ix] + == arr[(y * img.width + x) * elts_per_pixel + elt].as_py() + ) + else: + assert_deep_equal(px[x, y], arr[y * img.width + x].as_py()) + + +def _test_img_equals_int32_pyarray( + img: Image.Image, arr: Any, mask: list[int] | None, elts_per_pixel: int = 1 +) -> None: + assert img.height * img.width * elts_per_pixel == len(arr) + px = img.load() + assert px is not None + if mask is None: + # have to do element-wise comparison when we're comparing + # flattened rgba in an uint32 to a pixel. + mask = list(range(elts_per_pixel)) + for x in range(0, img.size[0], int(img.size[0] / 10)): + for y in range(0, img.size[1], int(img.size[1] / 10)): + pixel = px[x, y] + assert isinstance(pixel, tuple) + arr_pixel_int = arr[y * img.width + x].as_py() + arr_pixel_tuple = ( + arr_pixel_int % 256, + (arr_pixel_int // 256) % 256, + (arr_pixel_int // 256**2) % 256, + (arr_pixel_int // 256**3), + ) + if is_big_endian(): + arr_pixel_tuple = arr_pixel_tuple[::-1] + + for ix, elt in enumerate(mask): + assert pixel[ix] == arr_pixel_tuple[elt] + + +fl_uint8_4_type = nanoarrow.fixed_size_list(value_type=nanoarrow.uint8(nullable=False), + list_size=4, + nullable=False) + + +@pytest.mark.parametrize( + "mode, dtype, mask", + ( + ("L", nanoarrow.uint8(nullable=False), None), + ("I", nanoarrow.int32(nullable=False), None), + ("F", nanoarrow.float32(nullable=False), None), + ("LA", fl_uint8_4_type, [0, 3]), + ("RGB", fl_uint8_4_type, [0, 1, 2]), + ("RGBA", fl_uint8_4_type, None), + ("RGBX", fl_uint8_4_type, None), + ("CMYK", fl_uint8_4_type, None), + ("YCbCr", fl_uint8_4_type, [0, 1, 2]), + ("HSV", fl_uint8_4_type, [0, 1, 2]), + ), +) +def test_to_array(mode: str, dtype: nanoarrow, mask: list[int] | None) -> None: + img = hopper(mode) + + # Resize to non-square + img = img.crop((3, 0, 124, 127)) + assert img.size == (121, 127) + + arr = nanoarrow.Array(img) # type: ignore[call-overload] + _test_img_equals_pyarray(img, arr, mask) + assert arr.schema.type == dtype.type + assert arr.schema.nullable == dtype.nullable + + reloaded = Image.fromarrow(arr, mode, img.size) + + assert reloaded + + assert_image_equal(img, reloaded) + + +def test_lifetime() -> None: + # valgrind shouldn't error out here. + # arrays should be accessible after the image is deleted. + + img = hopper("L") + + arr_1 = nanoarrow.Array(img) # type: ignore[call-overload] + arr_2 = nanoarrow.Array(img) # type: ignore[call-overload] + + del img + + assert sum(arr_1.iter_py()) > 0 + del arr_1 + + assert sum(arr_2.iter_py()) > 0 + del arr_2 + + +def test_lifetime2() -> None: + # valgrind shouldn't error out here. + # img should remain after the arrays are collected. + + img = hopper("L") + + arr_1 = nanoarrow.Array(img) # type: ignore[call-overload] + arr_2 = nanoarrow.Array(img) # type: ignore[call-overload] + + assert sum(arr_1.iter_py()) > 0 + del arr_1 + + assert sum(arr_2.iter_py()) > 0 + del arr_2 + + img2 = img.copy() + px = img2.load() + assert px # make mypy happy + assert isinstance(px[0, 0], int) + + +class DataShape(NamedTuple): + dtype: nanoarrow + # Strictly speaking, elt should be a pixel or pixel component, so + # list[uint8][4], float, int, uint32, uint8, etc. But more + # correctly, it should be exactly the dtype from the line above. + elt: Any + elts_per_pixel: int + + +UINT_ARR = DataShape( + dtype=fl_uint8_4_type, + elt=[1, 2, 3, 4], # array of 4 uint8 per pixel + elts_per_pixel=1, # only one array per pixel +) + +UINT = DataShape( + dtype=nanoarrow.uint8(), + elt=3, # one uint8, + elts_per_pixel=4, # but repeated 4x per pixel +) + +UINT32 = DataShape( + dtype=nanoarrow.uint32(), + elt=0xABCDEF45, # one packed int, doesn't fit in a int32 > 0x80000000 + elts_per_pixel=1, # one per pixel +) + +INT32 = DataShape( + dtype=nanoarrow.uint32(), + elt=0x12CDEF45, # one packed int + elts_per_pixel=1, # one per pixel +) + + +@pytest.mark.xfail(reason="Support for nested array creation is not available in nanoarrow/python") +@pytest.mark.parametrize( + "mode, data_tp, mask", + ( + ("L", DataShape(nanoarrow.uint8(), 3, 1), None), + ("I", DataShape(nanoarrow.int32(), 1 << 24, 1), None), + ("F", DataShape(nanoarrow.float32(), 3.14159, 1), None), + ("LA", UINT_ARR, [0, 3]), + ("LA", UINT, [0, 3]), + ("RGB", UINT_ARR, [0, 1, 2]), + ("RGBA", UINT_ARR, None), + ("CMYK", UINT_ARR, None), + ("YCbCr", UINT_ARR, [0, 1, 2]), + ("HSV", UINT_ARR, [0, 1, 2]), + ("RGB", UINT, [0, 1, 2]), + ("RGBA", UINT, None), + ("CMYK", UINT, None), + ("YCbCr", UINT, [0, 1, 2]), + ("HSV", UINT, [0, 1, 2]), + ), +) +def test_fromarray(mode: str, data_tp: DataShape, mask: list[int] | None) -> None: + (dtype, elt, elts_per_pixel) = data_tp + + ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1] + if dtype == fl_uint8_4_type: + # Apparently there's no good way to create this array from python using nanoarrow + # https://github.com/apache/arrow-nanoarrow/issues/620 + # the following lines will fail. + tmp_arr = nanoarrow.c_array(elt * (ct_pixels * elts_per_pixel), schema=nanoarrow.uint8()) + arr = nanoarrow.Array(tmp_arr, schema=dtype) + else: + arr = nanoarrow.Array(nanoarrow.c_array([elt] * (ct_pixels * elts_per_pixel), schema=dtype)) + img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE) + + _test_img_equals_pyarray(img, arr, mask, elts_per_pixel) + + +@pytest.mark.parametrize( + "mode, data_tp, mask", + ( + ("LA", UINT32, [0, 3]), + ("RGB", UINT32, [0, 1, 2]), + ("RGBA", UINT32, None), + ("CMYK", UINT32, None), + ("YCbCr", UINT32, [0, 1, 2]), + ("HSV", UINT32, [0, 1, 2]), + ("LA", INT32, [0, 3]), + ("RGB", INT32, [0, 1, 2]), + ("RGBA", INT32, None), + ("CMYK", INT32, None), + ("YCbCr", INT32, [0, 1, 2]), + ("HSV", INT32, [0, 1, 2]), + ), +) +def test_from_int32array(mode: str, data_tp: DataShape, mask: list[int] | None) -> None: + (dtype, elt, elts_per_pixel) = data_tp + + ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1] + arr = nanoarrow.Array(nanoarrow.c_array([elt] * (ct_pixels * elts_per_pixel), schema=dtype)) + img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE) + + _test_img_equals_int32_pyarray(img, arr, mask, elts_per_pixel) + + +@pytest.mark.parametrize( + "mode, metadata", + ( + ("LA", ["L", "X", "X", "A"]), + ("RGB", ["R", "G", "B", "X"]), + ("RGBX", ["R", "G", "B", "X"]), + ("RGBA", ["R", "G", "B", "A"]), + ("CMYK", ["C", "M", "Y", "K"]), + ("YCbCr", ["Y", "Cb", "Cr", "X"]), + ("HSV", ["H", "S", "V", "X"]), + ), +) +def test_image_nested_metadata(mode: str, metadata: list[str]) -> None: + img = hopper(mode) + + arr = nanoarrow.Array(img) # type: ignore[call-overload] + + assert arr.schema.value_type.metadata + assert arr.schema.value_type.metadata[b"image"] + + parsed_metadata = json.loads(arr.schema.value_type.metadata[b"image"].decode("utf8")) + + assert "bands" in parsed_metadata + assert parsed_metadata["bands"] == metadata diff --git a/pyproject.toml b/pyproject.toml index 899e833e3..5a4f752b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,6 +60,7 @@ optional-dependencies.test-arrow = [ "arro3-compute", "arro3-core", "pyarrow", + "nanoarrow", ] optional-dependencies.tests = [ From 7d2abbdcf91f01fd2bc83dace639f5eb5f7a562c Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 21 Jul 2025 11:22:45 +0200 Subject: [PATCH 114/309] lint. --- Tests/test_nanoarrow.py | 26 ++++++++++++++++++-------- pyproject.toml | 2 +- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Tests/test_nanoarrow.py b/Tests/test_nanoarrow.py index e0ae11baa..3dc540043 100644 --- a/Tests/test_nanoarrow.py +++ b/Tests/test_nanoarrow.py @@ -78,9 +78,9 @@ def _test_img_equals_int32_pyarray( assert pixel[ix] == arr_pixel_tuple[elt] -fl_uint8_4_type = nanoarrow.fixed_size_list(value_type=nanoarrow.uint8(nullable=False), - list_size=4, - nullable=False) +fl_uint8_4_type = nanoarrow.fixed_size_list( + value_type=nanoarrow.uint8(nullable=False), list_size=4, nullable=False +) @pytest.mark.parametrize( @@ -190,7 +190,9 @@ INT32 = DataShape( ) -@pytest.mark.xfail(reason="Support for nested array creation is not available in nanoarrow/python") +@pytest.mark.xfail( + reason="Support for nested array creation is not available in nanoarrow/python" +) @pytest.mark.parametrize( "mode, data_tp, mask", ( @@ -219,10 +221,14 @@ def test_fromarray(mode: str, data_tp: DataShape, mask: list[int] | None) -> Non # Apparently there's no good way to create this array from python using nanoarrow # https://github.com/apache/arrow-nanoarrow/issues/620 # the following lines will fail. - tmp_arr = nanoarrow.c_array(elt * (ct_pixels * elts_per_pixel), schema=nanoarrow.uint8()) + tmp_arr = nanoarrow.c_array( + elt * (ct_pixels * elts_per_pixel), schema=nanoarrow.uint8() + ) arr = nanoarrow.Array(tmp_arr, schema=dtype) else: - arr = nanoarrow.Array(nanoarrow.c_array([elt] * (ct_pixels * elts_per_pixel), schema=dtype)) + arr = nanoarrow.Array( + nanoarrow.c_array([elt] * (ct_pixels * elts_per_pixel), schema=dtype) + ) img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE) _test_img_equals_pyarray(img, arr, mask, elts_per_pixel) @@ -249,7 +255,9 @@ def test_from_int32array(mode: str, data_tp: DataShape, mask: list[int] | None) (dtype, elt, elts_per_pixel) = data_tp ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1] - arr = nanoarrow.Array(nanoarrow.c_array([elt] * (ct_pixels * elts_per_pixel), schema=dtype)) + arr = nanoarrow.Array( + nanoarrow.c_array([elt] * (ct_pixels * elts_per_pixel), schema=dtype) + ) img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE) _test_img_equals_int32_pyarray(img, arr, mask, elts_per_pixel) @@ -275,7 +283,9 @@ def test_image_nested_metadata(mode: str, metadata: list[str]) -> None: assert arr.schema.value_type.metadata assert arr.schema.value_type.metadata[b"image"] - parsed_metadata = json.loads(arr.schema.value_type.metadata[b"image"].decode("utf8")) + parsed_metadata = json.loads( + arr.schema.value_type.metadata[b"image"].decode("utf8") + ) assert "bands" in parsed_metadata assert parsed_metadata["bands"] == metadata diff --git a/pyproject.toml b/pyproject.toml index 5a4f752b3..c450e4274 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,8 +59,8 @@ optional-dependencies.mic = [ optional-dependencies.test-arrow = [ "arro3-compute", "arro3-core", - "pyarrow", "nanoarrow", + "pyarrow", ] optional-dependencies.tests = [ From c07fe6e94313bf4172c670a8d6c4c3e7c78ac469 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 21 Jul 2025 11:33:14 +0200 Subject: [PATCH 115/309] Added flat image metadata tests This metadata is available in nanoarrow, but not pyarrow or arro3 --- Tests/test_nanoarrow.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Tests/test_nanoarrow.py b/Tests/test_nanoarrow.py index 3dc540043..f0d609545 100644 --- a/Tests/test_nanoarrow.py +++ b/Tests/test_nanoarrow.py @@ -289,3 +289,26 @@ def test_image_nested_metadata(mode: str, metadata: list[str]) -> None: assert "bands" in parsed_metadata assert parsed_metadata["bands"] == metadata + +@pytest.mark.parametrize( + "mode, metadata", + ( + ("L", ["L"]), + ("I", ["I"]), + ("F", ["F"]), + ), +) +def test_image_flat_metadata(mode: str, metadata: list[str]) -> None: + img = hopper(mode) + + arr = nanoarrow.Array(img) # type: ignore[call-overload] + + assert arr.schema.metadata + assert arr.schema.metadata[b"image"] + + parsed_metadata = json.loads( + arr.schema.metadata[b"image"].decode("utf8") + ) + + assert "bands" in parsed_metadata + assert parsed_metadata["bands"] == metadata From 9e415c787639d50fcfbd9a519174a60e3eb6c1e9 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 21 Jul 2025 17:24:52 +0200 Subject: [PATCH 116/309] A way to make nested arrays in nano arrow but detouring through a buffer --- Tests/test_nanoarrow.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Tests/test_nanoarrow.py b/Tests/test_nanoarrow.py index f0d609545..b08333ae9 100644 --- a/Tests/test_nanoarrow.py +++ b/Tests/test_nanoarrow.py @@ -190,9 +190,6 @@ INT32 = DataShape( ) -@pytest.mark.xfail( - reason="Support for nested array creation is not available in nanoarrow/python" -) @pytest.mark.parametrize( "mode, data_tp, mask", ( @@ -218,13 +215,13 @@ def test_fromarray(mode: str, data_tp: DataShape, mask: list[int] | None) -> Non ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1] if dtype == fl_uint8_4_type: - # Apparently there's no good way to create this array from python using nanoarrow - # https://github.com/apache/arrow-nanoarrow/issues/620 - # the following lines will fail. - tmp_arr = nanoarrow.c_array( + tmp_arr = nanoarrow.Array( elt * (ct_pixels * elts_per_pixel), schema=nanoarrow.uint8() ) - arr = nanoarrow.Array(tmp_arr, schema=dtype) + c_array = nanoarrow.c_array_from_buffers( + dtype, ct_pixels, buffers=[], children=[tmp_arr] + ) + arr = nanoarrow.Array(c_array) else: arr = nanoarrow.Array( nanoarrow.c_array([elt] * (ct_pixels * elts_per_pixel), schema=dtype) @@ -290,6 +287,7 @@ def test_image_nested_metadata(mode: str, metadata: list[str]) -> None: assert "bands" in parsed_metadata assert parsed_metadata["bands"] == metadata + @pytest.mark.parametrize( "mode, metadata", ( @@ -306,9 +304,7 @@ def test_image_flat_metadata(mode: str, metadata: list[str]) -> None: assert arr.schema.metadata assert arr.schema.metadata[b"image"] - parsed_metadata = json.loads( - arr.schema.metadata[b"image"].decode("utf8") - ) + parsed_metadata = json.loads(arr.schema.metadata[b"image"].decode("utf8")) assert "bands" in parsed_metadata assert parsed_metadata["bands"] == metadata From f4d86e4f44dfe799b0a2d6484fa53945ab89220e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 24 Jul 2025 07:27:39 +1000 Subject: [PATCH 117/309] Use teardown_method --- Tests/test_image_access.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index 2609b1e34..a847264d2 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -276,11 +276,10 @@ class TestEmbeddable: except Exception: pytest.skip("Compiler could not be initialized") - try: - with open("embed_pil.c", "w", encoding="utf-8") as fh: - home = sys.prefix.replace("\\", "\\\\") - fh.write( - f""" + with open("embed_pil.c", "w", encoding="utf-8") as fh: + home = sys.prefix.replace("\\", "\\\\") + fh.write( + f""" #include "Python.h" int main(int argc, char* argv[]) @@ -302,19 +301,20 @@ int main(int argc, char* argv[]) return 0; }} """ - ) + ) - objects = compiler.compile(["embed_pil.c"]) - compiler.link_executable(objects, "embed_pil") + objects = compiler.compile(["embed_pil.c"]) + compiler.link_executable(objects, "embed_pil") - env = os.environ.copy() - env["PATH"] = sys.prefix + ";" + env["PATH"] + env = os.environ.copy() + env["PATH"] = sys.prefix + ";" + env["PATH"] - # Do not display the Windows Error Reporting dialog - getattr(ctypes, "windll").kernel32.SetErrorMode(0x0002) + # Do not display the Windows Error Reporting dialog + getattr(ctypes, "windll").kernel32.SetErrorMode(0x0002) - process = subprocess.Popen(["embed_pil.exe"], env=env) - process.communicate() - assert process.returncode == 0 - finally: - os.remove("embed_pil.c") + process = subprocess.Popen(["embed_pil.exe"], env=env) + process.communicate() + assert process.returncode == 0 + + def teardown_method(self) -> None: + os.remove("embed_pil.c") From 103a5a0b5913ab403eeaaf0b589278bc8e2b067b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 23 Jul 2025 18:22:10 +1000 Subject: [PATCH 118/309] Fixed ZeroDivisionError --- Tests/test_imagestat.py | 10 ++++++++++ src/PIL/ImageStat.py | 13 ++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/Tests/test_imagestat.py b/Tests/test_imagestat.py index 0dfbc5a2a..0baab7ce2 100644 --- a/Tests/test_imagestat.py +++ b/Tests/test_imagestat.py @@ -57,3 +57,13 @@ def test_constant() -> None: assert st.rms[0] == 128 assert st.var[0] == 0 assert st.stddev[0] == 0 + + +def test_zero_count() -> None: + im = Image.new("L", (0, 0)) + + st = ImageStat.Stat(im) + + assert st.mean == [0] + assert st.rms == [0] + assert st.var == [0] diff --git a/src/PIL/ImageStat.py b/src/PIL/ImageStat.py index 8bc504526..3a1044ba4 100644 --- a/src/PIL/ImageStat.py +++ b/src/PIL/ImageStat.py @@ -120,7 +120,7 @@ class Stat: @cached_property def mean(self) -> list[float]: """Average (arithmetic mean) pixel level for each band in the image.""" - return [self.sum[i] / self.count[i] for i in self.bands] + return [self.sum[i] / self.count[i] if self.count[i] else 0 for i in self.bands] @cached_property def median(self) -> list[int]: @@ -141,13 +141,20 @@ class Stat: @cached_property def rms(self) -> list[float]: """RMS (root-mean-square) for each band in the image.""" - return [math.sqrt(self.sum2[i] / self.count[i]) for i in self.bands] + return [ + math.sqrt(self.sum2[i] / self.count[i]) if self.count[i] else 0 + for i in self.bands + ] @cached_property def var(self) -> list[float]: """Variance for each band in the image.""" return [ - (self.sum2[i] - (self.sum[i] ** 2.0) / self.count[i]) / self.count[i] + ( + (self.sum2[i] - (self.sum[i] ** 2.0) / self.count[i]) / self.count[i] + if self.count[i] + else 0 + ) for i in self.bands ] From 24681a39270ee09dc869711d39b9ae183e981ae7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 21 Jul 2025 17:09:26 +1000 Subject: [PATCH 119/309] Added ImageText --- Tests/test_imagetext.py | 41 ++++ docs/reference/ImageText.rst | 30 +++ docs/reference/index.rst | 1 + src/PIL/ImageDraw.py | 390 +++++++++-------------------------- src/PIL/ImageText.py | 318 ++++++++++++++++++++++++++++ src/PIL/_typing.py | 2 + 6 files changed, 490 insertions(+), 292 deletions(-) create mode 100644 Tests/test_imagetext.py create mode 100644 docs/reference/ImageText.rst create mode 100644 src/PIL/ImageText.py diff --git a/Tests/test_imagetext.py b/Tests/test_imagetext.py new file mode 100644 index 000000000..3a3a58975 --- /dev/null +++ b/Tests/test_imagetext.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +import pytest + +from PIL import ImageFont, ImageText + +from .helper import skip_unless_feature + +FONT_PATH = "Tests/fonts/FreeMono.ttf" + + +@pytest.fixture( + scope="module", + params=[ + pytest.param(ImageFont.Layout.BASIC), + pytest.param(ImageFont.Layout.RAQM, marks=skip_unless_feature("raqm")), + ], +) +def layout_engine(request: pytest.FixtureRequest) -> ImageFont.Layout: + return request.param + + +@pytest.fixture(scope="module") +def font(layout_engine: ImageFont.Layout) -> ImageFont.FreeTypeFont: + return ImageFont.truetype(FONT_PATH, 20, layout_engine=layout_engine) + + +def test_get_length(font: ImageFont.FreeTypeFont) -> None: + assert ImageText.ImageText("A", font).get_length() == 12 + assert ImageText.ImageText("AB", font).get_length() == 24 + assert ImageText.ImageText("M", font).get_length() == 12 + assert ImageText.ImageText("y", font).get_length() == 12 + assert ImageText.ImageText("a", font).get_length() == 12 + + +def test_get_bbox(font: ImageFont.FreeTypeFont) -> None: + assert ImageText.ImageText("A", font).get_bbox() == (0, 4, 12, 16) + assert ImageText.ImageText("AB", font).get_bbox() == (0, 4, 24, 16) + assert ImageText.ImageText("M", font).get_bbox() == (0, 4, 12, 16) + assert ImageText.ImageText("y", font).get_bbox() == (0, 7, 12, 20) + assert ImageText.ImageText("a", font).get_bbox() == (0, 7, 12, 16) diff --git a/docs/reference/ImageText.rst b/docs/reference/ImageText.rst new file mode 100644 index 000000000..ad5439751 --- /dev/null +++ b/docs/reference/ImageText.rst @@ -0,0 +1,30 @@ +.. py:module:: PIL.ImageText +.. py:currentmodule:: PIL.ImageText + +:py:mod:`~PIL.ImageText` module +=============================== + +The :py:mod:`~PIL.ImageText` module defines a class with the same name. Instances of +this class provide a way to use fonts with text strings or bytes. The result is a +simple API to apply styling to pieces of text and measure them. + +Example +------- + +:: + + from PIL import Image, ImageDraw, ImageFont, ImageText + font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 24) + + text = ImageText.ImageText("Hello world", font) + text.embed_color() + text.stroke(2, "#0f0") + + print(text.get_length()) # 154.0 + print(text.get_bbox()) # (-2, 3, 156, 22) + +Methods +------- + +.. autoclass:: PIL.ImageText.ImageText + :members: diff --git a/docs/reference/index.rst b/docs/reference/index.rst index effcd3c46..1ce26c909 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -24,6 +24,7 @@ Reference ImageSequence ImageShow ImageStat + ImageText ImageTk ImageTransform ImageWin diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index e95fa91f8..35ecbfb78 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -35,10 +35,10 @@ import math import struct from collections.abc import Sequence from types import ModuleType -from typing import Any, AnyStr, Callable, Union, cast +from typing import Any, AnyStr, Callable, cast -from . import Image, ImageColor -from ._typing import Coords +from . import Image, ImageColor, ImageText +from ._typing import Coords, _Ink # experimental access to the outline API Outline: Callable[[], Image.core._Outline] = Image.core.outline @@ -47,8 +47,6 @@ TYPE_CHECKING = False if TYPE_CHECKING: from . import ImageDraw2, ImageFont -_Ink = Union[float, tuple[int, ...], str] - """ A simple 2D drawing interface for PIL images.

@@ -536,11 +534,6 @@ class ImageDraw: right[3] -= r + 1 self.draw.draw_rectangle(right, ink, 1) - def _multiline_check(self, text: AnyStr) -> bool: - split_character = "\n" if isinstance(text, str) else b"\n" - - return split_character in text - def text( self, xy: tuple[float, float], @@ -565,29 +558,15 @@ class ImageDraw: **kwargs: Any, ) -> None: """Draw text.""" - if embedded_color and self.mode not in ("RGB", "RGBA"): - msg = "Embedded color supported only in RGB and RGBA modes" - raise ValueError(msg) - if font is None: font = self._getfont(kwargs.get("font_size")) - - if self._multiline_check(text): - return self.multiline_text( - xy, - text, - fill, - font, - anchor, - spacing, - align, - direction, - features, - language, - stroke_width, - stroke_fill, - embedded_color, - ) + imagetext = ImageText.ImageText( + text, font, self.mode, spacing, direction, features, language + ) + if embedded_color: + imagetext.embed_color() + if stroke_width: + imagetext.stroke(stroke_width, stroke_fill) def getink(fill: _Ink | None) -> int: ink, fill_ink = self._getink(fill) @@ -596,70 +575,79 @@ class ImageDraw: return fill_ink return ink - def draw_text(ink: int, stroke_width: float = 0) -> None: - mode = self.fontmode - if stroke_width == 0 and embedded_color: - mode = "RGBA" - coord = [] - for i in range(2): - coord.append(int(xy[i])) - start = (math.modf(xy[0])[0], math.modf(xy[1])[0]) - try: - mask, offset = font.getmask2( # type: ignore[union-attr,misc] - text, - mode, - direction=direction, - features=features, - language=language, - stroke_width=stroke_width, - stroke_filled=True, - anchor=anchor, - ink=ink, - start=start, - *args, - **kwargs, - ) - coord = [coord[0] + offset[0], coord[1] + offset[1]] - except AttributeError: + ink = getink(fill) + if ink is None: + return + + stroke_ink = None + if imagetext.stroke_width: + stroke_ink = ( + getink(imagetext.stroke_fill) + if imagetext.stroke_fill is not None + else ink + ) + + for xy, anchor, line in imagetext._split(xy, anchor, align): + + def draw_text(ink: int, stroke_width: float = 0) -> None: + mode = self.fontmode + if stroke_width == 0 and embedded_color: + mode = "RGBA" + coord = [] + for i in range(2): + coord.append(int(xy[i])) + start = (math.modf(xy[0])[0], math.modf(xy[1])[0]) try: - mask = font.getmask( # type: ignore[misc] - text, + mask, offset = imagetext.font.getmask2( # type: ignore[union-attr,misc] + line, mode, - direction, - features, - language, - stroke_width, - anchor, - ink, + direction=direction, + features=features, + language=language, + stroke_width=stroke_width, + stroke_filled=True, + anchor=anchor, + ink=ink, start=start, *args, **kwargs, ) - except TypeError: - mask = font.getmask(text) - if mode == "RGBA": - # font.getmask2(mode="RGBA") returns color in RGB bands and mask in A - # extract mask and set text alpha - color, mask = mask, mask.getband(3) - ink_alpha = struct.pack("i", ink)[3] - color.fillband(3, ink_alpha) - x, y = coord - if self.im is not None: - self.im.paste( - color, (x, y, x + mask.size[0], y + mask.size[1]), mask - ) - else: - self.draw.draw_bitmap(coord, mask, ink) - - ink = getink(fill) - if ink is not None: - stroke_ink = None - if stroke_width: - stroke_ink = getink(stroke_fill) if stroke_fill is not None else ink + coord = [coord[0] + offset[0], coord[1] + offset[1]] + except AttributeError: + try: + mask = imagetext.font.getmask( # type: ignore[misc] + line, + mode, + direction, + features, + language, + stroke_width, + anchor, + ink, + start=start, + *args, + **kwargs, + ) + except TypeError: + mask = imagetext.font.getmask(line) + if mode == "RGBA": + # imagetext.font.getmask2(mode="RGBA") + # returns color in RGB bands and mask in A + # extract mask and set text alpha + color, mask = mask, mask.getband(3) + ink_alpha = struct.pack("i", ink)[3] + color.fillband(3, ink_alpha) + x, y = coord + if self.im is not None: + self.im.paste( + color, (x, y, x + mask.size[0], y + mask.size[1]), mask + ) + else: + self.draw.draw_bitmap(coord, mask, ink) if stroke_ink is not None: # Draw stroked text - draw_text(stroke_ink, stroke_width) + draw_text(stroke_ink, imagetext.stroke_width) # Draw normal text if ink != stroke_ink: @@ -668,132 +656,6 @@ class ImageDraw: # Only draw normal text draw_text(ink) - def _prepare_multiline_text( - self, - xy: tuple[float, float], - text: AnyStr, - font: ( - ImageFont.ImageFont - | ImageFont.FreeTypeFont - | ImageFont.TransposedFont - | None - ), - anchor: str | None, - spacing: float, - align: str, - direction: str | None, - features: list[str] | None, - language: str | None, - stroke_width: float, - embedded_color: bool, - font_size: float | None, - ) -> tuple[ - ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont, - list[tuple[tuple[float, float], str, AnyStr]], - ]: - if anchor is None: - anchor = "lt" if direction == "ttb" else "la" - elif len(anchor) != 2: - msg = "anchor must be a 2 character string" - raise ValueError(msg) - elif anchor[1] in "tb" and direction != "ttb": - msg = "anchor not supported for multiline text" - raise ValueError(msg) - - if font is None: - font = self._getfont(font_size) - - lines = text.split("\n" if isinstance(text, str) else b"\n") - line_spacing = ( - self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3] - + stroke_width - + spacing - ) - - top = xy[1] - parts = [] - if direction == "ttb": - left = xy[0] - for line in lines: - parts.append(((left, top), anchor, line)) - left += line_spacing - else: - widths = [] - max_width: float = 0 - for line in lines: - line_width = self.textlength( - line, - font, - direction=direction, - features=features, - language=language, - embedded_color=embedded_color, - ) - widths.append(line_width) - max_width = max(max_width, line_width) - - if anchor[1] == "m": - top -= (len(lines) - 1) * line_spacing / 2.0 - elif anchor[1] == "d": - top -= (len(lines) - 1) * line_spacing - - for idx, line in enumerate(lines): - left = xy[0] - width_difference = max_width - widths[idx] - - # align by align parameter - if align in ("left", "justify"): - pass - elif align == "center": - left += width_difference / 2.0 - elif align == "right": - left += width_difference - else: - msg = 'align must be "left", "center", "right" or "justify"' - raise ValueError(msg) - - if ( - align == "justify" - and width_difference != 0 - and idx != len(lines) - 1 - ): - words = line.split(" " if isinstance(text, str) else b" ") - if len(words) > 1: - # align left by anchor - if anchor[0] == "m": - left -= max_width / 2.0 - elif anchor[0] == "r": - left -= max_width - - word_widths = [ - self.textlength( - word, - font, - direction=direction, - features=features, - language=language, - embedded_color=embedded_color, - ) - for word in words - ] - word_anchor = "l" + anchor[1] - width_difference = max_width - sum(word_widths) - for i, word in enumerate(words): - parts.append(((left, top), word_anchor, word)) - left += word_widths[i] + width_difference / (len(words) - 1) - top += line_spacing - continue - - # align left by anchor - if anchor[0] == "m": - left -= width_difference / 2.0 - elif anchor[0] == "r": - left -= width_difference - parts.append(((left, top), anchor, line)) - top += line_spacing - - return font, parts - def multiline_text( self, xy: tuple[float, float], @@ -817,9 +679,10 @@ class ImageDraw: *, font_size: float | None = None, ) -> None: - font, lines = self._prepare_multiline_text( + return self.text( xy, text, + fill, font, anchor, spacing, @@ -828,25 +691,11 @@ class ImageDraw: features, language, stroke_width, + stroke_fill, embedded_color, - font_size, + font_size=font_size, ) - for xy, anchor, line in lines: - self.text( - xy, - line, - fill, - font, - anchor, - direction=direction, - features=features, - language=language, - stroke_width=stroke_width, - stroke_fill=stroke_fill, - embedded_color=embedded_color, - ) - def textlength( self, text: AnyStr, @@ -864,17 +713,19 @@ class ImageDraw: font_size: float | None = None, ) -> float: """Get the length of a given string, in pixels with 1/64 precision.""" - if self._multiline_check(text): - msg = "can't measure length of multiline text" - raise ValueError(msg) - if embedded_color and self.mode not in ("RGB", "RGBA"): - msg = "Embedded color supported only in RGB and RGBA modes" - raise ValueError(msg) - if font is None: font = self._getfont(font_size) - mode = "RGBA" if embedded_color else self.fontmode - return font.getlength(text, mode, direction, features, language) + imagetext = ImageText.ImageText( + text, + font, + self.mode, + direction=direction, + features=features, + language=language, + ) + if embedded_color: + imagetext.embed_color() + return imagetext.get_length() def textbbox( self, @@ -898,33 +749,16 @@ class ImageDraw: font_size: float | None = None, ) -> tuple[float, float, float, float]: """Get the bounding box of a given string, in pixels.""" - if embedded_color and self.mode not in ("RGB", "RGBA"): - msg = "Embedded color supported only in RGB and RGBA modes" - raise ValueError(msg) - if font is None: font = self._getfont(font_size) - - if self._multiline_check(text): - return self.multiline_textbbox( - xy, - text, - font, - anchor, - spacing, - align, - direction, - features, - language, - stroke_width, - embedded_color, - ) - - mode = "RGBA" if embedded_color else self.fontmode - bbox = font.getbbox( - text, mode, direction, features, language, stroke_width, anchor + imagetext = ImageText.ImageText( + text, font, self.mode, spacing, direction, features, language ) - return bbox[0] + xy[0], bbox[1] + xy[1], bbox[2] + xy[0], bbox[3] + xy[1] + if embedded_color: + imagetext.embed_color() + if stroke_width: + imagetext.stroke(stroke_width) + return imagetext.get_bbox(xy, anchor, align) def multiline_textbbox( self, @@ -947,7 +781,7 @@ class ImageDraw: *, font_size: float | None = None, ) -> tuple[float, float, float, float]: - font, lines = self._prepare_multiline_text( + return self.textbbox( xy, text, font, @@ -959,37 +793,9 @@ class ImageDraw: language, stroke_width, embedded_color, - font_size, + font_size=font_size, ) - bbox: tuple[float, float, float, float] | None = None - - for xy, anchor, line in lines: - bbox_line = self.textbbox( - xy, - line, - font, - anchor, - direction=direction, - features=features, - language=language, - stroke_width=stroke_width, - embedded_color=embedded_color, - ) - if bbox is None: - bbox = bbox_line - else: - bbox = ( - min(bbox[0], bbox_line[0]), - min(bbox[1], bbox_line[1]), - max(bbox[2], bbox_line[2]), - max(bbox[3], bbox_line[3]), - ) - - if bbox is None: - return xy[0], xy[1], xy[0], xy[1] - return bbox - def Draw(im: Image.Image, mode: str | None = None) -> ImageDraw: """ diff --git a/src/PIL/ImageText.py b/src/PIL/ImageText.py new file mode 100644 index 000000000..9bb31a1c8 --- /dev/null +++ b/src/PIL/ImageText.py @@ -0,0 +1,318 @@ +from __future__ import annotations + +from . import ImageFont +from ._typing import _Ink + + +class ImageText: + def __init__( + self, + text: str | bytes, + font: ( + ImageFont.ImageFont + | ImageFont.FreeTypeFont + | ImageFont.TransposedFont + | None + ) = None, + mode: str = "RGB", + spacing: float = 4, + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + ) -> None: + """ + :param text: String to be drawn. + :param font: Either an :py:class:`~PIL.ImageFont.ImageFont` instance, + :py:class:`~PIL.ImageFont.FreeTypeFont` instance, + :py:class:`~PIL.ImageFont.TransposedFont` instance or ``None``. If + ``None``, the default font from :py:meth:`.ImageFont.load_default` + will be used. + :param mode: The image mode this will be used with. + :param spacing: The number of pixels between lines. + :param direction: Direction of the text. It can be ``"rtl"`` (right to left), + ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom). + Requires libraqm. + :param features: A list of OpenType font features to be used during text + layout. This is usually used to turn on optional font features + that are not enabled by default, for example ``"dlig"`` or + ``"ss01"``, but can be also used to turn off default font + features, for example ``"-liga"`` to disable ligatures or + ``"-kern"`` to disable kerning. To get all supported + features, see `OpenType docs`_. + Requires libraqm. + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP 47 language code`_. + Requires libraqm. + """ + self.text = text + self.font = font or ImageFont.load_default() + + self.mode = mode + self.spacing = spacing + self.direction = direction + self.features = features + self.language = language + + self.embedded_color = False + + self.stroke_width: float = 0 + self.stroke_fill: _Ink | None = None + + def embed_color(self) -> None: + """ + Use embedded color glyphs (COLR, CBDT, SBIX). + """ + if self.mode not in ("RGB", "RGBA"): + msg = "Embedded color supported only in RGB and RGBA modes" + raise ValueError(msg) + self.embedded_color = True + + def stroke(self, width: float = 0, fill: _Ink | None = None) -> None: + """ + :param width: The width of the text stroke. + :param fill: Color to use for the text stroke when drawing. If not given, will + default to the ``fill`` parameter from + :py:meth:`.ImageDraw.ImageDraw.text`. + """ + self.stroke_width = width + self.stroke_fill = fill + + def _get_fontmode(self) -> str: + if self.mode in ("1", "P", "I", "F"): + return "1" + elif self.embedded_color: + return "RGBA" + else: + return "L" + + def get_length(self): + """ + Returns length (in pixels with 1/64 precision) of text. + + This is the amount by which following text should be offset. + Text bounding box may extend past the length in some fonts, + e.g. when using italics or accents. + + The result is returned as a float; it is a whole number if using basic layout. + + Note that the sum of two lengths may not equal the length of a concatenated + string due to kerning. If you need to adjust for kerning, include the following + character and subtract its length. + + For example, instead of:: + + hello = ImageText.ImageText("Hello", font).get_length() + world = ImageText.ImageText("World", font).get_length() + helloworld = ImageText.ImageText("HelloWorld", font).get_length() + assert hello + world == helloworld + + use:: + + hello = ( + ImageText.ImageText("HelloW", font).get_length() - + ImageText.ImageText("W", font).get_length() + ) # adjusted for kerning + world = ImageText.ImageText("World", font).get_length() + helloworld = ImageText.ImageText("HelloWorld", font).get_length() + assert hello + world == helloworld + + or disable kerning with (requires libraqm):: + + hello = ImageText.ImageText("Hello", font, features=["-kern"]).get_length() + world = ImageText.ImageText("World", font, features=["-kern"]).get_length() + helloworld = ImageText.ImageText( + "HelloWorld", font, features=["-kern"] + ).get_length() + assert hello + world == helloworld + + :return: Either width for horizontal text, or height for vertical text. + """ + split_character = "\n" if isinstance(self.text, str) else b"\n" + if split_character in self.text: + msg = "can't measure length of multiline text" + raise ValueError(msg) + return self.font.getlength( + self.text, + self._get_fontmode(), + self.direction, + self.features, + self.language, + ) + + def _split( + self, xy: tuple[float, float], anchor: str | None, align: str + ) -> list[tuple[tuple[float, float], str, str | bytes]]: + if anchor is None: + anchor = "lt" if self.direction == "ttb" else "la" + elif len(anchor) != 2: + msg = "anchor must be a 2 character string" + raise ValueError(msg) + + lines = ( + self.text.split("\n") + if isinstance(self.text, str) + else self.text.split(b"\n") + ) + if len(lines) == 1: + return [(xy, anchor, self.text)] + + if anchor[1] in "tb" and self.direction != "ttb": + msg = "anchor not supported for multiline text" + raise ValueError(msg) + + fontmode = self._get_fontmode() + line_spacing = ( + self.font.getbbox( + "A", + fontmode, + None, + self.features, + self.language, + self.stroke_width, + )[3] + + self.stroke_width + + self.spacing + ) + + top = xy[1] + parts = [] + if self.direction == "ttb": + left = xy[0] + for line in lines: + parts.append(((left, top), anchor, line)) + left += line_spacing + else: + widths = [] + max_width: float = 0 + for line in lines: + line_width = self.font.getlength( + line, fontmode, self.direction, self.features, self.language + ) + widths.append(line_width) + max_width = max(max_width, line_width) + + if anchor[1] == "m": + top -= (len(lines) - 1) * line_spacing / 2.0 + elif anchor[1] == "d": + top -= (len(lines) - 1) * line_spacing + + idx = -1 + for line in lines: + left = xy[0] + idx += 1 + width_difference = max_width - widths[idx] + + # align by align parameter + if align in ("left", "justify"): + pass + elif align == "center": + left += width_difference / 2.0 + elif align == "right": + left += width_difference + else: + msg = 'align must be "left", "center", "right" or "justify"' + raise ValueError(msg) + + if ( + align == "justify" + and width_difference != 0 + and idx != len(lines) - 1 + ): + words = ( + line.split(" ") if isinstance(line, str) else line.split(b" ") + ) + if len(words) > 1: + # align left by anchor + if anchor[0] == "m": + left -= max_width / 2.0 + elif anchor[0] == "r": + left -= max_width + + word_widths = [ + self.font.getlength( + word, + fontmode, + self.direction, + self.features, + self.language, + ) + for word in words + ] + word_anchor = "l" + anchor[1] + width_difference = max_width - sum(word_widths) + i = 0 + for word in words: + parts.append(((left, top), word_anchor, word)) + left += word_widths[i] + width_difference / (len(words) - 1) + i += 1 + top += line_spacing + continue + + # align left by anchor + if anchor[0] == "m": + left -= width_difference / 2.0 + elif anchor[0] == "r": + left -= width_difference + parts.append(((left, top), anchor, line)) + top += line_spacing + + return parts + + def get_bbox( + self, + xy: tuple[float, float] = (0, 0), + anchor: str | None = None, + align: str = "left", + ) -> tuple[float, float, float, float]: + """ + Returns bounding box (in pixels) of text. + + Use :py:meth:`get_length` to get the offset of following text with 1/64 pixel + precision. The bounding box includes extra margins for some fonts, e.g. italics + or accents. + + :param xy: The anchor coordinates of the text. + :param anchor: The text anchor alignment. Determines the relative location of + the anchor to the text. The default alignment is top left, + specifically ``la`` for horizontal text and ``lt`` for + vertical text. See :ref:`text-anchors` for details. + :param align: For multiline text, ``"left"``, ``"center"``, ``"right"`` or + ``"justify"`` determines the relative alignment of lines. Use the + ``anchor`` parameter to specify the alignment to ``xy``. + + :return: ``(left, top, right, bottom)`` bounding box + """ + bbox: tuple[float, float, float, float] | None = None + fontmode = self._get_fontmode() + for xy, anchor, line in self._split(xy, anchor, align): + bbox_line = self.font.getbbox( + line, + fontmode, + self.direction, + self.features, + self.language, + self.stroke_width, + anchor, + ) + bbox_line = ( + bbox_line[0] + xy[0], + bbox_line[1] + xy[1], + bbox_line[2] + xy[0], + bbox_line[3] + xy[1], + ) + if bbox is None: + bbox = bbox_line + else: + bbox = ( + min(bbox[0], bbox_line[0]), + min(bbox[1], bbox_line[1]), + max(bbox[2], bbox_line[2]), + max(bbox[3], bbox_line[3]), + ) + + if bbox is None: + return xy[0], xy[1], xy[0], xy[1] + return bbox diff --git a/src/PIL/_typing.py b/src/PIL/_typing.py index 373938e71..685c425d5 100644 --- a/src/PIL/_typing.py +++ b/src/PIL/_typing.py @@ -38,6 +38,8 @@ else: return bool +_Ink = Union[float, tuple[int, ...], str] + Coords = Union[Sequence[float], Sequence[Sequence[float]]] From 969e4687497d447581e950ed26043a988f50e21b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 21 Jul 2025 13:07:38 +1000 Subject: [PATCH 120/309] Allow ImageDraw text() to use ImageText --- Tests/test_imagetext.py | 35 +++++++++++++++++++++++++++++++++-- docs/reference/ImageText.rst | 6 +++++- src/PIL/ImageDraw.py | 23 +++++++++++++---------- 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/Tests/test_imagetext.py b/Tests/test_imagetext.py index 3a3a58975..b58d048b5 100644 --- a/Tests/test_imagetext.py +++ b/Tests/test_imagetext.py @@ -2,9 +2,9 @@ from __future__ import annotations import pytest -from PIL import ImageFont, ImageText +from PIL import Image, ImageDraw, ImageFont, ImageText -from .helper import skip_unless_feature +from .helper import assert_image_similar_tofile, skip_unless_feature FONT_PATH = "Tests/fonts/FreeMono.ttf" @@ -39,3 +39,34 @@ def test_get_bbox(font: ImageFont.FreeTypeFont) -> None: assert ImageText.ImageText("M", font).get_bbox() == (0, 4, 12, 16) assert ImageText.ImageText("y", font).get_bbox() == (0, 7, 12, 20) assert ImageText.ImageText("a", font).get_bbox() == (0, 7, 12, 16) + + +def test_standard_embedded_color(layout_engine: ImageFont.Layout) -> None: + font = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine) + text = ImageText.ImageText("Hello World!", font) + text.embed_color() + + im = Image.new("RGB", (300, 64), "white") + draw = ImageDraw.Draw(im) + draw.text((10, 10), text, "#fa6") + + assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 3.1) + + +@skip_unless_feature("freetype2") +def test_stroke() -> None: + for suffix, stroke_fill in {"same": None, "different": "#0f0"}.items(): + # Arrange + im = Image.new("RGB", (120, 130)) + draw = ImageDraw.Draw(im) + font = ImageFont.truetype(FONT_PATH, 120) + text = ImageText.ImageText("A", font) + text.stroke(2, stroke_fill) + + # Act + draw.text((12, 12), text, "#f00") + + # Assert + assert_image_similar_tofile( + im, "Tests/images/imagedraw_stroke_" + suffix + ".png", 3.1 + ) diff --git a/docs/reference/ImageText.rst b/docs/reference/ImageText.rst index ad5439751..fa55b4f30 100644 --- a/docs/reference/ImageText.rst +++ b/docs/reference/ImageText.rst @@ -6,7 +6,7 @@ The :py:mod:`~PIL.ImageText` module defines a class with the same name. Instances of this class provide a way to use fonts with text strings or bytes. The result is a -simple API to apply styling to pieces of text and measure them. +simple API to apply styling to pieces of text and measure or draw them. Example ------- @@ -23,6 +23,10 @@ Example print(text.get_length()) # 154.0 print(text.get_bbox()) # (-2, 3, 156, 22) + im = Image.new("RGB", text.get_bbox()[2:]) + d = ImageDraw.Draw(im) + d.text((0, 0), text, "#f00") + Methods ------- diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 35ecbfb78..852e02698 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -537,7 +537,7 @@ class ImageDraw: def text( self, xy: tuple[float, float], - text: AnyStr, + text: AnyStr | ImageText.ImageText, fill: _Ink | None = None, font: ( ImageFont.ImageFont @@ -558,15 +558,18 @@ class ImageDraw: **kwargs: Any, ) -> None: """Draw text.""" - if font is None: - font = self._getfont(kwargs.get("font_size")) - imagetext = ImageText.ImageText( - text, font, self.mode, spacing, direction, features, language - ) - if embedded_color: - imagetext.embed_color() - if stroke_width: - imagetext.stroke(stroke_width, stroke_fill) + if isinstance(text, ImageText.ImageText): + imagetext = text + else: + if font is None: + font = self._getfont(kwargs.get("font_size")) + imagetext = ImageText.ImageText( + text, font, self.mode, spacing, direction, features, language + ) + if embedded_color: + imagetext.embed_color() + if stroke_width: + imagetext.stroke(stroke_width, stroke_fill) def getink(fill: _Ink | None) -> int: ink, fill_ink = self._getink(fill) From 63163d065d632cb75466d554fb1d6ea27cc43577 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 17 Jul 2025 19:59:47 +1000 Subject: [PATCH 121/309] Removed WebP feature handling --- Tests/test_features.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/Tests/test_features.py b/Tests/test_features.py index 520c25b46..7af3fffea 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -18,11 +18,7 @@ def test_check() -> None: for codec in features.codecs: assert features.check_codec(codec) == features.check(codec) for feature in features.features: - if "webp" in feature: - with pytest.warns(DeprecationWarning, match="webp"): - assert features.check_feature(feature) == features.check(feature) - else: - assert features.check_feature(feature) == features.check(feature) + assert features.check_feature(feature) == features.check(feature) def test_version() -> None: @@ -48,11 +44,7 @@ def test_version() -> None: for codec in features.codecs: test(codec, features.version_codec) for feature in features.features: - if "webp" in feature: - with pytest.warns(DeprecationWarning, match="webp"): - test(feature, features.version_feature) - else: - test(feature, features.version_feature) + test(feature, features.version_feature) @skip_unless_feature("libjpeg_turbo") From ec6d5efe4d02dc6d68e569abfd7523e21a89539f Mon Sep 17 00:00:00 2001 From: Luke Granger-Brown Date: Sat, 26 Jul 2025 08:33:11 +0100 Subject: [PATCH 122/309] Deprecate ImageCmsProfile product_name and product_info (#8995) Co-authored-by: Andrew Murray --- Tests/test_imagecms.py | 14 ++++++++++++++ docs/deprecations.rst | 9 +++++++++ docs/releasenotes/12.0.0.rst | 8 +++++--- src/PIL/ImageCms.py | 14 ++++++++++---- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 55a4a87fb..8b5d88ac8 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -690,3 +690,17 @@ def test_cmyk_lab() -> None: im = Image.new("CMYK", (1, 1)) converted_im = im.convert("LAB") assert converted_im.getpixel((0, 0)) == (255, 128, 128) + + +def test_deprecation() -> None: + profile = ImageCmsProfile(ImageCms.createProfile("sRGB")) + with pytest.warns( + DeprecationWarning, match="ImageCms.ImageCmsProfile.product_name" + ): + profile.product_name + with pytest.warns( + DeprecationWarning, match="ImageCms.ImageCmsProfile.product_info" + ): + profile.product_info + with pytest.raises(AttributeError): + profile.this_attribute_does_not_exist diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 4e65dc807..3f95cf7f5 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -52,6 +52,15 @@ another mode before saving:: im = Image.new("I", (1, 1)) im.convert("I;16").save("out.png") +ImageCms.ImageCmsProfile.product_name and .product_info +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 12.0.0 + +``ImageCms.ImageCmsProfile.product_name`` and the corresponding +``.product_info`` attributes have been deprecated, and will be removed in +Pillow 13 (2026-10-15). They have been set to ``None`` since Pillow 2.3.0. + Removed features ---------------- diff --git a/docs/releasenotes/12.0.0.rst b/docs/releasenotes/12.0.0.rst index 68b664443..6c0cd4dba 100644 --- a/docs/releasenotes/12.0.0.rst +++ b/docs/releasenotes/12.0.0.rst @@ -110,10 +110,12 @@ vulnerability introduced in FreeType 2.6 (:cve:`2020-15999`). Deprecations ============ -TODO -^^^^ +ImageCms.ImageCmsProfile.product_name and .product_info +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -TODO +``ImageCms.ImageCmsProfile.product_name`` and the corresponding +``.product_info`` attributes have been deprecated, and will be removed in +Pillow 13 (2026-10-15). They have been set to ``None`` since Pillow 2.3.0. API changes =========== diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index d3555694a..513e28acf 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -23,9 +23,10 @@ import operator import sys from enum import IntEnum, IntFlag from functools import reduce -from typing import Literal, SupportsFloat, SupportsInt, Union +from typing import Any, Literal, SupportsFloat, SupportsInt, Union from . import Image +from ._deprecate import deprecate from ._typing import SupportsRead try: @@ -233,9 +234,7 @@ class ImageCmsProfile: low-level profile object """ - self.filename = None - self.product_name = None # profile.product_name - self.product_info = None # profile.product_info + self.filename: str | None = None if isinstance(profile, str): if sys.platform == "win32": @@ -256,6 +255,13 @@ class ImageCmsProfile: msg = "Invalid type for Profile" # type: ignore[unreachable] raise TypeError(msg) + def __getattr__(self, name: str) -> Any: + if name in ("product_name", "product_info"): + deprecate(f"ImageCms.ImageCmsProfile.{name}", 13) + return None + msg = f"'{self.__class__.__name__}' object has no attribute '{name}'" + raise AttributeError(msg) + def tobytes(self) -> bytes: """ Returns the profile in a format suitable for embedding in From 7afbafd1e2ae9a3fe822b422cebe94092ad68b9c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 26 Jul 2025 19:21:50 +1000 Subject: [PATCH 123/309] Support saving variable length rational TIFF tags --- Tests/test_file_libtiff.py | 12 ++++++++++++ src/encode.c | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 958e2749f..d4d50e5b4 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -355,6 +355,18 @@ class TestFileLibTiff(LibTiffTestCase): # Should not segfault im.save(outfile) + def test_whitepoint_tag( + self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path + ) -> None: + monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True) + + out = tmp_path / "temp.tif" + hopper().save(out, tiffinfo={318: (0.3127, 0.3289)}) + + with Image.open(out) as reloaded: + assert isinstance(reloaded, TiffImagePlugin.TiffImageFile) + assert reloaded.tag_v2[318] == pytest.approx((0.3127, 0.3289)) + def test_xmlpacket_tag( self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: diff --git a/src/encode.c b/src/encode.c index e56494036..a8da32318 100644 --- a/src/encode.c +++ b/src/encode.c @@ -922,6 +922,18 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { ); free(av); } + } else if (type == TIFF_RATIONAL) { + FLOAT32 *av; + /* malloc check ok, calloc checks for overflow */ + av = calloc(len, sizeof(FLOAT32)); + if (av) { + for (i = 0; i < len; i++) { + av[i] = (FLOAT32)PyFloat_AsDouble(PyTuple_GetItem(value, i)); + } + status = + ImagingLibTiffSetField(&encoder->state, (ttag_t)key_int, av); + free(av); + } } } else { if (type == TIFF_SHORT) { From 7dbcb32cbe524a8ec4c12f21c762cd7153b2b03b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 26 Jul 2025 19:32:57 +1000 Subject: [PATCH 124/309] Update cygwin/cygwin-install-action action to v6 (#9108) Co-authored-by: Andrew Murray --- .github/workflows/test-cygwin.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/test-cygwin.yml b/.github/workflows/test-cygwin.yml index abfeaa77f..581cd6370 100644 --- a/.github/workflows/test-cygwin.yml +++ b/.github/workflows/test-cygwin.yml @@ -52,7 +52,7 @@ jobs: persist-credentials: false - name: Install Cygwin - uses: cygwin/cygwin-install-action@v5 + uses: cygwin/cygwin-install-action@v6 with: packages: > gcc-g++ @@ -89,10 +89,6 @@ jobs: with: dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack' - - name: Select Python version - run: | - ln -sf c:/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/cygwin/bin/python3 - - name: pip cache uses: actions/cache@v4 with: From 53b6d57b730a68ea58680483f8628c5e25301a1e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 26 Jul 2025 19:39:54 +1000 Subject: [PATCH 125/309] Drop support for PyPy3.10 --- .github/workflows/test-windows.yml | 2 +- .github/workflows/test.yml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 6d8acc44f..766c506e7 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -35,7 +35,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["pypy3.11", "pypy3.10", "3.10", "3.11", "3.12", ">=3.13.5", "3.14"] + python-version: ["pypy3.11", "3.10", "3.11", "3.12", ">=3.13.5", "3.14"] architecture: ["x64"] include: # Test the oldest Python on 32-bit diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b4b516228..d18023dbc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,7 +42,6 @@ jobs: ] python-version: [ "pypy3.11", - "pypy3.10", "3.14t", "3.14", "3.13t", From a6acc67660f4d2a157341c05d11e47e36c79802d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 26 Jul 2025 21:00:26 +1000 Subject: [PATCH 126/309] Always check XMLPacket value --- Tests/test_file_libtiff.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 958e2749f..f61f79f17 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -365,8 +365,7 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(out) as reloaded: assert isinstance(reloaded, TiffImagePlugin.TiffImageFile) - if 700 in reloaded.tag_v2: - assert reloaded.tag_v2[700] == b"xmlpacket tag" + assert reloaded.tag_v2[700] == b"xmlpacket tag" def test_int_dpi(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: # issue #1765 From 283dcfc024113f6d0d8bcbb216d1735cb05a16e6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 26 Jul 2025 23:39:11 +1000 Subject: [PATCH 127/309] Removed unused code --- src/decode.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/decode.c b/src/decode.c index 03db1ce35..e7a6e6323 100644 --- a/src/decode.c +++ b/src/decode.c @@ -870,8 +870,6 @@ PyImaging_Jpeg2KDecoderNew(PyObject *self, PyObject *args) { if (strcmp(format, "j2k") == 0) { codec_format = OPJ_CODEC_J2K; - } else if (strcmp(format, "jpt") == 0) { - codec_format = OPJ_CODEC_JPT; } else if (strcmp(format, "jp2") == 0) { codec_format = OPJ_CODEC_JP2; } else { From 98d38a3bffe572459939cbb6ab730229b4a5a833 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Mon, 28 Jul 2025 18:52:06 +1000 Subject: [PATCH 128/309] Updated libpng to 1.6.50 (#9058) --- .github/workflows/wheels-dependencies.sh | 2 +- winbuild/build_prepare.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 6b5aedb69..4519271b9 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -95,7 +95,7 @@ ARCHIVE_SDIR=pillow-depends-main # you change those versions, ensure the patch is also updated. FREETYPE_VERSION=2.13.3 HARFBUZZ_VERSION=11.2.1 -LIBPNG_VERSION=1.6.49 +LIBPNG_VERSION=1.6.50 JPEGTURBO_VERSION=3.1.1 OPENJPEG_VERSION=2.5.3 XZ_VERSION=5.8.1 diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 2307fc8b2..fbff0daf2 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -121,7 +121,7 @@ V = { "LCMS2": "2.17", "LIBAVIF": "1.3.0", "LIBIMAGEQUANT": "4.3.4", - "LIBPNG": "1.6.49", + "LIBPNG": "1.6.50", "LIBWEBP": "1.6.0", "OPENJPEG": "2.5.3", "TIFF": "4.7.0", From e8b3c17ebc90f36c8ec326e765246814c21f1f48 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 29 Jul 2025 07:28:03 +1000 Subject: [PATCH 129/309] Updated documentation --- docs/deprecations.rst | 7 +++++-- docs/releasenotes/11.3.0.rst | 7 +++++++ docs/releasenotes/12.0.0.rst | 10 +++++++--- src/PIL/Image.py | 3 ++- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 236554565..851f3e8d8 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -42,8 +42,11 @@ Image.fromarray mode parameter .. deprecated:: 11.3.0 -The ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` has been deprecated. The -mode can be automatically determined from the object's shape and type instead. +Using the ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` to change data types +has been deprecated. Since pixel values do not contain information about palettes or +color spaces, the parameter can still be used to place grayscale L mode data within a +P mode image, or read RGB data as YCbCr for example. If omitted, the mode will be +automatically determined from the object's shape and type. Saving I mode images as PNG ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/releasenotes/11.3.0.rst b/docs/releasenotes/11.3.0.rst index 409d50295..5c04a0373 100644 --- a/docs/releasenotes/11.3.0.rst +++ b/docs/releasenotes/11.3.0.rst @@ -29,6 +29,13 @@ Image.fromarray mode parameter The ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` has been deprecated. The mode can be automatically determined from the object's shape and type instead. +.. note:: + + Since pixel values do not contain information about palettes or color spaces, part + of this functionality was restored in Pillow 12.0.0. The parameter can be used to + place grayscale L mode data within a P mode image, or read RGB data as YCbCr for + example. + Saving I mode images as PNG ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/releasenotes/12.0.0.rst b/docs/releasenotes/12.0.0.rst index 68b664443..19508b08a 100644 --- a/docs/releasenotes/12.0.0.rst +++ b/docs/releasenotes/12.0.0.rst @@ -134,7 +134,11 @@ TODO Other changes ============= -TODO -^^^^ +Image.fromarray mode parameter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -TODO +In Pillow 11.3.0, the ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` was +deprecated. Part of this functionality has been restored in Pillow 12.0.0. Since pixel +values do not contain information about palettes or color spaces, the parameter can be +used to place grayscale L mode data within a P mode image, or read RGB data as YCbCr +for example. diff --git a/src/PIL/Image.py b/src/PIL/Image.py index c98630cc2..20917b1a4 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3253,7 +3253,8 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image: :param obj: Object with array interface :param mode: Optional mode to use when reading ``obj``. Since pixel values do not contain information about palettes or color spaces, this can be used to place - grayscale L mode data within a P mode image, or read RGB data as YCbCr. + grayscale L mode data within a P mode image, or read RGB data as YCbCr for + example. See: :ref:`concept-modes` for general information about modes. :returns: An image object. From bae97e1a2b75a1e3c01efc168d10b8d7ecdf3392 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 21:50:45 +1000 Subject: [PATCH 130/309] Update dependency cibuildwheel to v3.1.2 (#9118) --- .ci/requirements-cibw.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/requirements-cibw.txt b/.ci/requirements-cibw.txt index e1eb52eb8..823671828 100644 --- a/.ci/requirements-cibw.txt +++ b/.ci/requirements-cibw.txt @@ -1 +1 @@ -cibuildwheel==3.0.1 +cibuildwheel==3.1.2 From ba5f81fb6b4bd143b2ceba6875b33870eaa366ce Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:23:39 +0300 Subject: [PATCH 131/309] Add support for Python 3.14 (#9120) Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- docs/installation/newer-versions.csv | 19 ++++++++++--------- docs/releasenotes/12.0.0.rst | 10 +++++++--- pyproject.toml | 3 ++- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/docs/installation/newer-versions.csv b/docs/installation/newer-versions.csv index 19816af58..e948dd540 100644 --- a/docs/installation/newer-versions.csv +++ b/docs/installation/newer-versions.csv @@ -1,9 +1,10 @@ -Python,3.13,3.12,3.11,3.10,3.9,3.8,3.7,3.6,3.5 -Pillow >= 11,Yes,Yes,Yes,Yes,Yes,,,, -Pillow 10.1 - 10.4,,Yes,Yes,Yes,Yes,Yes,,, -Pillow 10.0,,,Yes,Yes,Yes,Yes,,, -Pillow 9.3 - 9.5,,,Yes,Yes,Yes,Yes,Yes,, -Pillow 9.0 - 9.2,,,,Yes,Yes,Yes,Yes,, -Pillow 8.3.2 - 8.4,,,,Yes,Yes,Yes,Yes,Yes, -Pillow 8.0 - 8.3.1,,,,,Yes,Yes,Yes,Yes, -Pillow 7.0 - 7.2,,,,,,Yes,Yes,Yes,Yes +Python,3.14,3.13,3.12,3.11,3.10,3.9,3.8,3.7,3.6,3.5 +Pillow 12,Yes,Yes,Yes,Yes,Yes,,,,, +Pillow 11,,Yes,Yes,Yes,Yes,Yes,,,, +Pillow 10.1 - 10.4,,,Yes,Yes,Yes,Yes,Yes,,, +Pillow 10.0,,,,Yes,Yes,Yes,Yes,,, +Pillow 9.3 - 9.5,,,,Yes,Yes,Yes,Yes,Yes,, +Pillow 9.0 - 9.2,,,,,Yes,Yes,Yes,Yes,, +Pillow 8.3.2 - 8.4,,,,,Yes,Yes,Yes,Yes,Yes, +Pillow 8.0 - 8.3.1,,,,,,Yes,Yes,Yes,Yes, +Pillow 7.0 - 7.2,,,,,,,Yes,Yes,Yes,Yes diff --git a/docs/releasenotes/12.0.0.rst b/docs/releasenotes/12.0.0.rst index 6c0cd4dba..46cf64cf1 100644 --- a/docs/releasenotes/12.0.0.rst +++ b/docs/releasenotes/12.0.0.rst @@ -136,7 +136,11 @@ TODO Other changes ============= -TODO -^^^^ +Python 3.14 +^^^^^^^^^^^ -TODO +Pillow 11.3.0 had wheels built against Python 3.14 beta, available as a preview to help +others prepare for 3.14, and to ensure Pillow could be used immediately at the release +of 3.14.0 final (2025-10-07, :pep:`745`). + +Pillow 12.0.0 now officially supports Python 3.14. diff --git a/pyproject.toml b/pyproject.toml index 4e8623118..3693ddb8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Multimedia :: Graphics", @@ -206,7 +207,7 @@ lint.isort.required-imports = [ ] [tool.pyproject-fmt] -max_supported_python = "3.13" +max_supported_python = "3.14" [tool.pytest.ini_options] addopts = "-ra --color=auto" From 98d6c3bf8818849e2414ef4de8c9e02b03de3886 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 1 Aug 2025 08:22:28 +0800 Subject: [PATCH 132/309] Restore pyroma test for iOS (#9116) Co-authored-by: Andrew Murray --- Tests/test_image_access.py | 6 +++++- Tests/test_pyroma.py | 25 ++++++++++++++++++++++++- pyproject.toml | 2 +- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index a847264d2..07c12594a 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -317,4 +317,8 @@ int main(int argc, char* argv[]) assert process.returncode == 0 def teardown_method(self) -> None: - os.remove("embed_pil.c") + try: + os.remove("embed_pil.c") + except FileNotFoundError: + # If the test was skipped or failed, the file won't exist + pass diff --git a/Tests/test_pyroma.py b/Tests/test_pyroma.py index c2f7fe22e..35f3fd076 100644 --- a/Tests/test_pyroma.py +++ b/Tests/test_pyroma.py @@ -1,5 +1,7 @@ from __future__ import annotations +from importlib.metadata import metadata + import pytest from PIL import __version__ @@ -7,9 +9,30 @@ from PIL import __version__ pyroma = pytest.importorskip("pyroma", reason="Pyroma not installed") +def map_metadata_keys(metadata): + # Convert installed wheel metadata into canonical Core Metadata 2.4 format. + # This was a utility method in pyroma 4.3.3; it was removed in 5.0. + # This implementation is constructed from the relevant logic from + # Pyroma 5.0's `build_metadata()` implementation. This has been submitted + # upstream to Pyroma as https://github.com/regebro/pyroma/pull/116, + # so it may be possible to simplify this test in future. + data = {} + for key in set(metadata.keys()): + value = metadata.get_all(key) + key = pyroma.projectdata.normalize(key) + + if len(value) == 1: + value = value[0] + if value.strip() == "UNKNOWN": + continue + + data[key] = value + return data + + def test_pyroma() -> None: # Arrange - data = pyroma.projectdata.get_data(".") + data = map_metadata_keys(metadata("Pillow")) # Act rating = pyroma.ratings.rate(data) diff --git a/pyproject.toml b/pyproject.toml index 3693ddb8d..4980a9cb8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,7 +68,7 @@ optional-dependencies.tests = [ "markdown2", "olefile", "packaging", - "pyroma", + "pyroma>=5", "pytest", "pytest-cov", "pytest-timeout", From 19829c3d95e9ee581d5ecd3f46e6c0af878482f8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 28 Jul 2025 18:55:45 +1000 Subject: [PATCH 133/309] Updated harfbuzz to 11.3.3 --- .github/workflows/wheels-dependencies.sh | 2 +- winbuild/build_prepare.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 4519271b9..f2b9a7f40 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -94,7 +94,7 @@ ARCHIVE_SDIR=pillow-depends-main # annotations have a source code patch that is required for some platforms. If # you change those versions, ensure the patch is also updated. FREETYPE_VERSION=2.13.3 -HARFBUZZ_VERSION=11.2.1 +HARFBUZZ_VERSION=11.3.3 LIBPNG_VERSION=1.6.50 JPEGTURBO_VERSION=3.1.1 OPENJPEG_VERSION=2.5.3 diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index fbff0daf2..7067fc3c4 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -116,7 +116,7 @@ V = { "BROTLI": "1.1.0", "FREETYPE": "2.13.3", "FRIBIDI": "1.0.16", - "HARFBUZZ": "11.2.1", + "HARFBUZZ": "11.3.3", "JPEGTURBO": "3.1.1", "LCMS2": "2.17", "LIBAVIF": "1.3.0", From 27a7582b3541ad92df9900c2a9edcfe91c44a313 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 2 Aug 2025 11:40:35 +1000 Subject: [PATCH 134/309] Moved imports into TYPE_CHECKING --- Tests/test_imagecms.py | 5 ++++- src/PIL/GimpPaletteFile.py | 5 ++++- src/PIL/Image.py | 11 ++++++++--- src/PIL/Jpeg2KImagePlugin.py | 8 ++++++-- src/PIL/JpegImagePlugin.py | 3 ++- src/PIL/PngImagePlugin.py | 6 ++++-- src/PIL/WebPImagePlugin.py | 4 +++- 7 files changed, 31 insertions(+), 11 deletions(-) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 8b5d88ac8..46c1baa2d 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -7,7 +7,7 @@ import shutil import sys from io import BytesIO from pathlib import Path -from typing import Any, Literal, cast +from typing import Literal, cast import pytest @@ -31,6 +31,9 @@ except ImportError: # Skipped via setup_module() pass +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import Any SRGB = "Tests/icc/sRGB_IEC61966-2-1_black_scaled.icc" HAVE_PROFILE = os.path.exists(SRGB) diff --git a/src/PIL/GimpPaletteFile.py b/src/PIL/GimpPaletteFile.py index 379ffd739..016257d3d 100644 --- a/src/PIL/GimpPaletteFile.py +++ b/src/PIL/GimpPaletteFile.py @@ -17,7 +17,10 @@ from __future__ import annotations import re from io import BytesIO -from typing import IO + +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import IO class GimpPaletteFile: diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 262b5478b..b7c185e0d 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -38,10 +38,9 @@ import struct import sys import tempfile import warnings -from collections.abc import Callable, Iterator, MutableMapping, Sequence +from collections.abc import MutableMapping from enum import IntEnum -from types import ModuleType -from typing import IO, Any, Literal, Protocol, cast +from typing import IO, Protocol, cast # VERSION was removed in Pillow 6.0.0. # PILLOW_VERSION was removed in Pillow 9.0.0. @@ -64,6 +63,12 @@ try: except ImportError: ElementTree = None +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Callable, Iterator, Sequence + from types import ModuleType + from typing import Any, Literal + logger = logging.getLogger(__name__) diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index e0f4ecae5..4c85dd4e2 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -18,11 +18,15 @@ from __future__ import annotations import io import os import struct -from collections.abc import Callable -from typing import IO, cast +from typing import cast from . import Image, ImageFile, ImagePalette, _binary +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Callable + from typing import IO + class BoxReader: """ diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index efe8eff3b..0d110035e 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -42,7 +42,6 @@ import subprocess import sys import tempfile import warnings -from typing import IO, Any from . import Image, ImageFile from ._binary import i16be as i16 @@ -53,6 +52,8 @@ from .JpegPresets import presets TYPE_CHECKING = False if TYPE_CHECKING: + from typing import IO, Any + from .MpoImagePlugin import MpoImageFile # diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 1b9a89aef..d0f22f812 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -38,9 +38,8 @@ import re import struct import warnings import zlib -from collections.abc import Callable from enum import IntEnum -from typing import IO, Any, NamedTuple, NoReturn, cast +from typing import IO, NamedTuple, cast from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence from ._binary import i16be as i16 @@ -53,6 +52,9 @@ from ._util import DeferredError TYPE_CHECKING = False if TYPE_CHECKING: + from collections.abc import Callable + from typing import Any, NoReturn + from . import _imaging logger = logging.getLogger(__name__) diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 1716a18cc..2847fed20 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -1,7 +1,6 @@ from __future__ import annotations from io import BytesIO -from typing import IO, Any from . import Image, ImageFile @@ -12,6 +11,9 @@ try: except ImportError: SUPPORTED = False +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import IO, Any _VP8_MODES_BY_IDENTIFIER = { b"VP8 ": "RGB", From 0620daf860d4d7a5cff6e29079ff1f9773423dc4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 2 Aug 2025 13:10:18 +1000 Subject: [PATCH 135/309] Renamed variable to not shadow import --- Tests/test_pyroma.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/test_pyroma.py b/Tests/test_pyroma.py index 35f3fd076..5871a7213 100644 --- a/Tests/test_pyroma.py +++ b/Tests/test_pyroma.py @@ -9,7 +9,7 @@ from PIL import __version__ pyroma = pytest.importorskip("pyroma", reason="Pyroma not installed") -def map_metadata_keys(metadata): +def map_metadata_keys(md): # Convert installed wheel metadata into canonical Core Metadata 2.4 format. # This was a utility method in pyroma 4.3.3; it was removed in 5.0. # This implementation is constructed from the relevant logic from @@ -17,8 +17,8 @@ def map_metadata_keys(metadata): # upstream to Pyroma as https://github.com/regebro/pyroma/pull/116, # so it may be possible to simplify this test in future. data = {} - for key in set(metadata.keys()): - value = metadata.get_all(key) + for key in set(md.keys()): + value = md.get_all(key) key = pyroma.projectdata.normalize(key) if len(value) == 1: From ae6bb29b8207023c704490405c254808b04643dd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 2 Aug 2025 18:35:16 +1000 Subject: [PATCH 136/309] Removed support for NumPy 1.20 when type checking --- src/PIL/_typing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/_typing.py b/src/PIL/_typing.py index 373938e71..e94045260 100644 --- a/src/PIL/_typing.py +++ b/src/PIL/_typing.py @@ -12,8 +12,8 @@ if TYPE_CHECKING: try: import numpy.typing as npt - NumpyArray = npt.NDArray[Any] # requires numpy>=1.21 - except (ImportError, AttributeError): + NumpyArray = npt.NDArray[Any] + except ImportError: pass if sys.version_info >= (3, 13): From 2ab301dcc95bee3b655aa0a2299907271b7a435a Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 2 Aug 2025 15:02:20 +0300 Subject: [PATCH 137/309] Drop support for Python 3.9 (#9119) Co-authored-by: Andrew Murray Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- .ci/install.sh | 67 +++++------ .github/mergify.yml | 1 - .github/workflows/test-cygwin.yml | 150 ------------------------- .github/workflows/test-windows.yml | 4 +- .github/workflows/test.yml | 11 +- README.md | 3 - Tests/helper.py | 9 +- Tests/test_features.py | 5 +- Tests/test_format_hsv.py | 5 +- Tests/test_image_transform.py | 5 +- Tests/test_imagechops.py | 6 +- Tests/test_imagecms.py | 7 +- Tests/test_imagedraw.py | 9 +- Tests/test_qt_image_qapplication.py | 42 +++---- Tests/test_qt_image_toqimage.py | 8 +- Tests/test_shell_injection.py | 8 +- checks/check_imaging_leaks.py | 3 +- docs/index.rst | 4 - docs/installation/platform-support.rst | 24 ++-- docs/reference/internal_modules.rst | 5 - docs/releasenotes/12.0.0.rst | 6 + pyproject.toml | 10 +- src/PIL/GifImagePlugin.py | 6 +- src/PIL/GimpGradientFile.py | 6 +- src/PIL/ImageDraw.py | 19 ++-- src/PIL/ImageFilter.py | 7 +- src/PIL/ImageMath.py | 8 +- src/PIL/ImageQt.py | 21 ++-- src/PIL/ImageSequence.py | 6 +- src/PIL/PcfFontFile.py | 6 +- src/PIL/PdfParser.py | 17 +-- src/PIL/TiffImagePlugin.py | 10 +- src/PIL/_imagingcms.pyi | 8 +- src/PIL/_imagingft.pyi | 3 +- src/PIL/_typing.py | 19 +--- src/PIL/_util.py | 7 +- tox.ini | 4 +- 37 files changed, 196 insertions(+), 343 deletions(-) delete mode 100644 .github/workflows/test-cygwin.yml diff --git a/.ci/install.sh b/.ci/install.sh index acb84f046..2178c6646 100755 --- a/.ci/install.sh +++ b/.ci/install.sh @@ -13,24 +13,21 @@ aptget_update() return 1 fi } -if [[ $(uname) != CYGWIN* ]]; then - aptget_update || aptget_update retry || aptget_update retry -fi +aptget_update || aptget_update retry || aptget_update retry set -e -if [[ $(uname) != CYGWIN* ]]; then - sudo apt-get -qq install libfreetype6-dev liblcms2-dev libtiff-dev python3-tk\ - ghostscript libjpeg-turbo8-dev libopenjp2-7-dev\ - cmake meson imagemagick libharfbuzz-dev libfribidi-dev\ - sway wl-clipboard libopenblas-dev nasm -fi +sudo apt-get -qq install libfreetype6-dev liblcms2-dev libtiff-dev python3-tk\ + ghostscript libjpeg-turbo8-dev libopenjp2-7-dev\ + cmake meson imagemagick libharfbuzz-dev libfribidi-dev\ + sway wl-clipboard libopenblas-dev nasm python3 -m pip install --upgrade pip python3 -m pip install --upgrade wheel python3 -m pip install coverage python3 -m pip install defusedxml python3 -m pip install ipython +python3 -m pip install numpy python3 -m pip install olefile python3 -m pip install -U pytest python3 -m pip install -U pytest-cov @@ -40,36 +37,24 @@ python3 -m pip install pyroma # fails on beta 3.14 and PyPy python3 -m pip install --only-binary=:all: pyarrow || true -if [[ $(uname) != CYGWIN* ]]; then - python3 -m pip install numpy - - # PyQt6 doesn't support PyPy3 - if [[ $GHA_PYTHON_VERSION == 3.* ]]; then - sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0 - # TODO Update condition when pyqt6 supports free-threading - if ! [[ "$PYTHON_GIL" == "0" ]]; then python3 -m pip install pyqt6 ; fi - fi - - # Pyroma uses non-isolated build and fails with old setuptools - if [[ $GHA_PYTHON_VERSION == 3.9 ]]; then - # To match pyproject.toml - python3 -m pip install "setuptools>=77" - fi - - # webp - pushd depends && ./install_webp.sh && popd - - # libimagequant - pushd depends && ./install_imagequant.sh && popd - - # raqm - pushd depends && ./install_raqm.sh && popd - - # libavif - pushd depends && ./install_libavif.sh && popd - - # extra test images - pushd depends && ./install_extra_test_images.sh && popd -else - cd depends && ./install_extra_test_images.sh && cd .. +# PyQt6 doesn't support PyPy3 +if [[ $GHA_PYTHON_VERSION == 3.* ]]; then + sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0 + # TODO Update condition when pyqt6 supports free-threading + if ! [[ "$PYTHON_GIL" == "0" ]]; then python3 -m pip install pyqt6 ; fi fi + +# webp +pushd depends && ./install_webp.sh && popd + +# libimagequant +pushd depends && ./install_imagequant.sh && popd + +# raqm +pushd depends && ./install_raqm.sh && popd + +# libavif +pushd depends && ./install_libavif.sh && popd + +# extra test images +pushd depends && ./install_extra_test_images.sh && popd diff --git a/.github/mergify.yml b/.github/mergify.yml index 9bb089615..14222db10 100644 --- a/.github/mergify.yml +++ b/.github/mergify.yml @@ -8,7 +8,6 @@ pull_request_rules: - status-success=Docker Test Successful - status-success=Windows Test Successful - status-success=MinGW - - status-success=Cygwin Test Successful actions: merge: method: merge diff --git a/.github/workflows/test-cygwin.yml b/.github/workflows/test-cygwin.yml deleted file mode 100644 index 581cd6370..000000000 --- a/.github/workflows/test-cygwin.yml +++ /dev/null @@ -1,150 +0,0 @@ -name: Test Cygwin - -on: - push: - branches: - - "**" - paths-ignore: - - ".github/workflows/docs.yml" - - ".github/workflows/wheels*" - - ".gitmodules" - - "docs/**" - - "wheels/**" - pull_request: - paths-ignore: - - ".github/workflows/docs.yml" - - ".github/workflows/wheels*" - - ".gitmodules" - - "docs/**" - - "wheels/**" - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - COVERAGE_CORE: sysmon - -jobs: - build: - runs-on: windows-latest - strategy: - fail-fast: false - matrix: - python-minor-version: [9] - - timeout-minutes: 40 - - name: Python 3.${{ matrix.python-minor-version }} - - steps: - - name: Fix line endings - run: | - git config --global core.autocrlf input - - - name: Checkout Pillow - uses: actions/checkout@v4 - with: - persist-credentials: false - - - name: Install Cygwin - uses: cygwin/cygwin-install-action@v6 - with: - packages: > - gcc-g++ - ghostscript - git - ImageMagick - jpeg - libfreetype-devel - libimagequant-devel - libjpeg-devel - liblapack-devel - liblcms2-devel - libopenjp2-devel - libraqm-devel - libtiff-devel - libwebp-devel - libxcb-devel - libxcb-xinerama0 - make - netpbm - perl - python3${{ matrix.python-minor-version }}-cython - python3${{ matrix.python-minor-version }}-devel - python3${{ matrix.python-minor-version }}-ipython - python3${{ matrix.python-minor-version }}-numpy - python3${{ matrix.python-minor-version }}-sip - python3${{ matrix.python-minor-version }}-tkinter - wget - xorg-server-extra - zlib-devel - - - name: Add Lapack to PATH - uses: egor-tensin/cleanup-path@v4 - with: - dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack' - - - name: pip cache - uses: actions/cache@v4 - with: - path: 'C:\cygwin\home\runneradmin\.cache\pip' - key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-${{ hashFiles('.ci/install.sh') }} - restore-keys: | - ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}- - - - name: Build system information - run: | - dash.exe -c "python3 .github/workflows/system-info.py" - - - name: Install dependencies - run: | - bash.exe .ci/install.sh - - - name: Build - shell: bash.exe -eo pipefail -o igncr "{0}" - run: | - .ci/build.sh - - - name: Test - run: | - bash.exe xvfb-run -s '-screen 0 1024x768x24' .ci/test.sh - - - name: Prepare to upload errors - if: failure() - run: | - dash.exe -c "mkdir -p Tests/errors" - - - name: Upload errors - uses: actions/upload-artifact@v4 - if: failure() - with: - name: errors - path: Tests/errors - - - name: After success - run: | - bash.exe .ci/after_success.sh - rm C:\cygwin\bin\bash.EXE - - - name: Upload coverage - uses: codecov/codecov-action@v5 - with: - files: ./coverage.xml - flags: GHA_Cygwin - name: Cygwin Python 3.${{ matrix.python-minor-version }} - token: ${{ secrets.CODECOV_ORG_TOKEN }} - - success: - permissions: - contents: none - needs: build - runs-on: ubuntu-latest - name: Cygwin Test Successful - steps: - - name: Success - run: echo Cygwin Test Successful diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 766c506e7..c80bb6eb6 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -35,11 +35,11 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["pypy3.11", "3.10", "3.11", "3.12", ">=3.13.5", "3.14"] + python-version: ["pypy3.11", "3.11", "3.12", "3.13", "3.14"] architecture: ["x64"] include: # Test the oldest Python on 32-bit - - { python-version: "3.9", architecture: "x86" } + - { python-version: "3.10", architecture: "x86" } timeout-minutes: 45 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d18023dbc..c075f04d7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,18 +49,17 @@ jobs: "3.12", "3.11", "3.10", - "3.9", ] include: - - { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" } - - { python-version: "3.10", PYTHONOPTIMIZE: 2 } + - { python-version: "3.12", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" } + - { python-version: "3.11", PYTHONOPTIMIZE: 2 } # Free-threaded - { python-version: "3.14t", disable-gil: true } - { python-version: "3.13t", disable-gil: true } - # M1 only available for 3.10+ - - { os: "macos-13", python-version: "3.9" } + # Intel + - { os: "macos-13", python-version: "3.10" } exclude: - - { os: "macos-latest", python-version: "3.9" } + - { os: "macos-latest", python-version: "3.10" } runs-on: ${{ matrix.os }} name: ${{ matrix.os }} Python ${{ matrix.python-version }} diff --git a/README.md b/README.md index 365d356a0..8585ef6cb 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,6 @@ As of 2019, Pillow development is GitHub Actions build status (Test MinGW) - GitHub Actions build status (Test Cygwin) GitHub Actions build status (Test Docker) diff --git a/Tests/helper.py b/Tests/helper.py index df99f5f55..e0dc8a9d4 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -10,17 +10,20 @@ import shutil import subprocess import sys import tempfile -from collections.abc import Sequence from functools import lru_cache from io import BytesIO -from pathlib import Path -from typing import Any, Callable import pytest from packaging.version import parse as parse_version from PIL import Image, ImageFile, ImageMath, features +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Callable, Sequence + from pathlib import Path + from typing import Any + logger = logging.getLogger(__name__) uploader = None diff --git a/Tests/test_features.py b/Tests/test_features.py index d9212daee..93d803fc1 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -2,7 +2,6 @@ from __future__ import annotations import io import re -from typing import Callable import pytest @@ -10,6 +9,10 @@ from PIL import features from .helper import skip_unless_feature +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Callable + def test_check() -> None: # Check the correctness of the convenience function diff --git a/Tests/test_format_hsv.py b/Tests/test_format_hsv.py index 9cbf18566..861eccc11 100644 --- a/Tests/test_format_hsv.py +++ b/Tests/test_format_hsv.py @@ -2,12 +2,15 @@ from __future__ import annotations import colorsys import itertools -from typing import Callable from PIL import Image from .helper import assert_image_similar, hopper +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Callable + def int_to_float(i: int) -> float: return i / 255 diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index 0429eb99d..7cf52ddba 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -1,7 +1,6 @@ from __future__ import annotations import math -from typing import Callable import pytest @@ -9,6 +8,10 @@ from PIL import Image, ImageTransform from .helper import assert_image_equal, assert_image_similar, hopper +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Callable + class TestImageTransform: def test_sanity(self) -> None: diff --git a/Tests/test_imagechops.py b/Tests/test_imagechops.py index 4309214f5..61812ca7d 100644 --- a/Tests/test_imagechops.py +++ b/Tests/test_imagechops.py @@ -1,11 +1,13 @@ from __future__ import annotations -from typing import Callable - from PIL import Image, ImageChops from .helper import assert_image_equal, hopper +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Callable + BLACK = (0, 0, 0) BROWN = (127, 64, 0) CYAN = (0, 255, 255) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 46c1baa2d..5fd7caa7c 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -211,9 +211,10 @@ def test_exceptions() -> None: ImageCms.getProfileName(None) # type: ignore[arg-type] skip_missing() - # Python <= 3.9: "an integer is required (got type NoneType)" - # Python > 3.9: "'NoneType' object cannot be interpreted as an integer" - with pytest.raises(ImageCms.PyCMSError, match="integer"): + with pytest.raises( + ImageCms.PyCMSError, + match="'NoneType' object cannot be interpreted as an integer", + ): ImageCms.isIntentSupported(SRGB, None, None) # type: ignore[arg-type] diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index e1dcbc52c..406d965b4 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1,13 +1,10 @@ from __future__ import annotations import os.path -from collections.abc import Sequence -from typing import Callable import pytest from PIL import Image, ImageColor, ImageDraw, ImageFont, features -from PIL._typing import Coords from .helper import ( assert_image_equal, @@ -17,6 +14,12 @@ from .helper import ( skip_unless_feature, ) +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Callable, Sequence + + from PIL._typing import Coords + BLACK = (0, 0, 0) WHITE = (255, 255, 255) GRAY = (190, 190, 190) diff --git a/Tests/test_qt_image_qapplication.py b/Tests/test_qt_image_qapplication.py index 82a3e0741..b31e2a4ef 100644 --- a/Tests/test_qt_image_qapplication.py +++ b/Tests/test_qt_image_qapplication.py @@ -1,8 +1,5 @@ from __future__ import annotations -from pathlib import Path -from typing import Union - import pytest from PIL import Image, ImageQt @@ -11,18 +8,8 @@ from .helper import assert_image_equal_tofile, assert_image_similar, hopper TYPE_CHECKING = False if TYPE_CHECKING: - import PyQt6 - import PySide6 + from pathlib import Path - QApplication = Union[PyQt6.QtWidgets.QApplication, PySide6.QtWidgets.QApplication] - QHBoxLayout = Union[PyQt6.QtWidgets.QHBoxLayout, PySide6.QtWidgets.QHBoxLayout] - QImage = Union[PyQt6.QtGui.QImage, PySide6.QtGui.QImage] - QLabel = Union[PyQt6.QtWidgets.QLabel, PySide6.QtWidgets.QLabel] - QPainter = Union[PyQt6.QtGui.QPainter, PySide6.QtGui.QPainter] - QPixmap = Union[PyQt6.QtGui.QPixmap, PySide6.QtGui.QPixmap] - QPoint = Union[PyQt6.QtCore.QPoint, PySide6.QtCore.QPoint] - QRegion = Union[PyQt6.QtGui.QRegion, PySide6.QtGui.QRegion] - QWidget = Union[PyQt6.QtWidgets.QWidget, PySide6.QtWidgets.QWidget] if ImageQt.qt_is_installed: from PIL.ImageQt import QPixmap @@ -32,11 +19,16 @@ if ImageQt.qt_is_installed: from PyQt6.QtGui import QImage, QPainter, QRegion from PyQt6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget elif ImageQt.qt_version == "side6": - from PySide6.QtCore import QPoint - from PySide6.QtGui import QImage, QPainter, QRegion - from PySide6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget + from PySide6.QtCore import QPoint # type: ignore[assignment] + from PySide6.QtGui import QImage, QPainter, QRegion # type: ignore[assignment] + from PySide6.QtWidgets import ( # type: ignore[assignment] + QApplication, + QHBoxLayout, + QLabel, + QWidget, + ) - class Example(QWidget): # type: ignore[misc] + class Example(QWidget): def __init__(self) -> None: super().__init__() @@ -47,9 +39,9 @@ if ImageQt.qt_is_installed: pixmap1 = getattr(ImageQt.QPixmap, "fromImage")(qimage) # hbox - QHBoxLayout(self) # type: ignore[operator] + QHBoxLayout(self) - lbl = QLabel(self) # type: ignore[operator] + lbl = QLabel(self) # Segfault in the problem lbl.setPixmap(pixmap1.copy()) @@ -63,7 +55,7 @@ def roundtrip(expected: Image.Image) -> None: @pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed") def test_sanity(tmp_path: Path) -> None: # Segfault test - app: QApplication | None = QApplication([]) # type: ignore[operator] + app: QApplication | None = QApplication([]) ex = Example() assert app # Silence warning assert ex # Silence warning @@ -84,11 +76,11 @@ def test_sanity(tmp_path: Path) -> None: imageqt = ImageQt.ImageQt(im) data = getattr(QPixmap, "fromImage")(imageqt) qt_format = getattr(QImage, "Format") if ImageQt.qt_version == "6" else QImage - qimage = QImage(128, 128, getattr(qt_format, "Format_ARGB32")) # type: ignore[operator] - painter = QPainter(qimage) # type: ignore[operator] - image_label = QLabel() # type: ignore[operator] + qimage = QImage(128, 128, getattr(qt_format, "Format_ARGB32")) + painter = QPainter(qimage) + image_label = QLabel() image_label.setPixmap(data) - image_label.render(painter, QPoint(0, 0), QRegion(0, 0, 128, 128)) # type: ignore[operator] + image_label.render(painter, QPoint(0, 0), QRegion(0, 0, 128, 128)) painter.end() rendered_tempfile = str(tmp_path / f"temp_rendered_{mode}.png") qimage.save(rendered_tempfile) diff --git a/Tests/test_qt_image_toqimage.py b/Tests/test_qt_image_toqimage.py index 8cb7ffb9b..0004b5521 100644 --- a/Tests/test_qt_image_toqimage.py +++ b/Tests/test_qt_image_toqimage.py @@ -1,13 +1,15 @@ from __future__ import annotations -from pathlib import Path - import pytest from PIL import ImageQt from .helper import assert_image_equal, assert_image_equal_tofile, hopper +TYPE_CHECKING = False +if TYPE_CHECKING: + from pathlib import Path + pytestmark = pytest.mark.skipif( not ImageQt.qt_is_installed, reason="Qt bindings are not installed" ) @@ -21,7 +23,7 @@ def test_sanity(mode: str, tmp_path: Path) -> None: src = hopper(mode) data = ImageQt.toqimage(src) - assert isinstance(data, QImage) # type: ignore[arg-type, misc] + assert isinstance(data, QImage) assert not data.isNull() # reload directly from the qimage diff --git a/Tests/test_shell_injection.py b/Tests/test_shell_injection.py index 38d46f312..465517bb6 100644 --- a/Tests/test_shell_injection.py +++ b/Tests/test_shell_injection.py @@ -2,8 +2,6 @@ from __future__ import annotations import shutil from io import BytesIO -from pathlib import Path -from typing import IO, Callable import pytest @@ -11,6 +9,12 @@ from PIL import GifImagePlugin, Image, JpegImagePlugin from .helper import djpeg_available, is_win32, netpbm_available +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Callable + from pathlib import Path + from typing import IO + TEST_JPG = "Tests/images/hopper.jpg" TEST_GIF = "Tests/images/hopper.gif" diff --git a/checks/check_imaging_leaks.py b/checks/check_imaging_leaks.py index 231789ca0..a1d59ed9c 100755 --- a/checks/check_imaging_leaks.py +++ b/checks/check_imaging_leaks.py @@ -1,7 +1,8 @@ #!/usr/bin/env python3 from __future__ import annotations -from typing import Any, Callable +from collections.abc import Callable +from typing import Any import pytest diff --git a/docs/index.rst b/docs/index.rst index 689088d48..ee51621ac 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -29,10 +29,6 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more =2024.10.12", ] -optional-dependencies.typing = [ - "typing-extensions; python_version<'3.10'", -] optional-dependencies.xmp = [ "defusedxml", ] @@ -189,8 +185,8 @@ lint.ignore = [ "PT011", # pytest-raises-too-broad "PT012", # pytest-raises-with-multiple-statements "PT017", # pytest-assert-in-except - "PYI026", # flake8-pyi: typing.TypeAlias added in Python 3.10 "PYI034", # flake8-pyi: typing.Self added in Python 3.11 + "UP038", # pyupgrade: deprecated rule ] lint.per-file-ignores."Tests/oss-fuzz/fuzz_font.py" = [ "I002", @@ -216,7 +212,7 @@ testpaths = [ ] [tool.mypy] -python_version = "3.9" +python_version = "3.10" pretty = true disallow_any_generics = true enable_error_code = "ignore-without-code" diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index b03aa7f15..58c460ef3 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -31,7 +31,7 @@ import os import subprocess from enum import IntEnum from functools import cached_property -from typing import IO, Any, Literal, NamedTuple, Union, cast +from typing import Any, NamedTuple, cast from . import ( Image, @@ -49,6 +49,8 @@ from ._util import DeferredError TYPE_CHECKING = False if TYPE_CHECKING: + from typing import IO, Literal + from . import _imaging from ._typing import Buffer @@ -535,7 +537,7 @@ def _normalize_mode(im: Image.Image) -> Image.Image: return im.convert("L") -_Palette = Union[bytes, bytearray, list[int], ImagePalette.ImagePalette] +_Palette = bytes | bytearray | list[int] | ImagePalette.ImagePalette def _normalize_palette( diff --git a/src/PIL/GimpGradientFile.py b/src/PIL/GimpGradientFile.py index ec62f8e4e..5f2691882 100644 --- a/src/PIL/GimpGradientFile.py +++ b/src/PIL/GimpGradientFile.py @@ -21,10 +21,14 @@ See the GIMP distribution for more information.) from __future__ import annotations from math import log, pi, sin, sqrt -from typing import IO, Callable from ._binary import o8 +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Callable + from typing import IO + EPSILON = 1e-10 """""" # Enable auto-doc for data member diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index e95fa91f8..ed46899b4 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -34,20 +34,23 @@ from __future__ import annotations import math import struct from collections.abc import Sequence -from types import ModuleType -from typing import Any, AnyStr, Callable, Union, cast +from typing import cast from . import Image, ImageColor -from ._typing import Coords + +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Callable + from types import ModuleType + from typing import Any, AnyStr + + from . import ImageDraw2, ImageFont + from ._typing import Coords # experimental access to the outline API Outline: Callable[[], Image.core._Outline] = Image.core.outline -TYPE_CHECKING = False -if TYPE_CHECKING: - from . import ImageDraw2, ImageFont - -_Ink = Union[float, tuple[int, ...], str] +_Ink = float | tuple[int, ...] | str """ A simple 2D drawing interface for PIL images. diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index b9ed54ab2..9326eeeda 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -19,11 +19,14 @@ from __future__ import annotations import abc import functools from collections.abc import Sequence -from types import ModuleType -from typing import Any, Callable, cast +from typing import cast TYPE_CHECKING = False if TYPE_CHECKING: + from collections.abc import Callable + from types import ModuleType + from typing import Any + from . import _imaging from ._typing import NumpyArray diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py index d2504b1ae..dfdc50c05 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -17,11 +17,15 @@ from __future__ import annotations import builtins -from types import CodeType -from typing import Any, Callable from . import Image, _imagingmath +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Callable + from types import CodeType + from typing import Any + class _Operand: """Wraps an image operand, providing standard operators""" diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index df7a57b65..af4d0742d 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -19,23 +19,18 @@ from __future__ import annotations import sys from io import BytesIO -from typing import Any, Callable, Union from . import Image from ._util import is_path TYPE_CHECKING = False if TYPE_CHECKING: - import PyQt6 - import PySide6 + from collections.abc import Callable + from typing import Any from . import ImageFile QBuffer: type - QByteArray = Union[PyQt6.QtCore.QByteArray, PySide6.QtCore.QByteArray] - QIODevice = Union[PyQt6.QtCore.QIODevice, PySide6.QtCore.QIODevice] - QImage = Union[PyQt6.QtGui.QImage, PySide6.QtGui.QImage] - QPixmap = Union[PyQt6.QtGui.QPixmap, PySide6.QtGui.QPixmap] qt_version: str | None qt_versions = [ @@ -49,11 +44,15 @@ for version, qt_module in qt_versions: try: qRgba: Callable[[int, int, int, int], int] if qt_module == "PyQt6": - from PyQt6.QtCore import QBuffer, QIODevice + from PyQt6.QtCore import QBuffer, QByteArray, QIODevice from PyQt6.QtGui import QImage, QPixmap, qRgba elif qt_module == "PySide6": - from PySide6.QtCore import QBuffer, QIODevice - from PySide6.QtGui import QImage, QPixmap, qRgba + from PySide6.QtCore import ( # type: ignore[assignment] + QBuffer, + QByteArray, + QIODevice, + ) + from PySide6.QtGui import QImage, QPixmap, qRgba # type: ignore[assignment] except (ImportError, RuntimeError): continue qt_is_installed = True @@ -183,7 +182,7 @@ def _toqclass_helper(im: Image.Image | str | QByteArray) -> dict[str, Any]: if qt_is_installed: - class ImageQt(QImage): # type: ignore[misc] + class ImageQt(QImage): def __init__(self, im: Image.Image | str | QByteArray) -> None: """ An PIL image wrapper for Qt. This is a subclass of PyQt's QImage diff --git a/src/PIL/ImageSequence.py b/src/PIL/ImageSequence.py index a6fc340d5..361be4897 100644 --- a/src/PIL/ImageSequence.py +++ b/src/PIL/ImageSequence.py @@ -16,10 +16,12 @@ ## from __future__ import annotations -from typing import Callable - from . import Image +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Callable + class Iterator: """ diff --git a/src/PIL/PcfFontFile.py b/src/PIL/PcfFontFile.py index 0d1968b14..a00e9b919 100644 --- a/src/PIL/PcfFontFile.py +++ b/src/PIL/PcfFontFile.py @@ -18,7 +18,6 @@ from __future__ import annotations import io -from typing import BinaryIO, Callable from . import FontFile, Image from ._binary import i8 @@ -27,6 +26,11 @@ from ._binary import i16le as l16 from ._binary import i32be as b32 from ._binary import i32le as l32 +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Callable + from typing import BinaryIO + # -------------------------------------------------------------------- # declarations diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index 73d8c21c0..2c9031469 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -8,7 +8,15 @@ import os import re import time import zlib -from typing import IO, Any, NamedTuple, Union +from typing import Any, NamedTuple + +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import IO + + _DictBase = collections.UserDict[str | bytes, Any] +else: + _DictBase = collections.UserDict # see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set @@ -251,13 +259,6 @@ class PdfArray(list[Any]): return b"[ " + b" ".join(pdf_repr(x) for x in self) + b" ]" -TYPE_CHECKING = False -if TYPE_CHECKING: - _DictBase = collections.UserDict[Union[str, bytes], Any] -else: - _DictBase = collections.UserDict - - class PdfDict(_DictBase): def __setattr__(self, key: str, value: Any) -> None: if key == "data": diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index c1850f084..c1741284b 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -47,22 +47,24 @@ import math import os import struct import warnings -from collections.abc import Iterator, MutableMapping +from collections.abc import Callable, MutableMapping from fractions import Fraction from numbers import Number, Rational -from typing import IO, Any, Callable, NoReturn, cast +from typing import IO, Any, cast from . import ExifTags, Image, ImageFile, ImageOps, ImagePalette, TiffTags from ._binary import i16be as i16 from ._binary import i32be as i32 from ._binary import o8 -from ._typing import StrOrBytesPath from ._util import DeferredError, is_path from .TiffTags import TYPES TYPE_CHECKING = False if TYPE_CHECKING: - from ._typing import Buffer, IntegralLike + from collections.abc import Iterator + from typing import NoReturn + + from ._typing import Buffer, IntegralLike, StrOrBytesPath logger = logging.getLogger(__name__) diff --git a/src/PIL/_imagingcms.pyi b/src/PIL/_imagingcms.pyi index ddcf93ab1..4fc0d60ab 100644 --- a/src/PIL/_imagingcms.pyi +++ b/src/PIL/_imagingcms.pyi @@ -1,14 +1,14 @@ import datetime import sys -from typing import Literal, SupportsFloat, TypedDict +from typing import Literal, SupportsFloat, TypeAlias, TypedDict from ._typing import CapsuleType littlecms_version: str | None -_Tuple3f = tuple[float, float, float] -_Tuple2x3f = tuple[_Tuple3f, _Tuple3f] -_Tuple3x3f = tuple[_Tuple3f, _Tuple3f, _Tuple3f] +_Tuple3f: TypeAlias = tuple[float, float, float] +_Tuple2x3f: TypeAlias = tuple[_Tuple3f, _Tuple3f] +_Tuple3x3f: TypeAlias = tuple[_Tuple3f, _Tuple3f, _Tuple3f] class _IccMeasurementCondition(TypedDict): observer: int diff --git a/src/PIL/_imagingft.pyi b/src/PIL/_imagingft.pyi index 1cb1429d6..2136810ba 100644 --- a/src/PIL/_imagingft.pyi +++ b/src/PIL/_imagingft.pyi @@ -1,4 +1,5 @@ -from typing import Any, Callable +from collections.abc import Callable +from typing import Any from . import ImageFont, _imaging diff --git a/src/PIL/_typing.py b/src/PIL/_typing.py index e94045260..979147e0c 100644 --- a/src/PIL/_typing.py +++ b/src/PIL/_typing.py @@ -3,7 +3,7 @@ from __future__ import annotations import os import sys from collections.abc import Sequence -from typing import Any, Protocol, TypeVar, Union +from typing import Any, Protocol, TypeVar TYPE_CHECKING = False if TYPE_CHECKING: @@ -26,19 +26,8 @@ if sys.version_info >= (3, 12): else: Buffer = Any -if sys.version_info >= (3, 10): - from typing import TypeGuard -else: - try: - from typing_extensions import TypeGuard - except ImportError: - class TypeGuard: # type: ignore[no-redef] - def __class_getitem__(cls, item: Any) -> type[bool]: - return bool - - -Coords = Union[Sequence[float], Sequence[Sequence[float]]] +Coords = Sequence[float] | Sequence[Sequence[float]] _T_co = TypeVar("_T_co", covariant=True) @@ -48,7 +37,7 @@ class SupportsRead(Protocol[_T_co]): def read(self, length: int = ..., /) -> _T_co: ... -StrOrBytesPath = Union[str, bytes, os.PathLike[str], os.PathLike[bytes]] +StrOrBytesPath = str | bytes | os.PathLike[str] | os.PathLike[bytes] -__all__ = ["Buffer", "IntegralLike", "StrOrBytesPath", "SupportsRead", "TypeGuard"] +__all__ = ["Buffer", "IntegralLike", "StrOrBytesPath", "SupportsRead"] diff --git a/src/PIL/_util.py b/src/PIL/_util.py index 8ef0d36f7..b1fa6a0f3 100644 --- a/src/PIL/_util.py +++ b/src/PIL/_util.py @@ -1,9 +1,12 @@ from __future__ import annotations import os -from typing import Any, NoReturn -from ._typing import StrOrBytesPath, TypeGuard +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import Any, NoReturn, TypeGuard + + from ._typing import StrOrBytesPath def is_path(f: Any) -> TypeGuard[StrOrBytesPath]: diff --git a/tox.ini b/tox.ini index 967d4b537..8933945b1 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ requires = tox>=4.2 env_list = lint - py{py3, 314, 313, 312, 311, 310, 39} + py{py3, 314, 313, 312, 311, 310} [testenv] deps = @@ -29,7 +29,5 @@ commands = skip_install = true deps = -r .ci/requirements-mypy.txt -extras = - typing commands = mypy conftest.py selftest.py setup.py docs src winbuild Tests {posargs} From 148e1ac914c411925df2de1972d88f7a01ccde9e Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 2 Aug 2025 20:10:55 +0800 Subject: [PATCH 138/309] Add libavif support for iOS (#9117) Co-authored-by: Andrew Murray --- .github/workflows/wheels-dependencies.sh | 39 +++++++++++++++++------- checks/check_wheel.py | 3 +- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 4519271b9..d58c65126 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -186,30 +186,43 @@ function build_libavif { python3 -m pip install meson ninja - if [[ "$PLAT" == "x86_64" ]] || [ -n "$SANITIZER" ]; then + if ([[ "$PLAT" == "x86_64" ]] && [[ -z "$IOS_SDK" ]]) || [ -n "$SANITIZER" ]; then build_simple nasm 2.16.03 https://www.nasm.us/pub/nasm/releasebuilds/2.16.03 fi local build_type=MinSizeRel + local build_shared=ON local lto=ON local libavif_cmake_flags - if [ -n "$IS_MACOS" ]; then + if [[ -n "$IS_MACOS" ]]; then lto=OFF libavif_cmake_flags=( -DCMAKE_C_FLAGS_MINSIZEREL="-Oz -DNDEBUG -flto" \ -DCMAKE_CXX_FLAGS_MINSIZEREL="-Oz -DNDEBUG -flto" \ -DCMAKE_SHARED_LINKER_FLAGS_INIT="-Wl,-S,-x,-dead_strip_dylibs" \ ) + if [[ -n "$IOS_SDK" ]]; then + build_shared=OFF + fi else if [[ "$MB_ML_VER" == 2014 ]] && [[ "$PLAT" == "x86_64" ]]; then build_type=Release fi libavif_cmake_flags=(-DCMAKE_SHARED_LINKER_FLAGS_INIT="-Wl,--strip-all,-z,relro,-z,now") fi + if [[ -n "$IOS_SDK" ]] && [[ "$PLAT" == "x86_64" ]]; then + libavif_cmake_flags+=(-DAOM_TARGET_CPU=generic) + else + libavif_cmake_flags+=( + -DAVIF_CODEC_AOM_DECODE=OFF \ + -DAVIF_CODEC_DAV1D=LOCAL + ) + fi local out_dir=$(fetch_unpack https://github.com/AOMediaCodec/libavif/archive/refs/tags/v$LIBAVIF_VERSION.tar.gz libavif-$LIBAVIF_VERSION.tar.gz) + # CONFIG_AV1_HIGHBITDEPTH=0 is a flag for libaom (included as a subproject # of libavif) that disables support for encoding high bit depth images. (cd $out_dir \ @@ -217,20 +230,27 @@ function build_libavif { -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX \ -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib \ -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib \ - -DBUILD_SHARED_LIBS=ON \ + -DBUILD_SHARED_LIBS=$build_shared \ -DAVIF_LIBSHARPYUV=LOCAL \ -DAVIF_LIBYUV=LOCAL \ -DAVIF_CODEC_AOM=LOCAL \ -DCONFIG_AV1_HIGHBITDEPTH=0 \ - -DAVIF_CODEC_AOM_DECODE=OFF \ - -DAVIF_CODEC_DAV1D=LOCAL \ -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=$lto \ -DCMAKE_C_VISIBILITY_PRESET=hidden \ -DCMAKE_CXX_VISIBILITY_PRESET=hidden \ -DCMAKE_BUILD_TYPE=$build_type \ "${libavif_cmake_flags[@]}" \ - . \ - && make install) + $HOST_CMAKE_FLAGS . ) + + if [[ -n "$IOS_SDK" ]]; then + # libavif's CMake configuration generates a meson cross file... but it + # doesn't work for iOS cross-compilation. Copy in Pillow-generated + # meson-cross config to replace the cmake-generated version. + cp $WORKDIR/meson-cross.txt $out_dir/crossfile-apple.meson + fi + + (cd $out_dir && make install) + touch libavif-stamp } @@ -268,10 +288,7 @@ function build { build_tiff fi - if [[ -z "$IOS_SDK" ]]; then - # Short term workaround; don't build libavif on iOS - build_libavif - fi + build_libavif build_libpng build_lcms2 build_openjpeg diff --git a/checks/check_wheel.py b/checks/check_wheel.py index 3d806eb71..937722c4b 100644 --- a/checks/check_wheel.py +++ b/checks/check_wheel.py @@ -25,8 +25,7 @@ def test_wheel_modules() -> None: elif sys.platform == "ios": # tkinter is not available on iOS - # libavif is not available on iOS (for now) - expected_modules -= {"tkinter", "avif"} + expected_modules.remove("tkinter") assert set(features.get_supported_modules()) == expected_modules From 77247b62833afc78d561ce16ec8c34aae35f58c9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 3 Aug 2025 12:48:47 +1000 Subject: [PATCH 139/309] Update dependency cibuildwheel to v3.1.3 (#9129) --- .ci/requirements-cibw.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/requirements-cibw.txt b/.ci/requirements-cibw.txt index 823671828..9f9136557 100644 --- a/.ci/requirements-cibw.txt +++ b/.ci/requirements-cibw.txt @@ -1 +1 @@ -cibuildwheel==3.1.2 +cibuildwheel==3.1.3 From 4677cf3b1600dae5687efe651c2814f6f0f48541 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 3 Aug 2025 13:58:41 +1000 Subject: [PATCH 140/309] Update dependency mypy to v1.17.1 (#9130) --- .ci/requirements-mypy.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/requirements-mypy.txt b/.ci/requirements-mypy.txt index 99eac6027..bd9563800 100644 --- a/.ci/requirements-mypy.txt +++ b/.ci/requirements-mypy.txt @@ -1,4 +1,4 @@ -mypy==1.17.0 +mypy==1.17.1 IceSpringPySideStubs-PyQt6 IceSpringPySideStubs-PySide6 ipython From 2973f69a756283fef2609ff473495827591b4551 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Mon, 4 Aug 2025 21:36:17 +1000 Subject: [PATCH 141/309] Updated libimagequant to 4.4.0 (#9074) --- depends/install_imagequant.sh | 2 +- docs/installation/building-from-source.rst | 2 +- winbuild/build_prepare.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/depends/install_imagequant.sh b/depends/install_imagequant.sh index 88756f8f9..357214f1f 100755 --- a/depends/install_imagequant.sh +++ b/depends/install_imagequant.sh @@ -2,7 +2,7 @@ # install libimagequant archive_name=libimagequant -archive_version=4.3.4 +archive_version=4.4.0 archive=$archive_name-$archive_version diff --git a/docs/installation/building-from-source.rst b/docs/installation/building-from-source.rst index 59c595742..fc7ef7646 100644 --- a/docs/installation/building-from-source.rst +++ b/docs/installation/building-from-source.rst @@ -64,7 +64,7 @@ Many of Pillow's features require external libraries: * **libimagequant** provides improved color quantization - * Pillow has been tested with libimagequant **2.6-4.3.4** + * Pillow has been tested with libimagequant **2.6-4.4.0** * Libimagequant is licensed GPLv3, which is more restrictive than the Pillow license, therefore we will not be distributing binaries with libimagequant support enabled. diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index fbff0daf2..4fab5f4c4 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -120,7 +120,7 @@ V = { "JPEGTURBO": "3.1.1", "LCMS2": "2.17", "LIBAVIF": "1.3.0", - "LIBIMAGEQUANT": "4.3.4", + "LIBIMAGEQUANT": "4.4.0", "LIBPNG": "1.6.50", "LIBWEBP": "1.6.0", "OPENJPEG": "2.5.3", From cee238bcb8ca6a49e21064dd5c40440bed838503 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 06:57:50 +1000 Subject: [PATCH 142/309] [pre-commit.ci] pre-commit autoupdate (#9131) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 75c7d3632..cff17cd36 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.2 + rev: v0.12.7 hooks: - id: ruff-check args: [--exit-non-zero-on-fix] @@ -24,7 +24,7 @@ repos: exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$|\.patch$) - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v20.1.7 + rev: v20.1.8 hooks: - id: clang-format types: [c] @@ -79,7 +79,7 @@ repos: additional_dependencies: [trove-classifiers>=2024.10.12] - repo: https://github.com/tox-dev/tox-ini-fmt - rev: 1.5.0 + rev: 1.6.0 hooks: - id: tox-ini-fmt From 0465627f0c43eb6a9a9b971d0ca0406e5b82cc8b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 5 Aug 2025 13:00:33 +1000 Subject: [PATCH 143/309] Fill alpha channel when quantizing RGB images --- Tests/test_image_quantize.py | 9 +++++++++ src/libImaging/Quant.c | 28 ++++++++++++++++------------ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index 6d313cb8c..4a0732269 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -116,6 +116,15 @@ def test_quantize_kmeans(method: Image.Quantize) -> None: im.quantize(kmeans=-1, method=method) +@skip_unless_feature("libimagequant") +def test_resize() -> None: + im = hopper().resize((100, 100)) + converted = im.quantize(100, Image.Quantize.LIBIMAGEQUANT) + colors = converted.getcolors() + assert colors is not None + assert len(colors) == 100 + + def test_colors() -> None: im = hopper() colors = 2 diff --git a/src/libImaging/Quant.c b/src/libImaging/Quant.c index a489a882d..2ad990227 100644 --- a/src/libImaging/Quant.c +++ b/src/libImaging/Quant.c @@ -1745,19 +1745,23 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) { for (i = y = 0; y < im->ysize; y++) { for (x = 0; x < im->xsize; x++, i++) { p[i].v = im->image32[y][x]; - if (withAlpha && p[i].c.a == 0) { - if (transparency == 0) { - transparency = 1; - r = p[i].c.r; - g = p[i].c.g; - b = p[i].c.b; - } else { - /* Set all subsequent transparent pixels - to the same colour as the first */ - p[i].c.r = r; - p[i].c.g = g; - p[i].c.b = b; + if (withAlpha) { + if (p[i].c.a == 0) { + if (transparency == 0) { + transparency = 1; + r = p[i].c.r; + g = p[i].c.g; + b = p[i].c.b; + } else { + /* Set all subsequent transparent pixels + to the same colour as the first */ + p[i].c.r = r; + p[i].c.g = g; + p[i].c.b = b; + } } + } else { + p[i].c.a = 255; } } } From d3fa549ec941997dfa48e59ecf0aa3cdeb007070 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 5 Aug 2025 18:03:47 +1000 Subject: [PATCH 144/309] Use Python 3.14 for gcc problem matching --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c075f04d7..0fad22a03 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -111,7 +111,7 @@ jobs: GHA_PYTHON_VERSION: ${{ matrix.python-version }} - name: Register gcc problem matcher - if: "matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13'" + if: "matrix.os == 'ubuntu-latest' && matrix.python-version == '3.14'" run: echo "::add-matcher::.github/problem-matchers/gcc.json" - name: Build From b07dbc167c3040f076ad679c5459979cb2ca71d7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 6 Aug 2025 08:17:09 +1000 Subject: [PATCH 145/309] Fixed typo --- docs/handbook/third-party-plugins.rst | 2 +- src/PIL/WmfImagePlugin.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/handbook/third-party-plugins.rst b/docs/handbook/third-party-plugins.rst index a189a5773..1c7dfb5e9 100644 --- a/docs/handbook/third-party-plugins.rst +++ b/docs/handbook/third-party-plugins.rst @@ -11,7 +11,7 @@ Here is a list of PyPI projects that offer additional plugins: * :pypi:`heif-image-plugin`: Simple HEIF/HEIC images plugin, based on the pyheif library. * :pypi:`jxlpy`: Introduces reading and writing support for JPEG XL. * :pypi:`pillow-heif`: Python bindings to libheif for working with HEIF images. -* :pypi:`pillow-jpls`: Plugin for the JPEG-LS codec, based on the Charls JPEG-LS implemetation. Python bindings implemented using pybind11. +* :pypi:`pillow-jpls`: Plugin for the JPEG-LS codec, based on the Charls JPEG-LS implementation. Python bindings implemented using pybind11. * :pypi:`pillow-jxl-plugin`: Plugin for JPEG-XL, using Rust for bindings. * :pypi:`pillow-mbm`: Adds support for KSP's proprietary MBM texture format. * :pypi:`pillow-svg`: Implements basic SVG read support. Supports basic paths, shapes, and text. diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py index d569cb4b8..de714d337 100644 --- a/src/PIL/WmfImagePlugin.py +++ b/src/PIL/WmfImagePlugin.py @@ -80,7 +80,7 @@ class WmfStubImageFile(ImageFile.StubImageFile): format_description = "Windows Metafile" def _open(self) -> None: - # check placable header + # check placeable header s = self.fp.read(44) if s.startswith(b"\xd7\xcd\xc6\x9a\x00\x00"): From 4f8ac76407f6dbaf0563b55700731955850170cf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 6 Aug 2025 09:00:36 +1000 Subject: [PATCH 146/309] Updated raqm to 0.10.3 --- depends/install_raqm.sh | 2 +- src/thirdparty/raqm/COPYING | 2 +- src/thirdparty/raqm/NEWS | 16 ++++++ src/thirdparty/raqm/raqm-version.h | 4 +- src/thirdparty/raqm/raqm.c | 84 ++++++++++++++++-------------- 5 files changed, 65 insertions(+), 43 deletions(-) diff --git a/depends/install_raqm.sh b/depends/install_raqm.sh index 5d862403e..b5a05100b 100755 --- a/depends/install_raqm.sh +++ b/depends/install_raqm.sh @@ -2,7 +2,7 @@ # install raqm -archive=libraqm-0.10.2 +archive=libraqm-0.10.3 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz diff --git a/src/thirdparty/raqm/COPYING b/src/thirdparty/raqm/COPYING index 97e2489b7..964318a8a 100644 --- a/src/thirdparty/raqm/COPYING +++ b/src/thirdparty/raqm/COPYING @@ -1,7 +1,7 @@ The MIT License (MIT) Copyright © 2015 Information Technology Authority (ITA) -Copyright © 2016-2023 Khaled Hosny +Copyright © 2016-2025 Khaled Hosny Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/thirdparty/raqm/NEWS b/src/thirdparty/raqm/NEWS index e8bf32e0b..fb432cffb 100644 --- a/src/thirdparty/raqm/NEWS +++ b/src/thirdparty/raqm/NEWS @@ -1,3 +1,19 @@ +Overview of changes leading to 0.10.3 +Tuesday, August 5, 2025 +==================================== + +Fix raqm_set_text_utf8/utf16 reading beyond len for multibyte. + +Support building against SheenBidi 2.9. + +Fix deprecation warning with latest HarfBuzz. + +Overview of changes leading to 0.10.2 +Sunday, September 22, 2024 +==================================== + +Fix Unicode codepoint conversion from UTF-16. + Overview of changes leading to 0.10.1 Wednesday, April 12, 2023 ==================================== diff --git a/src/thirdparty/raqm/raqm-version.h b/src/thirdparty/raqm/raqm-version.h index 62d2d2064..f2dd61cf6 100644 --- a/src/thirdparty/raqm/raqm-version.h +++ b/src/thirdparty/raqm/raqm-version.h @@ -33,9 +33,9 @@ #define RAQM_VERSION_MAJOR 0 #define RAQM_VERSION_MINOR 10 -#define RAQM_VERSION_MICRO 1 +#define RAQM_VERSION_MICRO 3 -#define RAQM_VERSION_STRING "0.10.1" +#define RAQM_VERSION_STRING "0.10.3" #define RAQM_VERSION_ATLEAST(major,minor,micro) \ ((major)*10000+(minor)*100+(micro) <= \ diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c index 2b331e1af..9ecc5cac8 100644 --- a/src/thirdparty/raqm/raqm.c +++ b/src/thirdparty/raqm/raqm.c @@ -30,7 +30,11 @@ #include #ifdef RAQM_SHEENBIDI +#ifdef RAQM_SHEENBIDI_GT_2_9 +#include +#else #include +#endif #else #ifdef HAVE_FRIBIDI_SYSTEM #include @@ -546,34 +550,32 @@ raqm_set_text (raqm_t *rq, return true; } -static void * -_raqm_get_utf8_codepoint (const void *str, +static const char * +_raqm_get_utf8_codepoint (const char *str, uint32_t *out_codepoint) { - const char *s = (const char *)str; - - if (0xf0 == (0xf8 & s[0])) + if (0xf0 == (0xf8 & str[0])) { - *out_codepoint = ((0x07 & s[0]) << 18) | ((0x3f & s[1]) << 12) | ((0x3f & s[2]) << 6) | (0x3f & s[3]); - s += 4; + *out_codepoint = ((0x07 & str[0]) << 18) | ((0x3f & str[1]) << 12) | ((0x3f & str[2]) << 6) | (0x3f & str[3]); + str += 4; } - else if (0xe0 == (0xf0 & s[0])) + else if (0xe0 == (0xf0 & str[0])) { - *out_codepoint = ((0x0f & s[0]) << 12) | ((0x3f & s[1]) << 6) | (0x3f & s[2]); - s += 3; + *out_codepoint = ((0x0f & str[0]) << 12) | ((0x3f & str[1]) << 6) | (0x3f & str[2]); + str += 3; } - else if (0xc0 == (0xe0 & s[0])) + else if (0xc0 == (0xe0 & str[0])) { - *out_codepoint = ((0x1f & s[0]) << 6) | (0x3f & s[1]); - s += 2; + *out_codepoint = ((0x1f & str[0]) << 6) | (0x3f & str[1]); + str += 2; } else { - *out_codepoint = s[0]; - s += 1; + *out_codepoint = str[0]; + str += 1; } - return (void *)s; + return str; } static size_t @@ -585,42 +587,41 @@ _raqm_u8_to_u32 (const char *text, size_t len, uint32_t *unicode) while ((*in_utf8 != '\0') && (in_len < len)) { - in_utf8 = _raqm_get_utf8_codepoint (in_utf8, out_utf32); + const char *out_utf8 = _raqm_get_utf8_codepoint (in_utf8, out_utf32); + in_len += out_utf8 - in_utf8; + in_utf8 = out_utf8; ++out_utf32; - ++in_len; } return (out_utf32 - unicode); } -static void * -_raqm_get_utf16_codepoint (const void *str, - uint32_t *out_codepoint) +static const uint16_t * +_raqm_get_utf16_codepoint (const uint16_t *str, + uint32_t *out_codepoint) { - const uint16_t *s = (const uint16_t *)str; - - if (s[0] > 0xD800 && s[0] < 0xDBFF) + if (str[0] >= 0xD800 && str[0] <= 0xDBFF) { - if (s[1] > 0xDC00 && s[1] < 0xDFFF) + if (str[1] >= 0xDC00 && str[1] <= 0xDFFF) { - uint32_t X = ((s[0] & ((1 << 6) -1)) << 10) | (s[1] & ((1 << 10) -1)); - uint32_t W = (s[0] >> 6) & ((1 << 5) - 1); + uint32_t X = ((str[0] & ((1 << 6) -1)) << 10) | (str[1] & ((1 << 10) -1)); + uint32_t W = (str[0] >> 6) & ((1 << 5) - 1); *out_codepoint = (W+1) << 16 | X; - s += 2; + str += 2; } else { /* A single high surrogate, this is an error. */ - *out_codepoint = s[0]; - s += 1; + *out_codepoint = str[0]; + str += 1; } } else { - *out_codepoint = s[0]; - s += 1; + *out_codepoint = str[0]; + str += 1; } - return (void *)s; + return str; } static size_t @@ -632,9 +633,10 @@ _raqm_u16_to_u32 (const uint16_t *text, size_t len, uint32_t *unicode) while ((*in_utf16 != '\0') && (in_len < len)) { - in_utf16 = _raqm_get_utf16_codepoint (in_utf16, out_utf32); + const uint16_t *out_utf16 = _raqm_get_utf16_codepoint (in_utf16, out_utf32); + in_len += (out_utf16 - in_utf16); + in_utf16 = out_utf16; ++out_utf32; - ++in_len; } return (out_utf32 - unicode); @@ -1114,12 +1116,12 @@ _raqm_set_spacing (raqm_t *rq, { if (_raqm_allowed_grapheme_boundary (rq->text[i], rq->text[i+1])) { - /* CSS word seperators, word spacing is only applied on these.*/ + /* CSS word separators, word spacing is only applied on these.*/ if (rq->text[i] == 0x0020 || /* Space */ rq->text[i] == 0x00A0 || /* No Break Space */ rq->text[i] == 0x1361 || /* Ethiopic Word Space */ - rq->text[i] == 0x10100 || /* Aegean Word Seperator Line */ - rq->text[i] == 0x10101 || /* Aegean Word Seperator Dot */ + rq->text[i] == 0x10100 || /* Aegean Word Separator Line */ + rq->text[i] == 0x10101 || /* Aegean Word Separator Dot */ rq->text[i] == 0x1039F || /* Ugaric Word Divider */ rq->text[i] == 0x1091F) /* Phoenician Word Separator */ { @@ -2167,6 +2169,10 @@ _raqm_ft_transform (int *x, *y = vector.y; } +#if !HB_VERSION_ATLEAST (10, 4, 0) +# define hb_ft_font_get_ft_face hb_ft_font_get_face +#endif + static bool _raqm_shape (raqm_t *rq) { @@ -2199,7 +2205,7 @@ _raqm_shape (raqm_t *rq) hb_glyph_position_t *pos; unsigned int len; - FT_Get_Transform (hb_ft_font_get_face (run->font), &matrix, NULL); + FT_Get_Transform (hb_ft_font_get_ft_face (run->font), &matrix, NULL); pos = hb_buffer_get_glyph_positions (run->buffer, &len); info = hb_buffer_get_glyph_infos (run->buffer, &len); From 35c92308ad84b28cb8956b9b19cf6f769a9250e6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 6 Aug 2025 11:41:26 +1000 Subject: [PATCH 147/309] Allow RGBA palettes to work with expand() --- Tests/test_imageops.py | 15 +++++++++++++++ src/PIL/ImageOps.py | 5 +++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 9f2fd5ba2..27ac6f308 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -186,6 +186,21 @@ def test_palette(mode: str) -> None: ) +def test_rgba_palette() -> None: + im = Image.new("P", (1, 1)) + + red = (255, 0, 0, 255) + translucent_black = (0, 0, 0, 127) + im.putpalette(red + translucent_black, "RGBA") + + expanded_im = ImageOps.expand(im, 1, 1) + + palette = expanded_im.palette + assert palette is not None + assert palette.mode == "RGBA" + assert expanded_im.convert("RGBA").getpixel((0, 0)) == translucent_black + + def test_pil163() -> None: # Division by zero in equalize if < 255 pixels in image (@PIL163) diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index da28854b5..42b10bd7b 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -499,14 +499,15 @@ def expand( height = top + image.size[1] + bottom color = _color(fill, image.mode) if image.palette: - palette = ImagePalette.ImagePalette(palette=image.getpalette()) + mode = image.palette.mode + palette = ImagePalette.ImagePalette(mode, image.getpalette(mode)) if isinstance(color, tuple) and (len(color) == 3 or len(color) == 4): color = palette.getcolor(color) else: palette = None out = Image.new(image.mode, (width, height), color) if palette: - out.putpalette(palette.palette) + out.putpalette(palette.palette, mode) out.paste(image, (left, top)) return out From d975e312e288630cd25973497afdf92ffdc6ba2e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 8 Aug 2025 05:46:10 +1000 Subject: [PATCH 148/309] Updated zlib-ng to 2.2.5 --- .github/workflows/wheels-dependencies.sh | 2 +- winbuild/build_prepare.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index fb86b6c7d..920dd1cc6 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -102,7 +102,7 @@ XZ_VERSION=5.8.1 TIFF_VERSION=4.7.0 LCMS2_VERSION=2.17 ZLIB_VERSION=1.3.1 -ZLIB_NG_VERSION=2.2.4 +ZLIB_NG_VERSION=2.2.5 LIBWEBP_VERSION=1.6.0 BZIP2_VERSION=1.0.8 LIBXCB_VERSION=1.17.0 diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 5633519dd..86485868c 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -126,7 +126,7 @@ V = { "OPENJPEG": "2.5.3", "TIFF": "4.7.0", "XZ": "5.8.1", - "ZLIBNG": "2.2.4", + "ZLIBNG": "2.2.5", } V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2]) From b8ffea2c56808661e460ecb4bca71b8c0a81265b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 8 Aug 2025 06:05:30 +1000 Subject: [PATCH 149/309] Revert "Revert to zlib on macOS < 10.15" This reverts commit 6c7917d7a6031ae22e1d9eaccc2e536123ea25c2. --- .github/workflows/wheels-dependencies.sh | 7 +------ checks/check_wheel.py | 3 --- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 920dd1cc6..c37ef7996 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -101,7 +101,6 @@ OPENJPEG_VERSION=2.5.3 XZ_VERSION=5.8.1 TIFF_VERSION=4.7.0 LCMS2_VERSION=2.17 -ZLIB_VERSION=1.3.1 ZLIB_NG_VERSION=2.2.5 LIBWEBP_VERSION=1.6.0 BZIP2_VERSION=1.0.8 @@ -259,11 +258,7 @@ function build { if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then yum remove -y zlib-devel fi - if [[ -n "$IS_MACOS" ]] && [[ "$MACOSX_DEPLOYMENT_TARGET" == "10.10" || "$MACOSX_DEPLOYMENT_TARGET" == "10.13" ]]; then - build_new_zlib - else - build_zlib_ng - fi + build_zlib_ng build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto if [[ -n "$IS_MACOS" ]]; then diff --git a/checks/check_wheel.py b/checks/check_wheel.py index 937722c4b..f716c8498 100644 --- a/checks/check_wheel.py +++ b/checks/check_wheel.py @@ -4,7 +4,6 @@ import platform import sys from PIL import features -from Tests.helper import is_pypy def test_wheel_modules() -> None: @@ -48,8 +47,6 @@ def test_wheel_features() -> None: if sys.platform == "win32": expected_features.remove("xcb") - elif sys.platform == "darwin" and not is_pypy() and platform.processor() != "arm": - expected_features.remove("zlib_ng") elif sys.platform == "ios": # Can't distribute raqm due to licensing, and there's no system version; # fribidi and harfbuzz won't be available if raqm isn't available. From b1cfa7769ba64c0546a25c06fc7b3289a8b041c0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 9 Aug 2025 07:13:41 +1000 Subject: [PATCH 150/309] Update actions/download-artifact action to v5 (#9141) --- .github/workflows/wheels.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 5cc4f0355..d217d9292 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -256,7 +256,7 @@ jobs: runs-on: ubuntu-latest name: Upload wheels to scientific-python-nightly-wheels steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: pattern: dist-* path: dist @@ -278,7 +278,7 @@ jobs: permissions: id-token: write steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: pattern: dist-* path: dist From ee8fbc0ac9510551f3dea5d24938af8be3b196de Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 9 Aug 2025 14:58:31 +1000 Subject: [PATCH 151/309] Make in parallel when building brotli and libavif --- .github/workflows/wheels-dependencies.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index fb86b6c7d..c79cd2f17 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -165,7 +165,7 @@ function build_brotli { local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz) (cd $out_dir \ && cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib $HOST_CMAKE_FLAGS . \ - && make install) + && make -j4 install) touch brotli-stamp } @@ -249,7 +249,7 @@ function build_libavif { cp $WORKDIR/meson-cross.txt $out_dir/crossfile-apple.meson fi - (cd $out_dir && make install) + (cd $out_dir && make -j4 install) touch libavif-stamp } From 5a90fb81cb75c9b33f5505659a9c5aa4f9d7881a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 9 Aug 2025 18:37:17 +1000 Subject: [PATCH 152/309] Added checks directory to mypy --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 8933945b1..d58fd67b6 100644 --- a/tox.ini +++ b/tox.ini @@ -30,4 +30,4 @@ skip_install = true deps = -r .ci/requirements-mypy.txt commands = - mypy conftest.py selftest.py setup.py docs src winbuild Tests {posargs} + mypy conftest.py selftest.py setup.py checks docs src winbuild Tests {posargs} From f69c221376aebb7a2db019ae46c53814234e9a1e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 9 Aug 2025 18:56:55 +1000 Subject: [PATCH 153/309] Do not import from Tests directory --- checks/check_imaging_leaks.py | 7 ++++--- checks/check_j2k_leaks.py | 11 ++++++----- checks/check_jpeg_leaks.py | 32 +++++++++++++++++--------------- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/checks/check_imaging_leaks.py b/checks/check_imaging_leaks.py index a1d59ed9c..e9f202f3d 100755 --- a/checks/check_imaging_leaks.py +++ b/checks/check_imaging_leaks.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from __future__ import annotations +import sys from collections.abc import Callable from typing import Any @@ -8,12 +9,12 @@ import pytest from PIL import Image -from .helper import is_win32 - min_iterations = 100 max_iterations = 10000 -pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS") +pytestmark = pytest.mark.skipif( + sys.platform.startswith("win32"), reason="requires Unix or macOS" +) def _get_mem_usage() -> float: diff --git a/checks/check_j2k_leaks.py b/checks/check_j2k_leaks.py index bbe35b591..7103d502e 100644 --- a/checks/check_j2k_leaks.py +++ b/checks/check_j2k_leaks.py @@ -1,12 +1,11 @@ from __future__ import annotations +import sys from io import BytesIO import pytest -from PIL import Image - -from .helper import is_win32, skip_unless_feature +from PIL import Image, features # Limits for testing the leak mem_limit = 1024 * 1048576 @@ -15,8 +14,10 @@ iterations = int((mem_limit / stack_size) * 2) test_file = "Tests/images/rgb_trns_ycbc.jp2" pytestmark = [ - pytest.mark.skipif(is_win32(), reason="requires Unix or macOS"), - skip_unless_feature("jpg_2000"), + pytest.mark.skipif( + sys.platform.startswith("win32"), reason="requires Unix or macOS" + ), + pytest.mark.skipif(not features.check("jpg_2000"), reason="jpg_2000 not available"), ] diff --git a/checks/check_jpeg_leaks.py b/checks/check_jpeg_leaks.py index 2f42ad734..2c27ce1d5 100644 --- a/checks/check_jpeg_leaks.py +++ b/checks/check_jpeg_leaks.py @@ -1,10 +1,11 @@ from __future__ import annotations +import sys from io import BytesIO import pytest -from .helper import hopper, is_win32 +from PIL import Image iterations = 5000 @@ -18,7 +19,9 @@ valgrind --tool=massif python test-installed.py -s -v checks/check_jpeg_leaks.py """ -pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS") +pytestmark = pytest.mark.skipif( + sys.platform.startswith("win32"), reason="requires Unix or macOS" +) """ pre patch: @@ -112,10 +115,10 @@ standard_chrominance_qtable = ( ), ) def test_qtables_leak(qtables: tuple[tuple[int, ...]] | list[tuple[int, ...]]) -> None: - im = hopper("RGB") - for _ in range(iterations): - test_output = BytesIO() - im.save(test_output, "JPEG", qtables=qtables) + with Image.open("Tests/images/hopper.ppm") as im: + for _ in range(iterations): + test_output = BytesIO() + im.save(test_output, "JPEG", qtables=qtables) def test_exif_leak() -> None: @@ -173,12 +176,12 @@ def test_exif_leak() -> None: 0 +----------------------------------------------------------------------->Gi 0 11.33 """ - im = hopper("RGB") exif = b"12345678" * 4096 - for _ in range(iterations): - test_output = BytesIO() - im.save(test_output, "JPEG", exif=exif) + with Image.open("Tests/images/hopper.ppm") as im: + for _ in range(iterations): + test_output = BytesIO() + im.save(test_output, "JPEG", exif=exif) def test_base_save() -> None: @@ -207,8 +210,7 @@ def test_base_save() -> None: | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: 0 +----------------------------------------------------------------------->Gi 0 7.882""" - im = hopper("RGB") - - for _ in range(iterations): - test_output = BytesIO() - im.save(test_output, "JPEG") + with Image.open("Tests/images/hopper.ppm") as im: + for _ in range(iterations): + test_output = BytesIO() + im.save(test_output, "JPEG") From 1a5acabd32fcb505350c84e35dc78319c3f63899 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 9 Aug 2025 19:53:05 +1000 Subject: [PATCH 154/309] Make in parallel when building libjpeg-turbo and openjpeg --- wheels/multibuild | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wheels/multibuild b/wheels/multibuild index 42d761728..647393271 160000 --- a/wheels/multibuild +++ b/wheels/multibuild @@ -1 +1 @@ -Subproject commit 42d761728d141d8462cd9943f4329f12fe62b155 +Subproject commit 64739327166fcad1fa41ad9b23fa910fa244c84f From 5e7f1312874dfd01a7b03af26b5f836bf0aa65ac Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 11 Aug 2025 10:40:32 +0300 Subject: [PATCH 155/309] Add Debian 13 Trixie (#9147) Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- .github/workflows/test-docker.yml | 2 ++ docs/installation/platform-support.rst | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 0b90732eb..47f2d3f0a 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -47,6 +47,8 @@ jobs: centos-stream-10-amd64, debian-12-bookworm-x86, debian-12-bookworm-amd64, + debian-13-trixie-x86, + debian-13-trixie-amd64, fedora-41-amd64, fedora-42-amd64, gentoo, diff --git a/docs/installation/platform-support.rst b/docs/installation/platform-support.rst index 5cf0276d1..d97895c86 100644 --- a/docs/installation/platform-support.rst +++ b/docs/installation/platform-support.rst @@ -31,6 +31,8 @@ These platforms are built and tested for every change. +----------------------------------+----------------------------+---------------------+ | Debian 12 Bookworm | 3.11 | x86, x86-64 | +----------------------------------+----------------------------+---------------------+ +| Debian 13 Trixie | 3.13 | x86, x86-64 | ++----------------------------------+----------------------------+---------------------+ | Fedora 41 | 3.13 | x86-64 | +----------------------------------+----------------------------+---------------------+ | Fedora 42 | 3.13 | x86-64 | From a72c6318771ca8e385a5dcd5a72a721df403dc21 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 12 Aug 2025 12:36:33 +1000 Subject: [PATCH 156/309] Updated URLs --- .github/zizmor.yml | 2 +- .pre-commit-config.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/zizmor.yml b/.github/zizmor.yml index 5bdc48c30..b56709781 100644 --- a/.github/zizmor.yml +++ b/.github/zizmor.yml @@ -1,5 +1,5 @@ # Configuration for the zizmor static analysis tool, run via pre-commit in CI -# https://woodruffw.github.io/zizmor/configuration/ +# https://docs.zizmor.sh/configuration/ rules: unpinned-uses: config: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cff17cd36..2be509d54 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -57,7 +57,7 @@ repos: - id: check-readthedocs - id: check-renovate - - repo: https://github.com/woodruffw/zizmor-pre-commit + - repo: https://github.com/zizmorcore/zizmor-pre-commit rev: v1.11.0 hooks: - id: zizmor From 6d974b61d6144fab9e65aff1b84bedd377737db8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 15 Aug 2025 14:37:31 +1000 Subject: [PATCH 157/309] Load image palette into Python after converting to PA --- Tests/test_image_convert.py | 8 ++++++++ src/PIL/Image.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 33f844437..7ba3fb555 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -107,6 +107,14 @@ def test_rgba_p() -> None: assert_image_similar(im, comparable, 20) +def test_pa() -> None: + im = hopper().convert("PA") + + palette = im.palette + assert palette is not None + assert palette.colors != {} + + def test_rgba() -> None: with Image.open("Tests/images/transparent.png") as im: assert im.mode == "RGBA" diff --git a/src/PIL/Image.py b/src/PIL/Image.py index b7c185e0d..b435b17ec 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1143,7 +1143,7 @@ class Image: raise ValueError(msg) from e new_im = self._new(im) - if mode == "P" and palette != Palette.ADAPTIVE: + if mode in ("P", "PA") and palette != Palette.ADAPTIVE: from . import ImagePalette new_im.palette = ImagePalette.ImagePalette("RGB", im.getpalette("RGB")) From 0ae2611b4438c847054d43d54ff21366eb1456bd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 15 Aug 2025 15:56:56 +1000 Subject: [PATCH 158/309] Copy C palette when merging --- Tests/test_image.py | 6 ++++++ src/_imaging.c | 7 ++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 83b027aa2..4b8ef02cd 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -1076,6 +1076,12 @@ class TestImage: assert im.palette is not None assert im.palette.colors[(27, 35, 6, 214)] == 24 + def test_merge_pa(self) -> None: + p = hopper("P") + a = Image.new("L", p.size) + pa = Image.merge("PA", (p, a)) + assert p.getpalette() == pa.getpalette() + def test_constants(self) -> None: for enum in ( Image.Transpose, diff --git a/src/_imaging.c b/src/_imaging.c index fbfc0e41a..6ab8e010d 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -2419,7 +2419,12 @@ _merge(PyObject *self, PyObject *args) { bands[3] = band3->image; } - return PyImagingNew(ImagingMerge(mode, bands)); + Imaging imOut = ImagingMerge(mode, bands); + if (!imOut) { + return NULL; + } + ImagingCopyPalette(imOut, bands[0]); + return PyImagingNew(imOut); } static PyObject * From ba66fec3d242fc1a8b287ba00baaed766b60786a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 15 Aug 2025 23:39:33 +1000 Subject: [PATCH 159/309] When converting RGBA to PA, use RGB to P quantization --- Tests/test_image_convert.py | 7 +++++++ src/PIL/Image.py | 10 ++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 33f844437..6c7026d47 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -107,6 +107,13 @@ def test_rgba_p() -> None: assert_image_similar(im, comparable, 20) +def test_rgba_pa() -> None: + im = hopper("RGBA").convert("PA").convert("RGB") + expected = hopper("RGB") + + assert_image_similar(im, expected, 9.3) + + def test_rgba() -> None: with Image.open("Tests/images/transparent.png") as im: assert im.mode == "RGBA" diff --git a/src/PIL/Image.py b/src/PIL/Image.py index b7c185e0d..55309adbc 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1010,8 +1010,14 @@ class Image: new_im.info["transparency"] = transparency return new_im - if mode == "P" and self.mode == "RGBA": - return self.quantize(colors) + if self.mode == "RGBA": + if mode == "P": + return self.quantize(colors) + elif mode == "PA": + r, g, b, a = self.split() + rgb = merge("RGB", (r, g, b)) + p = rgb.quantize(colors) + return merge("PA", (p, a)) trns = None delete_trns = False From 425a3a1af07c262f39e6f0d4fd5fa47e7f711859 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 16 Aug 2025 11:33:02 +1000 Subject: [PATCH 160/309] Updated macOS version in CI targets --- docs/installation/platform-support.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/platform-support.rst b/docs/installation/platform-support.rst index d97895c86..3c5e4cd51 100644 --- a/docs/installation/platform-support.rst +++ b/docs/installation/platform-support.rst @@ -41,7 +41,7 @@ These platforms are built and tested for every change. +----------------------------------+----------------------------+---------------------+ | macOS 13 Ventura | 3.10 | x86-64 | +----------------------------------+----------------------------+---------------------+ -| macOS 14 Sonoma | 3.11, 3.12, 3.13, 3.14 | arm64 | +| macOS 15 Sequoia | 3.11, 3.12, 3.13, 3.14 | arm64 | | | PyPy3 | | +----------------------------------+----------------------------+---------------------+ | Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 | From 62546924b5c890c4b7eebb163afc11fd8a71f0d7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 18 Aug 2025 08:07:12 +1000 Subject: [PATCH 161/309] Remove support for FreeType <= 2.9.0 --- src/PIL/ImageFont.py | 18 +++--------------- src/_imagingft.c | 6 ------ 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index bf3f471f5..a2bf9ccf9 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -671,11 +671,7 @@ class FreeTypeFont: :returns: A list of the named styles in a variation font. :exception OSError: If the font is not a variation font. """ - try: - names = self.font.getvarnames() - except AttributeError as e: - msg = "FreeType 2.9.1 or greater is required" - raise NotImplementedError(msg) from e + names = self.font.getvarnames() return [name.replace(b"\x00", b"") for name in names] def set_variation_by_name(self, name: str | bytes) -> None: @@ -702,11 +698,7 @@ class FreeTypeFont: :returns: A list of the axes in a variation font. :exception OSError: If the font is not a variation font. """ - try: - axes = self.font.getvaraxes() - except AttributeError as e: - msg = "FreeType 2.9.1 or greater is required" - raise NotImplementedError(msg) from e + axes = self.font.getvaraxes() for axis in axes: if axis["name"]: axis["name"] = axis["name"].replace(b"\x00", b"") @@ -717,11 +709,7 @@ class FreeTypeFont: :param axes: A list of values for each axis. :exception OSError: If the font is not a variation font. """ - try: - self.font.setvaraxes(axes) - except AttributeError as e: - msg = "FreeType 2.9.1 or greater is required" - raise NotImplementedError(msg) from e + self.font.setvaraxes(axes) class TransposedFont: diff --git a/src/_imagingft.c b/src/_imagingft.c index 29d8e9e71..c9938fd3e 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -1221,8 +1221,6 @@ glyph_error: return NULL; } -#if FREETYPE_MAJOR > 2 || (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) || \ - (FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1) static PyObject * font_getvarnames(FontObject *self) { int error; @@ -1432,7 +1430,6 @@ font_setvaraxes(FontObject *self, PyObject *args) { Py_RETURN_NONE; } -#endif static void font_dealloc(FontObject *self) { @@ -1451,13 +1448,10 @@ static PyMethodDef font_methods[] = { {"render", (PyCFunction)font_render, METH_VARARGS}, {"getsize", (PyCFunction)font_getsize, METH_VARARGS}, {"getlength", (PyCFunction)font_getlength, METH_VARARGS}, -#if FREETYPE_MAJOR > 2 || (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) || \ - (FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1) {"getvarnames", (PyCFunction)font_getvarnames, METH_NOARGS}, {"getvaraxes", (PyCFunction)font_getvaraxes, METH_NOARGS}, {"setvarname", (PyCFunction)font_setvarname, METH_VARARGS}, {"setvaraxes", (PyCFunction)font_setvaraxes, METH_VARARGS}, -#endif {NULL, NULL} }; From c214ad8c8d40c785c8aca6226b5033085f24cb3d Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Tue, 19 Aug 2025 06:43:07 +1000 Subject: [PATCH 162/309] Use macos-14 for iOS arm64 simulator (#9161) --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index d217d9292..d5aacb162 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -99,7 +99,7 @@ jobs: cibw_arch: arm64_iphoneos - name: "iOS arm64 simulator" platform: ios - os: macos-latest + os: macos-14 cibw_arch: arm64_iphonesimulator - name: "iOS x86_64 simulator" platform: ios From 1435339290f8112999dfa85a07718cdac2ce2cfc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 10:13:56 +1000 Subject: [PATCH 163/309] Update actions/checkout action to v5 (#9156) --- .github/workflows/docs.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/test-docker.yml | 2 +- .github/workflows/test-mingw.yml | 2 +- .github/workflows/test-valgrind-memory.yml | 2 +- .github/workflows/test-valgrind.yml | 2 +- .github/workflows/test-windows.yml | 6 +++--- .github/workflows/test.yml | 2 +- .github/workflows/wheels.yml | 8 ++++---- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 626824f38..761dc1125 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -32,7 +32,7 @@ jobs: name: Docs steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8e789a734..9827ef1cd 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,7 +20,7 @@ jobs: name: Lint steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 47f2d3f0a..30e5c494d 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -68,7 +68,7 @@ jobs: name: ${{ matrix.docker }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index 5a83c16c3..6c4206083 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -45,7 +45,7 @@ jobs: steps: - name: Checkout Pillow - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: persist-credentials: false diff --git a/.github/workflows/test-valgrind-memory.yml b/.github/workflows/test-valgrind-memory.yml index e6a5f6e77..0f36fe30d 100644 --- a/.github/workflows/test-valgrind-memory.yml +++ b/.github/workflows/test-valgrind-memory.yml @@ -41,7 +41,7 @@ jobs: name: ${{ matrix.docker }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false diff --git a/.github/workflows/test-valgrind.yml b/.github/workflows/test-valgrind.yml index 8818b3b23..30caa0d4e 100644 --- a/.github/workflows/test-valgrind.yml +++ b/.github/workflows/test-valgrind.yml @@ -39,7 +39,7 @@ jobs: name: ${{ matrix.docker }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index c80bb6eb6..d55a8e5f5 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -47,19 +47,19 @@ jobs: steps: - name: Checkout Pillow - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: persist-credentials: false - name: Checkout cached dependencies - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: persist-credentials: false repository: python-pillow/pillow-depends path: winbuild\depends - name: Checkout extra test images - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: persist-credentials: false repository: python-pillow/test-images diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0fad22a03..b17d08892 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -65,7 +65,7 @@ jobs: name: ${{ matrix.os }} Python ${{ matrix.python-version }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index d5aacb162..24e78f965 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -106,7 +106,7 @@ jobs: os: macos-13 cibw_arch: x86_64_iphonesimulator steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false submodules: true @@ -153,12 +153,12 @@ jobs: - cibw_arch: ARM64 os: windows-11-arm steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false - name: Checkout extra test images - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: persist-credentials: false repository: python-pillow/test-images @@ -234,7 +234,7 @@ jobs: if: github.event_name != 'schedule' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false From c826b932c07522272eb1297a595c8c90726970db Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 19 Aug 2025 15:45:42 +1000 Subject: [PATCH 164/309] Document MAXBLOCK --- docs/reference/ImageFile.rst | 1 + src/PIL/ImageFile.py | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/docs/reference/ImageFile.rst b/docs/reference/ImageFile.rst index 043559352..4c34ff812 100644 --- a/docs/reference/ImageFile.rst +++ b/docs/reference/ImageFile.rst @@ -74,5 +74,6 @@ Constants --------- .. autodata:: PIL.ImageFile.LOAD_TRUNCATED_IMAGES +.. autodata:: PIL.ImageFile.MAXBLOCK .. autodata:: PIL.ImageFile.ERRORS :annotation: diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 27b27127e..e33b846d4 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -46,6 +46,18 @@ if TYPE_CHECKING: logger = logging.getLogger(__name__) MAXBLOCK = 65536 +""" +By default, Pillow processes image data in blocks. This helps to prevent excessive use +of resources. Codecs may disable this behaviour with ``_pulls_fd`` or ``_pushes_fd``. + +When reading an image, this is the number of bytes to read at once. + +When writing an image, this is the number of bytes to write at once. +If the image width times 4 is greater, then that will be used instead. +Plugins may also set a greater number. + +User code may set this to another number. +""" SAFEBLOCK = 1024 * 1024 From 34c651deb8390ef752425a31e1473af4d6c9db3d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 20 Aug 2025 08:48:38 +1000 Subject: [PATCH 165/309] Update dependency cibuildwheel to v3.1.4 (#9164) --- .ci/requirements-cibw.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/requirements-cibw.txt b/.ci/requirements-cibw.txt index 9f9136557..d87d7956f 100644 --- a/.ci/requirements-cibw.txt +++ b/.ci/requirements-cibw.txt @@ -1 +1 @@ -cibuildwheel==3.1.3 +cibuildwheel==3.1.4 From 6a3bde05a46a8326fe02fb53fcab5a6f915d7193 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 20 Aug 2025 15:32:12 +1000 Subject: [PATCH 166/309] Do not set core to DeferredError --- src/PIL/Image.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index b7c185e0d..683c80762 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -103,7 +103,6 @@ try: raise ImportError(msg) except ImportError as v: - core = DeferredError.new(ImportError("The _imaging C module is not installed.")) # Explanations for ways that we know we might have an import error if str(v).startswith("Module use of python"): # The _imaging C module is present, but not compiled for From 009444f9c51d2d008fd0256769c93a7c2acb670a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 21 Aug 2025 21:56:03 +1000 Subject: [PATCH 167/309] Improved _accept length check --- src/PIL/FliImagePlugin.py | 2 +- src/PIL/GribStubImagePlugin.py | 2 +- src/PIL/PcxImagePlugin.py | 2 +- src/PIL/PpmImagePlugin.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PIL/FliImagePlugin.py b/src/PIL/FliImagePlugin.py index 7c5bfeefa..ccb8a5953 100644 --- a/src/PIL/FliImagePlugin.py +++ b/src/PIL/FliImagePlugin.py @@ -30,7 +30,7 @@ from ._util import DeferredError def _accept(prefix: bytes) -> bool: return ( - len(prefix) >= 6 + len(prefix) >= 16 and i16(prefix, 4) in [0xAF11, 0xAF12] and i16(prefix, 14) in [0, 3] # flags ) diff --git a/src/PIL/GribStubImagePlugin.py b/src/PIL/GribStubImagePlugin.py index 439fc5a3e..dfa798893 100644 --- a/src/PIL/GribStubImagePlugin.py +++ b/src/PIL/GribStubImagePlugin.py @@ -33,7 +33,7 @@ def register_handler(handler: ImageFile.StubHandler | None) -> None: def _accept(prefix: bytes) -> bool: - return prefix.startswith(b"GRIB") and prefix[7] == 1 + return len(prefix) >= 8 and prefix.startswith(b"GRIB") and prefix[7] == 1 class GribStubImageFile(ImageFile.StubImageFile): diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py index 458d586c4..6b16d5385 100644 --- a/src/PIL/PcxImagePlugin.py +++ b/src/PIL/PcxImagePlugin.py @@ -39,7 +39,7 @@ logger = logging.getLogger(__name__) def _accept(prefix: bytes) -> bool: - return prefix[0] == 10 and prefix[1] in [0, 2, 3, 5] + return len(prefix) >= 2 and prefix[0] == 10 and prefix[1] in [0, 2, 3, 5] ## diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index db34d107a..307bc97ff 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -47,7 +47,7 @@ MODES = { def _accept(prefix: bytes) -> bool: - return prefix.startswith(b"P") and prefix[1] in b"0123456fy" + return len(prefix) >= 2 and prefix.startswith(b"P") and prefix[1] in b"0123456fy" ## From 84122a20c70589ee4d68986507b9b54c91a19620 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 22 Aug 2025 18:29:25 +1000 Subject: [PATCH 168/309] Replaced print with assert --- Tests/test_numpy.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index ef54deeeb..f6acb3aff 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -28,15 +28,13 @@ def test_numpy_to_image() -> None: a = numpy.array(data, dtype=dtype) a.shape = TEST_IMAGE_SIZE i = Image.fromarray(a) - if list(i.getdata()) != data: - print("data mismatch for", dtype) + assert list(i.getdata()) == data else: data = list(range(100)) a = numpy.array([[x] * bands for x in data], dtype=dtype) a.shape = TEST_IMAGE_SIZE[0], TEST_IMAGE_SIZE[1], bands i = Image.fromarray(a) - if list(i.getchannel(0).getdata()) != list(range(100)): - print("data mismatch for", dtype) + assert list(i.getchannel(0).getdata()) == list(range(100)) return i # Check supported 1-bit integer formats From 54f4a346ef89e33eec0f889569a6d280eca70656 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 22 Aug 2025 19:06:19 +1000 Subject: [PATCH 169/309] Added has_feature_version --- Tests/helper.py | 8 ++++++++ Tests/test_file_webp_animated.py | 18 ++++++------------ Tests/test_image_quantize.py | 18 ++++++++++-------- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index e0dc8a9d4..dbdd30b42 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -175,6 +175,14 @@ def skip_unless_feature(feature: str) -> pytest.MarkDecorator: return pytest.mark.skipif(not features.check(feature), reason=reason) +def has_feature_version(feature: str, required: str) -> bool: + version = features.version(feature) + assert version is not None + version_required = parse_version(required) + version_available = parse_version(version) + return version_available >= version_required + + def skip_unless_feature_version( feature: str, required: str, reason: str | None = None ) -> pytest.MarkDecorator: diff --git a/Tests/test_file_webp_animated.py b/Tests/test_file_webp_animated.py index 503761374..600448fb9 100644 --- a/Tests/test_file_webp_animated.py +++ b/Tests/test_file_webp_animated.py @@ -4,13 +4,13 @@ from collections.abc import Generator from pathlib import Path import pytest -from packaging.version import parse as parse_version -from PIL import GifImagePlugin, Image, WebPImagePlugin, features +from PIL import GifImagePlugin, Image, WebPImagePlugin from .helper import ( assert_image_equal, assert_image_similar, + has_feature_version, is_big_endian, skip_unless_feature, ) @@ -53,11 +53,8 @@ def test_write_animation_L(tmp_path: Path) -> None: im.load() assert_image_similar(im, orig.convert("RGBA"), 32.9) - if is_big_endian(): - version = features.version_module("webp") - assert version is not None - if parse_version(version) < parse_version("1.2.2"): - pytest.skip("Fails with libwebp earlier than 1.2.2") + if is_big_endian() and not has_feature_version("webp", "1.2.2"): + pytest.skip("Fails with libwebp earlier than 1.2.2") orig.seek(orig.n_frames - 1) im.seek(im.n_frames - 1) orig.load() @@ -81,11 +78,8 @@ def test_write_animation_RGB(tmp_path: Path) -> None: assert_image_equal(im, frame1.convert("RGBA")) # Compare second frame to original - if is_big_endian(): - version = features.version_module("webp") - assert version is not None - if parse_version(version) < parse_version("1.2.2"): - pytest.skip("Fails with libwebp earlier than 1.2.2") + if is_big_endian() and not has_feature_version("webp", "1.2.2"): + pytest.skip("Fails with libwebp earlier than 1.2.2") im.seek(1) im.load() assert_image_equal(im, frame2.convert("RGBA")) diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index 6d313cb8c..d847c7440 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -1,11 +1,16 @@ from __future__ import annotations import pytest -from packaging.version import parse as parse_version -from PIL import Image, features +from PIL import Image -from .helper import assert_image_similar, hopper, is_ppc64le, skip_unless_feature +from .helper import ( + assert_image_similar, + has_feature_version, + hopper, + is_ppc64le, + skip_unless_feature, +) def test_sanity() -> None: @@ -23,11 +28,8 @@ def test_sanity() -> None: @skip_unless_feature("libimagequant") def test_libimagequant_quantize() -> None: image = hopper() - if is_ppc64le(): - version = features.version_feature("libimagequant") - assert version is not None - if parse_version(version) < parse_version("4"): - pytest.skip("Fails with libimagequant earlier than 4.0.0 on ppc64le") + if is_ppc64le() and not has_feature_version("libimagequant", "4"): + pytest.skip("Fails with libimagequant earlier than 4.0.0 on ppc64le") converted = image.quantize(100, Image.Quantize.LIBIMAGEQUANT) assert converted.mode == "P" assert_image_similar(converted.convert("RGB"), image, 15) From f80ac8d6b8915a7150d6179798e284f6002b8bd9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 22 Aug 2025 19:16:38 +1000 Subject: [PATCH 170/309] Check version independently --- Tests/test_imagefontctl.py | 81 ++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 48 deletions(-) diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index 5954de874..95af3fda8 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -7,6 +7,7 @@ from PIL import Image, ImageDraw, ImageFont from .helper import ( assert_image_equal_tofile, assert_image_similar_tofile, + has_feature_version, skip_unless_feature, ) @@ -104,11 +105,9 @@ def test_text_direction_ttb() -> None: im = Image.new(mode="RGB", size=(100, 300)) draw = ImageDraw.Draw(im) - try: - draw.text((0, 0), "English あい", font=ttf, fill=500, direction="ttb") - except ValueError as ex: - if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction": - pytest.skip("libraqm 0.7 or greater not available") + if not has_feature_version("raqm", "0.7"): + pytest.skip("libraqm 0.7 or greater not available") + draw.text((0, 0), "English あい", font=ttf, fill=500, direction="ttb") target = "Tests/images/test_direction_ttb.png" assert_image_similar_tofile(im, target, 2.8) @@ -119,19 +118,17 @@ def test_text_direction_ttb_stroke() -> None: im = Image.new(mode="RGB", size=(100, 300)) draw = ImageDraw.Draw(im) - try: - draw.text( - (27, 27), - "あい", - font=ttf, - fill=500, - direction="ttb", - stroke_width=2, - stroke_fill="#0f0", - ) - except ValueError as ex: - if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction": - pytest.skip("libraqm 0.7 or greater not available") + if not has_feature_version("raqm", "0.7"): + pytest.skip("libraqm 0.7 or greater not available") + draw.text( + (27, 27), + "あい", + font=ttf, + fill=500, + direction="ttb", + stroke_width=2, + stroke_fill="#0f0", + ) target = "Tests/images/test_direction_ttb_stroke.png" assert_image_similar_tofile(im, target, 19.4) @@ -219,14 +216,9 @@ def test_getlength( im = Image.new(mode, (1, 1), 0) d = ImageDraw.Draw(im) - try: - assert d.textlength(text, ttf, direction) == expected - except ValueError as ex: - if ( - direction == "ttb" - and str(ex) == "libraqm 0.7 or greater required for 'ttb' direction" - ): - pytest.skip("libraqm 0.7 or greater not available") + if direction == "ttb" and not has_feature_version("raqm", "0.7"): + pytest.skip("libraqm 0.7 or greater not available") + assert d.textlength(text, ttf, direction) == expected @pytest.mark.parametrize("mode", ("L", "1")) @@ -242,17 +234,12 @@ def test_getlength_combine(mode: str, direction: str, text: str) -> None: ttf = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48) - try: - target = ttf.getlength("ii", mode, direction) - actual = ttf.getlength(text, mode, direction) + if direction == "ttb" and not has_feature_version("raqm", "0.7"): + pytest.skip("libraqm 0.7 or greater not available") + target = ttf.getlength("ii", mode, direction) + actual = ttf.getlength(text, mode, direction) - assert actual == target - except ValueError as ex: - if ( - direction == "ttb" - and str(ex) == "libraqm 0.7 or greater required for 'ttb' direction" - ): - pytest.skip("libraqm 0.7 or greater not available") + assert actual == target @pytest.mark.parametrize("anchor", ("lt", "mm", "rb", "sm")) @@ -265,11 +252,9 @@ def test_anchor_ttb(anchor: str) -> None: d = ImageDraw.Draw(im) d.line(((0, 200), (200, 200)), "gray") d.line(((100, 0), (100, 400)), "gray") - try: - d.text((100, 200), text, fill="black", anchor=anchor, direction="ttb", font=f) - except ValueError as ex: - if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction": - pytest.skip("libraqm 0.7 or greater not available") + if not has_feature_version("raqm", "0.7"): + pytest.skip("libraqm 0.7 or greater not available") + d.text((100, 200), text, fill="black", anchor=anchor, direction="ttb", font=f) assert_image_similar_tofile(im, path, 1) # fails at 5 @@ -310,10 +295,12 @@ combine_tests = ( # this tests various combining characters for anchor alignment and clipping @pytest.mark.parametrize( - "name, text, anchor, dir, epsilon", combine_tests, ids=[r[0] for r in combine_tests] + "name, text, anchor, direction, epsilon", + combine_tests, + ids=[r[0] for r in combine_tests], ) def test_combine( - name: str, text: str, dir: str | None, anchor: str | None, epsilon: float + name: str, text: str, direction: str | None, anchor: str | None, epsilon: float ) -> None: path = f"Tests/images/test_combine_{name}.png" f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48) @@ -322,11 +309,9 @@ def test_combine( d = ImageDraw.Draw(im) d.line(((0, 200), (400, 200)), "gray") d.line(((200, 0), (200, 400)), "gray") - try: - d.text((200, 200), text, fill="black", anchor=anchor, direction=dir, font=f) - except ValueError as ex: - if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction": - pytest.skip("libraqm 0.7 or greater not available") + if direction == "ttb" and not has_feature_version("raqm", "0.7"): + pytest.skip("libraqm 0.7 or greater not available") + d.text((200, 200), text, fill="black", anchor=anchor, direction=direction, font=f) assert_image_similar_tofile(im, path, epsilon) From 0d72707d4f1da4f72ee6b5ece10d13080f877796 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 26 Aug 2025 08:55:11 +1000 Subject: [PATCH 171/309] Removed version from PDF comment --- src/PIL/PdfImagePlugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py index e9c20ddc1..5594c7e0f 100644 --- a/src/PIL/PdfImagePlugin.py +++ b/src/PIL/PdfImagePlugin.py @@ -27,7 +27,7 @@ import os import time from typing import IO, Any -from . import Image, ImageFile, ImageSequence, PdfParser, __version__, features +from . import Image, ImageFile, ImageSequence, PdfParser, features # # -------------------------------------------------------------------- @@ -221,7 +221,7 @@ def _save( existing_pdf.start_writing() existing_pdf.write_header() - existing_pdf.write_comment(f"created by Pillow {__version__} PDF driver") + existing_pdf.write_comment("created by Pillow PDF driver") # # pages From 59d6f313d6e70f78e41a6e8c3c8848553a0e37c7 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Tue, 26 Aug 2025 21:07:32 +1000 Subject: [PATCH 172/309] Removed setuptools version requirement --- .ci/requirements-mypy.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/requirements-mypy.txt b/.ci/requirements-mypy.txt index 3519707f1..99eac6027 100644 --- a/.ci/requirements-mypy.txt +++ b/.ci/requirements-mypy.txt @@ -11,4 +11,4 @@ sphinx types-atheris types-defusedxml types-olefile -types-setuptools>=75.2.0 +types-setuptools From ed164d1bfab8a59d411aadab7d56d1ec116f572a Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Tue, 26 Aug 2025 22:13:45 +1000 Subject: [PATCH 173/309] pre-commit fixes --- setup.py | 2 +- src/_imaging.c | 3 ++- src/libImaging/Filter.c | 6 ++++-- src/libImaging/Pack.c | 6 ++++-- src/libImaging/Resample.c | 6 ++++-- src/libImaging/Unpack.c | 14 +++++++++++--- 6 files changed, 26 insertions(+), 11 deletions(-) diff --git a/setup.py b/setup.py index dcc07eaf6..b9f5cfe06 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ import subprocess import sys import warnings from collections.abc import Iterator -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from pybind11.setup_helpers import ParallelCompile from setuptools import Extension, setup diff --git a/src/_imaging.c b/src/_imaging.c index 7823745f0..8412124c1 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -1677,7 +1677,8 @@ _putdata(ImagingObject *self, PyObject *args) { int bigendian = 0; if (image->type == IMAGING_TYPE_SPECIAL) { // I;16* - if (image->mode == IMAGING_MODE_I_16B + if ( + image->mode == IMAGING_MODE_I_16B #ifdef WORDS_BIGENDIAN || image->mode == IMAGING_MODE_I_16N #endif diff --git a/src/libImaging/Filter.c b/src/libImaging/Filter.c index 48f210809..cefb8fcdc 100644 --- a/src/libImaging/Filter.c +++ b/src/libImaging/Filter.c @@ -155,7 +155,8 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) { } else { int bigendian = 0; if (im->type == IMAGING_TYPE_SPECIAL) { - if (im->mode == IMAGING_MODE_I_16B + if ( + im->mode == IMAGING_MODE_I_16B #ifdef WORDS_BIGENDIAN || im->mode == IMAGING_MODE_I_16N #endif @@ -308,7 +309,8 @@ ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) { } else { int bigendian = 0; if (im->type == IMAGING_TYPE_SPECIAL) { - if (im->mode == IMAGING_MODE_I_16B + if ( + im->mode == IMAGING_MODE_I_16B #ifdef WORDS_BIGENDIAN || im->mode == IMAGING_MODE_I_16N #endif diff --git a/src/libImaging/Pack.c b/src/libImaging/Pack.c index 0a97c4872..4afeb15b7 100644 --- a/src/libImaging/Pack.c +++ b/src/libImaging/Pack.c @@ -648,8 +648,10 @@ static struct { {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16B, 16, copy2}, {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16L, 16, copy2}, {IMAGING_MODE_I_16N, IMAGING_RAWMODE_I_16N, 16, copy2}, - {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16N, 16, packI16N_I16 - }, // LibTiff native->image endian. + {IMAGING_MODE_I_16, + IMAGING_RAWMODE_I_16N, + 16, + packI16N_I16}, // LibTiff native->image endian. {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16N, 16, packI16N_I16}, {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16N, 16, packI16N_I16B} }; diff --git a/src/libImaging/Resample.c b/src/libImaging/Resample.c index 3ab43a895..cbd18d0c1 100644 --- a/src/libImaging/Resample.c +++ b/src/libImaging/Resample.c @@ -470,7 +470,8 @@ ImagingResampleHorizontal_16bpc( double *k; int bigendian = 0; - if (imIn->mode == IMAGING_MODE_I_16N + if ( + imIn->mode == IMAGING_MODE_I_16N #ifdef WORDS_BIGENDIAN || imIn->mode == IMAGING_MODE_I_16B #endif @@ -509,7 +510,8 @@ ImagingResampleVertical_16bpc( double *k; int bigendian = 0; - if (imIn->mode == IMAGING_MODE_I_16N + if ( + imIn->mode == IMAGING_MODE_I_16N #ifdef WORDS_BIGENDIAN || imIn->mode == IMAGING_MODE_I_16B #endif diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index 075ec5b95..27ac7c467 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -1833,13 +1833,21 @@ static struct { {IMAGING_MODE_I_16N, IMAGING_RAWMODE_I_16N, 16, copy2}, {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16B, 16, unpackI16B_I16}, - {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16N, 16, unpackI16N_I16}, // LibTiff native->image endian. - {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16N, 16, unpackI16N_I16}, // LibTiff native->image endian. + {IMAGING_MODE_I_16, + IMAGING_RAWMODE_I_16N, + 16, + unpackI16N_I16}, // LibTiff native->image endian. + { + IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16N, 16, unpackI16N_I16 + }, // LibTiff native->image endian. {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16N, 16, unpackI16N_I16B}, {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16R, 16, unpackI16R_I16}, - {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_12, 12, unpackI12_I16} // 12 bit Tiffs stored in 16bits. + {IMAGING_MODE_I_16, + IMAGING_RAWMODE_I_12, + 12, + unpackI12_I16} // 12 bit Tiffs stored in 16bits. }; ImagingShuffler From 178b3a70ccafd2fb81438cad2ebe8bb2a16ef67d Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Wed, 27 Aug 2025 06:58:51 +1000 Subject: [PATCH 174/309] Updated formatting --- src/libImaging/Pack.c | 6 ++---- src/libImaging/Unpack.c | 19 ++++++------------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/libImaging/Pack.c b/src/libImaging/Pack.c index 4afeb15b7..fdf5a72aa 100644 --- a/src/libImaging/Pack.c +++ b/src/libImaging/Pack.c @@ -648,10 +648,8 @@ static struct { {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16B, 16, copy2}, {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16L, 16, copy2}, {IMAGING_MODE_I_16N, IMAGING_RAWMODE_I_16N, 16, copy2}, - {IMAGING_MODE_I_16, - IMAGING_RAWMODE_I_16N, - 16, - packI16N_I16}, // LibTiff native->image endian. + // LibTiff native->image endian. + {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16N, 16, packI16N_I16}, {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16N, 16, packI16N_I16}, {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16N, 16, packI16N_I16B} }; diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index 27ac7c467..ab5f2c158 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -1833,21 +1833,14 @@ static struct { {IMAGING_MODE_I_16N, IMAGING_RAWMODE_I_16N, 16, copy2}, {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16B, 16, unpackI16B_I16}, - {IMAGING_MODE_I_16, - IMAGING_RAWMODE_I_16N, - 16, - unpackI16N_I16}, // LibTiff native->image endian. - { - IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16N, 16, unpackI16N_I16 - }, // LibTiff native->image endian. - {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16N, 16, unpackI16N_I16B}, - {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16R, 16, unpackI16R_I16}, - {IMAGING_MODE_I_16, - IMAGING_RAWMODE_I_12, - 12, - unpackI12_I16} // 12 bit Tiffs stored in 16bits. + // LibTiff native->image endian. + {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16N, 16, unpackI16N_I16}, + {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16N, 16, unpackI16N_I16}, + + // 12 bit Tiffs stored in 16bits. + {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_12, 12, unpackI12_I16} }; ImagingShuffler From 84e89bf5c3c798ac55e726e3624c4bca5bacc90f Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Wed, 27 Aug 2025 07:07:13 +1000 Subject: [PATCH 175/309] Restored unpacker --- src/libImaging/Unpack.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index ab5f2c158..203bcac2c 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -1833,6 +1833,7 @@ static struct { {IMAGING_MODE_I_16N, IMAGING_RAWMODE_I_16N, 16, copy2}, {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16B, 16, unpackI16B_I16}, + {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16N, 16, unpackI16N_I16B}, {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16R, 16, unpackI16R_I16}, // LibTiff native->image endian. From a59ce257e9ccc966d0a4a2b57a1ef2f05134f9b7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 28 Aug 2025 19:37:26 +1000 Subject: [PATCH 176/309] Install zstd for libtiff on Linux --- .github/workflows/wheels-dependencies.sh | 10 ++++++++ wheels/dependency_licenses/ZSTD.txt | 30 ++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 wheels/dependency_licenses/ZSTD.txt diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index c79cd2f17..72934a9b9 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -99,6 +99,7 @@ LIBPNG_VERSION=1.6.50 JPEGTURBO_VERSION=3.1.1 OPENJPEG_VERSION=2.5.3 XZ_VERSION=5.8.1 +ZSTD_VERSION=1.5.7 TIFF_VERSION=4.7.0 LCMS2_VERSION=2.17 ZLIB_VERSION=1.3.1 @@ -254,6 +255,14 @@ function build_libavif { touch libavif-stamp } +function build_zstd { + if [ -e zstd-stamp ]; then return; fi + local out_dir=$(fetch_unpack https://github.com/facebook/zstd/releases/download/v$ZSTD_VERSION/zstd-$ZSTD_VERSION.tar.gz) + (cd $out_dir \ + && make -j4 install) + touch zstd-stamp +} + function build { build_xz if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then @@ -285,6 +294,7 @@ function build { --with-jpeg-include-dir=$BUILD_PREFIX/include --with-jpeg-lib-dir=$BUILD_PREFIX/lib \ --disable-webp --disable-libdeflate --disable-zstd else + build_zstd build_tiff fi diff --git a/wheels/dependency_licenses/ZSTD.txt b/wheels/dependency_licenses/ZSTD.txt new file mode 100644 index 000000000..75800288c --- /dev/null +++ b/wheels/dependency_licenses/ZSTD.txt @@ -0,0 +1,30 @@ +BSD License + +For Zstandard software + +Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook, nor Meta, nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 05a601031142ecf0ca21c521a6c312c66c4e48b6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 29 Aug 2025 07:35:18 +1000 Subject: [PATCH 177/309] Fixed loading rotated PCD images --- Tests/test_file_pcd.py | 7 +++++++ src/PIL/PcdImagePlugin.py | 9 +++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_pcd.py b/Tests/test_file_pcd.py index 9bf1a75f0..a2d07ff51 100644 --- a/Tests/test_file_pcd.py +++ b/Tests/test_file_pcd.py @@ -6,6 +6,8 @@ import pytest from PIL import Image +from .helper import assert_image_equal + def test_load_raw() -> None: with Image.open("Tests/images/hopper.pcd") as im: @@ -30,3 +32,8 @@ def test_rotated(orientation: int) -> None: f = BytesIO(data) with Image.open(f) as im: assert im.size == (512, 768) + + with Image.open("Tests/images/hopper.pcd") as expected: + assert_image_equal( + im, expected.rotate(90 if orientation == 1 else -90, expand=True) + ) diff --git a/src/PIL/PcdImagePlugin.py b/src/PIL/PcdImagePlugin.py index 7f9ab525c..00864a4bf 100644 --- a/src/PIL/PcdImagePlugin.py +++ b/src/PIL/PcdImagePlugin.py @@ -47,12 +47,17 @@ class PcdImageFile(ImageFile.ImageFile): self._mode = "RGB" self._size = (512, 768) if orientation in (1, 3) else (768, 512) - self.tile = [ImageFile._Tile("pcd", (0, 0) + self.size, 96 * 2048)] + self.tile = [ImageFile._Tile("pcd", (0, 0, 768, 512), 96 * 2048)] + + def load_prepare(self) -> None: + if self._im is None and self.tile_post_rotate: + self.im = Image.core.new(self.mode, (768, 512)) + ImageFile.ImageFile.load_prepare(self) def load_end(self) -> None: if self.tile_post_rotate: # Handle rotated PCDs - self.im = self.im.rotate(self.tile_post_rotate) + self.im = self.rotate(self.tile_post_rotate, expand=True).im # From c6915f717f3b9bb694421f4d711808ba2c464d40 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 29 Aug 2025 07:43:51 +1000 Subject: [PATCH 178/309] rotate() will use "angle % 360" --- Tests/test_file_pcd.py | 2 +- src/PIL/PcdImagePlugin.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_pcd.py b/Tests/test_file_pcd.py index a2d07ff51..15dd7f116 100644 --- a/Tests/test_file_pcd.py +++ b/Tests/test_file_pcd.py @@ -35,5 +35,5 @@ def test_rotated(orientation: int) -> None: with Image.open("Tests/images/hopper.pcd") as expected: assert_image_equal( - im, expected.rotate(90 if orientation == 1 else -90, expand=True) + im, expected.rotate(90 if orientation == 1 else 270, expand=True) ) diff --git a/src/PIL/PcdImagePlugin.py b/src/PIL/PcdImagePlugin.py index 00864a4bf..296f3775b 100644 --- a/src/PIL/PcdImagePlugin.py +++ b/src/PIL/PcdImagePlugin.py @@ -43,7 +43,7 @@ class PcdImageFile(ImageFile.ImageFile): if orientation == 1: self.tile_post_rotate = 90 elif orientation == 3: - self.tile_post_rotate = -90 + self.tile_post_rotate = 270 self._mode = "RGB" self._size = (512, 768) if orientation in (1, 3) else (768, 512) From c7a268e5a5d026d17374309a0fde23cbcc0f8bf0 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Mon, 1 Sep 2025 08:23:30 +1000 Subject: [PATCH 179/309] ImageMorph operations must have length 1 (#9102) --- Tests/test_imagemorph.py | 14 ++++++++------ docs/releasenotes/12.0.0.rst | 7 +++++++ src/PIL/ImageMorph.py | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 515e29cea..ca192a809 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -7,7 +7,7 @@ import pytest from PIL import Image, ImageMorph, _imagingmorph -from .helper import assert_image_equal_tofile, hopper +from .helper import assert_image_equal_tofile, hopper, timeout_unless_slower_valgrind def string_to_img(image_string: str) -> Image.Image: @@ -266,16 +266,18 @@ def test_unknown_pattern() -> None: ImageMorph.LutBuilder(op_name="unknown") -def test_pattern_syntax_error() -> None: +@pytest.mark.parametrize( + "pattern", ("a pattern with a syntax error", "4:(" + "X" * 30000) +) +@timeout_unless_slower_valgrind(1) +def test_pattern_syntax_error(pattern: str) -> None: # Arrange lb = ImageMorph.LutBuilder(op_name="corner") - new_patterns = ["a pattern with a syntax error"] + new_patterns = [pattern] lb.add_patterns(new_patterns) # Act / Assert - with pytest.raises( - Exception, match='Syntax error in pattern "a pattern with a syntax error"' - ): + with pytest.raises(Exception, match='Syntax error in pattern "'): lb.build_lut() diff --git a/docs/releasenotes/12.0.0.rst b/docs/releasenotes/12.0.0.rst index e21c243ea..41edea318 100644 --- a/docs/releasenotes/12.0.0.rst +++ b/docs/releasenotes/12.0.0.rst @@ -150,3 +150,10 @@ others prepare for 3.14, and to ensure Pillow could be used immediately at the r of 3.14.0 final (2025-10-07, :pep:`745`). Pillow 12.0.0 now officially supports Python 3.14. + +ImageMorph operations must have length 1 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Valid ImageMorph operations are 4, N, 1 and M. By limiting the length to 1 character +within Pillow, long execution times can be avoided if a user provided long pattern +strings. Reported by Jang Choi. diff --git a/src/PIL/ImageMorph.py b/src/PIL/ImageMorph.py index f0a066b5b..bd70aff7b 100644 --- a/src/PIL/ImageMorph.py +++ b/src/PIL/ImageMorph.py @@ -150,7 +150,7 @@ class LutBuilder: # Parse and create symmetries of the patterns strings for p in self.patterns: - m = re.search(r"(\w*):?\s*\((.+?)\)\s*->\s*(\d)", p.replace("\n", "")) + m = re.search(r"(\w):?\s*\((.+?)\)\s*->\s*(\d)", p.replace("\n", "")) if not m: msg = 'Syntax error in pattern "' + p + '"' raise Exception(msg) From 31eee6e5f706cd0a41ac26c45694adef1eca72a3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 07:57:54 +1000 Subject: [PATCH 180/309] [pre-commit.ci] pre-commit autoupdate (#9180) --- .pre-commit-config.yaml | 10 +++++----- src/libImaging/Palette.c | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2be509d54..23bda1ec7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.7 + rev: v0.12.11 hooks: - id: ruff-check args: [--exit-non-zero-on-fix] @@ -24,7 +24,7 @@ repos: exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$|\.patch$) - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v20.1.8 + rev: v21.1.0 hooks: - id: clang-format types: [c] @@ -36,7 +36,7 @@ repos: - id: rst-backticks - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: check-executables-have-shebangs - id: check-shebang-scripts-are-executable @@ -51,14 +51,14 @@ repos: exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/|\.patch$ - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.33.2 + rev: 0.33.3 hooks: - id: check-github-workflows - id: check-readthedocs - id: check-renovate - repo: https://github.com/zizmorcore/zizmor-pre-commit - rev: v1.11.0 + rev: v1.12.1 hooks: - id: zizmor diff --git a/src/libImaging/Palette.c b/src/libImaging/Palette.c index 78916bca5..da1d80504 100644 --- a/src/libImaging/Palette.c +++ b/src/libImaging/Palette.c @@ -148,7 +148,7 @@ ImagingPaletteDelete(ImagingPalette palette) { #define BOX 8 -#define BOXVOLUME BOX *BOX *BOX +#define BOXVOLUME BOX * BOX * BOX void ImagingPaletteCacheUpdate(ImagingPalette palette, int r, int g, int b) { From 57a5f76e6d78f280fa7cd666bff32fe3452f140b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 2 Sep 2025 21:09:07 +1000 Subject: [PATCH 181/309] Removed unused split --- src/PIL/ImageFont.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index a2bf9ccf9..446160c2f 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -129,7 +129,7 @@ class ImageFont: if file.readline() != b"PILfont\n": msg = "Not a PILfont file" raise SyntaxError(msg) - file.readline().split(b";") + file.readline() self.info = [] # FIXME: should be a dictionary while True: s = file.readline() From 485d9884cf7a3cd2ceedc91df9c8625454b6d8f5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 2 Sep 2025 21:24:57 +1000 Subject: [PATCH 182/309] Limit length of read operation --- Tests/test_imagefont.py | 5 +++++ src/PIL/ImageFont.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 4565d35ba..08034ad0d 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -492,6 +492,11 @@ def test_stroke_mask() -> None: assert mask.getpixel((42, 5)) == 255 +def test_load_invalid_file() -> None: + with pytest.raises(SyntaxError, match="Not a PILfont file"): + ImageFont.load("Tests/images/1_trns.png") + + def test_load_when_image_not_found() -> None: with tempfile.NamedTemporaryFile(delete=False) as tmp: pass diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 446160c2f..df2f00882 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -126,7 +126,7 @@ class ImageFont: def _load_pilfont_data(self, file: IO[bytes], image: Image.Image) -> None: # read PILfont header - if file.readline() != b"PILfont\n": + if file.read(8) != b"PILfont\n": msg = "Not a PILfont file" raise SyntaxError(msg) file.readline() From caacd38e1be189ed5a9d9ba892e595cbdfaa551b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 2 Sep 2025 21:32:13 +1000 Subject: [PATCH 183/309] Raise mode error before reading --- Tests/test_imagefontpil.py | 8 ++++++++ src/PIL/ImageFont.py | 10 +++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Tests/test_imagefontpil.py b/Tests/test_imagefontpil.py index 3eb98d379..8c1cb3f58 100644 --- a/Tests/test_imagefontpil.py +++ b/Tests/test_imagefontpil.py @@ -30,6 +30,14 @@ def test_default_font(font: ImageFont.ImageFont) -> None: assert_image_equal_tofile(im, "Tests/images/default_font.png") +def test_invalid_mode() -> None: + font = ImageFont.ImageFont() + fp = BytesIO() + with Image.open("Tests/images/hopper.png") as im: + with pytest.raises(TypeError, match="invalid font image mode"): + font._load_pilfont_data(fp, im) + + def test_without_freetype() -> None: original_core = ImageFont.core if features.check_module("freetype2"): diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index df2f00882..92eb763a5 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -125,6 +125,11 @@ class ImageFont: image.close() def _load_pilfont_data(self, file: IO[bytes], image: Image.Image) -> None: + # check image + if image.mode not in ("1", "L"): + msg = "invalid font image mode" + raise TypeError(msg) + # read PILfont header if file.read(8) != b"PILfont\n": msg = "Not a PILfont file" @@ -140,11 +145,6 @@ class ImageFont: # read PILfont metrics data = file.read(256 * 20) - # check image - if image.mode not in ("1", "L"): - msg = "invalid font image mode" - raise TypeError(msg) - image.load() self.font = Image.core.font(image.im, data) From 0e22b0ca6c9577fcd5be0013ce6d10e0ee28999a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 3 Sep 2025 18:33:52 +1000 Subject: [PATCH 184/309] Removed unused code --- Tests/test_font_crash.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Tests/test_font_crash.py b/Tests/test_font_crash.py index b82340ef7..fb5026ee0 100644 --- a/Tests/test_font_crash.py +++ b/Tests/test_font_crash.py @@ -2,7 +2,7 @@ from __future__ import annotations import pytest -from PIL import Image, ImageDraw, ImageFont +from PIL import ImageFont from .helper import skip_unless_feature @@ -12,10 +12,6 @@ class TestFontCrash: # from fuzzers.fuzz_font font.getbbox("ABC") font.getmask("test text") - with Image.new(mode="RGBA", size=(200, 200)) as im: - draw = ImageDraw.Draw(im) - draw.multiline_textbbox((10, 10), "ABC\nAaaa", font, stroke_width=2) - draw.text((10, 10), "Test Text", font=font, fill="#000") @skip_unless_feature("freetype2") def test_segfault(self) -> None: From 72c067af2969517fde1979a4749c5076be96894a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 3 Sep 2025 19:23:26 +1000 Subject: [PATCH 185/309] Check all reserved bytes in header --- Tests/images/crash-5762152299364352.fli | Bin 8731 -> 8731 bytes ...39147ce93e20eb14088fe238e541443ffd64b3.fli | Bin 200 -> 200 bytes ...f0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli | Bin 159 -> 159 bytes src/PIL/FliImagePlugin.py | 7 ++++++- 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Tests/images/crash-5762152299364352.fli b/Tests/images/crash-5762152299364352.fli index 944fe0b56c73b016c7599beb5b8e47cd33f0432f..d7588eea88f4a37d000e6c12949a13e219298092 100644 GIT binary patch delta 21 dcmbR3GTUW>)?^7rwTS^jlNA`{Ha5&w1OQM22K@j4 delta 28 kcmbR3GTUW>)@CbaM#jksjBb CO)#eb delta 86 zcmWm2u?;{#07l{8MIq5BbeD+Q1_~Rf%wPsBBT(B1!)Qe$?w<3SmwZQbM03?bgYhQ` nYbFW3g#Foq(fNm1&IqpHm^-(gUO4dtaIkM>y-n1w(q-sA4znvm diff --git a/Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli b/Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli index 77a94b87a3ade935e707f3d89c9fbff801a1e976..abe642e6a9d665941b34501c6c0879370cac87b0 100644 GIT binary patch literal 159 zcmccrk72RkdM*Y=0S1Bp3^3sGi1Yo=yXXHu{?G9L4a5Hi1}=tgFgXJB|Nmcs<{*qB Zq#UU9*GH!Ykh1?k0HS$81PJ_}4FLDPAGiPj delta 86 zcmbQwIG=HXme2qH9RHdAy#bQ51sE6@{xkgf52QdqTJHb None: # HEAD s = self.fp.read(128) - if not (_accept(s) and s[20:22] == b"\x00\x00"): + if not ( + _accept(s) + and s[20:22] == b"\x00" * 2 + and s[42:80] == b"\x00" * 38 + and s[88:] == b"\x00" * 40 + ): msg = "not an FLI/FLC file" raise SyntaxError(msg) From e73b5ff4cd0c2ba4587c49bf310bb1421b47e725 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 3 Sep 2025 19:35:01 +1000 Subject: [PATCH 186/309] Do not unnecessarily update __offset --- src/PIL/FliImagePlugin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PIL/FliImagePlugin.py b/src/PIL/FliImagePlugin.py index ccb8a5953..679a9edd9 100644 --- a/src/PIL/FliImagePlugin.py +++ b/src/PIL/FliImagePlugin.py @@ -77,8 +77,7 @@ class FliImageFile(ImageFile.ImageFile): if i16(s, 4) == 0xF100: # prefix chunk; ignore it - self.__offset = self.__offset + i32(s) - self.fp.seek(self.__offset) + self.fp.seek(self.__offset + i32(s)) s = self.fp.read(16) if i16(s, 4) == 0xF1FA: From caede14465b664c542eff9365afb128d0b19a729 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 3 Sep 2025 21:46:54 +1000 Subject: [PATCH 187/309] Revert "Removed unused code" This reverts commit 0e22b0ca6c9577fcd5be0013ce6d10e0ee28999a. --- Tests/test_font_crash.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Tests/test_font_crash.py b/Tests/test_font_crash.py index fb5026ee0..b82340ef7 100644 --- a/Tests/test_font_crash.py +++ b/Tests/test_font_crash.py @@ -2,7 +2,7 @@ from __future__ import annotations import pytest -from PIL import ImageFont +from PIL import Image, ImageDraw, ImageFont from .helper import skip_unless_feature @@ -12,6 +12,10 @@ class TestFontCrash: # from fuzzers.fuzz_font font.getbbox("ABC") font.getmask("test text") + with Image.new(mode="RGBA", size=(200, 200)) as im: + draw = ImageDraw.Draw(im) + draw.multiline_textbbox((10, 10), "ABC\nAaaa", font, stroke_width=2) + draw.text((10, 10), "Test Text", font=font, fill="#000") @skip_unless_feature("freetype2") def test_segfault(self) -> None: From abf088fae57ff5fb8476652531c84755fd7d2bdd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 3 Sep 2025 21:52:27 +1000 Subject: [PATCH 188/309] Updated comment --- Tests/test_font_crash.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/test_font_crash.py b/Tests/test_font_crash.py index b82340ef7..54bd2d183 100644 --- a/Tests/test_font_crash.py +++ b/Tests/test_font_crash.py @@ -9,7 +9,8 @@ from .helper import skip_unless_feature class TestFontCrash: def _fuzz_font(self, font: ImageFont.FreeTypeFont) -> None: - # from fuzzers.fuzz_font + # Copy of the code from fuzz_font() in Tests/oss-fuzz/fuzzers.py + # that triggered a problem when fuzzing font.getbbox("ABC") font.getmask("test text") with Image.new(mode="RGBA", size=(200, 200)) as im: From 877707379bda7923de612a4ed4116fd1ec3b6017 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 3 Sep 2025 22:38:37 +1000 Subject: [PATCH 189/309] Deprecate Image._show --- Tests/test_image.py | 8 ++++++++ docs/deprecations.rst | 8 ++++++++ docs/releasenotes/12.0.0.rst | 6 ++++++ src/PIL/Image.py | 5 ++++- 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index be7ca6a6f..eb3882ddc 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -19,6 +19,7 @@ from PIL import ( ImageDraw, ImageFile, ImagePalette, + ImageShow, UnidentifiedImageError, features, ) @@ -1047,6 +1048,13 @@ class TestImage: with pytest.warns(DeprecationWarning, match="Image.Image.get_child_images"): assert im.get_child_images() == [] + def test_show(self, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(ImageShow, "_viewers", []) + + im = Image.new("RGB", (1, 1)) + with pytest.warns(DeprecationWarning, match="Image._show"): + Image._show(im) + @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0))) def test_zero_tobytes(self, size: tuple[int, int]) -> None: im = Image.new("RGB", size) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 3f95cf7f5..e31d3c31c 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -61,6 +61,14 @@ ImageCms.ImageCmsProfile.product_name and .product_info ``.product_info`` attributes have been deprecated, and will be removed in Pillow 13 (2026-10-15). They have been set to ``None`` since Pillow 2.3.0. +Image._show +~~~~~~~~~~~ + +.. deprecated:: 12.0.0 + +``Image._show`` has been deprecated, and will be removed in Pillow 13 (2026-10-15). +Use :py:meth:`~PIL.ImageShow.show` instead. + Removed features ---------------- diff --git a/docs/releasenotes/12.0.0.rst b/docs/releasenotes/12.0.0.rst index 41edea318..12bf760e2 100644 --- a/docs/releasenotes/12.0.0.rst +++ b/docs/releasenotes/12.0.0.rst @@ -116,6 +116,12 @@ vulnerability introduced in FreeType 2.6 (:cve:`2020-15999`). Deprecations ============ +Image._show +^^^^^^^^^^^ + +``Image._show`` has been deprecated, and will be removed in Pillow 13 (2026-10-15). +Use :py:meth:`~PIL.ImageShow.show` instead. + ImageCms.ImageCmsProfile.product_name and .product_info ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 354118a87..5a457803b 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2632,7 +2632,9 @@ class Image: :param title: Optional title to use for the image window, where possible. """ - _show(self, title=title) + from . import ImageShow + + ImageShow.show(self, title) def split(self) -> tuple[Image, ...]: """ @@ -3797,6 +3799,7 @@ def register_encoder(name: str, encoder: type[ImageFile.PyEncoder]) -> None: def _show(image: Image, **options: Any) -> None: from . import ImageShow + deprecate("Image._show", 13, "ImageShow.show") ImageShow.show(image, **options) From f0bbab94a6da39b0366d0f55c9f033c6ab335d28 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 4 Sep 2025 07:23:15 +1000 Subject: [PATCH 190/309] Updated libjpeg-turbo to 3.1.2 --- .github/workflows/wheels-dependencies.sh | 2 +- winbuild/build_prepare.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index c79cd2f17..b4309e8d9 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -96,7 +96,7 @@ ARCHIVE_SDIR=pillow-depends-main FREETYPE_VERSION=2.13.3 HARFBUZZ_VERSION=11.3.3 LIBPNG_VERSION=1.6.50 -JPEGTURBO_VERSION=3.1.1 +JPEGTURBO_VERSION=3.1.2 OPENJPEG_VERSION=2.5.3 XZ_VERSION=5.8.1 TIFF_VERSION=4.7.0 diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 5633519dd..7539cff82 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -117,7 +117,7 @@ V = { "FREETYPE": "2.13.3", "FRIBIDI": "1.0.16", "HARFBUZZ": "11.3.3", - "JPEGTURBO": "3.1.1", + "JPEGTURBO": "3.1.2", "LCMS2": "2.17", "LIBAVIF": "1.3.0", "LIBIMAGEQUANT": "4.4.0", From e0da1a62ec120cba1ae32a38880dd7c749051bda Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 4 Sep 2025 08:10:31 +1000 Subject: [PATCH 191/309] Use walrus operator --- src/PIL/WalImageFile.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PIL/WalImageFile.py b/src/PIL/WalImageFile.py index 87e32878b..5494f62e8 100644 --- a/src/PIL/WalImageFile.py +++ b/src/PIL/WalImageFile.py @@ -49,8 +49,7 @@ class WalImageFile(ImageFile.ImageFile): # strings are null-terminated self.info["name"] = header[:32].split(b"\0", 1)[0] - next_name = header[56 : 56 + 32].split(b"\0", 1)[0] - if next_name: + if next_name := header[56 : 56 + 32].split(b"\0", 1)[0]: self.info["next_name"] = next_name def load(self) -> Image.core.PixelAccess | None: From cfca02a75970cc2816ce341eae5099e1465c6ac9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 4 Sep 2025 08:27:52 +1000 Subject: [PATCH 192/309] Improved WAL test coverage --- Tests/test_file_wal.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Tests/test_file_wal.py b/Tests/test_file_wal.py index b15d79d61..549d47054 100644 --- a/Tests/test_file_wal.py +++ b/Tests/test_file_wal.py @@ -1,5 +1,7 @@ from __future__ import annotations +from io import BytesIO + from PIL import WalImageFile from .helper import assert_image_equal_tofile @@ -13,12 +15,22 @@ def test_open() -> None: assert im.format_description == "Quake2 Texture" assert im.mode == "P" assert im.size == (128, 128) + assert "next_name" not in im.info assert isinstance(im, WalImageFile.WalImageFile) assert_image_equal_tofile(im, "Tests/images/hopper_wal.png") +def test_next_name() -> None: + with open(TEST_FILE, "rb") as fp: + data = bytearray(fp.read()) + data[56:60] = b"Test" + f = BytesIO(data) + with WalImageFile.open(f) as im: + assert im.info["next_name"] == b"Test" + + def test_load() -> None: with WalImageFile.open(TEST_FILE) as im: px = im.load() From 73490e10ad7dd7821aed94ee088cef82659a9fa1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 4 Sep 2025 21:00:13 +1000 Subject: [PATCH 193/309] Mention Pillow 11.3.0 behaviour --- docs/deprecations.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 8f7800ba5..a3c2c55db 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -35,11 +35,12 @@ Image.fromarray mode parameter .. deprecated:: 11.3.0 -Using the ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` to change data types -has been deprecated. Since pixel values do not contain information about palettes or -color spaces, the parameter can still be used to place grayscale L mode data within a -P mode image, or read RGB data as YCbCr for example. If omitted, the mode will be -automatically determined from the object's shape and type. +Using the ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` was deprecated in +Pillow 11.3.0. In Pillow 12.0.0, this was partially reverted, and it is now only +deprecated when changing data types. Since pixel values do not contain information +about palettes or color spaces, the parameter can still be used to place grayscale L +mode data within a P mode image, or read RGB data as YCbCr for example. If omitted, the +mode will be automatically determined from the object's shape and type. Saving I mode images as PNG ^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 5de27c6258f9c4c7a3686d6e2ae9ce07c4ec1138 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 4 Sep 2025 21:09:00 +1000 Subject: [PATCH 194/309] Split versionadded info --- docs/reference/ImageGrab.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/reference/ImageGrab.rst b/docs/reference/ImageGrab.rst index f6a2ec5bc..5c3a73fad 100644 --- a/docs/reference/ImageGrab.rst +++ b/docs/reference/ImageGrab.rst @@ -20,7 +20,9 @@ or the clipboard to a PIL image memory. used as a fallback if they are installed. To disable this behaviour, pass ``xdisplay=""`` instead. - .. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS), 7.1.0 (Linux) + .. versionadded:: 1.1.3 Windows support + .. versionadded:: 3.0.0 macOS support + .. versionadded:: 7.1.0 Linux support :param bbox: What region to copy. Default is the entire screen. On macOS, this is not increased to 2x for Retina screens, so the full @@ -53,7 +55,9 @@ or the clipboard to a PIL image memory. On Linux, ``wl-paste`` or ``xclip`` is required. - .. versionadded:: 1.1.4 (Windows), 3.3.0 (macOS), 9.4.0 (Linux) + .. versionadded:: 1.1.4 Windows support + .. versionadded:: 3.3.0 macOS support + .. versionadded:: 9.4.0 Linux support :return: On Windows, an image, a list of filenames, or None if the clipboard does not contain image data or filenames. From 54d329f98f214bbaf6ee23df0aec91da7f03f035 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Thu, 4 Sep 2025 23:26:47 +1000 Subject: [PATCH 195/309] Updated harfbuzz to 11.4.5 (#9150) Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- .github/workflows/wheels-dependencies.sh | 2 +- winbuild/build_prepare.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index cbd8534aa..1fa634096 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -94,7 +94,7 @@ ARCHIVE_SDIR=pillow-depends-main # annotations have a source code patch that is required for some platforms. If # you change those versions, ensure the patch is also updated. FREETYPE_VERSION=2.13.3 -HARFBUZZ_VERSION=11.3.3 +HARFBUZZ_VERSION=11.4.5 LIBPNG_VERSION=1.6.50 JPEGTURBO_VERSION=3.1.2 OPENJPEG_VERSION=2.5.3 diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 4ba683801..ba69878bc 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -116,7 +116,7 @@ V = { "BROTLI": "1.1.0", "FREETYPE": "2.13.3", "FRIBIDI": "1.0.16", - "HARFBUZZ": "11.3.3", + "HARFBUZZ": "11.4.5", "JPEGTURBO": "3.1.2", "LCMS2": "2.17", "LIBAVIF": "1.3.0", From 476b122ae45f1f6efd48f07f609114b4987c74c0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 5 Sep 2025 20:00:04 +1000 Subject: [PATCH 196/309] Simplified code --- src/PIL/CurImagePlugin.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/PIL/CurImagePlugin.py b/src/PIL/CurImagePlugin.py index b817dbc87..868ff50b5 100644 --- a/src/PIL/CurImagePlugin.py +++ b/src/PIL/CurImagePlugin.py @@ -17,7 +17,7 @@ # from __future__ import annotations -from . import BmpImagePlugin, Image, ImageFile +from . import BmpImagePlugin, Image from ._binary import i16le as i16 from ._binary import i32le as i32 @@ -63,8 +63,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile): # patch up the bitmap height self._size = self.size[0], self.size[1] // 2 - d, e, o, a = self.tile[0] - self.tile[0] = ImageFile._Tile(d, (0, 0) + self.size, o, a) + self.tile = [self.tile[0]._replace(extents=(0, 0) + self.size)] # From a52979785756bd2cd68aa3910945dce5ed96138f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 5 Sep 2025 20:04:50 +1000 Subject: [PATCH 197/309] Assert fp is not None --- src/PIL/FliImagePlugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PIL/FliImagePlugin.py b/src/PIL/FliImagePlugin.py index 679a9edd9..b9cc8ad6c 100644 --- a/src/PIL/FliImagePlugin.py +++ b/src/PIL/FliImagePlugin.py @@ -48,6 +48,7 @@ class FliImageFile(ImageFile.ImageFile): def _open(self) -> None: # HEAD + assert self.fp is not None s = self.fp.read(128) if not (_accept(s) and s[20:22] == b"\x00\x00"): msg = "not an FLI/FLC file" @@ -110,6 +111,7 @@ class FliImageFile(ImageFile.ImageFile): # load palette i = 0 + assert self.fp is not None for e in range(i16(self.fp.read(2))): s = self.fp.read(2) i = i + s[0] From bf18e5fe8bf837ba6756bef0384eae82504c7883 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 5 Sep 2025 20:03:31 +1000 Subject: [PATCH 198/309] Assert fp is not None --- Tests/test_file_cur.py | 1 + src/PIL/CurImagePlugin.py | 1 + 2 files changed, 2 insertions(+) diff --git a/Tests/test_file_cur.py b/Tests/test_file_cur.py index dbf1b866d..ff82e2983 100644 --- a/Tests/test_file_cur.py +++ b/Tests/test_file_cur.py @@ -26,6 +26,7 @@ def test_invalid_file() -> None: no_cursors_file = "Tests/images/no_cursors.cur" cur = CurImagePlugin.CurImageFile(TEST_FILE) + assert cur.fp is not None cur.fp.close() with open(no_cursors_file, "rb") as cur.fp: with pytest.raises(TypeError): diff --git a/src/PIL/CurImagePlugin.py b/src/PIL/CurImagePlugin.py index 868ff50b5..9c188e084 100644 --- a/src/PIL/CurImagePlugin.py +++ b/src/PIL/CurImagePlugin.py @@ -38,6 +38,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile): format_description = "Windows Cursor" def _open(self) -> None: + assert self.fp is not None offset = self.fp.tell() # check magic From 067569790ba47e4149114cb3cd5df8561c8c0b52 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 5 Sep 2025 20:11:02 +1000 Subject: [PATCH 199/309] Test largest cursor --- Tests/test_file_cur.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Tests/test_file_cur.py b/Tests/test_file_cur.py index ff82e2983..4b3e3afcb 100644 --- a/Tests/test_file_cur.py +++ b/Tests/test_file_cur.py @@ -1,8 +1,13 @@ from __future__ import annotations +from io import BytesIO + import pytest from PIL import CurImagePlugin, Image +from PIL._binary import o8 +from PIL._binary import o16le as o16 +from PIL._binary import o32le as o32 TEST_FILE = "Tests/images/deerstalker.cur" @@ -17,6 +22,24 @@ def test_sanity() -> None: assert im.getpixel((16, 16)) == (84, 87, 86, 255) +def test_largest_cursor() -> None: + magic = b"\x00\x00\x02\x00" + sizes = ((1, 1), (8, 8), (4, 4)) + data = magic + o16(len(sizes)) + for w, h in sizes: + image_offset = 6 + len(sizes) * 16 if (w, h) == max(sizes) else 0 + data += o8(w) + o8(h) + o8(0) * 10 + o32(image_offset) + data += ( + o32(12) # header size + + o16(8) # width + + o16(16) # height + + o16(0) # planes + + o16(1) # bits + ) + with Image.open(BytesIO(data)) as im: + assert im.size == (8, 8) + + def test_invalid_file() -> None: invalid_file = "Tests/images/flower.jpg" From d4ed512bec3258d38a9debd62cdd08fe86f4c27c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 5 Sep 2025 23:14:52 +1000 Subject: [PATCH 200/309] Use monkeypatch --- Tests/test_imageshow.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index 7a2f58767..8d6731acc 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -59,15 +59,12 @@ def test_show(mode: str) -> None: assert ImageShow.show(im) -def test_show_without_viewers() -> None: - viewers = ImageShow._viewers - ImageShow._viewers = [] +def test_show_without_viewers(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(ImageShow, "_viewers", []) with hopper() as im: assert not ImageShow.show(im) - ImageShow._viewers = viewers - @pytest.mark.parametrize( "viewer", From 2bf482230d61d785e17d74bd69dcb2fa2a71b1d1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 5 Sep 2025 23:43:47 +1000 Subject: [PATCH 201/309] Test unsupported BMP bitfields layout --- Tests/test_file_bmp.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index 746b2e180..c1c430aa5 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -6,6 +6,8 @@ from pathlib import Path import pytest from PIL import BmpImagePlugin, Image, _binary +from PIL._binary import o16le as o16 +from PIL._binary import o32le as o32 from .helper import ( assert_image_equal, @@ -114,7 +116,7 @@ def test_save_float_dpi(tmp_path: Path) -> None: def test_load_dib() -> None: - # test for #1293, Imagegrab returning Unsupported Bitfields Format + # test for #1293, ImageGrab returning Unsupported Bitfields Format with Image.open("Tests/images/clipboard.dib") as im: assert im.format == "DIB" assert im.get_format_mimetype() == "image/bmp" @@ -219,6 +221,18 @@ def test_rle8_eof(file_name: str, length: int) -> None: im.load() +def test_unsupported_bmp_bitfields_layout() -> None: + fp = io.BytesIO( + o32(40) # header size + + b"\x00" * 10 + + o16(1) # bits + + o32(3) # BITFIELDS compression + + b"\x00" * 32 + ) + with pytest.raises(OSError, match="Unsupported BMP bitfields layout"): + Image.open(fp) + + def test_offset() -> None: # This image has been hexedited # to exclude the palette size from the pixel data offset From 4469ee0fc0206326cd6cf016d31ce204342e35db Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 7 Sep 2025 12:25:56 +1000 Subject: [PATCH 202/309] Test saving P4 images --- Tests/test_file_ppm.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 68f2f9468..ca5347f0f 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -92,6 +92,13 @@ def test_16bit_pgm() -> None: assert_image_equal_tofile(im, "Tests/images/16_bit_binary_pgm.tiff") +def test_p4_save(tmp_path: Path) -> None: + with Image.open("Tests/images/hopper_1bit.pbm") as im: + filename = tmp_path / "temp.pbm" + im.save(filename) + assert_image_equal_tofile(im, filename) + + def test_16bit_pgm_write(tmp_path: Path) -> None: with Image.open("Tests/images/16_bit_binary.pgm") as im: filename = tmp_path / "temp.pgm" From 7d379842c12f33c9cf972ee7fda1e16d207fbbe6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 7 Sep 2025 12:28:20 +1000 Subject: [PATCH 203/309] Test saving unsupported mode --- Tests/test_file_ppm.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index ca5347f0f..598e9a445 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -141,6 +141,12 @@ def test_pfm_big_endian(tmp_path: Path) -> None: assert_image_equal_tofile(im, filename) +def test_save_unsupported_mode(tmp_path: Path) -> None: + im = hopper("P") + with pytest.raises(OSError, match="cannot write mode P as PPM"): + im.save(tmp_path / "out.ppm") + + @pytest.mark.parametrize( "data", [ From b90fe802ced1318a9b1b76dc78e53c458d75340b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 7 Sep 2025 12:49:10 +1000 Subject: [PATCH 204/309] Test transparency --- Tests/test_file_gd.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Tests/test_file_gd.py b/Tests/test_file_gd.py index 806532c17..8a49fd4fa 100644 --- a/Tests/test_file_gd.py +++ b/Tests/test_file_gd.py @@ -1,5 +1,7 @@ from __future__ import annotations +from io import BytesIO + import pytest from PIL import GdImageFile, UnidentifiedImageError @@ -16,6 +18,14 @@ def test_sanity() -> None: assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.jpg", 14) +def test_transparency() -> None: + with open(TEST_GD_FILE, "rb") as fp: + data = bytearray(fp.read()) + data[7:11] = b"\x00\x00\x00\x05" + with GdImageFile.open(BytesIO(data)) as im: + assert im.info["transparency"] == 5 + + def test_bad_mode() -> None: with pytest.raises(ValueError): GdImageFile.open(TEST_GD_FILE, "bad mode") From a58fc562f08ece7763824fea1e5ee02ed3000024 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 7 Sep 2025 13:55:35 +1000 Subject: [PATCH 205/309] Update github-actions (#9194) --- .github/workflows/docs.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/stale.yml | 2 +- .github/workflows/test-windows.yml | 2 +- .github/workflows/test.yml | 2 +- .github/workflows/wheels.yml | 6 +++--- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 761dc1125..cf917407c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -37,7 +37,7 @@ jobs: persist-credentials: false - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.x" cache: pip diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9827ef1cd..2addbaf67 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -33,7 +33,7 @@ jobs: lint-pre-commit- - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.x" cache: pip diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 61ccf58e2..1b0c3c654 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -22,7 +22,7 @@ jobs: steps: - name: "Check issues" - uses: actions/stale@v9 + uses: actions/stale@v10 with: repo-token: ${{ secrets.GITHUB_TOKEN }} only-labels: "Awaiting OP Action" diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index d55a8e5f5..e12a5b1f7 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -67,7 +67,7 @@ jobs: # sets env: pythonLocation - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} allow-prereleases: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b17d08892..8504e5c1e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -70,7 +70,7 @@ jobs: persist-credentials: false - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} allow-prereleases: true diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 24e78f965..81a688135 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -111,7 +111,7 @@ jobs: persist-credentials: false submodules: true - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: "3.x" @@ -164,7 +164,7 @@ jobs: repository: python-pillow/test-images path: Tests\test-images - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: "3.x" @@ -239,7 +239,7 @@ jobs: persist-credentials: false - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.x" From 2d8244c45adeab9fecdf7e1fa3adc9af175a67d8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 8 Sep 2025 23:39:04 +1000 Subject: [PATCH 206/309] Added GitHub profile link --- docs/releasenotes/12.0.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releasenotes/12.0.0.rst b/docs/releasenotes/12.0.0.rst index b166f51b3..0a03b982f 100644 --- a/docs/releasenotes/12.0.0.rst +++ b/docs/releasenotes/12.0.0.rst @@ -171,4 +171,4 @@ ImageMorph operations must have length 1 Valid ImageMorph operations are 4, N, 1 and M. By limiting the length to 1 character within Pillow, long execution times can be avoided if a user provided long pattern -strings. Reported by Jang Choi. +strings. Reported by Jang Choi (https://github.com/uko3211). From 4b8bcb6f379e8073519b3afff745156542f78258 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Tue, 9 Sep 2025 00:04:01 +1000 Subject: [PATCH 207/309] Use link Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- docs/releasenotes/12.0.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releasenotes/12.0.0.rst b/docs/releasenotes/12.0.0.rst index 0a03b982f..de9d6dffd 100644 --- a/docs/releasenotes/12.0.0.rst +++ b/docs/releasenotes/12.0.0.rst @@ -171,4 +171,4 @@ ImageMorph operations must have length 1 Valid ImageMorph operations are 4, N, 1 and M. By limiting the length to 1 character within Pillow, long execution times can be avoided if a user provided long pattern -strings. Reported by Jang Choi (https://github.com/uko3211). +strings. Reported by `Jang Choi `__. From 3a580e0f79bfb5bd491f24e604960e4823c4f58f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 9 Sep 2025 21:04:16 +1000 Subject: [PATCH 208/309] Use _ensure_mutable --- src/PIL/Image.py | 4 +--- src/PIL/ImageDraw.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index b17fd131d..95bf2da3f 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2070,9 +2070,7 @@ class Image: :param value: The pixel value. """ - if self.readonly: - self._copy() - self.load() + self._ensure_mutable() if ( self.mode in ("P", "PA") diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index ed46899b4..1384f1169 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -76,9 +76,7 @@ class ImageDraw: must be the same as the image mode. If omitted, the mode defaults to the mode of the image. """ - im.load() - if im.readonly: - im._copy() # make it writeable + im._ensure_mutable() blend = 0 if mode is None: mode = im.mode From 410fb60f65f21d6b1ee968b3d9d278d0ef97e355 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 9 Sep 2025 22:01:07 +1000 Subject: [PATCH 209/309] Added alpha channel examples --- docs/reference/ImageDraw.rst | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 4a2223a40..6768a04c6 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -57,6 +57,43 @@ Color names See :ref:`color-names` for the color names supported by Pillow. +Alpha channel +^^^^^^^^^^^^^ + +By default, when drawing onto an existing image, the image's pixel values are simply +replaced by the new color:: + + im = Image.new("RGBA", (1, 1), (255, 0, 0)) + d = ImageDraw.Draw(im) + d.rectangle((0, 0, 1, 1), (0, 255, 0, 127)) + assert im.getpixel((0, 0)) == (0, 255, 0, 127) + + # Alpha channel values have no effect when drawing with RGB mode + im = Image.new("RGB", (1, 1), (255, 0, 0)) + d = ImageDraw.Draw(im) + d.rectangle((0, 0, 1, 1), (0, 255, 0, 127)) + assert im.getpixel((0, 0)) == (0, 255, 0) + +If you would like to combine translucent color with an RGB image, then initialize the +ImageDraw instance with the RGBA mode:: + + from PIL import Image, ImageDraw + im = Image.new("RGB", (1, 1), (255, 0, 0)) + d = ImageDraw.Draw(im, "RGBA") + d.rectangle((0, 0, 1, 1), (0, 255, 0, 127)) + assert im.getpixel((0, 0)) == (128, 127, 0) + +If you would like to combine translucent color with an RGBA image underneath, you will +need to combine multiple images:: + + from PIL import Image, ImageDraw + im = Image.new("RGBA", (1, 1), (255, 0, 0, 255)) + im2 = Image.new("RGBA", (1, 1)) + d = ImageDraw.Draw(im2) + d.rectangle((0, 0, 1, 1), (0, 255, 0, 127)) + im.paste(im2.convert("RGB"), mask=im2) + assert im.getpixel((0, 0)) == (128, 127, 0, 255) + Fonts ^^^^^ From 5df7f98a591e494cd2b0516c3f49d37eb8ee2dd9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 10 Sep 2025 13:16:12 +1000 Subject: [PATCH 210/309] Updated Ghostscript to 10.6.0 --- .github/workflows/test-windows.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index e12a5b1f7..f6a7dd46b 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -97,8 +97,8 @@ jobs: choco install nasm --no-progress echo "C:\Program Files\NASM" >> $env:GITHUB_PATH - choco install ghostscript --version=10.5.1 --no-progress - echo "C:\Program Files\gs\gs10.05.1\bin" >> $env:GITHUB_PATH + choco install ghostscript --version=10.6.0 --no-progress + echo "C:\Program Files\gs\gs10.06.0\bin" >> $env:GITHUB_PATH # Install extra test images xcopy /S /Y Tests\test-images\* Tests\images From d70cba37627586b243a9b3aeac7899f5389d3ba8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 14 Sep 2025 15:55:50 +1000 Subject: [PATCH 211/309] Update dependency mypy to v1.18.1 (#9207) --- .ci/requirements-mypy.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/requirements-mypy.txt b/.ci/requirements-mypy.txt index bd9563800..68d69c183 100644 --- a/.ci/requirements-mypy.txt +++ b/.ci/requirements-mypy.txt @@ -1,4 +1,4 @@ -mypy==1.17.1 +mypy==1.18.1 IceSpringPySideStubs-PyQt6 IceSpringPySideStubs-PySide6 ipython From 53302c2281a9576acabf5815f3eda1f48f253cf0 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Tue, 16 Sep 2025 19:43:03 +1000 Subject: [PATCH 212/309] Split versionadded info Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- docs/reference/ImageGrab.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/reference/ImageGrab.rst b/docs/reference/ImageGrab.rst index 25afc9926..00d7f8e3c 100644 --- a/docs/reference/ImageGrab.rst +++ b/docs/reference/ImageGrab.rst @@ -44,7 +44,8 @@ or the clipboard to a PIL image memory. :param window: Capture a single window. On Windows, this is a HWND. On macOS, it uses windowid. - .. versionadded:: 11.2.1 (Windows), 12.0.0 (macOS) + .. versionadded:: 11.2.1 Windows support + .. versionadded:: 12.0.0 macOS support :return: An image .. py:function:: grabclipboard() From ca3528f46eacb005d3875410655b6ae9dc91c45c Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Tue, 16 Sep 2025 21:43:24 +1000 Subject: [PATCH 213/309] Document that macOS window value is a CGWindowID --- docs/reference/ImageGrab.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/reference/ImageGrab.rst b/docs/reference/ImageGrab.rst index c6671ca71..e7dd41de1 100644 --- a/docs/reference/ImageGrab.rst +++ b/docs/reference/ImageGrab.rst @@ -44,7 +44,8 @@ or the clipboard to a PIL image memory. .. versionadded:: 7.1.0 :param window: - Capture a single window. On Windows, this is a HWND. On macOS, it uses windowid. + Capture a single window. On Windows, this is a HWND. On macOS, this is a + CGWindowID. .. versionadded:: 11.2.1 Windows support .. versionadded:: 12.0.0 macOS support From c8b4a24e75d894a2fa7233c4acaf75ed061d08f8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 17 Sep 2025 19:51:50 +1000 Subject: [PATCH 214/309] Updated macOS tested Pillow versions --- docs/installation/platform-support.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/installation/platform-support.rst b/docs/installation/platform-support.rst index 3c5e4cd51..186d9b96d 100644 --- a/docs/installation/platform-support.rst +++ b/docs/installation/platform-support.rst @@ -75,6 +75,8 @@ These platforms have been reported to work at the versions mentioned. | Operating system | | Tested Python | | Latest tested | | Tested | | | | versions | | Pillow version | | processors | +==================================+============================+==================+==============+ +| macOS 26 Tahoe | 3.9, 3.10, 3.11, 3.12, 3.13| 11.3.0 |arm | ++----------------------------------+----------------------------+------------------+--------------+ | macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13| 11.3.0 |arm | | +----------------------------+------------------+ | | | 3.8 | 10.4.0 | | From 9e4256e8aa9d7adad4271069732c48d129d5b38f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 22:22:30 +1000 Subject: [PATCH 215/309] Update dependency mypy to v1.18.2 (#9213) --- .ci/requirements-mypy.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/requirements-mypy.txt b/.ci/requirements-mypy.txt index 68d69c183..447856433 100644 --- a/.ci/requirements-mypy.txt +++ b/.ci/requirements-mypy.txt @@ -1,4 +1,4 @@ -mypy==1.18.1 +mypy==1.18.2 IceSpringPySideStubs-PyQt6 IceSpringPySideStubs-PySide6 ipython From 92e671d7970b8f96c50424c0c47efd0a1c95bc51 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 20 Sep 2025 20:15:20 +1000 Subject: [PATCH 216/309] Updated tests for FreeType 2.14.1 --- Tests/images/colr_bungee.png | Bin 4545 -> 4350 bytes Tests/images/colr_bungee_mask.png | Bin 2789 -> 2534 bytes Tests/images/colr_bungee_older.png | Bin 0 -> 4545 bytes Tests/test_imagedraw.py | 8 ++++++-- Tests/test_imagefont.py | 10 +++++++--- Tests/test_imagefontctl.py | 2 +- 6 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 Tests/images/colr_bungee_older.png diff --git a/Tests/images/colr_bungee.png b/Tests/images/colr_bungee.png index b10a60be057c4654ebcf36de28870e6bd6ee8010..9ec6b11823b429c1b7280b1539ce3bb2c4511e37 100644 GIT binary patch literal 4350 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 literal 2789 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 diff --git a/Tests/images/colr_bungee_older.png b/Tests/images/colr_bungee_older.png new file mode 100644 index 0000000000000000000000000000000000000000..b10a60be057c4654ebcf36de28870e6bd6ee8010 GIT binary patch literal 4545 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 None: def draw_text() -> None: draw.text((0, 0), text, font_size=16) - assert_image_equal_tofile(im, "Tests/images/imagedraw_default_font_size.png") + assert_image_similar_tofile( + im, "Tests/images/imagedraw_default_font_size.png", 1 + ) check(draw_text) @@ -1513,7 +1515,9 @@ def test_default_font_size() -> None: def draw_multiline_text() -> None: draw.multiline_text((0, 0), text, font_size=16) - assert_image_equal_tofile(im, "Tests/images/imagedraw_default_font_size.png") + assert_image_similar_tofile( + im, "Tests/images/imagedraw_default_font_size.png", 1 + ) check(draw_multiline_text) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 4565d35ba..4b8a61eb3 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -19,6 +19,7 @@ from .helper import ( assert_image_equal, assert_image_equal_tofile, assert_image_similar_tofile, + has_feature_version, is_win32, skip_unless_feature, skip_unless_feature_version, @@ -549,7 +550,7 @@ def test_default_font() -> None: draw.text((10, 60), txt, font=larger_default_font) # Assert - assert_image_equal_tofile(im, "Tests/images/default_font_freetype.png") + assert_image_similar_tofile(im, "Tests/images/default_font_freetype.png", 0.13) @pytest.mark.parametrize("mode", ("", "1", "RGBA")) @@ -1055,7 +1056,10 @@ def test_colr(layout_engine: ImageFont.Layout) -> None: d.text((15, 5), "Bungee", font=font, embedded_color=True) - assert_image_similar_tofile(im, "Tests/images/colr_bungee.png", 21) + if has_feature_version("freetype2", "2.14.0"): + assert_image_similar_tofile(im, "Tests/images/colr_bungee.png", 6.1) + else: + assert_image_similar_tofile(im, "Tests/images/colr_bungee_older.png", 21) @skip_unless_feature_version("freetype2", "2.10.0") @@ -1071,7 +1075,7 @@ def test_colr_mask(layout_engine: ImageFont.Layout) -> None: d.text((15, 5), "Bungee", "black", font=font) - assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 22) + assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 14.1) def test_woff2(layout_engine: ImageFont.Layout) -> None: diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index 95af3fda8..633f6756b 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -183,7 +183,7 @@ def test_x_max_and_y_offset() -> None: draw.text((0, 0), "لح", font=ttf, fill=500) target = "Tests/images/test_x_max_and_y_offset.png" - assert_image_similar_tofile(im, target, 0.5) + assert_image_similar_tofile(im, target, 3.8) def test_language() -> None: From 6916a73b579df0f78ee9734b83f58c2acbb8b17e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 20 Sep 2025 20:16:50 +1000 Subject: [PATCH 217/309] Build FreeType 2.14.1 on macOS 13, instead of using 2.14.0 from brew --- .github/workflows/macos-install.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/macos-install.sh b/.github/workflows/macos-install.sh index 94e3d5d08..8060e0850 100755 --- a/.github/workflows/macos-install.sh +++ b/.github/workflows/macos-install.sh @@ -4,11 +4,19 @@ set -e if [[ "$ImageOS" == "macos13" ]]; then brew uninstall gradle maven + + wget https://raw.githubusercontent.com/python-pillow/pillow-depends/main/freetype-2.14.1.tar.gz + tar -xvzf freetype-2.14.1.tar.gz + (cd freetype-2.14.1 \ + && ./configure \ + && make -j4 \ + && make install) +else + brew install freetype fi brew install \ aom \ dav1d \ - freetype \ ghostscript \ jpeg-turbo \ libimagequant \ From 04177eb6ba6af0aaea8d959e99d4cff7cd22c798 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 20 Sep 2025 20:17:10 +1000 Subject: [PATCH 218/309] Updated FreeType to 2.14.1 on Windows --- winbuild/build_prepare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index ba69878bc..5c638829e 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -114,7 +114,7 @@ ARCHITECTURES = { V = { "BROTLI": "1.1.0", - "FREETYPE": "2.13.3", + "FREETYPE": "2.14.1", "FRIBIDI": "1.0.16", "HARFBUZZ": "11.4.5", "JPEGTURBO": "3.1.2", From d64f56f53bde0f262250068471d3713c9d6ed3e9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 21 Sep 2025 07:38:17 +1000 Subject: [PATCH 219/309] Updated openjpeg to 2.5.4 --- .github/workflows/wheels-dependencies.sh | 2 +- depends/install_openjpeg.sh | 2 +- docs/installation/building-from-source.rst | 2 +- winbuild/build_prepare.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 1fa634096..f400994d7 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -97,7 +97,7 @@ FREETYPE_VERSION=2.13.3 HARFBUZZ_VERSION=11.4.5 LIBPNG_VERSION=1.6.50 JPEGTURBO_VERSION=3.1.2 -OPENJPEG_VERSION=2.5.3 +OPENJPEG_VERSION=2.5.4 XZ_VERSION=5.8.1 ZSTD_VERSION=1.5.7 TIFF_VERSION=4.7.0 diff --git a/depends/install_openjpeg.sh b/depends/install_openjpeg.sh index 1f8d78193..bc7c7c634 100755 --- a/depends/install_openjpeg.sh +++ b/depends/install_openjpeg.sh @@ -1,7 +1,7 @@ #!/bin/bash # install openjpeg -archive=openjpeg-2.5.3 +archive=openjpeg-2.5.4 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz diff --git a/docs/installation/building-from-source.rst b/docs/installation/building-from-source.rst index fc7ef7646..656d54325 100644 --- a/docs/installation/building-from-source.rst +++ b/docs/installation/building-from-source.rst @@ -58,7 +58,7 @@ 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**, - **2.4.0**, **2.5.0**, **2.5.2** and **2.5.3**. + **2.4.0**, **2.5.0**, **2.5.2**, **2.5.3** and **2.5.4**. * Pillow does **not** support the earlier **1.5** series which ships with Debian Jessie. diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 5c638829e..30f7a123c 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -123,7 +123,7 @@ V = { "LIBIMAGEQUANT": "4.4.0", "LIBPNG": "1.6.50", "LIBWEBP": "1.6.0", - "OPENJPEG": "2.5.3", + "OPENJPEG": "2.5.4", "TIFF": "4.7.0", "XZ": "5.8.1", "ZLIBNG": "2.2.5", From 222933df542d9a0c956bad2b8a4429ed7c973145 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 18 Sep 2025 21:48:01 +1000 Subject: [PATCH 220/309] Seek past BeginBinary data when parsing metadata --- Tests/test_file_eps.py | 8 ++++++++ src/PIL/EpsImagePlugin.py | 3 +++ 2 files changed, 11 insertions(+) diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index d94de7287..b50915f28 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -197,6 +197,14 @@ def test_load_long_binary_data(prefix: bytes) -> None: assert img.format == "EPS" +def test_begin_binary() -> None: + with open("Tests/images/eps/binary_preview_map.eps", "rb") as fp: + data = bytearray(fp.read()) + data[76875 : 76875 + 11] = b"%" * 11 + with Image.open(io.BytesIO(data)) as img: + assert img.size == (399, 480) + + @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index 5e2ddad99..69f3062b4 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -354,6 +354,9 @@ class EpsImageFile(ImageFile.ImageFile): read_comment(s) elif bytes_mv[:9] == b"%%Trailer": trailer_reached = True + elif bytes_mv[:14] == b"%%BeginBinary:": + bytecount = int(byte_arr[14:bytes_read]) + self.fp.seek(bytecount, os.SEEK_CUR) bytes_read = 0 # A "BoundingBox" is always required, From 9ba1029d515c5113bd0b1ea4f99fb5d3f1a9659b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 15 Sep 2025 22:28:30 +1000 Subject: [PATCH 221/309] Clear C image when MPO frame image size changes --- Tests/test_file_mpo.py | 2 ++ src/PIL/JpegImagePlugin.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 9262e6ca7..ba05bbe43 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -108,9 +108,11 @@ def test_frame_size() -> None: # in the SOF marker of the second frame with Image.open("Tests/images/sugarshack_frame_size.mpo") as im: assert im.size == (640, 480) + im.load() im.seek(1) assert im.size == (680, 480) + im.load() im.seek(0) assert im.size == (640, 480) diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 0d110035e..755ca648e 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -193,6 +193,8 @@ def SOF(self: JpegImageFile, marker: int) -> None: n = i16(self.fp.read(2)) - 2 s = ImageFile._safe_read(self.fp, n) self._size = i16(s, 3), i16(s, 1) + if self._im is not None and self.size != self.im.size: + self._im = None self.bits = s[0] if self.bits != 8: From ce8d05484b71737a352eb6a52332cb7856b597c6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 15 Sep 2025 22:31:15 +1000 Subject: [PATCH 222/309] Use naturally created image --- Tests/images/frame_size.mpo | Bin 0 -> 14574 bytes Tests/images/sugarshack_frame_size.mpo | Bin 120198 -> 0 bytes Tests/test_file_mpo.py | 10 ++++------ 3 files changed, 4 insertions(+), 6 deletions(-) create mode 100644 Tests/images/frame_size.mpo delete mode 100644 Tests/images/sugarshack_frame_size.mpo diff --git a/Tests/images/frame_size.mpo b/Tests/images/frame_size.mpo new file mode 100644 index 0000000000000000000000000000000000000000..ee5c6cdf7a926901d490becc6892cc18dfc075f5 GIT binary patch literal 14574 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 y`1P$7zJ)4hg6^ct`UVN>+#5FHi( diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index ba05bbe43..ddb589736 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -104,18 +104,16 @@ def test_exif(test_file: str) -> None: def test_frame_size() -> None: - # This image has been hexedited to contain a different size - # in the SOF marker of the second frame - with Image.open("Tests/images/sugarshack_frame_size.mpo") as im: - assert im.size == (640, 480) + with Image.open("Tests/images/frame_size.mpo") as im: + assert im.size == (56, 70) im.load() im.seek(1) - assert im.size == (680, 480) + assert im.size == (349, 434) im.load() im.seek(0) - assert im.size == (640, 480) + assert im.size == (56, 70) def test_ignore_frame_size() -> None: From 913a6d8390b57dde5f1dcfbd4a0fa8887d992e8a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 13 Sep 2025 09:12:01 +1000 Subject: [PATCH 223/309] Updated harfbuzz to 11.5.0 --- .github/workflows/wheels-dependencies.sh | 2 +- winbuild/build_prepare.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 1fa634096..3d016b5e2 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -94,7 +94,7 @@ ARCHIVE_SDIR=pillow-depends-main # annotations have a source code patch that is required for some platforms. If # you change those versions, ensure the patch is also updated. FREETYPE_VERSION=2.13.3 -HARFBUZZ_VERSION=11.4.5 +HARFBUZZ_VERSION=11.5.0 LIBPNG_VERSION=1.6.50 JPEGTURBO_VERSION=3.1.2 OPENJPEG_VERSION=2.5.3 diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 5c638829e..d21b549b6 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -116,7 +116,7 @@ V = { "BROTLI": "1.1.0", "FREETYPE": "2.14.1", "FRIBIDI": "1.0.16", - "HARFBUZZ": "11.4.5", + "HARFBUZZ": "11.5.0", "JPEGTURBO": "3.1.2", "LCMS2": "2.17", "LIBAVIF": "1.3.0", From d42e537efeb1bd11cd9df1db1c7d7a6dc529d9e2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 12:17:40 +1000 Subject: [PATCH 224/309] Update dependency cibuildwheel to v3.2.0 (#9219) --- .ci/requirements-cibw.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/requirements-cibw.txt b/.ci/requirements-cibw.txt index d87d7956f..8ec7262c0 100644 --- a/.ci/requirements-cibw.txt +++ b/.ci/requirements-cibw.txt @@ -1 +1 @@ -cibuildwheel==3.1.4 +cibuildwheel==3.2.0 From 2c438830736a5cb17cd9aea8c8aacb67a11dd86c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 25 Sep 2025 21:01:16 +1000 Subject: [PATCH 225/309] Updated libtiff to 4.7.1 --- .github/workflows/wheels-dependencies.sh | 2 +- docs/installation/building-from-source.rst | 2 +- winbuild/build_prepare.py | 8 +------- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index cbeee8f9d..8a3985763 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -100,7 +100,7 @@ JPEGTURBO_VERSION=3.1.2 OPENJPEG_VERSION=2.5.4 XZ_VERSION=5.8.1 ZSTD_VERSION=1.5.7 -TIFF_VERSION=4.7.0 +TIFF_VERSION=4.7.1 LCMS2_VERSION=2.17 ZLIB_NG_VERSION=2.2.5 LIBWEBP_VERSION=1.6.0 diff --git a/docs/installation/building-from-source.rst b/docs/installation/building-from-source.rst index 656d54325..6080d29af 100644 --- a/docs/installation/building-from-source.rst +++ b/docs/installation/building-from-source.rst @@ -44,7 +44,7 @@ Many of Pillow's features require external libraries: * **libtiff** provides compressed TIFF functionality - * Pillow has been tested with libtiff versions **4.0-4.7.0** + * Pillow has been tested with libtiff versions **4.0-4.7.1** * **libfreetype** provides type related services diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index b28aa8caa..e00f6185d 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -124,7 +124,7 @@ V = { "LIBPNG": "1.6.50", "LIBWEBP": "1.6.0", "OPENJPEG": "2.5.4", - "TIFF": "4.7.0", + "TIFF": "4.7.1", "XZ": "5.8.1", "ZLIBNG": "2.2.5", } @@ -228,12 +228,6 @@ DEPS: dict[str, dict[str, Any]] = { # link against libwebp.lib "#ifdef WEBP_SUPPORT": '#ifdef WEBP_SUPPORT\n#pragma comment(lib, "libwebp.lib")', # noqa: E501 }, - r"test\CMakeLists.txt": { - "add_executable(test_write_read_tags ../placeholder.h)": "", - "target_sources(test_write_read_tags PRIVATE test_write_read_tags.c)": "", # noqa: E501 - "target_link_libraries(test_write_read_tags PRIVATE tiff)": "", - "list(APPEND simple_tests test_write_read_tags)": "", - }, }, "build": [ *cmds_cmake( From 637f25dc2c7f1593bc7fca0ce3654feaff752b59 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 25 Sep 2025 21:01:33 +1000 Subject: [PATCH 226/309] Revert "Allow cmake<4 when building libtiff" This reverts commit 81412212016a70eb160460e26dc552a0f8a8c153. --- winbuild/build_prepare.py | 1 - 1 file changed, 1 deletion(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index e00f6185d..76f05bdcb 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -235,7 +235,6 @@ DEPS: dict[str, dict[str, Any]] = { "-DBUILD_SHARED_LIBS:BOOL=OFF", "-DWebP_LIBRARY=libwebp", '-DCMAKE_C_FLAGS="-nologo -DLZMA_API_STATIC"', - "-DCMAKE_POLICY_VERSION_MINIMUM=3.5", ) ], "headers": [r"libtiff\tiff*.h"], From e2a8e217dad1445caff35092695264eefbbf8ae7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 29 Sep 2025 23:18:47 +1000 Subject: [PATCH 227/309] Removed _expand() --- Tests/test_image.py | 27 --------------------------- src/PIL/Image.py | 6 ------ 2 files changed, 33 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index eb3882ddc..178644365 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -284,33 +284,6 @@ class TestImage: assert item is not None assert item != num - def test_expand_x(self) -> None: - # Arrange - im = hopper() - orig_size = im.size - xmargin = 5 - - # Act - im = im._expand(xmargin) - - # Assert - assert im.size[0] == orig_size[0] + 2 * xmargin - assert im.size[1] == orig_size[1] + 2 * xmargin - - def test_expand_xy(self) -> None: - # Arrange - im = hopper() - orig_size = im.size - xmargin = 5 - ymargin = 3 - - # Act - im = im._expand(xmargin, ymargin) - - # Assert - assert im.size[0] == orig_size[0] + 2 * xmargin - assert im.size[1] == orig_size[1] + 2 * ymargin - def test_getbands(self) -> None: # Assert assert hopper("RGB").getbands() == ("R", "G", "B") diff --git a/src/PIL/Image.py b/src/PIL/Image.py index b17fd131d..708e85899 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1336,12 +1336,6 @@ class Image: """ pass - def _expand(self, xmargin: int, ymargin: int | None = None) -> Image: - if ymargin is None: - ymargin = xmargin - self.load() - return self._new(self.im.expand(xmargin, ymargin)) - def filter(self, filter: ImageFilter.Filter | type[ImageFilter.Filter]) -> Image: """ Filters this image using the given filter. For a list of From a953d86b4db252d614312e2684c0c1f459d6a796 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Wed, 1 Oct 2025 23:11:53 +1000 Subject: [PATCH 228/309] Python 3.9 wheels are no longer needed (#9214) --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 81a688135..68a446f79 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -54,7 +54,7 @@ jobs: platform: macos os: macos-13 cibw_arch: x86_64 - build: "cp3{9,10,11}*" + build: "cp3{10,11}*" macosx_deployment_target: "10.10" - name: "macOS 10.13 x86_64" platform: macos From 0bcfd3b55c350269c7275067bc10bc095d0df27c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 2 Oct 2025 21:35:26 +1000 Subject: [PATCH 229/309] Updated Python version --- winbuild/README.md | 2 +- winbuild/build.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/winbuild/README.md b/winbuild/README.md index 62345af60..db71f094e 100644 --- a/winbuild/README.md +++ b/winbuild/README.md @@ -16,7 +16,7 @@ For more extensive info, see the [Windows build instructions](build.rst). Here's an example script to build on Windows: ``` -set PYTHON=C:\Python39\bin +set PYTHON=C:\Python310\bin cd /D C:\Pillow\winbuild %PYTHON%\python.exe build_prepare.py -v --depends=C:\pillow-depends build\build_dep_all.cmd diff --git a/winbuild/build.rst b/winbuild/build.rst index aa4677ad5..23b26c422 100644 --- a/winbuild/build.rst +++ b/winbuild/build.rst @@ -115,7 +115,7 @@ Example Here's an example script to build on Windows:: - set PYTHON=C:\Python39\bin + set PYTHON=C:\Python310\bin cd /D C:\Pillow\winbuild %PYTHON%\python.exe build_prepare.py -v --depends C:\pillow-depends build\build_dep_all.cmd From 7cb518031ad64d9566f8d20d51c5da784023d49f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 2 Oct 2025 22:21:16 +1000 Subject: [PATCH 230/309] Updated FreeType to 2.14.1 on macOS and Linux --- .github/workflows/wheels-dependencies.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index cbeee8f9d..bc490a38a 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -93,7 +93,11 @@ ARCHIVE_SDIR=pillow-depends-main # Package versions for fresh source builds. Version numbers with "Patched" # annotations have a source code patch that is required for some platforms. If # you change those versions, ensure the patch is also updated. -FREETYPE_VERSION=2.13.3 +if [[ -n "$IOS_SDK" ]]; then + FREETYPE_VERSION=2.13.3 +else + FREETYPE_VERSION=2.14.1 +fi HARFBUZZ_VERSION=11.5.0 LIBPNG_VERSION=1.6.50 JPEGTURBO_VERSION=3.1.2 @@ -314,6 +318,10 @@ function build { if [[ -n "$IS_MACOS" ]]; then # Custom freetype build + if [[ -z "$IOS_SDK" ]]; then + build_simple sed 4.9 https://mirrors.middlendian.com/gnu/sed + fi + build_simple freetype $FREETYPE_VERSION https://download.savannah.gnu.org/releases/freetype tar.gz --with-harfbuzz=no else build_freetype From 0c0ff7c38f91182b30b979a81423efd84deb3805 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 3 Oct 2025 20:27:42 +1000 Subject: [PATCH 231/309] Removed use of sudo from libavif and raqm install scripts --- .ci/install.sh | 4 ++-- depends/install_libavif.sh | 2 +- depends/install_raqm.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.ci/install.sh b/.ci/install.sh index 2178c6646..52b821417 100755 --- a/.ci/install.sh +++ b/.ci/install.sh @@ -51,10 +51,10 @@ pushd depends && ./install_webp.sh && popd pushd depends && ./install_imagequant.sh && popd # raqm -pushd depends && ./install_raqm.sh && popd +pushd depends && sudo ./install_raqm.sh && popd # libavif -pushd depends && ./install_libavif.sh && popd +pushd depends && sudo ./install_libavif.sh && popd # extra test images pushd depends && ./install_extra_test_images.sh && popd diff --git a/depends/install_libavif.sh b/depends/install_libavif.sh index 26af8a36c..50ba01755 100755 --- a/depends/install_libavif.sh +++ b/depends/install_libavif.sh @@ -59,6 +59,6 @@ cmake \ "${LIBAVIF_CMAKE_FLAGS[@]}" \ . -sudo make install +make install popd diff --git a/depends/install_raqm.sh b/depends/install_raqm.sh index b5a05100b..33bb2d0a7 100755 --- a/depends/install_raqm.sh +++ b/depends/install_raqm.sh @@ -8,6 +8,6 @@ archive=libraqm-0.10.3 pushd $archive -meson build --prefix=/usr && sudo ninja -C build install +meson build --prefix=/usr && ninja -C build install popd From b3d1836907796e6d6f3c590c9a1860f6ecb04246 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Sat, 4 Oct 2025 19:49:09 +1000 Subject: [PATCH 232/309] Update harfbuzz to 12.1.0 (#9218) Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- .github/workflows/wheels-dependencies.sh | 2 +- winbuild/build_prepare.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 68c2eea30..69c867b4d 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -98,7 +98,7 @@ if [[ -n "$IOS_SDK" ]]; then else FREETYPE_VERSION=2.14.1 fi -HARFBUZZ_VERSION=11.5.0 +HARFBUZZ_VERSION=12.1.0 LIBPNG_VERSION=1.6.50 JPEGTURBO_VERSION=3.1.2 OPENJPEG_VERSION=2.5.4 diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 76f05bdcb..186a80cca 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -116,7 +116,7 @@ V = { "BROTLI": "1.1.0", "FREETYPE": "2.14.1", "FRIBIDI": "1.0.16", - "HARFBUZZ": "11.5.0", + "HARFBUZZ": "12.1.0", "JPEGTURBO": "3.1.2", "LCMS2": "2.17", "LIBAVIF": "1.3.0", From 09e571780ec33df260c0dccfb7efdf59cdea0d8d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 17:29:41 +0000 Subject: [PATCH 233/309] [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.12.11 → v0.13.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.11...v0.13.3) - [github.com/psf/black-pre-commit-mirror: 25.1.0 → 25.9.0](https://github.com/psf/black-pre-commit-mirror/compare/25.1.0...25.9.0) - [github.com/pre-commit/mirrors-clang-format: v21.1.0 → v21.1.2](https://github.com/pre-commit/mirrors-clang-format/compare/v21.1.0...v21.1.2) - [github.com/python-jsonschema/check-jsonschema: 0.33.3 → 0.34.0](https://github.com/python-jsonschema/check-jsonschema/compare/0.33.3...0.34.0) - [github.com/zizmorcore/zizmor-pre-commit: v1.12.1 → v1.14.2](https://github.com/zizmorcore/zizmor-pre-commit/compare/v1.12.1...v1.14.2) - [github.com/tox-dev/pyproject-fmt: v2.6.0 → v2.7.0](https://github.com/tox-dev/pyproject-fmt/compare/v2.6.0...v2.7.0) --- .pre-commit-config.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 23bda1ec7..ab0153687 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.12.11 + rev: v0.13.3 hooks: - id: ruff-check args: [--exit-non-zero-on-fix] - repo: https://github.com/psf/black-pre-commit-mirror - rev: 25.1.0 + rev: 25.9.0 hooks: - id: black @@ -24,7 +24,7 @@ repos: exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$|\.patch$) - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v21.1.0 + rev: v21.1.2 hooks: - id: clang-format types: [c] @@ -51,14 +51,14 @@ repos: exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/|\.patch$ - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.33.3 + rev: 0.34.0 hooks: - id: check-github-workflows - id: check-readthedocs - id: check-renovate - repo: https://github.com/zizmorcore/zizmor-pre-commit - rev: v1.12.1 + rev: v1.14.2 hooks: - id: zizmor @@ -68,7 +68,7 @@ repos: - id: sphinx-lint - repo: https://github.com/tox-dev/pyproject-fmt - rev: v2.6.0 + rev: v2.7.0 hooks: - id: pyproject-fmt From 7259685ba4d05a77ae802920bf54c02a84b6db79 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 7 Oct 2025 09:05:53 +1100 Subject: [PATCH 234/309] Build Python 3.14 on macOS 10.15 --- .github/workflows/wheels.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 68a446f79..f1c851bc7 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -60,13 +60,13 @@ jobs: platform: macos os: macos-13 cibw_arch: x86_64 - build: "cp3{12,13,14}*" + build: "cp3{12,13}*" macosx_deployment_target: "10.13" - name: "macOS 10.15 x86_64" platform: macos os: macos-13 cibw_arch: x86_64 - build: "pp3*" + build: "{cp314,pp3}*" macosx_deployment_target: "10.15" - name: "macOS arm64" platform: macos From 6d19b8adeff16674e62bd1e0aed95f29ff1932fb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 26 Jul 2025 21:58:41 +1000 Subject: [PATCH 235/309] Do not allow negative offset with memory mapping --- Tests/test_imagefile.py | 5 +++++ src/PIL/ImageFile.py | 3 +++ 2 files changed, 8 insertions(+) diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index d4dfb1b6d..7dfb3abf9 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -164,6 +164,11 @@ class TestImageFile: with pytest.raises(OSError): p.close() + def test_negative_offset(self) -> None: + with Image.open("Tests/images/raw_negative_stride.bin") as im: + with pytest.raises(ValueError, match="Tile offset cannot be negative"): + im.load() + def test_no_format(self) -> None: buf = BytesIO(b"\x00" * 255) diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index e33b846d4..a1d98bd51 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -313,6 +313,9 @@ class ImageFile(Image.Image): and args[0] == self.mode and args[0] in Image._MAPMODES ): + if offset < 0: + msg = "Tile offset cannot be negative" + raise ValueError(msg) try: # use mmap, if possible import mmap From 1d4cda65cf31d012690c1637ed1046a5de1448b7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 12 Sep 2025 16:27:38 +1000 Subject: [PATCH 236/309] Cast to UINT32 before shifting bits --- src/libImaging/SgiRleDecode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libImaging/SgiRleDecode.c b/src/libImaging/SgiRleDecode.c index e60468990..a562f582c 100644 --- a/src/libImaging/SgiRleDecode.c +++ b/src/libImaging/SgiRleDecode.c @@ -22,7 +22,8 @@ static void read4B(UINT32 *dest, UINT8 *buf) { - *dest = (UINT32)((buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]); + *dest = ((UINT32)buf[0] << 24) | ((UINT32)buf[1] << 16) | ((UINT32)buf[2] << 8) | + buf[3]; } /* From a2ef220b320b82cc6a42ef65fba4dbddd360107a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 9 Oct 2025 21:01:42 +1100 Subject: [PATCH 237/309] Cast before additional shifting --- src/libImaging/Access.c | 10 ++++++---- src/libImaging/BcnEncode.c | 10 +++++----- src/libImaging/FliDecode.c | 6 ++++-- src/libImaging/GetBBox.c | 4 ++-- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/libImaging/Access.c b/src/libImaging/Access.c index 3db52377e..65c832cbe 100644 --- a/src/libImaging/Access.c +++ b/src/libImaging/Access.c @@ -64,7 +64,7 @@ static void get_pixel_16L(Imaging im, int x, int y, void *color) { UINT8 *in = (UINT8 *)&im->image[y][x + x]; #ifdef WORDS_BIGENDIAN - UINT16 out = in[0] + (in[1] << 8); + UINT16 out = in[0] + ((UINT16)in[1] << 8); memcpy(color, &out, sizeof(out)); #else memcpy(color, in, sizeof(UINT16)); @@ -77,7 +77,7 @@ get_pixel_16B(Imaging im, int x, int y, void *color) { #ifdef WORDS_BIGENDIAN memcpy(color, in, sizeof(UINT16)); #else - UINT16 out = in[1] + (in[0] << 8); + UINT16 out = in[1] + ((UINT16)in[0] << 8); memcpy(color, &out, sizeof(out)); #endif } @@ -91,7 +91,8 @@ static void get_pixel_32L(Imaging im, int x, int y, void *color) { UINT8 *in = (UINT8 *)&im->image[y][x * 4]; #ifdef WORDS_BIGENDIAN - INT32 out = in[0] + (in[1] << 8) + (in[2] << 16) + (in[3] << 24); + INT32 out = + in[0] + ((INT32)in[1] << 8) + ((INT32)in[2] << 16) + ((INT32)in[3] << 24); memcpy(color, &out, sizeof(out)); #else memcpy(color, in, sizeof(INT32)); @@ -104,7 +105,8 @@ get_pixel_32B(Imaging im, int x, int y, void *color) { #ifdef WORDS_BIGENDIAN memcpy(color, in, sizeof(INT32)); #else - INT32 out = in[3] + (in[2] << 8) + (in[1] << 16) + (in[0] << 24); + INT32 out = + in[3] + ((INT32)in[2] << 8) + ((INT32)in[1] << 16) + ((INT32)in[0] << 24); memcpy(color, &out, sizeof(out)); #endif } diff --git a/src/libImaging/BcnEncode.c b/src/libImaging/BcnEncode.c index 7a5072dde..861ae1c26 100644 --- a/src/libImaging/BcnEncode.c +++ b/src/libImaging/BcnEncode.c @@ -36,10 +36,9 @@ decode_565(UINT16 x) { static UINT16 encode_565(rgba item) { - UINT8 r, g, b; - r = item.color[0] >> (8 - 5); - g = item.color[1] >> (8 - 6); - b = item.color[2] >> (8 - 5); + UINT16 r = item.color[0] >> (8 - 5); + UINT8 g = item.color[1] >> (8 - 6); + UINT8 b = item.color[2] >> (8 - 5); return (r << (5 + 6)) | (g << 5) | b; } @@ -157,7 +156,8 @@ encode_bc1_color(Imaging im, ImagingCodecState state, UINT8 *dst, int separate_a static void encode_bc2_block(Imaging im, ImagingCodecState state, UINT8 *dst) { int i, j; - UINT8 block[16], current_alpha; + UINT8 block[16]; + UINT32 current_alpha; for (i = 0; i < 4; i++) { for (j = 0; j < 4; j++) { int x = state->x + i * im->pixelsize; diff --git a/src/libImaging/FliDecode.c b/src/libImaging/FliDecode.c index 130ecb7f7..44994823e 100644 --- a/src/libImaging/FliDecode.c +++ b/src/libImaging/FliDecode.c @@ -16,9 +16,11 @@ #include "Imaging.h" -#define I16(ptr) ((ptr)[0] + ((ptr)[1] << 8)) +#define I16(ptr) ((ptr)[0] + ((int)(ptr)[1] << 8)) -#define I32(ptr) ((ptr)[0] + ((ptr)[1] << 8) + ((ptr)[2] << 16) + ((ptr)[3] << 24)) +#define I32(ptr) \ + ((ptr)[0] + ((INT32)(ptr)[1] << 8) + ((INT32)(ptr)[2] << 16) + \ + ((INT32)(ptr)[3] << 24)) #define ERR_IF_DATA_OOB(offset) \ if ((data + (offset)) > ptr + bytes) { \ diff --git a/src/libImaging/GetBBox.c b/src/libImaging/GetBBox.c index d430893dd..e50bd7140 100644 --- a/src/libImaging/GetBBox.c +++ b/src/libImaging/GetBBox.c @@ -212,7 +212,7 @@ ImagingGetExtrema(Imaging im, void *extrema) { UINT16 v; UINT8 *pixel = *im->image8; #ifdef WORDS_BIGENDIAN - v = pixel[0] + (pixel[1] << 8); + v = pixel[0] + ((UINT16)pixel[1] << 8); #else memcpy(&v, pixel, sizeof(v)); #endif @@ -221,7 +221,7 @@ ImagingGetExtrema(Imaging im, void *extrema) { for (x = 0; x < im->xsize; x++) { pixel = (UINT8 *)im->image[y] + x * sizeof(v); #ifdef WORDS_BIGENDIAN - v = pixel[0] + (pixel[1] << 8); + v = pixel[0] + ((UINT16)pixel[1] << 8); #else memcpy(&v, pixel, sizeof(v)); #endif From 2b4c7c011eef28401828f979f1005ad80bbf8709 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 10 Oct 2025 11:55:45 +0100 Subject: [PATCH 238/309] Typing import suggestion Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b9f5cfe06..3a72a0742 100644 --- a/setup.py +++ b/setup.py @@ -16,12 +16,12 @@ import subprocess import sys import warnings from collections.abc import Iterator -from typing import TYPE_CHECKING from pybind11.setup_helpers import ParallelCompile from setuptools import Extension, setup from setuptools.command.build_ext import build_ext +TYPE_CHECKING = False if TYPE_CHECKING: from setuptools import _BuildInfo From bd6e70fccdf14f9a2d0ff4201c4c82b3cb84572f Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 10 Oct 2025 12:31:15 +0100 Subject: [PATCH 239/309] Check against mode 1 instead of input mode for Chops.c --- src/libImaging/Chops.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libImaging/Chops.c b/src/libImaging/Chops.c index 331f2dfe6..3ce8a0903 100644 --- a/src/libImaging/Chops.c +++ b/src/libImaging/Chops.c @@ -64,7 +64,8 @@ create(Imaging im1, Imaging im2, const ModeID mode) { int xsize, ysize; if (!im1 || !im2 || im1->type != IMAGING_TYPE_UINT8 || - (mode != IMAGING_MODE_UNKNOWN && (im1->mode != mode || im2->mode != mode))) { + (mode != IMAGING_MODE_UNKNOWN && + (im1->mode != IMAGING_MODE_1 || im2->mode != IMAGING_MODE_1))) { return (Imaging)ImagingError_ModeError(); } if (im1->type != im2->type || im1->bands != im2->bands) { From 5d3086b01ff356c123bd2d9ea929a0bee030c08c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 10 Oct 2025 22:44:09 +1100 Subject: [PATCH 240/309] Removed unused access for I;32L and I;32B --- src/libImaging/Access.c | 45 ++--------------------------------------- 1 file changed, 2 insertions(+), 43 deletions(-) diff --git a/src/libImaging/Access.c b/src/libImaging/Access.c index 65c832cbe..00aaaa405 100644 --- a/src/libImaging/Access.c +++ b/src/libImaging/Access.c @@ -12,8 +12,8 @@ #include "Imaging.h" /* use make_hash.py from the pillow-scripts repository to calculate these values */ -#define ACCESS_TABLE_SIZE 35 -#define ACCESS_TABLE_HASH 8940 +#define ACCESS_TABLE_SIZE 23 +#define ACCESS_TABLE_HASH 28677 static struct ImagingAccessInstance access_table[ACCESS_TABLE_SIZE]; @@ -87,30 +87,6 @@ get_pixel_32(Imaging im, int x, int y, void *color) { memcpy(color, &im->image32[y][x], sizeof(INT32)); } -static void -get_pixel_32L(Imaging im, int x, int y, void *color) { - UINT8 *in = (UINT8 *)&im->image[y][x * 4]; -#ifdef WORDS_BIGENDIAN - INT32 out = - in[0] + ((INT32)in[1] << 8) + ((INT32)in[2] << 16) + ((INT32)in[3] << 24); - memcpy(color, &out, sizeof(out)); -#else - memcpy(color, in, sizeof(INT32)); -#endif -} - -static void -get_pixel_32B(Imaging im, int x, int y, void *color) { - UINT8 *in = (UINT8 *)&im->image[y][x * 4]; -#ifdef WORDS_BIGENDIAN - memcpy(color, in, sizeof(INT32)); -#else - INT32 out = - in[3] + ((INT32)in[2] << 8) + ((INT32)in[1] << 16) + ((INT32)in[0] << 24); - memcpy(color, &out, sizeof(out)); -#endif -} - /* store individual pixel */ static void @@ -131,21 +107,6 @@ put_pixel_16B(Imaging im, int x, int y, const void *color) { out[1] = in[0]; } -static void -put_pixel_32L(Imaging im, int x, int y, const void *color) { - memcpy(&im->image8[y][x * 4], color, 4); -} - -static void -put_pixel_32B(Imaging im, int x, int y, const void *color) { - const char *in = color; - UINT8 *out = (UINT8 *)&im->image8[y][x * 4]; - out[0] = in[3]; - out[1] = in[2]; - out[2] = in[1]; - out[3] = in[0]; -} - static void put_pixel_32(Imaging im, int x, int y, const void *color) { memcpy(&im->image32[y][x], color, sizeof(INT32)); @@ -174,8 +135,6 @@ ImagingAccessInit(void) { #else ADD("I;16N", get_pixel_16L, put_pixel_16L); #endif - ADD("I;32L", get_pixel_32L, put_pixel_32L); - ADD("I;32B", get_pixel_32B, put_pixel_32B); ADD("F", get_pixel_32, put_pixel_32); ADD("P", get_pixel_8, put_pixel_8); ADD("PA", get_pixel_32_2bands, put_pixel_32); From 324258ca7a1836e0fb42fa84038619a4f3f8abd8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 22 Jul 2025 22:59:15 +1000 Subject: [PATCH 241/309] Split parametrization --- Tests/test_arro3.py | 23 +++++++++-------------- Tests/test_nanoarrow.py | 23 +++++++++-------------- 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/Tests/test_arro3.py b/Tests/test_arro3.py index a7c755fc2..92493d9b0 100644 --- a/Tests/test_arro3.py +++ b/Tests/test_arro3.py @@ -225,23 +225,18 @@ def test_fromarray(mode: str, data_tp: DataShape, mask: list[int] | None) -> Non @pytest.mark.parametrize( - "mode, data_tp, mask", + "mode, mask", ( - ("LA", UINT32, [0, 3]), - ("RGB", UINT32, [0, 1, 2]), - ("RGBA", UINT32, None), - ("CMYK", UINT32, None), - ("YCbCr", UINT32, [0, 1, 2]), - ("HSV", UINT32, [0, 1, 2]), - ("LA", INT32, [0, 3]), - ("RGB", INT32, [0, 1, 2]), - ("RGBA", INT32, None), - ("CMYK", INT32, None), - ("YCbCr", INT32, [0, 1, 2]), - ("HSV", INT32, [0, 1, 2]), + ("LA", [0, 3]), + ("RGB", [0, 1, 2]), + ("RGBA", None), + ("CMYK", None), + ("YCbCr", [0, 1, 2]), + ("HSV", [0, 1, 2]), ), ) -def test_from_int32array(mode: str, data_tp: DataShape, mask: list[int] | None) -> None: +@pytest.mark.parametrize("data_tp", (UINT32, INT32)) +def test_from_int32array(mode: str, mask: list[int] | None, data_tp: DataShape) -> None: (dtype, elt, elts_per_pixel) = data_tp ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1] diff --git a/Tests/test_nanoarrow.py b/Tests/test_nanoarrow.py index b08333ae9..3a839a015 100644 --- a/Tests/test_nanoarrow.py +++ b/Tests/test_nanoarrow.py @@ -232,23 +232,18 @@ def test_fromarray(mode: str, data_tp: DataShape, mask: list[int] | None) -> Non @pytest.mark.parametrize( - "mode, data_tp, mask", + "mode, mask", ( - ("LA", UINT32, [0, 3]), - ("RGB", UINT32, [0, 1, 2]), - ("RGBA", UINT32, None), - ("CMYK", UINT32, None), - ("YCbCr", UINT32, [0, 1, 2]), - ("HSV", UINT32, [0, 1, 2]), - ("LA", INT32, [0, 3]), - ("RGB", INT32, [0, 1, 2]), - ("RGBA", INT32, None), - ("CMYK", INT32, None), - ("YCbCr", INT32, [0, 1, 2]), - ("HSV", INT32, [0, 1, 2]), + ("LA", [0, 3]), + ("RGB", [0, 1, 2]), + ("RGBA", None), + ("CMYK", None), + ("YCbCr", [0, 1, 2]), + ("HSV", [0, 1, 2]), ), ) -def test_from_int32array(mode: str, data_tp: DataShape, mask: list[int] | None) -> None: +@pytest.mark.parametrize("data_tp", (UINT32, INT32)) +def test_from_int32array(mode: str, mask: list[int] | None, data_tp: DataShape) -> None: (dtype, elt, elts_per_pixel) = data_tp ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1] From 13e4e587e65fe652a1392244e746715f8da740d7 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 10 Oct 2025 15:34:11 +0100 Subject: [PATCH 242/309] added import-not-found ignores, removed call-overload ignores --- Tests/test_arro3.py | 17 ++++++++++------- Tests/test_nanoarrow.py | 16 ++++++++-------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/Tests/test_arro3.py b/Tests/test_arro3.py index 92493d9b0..a161a7a96 100644 --- a/Tests/test_arro3.py +++ b/Tests/test_arro3.py @@ -16,7 +16,9 @@ from .helper import ( TYPE_CHECKING = False if TYPE_CHECKING: - from arro3 import compute + from arro3 import compute # type: ignore [import-not-found] + + # type: ignore [import-not-found] from arro3.core import Array, DataType, Field, fixed_size_list_array else: arro3 = pytest.importorskip("arro3", reason="Arro3 not installed") @@ -106,7 +108,7 @@ def test_to_array(mode: str, dtype: DataType, mask: list[int] | None) -> None: img = img.crop((3, 0, 124, 127)) assert img.size == (121, 127) - arr = Array(img) # type: ignore[call-overload] + arr = Array(img) _test_img_equals_pyarray(img, arr, mask) assert arr.type == dtype @@ -123,8 +125,8 @@ def test_lifetime() -> None: img = hopper("L") - arr_1 = Array(img) # type: ignore[call-overload] - arr_2 = Array(img) # type: ignore[call-overload] + arr_1 = Array(img) + arr_2 = Array(img) del img @@ -141,8 +143,8 @@ def test_lifetime2() -> None: img = hopper("L") - arr_1 = Array(img) # type: ignore[call-overload] - arr_2 = Array(img) # type: ignore[call-overload] + arr_1 = Array(img) + arr_2 = Array(img) assert compute.sum(arr_1).as_py() > 0 del arr_1 @@ -261,8 +263,9 @@ def test_from_int32array(mode: str, mask: list[int] | None, data_tp: DataShape) def test_image_metadata(mode: str, metadata: list[str]) -> None: img = hopper(mode) - arr = Array(img) # type: ignore[call-overload] + arr = Array(img) + assert arr.type.value_field assert arr.type.value_field.metadata assert arr.type.value_field.metadata[b"image"] diff --git a/Tests/test_nanoarrow.py b/Tests/test_nanoarrow.py index 3a839a015..fe7505134 100644 --- a/Tests/test_nanoarrow.py +++ b/Tests/test_nanoarrow.py @@ -16,7 +16,7 @@ from .helper import ( TYPE_CHECKING = False if TYPE_CHECKING: - import nanoarrow + import nanoarrow # type: ignore [import-untyped] else: nanoarrow = pytest.importorskip("nanoarrow", reason="Nanoarrow not installed") @@ -105,7 +105,7 @@ def test_to_array(mode: str, dtype: nanoarrow, mask: list[int] | None) -> None: img = img.crop((3, 0, 124, 127)) assert img.size == (121, 127) - arr = nanoarrow.Array(img) # type: ignore[call-overload] + arr = nanoarrow.Array(img) _test_img_equals_pyarray(img, arr, mask) assert arr.schema.type == dtype.type assert arr.schema.nullable == dtype.nullable @@ -123,8 +123,8 @@ def test_lifetime() -> None: img = hopper("L") - arr_1 = nanoarrow.Array(img) # type: ignore[call-overload] - arr_2 = nanoarrow.Array(img) # type: ignore[call-overload] + arr_1 = nanoarrow.Array(img) + arr_2 = nanoarrow.Array(img) del img @@ -141,8 +141,8 @@ def test_lifetime2() -> None: img = hopper("L") - arr_1 = nanoarrow.Array(img) # type: ignore[call-overload] - arr_2 = nanoarrow.Array(img) # type: ignore[call-overload] + arr_1 = nanoarrow.Array(img) + arr_2 = nanoarrow.Array(img) assert sum(arr_1.iter_py()) > 0 del arr_1 @@ -270,7 +270,7 @@ def test_from_int32array(mode: str, mask: list[int] | None, data_tp: DataShape) def test_image_nested_metadata(mode: str, metadata: list[str]) -> None: img = hopper(mode) - arr = nanoarrow.Array(img) # type: ignore[call-overload] + arr = nanoarrow.Array(img) assert arr.schema.value_type.metadata assert arr.schema.value_type.metadata[b"image"] @@ -294,7 +294,7 @@ def test_image_nested_metadata(mode: str, metadata: list[str]) -> None: def test_image_flat_metadata(mode: str, metadata: list[str]) -> None: img = hopper(mode) - arr = nanoarrow.Array(img) # type: ignore[call-overload] + arr = nanoarrow.Array(img) assert arr.schema.metadata assert arr.schema.metadata[b"image"] From b4fe17cecf0f5c6bce2e8c637a7e3c40601ec665 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 10 Oct 2025 15:39:47 +0100 Subject: [PATCH 243/309] More typey lint --- Tests/test_arro3.py | 9 ++++++--- Tests/test_nanoarrow.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Tests/test_arro3.py b/Tests/test_arro3.py index a161a7a96..9c70daf5a 100644 --- a/Tests/test_arro3.py +++ b/Tests/test_arro3.py @@ -17,9 +17,12 @@ from .helper import ( TYPE_CHECKING = False if TYPE_CHECKING: from arro3 import compute # type: ignore [import-not-found] - - # type: ignore [import-not-found] - from arro3.core import Array, DataType, Field, fixed_size_list_array + from arro3.core import ( # type: ignore [import-not-found] + Array, + DataType, + Field, + fixed_size_list_array, + ) else: arro3 = pytest.importorskip("arro3", reason="Arro3 not installed") from arro3 import compute diff --git a/Tests/test_nanoarrow.py b/Tests/test_nanoarrow.py index fe7505134..90293130e 100644 --- a/Tests/test_nanoarrow.py +++ b/Tests/test_nanoarrow.py @@ -16,7 +16,7 @@ from .helper import ( TYPE_CHECKING = False if TYPE_CHECKING: - import nanoarrow # type: ignore [import-untyped] + import nanoarrow # type: ignore [import-not-found] else: nanoarrow = pytest.importorskip("nanoarrow", reason="Nanoarrow not installed") From 76ab80f10b50dd9abc5ef4fc4a0e537dfb1f8505 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 28 Jun 2025 14:08:15 +1000 Subject: [PATCH 244/309] Assert getpixel returns tuple --- Tests/test_file_avif.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/test_file_avif.py b/Tests/test_file_avif.py index 505d2e596..727191153 100644 --- a/Tests/test_file_avif.py +++ b/Tests/test_file_avif.py @@ -221,6 +221,7 @@ class TestFileAvif: def test_background_from_gif(self, tmp_path: Path) -> None: with Image.open("Tests/images/chi.gif") as im: original_value = im.convert("RGB").getpixel((1, 1)) + assert isinstance(original_value, tuple) # Save as AVIF out_avif = tmp_path / "temp.avif" @@ -233,6 +234,7 @@ class TestFileAvif: with Image.open(out_gif) as reread: reread_value = reread.convert("RGB").getpixel((1, 1)) + assert isinstance(reread_value, tuple) difference = sum([abs(original_value[i] - reread_value[i]) for i in range(3)]) assert difference <= 6 From 755ebb8307f887a69a75c0fe024eaed963c411bf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 28 Jun 2025 14:19:27 +1000 Subject: [PATCH 245/309] Assert getcolors does not return None --- Tests/test_file_png.py | 24 ++++++++++++++++++------ Tests/test_file_tga.py | 8 ++++++-- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index ce6552354..dc1077fed 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -229,7 +229,9 @@ class TestFilePng: assert_image(im, "RGBA", (162, 150)) # image has 124 unique alpha values - assert len(im.getchannel("A").getcolors()) == 124 + colors = im.getchannel("A").getcolors() + assert colors is not None + assert len(colors) == 124 def test_load_transparent_rgb(self) -> None: test_file = "Tests/images/rgb_trns.png" @@ -241,7 +243,9 @@ class TestFilePng: assert_image(im, "RGBA", (64, 64)) # image has 876 transparent pixels - assert im.getchannel("A").getcolors()[0][0] == 876 + colors = im.getchannel("A").getcolors() + assert colors is not None + assert colors[0][0] == 876 def test_save_p_transparent_palette(self, tmp_path: Path) -> None: in_file = "Tests/images/pil123p.png" @@ -262,7 +266,9 @@ class TestFilePng: assert_image(im, "RGBA", (162, 150)) # image has 124 unique alpha values - assert len(im.getchannel("A").getcolors()) == 124 + colors = im.getchannel("A").getcolors() + assert colors is not None + assert len(colors) == 124 def test_save_p_single_transparency(self, tmp_path: Path) -> None: in_file = "Tests/images/p_trns_single.png" @@ -285,7 +291,9 @@ class TestFilePng: assert im.getpixel((31, 31)) == (0, 255, 52, 0) # image has 876 transparent pixels - assert im.getchannel("A").getcolors()[0][0] == 876 + colors = im.getchannel("A").getcolors() + assert colors is not None + assert colors[0][0] == 876 def test_save_p_transparent_black(self, tmp_path: Path) -> None: # check if solid black image with full transparency @@ -313,7 +321,9 @@ class TestFilePng: assert im.info["transparency"] == 255 im_rgba = im.convert("RGBA") - assert im_rgba.getchannel("A").getcolors()[0][0] == num_transparent + colors = im_rgba.getchannel("A").getcolors() + assert colors is not None + assert colors[0][0] == num_transparent test_file = tmp_path / "temp.png" im.save(test_file) @@ -324,7 +334,9 @@ class TestFilePng: assert_image_equal(im, test_im) test_im_rgba = test_im.convert("RGBA") - assert test_im_rgba.getchannel("A").getcolors()[0][0] == num_transparent + colors = test_im_rgba.getchannel("A").getcolors() + assert colors is not None + assert colors[0][0] == num_transparent def test_save_rgb_single_transparency(self, tmp_path: Path) -> None: in_file = "Tests/images/caption_6_33_22.png" diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index bd39de2e1..bb8d3eefc 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -274,13 +274,17 @@ def test_save_l_transparency(tmp_path: Path) -> None: in_file = "Tests/images/la.tga" with Image.open(in_file) as im: assert im.mode == "LA" - assert im.getchannel("A").getcolors()[0][0] == num_transparent + colors = im.getchannel("A").getcolors() + assert colors is not None + assert colors[0][0] == num_transparent out = tmp_path / "temp.tga" im.save(out) with Image.open(out) as test_im: assert test_im.mode == "LA" - assert test_im.getchannel("A").getcolors()[0][0] == num_transparent + colors = test_im.getchannel("A").getcolors() + assert colors is not None + assert colors[0][0] == num_transparent assert_image_equal(im, test_im) From a66d0d1f05a7a07904961623037ffe4de11fa4f2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 11 Oct 2025 14:48:13 +1100 Subject: [PATCH 246/309] Assert getpalette does not return None --- Tests/test_image_putpalette.py | 1 + Tests/test_imagesequence.py | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Tests/test_image_putpalette.py b/Tests/test_image_putpalette.py index f2c447f71..661764b60 100644 --- a/Tests/test_image_putpalette.py +++ b/Tests/test_image_putpalette.py @@ -62,6 +62,7 @@ def test_putpalette_with_alpha_values() -> None: expected = im.convert("RGBA") palette = im.getpalette() + assert palette is not None transparency = im.info.pop("transparency") palette_with_alpha_values = [] diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py index 7b9ac80bc..32da22e04 100644 --- a/Tests/test_imagesequence.py +++ b/Tests/test_imagesequence.py @@ -76,9 +76,14 @@ def test_consecutive() -> None: def test_palette_mmap() -> None: # Using mmap in ImageFile can require to reload the palette. with Image.open("Tests/images/multipage-mmap.tiff") as im: - color1 = im.getpalette()[:3] + palette = im.getpalette() + assert palette is not None + color1 = palette[:3] im.seek(0) - color2 = im.getpalette()[:3] + + palette = im.getpalette() + assert palette is not None + color2 = palette[:3] assert color1 == color2 From 52413cf0dce42c5eab96209a0a271e125c6cce8c Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 11 Oct 2025 08:25:07 +0100 Subject: [PATCH 247/309] Update Tests/test_arro3.py Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- Tests/test_arro3.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/Tests/test_arro3.py b/Tests/test_arro3.py index 9c70daf5a..60955cfdb 100644 --- a/Tests/test_arro3.py +++ b/Tests/test_arro3.py @@ -116,9 +116,6 @@ def test_to_array(mode: str, dtype: DataType, mask: list[int] | None) -> None: assert arr.type == dtype reloaded = Image.fromarrow(arr, mode, img.size) - - assert reloaded - assert_image_equal(img, reloaded) From fbdf607c7f85731cc66db42938f1cee580303023 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 11 Oct 2025 17:13:22 +0300 Subject: [PATCH 248/309] Wheels CI: Check number of expected dists (#9239) Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> Co-authored-by: Andrew Murray --- .github/workflows/wheels.yml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index f1c851bc7..9de8a440f 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -39,6 +39,7 @@ concurrency: cancel-in-progress: true env: + EXPECTED_DISTS: 91 FORCE_COLOR: 1 jobs: @@ -250,9 +251,27 @@ jobs: name: dist-sdist path: dist/*.tar.gz + count-dists: + needs: [build-native-wheels, windows, sdist] + runs-on: ubuntu-latest + name: Count dists + steps: + - uses: actions/download-artifact@v5 + with: + pattern: dist-* + path: dist + merge-multiple: true + - name: "What did we get?" + run: | + ls -alR + echo "Number of dists, should be $EXPECTED_DISTS:" + files=$(ls dist 2>/dev/null | wc -l) + echo $files + [ "$files" -eq $EXPECTED_DISTS ] || exit 1 + scientific-python-nightly-wheels-publish: if: github.repository_owner == 'python-pillow' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') - needs: [build-native-wheels, windows] + needs: count-dists runs-on: ubuntu-latest name: Upload wheels to scientific-python-nightly-wheels steps: @@ -269,7 +288,7 @@ jobs: pypi-publish: if: github.repository_owner == 'python-pillow' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags') - needs: [build-native-wheels, windows, sdist] + needs: count-dists runs-on: ubuntu-latest name: Upload release to PyPI environment: From c874256132f67543e6928be9e97ad3c4bb806d3f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 12 Oct 2025 07:08:52 +1100 Subject: [PATCH 249/309] Support saving variable length rational TIFF tags by default --- Tests/test_file_libtiff.py | 9 +++++++++ src/PIL/TiffImagePlugin.py | 10 +++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 18bcfaa20..4908496cf 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -367,6 +367,15 @@ class TestFileLibTiff(LibTiffTestCase): assert isinstance(reloaded, TiffImagePlugin.TiffImageFile) assert reloaded.tag_v2[318] == pytest.approx((0.3127, 0.3289)) + # Save tag by default + out = tmp_path / "temp2.tif" + with Image.open("Tests/images/rdf.tif") as im: + im.save(out) + + with Image.open(out) as reloaded: + assert isinstance(reloaded, TiffImagePlugin.TiffImageFile) + assert reloaded.tag_v2[318] == pytest.approx((0.3127, 0.3289999)) + def test_xmlpacket_tag( self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index c1741284b..de2ce066e 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -252,6 +252,7 @@ OPEN_INFO = { (II, 3, (1,), 1, (8,), ()): ("P", "P"), (MM, 3, (1,), 1, (8,), ()): ("P", "P"), (II, 3, (1,), 1, (8, 8), (0,)): ("P", "PX"), + (MM, 3, (1,), 1, (8, 8), (0,)): ("P", "PX"), (II, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"), (MM, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"), (II, 3, (1,), 2, (8,), ()): ("P", "P;R"), @@ -1177,6 +1178,7 @@ class TiffImageFile(ImageFile.ImageFile): """Open the first image in a TIFF file""" # Header + assert self.fp is not None ifh = self.fp.read(8) if ifh[2] == 43: ifh += self.fp.read(8) @@ -1343,6 +1345,7 @@ class TiffImageFile(ImageFile.ImageFile): # To be nice on memory footprint, if there's a # file descriptor, use that instead of reading # into a string in python. + assert self.fp is not None try: fp = hasattr(self.fp, "fileno") and self.fp.fileno() # flush the file descriptor, prevents error on pypy 2.4+ @@ -1936,9 +1939,10 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: types[tag] = TiffTags.LONG8 elif tag in ifd.tagtype: types[tag] = ifd.tagtype[tag] - elif not (isinstance(value, (int, float, str, bytes))): - continue - else: + elif isinstance(value, (int, float, str, bytes)) or ( + isinstance(value, tuple) + and all(isinstance(v, (int, float, IFDRational)) for v in value) + ): type = TiffTags.lookup(tag).type if type: types[tag] = type From e36bf768c5ae17a64e86433f7fb2bd0d67fb1a91 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 12 Oct 2025 15:58:22 +1100 Subject: [PATCH 250/309] Added four private SGI tags --- src/PIL/TiffTags.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py index 86adaa458..761aa3f6b 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -203,6 +203,11 @@ _tags_v2: dict[int, tuple[str, int, int] | tuple[str, int, int, dict[str, int]]] 531: ("YCbCrPositioning", SHORT, 1), 532: ("ReferenceBlackWhite", RATIONAL, 6), 700: ("XMP", BYTE, 0), + # Four private SGI tags + 32995: ("Matteing", SHORT, 1), + 32996: ("DataType", SHORT, 0), + 32997: ("ImageDepth", LONG, 1), + 32998: ("TileDepth", LONG, 1), 33432: ("Copyright", ASCII, 1), 33723: ("IptcNaaInfo", UNDEFINED, 1), 34377: ("PhotoshopInfo", BYTE, 0), From 1b2121c7a1c17d998af52687cdc13153e8f3622f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 12 Oct 2025 09:35:05 +0000 Subject: [PATCH 251/309] Update dependency cibuildwheel to v3.2.1 --- .ci/requirements-cibw.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/requirements-cibw.txt b/.ci/requirements-cibw.txt index 8ec7262c0..56517374f 100644 --- a/.ci/requirements-cibw.txt +++ b/.ci/requirements-cibw.txt @@ -1 +1 @@ -cibuildwheel==3.2.0 +cibuildwheel==3.2.1 From 416fb810742c597280d627323d0e558e8a7f713c Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Sun, 12 Oct 2025 21:19:34 +1100 Subject: [PATCH 252/309] Removed shebang lines and executable flags (#9179) Co-authored-by: Andrew Murray --- Tests/createfontdatachunk.py | 1 - checks/32bit_segfault_check.py | 1 - checks/check_imaging_leaks.py | 1 - 3 files changed, 3 deletions(-) mode change 100755 => 100644 Tests/createfontdatachunk.py mode change 100755 => 100644 checks/32bit_segfault_check.py mode change 100755 => 100644 checks/check_imaging_leaks.py diff --git a/Tests/createfontdatachunk.py b/Tests/createfontdatachunk.py old mode 100755 new mode 100644 index 41c76f87e..0a3fdb809 --- a/Tests/createfontdatachunk.py +++ b/Tests/createfontdatachunk.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 from __future__ import annotations import base64 diff --git a/checks/32bit_segfault_check.py b/checks/32bit_segfault_check.py old mode 100755 new mode 100644 index 06ed2ed2f..e277bc10a --- a/checks/32bit_segfault_check.py +++ b/checks/32bit_segfault_check.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 from __future__ import annotations import sys diff --git a/checks/check_imaging_leaks.py b/checks/check_imaging_leaks.py old mode 100755 new mode 100644 index e9f202f3d..65090b6b6 --- a/checks/check_imaging_leaks.py +++ b/checks/check_imaging_leaks.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 from __future__ import annotations import sys From 48922449080e9d9fa7ddcb030bd7d0f242a6bd49 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 13 Oct 2025 00:31:32 +0300 Subject: [PATCH 253/309] Update 12.0.0 release notes (#9247) Co-authored-by: Andrew Murray --- docs/releasenotes/12.0.0.rst | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/docs/releasenotes/12.0.0.rst b/docs/releasenotes/12.0.0.rst index de9d6dffd..fb5733944 100644 --- a/docs/releasenotes/12.0.0.rst +++ b/docs/releasenotes/12.0.0.rst @@ -1,19 +1,6 @@ 12.0.0 ------ -Security -======== - -TODO -^^^^ - -TODO - -:cve:`YYYY-XXXXX`: TODO -^^^^^^^^^^^^^^^^^^^^^^^ - -TODO - Backwards incompatible changes ============================== @@ -132,18 +119,10 @@ Pillow 13 (2026-10-15). They have been set to ``None`` since Pillow 2.3.0. API changes =========== -TODO -^^^^ +Image.alpha_composite: LA images +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -TODO - -API additions -============= - -TODO -^^^^ - -TODO +:py:meth:`~PIL.Image.alpha_composite` can now use LA images as well as RGBA. Other changes ============= From c60b36d0a738fcfe0fc16ada872564426b5b0748 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Mon, 13 Oct 2025 23:24:04 +1100 Subject: [PATCH 254/309] Run sdist when scheduled, but do not upload to scientific-python-nightly-wheels index (#9248) --- .github/workflows/wheels.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 9de8a440f..3017e36a7 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -232,7 +232,7 @@ jobs: path: winbuild\build\bin\fribidi* sdist: - if: github.event_name != 'schedule' + if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow' runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -277,7 +277,7 @@ jobs: steps: - uses: actions/download-artifact@v5 with: - pattern: dist-* + pattern: dist-*.whl path: dist merge-multiple: true - name: Upload wheels to scientific-python-nightly-wheels From 014f4212214025bdc417e168a7bb415bbdf0f669 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 14 Oct 2025 08:48:22 +1100 Subject: [PATCH 255/309] Removed assert --- Tests/test_nanoarrow.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/Tests/test_nanoarrow.py b/Tests/test_nanoarrow.py index 90293130e..69980e719 100644 --- a/Tests/test_nanoarrow.py +++ b/Tests/test_nanoarrow.py @@ -111,9 +111,6 @@ def test_to_array(mode: str, dtype: nanoarrow, mask: list[int] | None) -> None: assert arr.schema.nullable == dtype.nullable reloaded = Image.fromarrow(arr, mode, img.size) - - assert reloaded - assert_image_equal(img, reloaded) From 55f3e63b2251c0ba06b238c8c6bb540ff1c22d46 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 14 Oct 2025 18:25:56 +1100 Subject: [PATCH 256/309] Revert "Use macos-14 for iOS arm64 simulator (#9161)" This reverts commit c214ad8c8d40c785c8aca6226b5033085f24cb3d. --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 3017e36a7..8f717a627 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -100,7 +100,7 @@ jobs: cibw_arch: arm64_iphoneos - name: "iOS arm64 simulator" platform: ios - os: macos-14 + os: macos-latest cibw_arch: arm64_iphonesimulator - name: "iOS x86_64 simulator" platform: ios From 2caa504991a2713ddee2a161e6b9f8416b7225ec Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 14 Oct 2025 18:57:26 +1100 Subject: [PATCH 257/309] ImagingHistogramInstance can use two bands --- src/libImaging/Imaging.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index bfe67d462..5d85ea73e 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -148,7 +148,7 @@ struct ImagingAccessInstance { struct ImagingHistogramInstance { /* Format */ char mode[IMAGING_MODE_LENGTH]; /* Band names (of corresponding source image) */ - int bands; /* Number of bands (1, 3, or 4) */ + int bands; /* Number of bands (1, 2, 3, or 4) */ /* Data */ long *histogram; /* Histogram (bands*256 longs) */ From a59100005548a8dd3df6b201acfd112dcf19bb22 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 14 Oct 2025 20:31:42 +1100 Subject: [PATCH 258/309] Removed BGR;24 and BGR;32 --- src/_imaging.c | 6 ------ src/libImaging/Mode.c | 3 --- src/libImaging/Mode.h | 3 --- 3 files changed, 12 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index 8412124c1..999b8a30d 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -701,12 +701,6 @@ getink(PyObject *color, Imaging im, char *ink) { ink[1] = (UINT8)(v >> 8); ink[2] = ink[3] = 0; return ink; - } else if (im->mode == IMAGING_MODE_BGR_24) { - ink[0] = (UINT8)b; - ink[1] = (UINT8)g; - ink[2] = (UINT8)r; - ink[3] = 0; - return ink; } } } diff --git a/src/libImaging/Mode.c b/src/libImaging/Mode.c index 8222c585b..78ea5aa70 100644 --- a/src/libImaging/Mode.c +++ b/src/libImaging/Mode.c @@ -19,7 +19,6 @@ const ModeData MODES[] = { [IMAGING_MODE_RGBa] = {"RGBa"}, [IMAGING_MODE_YCbCr] = {"YCbCr"}, [IMAGING_MODE_BGR_15] = {"BGR;15"}, [IMAGING_MODE_BGR_16] = {"BGR;16"}, - [IMAGING_MODE_BGR_24] = {"BGR;24"}, [IMAGING_MODE_I_16] = {"I;16"}, [IMAGING_MODE_I_16L] = {"I;16L"}, [IMAGING_MODE_I_16B] = {"I;16B"}, [IMAGING_MODE_I_16N] = {"I;16N"}, @@ -74,8 +73,6 @@ const RawModeData RAWMODES[] = { [IMAGING_RAWMODE_BGR_15] = {"BGR;15"}, [IMAGING_RAWMODE_BGR_16] = {"BGR;16"}, - [IMAGING_RAWMODE_BGR_24] = {"BGR;24"}, - [IMAGING_RAWMODE_BGR_32] = {"BGR;32"}, [IMAGING_RAWMODE_I_16] = {"I;16"}, [IMAGING_RAWMODE_I_16L] = {"I;16L"}, diff --git a/src/libImaging/Mode.h b/src/libImaging/Mode.h index a20ad0cb6..b824becf6 100644 --- a/src/libImaging/Mode.h +++ b/src/libImaging/Mode.h @@ -23,7 +23,6 @@ typedef enum { IMAGING_MODE_BGR_15, IMAGING_MODE_BGR_16, - IMAGING_MODE_BGR_24, IMAGING_MODE_I_16, IMAGING_MODE_I_16L, @@ -66,8 +65,6 @@ typedef enum { // BGR modes. IMAGING_RAWMODE_BGR_15, IMAGING_RAWMODE_BGR_16, - IMAGING_RAWMODE_BGR_24, - IMAGING_RAWMODE_BGR_32, // I;* modes. IMAGING_RAWMODE_I_16, From 55a4901bba47ebeed65476e8637cf47cfe07a995 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 14 Oct 2025 20:34:03 +1100 Subject: [PATCH 259/309] Removed BGR;15 and BGR;16 modes --- src/_imaging.c | 18 ------------------ src/libImaging/Mode.c | 2 -- src/libImaging/Mode.h | 9 ++------- 3 files changed, 2 insertions(+), 27 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index 999b8a30d..9867fe571 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -684,24 +684,6 @@ getink(PyObject *color, Imaging im, char *ink) { } else if (!PyArg_ParseTuple(color, "iiL", &b, &g, &r)) { return NULL; } - if (im->mode == IMAGING_MODE_BGR_15) { - UINT16 v = ((((UINT16)r) << 7) & 0x7c00) + - ((((UINT16)g) << 2) & 0x03e0) + - ((((UINT16)b) >> 3) & 0x001f); - - ink[0] = (UINT8)v; - ink[1] = (UINT8)(v >> 8); - ink[2] = ink[3] = 0; - return ink; - } else if (im->mode == IMAGING_MODE_BGR_16) { - UINT16 v = ((((UINT16)r) << 8) & 0xf800) + - ((((UINT16)g) << 3) & 0x07e0) + - ((((UINT16)b) >> 3) & 0x001f); - ink[0] = (UINT8)v; - ink[1] = (UINT8)(v >> 8); - ink[2] = ink[3] = 0; - return ink; - } } } diff --git a/src/libImaging/Mode.c b/src/libImaging/Mode.c index 78ea5aa70..9a8558179 100644 --- a/src/libImaging/Mode.c +++ b/src/libImaging/Mode.c @@ -18,8 +18,6 @@ const ModeData MODES[] = { [IMAGING_MODE_RGBA] = {"RGBA"}, [IMAGING_MODE_RGBX] = {"RGBX"}, [IMAGING_MODE_RGBa] = {"RGBa"}, [IMAGING_MODE_YCbCr] = {"YCbCr"}, - [IMAGING_MODE_BGR_15] = {"BGR;15"}, [IMAGING_MODE_BGR_16] = {"BGR;16"}, - [IMAGING_MODE_I_16] = {"I;16"}, [IMAGING_MODE_I_16L] = {"I;16L"}, [IMAGING_MODE_I_16B] = {"I;16B"}, [IMAGING_MODE_I_16N] = {"I;16N"}, [IMAGING_MODE_I_32L] = {"I;32L"}, [IMAGING_MODE_I_32B] = {"I;32B"}, diff --git a/src/libImaging/Mode.h b/src/libImaging/Mode.h index b824becf6..a3eb3d86d 100644 --- a/src/libImaging/Mode.h +++ b/src/libImaging/Mode.h @@ -21,9 +21,6 @@ typedef enum { IMAGING_MODE_RGBa, IMAGING_MODE_YCbCr, - IMAGING_MODE_BGR_15, - IMAGING_MODE_BGR_16, - IMAGING_MODE_I_16, IMAGING_MODE_I_16L, IMAGING_MODE_I_16B, @@ -62,10 +59,6 @@ typedef enum { IMAGING_RAWMODE_RGBa, IMAGING_RAWMODE_YCbCr, - // BGR modes. - IMAGING_RAWMODE_BGR_15, - IMAGING_RAWMODE_BGR_16, - // I;* modes. IMAGING_RAWMODE_I_16, IMAGING_RAWMODE_I_16L, @@ -95,6 +88,8 @@ typedef enum { IMAGING_RAWMODE_BGRA_16L, IMAGING_RAWMODE_BGRX, IMAGING_RAWMODE_BGR_5, + IMAGING_RAWMODE_BGR_15, + IMAGING_RAWMODE_BGR_16, IMAGING_RAWMODE_BGRa, IMAGING_RAWMODE_BGXR, IMAGING_RAWMODE_B_16B, From 8de7e7763e0d7b117c25ae31ef2e19404bf35c17 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 14 Oct 2025 21:47:56 +1100 Subject: [PATCH 260/309] Corrected scientific-python-nightly-wheels pattern --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 3017e36a7..eef70f894 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -277,7 +277,7 @@ jobs: steps: - uses: actions/download-artifact@v5 with: - pattern: dist-*.whl + pattern: dist-!(sdist)* path: dist merge-multiple: true - name: Upload wheels to scientific-python-nightly-wheels From 9cb36a91d026115734a5dd46f408983035e6c3c4 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Wed, 15 Oct 2025 18:53:49 +1100 Subject: [PATCH 261/309] Upgrade from macos-13 (#9212) Co-authored-by: Andrew Murray --- .github/workflows/macos-install.sh | 13 +------------ .github/workflows/test.yml | 2 +- .github/workflows/wheels-dependencies.sh | 6 +++++- .github/workflows/wheels.yml | 8 ++++---- docs/installation/platform-support.rst | 6 +++--- 5 files changed, 14 insertions(+), 21 deletions(-) diff --git a/.github/workflows/macos-install.sh b/.github/workflows/macos-install.sh index 8060e0850..b114d4a23 100755 --- a/.github/workflows/macos-install.sh +++ b/.github/workflows/macos-install.sh @@ -2,21 +2,10 @@ set -e -if [[ "$ImageOS" == "macos13" ]]; then - brew uninstall gradle maven - - wget https://raw.githubusercontent.com/python-pillow/pillow-depends/main/freetype-2.14.1.tar.gz - tar -xvzf freetype-2.14.1.tar.gz - (cd freetype-2.14.1 \ - && ./configure \ - && make -j4 \ - && make install) -else - brew install freetype -fi brew install \ aom \ dav1d \ + freetype \ ghostscript \ jpeg-turbo \ libimagequant \ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8504e5c1e..b52000a27 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,7 +57,7 @@ jobs: - { python-version: "3.14t", disable-gil: true } - { python-version: "3.13t", disable-gil: true } # Intel - - { os: "macos-13", python-version: "3.10" } + - { os: "macos-15-intel", python-version: "3.10" } exclude: - { os: "macos-latest", python-version: "3.10" } diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 69c867b4d..7d6eb8681 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -271,7 +271,11 @@ function build { if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then yum remove -y zlib-devel fi - build_zlib_ng + if [[ -n "$IS_MACOS" ]]; then + CFLAGS="$CFLAGS -headerpad_max_install_names" build_zlib_ng + else + build_zlib_ng + fi build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto if [[ -n "$IS_MACOS" ]]; then diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index eef70f894..6dc8db7e9 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -53,19 +53,19 @@ jobs: include: - name: "macOS 10.10 x86_64" platform: macos - os: macos-13 + os: macos-15-intel cibw_arch: x86_64 build: "cp3{10,11}*" macosx_deployment_target: "10.10" - name: "macOS 10.13 x86_64" platform: macos - os: macos-13 + os: macos-15-intel cibw_arch: x86_64 build: "cp3{12,13}*" macosx_deployment_target: "10.13" - name: "macOS 10.15 x86_64" platform: macos - os: macos-13 + os: macos-15-intel cibw_arch: x86_64 build: "{cp314,pp3}*" macosx_deployment_target: "10.15" @@ -104,7 +104,7 @@ jobs: cibw_arch: arm64_iphonesimulator - name: "iOS x86_64 simulator" platform: ios - os: macos-13 + os: macos-15-intel cibw_arch: x86_64_iphonesimulator steps: - uses: actions/checkout@v5 diff --git a/docs/installation/platform-support.rst b/docs/installation/platform-support.rst index 186d9b96d..7999504fb 100644 --- a/docs/installation/platform-support.rst +++ b/docs/installation/platform-support.rst @@ -39,9 +39,9 @@ These platforms are built and tested for every change. +----------------------------------+----------------------------+---------------------+ | Gentoo | 3.12 | x86-64 | +----------------------------------+----------------------------+---------------------+ -| macOS 13 Ventura | 3.10 | x86-64 | -+----------------------------------+----------------------------+---------------------+ -| macOS 15 Sequoia | 3.11, 3.12, 3.13, 3.14 | arm64 | +| macOS 15 Sequoia | 3.10 | x86-64 | +| +----------------------------+---------------------+ +| | 3.11, 3.12, 3.13, 3.14, | arm64 | | | PyPy3 | | +----------------------------------+----------------------------+---------------------+ | Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 | From ef323ab7d71ab8ed02704e34e19d51091a976118 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 15 Oct 2025 18:58:55 +1100 Subject: [PATCH 262/309] Install dependencies when type checking --- .ci/requirements-mypy.txt | 2 ++ Tests/test_arro3.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.ci/requirements-mypy.txt b/.ci/requirements-mypy.txt index 447856433..6ca35d286 100644 --- a/.ci/requirements-mypy.txt +++ b/.ci/requirements-mypy.txt @@ -1,4 +1,6 @@ mypy==1.18.2 +arro3-compute +arro3-core IceSpringPySideStubs-PyQt6 IceSpringPySideStubs-PySide6 ipython diff --git a/Tests/test_arro3.py b/Tests/test_arro3.py index 60955cfdb..672eedc9b 100644 --- a/Tests/test_arro3.py +++ b/Tests/test_arro3.py @@ -16,8 +16,8 @@ from .helper import ( TYPE_CHECKING = False if TYPE_CHECKING: - from arro3 import compute # type: ignore [import-not-found] - from arro3.core import ( # type: ignore [import-not-found] + from arro3 import compute + from arro3.core import ( Array, DataType, Field, From 78b0e06dbbe8f7cdeee1fd9234c13b6d4d997876 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 14 Oct 2025 23:41:50 +1100 Subject: [PATCH 263/309] Shift bits before making value negative --- src/libImaging/BcnDecode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/BcnDecode.c b/src/libImaging/BcnDecode.c index 7b3d8f908..ac81ed6df 100644 --- a/src/libImaging/BcnDecode.c +++ b/src/libImaging/BcnDecode.c @@ -603,7 +603,7 @@ static void bc6_sign_extend(UINT16 *v, int prec) { int x = *v; if (x & (1 << (prec - 1))) { - x |= -1 << prec; + x |= -(1 << prec); } *v = (UINT16)x; } From 4889863139473270b69e5583007760401198cd4c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 15 Oct 2025 19:38:25 +1100 Subject: [PATCH 264/309] Renamed ImageText class to Text --- Tests/test_imagetext.py | 24 ++++++++++++------------ docs/reference/ImageText.rst | 4 ++-- src/PIL/ImageDraw.py | 10 +++++----- src/PIL/ImageText.py | 22 +++++++++++----------- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/Tests/test_imagetext.py b/Tests/test_imagetext.py index b58d048b5..7db229897 100644 --- a/Tests/test_imagetext.py +++ b/Tests/test_imagetext.py @@ -26,24 +26,24 @@ def font(layout_engine: ImageFont.Layout) -> ImageFont.FreeTypeFont: def test_get_length(font: ImageFont.FreeTypeFont) -> None: - assert ImageText.ImageText("A", font).get_length() == 12 - assert ImageText.ImageText("AB", font).get_length() == 24 - assert ImageText.ImageText("M", font).get_length() == 12 - assert ImageText.ImageText("y", font).get_length() == 12 - assert ImageText.ImageText("a", font).get_length() == 12 + assert ImageText.Text("A", font).get_length() == 12 + assert ImageText.Text("AB", font).get_length() == 24 + assert ImageText.Text("M", font).get_length() == 12 + assert ImageText.Text("y", font).get_length() == 12 + assert ImageText.Text("a", font).get_length() == 12 def test_get_bbox(font: ImageFont.FreeTypeFont) -> None: - assert ImageText.ImageText("A", font).get_bbox() == (0, 4, 12, 16) - assert ImageText.ImageText("AB", font).get_bbox() == (0, 4, 24, 16) - assert ImageText.ImageText("M", font).get_bbox() == (0, 4, 12, 16) - assert ImageText.ImageText("y", font).get_bbox() == (0, 7, 12, 20) - assert ImageText.ImageText("a", font).get_bbox() == (0, 7, 12, 16) + assert ImageText.Text("A", font).get_bbox() == (0, 4, 12, 16) + assert ImageText.Text("AB", font).get_bbox() == (0, 4, 24, 16) + assert ImageText.Text("M", font).get_bbox() == (0, 4, 12, 16) + assert ImageText.Text("y", font).get_bbox() == (0, 7, 12, 20) + assert ImageText.Text("a", font).get_bbox() == (0, 7, 12, 16) def test_standard_embedded_color(layout_engine: ImageFont.Layout) -> None: font = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine) - text = ImageText.ImageText("Hello World!", font) + text = ImageText.Text("Hello World!", font) text.embed_color() im = Image.new("RGB", (300, 64), "white") @@ -60,7 +60,7 @@ def test_stroke() -> None: im = Image.new("RGB", (120, 130)) draw = ImageDraw.Draw(im) font = ImageFont.truetype(FONT_PATH, 120) - text = ImageText.ImageText("A", font) + text = ImageText.Text("A", font) text.stroke(2, stroke_fill) # Act diff --git a/docs/reference/ImageText.rst b/docs/reference/ImageText.rst index fa55b4f30..299561ace 100644 --- a/docs/reference/ImageText.rst +++ b/docs/reference/ImageText.rst @@ -16,7 +16,7 @@ Example from PIL import Image, ImageDraw, ImageFont, ImageText font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 24) - text = ImageText.ImageText("Hello world", font) + text = ImageText.Text("Hello world", font) text.embed_color() text.stroke(2, "#0f0") @@ -30,5 +30,5 @@ Example Methods ------- -.. autoclass:: PIL.ImageText.ImageText +.. autoclass:: PIL.ImageText.Text :members: diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index f1b5dd4f3..0256efd62 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -540,7 +540,7 @@ class ImageDraw: def text( self, xy: tuple[float, float], - text: AnyStr | ImageText.ImageText, + text: AnyStr | ImageText.Text, fill: _Ink | None = None, font: ( ImageFont.ImageFont @@ -561,12 +561,12 @@ class ImageDraw: **kwargs: Any, ) -> None: """Draw text.""" - if isinstance(text, ImageText.ImageText): + if isinstance(text, ImageText.Text): imagetext = text else: if font is None: font = self._getfont(kwargs.get("font_size")) - imagetext = ImageText.ImageText( + imagetext = ImageText.Text( text, font, self.mode, spacing, direction, features, language ) if embedded_color: @@ -721,7 +721,7 @@ class ImageDraw: """Get the length of a given string, in pixels with 1/64 precision.""" if font is None: font = self._getfont(font_size) - imagetext = ImageText.ImageText( + imagetext = ImageText.Text( text, font, self.mode, @@ -757,7 +757,7 @@ class ImageDraw: """Get the bounding box of a given string, in pixels.""" if font is None: font = self._getfont(font_size) - imagetext = ImageText.ImageText( + imagetext = ImageText.Text( text, font, self.mode, spacing, direction, features, language ) if embedded_color: diff --git a/src/PIL/ImageText.py b/src/PIL/ImageText.py index 9bb31a1c8..c74570e69 100644 --- a/src/PIL/ImageText.py +++ b/src/PIL/ImageText.py @@ -4,7 +4,7 @@ from . import ImageFont from ._typing import _Ink -class ImageText: +class Text: def __init__( self, text: str | bytes, @@ -104,26 +104,26 @@ class ImageText: For example, instead of:: - hello = ImageText.ImageText("Hello", font).get_length() - world = ImageText.ImageText("World", font).get_length() - helloworld = ImageText.ImageText("HelloWorld", font).get_length() + hello = ImageText.Text("Hello", font).get_length() + world = ImageText.Text("World", font).get_length() + helloworld = ImageText.Text("HelloWorld", font).get_length() assert hello + world == helloworld use:: hello = ( - ImageText.ImageText("HelloW", font).get_length() - - ImageText.ImageText("W", font).get_length() + ImageText.Text("HelloW", font).get_length() - + ImageText.Text("W", font).get_length() ) # adjusted for kerning - world = ImageText.ImageText("World", font).get_length() - helloworld = ImageText.ImageText("HelloWorld", font).get_length() + world = ImageText.Text("World", font).get_length() + helloworld = ImageText.Text("HelloWorld", font).get_length() assert hello + world == helloworld or disable kerning with (requires libraqm):: - hello = ImageText.ImageText("Hello", font, features=["-kern"]).get_length() - world = ImageText.ImageText("World", font, features=["-kern"]).get_length() - helloworld = ImageText.ImageText( + hello = ImageText.Text("Hello", font, features=["-kern"]).get_length() + world = ImageText.Text("World", font, features=["-kern"]).get_length() + helloworld = ImageText.Text( "HelloWorld", font, features=["-kern"] ).get_length() assert hello + world == helloworld From 95a85dc6693ca221643906214b0e1f4590986c0f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 15 Oct 2025 19:36:10 +1100 Subject: [PATCH 265/309] Use snake case --- src/PIL/ImageDraw.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 0256efd62..a720ad40a 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -562,17 +562,17 @@ class ImageDraw: ) -> None: """Draw text.""" if isinstance(text, ImageText.Text): - imagetext = text + image_text = text else: if font is None: font = self._getfont(kwargs.get("font_size")) - imagetext = ImageText.Text( + image_text = ImageText.Text( text, font, self.mode, spacing, direction, features, language ) if embedded_color: - imagetext.embed_color() + image_text.embed_color() if stroke_width: - imagetext.stroke(stroke_width, stroke_fill) + image_text.stroke(stroke_width, stroke_fill) def getink(fill: _Ink | None) -> int: ink, fill_ink = self._getink(fill) @@ -586,14 +586,14 @@ class ImageDraw: return stroke_ink = None - if imagetext.stroke_width: + if image_text.stroke_width: stroke_ink = ( - getink(imagetext.stroke_fill) - if imagetext.stroke_fill is not None + getink(image_text.stroke_fill) + if image_text.stroke_fill is not None else ink ) - for xy, anchor, line in imagetext._split(xy, anchor, align): + for xy, anchor, line in image_text._split(xy, anchor, align): def draw_text(ink: int, stroke_width: float = 0) -> None: mode = self.fontmode @@ -604,7 +604,7 @@ class ImageDraw: coord.append(int(xy[i])) start = (math.modf(xy[0])[0], math.modf(xy[1])[0]) try: - mask, offset = imagetext.font.getmask2( # type: ignore[union-attr,misc] + mask, offset = image_text.font.getmask2( # type: ignore[union-attr,misc] line, mode, direction=direction, @@ -621,7 +621,7 @@ class ImageDraw: coord = [coord[0] + offset[0], coord[1] + offset[1]] except AttributeError: try: - mask = imagetext.font.getmask( # type: ignore[misc] + mask = image_text.font.getmask( # type: ignore[misc] line, mode, direction, @@ -635,9 +635,9 @@ class ImageDraw: **kwargs, ) except TypeError: - mask = imagetext.font.getmask(line) + mask = image_text.font.getmask(line) if mode == "RGBA": - # imagetext.font.getmask2(mode="RGBA") + # image_text.font.getmask2(mode="RGBA") # returns color in RGB bands and mask in A # extract mask and set text alpha color, mask = mask, mask.getband(3) @@ -653,7 +653,7 @@ class ImageDraw: if stroke_ink is not None: # Draw stroked text - draw_text(stroke_ink, imagetext.stroke_width) + draw_text(stroke_ink, image_text.stroke_width) # Draw normal text if ink != stroke_ink: @@ -721,7 +721,7 @@ class ImageDraw: """Get the length of a given string, in pixels with 1/64 precision.""" if font is None: font = self._getfont(font_size) - imagetext = ImageText.Text( + image_text = ImageText.Text( text, font, self.mode, @@ -730,8 +730,8 @@ class ImageDraw: language=language, ) if embedded_color: - imagetext.embed_color() - return imagetext.get_length() + image_text.embed_color() + return image_text.get_length() def textbbox( self, @@ -757,14 +757,14 @@ class ImageDraw: """Get the bounding box of a given string, in pixels.""" if font is None: font = self._getfont(font_size) - imagetext = ImageText.Text( + image_text = ImageText.Text( text, font, self.mode, spacing, direction, features, language ) if embedded_color: - imagetext.embed_color() + image_text.embed_color() if stroke_width: - imagetext.stroke(stroke_width) - return imagetext.get_bbox(xy, anchor, align) + image_text.stroke(stroke_width) + return image_text.get_bbox(xy, anchor, align) def multiline_textbbox( self, From d5e1601b32ea43b45ce8f820e4b349e9b5e2dd6c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 15 Oct 2025 20:02:12 +1100 Subject: [PATCH 266/309] Improved documentation --- docs/reference/ImageDraw.rst | 4 ++++ docs/reference/ImageText.rst | 33 ++++++++++++++++++++++++++++++--- docs/releasenotes/12.0.0.rst | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 6768a04c6..4c9567593 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -582,6 +582,8 @@ Methods hello_world = hello + world # kerning is disabled, no need to adjust assert hello_world == draw.textlength("HelloWorld", font, features=["-kern"]) # True + .. seealso:: :py:meth:`PIL.ImageText.Text.get_length` + .. versionadded:: 8.0.0 :param text: Text to be measured. May not contain any newline characters. @@ -683,6 +685,8 @@ Methods 1/64 pixel precision. The bounding box includes extra margins for some fonts, e.g. italics or accents. + .. seealso:: :py:meth:`PIL.ImageText.Text.get_bbox` + .. versionadded:: 8.0.0 :param xy: The anchor coordinates of the text. diff --git a/docs/reference/ImageText.rst b/docs/reference/ImageText.rst index 299561ace..8744ad368 100644 --- a/docs/reference/ImageText.rst +++ b/docs/reference/ImageText.rst @@ -4,9 +4,9 @@ :py:mod:`~PIL.ImageText` module =============================== -The :py:mod:`~PIL.ImageText` module defines a class with the same name. Instances of -this class provide a way to use fonts with text strings or bytes. The result is a -simple API to apply styling to pieces of text and measure or draw them. +The :py:mod:`~PIL.ImageText` module defines a :py:class:`~PIL.ImageText.Text` class. +Instances of this class provide a way to use fonts with text strings or bytes. The +result is a simple API to apply styling to pieces of text and measure or draw them. Example ------- @@ -27,6 +27,33 @@ Example d = ImageDraw.Draw(im) d.text((0, 0), text, "#f00") +Comparison +---------- + +Without ``ImageText.Text``:: + + from PIL import Image, ImageDraw + im = Image.new(mode, size) + d = ImageDraw.Draw(im) + + d.textlength(text, font, direction, features, language, embedded_color) + d.multiline_textbbox(xy, text, font, anchor, spacing, align, direction, features, language, stroke_width, embedded_color) + d.text(xy, text, fill, font, anchor, spacing, align, direction, features, language, stroke_width, stroke_fill, embedded_color) + +With ``ImageText.Text``:: + + from PIL import ImageText + text = ImageText.Text(text, font, mode, spacing, direction, features, language) + text.embed_color() + text.stroke(stroke_width, stroke_fill) + + text.get_length() + text.get_bbox(xy, anchor, align) + + im = Image.new(mode, size) + d = ImageDraw.Draw(im) + d.text(xy, text, fill, anchor=anchor, align=align) + Methods ------- diff --git a/docs/releasenotes/12.0.0.rst b/docs/releasenotes/12.0.0.rst index fb5733944..4c00d8c4c 100644 --- a/docs/releasenotes/12.0.0.rst +++ b/docs/releasenotes/12.0.0.rst @@ -124,6 +124,39 @@ Image.alpha_composite: LA images :py:meth:`~PIL.Image.alpha_composite` can now use LA images as well as RGBA. +API additions +============= + +Added ImageText.Text +^^^^^^^^^^^^^^^^^^^^ + +:py:class:`PIL.ImageText.Text` has been added, as a simpler way to use fonts with text +strings or bytes. + +Without ``ImageText.Text``:: + + from PIL import Image, ImageDraw + im = Image.new(mode, size) + d = ImageDraw.Draw(im) + + d.textlength(text, font, direction, features, language, embedded_color) + d.multiline_textbbox(xy, text, font, anchor, spacing, align, direction, features, language, stroke_width, embedded_color) + d.text(xy, text, fill, font, anchor, spacing, align, direction, features, language, stroke_width, stroke_fill, embedded_color) + +With ``ImageText.Text``:: + + from PIL import ImageText + text = ImageText.Text(text, font, mode, spacing, direction, features, language) + text.embed_color() + text.stroke(stroke_width, stroke_fill) + + text.get_length() + text.get_bbox(xy, anchor, align) + + im = Image.new(mode, size) + d = ImageDraw.Draw(im) + d.text(xy, text, fill, anchor=anchor, align=align) + Other changes ============= From 3eecafd62c760cf2715f4bcc4c995ead35680e0e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 15 Oct 2025 22:19:38 +1100 Subject: [PATCH 267/309] Fixed warning --- src/libImaging/Arrow.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/Arrow.c b/src/libImaging/Arrow.c index e353ab2e9..d2ed10f0a 100644 --- a/src/libImaging/Arrow.c +++ b/src/libImaging/Arrow.c @@ -149,7 +149,7 @@ assemble_metadata(const char *band_json) { } int -export_named_type(struct ArrowSchema *schema, char *format, char *name) { +export_named_type(struct ArrowSchema *schema, char *format, const char *name) { char *formatp; char *namep; size_t format_len = strlen(format) + 1; From 7d89946688a44e302b5480e8c02c37fe97369c6b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 15 Oct 2025 22:21:51 +1100 Subject: [PATCH 268/309] Removed duplicate library --- setup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 3a72a0742..032c1c6d2 100644 --- a/setup.py +++ b/setup.py @@ -1086,10 +1086,10 @@ for src_file in _IMAGING: for src_file in _LIB_IMAGING: files.append(os.path.join("src/libImaging", src_file + ".c")) ext_modules = [ - Extension("PIL._imaging", files, libraries=["pil_imaging_mode"]), - Extension("PIL._imagingft", ["src/_imagingft.c"], libraries=["pil_imaging_mode"]), - Extension("PIL._imagingcms", ["src/_imagingcms.c"], libraries=["pil_imaging_mode"]), - Extension("PIL._webp", ["src/_webp.c"], libraries=["pil_imaging_mode"]), + Extension("PIL._imaging", files), + Extension("PIL._imagingft", ["src/_imagingft.c"]), + Extension("PIL._imagingcms", ["src/_imagingcms.c"]), + Extension("PIL._webp", ["src/_webp.c"]), Extension("PIL._avif", ["src/_avif.c"]), Extension("PIL._imagingtk", ["src/_imagingtk.c", "src/Tk/tkImaging.c"]), Extension("PIL._imagingmath", ["src/_imagingmath.c"]), From 592b2f820aa1f75f8ae8bf4f30e1b4bc62023535 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 15 Oct 2025 19:00:54 +0300 Subject: [PATCH 269/309] Revert "Use macos-latest for iOS arm64 simulator" --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 21ea79553..6dc8db7e9 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -100,7 +100,7 @@ jobs: cibw_arch: arm64_iphoneos - name: "iOS arm64 simulator" platform: ios - os: macos-latest + os: macos-14 cibw_arch: arm64_iphonesimulator - name: "iOS x86_64 simulator" platform: ios From 693df7b42c666f88c719f9973be0ad71607328e0 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 15 Oct 2025 20:06:44 +0300 Subject: [PATCH 270/309] 12.0.0 version bump --- src/PIL/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/_version.py b/src/PIL/_version.py index 6a3c01f26..79ce194c3 100644 --- a/src/PIL/_version.py +++ b/src/PIL/_version.py @@ -1,4 +1,4 @@ # Master version for Pillow from __future__ import annotations -__version__ = "12.0.0.dev0" +__version__ = "12.0.0" From 3620d48459da4e8f30278b0abc8d6c3d51565447 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 15 Oct 2025 21:28:16 +0300 Subject: [PATCH 271/309] 12.1.0.dev0 version bump --- src/PIL/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/_version.py b/src/PIL/_version.py index 79ce194c3..41cb17a36 100644 --- a/src/PIL/_version.py +++ b/src/PIL/_version.py @@ -1,4 +1,4 @@ # Master version for Pillow from __future__ import annotations -__version__ = "12.0.0" +__version__ = "12.1.0.dev0" From 933df2450d9b415eeed656525d4d69c347fa1c7e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 16 Oct 2025 07:21:15 +1100 Subject: [PATCH 272/309] Reapply "Use macos-latest for iOS arm64 simulator" This reverts commit 592b2f820aa1f75f8ae8bf4f30e1b4bc62023535. --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 6dc8db7e9..21ea79553 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -100,7 +100,7 @@ jobs: cibw_arch: arm64_iphoneos - name: "iOS arm64 simulator" platform: ios - os: macos-14 + os: macos-latest cibw_arch: arm64_iphonesimulator - name: "iOS x86_64 simulator" platform: ios From ae7d28eddbdc24bfadb13ed27f01ef8256b29480 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 16 Oct 2025 12:03:13 +1100 Subject: [PATCH 273/309] Removed Fedora 41 --- .github/workflows/test-docker.yml | 1 - docs/installation/platform-support.rst | 2 -- 2 files changed, 3 deletions(-) diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 30e5c494d..581e1f52b 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -49,7 +49,6 @@ jobs: debian-12-bookworm-amd64, debian-13-trixie-x86, debian-13-trixie-amd64, - fedora-41-amd64, fedora-42-amd64, gentoo, ubuntu-22.04-jammy-amd64, diff --git a/docs/installation/platform-support.rst b/docs/installation/platform-support.rst index 7999504fb..471bc1eb3 100644 --- a/docs/installation/platform-support.rst +++ b/docs/installation/platform-support.rst @@ -33,8 +33,6 @@ These platforms are built and tested for every change. +----------------------------------+----------------------------+---------------------+ | Debian 13 Trixie | 3.13 | x86, x86-64 | +----------------------------------+----------------------------+---------------------+ -| Fedora 41 | 3.13 | x86-64 | -+----------------------------------+----------------------------+---------------------+ | Fedora 42 | 3.13 | x86-64 | +----------------------------------+----------------------------+---------------------+ | Gentoo | 3.12 | x86-64 | From ae43b36030a3d8dca20bb908bf158dcadc2cf35f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 16 Oct 2025 20:55:56 +1100 Subject: [PATCH 274/309] Simplified code now that I;16* modes are the only IMAGING_TYPE_SPECIAL --- src/_imaging.c | 31 +++++-------------------------- src/libImaging/Geometry.c | 9 +-------- 2 files changed, 6 insertions(+), 34 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index 41af72568..f6be4a901 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -543,12 +543,7 @@ getpixel(Imaging im, ImagingAccess access, int x, int y) { case IMAGING_TYPE_FLOAT32: return PyFloat_FromDouble(pixel.f); case IMAGING_TYPE_SPECIAL: - if (im->bands == 1) { - return PyLong_FromLong(pixel.h); - } else { - return Py_BuildValue("BBB", pixel.b[0], pixel.b[1], pixel.b[2]); - } - break; + return PyLong_FromLong(pixel.h); } /* unknown type */ @@ -665,26 +660,10 @@ getink(PyObject *color, Imaging im, char *ink) { memcpy(ink, &ftmp, sizeof(ftmp)); return ink; case IMAGING_TYPE_SPECIAL: - if (isModeI16(im->mode)) { - ink[0] = (UINT8)r; - ink[1] = (UINT8)(r >> 8); - ink[2] = ink[3] = 0; - return ink; - } else { - if (rIsInt) { - b = (UINT8)(r >> 16); - g = (UINT8)(r >> 8); - r = (UINT8)r; - } else if (tupleSize != 3) { - PyErr_SetString( - PyExc_TypeError, - "color must be int, or tuple of one or three elements" - ); - return NULL; - } else if (!PyArg_ParseTuple(color, "iiL", &b, &g, &r)) { - return NULL; - } - } + ink[0] = (UINT8)r; + ink[1] = (UINT8)(r >> 8); + ink[2] = ink[3] = 0; + return ink; } PyErr_SetString(PyExc_ValueError, wrong_mode); diff --git a/src/libImaging/Geometry.c b/src/libImaging/Geometry.c index 80ecd7cb6..2186f95f8 100644 --- a/src/libImaging/Geometry.c +++ b/src/libImaging/Geometry.c @@ -714,14 +714,7 @@ getfilter(Imaging im, int filterid) { case IMAGING_TYPE_UINT8: return nearest_filter8; case IMAGING_TYPE_SPECIAL: - switch (im->pixelsize) { - case 1: - return nearest_filter8; - case 2: - return nearest_filter16; - case 4: - return nearest_filter32; - } + return nearest_filter16; } } else { return nearest_filter32; From e969fa7aeac1cfc46ef1e6a8e77699f71a9effe8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 17 Oct 2025 06:14:02 +1100 Subject: [PATCH 275/309] Correct __getitem__ return type --- Tests/test_image_getdata.py | 2 +- src/PIL/_imaging.pyi | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_image_getdata.py b/Tests/test_image_getdata.py index dd3d70b34..c8b213d84 100644 --- a/Tests/test_image_getdata.py +++ b/Tests/test_image_getdata.py @@ -15,7 +15,7 @@ def test_sanity() -> None: def test_mode() -> None: - def getdata(mode: str) -> tuple[float | tuple[int, ...], int, int]: + def getdata(mode: str) -> tuple[float | tuple[int, ...] | None, int, int]: im = hopper(mode).resize((32, 30), Image.Resampling.NEAREST) data = im.getdata() return data[0], len(data), len(list(data)) diff --git a/src/PIL/_imaging.pyi b/src/PIL/_imaging.pyi index 998bc52eb..81028a596 100644 --- a/src/PIL/_imaging.pyi +++ b/src/PIL/_imaging.pyi @@ -1,7 +1,7 @@ from typing import Any class ImagingCore: - def __getitem__(self, index: int) -> float: ... + def __getitem__(self, index: int) -> float | tuple[int, ...] | None: ... def __getattr__(self, name: str) -> Any: ... class ImagingFont: From 03d48f4011d4c35099341ae76fc5bf1f34ea3e9e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 17 Oct 2025 23:05:33 +1100 Subject: [PATCH 276/309] Updated macOS tested Pillow versions --- docs/installation/platform-support.rst | 196 +++++++++++++------------ 1 file changed, 99 insertions(+), 97 deletions(-) diff --git a/docs/installation/platform-support.rst b/docs/installation/platform-support.rst index 471bc1eb3..e0c4a8eec 100644 --- a/docs/installation/platform-support.rst +++ b/docs/installation/platform-support.rst @@ -69,100 +69,102 @@ These platforms have been reported to work at the versions mentioned. Contributors please test Pillow on your platform then update this document and send a pull request. -+----------------------------------+----------------------------+------------------+--------------+ -| Operating system | | Tested Python | | Latest tested | | Tested | -| | | versions | | Pillow version | | processors | -+==================================+============================+==================+==============+ -| macOS 26 Tahoe | 3.9, 3.10, 3.11, 3.12, 3.13| 11.3.0 |arm | -+----------------------------------+----------------------------+------------------+--------------+ -| macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13| 11.3.0 |arm | -| +----------------------------+------------------+ | -| | 3.8 | 10.4.0 | | -+----------------------------------+----------------------------+------------------+--------------+ -| macOS 14 Sonoma | 3.8, 3.9, 3.10, 3.11, 3.12 | 10.4.0 |arm | -+----------------------------------+----------------------------+------------------+--------------+ -| macOS 13 Ventura | 3.8, 3.9, 3.10, 3.11 | 10.0.1 |arm | -| +----------------------------+------------------+ | -| | 3.7 | 9.5.0 | | -+----------------------------------+----------------------------+------------------+--------------+ -| macOS 12 Monterey | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm | -+----------------------------------+----------------------------+------------------+--------------+ -| macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm | -| +----------------------------+------------------+--------------+ -| | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.4.0 |x86-64 | -| +----------------------------+------------------+ | -| | 3.6 | 8.4.0 | | -+----------------------------------+----------------------------+------------------+--------------+ -| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.3.2 |x86-64 | -| +----------------------------+------------------+ | -| | 3.5 | 7.2.0 | | -+----------------------------------+----------------------------+------------------+--------------+ -| macOS 10.14 Mojave | 3.5, 3.6, 3.7, 3.8 | 7.2.0 |x86-64 | -| +----------------------------+------------------+ | -| | 2.7 | 6.0.0 | | -| +----------------------------+------------------+ | -| | 3.4 | 5.4.1 | | -+----------------------------------+----------------------------+------------------+--------------+ -| macOS 10.13 High Sierra | 2.7, 3.4, 3.5, 3.6 | 4.2.1 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| macOS 10.12 Sierra | 2.7, 3.4, 3.5, 3.6 | 4.1.1 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Mac OS X 10.11 El Capitan | 2.7, 3.4, 3.5, 3.6, 3.7 | 5.4.1 |x86-64 | -| +----------------------------+------------------+ | -| | 3.3 | 4.1.0 | | -+----------------------------------+----------------------------+------------------+--------------+ -| Mac OS X 10.9 Mavericks | 2.7, 3.2, 3.3, 3.4 | 3.0.0 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Mac OS X 10.8 Mountain Lion | 2.6, 2.7, 3.2, 3.3 | |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Redhat Linux 6 | 2.6 | |x86 | -+----------------------------------+----------------------------+------------------+--------------+ -| CentOS 6.3 | 2.7, 3.3 | |x86 | -+----------------------------------+----------------------------+------------------+--------------+ -| CentOS 8 | 3.9 | 9.0.0 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Fedora 23 | 2.7, 3.4 | 3.1.0 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Ubuntu Linux 12.04 LTS (Precise) | | 2.6, 3.2, 3.3, 3.4, 3.5 | 3.4.1 |x86,x86-64 | -| | | PyPy5.3.1, PyPy3 v2.4.0 | | | -| +----------------------------+------------------+--------------+ -| | 2.7 | 4.3.0 |x86-64 | -| +----------------------------+------------------+--------------+ -| | 2.7, 3.2 | 3.4.1 |ppc | -+----------------------------------+----------------------------+------------------+--------------+ -| Ubuntu Linux 10.04 LTS (Lucid) | 2.6 | 2.3.0 |x86,x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Debian 8.2 Jessie | 2.7, 3.4 | 3.1.0 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Raspbian Jessie | 2.7, 3.4 | 3.1.0 |arm | -+----------------------------------+----------------------------+------------------+--------------+ -| Raspbian Stretch | 2.7, 3.5 | 4.0.0 |arm | -+----------------------------------+----------------------------+------------------+--------------+ -| Raspberry Pi OS | 3.6, 3.7, 3.8, 3.9 | 8.2.0 |arm | -| +----------------------------+------------------+ | -| | 2.7 | 6.2.2 | | -+----------------------------------+----------------------------+------------------+--------------+ -| Gentoo Linux | 2.7, 3.2 | 2.1.0 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| FreeBSD 11.1 | 2.7, 3.4, 3.5, 3.6 | 4.3.0 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| FreeBSD 10.3 | 2.7, 3.4, 3.5 | 4.2.0 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| FreeBSD 10.2 | 2.7, 3.4 | 3.1.0 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Windows 11 23H2 | 3.9, 3.10, 3.11, 3.12, 3.13| 11.0.0 |arm64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Windows 11 Pro | 3.11, 3.12 | 10.2.0 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Windows 10 | 3.7 | 7.1.0 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Windows 10/Cygwin 3.3 | 3.6, 3.7, 3.8, 3.9 | 8.4.0 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Windows 8.1 Pro | 2.6, 2.7, 3.2, 3.3, 3.4 | 2.4.0 |x86,x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Windows 8 Pro | 2.6, 2.7, 3.2, 3.3, 3.4a3 | 2.2.0 |x86,x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Windows 7 Professional | 3.7 | 7.0.0 |x86,x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Windows Server 2008 R2 Enterprise| 3.3 | |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ ++----------------------------------+-----------------------------+------------------+--------------+ +| Operating system | | Tested Python | | Latest tested | | Tested | +| | | versions | | Pillow version | | processors | ++==================================+=============================+==================+==============+ +| macOS 26 Tahoe | 3.10, 3.11, 3.12, 3.13, 3.14| 12.0.0 |arm | +| +-----------------------------+------------------+ | +| | 3.9 | 11.3.0 | | ++----------------------------------+-----------------------------+------------------+--------------+ +| macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13 | 11.3.0 |arm | +| +-----------------------------+------------------+ | +| | 3.8 | 10.4.0 | | ++----------------------------------+-----------------------------+------------------+--------------+ +| macOS 14 Sonoma | 3.8, 3.9, 3.10, 3.11, 3.12 | 10.4.0 |arm | ++----------------------------------+-----------------------------+------------------+--------------+ +| macOS 13 Ventura | 3.8, 3.9, 3.10, 3.11 | 10.0.1 |arm | +| +-----------------------------+------------------+ | +| | 3.7 | 9.5.0 | | ++----------------------------------+-----------------------------+------------------+--------------+ +| macOS 12 Monterey | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm | ++----------------------------------+-----------------------------+------------------+--------------+ +| macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm | +| +-----------------------------+------------------+--------------+ +| | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.4.0 |x86-64 | +| +-----------------------------+------------------+ | +| | 3.6 | 8.4.0 | | ++----------------------------------+-----------------------------+------------------+--------------+ +| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.3.2 |x86-64 | +| +-----------------------------+------------------+ | +| | 3.5 | 7.2.0 | | ++----------------------------------+-----------------------------+------------------+--------------+ +| macOS 10.14 Mojave | 3.5, 3.6, 3.7, 3.8 | 7.2.0 |x86-64 | +| +-----------------------------+------------------+ | +| | 2.7 | 6.0.0 | | +| +-----------------------------+------------------+ | +| | 3.4 | 5.4.1 | | ++----------------------------------+-----------------------------+------------------+--------------+ +| macOS 10.13 High Sierra | 2.7, 3.4, 3.5, 3.6 | 4.2.1 |x86-64 | ++----------------------------------+-----------------------------+------------------+--------------+ +| macOS 10.12 Sierra | 2.7, 3.4, 3.5, 3.6 | 4.1.1 |x86-64 | ++----------------------------------+-----------------------------+------------------+--------------+ +| Mac OS X 10.11 El Capitan | 2.7, 3.4, 3.5, 3.6, 3.7 | 5.4.1 |x86-64 | +| +-----------------------------+------------------+ | +| | 3.3 | 4.1.0 | | ++----------------------------------+-----------------------------+------------------+--------------+ +| Mac OS X 10.9 Mavericks | 2.7, 3.2, 3.3, 3.4 | 3.0.0 |x86-64 | ++----------------------------------+-----------------------------+------------------+--------------+ +| Mac OS X 10.8 Mountain Lion | 2.6, 2.7, 3.2, 3.3 | |x86-64 | ++----------------------------------+-----------------------------+------------------+--------------+ +| Redhat Linux 6 | 2.6 | |x86 | ++----------------------------------+-----------------------------+------------------+--------------+ +| CentOS 6.3 | 2.7, 3.3 | |x86 | ++----------------------------------+-----------------------------+------------------+--------------+ +| CentOS 8 | 3.9 | 9.0.0 |x86-64 | ++----------------------------------+-----------------------------+------------------+--------------+ +| Fedora 23 | 2.7, 3.4 | 3.1.0 |x86-64 | ++----------------------------------+-----------------------------+------------------+--------------+ +| Ubuntu Linux 12.04 LTS (Precise) | | 2.6, 3.2, 3.3, 3.4, 3.5 | 3.4.1 |x86,x86-64 | +| | | PyPy5.3.1, PyPy3 v2.4.0 | | | +| +-----------------------------+------------------+--------------+ +| | 2.7 | 4.3.0 |x86-64 | +| +-----------------------------+------------------+--------------+ +| | 2.7, 3.2 | 3.4.1 |ppc | ++----------------------------------+-----------------------------+------------------+--------------+ +| Ubuntu Linux 10.04 LTS (Lucid) | 2.6 | 2.3.0 |x86,x86-64 | ++----------------------------------+-----------------------------+------------------+--------------+ +| Debian 8.2 Jessie | 2.7, 3.4 | 3.1.0 |x86-64 | ++----------------------------------+-----------------------------+------------------+--------------+ +| Raspbian Jessie | 2.7, 3.4 | 3.1.0 |arm | ++----------------------------------+-----------------------------+------------------+--------------+ +| Raspbian Stretch | 2.7, 3.5 | 4.0.0 |arm | ++----------------------------------+-----------------------------+------------------+--------------+ +| Raspberry Pi OS | 3.6, 3.7, 3.8, 3.9 | 8.2.0 |arm | +| +-----------------------------+------------------+ | +| | 2.7 | 6.2.2 | | ++----------------------------------+-----------------------------+------------------+--------------+ +| Gentoo Linux | 2.7, 3.2 | 2.1.0 |x86-64 | ++----------------------------------+-----------------------------+------------------+--------------+ +| FreeBSD 11.1 | 2.7, 3.4, 3.5, 3.6 | 4.3.0 |x86-64 | ++----------------------------------+-----------------------------+------------------+--------------+ +| FreeBSD 10.3 | 2.7, 3.4, 3.5 | 4.2.0 |x86-64 | ++----------------------------------+-----------------------------+------------------+--------------+ +| FreeBSD 10.2 | 2.7, 3.4 | 3.1.0 |x86-64 | ++----------------------------------+-----------------------------+------------------+--------------+ +| Windows 11 23H2 | 3.9, 3.10, 3.11, 3.12, 3.13 | 11.0.0 |arm64 | ++----------------------------------+-----------------------------+------------------+--------------+ +| Windows 11 Pro | 3.11, 3.12 | 10.2.0 |x86-64 | ++----------------------------------+-----------------------------+------------------+--------------+ +| Windows 10 | 3.7 | 7.1.0 |x86-64 | ++----------------------------------+-----------------------------+------------------+--------------+ +| Windows 10/Cygwin 3.3 | 3.6, 3.7, 3.8, 3.9 | 8.4.0 |x86-64 | ++----------------------------------+-----------------------------+------------------+--------------+ +| Windows 8.1 Pro | 2.6, 2.7, 3.2, 3.3, 3.4 | 2.4.0 |x86,x86-64 | ++----------------------------------+-----------------------------+------------------+--------------+ +| Windows 8 Pro | 2.6, 2.7, 3.2, 3.3, 3.4a3 | 2.2.0 |x86,x86-64 | ++----------------------------------+-----------------------------+------------------+--------------+ +| Windows 7 Professional | 3.7 | 7.0.0 |x86,x86-64 | ++----------------------------------+-----------------------------+------------------+--------------+ +| Windows Server 2008 R2 Enterprise| 3.3 | |x86-64 | ++----------------------------------+-----------------------------+------------------+--------------+ From 51e3fe45bf34fb4c344eaaaadf3434a079c5b6dd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 20 Oct 2025 19:17:38 +1100 Subject: [PATCH 277/309] Use different variables for Image and ImageFile instances --- Tests/test_file_mic.py | 4 ++-- Tests/test_file_mpo.py | 8 ++++---- Tests/test_file_sun.py | 4 ++-- Tests/test_file_tiff.py | 6 +++--- Tests/test_file_tiff_metadata.py | 6 +++--- Tests/test_image.py | 26 +++++++++++++------------- Tests/test_image_convert.py | 4 ++-- Tests/test_image_crop.py | 8 ++++---- Tests/test_image_quantize.py | 18 +++++++++--------- Tests/test_image_resize.py | 4 ++-- Tests/test_image_rotate.py | 16 ++++++++-------- Tests/test_image_thumbnail.py | 4 ++-- Tests/test_imagedraw.py | 4 ++-- Tests/test_imageops.py | 12 ++++++------ Tests/test_pickle.py | 10 +++++----- Tests/test_shell_injection.py | 10 ++++++---- 16 files changed, 73 insertions(+), 71 deletions(-) diff --git a/Tests/test_file_mic.py b/Tests/test_file_mic.py index 9aeb306e4..0706af4c0 100644 --- a/Tests/test_file_mic.py +++ b/Tests/test_file_mic.py @@ -22,10 +22,10 @@ def test_sanity() -> None: # Adjust for the gamma of 2.2 encoded into the file lut = ImagePalette.make_gamma_lut(1 / 2.2) - im = Image.merge("RGBA", [chan.point(lut) for chan in im.split()]) + im1 = Image.merge("RGBA", [chan.point(lut) for chan in im.split()]) im2 = hopper("RGBA") - assert_image_similar(im, im2, 10) + assert_image_similar(im1, im2, 10) def test_n_frames() -> None: diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index f947d1419..4db62bd6d 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -300,12 +300,12 @@ def test_save_all() -> None: im_reloaded.seek(1) assert_image_similar(im, im_reloaded, 30) - im = Image.new("RGB", (1, 1)) + im_rgb = Image.new("RGB", (1, 1)) for colors in (("#f00",), ("#f00", "#0f0")): append_images = [Image.new("RGB", (1, 1), color) for color in colors] - im_reloaded = roundtrip(im, save_all=True, append_images=append_images) + im_reloaded = roundtrip(im_rgb, save_all=True, append_images=append_images) - assert_image_equal(im, im_reloaded) + assert_image_equal(im_rgb, im_reloaded) assert isinstance(im_reloaded, MpoImagePlugin.MpoImageFile) assert im_reloaded.mpinfo is not None assert im_reloaded.mpinfo[45056] == b"0100" @@ -315,7 +315,7 @@ def test_save_all() -> None: assert_image_similar(im_reloaded, im_expected, 1) # Test that a single frame image will not be saved as an MPO - jpg = roundtrip(im, save_all=True) + jpg = roundtrip(im_rgb, save_all=True) assert "mp" not in jpg.info diff --git a/Tests/test_file_sun.py b/Tests/test_file_sun.py index c2f162cf9..78534e154 100644 --- a/Tests/test_file_sun.py +++ b/Tests/test_file_sun.py @@ -84,8 +84,8 @@ def test_rgbx() -> None: with Image.open(io.BytesIO(data)) as im: r, g, b = im.split() - im = Image.merge("RGB", (b, g, r)) - assert_image_equal_tofile(im, os.path.join(EXTRA_DIR, "32bpp.png")) + im_rgb = Image.merge("RGB", (b, g, r)) + assert_image_equal_tofile(im_rgb, os.path.join(EXTRA_DIR, "32bpp.png")) @pytest.mark.skipif( diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index bd364377b..556c88647 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -764,9 +764,9 @@ class TestFileTiff: # Test appending images mp = BytesIO() - im = Image.new("RGB", (100, 100), "#f00") + im_rgb = Image.new("RGB", (100, 100), "#f00") ims = [Image.new("RGB", (100, 100), color) for color in ["#0f0", "#00f"]] - im.copy().save(mp, format="TIFF", save_all=True, append_images=ims) + im_rgb.copy().save(mp, format="TIFF", save_all=True, append_images=ims) mp.seek(0, os.SEEK_SET) with Image.open(mp) as reread: @@ -778,7 +778,7 @@ class TestFileTiff: yield from ims mp = BytesIO() - im.save(mp, format="TIFF", save_all=True, append_images=im_generator(ims)) + im_rgb.save(mp, format="TIFF", save_all=True, append_images=im_generator(ims)) mp.seek(0, os.SEEK_SET) with Image.open(mp) as reread: diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 36ad8cee9..322ef5abc 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -175,13 +175,13 @@ def test_change_stripbytecounts_tag_type(tmp_path: Path) -> None: del info[278] # Resize the image so that STRIPBYTECOUNTS will be larger than a SHORT - im = im.resize((500, 500)) - info[TiffImagePlugin.IMAGEWIDTH] = im.width + im_resized = im.resize((500, 500)) + info[TiffImagePlugin.IMAGEWIDTH] = im_resized.width # STRIPBYTECOUNTS can be a SHORT or a LONG info.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] = TiffTags.SHORT - im.save(out, tiffinfo=info) + im_resized.save(out, tiffinfo=info) with Image.open(out) as reloaded: assert isinstance(reloaded, TiffImagePlugin.TiffImageFile) diff --git a/Tests/test_image.py b/Tests/test_image.py index ac30f785c..88f55638e 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -613,8 +613,8 @@ class TestImage: assert im.getpixel((0, 0)) == 0 assert im.getpixel((255, 255)) == 255 with Image.open(target_file) as target: - target = target.convert(mode) - assert_image_equal(im, target) + im_target = target.convert(mode) + assert_image_equal(im, im_target) def test_radial_gradient_wrong_mode(self) -> None: # Arrange @@ -638,8 +638,8 @@ class TestImage: assert im.getpixel((0, 0)) == 255 assert im.getpixel((128, 128)) == 0 with Image.open(target_file) as target: - target = target.convert(mode) - assert_image_equal(im, target) + im_target = target.convert(mode) + assert_image_equal(im, im_target) def test_register_extensions(self) -> None: test_format = "a" @@ -663,20 +663,20 @@ class TestImage: assert_image_equal(im, im.remap_palette(list(range(256)))) # Test identity transform with an RGBA palette - im = Image.new("P", (256, 1)) + im_p = Image.new("P", (256, 1)) for x in range(256): - im.putpixel((x, 0), x) - im.putpalette(list(range(256)) * 4, "RGBA") - im_remapped = im.remap_palette(list(range(256))) - assert_image_equal(im, im_remapped) - assert im.palette is not None + im_p.putpixel((x, 0), x) + im_p.putpalette(list(range(256)) * 4, "RGBA") + im_remapped = im_p.remap_palette(list(range(256))) + assert_image_equal(im_p, im_remapped) + assert im_p.palette is not None assert im_remapped.palette is not None - assert im.palette.palette == im_remapped.palette.palette + assert im_p.palette.palette == im_remapped.palette.palette # Test illegal image mode - with hopper() as im: + with hopper() as im_hopper: with pytest.raises(ValueError): - im.remap_palette([]) + im_hopper.remap_palette([]) def test_remap_palette_transparency(self) -> None: im = Image.new("P", (1, 2), (0, 0, 0)) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 8d0ef4b22..547a6c2c6 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -80,8 +80,8 @@ def test_16bit() -> None: _test_float_conversion(im) for color in (65535, 65536): - im = Image.new("I", (1, 1), color) - im_i16 = im.convert("I;16") + im_i = Image.new("I", (1, 1), color) + im_i16 = im_i.convert("I;16") assert im_i16.getpixel((0, 0)) == 65535 diff --git a/Tests/test_image_crop.py b/Tests/test_image_crop.py index 07fec2e64..b90ce84bc 100644 --- a/Tests/test_image_crop.py +++ b/Tests/test_image_crop.py @@ -78,13 +78,13 @@ def test_crop_crash() -> None: extents = (1, 1, 10, 10) # works prepatch with Image.open(test_img) as img: - img2 = img.crop(extents) - img2.load() + img1 = img.crop(extents) + img1.load() # fail prepatch with Image.open(test_img) as img: - img = img.crop(extents) - img.load() + img2 = img.crop(extents) + img2.load() def test_crop_zero() -> None: diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index e8b783ff3..887628560 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -58,8 +58,8 @@ def test_rgba_quantize() -> None: def test_quantize() -> None: with Image.open("Tests/images/caption_6_33_22.png") as image: - image = image.convert("RGB") - converted = image.quantize() + converted = image.convert("RGB") + converted = converted.quantize() assert converted.mode == "P" assert_image_similar(converted.convert("RGB"), image, 1) @@ -67,13 +67,13 @@ def test_quantize() -> None: def test_quantize_no_dither() -> None: image = hopper() with Image.open("Tests/images/caption_6_33_22.png") as palette: - palette = palette.convert("P") + palette_p = palette.convert("P") - converted = image.quantize(dither=Image.Dither.NONE, palette=palette) + converted = image.quantize(dither=Image.Dither.NONE, palette=palette_p) assert converted.mode == "P" assert converted.palette is not None - assert palette.palette is not None - assert converted.palette.palette == palette.palette.palette + assert palette_p.palette is not None + assert converted.palette.palette == palette_p.palette.palette def test_quantize_no_dither2() -> None: @@ -97,10 +97,10 @@ def test_quantize_no_dither2() -> None: def test_quantize_dither_diff() -> None: image = hopper() with Image.open("Tests/images/caption_6_33_22.png") as palette: - palette = palette.convert("P") + palette_p = palette.convert("P") - dither = image.quantize(dither=Image.Dither.FLOYDSTEINBERG, palette=palette) - nodither = image.quantize(dither=Image.Dither.NONE, palette=palette) + dither = image.quantize(dither=Image.Dither.FLOYDSTEINBERG, palette=palette_p) + nodither = image.quantize(dither=Image.Dither.NONE, palette=palette_p) assert dither.tobytes() != nodither.tobytes() diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index 270500a44..323d31f51 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -314,8 +314,8 @@ class TestImageResize: @skip_unless_feature("libtiff") def test_transposed(self) -> None: with Image.open("Tests/images/g4_orientation_5.tif") as im: - im = im.resize((64, 64)) - assert im.size == (64, 64) + im_resized = im.resize((64, 64)) + assert im_resized.size == (64, 64) @pytest.mark.parametrize( "mode", ("L", "RGB", "I", "I;16", "I;16L", "I;16B", "I;16N", "F") diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index 252a15db7..c3ff52f57 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -43,8 +43,8 @@ def test_angle(angle: int) -> None: with Image.open("Tests/images/test-card.png") as im: rotate(im, im.mode, angle) - im = hopper() - assert_image_equal(im.rotate(angle), im.rotate(angle, expand=1)) + im_hopper = hopper() + assert_image_equal(im_hopper.rotate(angle), im_hopper.rotate(angle, expand=1)) @pytest.mark.parametrize("angle", (0, 45, 90, 180, 270)) @@ -76,9 +76,9 @@ def test_center_0() -> None: with Image.open("Tests/images/hopper_45.png") as target: target_origin = target.size[1] / 2 - target = target.crop((0, target_origin, 128, target_origin + 128)) + im_target = target.crop((0, target_origin, 128, target_origin + 128)) - assert_image_similar(im, target, 15) + assert_image_similar(im, im_target, 15) def test_center_14() -> None: @@ -87,22 +87,22 @@ def test_center_14() -> None: with Image.open("Tests/images/hopper_45.png") as target: target_origin = target.size[1] / 2 - 14 - target = target.crop((6, target_origin, 128 + 6, target_origin + 128)) + im_target = target.crop((6, target_origin, 128 + 6, target_origin + 128)) - assert_image_similar(im, target, 10) + assert_image_similar(im, im_target, 10) def test_translate() -> None: im = hopper() with Image.open("Tests/images/hopper_45.png") as target: target_origin = (target.size[1] / 2 - 64) - 5 - target = target.crop( + im_target = target.crop( (target_origin, target_origin, target_origin + 128, target_origin + 128) ) im = im.rotate(45, translate=(5, 5), resample=Image.Resampling.BICUBIC) - assert_image_similar(im, target, 1) + assert_image_similar(im, im_target, 1) def test_fastpath_center() -> None: diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index 1181f6fca..2ae230f3d 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -159,9 +159,9 @@ def test_reducing_gap_for_DCT_scaling() -> None: with Image.open("Tests/images/hopper.jpg") as ref: # thumbnail should call draft with reducing_gap scale ref.draft(None, (18 * 3, 18 * 3)) - ref = ref.resize((18, 18), Image.Resampling.BICUBIC) + im_ref = ref.resize((18, 18), Image.Resampling.BICUBIC) with Image.open("Tests/images/hopper.jpg") as im: im.thumbnail((18, 18), Image.Resampling.BICUBIC, reducing_gap=3.0) - assert_image_similar(ref, im, 1.4) + assert_image_similar(im_ref, im, 1.4) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 790acee2a..49765cd68 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -198,10 +198,10 @@ def test_bitmap() -> None: im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) with Image.open("Tests/images/pil123rgba.png") as small: - small = small.resize((50, 50), Image.Resampling.NEAREST) + small_resized = small.resize((50, 50), Image.Resampling.NEAREST) # Act - draw.bitmap((10, 10), small) + draw.bitmap((10, 10), small_resized) # Assert assert_image_equal_tofile(im, "Tests/images/imagedraw_bitmap.png") diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 27ac6f308..63cd0e4d4 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -261,10 +261,10 @@ def test_colorize_2color() -> None: # Open test image (256px by 10px, black to white) with Image.open("Tests/images/bw_gradient.png") as im: - im = im.convert("L") + im_l = im.convert("L") # Create image with original 2-color functionality - im_test = ImageOps.colorize(im, "red", "green") + im_test = ImageOps.colorize(im_l, "red", "green") # Test output image (2-color) left = (0, 1) @@ -301,11 +301,11 @@ def test_colorize_2color_offset() -> None: # Open test image (256px by 10px, black to white) with Image.open("Tests/images/bw_gradient.png") as im: - im = im.convert("L") + im_l = im.convert("L") # Create image with original 2-color functionality with offsets im_test = ImageOps.colorize( - im, black="red", white="green", blackpoint=50, whitepoint=100 + im_l, black="red", white="green", blackpoint=50, whitepoint=100 ) # Test output image (2-color) with offsets @@ -343,11 +343,11 @@ def test_colorize_3color_offset() -> None: # Open test image (256px by 10px, black to white) with Image.open("Tests/images/bw_gradient.png") as im: - im = im.convert("L") + im_l = im.convert("L") # Create image with new three color functionality with offsets im_test = ImageOps.colorize( - im, + im_l, black="red", white="green", mid="blue", diff --git a/Tests/test_pickle.py b/Tests/test_pickle.py index 54cef00ad..fc76f81e9 100644 --- a/Tests/test_pickle.py +++ b/Tests/test_pickle.py @@ -90,18 +90,18 @@ def test_pickle_la_mode_with_palette(tmp_path: Path) -> None: # Arrange filename = tmp_path / "temp.pkl" with Image.open("Tests/images/hopper.jpg") as im: - im = im.convert("PA") + im_pa = im.convert("PA") # Act / Assert for protocol in range(pickle.HIGHEST_PROTOCOL + 1): - im._mode = "LA" + im_pa._mode = "LA" with open(filename, "wb") as f: - pickle.dump(im, f, protocol) + pickle.dump(im_pa, f, protocol) with open(filename, "rb") as f: loaded_im = pickle.load(f) - im._mode = "PA" - assert im == loaded_im + im_pa._mode = "PA" + assert im_pa == loaded_im @skip_unless_feature("webp") diff --git a/Tests/test_shell_injection.py b/Tests/test_shell_injection.py index 465517bb6..a7e95ed83 100644 --- a/Tests/test_shell_injection.py +++ b/Tests/test_shell_injection.py @@ -49,11 +49,13 @@ class TestShellInjection: @pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") def test_save_netpbm_filename_bmp_mode(self, tmp_path: Path) -> None: with Image.open(TEST_GIF) as im: - im = im.convert("RGB") - self.assert_save_filename_check(tmp_path, im, GifImagePlugin._save_netpbm) + im_rgb = im.convert("RGB") + self.assert_save_filename_check( + tmp_path, im_rgb, GifImagePlugin._save_netpbm + ) @pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") def test_save_netpbm_filename_l_mode(self, tmp_path: Path) -> None: with Image.open(TEST_GIF) as im: - im = im.convert("L") - self.assert_save_filename_check(tmp_path, im, GifImagePlugin._save_netpbm) + im_l = im.convert("L") + self.assert_save_filename_check(tmp_path, im_l, GifImagePlugin._save_netpbm) From 4b90888a7db8953f3f56bf166672661b93343f5a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 20 Oct 2025 19:38:29 +1100 Subject: [PATCH 278/309] Added type hints --- Tests/test_file_gbr.py | 2 +- Tests/test_file_iptc.py | 2 +- src/PIL/ImageText.py | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Tests/test_file_gbr.py b/Tests/test_file_gbr.py index b8851d82b..d89ef0583 100644 --- a/Tests/test_file_gbr.py +++ b/Tests/test_file_gbr.py @@ -33,7 +33,7 @@ def test_multiple_load_operations() -> None: assert_image_equal_tofile(im, "Tests/images/gbr.png") -def create_gbr_image(info: dict[str, int] = {}, magic_number=b"") -> BytesIO: +def create_gbr_image(info: dict[str, int] = {}, magic_number: bytes = b"") -> BytesIO: return BytesIO( b"".join( _binary.o32be(i) diff --git a/Tests/test_file_iptc.py b/Tests/test_file_iptc.py index 5a8aaa3ef..0376b9997 100644 --- a/Tests/test_file_iptc.py +++ b/Tests/test_file_iptc.py @@ -12,7 +12,7 @@ TEST_FILE = "Tests/images/iptc.jpg" def create_iptc_image(info: dict[str, int] = {}) -> BytesIO: - def field(tag, value): + def field(tag: tuple[int, int], value: bytes) -> bytes: return bytes((0x1C,) + tag + (0, len(value))) + value data = field((3, 60), bytes((info.get("layers", 1), info.get("component", 0)))) diff --git a/src/PIL/ImageText.py b/src/PIL/ImageText.py index c74570e69..6c5a8a8a3 100644 --- a/src/PIL/ImageText.py +++ b/src/PIL/ImageText.py @@ -88,7 +88,7 @@ class Text: else: return "L" - def get_length(self): + def get_length(self) -> float: """ Returns length (in pixels with 1/64 precision) of text. @@ -130,8 +130,7 @@ class Text: :return: Either width for horizontal text, or height for vertical text. """ - split_character = "\n" if isinstance(self.text, str) else b"\n" - if split_character in self.text: + if "\n" in self.text if isinstance(self.text, str) else b"\n" in self.text: msg = "can't measure length of multiline text" raise ValueError(msg) return self.font.getlength( From e90bb1559cfd0ab18c2af653a1a8b1305bb0b2ca Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 20 Oct 2025 21:00:29 +1100 Subject: [PATCH 279/309] Rearranged code --- src/PIL/ImageText.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/PIL/ImageText.py b/src/PIL/ImageText.py index 6c5a8a8a3..8bfd89884 100644 --- a/src/PIL/ImageText.py +++ b/src/PIL/ImageText.py @@ -130,7 +130,11 @@ class Text: :return: Either width for horizontal text, or height for vertical text. """ - if "\n" in self.text if isinstance(self.text, str) else b"\n" in self.text: + if isinstance(self.text, str): + multiline = "\n" in self.text + else: + multiline = b"\n" in self.text + if multiline: msg = "can't measure length of multiline text" raise ValueError(msg) return self.font.getlength( From e1f4352ce9b3b92912f7a678666c9f5bca09101e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 21 Oct 2025 23:11:18 +1100 Subject: [PATCH 280/309] Fixed ZeroDivisionError --- Tests/images/zero_mask_totals.dds | Bin 0 -> 131 bytes Tests/test_file_dds.py | 5 +++++ src/PIL/DdsImagePlugin.py | 3 +++ 3 files changed, 8 insertions(+) create mode 100644 Tests/images/zero_mask_totals.dds diff --git a/Tests/images/zero_mask_totals.dds b/Tests/images/zero_mask_totals.dds new file mode 100644 index 0000000000000000000000000000000000000000..31e329e4f266b8c8407531b77e65fbd9c46b41a4 GIT binary patch literal 131 pcmZ>930A0KU|`@EU|?Vb(jd$X#N+@4pe6^XMhPg5LILf-0s!Yi0we$c literal 0 HcmV?d00001 diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 116dfa59c..60d0c09bc 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -380,6 +380,11 @@ def test_palette() -> None: assert_image_equal_tofile(im, "Tests/images/transparent.gif") +def test_zero_mask_totals() -> None: + with Image.open("Tests/images/zero_mask_totals.dds") as im: + im.load() + + def test_unsupported_header_size() -> None: with pytest.raises(OSError, match="Unsupported header size 0"): with Image.open(BytesIO(b"DDS " + b"\x00" * 4)): diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index f9ade18f9..37e16d527 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -333,6 +333,7 @@ class DdsImageFile(ImageFile.ImageFile): format_description = "DirectDraw Surface" def _open(self) -> None: + assert self.fp is not None if not _accept(self.fp.read(4)): msg = "not a DDS file" raise SyntaxError(msg) @@ -516,6 +517,8 @@ class DdsRgbDecoder(ImageFile.PyDecoder): # Remove the zero padding, and scale it to 8 bits data += o8( int(((masked_value >> mask_offsets[i]) / mask_totals[i]) * 255) + if mask_totals[i] + else 0 ) self.set_as_raw(data) return -1, 0 From 7d6f2ce90b688ef4dfd485cd5336ea52cdf48c77 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 21 Oct 2025 23:35:17 +1100 Subject: [PATCH 281/309] Removed BytesIO --- src/PIL/DdsImagePlugin.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index f9ade18f9..19a210275 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -12,7 +12,6 @@ https://creativecommons.org/publicdomain/zero/1.0/ from __future__ import annotations -import io import struct import sys from enum import IntEnum, IntFlag @@ -340,21 +339,20 @@ class DdsImageFile(ImageFile.ImageFile): if header_size != 124: msg = f"Unsupported header size {repr(header_size)}" raise OSError(msg) - header_bytes = self.fp.read(header_size - 4) - if len(header_bytes) != 120: - msg = f"Incomplete header: {len(header_bytes)} bytes" + header = self.fp.read(header_size - 4) + if len(header) != 120: + msg = f"Incomplete header: {len(header)} bytes" raise OSError(msg) - header = io.BytesIO(header_bytes) - flags, height, width = struct.unpack("<3I", header.read(12)) + flags, height, width = struct.unpack("<3I", header[:12]) self._size = (width, height) extents = (0, 0) + self.size - pitch, depth, mipmaps = struct.unpack("<3I", header.read(12)) - struct.unpack("<11I", header.read(44)) # reserved + pitch, depth, mipmaps = struct.unpack("<3I", header[12:24]) + struct.unpack("<11I", header[24:68]) # reserved # pixel format - pfsize, pfflags, fourcc, bitcount = struct.unpack("<4I", header.read(16)) + pfsize, pfflags, fourcc, bitcount = struct.unpack("<4I", header[68:84]) n = 0 rawmode = None if pfflags & DDPF.RGB: @@ -366,7 +364,7 @@ class DdsImageFile(ImageFile.ImageFile): self._mode = "RGB" mask_count = 3 - masks = struct.unpack(f"<{mask_count}I", header.read(mask_count * 4)) + masks = struct.unpack(f"<{mask_count}I", header[84 : 84 + mask_count * 4]) self.tile = [ImageFile._Tile("dds_rgb", extents, 0, (bitcount, masks))] return elif pfflags & DDPF.LUMINANCE: From b1e2f2e6528f92c0b763c86374ef782210a625fe Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 22 Oct 2025 20:08:22 +1100 Subject: [PATCH 282/309] Improved coverage --- Tests/test_imagetext.py | 11 +++++++++++ src/PIL/ImageText.py | 3 +-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Tests/test_imagetext.py b/Tests/test_imagetext.py index 7db229897..46afea064 100644 --- a/Tests/test_imagetext.py +++ b/Tests/test_imagetext.py @@ -32,6 +32,10 @@ def test_get_length(font: ImageFont.FreeTypeFont) -> None: assert ImageText.Text("y", font).get_length() == 12 assert ImageText.Text("a", font).get_length() == 12 + text = ImageText.Text("\n", font) + with pytest.raises(ValueError, match="can't measure length of multiline text"): + text.get_length() + def test_get_bbox(font: ImageFont.FreeTypeFont) -> None: assert ImageText.Text("A", font).get_bbox() == (0, 4, 12, 16) @@ -45,6 +49,7 @@ def test_standard_embedded_color(layout_engine: ImageFont.Layout) -> None: font = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine) text = ImageText.Text("Hello World!", font) text.embed_color() + assert text.get_length() == 288 im = Image.new("RGB", (300, 64), "white") draw = ImageDraw.Draw(im) @@ -52,6 +57,12 @@ def test_standard_embedded_color(layout_engine: ImageFont.Layout) -> None: assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 3.1) + text = ImageText.Text("", mode="1") + with pytest.raises( + ValueError, match="Embedded color supported only in RGB and RGBA modes" + ): + text.embed_color() + @skip_unless_feature("freetype2") def test_stroke() -> None: diff --git a/src/PIL/ImageText.py b/src/PIL/ImageText.py index 8bfd89884..e6ccd8243 100644 --- a/src/PIL/ImageText.py +++ b/src/PIL/ImageText.py @@ -316,6 +316,5 @@ class Text: max(bbox[3], bbox_line[3]), ) - if bbox is None: - return xy[0], xy[1], xy[0], xy[1] + assert bbox is not None return bbox From 208bbe95f9ccaf5659d5b246e18ce76bcefeea84 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 22 Oct 2025 22:22:00 +1100 Subject: [PATCH 283/309] Remove I;32L and I;32B modes --- src/libImaging/Mode.c | 1 - src/libImaging/Mode.h | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/libImaging/Mode.c b/src/libImaging/Mode.c index 7521f4cda..d6ee26131 100644 --- a/src/libImaging/Mode.c +++ b/src/libImaging/Mode.c @@ -20,7 +20,6 @@ const ModeData MODES[] = { [IMAGING_MODE_I_16] = {"I;16"}, [IMAGING_MODE_I_16L] = {"I;16L"}, [IMAGING_MODE_I_16B] = {"I;16B"}, [IMAGING_MODE_I_16N] = {"I;16N"}, - [IMAGING_MODE_I_32L] = {"I;32L"}, [IMAGING_MODE_I_32B] = {"I;32B"}, }; const ModeID diff --git a/src/libImaging/Mode.h b/src/libImaging/Mode.h index a3eb3d86d..8cb96c984 100644 --- a/src/libImaging/Mode.h +++ b/src/libImaging/Mode.h @@ -25,8 +25,6 @@ typedef enum { IMAGING_MODE_I_16L, IMAGING_MODE_I_16B, IMAGING_MODE_I_16N, - IMAGING_MODE_I_32L, - IMAGING_MODE_I_32B, } ModeID; typedef struct { @@ -64,8 +62,6 @@ typedef enum { IMAGING_RAWMODE_I_16L, IMAGING_RAWMODE_I_16B, IMAGING_RAWMODE_I_16N, - IMAGING_RAWMODE_I_32L, - IMAGING_RAWMODE_I_32B, // Rawmodes IMAGING_RAWMODE_1_8, @@ -106,6 +102,8 @@ typedef enum { IMAGING_RAWMODE_C_I, IMAGING_RAWMODE_Cb, IMAGING_RAWMODE_Cr, + IMAGING_RAWMODE_I_32B, + IMAGING_RAWMODE_I_32L, IMAGING_RAWMODE_F_16, IMAGING_RAWMODE_F_16B, IMAGING_RAWMODE_F_16BS, From 109ee1569ddc3156229dfc4b683d252409afe51f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 22 Oct 2025 22:24:15 +1100 Subject: [PATCH 284/309] Removed I;32L rawmode --- src/libImaging/Mode.c | 1 - src/libImaging/Mode.h | 1 - 2 files changed, 2 deletions(-) diff --git a/src/libImaging/Mode.c b/src/libImaging/Mode.c index d6ee26131..2e459c48f 100644 --- a/src/libImaging/Mode.c +++ b/src/libImaging/Mode.c @@ -75,7 +75,6 @@ const RawModeData RAWMODES[] = { [IMAGING_RAWMODE_I_16L] = {"I;16L"}, [IMAGING_RAWMODE_I_16B] = {"I;16B"}, [IMAGING_RAWMODE_I_16N] = {"I;16N"}, - [IMAGING_RAWMODE_I_32L] = {"I;32L"}, [IMAGING_RAWMODE_I_32B] = {"I;32B"}, [IMAGING_RAWMODE_1_8] = {"1;8"}, diff --git a/src/libImaging/Mode.h b/src/libImaging/Mode.h index 8cb96c984..39c0eb919 100644 --- a/src/libImaging/Mode.h +++ b/src/libImaging/Mode.h @@ -103,7 +103,6 @@ typedef enum { IMAGING_RAWMODE_Cb, IMAGING_RAWMODE_Cr, IMAGING_RAWMODE_I_32B, - IMAGING_RAWMODE_I_32L, IMAGING_RAWMODE_F_16, IMAGING_RAWMODE_F_16B, IMAGING_RAWMODE_F_16BS, From b04d8792f5779b24c3c723dd368a4d43d7609276 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 23 Oct 2025 08:53:00 +1100 Subject: [PATCH 285/309] Support writing InkNames --- Tests/test_file_libtiff.py | 12 ++++++++++++ src/PIL/TiffTags.py | 1 - src/encode.c | 11 ++++++----- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 4908496cf..e53832db3 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -355,6 +355,18 @@ class TestFileLibTiff(LibTiffTestCase): # Should not segfault im.save(outfile) + def test_inknames_tag( + self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path + ) -> None: + monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True) + + out = tmp_path / "temp.tif" + hopper("L").save(out, tiffinfo={333: "name\x00"}) + + with Image.open(out) as reloaded: + assert isinstance(reloaded, TiffImagePlugin.TiffImageFile) + assert reloaded.tag_v2[333] in ("name", "name\x00") + def test_whitepoint_tag( self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py index 761aa3f6b..613a3b7de 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -558,7 +558,6 @@ LIBTIFF_CORE = { LIBTIFF_CORE.remove(255) # We don't have support for subfiletypes LIBTIFF_CORE.remove(322) # We don't have support for writing tiled images with libtiff LIBTIFF_CORE.remove(323) # Tiled images -LIBTIFF_CORE.remove(333) # Ink Names either # Note to advanced users: There may be combinations of these # parameters and values that when added properly, will work and diff --git a/src/encode.c b/src/encode.c index b1d0181e0..f0e204bc6 100644 --- a/src/encode.c +++ b/src/encode.c @@ -668,10 +668,10 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { int key_int, status, is_core_tag, is_var_length, num_core_tags, i; TIFFDataType type = TIFF_NOTYPE; // This list also exists in TiffTags.py - const int core_tags[] = {256, 257, 258, 259, 262, 263, 266, 269, 274, - 277, 278, 280, 281, 340, 341, 282, 283, 284, - 286, 287, 296, 297, 320, 321, 338, 32995, 32998, - 32996, 339, 32997, 330, 531, 530, 65537, 301, 532}; + const int core_tags[] = {256, 257, 258, 259, 262, 263, 266, 269, 274, 277, + 278, 280, 281, 282, 283, 284, 286, 287, 296, 297, + 301, 320, 321, 330, 333, 338, 339, 340, 341, 530, + 531, 532, 32995, 32996, 32997, 32998, 65537}; Py_ssize_t tags_size; PyObject *item; @@ -821,7 +821,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { } } - if (type == TIFF_BYTE || type == TIFF_UNDEFINED) { + if (type == TIFF_BYTE || type == TIFF_UNDEFINED || + key_int == TIFFTAG_INKNAMES) { status = ImagingLibTiffSetField( &encoder->state, (ttag_t)key_int, From ddd4f007209a3af7f8976322c13c97f205cc4f06 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 23 Oct 2025 20:03:14 +1100 Subject: [PATCH 286/309] Support writing IFD tag types --- Tests/test_file_libtiff.py | 14 ++++++++++++++ src/encode.c | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index e53832db3..38e4111a1 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -355,6 +355,20 @@ class TestFileLibTiff(LibTiffTestCase): # Should not segfault im.save(outfile) + def test_ifd(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: + monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True) + + ifd = TiffImagePlugin.ImageFileDirectory_v2() + ifd[37000] = 100 + ifd.tagtype[37000] = TiffTags.IFD + + out = tmp_path / "temp.tif" + im = Image.new("L", (1, 1)) + im.save(out, tiffinfo=ifd) + + with Image.open(out) as reloaded: + assert reloaded.tag_v2[37000] == 100 + def test_inknames_tag( self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: diff --git a/src/encode.c b/src/encode.c index f0e204bc6..2e9ef843d 100644 --- a/src/encode.c +++ b/src/encode.c @@ -974,7 +974,7 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { status = ImagingLibTiffSetField( &encoder->state, (ttag_t)key_int, (UINT16)PyLong_AsLong(value) ); - } else if (type == TIFF_LONG) { + } else if (type == TIFF_LONG || type == TIFF_IFD) { status = ImagingLibTiffSetField( &encoder->state, (ttag_t)key_int, (UINT32)PyLong_AsLong(value) ); From 82cdaa456c88139a2d8d6d23cac8b9dacf4dc75f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 24 Oct 2025 03:55:45 +1100 Subject: [PATCH 287/309] Support writing SIGNED_RATIONAL tag types --- Tests/test_file_libtiff.py | 7 +++++-- src/encode.c | 7 ++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 38e4111a1..7cb3ea8e4 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -355,12 +355,15 @@ class TestFileLibTiff(LibTiffTestCase): # Should not segfault im.save(outfile) - def test_ifd(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: + @pytest.mark.parametrize("tagtype", (TiffTags.SIGNED_RATIONAL, TiffTags.IFD)) + def test_tag_type( + self, tagtype: int, monkeypatch: pytest.MonkeyPatch, tmp_path: Path + ) -> None: monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True) ifd = TiffImagePlugin.ImageFileDirectory_v2() ifd[37000] = 100 - ifd.tagtype[37000] = TiffTags.IFD + ifd.tagtype[37000] = tagtype out = tmp_path / "temp.tif" im = Image.new("L", (1, 1)) diff --git a/src/encode.c b/src/encode.c index 2e9ef843d..513309c8d 100644 --- a/src/encode.c +++ b/src/encode.c @@ -990,10 +990,6 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { status = ImagingLibTiffSetField( &encoder->state, (ttag_t)key_int, (FLOAT32)PyFloat_AsDouble(value) ); - } else if (type == TIFF_DOUBLE) { - status = ImagingLibTiffSetField( - &encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value) - ); } else if (type == TIFF_SBYTE) { status = ImagingLibTiffSetField( &encoder->state, (ttag_t)key_int, (INT8)PyLong_AsLong(value) @@ -1002,7 +998,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { status = ImagingLibTiffSetField( &encoder->state, (ttag_t)key_int, PyBytes_AsString(value) ); - } else if (type == TIFF_RATIONAL) { + } else if (type == TIFF_DOUBLE || type == TIFF_SRATIONAL || + type == TIFF_RATIONAL) { status = ImagingLibTiffSetField( &encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value) ); From 29c5ffe7451bc981925a9c4f8cb294d69e086d32 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 02:48:10 +0000 Subject: [PATCH 288/309] Update github-actions --- .github/workflows/cifuzz.yml | 4 ++-- .github/workflows/test-windows.yml | 2 +- .github/workflows/test.yml | 2 +- .github/workflows/wheels.yml | 14 +++++++------- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index 0456bbaba..6a86b8aeb 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -44,13 +44,13 @@ jobs: language: python dry-run: false - name: Upload New Crash - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 if: failure() && steps.build.outcome == 'success' with: name: artifacts path: ./out/artifacts - name: Upload Legacy Crash - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 if: steps.run.outcome == 'success' with: name: crash diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index f6a7dd46b..02d4da999 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -216,7 +216,7 @@ jobs: shell: bash - name: Upload errors - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 if: failure() with: name: errors diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b52000a27..ef7b34b8d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -140,7 +140,7 @@ jobs: mkdir -p Tests/errors - name: Upload errors - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 if: failure() with: name: errors diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 6dc8db7e9..4bc48bec9 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -134,7 +134,7 @@ jobs: CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }} MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }} - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v5 with: name: dist-${{ matrix.name }} path: ./wheelhouse/*.whl @@ -220,13 +220,13 @@ jobs: shell: cmd - name: Upload wheels - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: dist-windows-${{ matrix.cibw_arch }} path: ./wheelhouse/*.whl - name: Upload fribidi.dll - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: fribidi-windows-${{ matrix.cibw_arch }} path: winbuild\build\bin\fribidi* @@ -246,7 +246,7 @@ jobs: - run: make sdist - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v5 with: name: dist-sdist path: dist/*.tar.gz @@ -256,7 +256,7 @@ jobs: runs-on: ubuntu-latest name: Count dists steps: - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: pattern: dist-* path: dist @@ -275,7 +275,7 @@ jobs: runs-on: ubuntu-latest name: Upload wheels to scientific-python-nightly-wheels steps: - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: pattern: dist-!(sdist)* path: dist @@ -297,7 +297,7 @@ jobs: permissions: id-token: write steps: - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: pattern: dist-* path: dist From dfd24ba6150ea3803099d445ce1a486fb7e87c13 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 30 Oct 2025 22:03:39 +1100 Subject: [PATCH 289/309] Read all non-zero transparency from mode 1 images in the same way --- Tests/test_file_png.py | 9 +++++++++ src/PIL/PngImagePlugin.py | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index dc1077fed..9875fe096 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -338,6 +338,15 @@ class TestFilePng: assert colors is not None assert colors[0][0] == num_transparent + def test_save_1_transparency(self, tmp_path: Path) -> None: + out = tmp_path / "temp.png" + + im = Image.new("1", (1, 1), 1) + im.save(out, transparency=1) + + with Image.open(out) as reloaded: + assert reloaded.info["transparency"] == 255 + def test_save_rgb_single_transparency(self, tmp_path: Path) -> None: in_file = "Tests/images/caption_6_33_22.png" with Image.open(in_file) as im: diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index d0f22f812..11a48e55c 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -509,7 +509,9 @@ class PngStream(ChunkStream): # otherwise, we have a byte string with one alpha value # for each palette entry self.im_info["transparency"] = s - elif self.im_mode in ("1", "L", "I;16"): + elif self.im_mode == "1": + self.im_info["transparency"] = 255 if i16(s) else 0 + elif self.im_mode in ("L", "I;16"): self.im_info["transparency"] = i16(s) elif self.im_mode == "RGB": self.im_info["transparency"] = i16(s), i16(s, 2), i16(s, 4) From 1a27f958d7d51e9fef68493d68a29c423e5f6e38 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 31 Oct 2025 18:19:05 +1100 Subject: [PATCH 290/309] Updated brotli to 1.2.0 --- .github/workflows/wheels-dependencies.sh | 9 ++--- .pre-commit-config.yaml | 6 ++-- MANIFEST.in | 1 - patches/README.md | 14 -------- patches/iOS/brotli-1.1.0.tar.gz.patch | 46 ------------------------ winbuild/build_prepare.py | 2 +- 6 files changed, 7 insertions(+), 71 deletions(-) delete mode 100644 patches/README.md delete mode 100644 patches/iOS/brotli-1.1.0.tar.gz.patch diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 7d6eb8681..cdc1faf15 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -32,7 +32,6 @@ if [[ "$CIBW_PLATFORM" == "ios" ]]; then # or `build/deps/iphonesimulator` WORKDIR=$(pwd)/build/$IOS_SDK BUILD_PREFIX=$(pwd)/build/deps/$IOS_SDK - PATCH_DIR=$(pwd)/patches/iOS # GNU tooling insists on using aarch64 rather than arm64 if [[ $PLAT == "arm64" ]]; then @@ -90,9 +89,7 @@ fi ARCHIVE_SDIR=pillow-depends-main -# Package versions for fresh source builds. Version numbers with "Patched" -# annotations have a source code patch that is required for some platforms. If -# you change those versions, ensure the patch is also updated. +# Package versions for fresh source builds. if [[ -n "$IOS_SDK" ]]; then FREETYPE_VERSION=2.13.3 else @@ -110,7 +107,7 @@ ZLIB_NG_VERSION=2.2.5 LIBWEBP_VERSION=1.6.0 BZIP2_VERSION=1.0.8 LIBXCB_VERSION=1.17.0 -BROTLI_VERSION=1.1.0 # Patched; next release won't need patching. See patch file. +BROTLI_VERSION=1.2.0 LIBAVIF_VERSION=1.3.0 function build_pkg_config { @@ -168,7 +165,7 @@ function build_brotli { if [ -e brotli-stamp ]; then return; fi local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz) (cd $out_dir \ - && cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib $HOST_CMAKE_FLAGS . \ + && cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib -DCMAKE_MACOSX_BUNDLE=OFF $HOST_CMAKE_FLAGS . \ && make -j4 install) touch brotli-stamp } diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ab0153687..b9f7e599b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: rev: v1.5.5 hooks: - id: remove-tabs - exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$|\.patch$) + exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$) - repo: https://github.com/pre-commit/mirrors-clang-format rev: v21.1.2 @@ -46,9 +46,9 @@ repos: - id: check-yaml args: [--allow-multiple-documents] - id: end-of-file-fixer - exclude: ^Tests/images/|\.patch$ + exclude: ^Tests/images/ - id: trailing-whitespace - exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/|\.patch$ + exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/ - repo: https://github.com/python-jsonschema/check-jsonschema rev: 0.34.0 diff --git a/MANIFEST.in b/MANIFEST.in index 6623f227d..d4623a4a8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -15,7 +15,6 @@ include tox.ini graft Tests graft Tests/images graft checks -graft patches graft src graft depends graft winbuild diff --git a/patches/README.md b/patches/README.md deleted file mode 100644 index ff4a8f099..000000000 --- a/patches/README.md +++ /dev/null @@ -1,14 +0,0 @@ -Although we try to use official sources for dependencies, sometimes the official -sources don't support a platform (especially mobile platforms), or there's a bug -fix/feature that is required to support Pillow's usage. - -This folder contains patches that must be applied to official sources, organized -by the platforms that need those patches. - -Each patch is against the root of the unpacked official tarball, and is named by -appending `.patch` to the end of the tarball that is to be patched. This -includes the full version number; so if the version is bumped, the patch will -at a minimum require a filename change. - -Wherever possible, these patches should be contributed upstream, in the hope that -future Pillow versions won't need to maintain these patches. diff --git a/patches/iOS/brotli-1.1.0.tar.gz.patch b/patches/iOS/brotli-1.1.0.tar.gz.patch deleted file mode 100644 index f165a9ac1..000000000 --- a/patches/iOS/brotli-1.1.0.tar.gz.patch +++ /dev/null @@ -1,46 +0,0 @@ -# Brotli 1.1.0 doesn't have explicit support for iOS as a CMAKE_SYSTEM_NAME. -# That release was from 2023; there have been subsequent changes that allow -# Brotli to build on iOS without any patches, as long as -DBROTLI_BUILD_TOOLS=NO -# is specified on the command line. -# -diff -ru brotli-1.1.0-orig/CMakeLists.txt brotli-1.1.0/CMakeLists.txt ---- brotli-1.1.0-orig/CMakeLists.txt 2023-08-29 19:00:29 -+++ brotli-1.1.0/CMakeLists.txt 2024-11-07 10:46:26 -@@ -114,6 +114,8 @@ - add_definitions(-DOS_MACOSX) - set(CMAKE_MACOS_RPATH TRUE) - set(CMAKE_INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/lib") -+elseif(${CMAKE_SYSTEM_NAME} MATCHES "iOS") -+ add_definitions(-DOS_IOS) - endif() - - if(BROTLI_EMSCRIPTEN) -@@ -174,10 +176,12 @@ - - # Installation - if(NOT BROTLI_BUNDLED_MODE) -- install( -- TARGETS brotli -- RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" -- ) -+ if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "iOS") -+ install( -+ TARGETS brotli -+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" -+ ) -+ endif() - - install( - TARGETS ${BROTLI_LIBRARIES_CORE} -diff -ru brotli-1.1.0-orig/c/common/platform.h brotli-1.1.0/c/common/platform.h ---- brotli-1.1.0-orig/c/common/platform.h 2023-08-29 19:00:29 -+++ brotli-1.1.0/c/common/platform.h 2024-11-07 10:47:28 -@@ -33,7 +33,7 @@ - #include - #elif defined(OS_FREEBSD) - #include --#elif defined(OS_MACOSX) -+#elif defined(OS_MACOSX) || defined(OS_IOS) - #include - /* Let's try and follow the Linux convention */ - #define BROTLI_X_BYTE_ORDER BYTE_ORDER diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 186a80cca..1277e404f 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -113,7 +113,7 @@ ARCHITECTURES = { } V = { - "BROTLI": "1.1.0", + "BROTLI": "1.2.0", "FREETYPE": "2.14.1", "FRIBIDI": "1.0.16", "HARFBUZZ": "12.1.0", From b3d9bd9950d9806ef904566896228d37df824821 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 3 Nov 2025 23:07:15 +1100 Subject: [PATCH 291/309] Test ImageFont.ImageFont, in case freetype2 is not supported --- Tests/test_imagetext.py | 75 ++++++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 24 deletions(-) diff --git a/Tests/test_imagetext.py b/Tests/test_imagetext.py index 46afea064..2b424629d 100644 --- a/Tests/test_imagetext.py +++ b/Tests/test_imagetext.py @@ -2,7 +2,7 @@ from __future__ import annotations import pytest -from PIL import Image, ImageDraw, ImageFont, ImageText +from PIL import Image, ImageDraw, ImageFont, ImageText, features from .helper import assert_image_similar_tofile, skip_unless_feature @@ -20,42 +20,69 @@ def layout_engine(request: pytest.FixtureRequest) -> ImageFont.Layout: return request.param -@pytest.fixture(scope="module") -def font(layout_engine: ImageFont.Layout) -> ImageFont.FreeTypeFont: - return ImageFont.truetype(FONT_PATH, 20, layout_engine=layout_engine) +@pytest.fixture( + scope="module", + params=[ + None, + pytest.param(ImageFont.Layout.BASIC, marks=skip_unless_feature("freetype2")), + pytest.param(ImageFont.Layout.RAQM, marks=skip_unless_feature("raqm")), + ], +) +def font( + request: pytest.FixtureRequest, +) -> ImageFont.ImageFont | ImageFont.FreeTypeFont: + layout_engine = request.param + if layout_engine is None: + return ImageFont.load_default_imagefont() + else: + return ImageFont.truetype(FONT_PATH, 20, layout_engine=layout_engine) -def test_get_length(font: ImageFont.FreeTypeFont) -> None: - assert ImageText.Text("A", font).get_length() == 12 - assert ImageText.Text("AB", font).get_length() == 24 - assert ImageText.Text("M", font).get_length() == 12 - assert ImageText.Text("y", font).get_length() == 12 - assert ImageText.Text("a", font).get_length() == 12 +def test_get_length(font: ImageFont.ImageFont | ImageFont.FreeTypeFont) -> None: + factor = 1 if isinstance(font, ImageFont.ImageFont) else 2 + assert ImageText.Text("A", font).get_length() == 6 * factor + assert ImageText.Text("AB", font).get_length() == 12 * factor + assert ImageText.Text("M", font).get_length() == 6 * factor + assert ImageText.Text("y", font).get_length() == 6 * factor + assert ImageText.Text("a", font).get_length() == 6 * factor text = ImageText.Text("\n", font) with pytest.raises(ValueError, match="can't measure length of multiline text"): text.get_length() -def test_get_bbox(font: ImageFont.FreeTypeFont) -> None: - assert ImageText.Text("A", font).get_bbox() == (0, 4, 12, 16) - assert ImageText.Text("AB", font).get_bbox() == (0, 4, 24, 16) - assert ImageText.Text("M", font).get_bbox() == (0, 4, 12, 16) - assert ImageText.Text("y", font).get_bbox() == (0, 7, 12, 20) - assert ImageText.Text("a", font).get_bbox() == (0, 7, 12, 16) +@pytest.mark.parametrize( + "text, expected", + ( + ("A", (0, 4, 12, 16)), + ("AB", (0, 4, 24, 16)), + ("M", (0, 4, 12, 16)), + ("y", (0, 7, 12, 20)), + ("a", (0, 7, 12, 16)), + ), +) +def test_get_bbox( + font: ImageFont.ImageFont | ImageFont.FreeTypeFont, + text: str, + expected: tuple[int, int, int, int], +) -> None: + if isinstance(font, ImageFont.ImageFont): + expected = (0, 0, expected[2] // 2, 11) + assert ImageText.Text(text, font).get_bbox() == expected def test_standard_embedded_color(layout_engine: ImageFont.Layout) -> None: - font = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine) - text = ImageText.Text("Hello World!", font) - text.embed_color() - assert text.get_length() == 288 + if features.check_module("freetype2"): + font = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine) + text = ImageText.Text("Hello World!", font) + text.embed_color() + assert text.get_length() == 288 - im = Image.new("RGB", (300, 64), "white") - draw = ImageDraw.Draw(im) - draw.text((10, 10), text, "#fa6") + im = Image.new("RGB", (300, 64), "white") + draw = ImageDraw.Draw(im) + draw.text((10, 10), text, "#fa6") - assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 3.1) + assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 3.1) text = ImageText.Text("", mode="1") with pytest.raises( From 85d783fb52cd93584da76018baec888cbf680f6d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 17:20:17 +0000 Subject: [PATCH 292/309] [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.13.3 → v0.14.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.13.3...v0.14.3) - [github.com/python-jsonschema/check-jsonschema: 0.34.0 → 0.34.1](https://github.com/python-jsonschema/check-jsonschema/compare/0.34.0...0.34.1) - [github.com/zizmorcore/zizmor-pre-commit: v1.14.2 → v1.16.2](https://github.com/zizmorcore/zizmor-pre-commit/compare/v1.14.2...v1.16.2) - [github.com/sphinx-contrib/sphinx-lint: v1.0.0 → v1.0.1](https://github.com/sphinx-contrib/sphinx-lint/compare/v1.0.0...v1.0.1) - [github.com/tox-dev/pyproject-fmt: v2.7.0 → v2.11.0](https://github.com/tox-dev/pyproject-fmt/compare/v2.7.0...v2.11.0) - [github.com/tox-dev/tox-ini-fmt: 1.6.0 → 1.7.0](https://github.com/tox-dev/tox-ini-fmt/compare/1.6.0...1.7.0) --- .pre-commit-config.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ab0153687..beef225fd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.13.3 + rev: v0.14.3 hooks: - id: ruff-check args: [--exit-non-zero-on-fix] @@ -51,24 +51,24 @@ repos: exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/|\.patch$ - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.34.0 + rev: 0.34.1 hooks: - id: check-github-workflows - id: check-readthedocs - id: check-renovate - repo: https://github.com/zizmorcore/zizmor-pre-commit - rev: v1.14.2 + rev: v1.16.2 hooks: - id: zizmor - repo: https://github.com/sphinx-contrib/sphinx-lint - rev: v1.0.0 + rev: v1.0.1 hooks: - id: sphinx-lint - repo: https://github.com/tox-dev/pyproject-fmt - rev: v2.7.0 + rev: v2.11.0 hooks: - id: pyproject-fmt @@ -79,7 +79,7 @@ repos: additional_dependencies: [trove-classifiers>=2024.10.12] - repo: https://github.com/tox-dev/tox-ini-fmt - rev: 1.6.0 + rev: 1.7.0 hooks: - id: tox-ini-fmt From 666dd5247819ceec8b772758f2d0d2d1e9ec0572 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 3 Nov 2025 19:56:21 +0200 Subject: [PATCH 293/309] Drop removed rule --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0006ccd12..f4514925d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -189,7 +189,6 @@ lint.ignore = [ "PT012", # pytest-raises-with-multiple-statements "PT017", # pytest-assert-in-except "PYI034", # flake8-pyi: typing.Self added in Python 3.11 - "UP038", # pyupgrade: deprecated rule ] lint.per-file-ignores."Tests/oss-fuzz/fuzz_font.py" = [ "I002", From e44ce2f00e6c1fd8fffa30b812aa522e5c16fbdb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 5 Nov 2025 19:09:49 +1100 Subject: [PATCH 294/309] Updated harfbuzz to 12.2.0 --- .github/workflows/wheels-dependencies.sh | 2 +- winbuild/build_prepare.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 7d6eb8681..226fcdb6a 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -98,7 +98,7 @@ if [[ -n "$IOS_SDK" ]]; then else FREETYPE_VERSION=2.14.1 fi -HARFBUZZ_VERSION=12.1.0 +HARFBUZZ_VERSION=12.2.0 LIBPNG_VERSION=1.6.50 JPEGTURBO_VERSION=3.1.2 OPENJPEG_VERSION=2.5.4 diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 186a80cca..2401dd4ec 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -116,7 +116,7 @@ V = { "BROTLI": "1.1.0", "FREETYPE": "2.14.1", "FRIBIDI": "1.0.16", - "HARFBUZZ": "12.1.0", + "HARFBUZZ": "12.2.0", "JPEGTURBO": "3.1.2", "LCMS2": "2.17", "LIBAVIF": "1.3.0", From 18c7f87fe3180e494c9397d63f917aa9f05d870c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 5 Nov 2025 23:21:46 +1100 Subject: [PATCH 295/309] Added Fedora 43 --- .github/workflows/test-docker.yml | 1 + docs/installation/platform-support.rst | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 581e1f52b..213062ee2 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -50,6 +50,7 @@ jobs: debian-13-trixie-x86, debian-13-trixie-amd64, fedora-42-amd64, + fedora-43-amd64, gentoo, ubuntu-22.04-jammy-amd64, ubuntu-24.04-noble-amd64, diff --git a/docs/installation/platform-support.rst b/docs/installation/platform-support.rst index e0c4a8eec..17e38719a 100644 --- a/docs/installation/platform-support.rst +++ b/docs/installation/platform-support.rst @@ -35,6 +35,8 @@ These platforms are built and tested for every change. +----------------------------------+----------------------------+---------------------+ | Fedora 42 | 3.13 | x86-64 | +----------------------------------+----------------------------+---------------------+ +| Fedora 43 | 3.14 | x86-64 | ++----------------------------------+----------------------------+---------------------+ | Gentoo | 3.12 | x86-64 | +----------------------------------+----------------------------+---------------------+ | macOS 15 Sequoia | 3.10 | x86-64 | From 142c1320b23fa645dd115e8b979407518e3cd696 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 14 Nov 2025 20:08:49 +1100 Subject: [PATCH 296/309] Apply encoder options when saving multiple PNG frames --- Tests/test_file_apng.py | 20 ++++++++++++++++++++ src/PIL/PngImagePlugin.py | 25 ++++++++++++++----------- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index 12204b5b7..d918a24a7 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -1,5 +1,6 @@ from __future__ import annotations +from io import BytesIO from pathlib import Path import pytest @@ -718,6 +719,25 @@ def test_apng_save_size(tmp_path: Path) -> None: assert reloaded.size == (200, 200) +def test_compress_level() -> None: + compress_level_sizes = {} + for compress_level in (0, 9): + out = BytesIO() + + im = Image.new("L", (100, 100)) + im.save( + out, + "PNG", + save_all=True, + append_images=[Image.new("L", (200, 200))], + compress_level=compress_level, + ) + + compress_level_sizes[compress_level] = len(out.getvalue()) + + assert compress_level_sizes[0] > compress_level_sizes[9] + + def test_seek_after_close() -> None: im = Image.open("Tests/images/apng/delay.png") im.seek(1) diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index d0f22f812..b89c10da3 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1152,6 +1152,15 @@ class _fdat: self.seq_num += 1 +def _apply_encoderinfo(im: Image.Image, encoderinfo: dict[str, Any]) -> None: + im.encoderconfig = ( + encoderinfo.get("optimize", False), + encoderinfo.get("compress_level", -1), + encoderinfo.get("compress_type", -1), + encoderinfo.get("dictionary", b""), + ) + + class _Frame(NamedTuple): im: Image.Image bbox: tuple[int, int, int, int] | None @@ -1245,10 +1254,10 @@ def _write_multiple_frames( # default image IDAT (if it exists) if default_image: - if im.mode != mode: - im = im.convert(mode) + default_im = im if im.mode == mode else im.convert(mode) + _apply_encoderinfo(default_im, im.encoderinfo) ImageFile._save( - im, + default_im, cast(IO[bytes], _idat(fp, chunk)), [ImageFile._Tile("zip", (0, 0) + im.size, 0, rawmode)], ) @@ -1282,6 +1291,7 @@ def _write_multiple_frames( ) seq_num += 1 # frame data + _apply_encoderinfo(im_frame, im.encoderinfo) if frame == 0 and not default_image: # first frame must be in IDAT chunks for backwards compatibility ImageFile._save( @@ -1357,14 +1367,6 @@ def _save( bits = 4 outmode += f";{bits}" - # encoder options - im.encoderconfig = ( - im.encoderinfo.get("optimize", False), - im.encoderinfo.get("compress_level", -1), - im.encoderinfo.get("compress_type", -1), - im.encoderinfo.get("dictionary", b""), - ) - # get the corresponding PNG mode try: rawmode, bit_depth, color_type = _OUTMODES[outmode] @@ -1494,6 +1496,7 @@ def _save( im, fp, chunk, mode, rawmode, default_image, append_images ) if single_im: + _apply_encoderinfo(single_im, im.encoderinfo) ImageFile._save( single_im, cast(IO[bytes], _idat(fp, chunk)), From bc1237ef3d22f7407fbeb3de19c1c867ed5dfd0a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 14 Nov 2025 21:22:58 +1100 Subject: [PATCH 297/309] Update dependency cibuildwheel to v3.3.0 --- .ci/requirements-cibw.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/requirements-cibw.txt b/.ci/requirements-cibw.txt index 56517374f..485866de6 100644 --- a/.ci/requirements-cibw.txt +++ b/.ci/requirements-cibw.txt @@ -1 +1 @@ -cibuildwheel==3.2.1 +cibuildwheel==3.3.0 From 6107b9e82d93b29ca86a4261eec832de48ee45f8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 15 Nov 2025 07:41:59 +1100 Subject: [PATCH 298/309] Update libimagequant to 4.4.1 --- depends/install_imagequant.sh | 2 +- docs/installation/building-from-source.rst | 2 +- winbuild/build_prepare.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/depends/install_imagequant.sh b/depends/install_imagequant.sh index 357214f1f..de63abdec 100755 --- a/depends/install_imagequant.sh +++ b/depends/install_imagequant.sh @@ -2,7 +2,7 @@ # install libimagequant archive_name=libimagequant -archive_version=4.4.0 +archive_version=4.4.1 archive=$archive_name-$archive_version diff --git a/docs/installation/building-from-source.rst b/docs/installation/building-from-source.rst index 6080d29af..4349f9804 100644 --- a/docs/installation/building-from-source.rst +++ b/docs/installation/building-from-source.rst @@ -64,7 +64,7 @@ Many of Pillow's features require external libraries: * **libimagequant** provides improved color quantization - * Pillow has been tested with libimagequant **2.6-4.4.0** + * Pillow has been tested with libimagequant **2.6-4.4.1** * Libimagequant is licensed GPLv3, which is more restrictive than the Pillow license, therefore we will not be distributing binaries with libimagequant support enabled. diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 186a80cca..6cbc5c1e1 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -120,7 +120,7 @@ V = { "JPEGTURBO": "3.1.2", "LCMS2": "2.17", "LIBAVIF": "1.3.0", - "LIBIMAGEQUANT": "4.4.0", + "LIBIMAGEQUANT": "4.4.1", "LIBPNG": "1.6.50", "LIBWEBP": "1.6.0", "OPENJPEG": "2.5.4", From 88247a9ef38d8eba9610393ddfe34616e108257d Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Wed, 19 Nov 2025 21:31:27 +1100 Subject: [PATCH 299/309] Updated version --- docs/reference/ImageGrab.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/ImageGrab.rst b/docs/reference/ImageGrab.rst index e7dd41de1..413866785 100644 --- a/docs/reference/ImageGrab.rst +++ b/docs/reference/ImageGrab.rst @@ -48,7 +48,7 @@ or the clipboard to a PIL image memory. CGWindowID. .. versionadded:: 11.2.1 Windows support - .. versionadded:: 12.0.0 macOS support + .. versionadded:: 12.1.0 macOS support :return: An image .. py:function:: grabclipboard() From cce73b1e892a14d8d48146ac6ad3500be4d40d44 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 19 Nov 2025 21:52:21 +1100 Subject: [PATCH 300/309] Close image on ImageFont exception --- src/PIL/ImageFont.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 92eb763a5..2e8ace98d 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -127,11 +127,15 @@ class ImageFont: def _load_pilfont_data(self, file: IO[bytes], image: Image.Image) -> None: # check image if image.mode not in ("1", "L"): + image.close() + msg = "invalid font image mode" raise TypeError(msg) # read PILfont header if file.read(8) != b"PILfont\n": + image.close() + msg = "Not a PILfont file" raise SyntaxError(msg) file.readline() From 7055937eb15a66209ebf8f5e275e58cbd56ca629 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 22 Nov 2025 17:47:09 +1100 Subject: [PATCH 301/309] Updated Ubuntu version --- docs/installation/building-from-source.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/building-from-source.rst b/docs/installation/building-from-source.rst index 6080d29af..40e2a1938 100644 --- a/docs/installation/building-from-source.rst +++ b/docs/installation/building-from-source.rst @@ -116,7 +116,7 @@ Many of Pillow's features require external libraries: .. Note:: ``redhat-rpm-config`` is required on Fedora 23, but not earlier versions. - Prerequisites for **Ubuntu 16.04 LTS - 22.04 LTS** are installed with:: + Prerequisites for **Ubuntu 16.04 LTS - 24.04 LTS** are installed with:: sudo apt-get install libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev \ libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \ From 8814d42fd96ea8c86e5aa3bc4970066ade5de489 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 29 Nov 2025 14:24:43 +1100 Subject: [PATCH 302/309] Update zlib-ng to 2.3.1, except on manylinux2014 aarch64 --- .github/workflows/wheels-dependencies.sh | 21 ++++++++++----------- winbuild/build_prepare.py | 6 +++--- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 226fcdb6a..194c51a94 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -106,7 +106,11 @@ XZ_VERSION=5.8.1 ZSTD_VERSION=1.5.7 TIFF_VERSION=4.7.1 LCMS2_VERSION=2.17 -ZLIB_NG_VERSION=2.2.5 +if [[ "$MB_ML_VER" == 2014 ]] && [[ "$PLAT" == "aarch64" ]]; then + ZLIB_NG_VERSION=2.2.5 +else + ZLIB_NG_VERSION=2.3.1 +fi LIBWEBP_VERSION=1.6.0 BZIP2_VERSION=1.0.8 LIBXCB_VERSION=1.17.0 @@ -149,18 +153,13 @@ function build_zlib_ng { ORIGINAL_HOST_CONFIGURE_FLAGS=$HOST_CONFIGURE_FLAGS unset HOST_CONFIGURE_FLAGS - build_github zlib-ng/zlib-ng $ZLIB_NG_VERSION --zlib-compat + if [[ "$ZLIB_NG_VERSION" == 2.2.5 ]]; then + build_github zlib-ng/zlib-ng $ZLIB_NG_VERSION --zlib-compat + else + build_github zlib-ng/zlib-ng $ZLIB_NG_VERSION --installnamedir=$BUILD_PREFIX/lib --zlib-compat + fi HOST_CONFIGURE_FLAGS=$ORIGINAL_HOST_CONFIGURE_FLAGS - - if [[ -n "$IS_MACOS" ]] && [[ -z "$IOS_SDK" ]]; then - # Ensure that on macOS, the library name is an absolute path, not an - # @rpath, so that delocate picks up the right library (and doesn't need - # DYLD_LIBRARY_PATH to be set). The default Makefile doesn't have an - # option to control the install_name. This isn't needed on iOS, as iOS - # only builds the static library. - install_name_tool -id $BUILD_PREFIX/lib/libz.1.dylib $BUILD_PREFIX/lib/libz.1.dylib - fi touch zlib-stamp } diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 2401dd4ec..65d0af481 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -126,7 +126,7 @@ V = { "OPENJPEG": "2.5.4", "TIFF": "4.7.1", "XZ": "5.8.1", - "ZLIBNG": "2.2.5", + "ZLIBNG": "2.3.1", } V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2]) @@ -167,12 +167,12 @@ DEPS: dict[str, dict[str, Any]] = { "license": "LICENSE.md", "patch": { r"CMakeLists.txt": { - "set_target_properties(zlib PROPERTIES OUTPUT_NAME zlibstatic${{SUFFIX}})": "set_target_properties(zlib PROPERTIES OUTPUT_NAME zlib)", # noqa: E501 + "set_target_properties(zlib-ng PROPERTIES OUTPUT_NAME zlibstatic${{SUFFIX}})": "set_target_properties(zlib-ng PROPERTIES OUTPUT_NAME zlib)", # noqa: E501 }, }, "build": [ *cmds_cmake( - "zlib", "-DBUILD_SHARED_LIBS:BOOL=OFF", "-DZLIB_COMPAT:BOOL=ON" + "zlib-ng", "-DBUILD_SHARED_LIBS:BOOL=OFF", "-DZLIB_COMPAT:BOOL=ON" ), ], "headers": [r"z*.h"], From 37da2ba381ecb47fb7a06af88fe6ed9dc64349f7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 29 Nov 2025 17:22:44 +1100 Subject: [PATCH 303/309] Corrected allocating new color to RGBA palette --- Tests/test_imagepalette.py | 6 ++++++ src/PIL/ImagePalette.py | 9 +++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index 782022f51..6ad21502f 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -49,6 +49,12 @@ def test_getcolor() -> None: palette.getcolor("unknown") # type: ignore[arg-type] +def test_getcolor_rgba() -> None: + palette = ImagePalette.ImagePalette("RGBA", (1, 2, 3, 4)) + palette.getcolor((5, 6, 7, 8)) + assert palette.palette == b"\x01\x02\x03\x04\x05\x06\x07\x08" + + def test_getcolor_rgba_color_rgb_palette() -> None: palette = ImagePalette.ImagePalette("RGB") diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index 103697117..eae7aea8f 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -118,7 +118,7 @@ class ImagePalette: ) -> int: if not isinstance(self.palette, bytearray): self._palette = bytearray(self.palette) - index = len(self.palette) // 3 + index = len(self.palette) // len(self.mode) special_colors: tuple[int | tuple[int, ...] | None, ...] = () if image: special_colors = ( @@ -168,11 +168,12 @@ class ImagePalette: index = self._new_color_index(image, e) assert isinstance(self._palette, bytearray) self.colors[color] = index - if index * 3 < len(self.palette): + mode_len = len(self.mode) + if index * mode_len < len(self.palette): self._palette = ( - self._palette[: index * 3] + self._palette[: index * mode_len] + bytes(color) - + self._palette[index * 3 + 3 :] + + self._palette[index * mode_len + mode_len :] ) else: self._palette += bytes(color) From 65c32ecca4019984862aa9caa916fb2196e1cb2d Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 30 Nov 2025 21:55:59 +0200 Subject: [PATCH 304/309] retina -> Retina --- src/PIL/ImageGrab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index b82a2ff3a..4228078b1 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -54,7 +54,7 @@ def grab( os.unlink(filepath) if bbox: if window: - # Determine if the window was in retina mode or not + # Determine if the window was in Retina mode or not # by capturing it without the shadow, # and checking how different the width is fh, filepath = tempfile.mkstemp(".png") From 370da461cf5e1226376d7ea591265a84dc5a5b06 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Mon, 1 Dec 2025 10:02:09 +1100 Subject: [PATCH 305/309] Updated libpng to 1.6.51 (#9305) Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- .github/workflows/wheels-dependencies.sh | 2 +- winbuild/build_prepare.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index e033b69a9..07ea75a75 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -96,7 +96,7 @@ else FREETYPE_VERSION=2.14.1 fi HARFBUZZ_VERSION=12.2.0 -LIBPNG_VERSION=1.6.50 +LIBPNG_VERSION=1.6.51 JPEGTURBO_VERSION=3.1.2 OPENJPEG_VERSION=2.5.4 XZ_VERSION=5.8.1 diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 99421ebe2..cd2ef13c1 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -121,7 +121,7 @@ V = { "LCMS2": "2.17", "LIBAVIF": "1.3.0", "LIBIMAGEQUANT": "4.4.1", - "LIBPNG": "1.6.50", + "LIBPNG": "1.6.51", "LIBWEBP": "1.6.0", "OPENJPEG": "2.5.4", "TIFF": "4.7.1", From ce3e08575164756e5dcbcc07d49105cd9e8d5c55 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 17:26:21 +0000 Subject: [PATCH 306/309] [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.14.3 → v0.14.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.3...v0.14.7) - [github.com/psf/black-pre-commit-mirror: 25.9.0 → 25.11.0](https://github.com/psf/black-pre-commit-mirror/compare/25.9.0...25.11.0) - [github.com/PyCQA/bandit: 1.8.6 → 1.9.2](https://github.com/PyCQA/bandit/compare/1.8.6...1.9.2) - [github.com/pre-commit/mirrors-clang-format: v21.1.2 → v21.1.6](https://github.com/pre-commit/mirrors-clang-format/compare/v21.1.2...v21.1.6) - [github.com/python-jsonschema/check-jsonschema: 0.34.1 → 0.35.0](https://github.com/python-jsonschema/check-jsonschema/compare/0.34.1...0.35.0) - [github.com/zizmorcore/zizmor-pre-commit: v1.16.2 → v1.18.0](https://github.com/zizmorcore/zizmor-pre-commit/compare/v1.16.2...v1.18.0) - [github.com/sphinx-contrib/sphinx-lint: v1.0.1 → v1.0.2](https://github.com/sphinx-contrib/sphinx-lint/compare/v1.0.1...v1.0.2) - [github.com/tox-dev/pyproject-fmt: v2.11.0 → v2.11.1](https://github.com/tox-dev/pyproject-fmt/compare/v2.11.0...v2.11.1) --- .pre-commit-config.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 564206ce1..8477729e6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,17 +1,17 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.3 + rev: v0.14.7 hooks: - id: ruff-check args: [--exit-non-zero-on-fix] - repo: https://github.com/psf/black-pre-commit-mirror - rev: 25.9.0 + rev: 25.11.0 hooks: - id: black - repo: https://github.com/PyCQA/bandit - rev: 1.8.6 + rev: 1.9.2 hooks: - id: bandit args: [--severity-level=high] @@ -24,7 +24,7 @@ repos: exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$) - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v21.1.2 + rev: v21.1.6 hooks: - id: clang-format types: [c] @@ -51,24 +51,24 @@ repos: exclude: ^\.github/.*TEMPLATE|^Tests/(fonts|images)/ - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.34.1 + rev: 0.35.0 hooks: - id: check-github-workflows - id: check-readthedocs - id: check-renovate - repo: https://github.com/zizmorcore/zizmor-pre-commit - rev: v1.16.2 + rev: v1.18.0 hooks: - id: zizmor - repo: https://github.com/sphinx-contrib/sphinx-lint - rev: v1.0.1 + rev: v1.0.2 hooks: - id: sphinx-lint - repo: https://github.com/tox-dev/pyproject-fmt - rev: v2.11.0 + rev: v2.11.1 hooks: - id: pyproject-fmt From 9342e209b2176bde761b321a74846857257ea78c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 2 Dec 2025 10:21:55 +1100 Subject: [PATCH 307/309] Disable https://docs.zizmor.sh/audits/#obfuscation --- .github/zizmor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/zizmor.yml b/.github/zizmor.yml index b56709781..e60c79441 100644 --- a/.github/zizmor.yml +++ b/.github/zizmor.yml @@ -1,6 +1,8 @@ # Configuration for the zizmor static analysis tool, run via pre-commit in CI # https://docs.zizmor.sh/configuration/ rules: + obfuscation: + disable: true unpinned-uses: config: policies: From 7adecb792c07023cc6c7b410323bb4eabe5d2a23 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 07:14:12 +0000 Subject: [PATCH 308/309] Update dependency mypy to v1.19.0 --- .ci/requirements-mypy.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/requirements-mypy.txt b/.ci/requirements-mypy.txt index 6ca35d286..5b0e2eaf8 100644 --- a/.ci/requirements-mypy.txt +++ b/.ci/requirements-mypy.txt @@ -1,4 +1,4 @@ -mypy==1.18.2 +mypy==1.19.0 arro3-compute arro3-core IceSpringPySideStubs-PyQt6 From b633f49b9c58079975628f2a7eb5acf7118483a5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 07:14:19 +0000 Subject: [PATCH 309/309] Update actions/checkout action to v6 --- .github/workflows/docs.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/test-docker.yml | 2 +- .github/workflows/test-mingw.yml | 2 +- .github/workflows/test-valgrind-memory.yml | 2 +- .github/workflows/test-valgrind.yml | 2 +- .github/workflows/test-windows.yml | 6 +++--- .github/workflows/test.yml | 2 +- .github/workflows/wheels.yml | 8 ++++---- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index cf917407c..e88abf16f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -32,7 +32,7 @@ jobs: name: Docs steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2addbaf67..77d1d1caa 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,7 +20,7 @@ jobs: name: Lint steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 213062ee2..091edb222 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -68,7 +68,7 @@ jobs: name: ${{ matrix.docker }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index 6c4206083..e247414c8 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -45,7 +45,7 @@ jobs: steps: - name: Checkout Pillow - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/test-valgrind-memory.yml b/.github/workflows/test-valgrind-memory.yml index 0f36fe30d..bd244aa5a 100644 --- a/.github/workflows/test-valgrind-memory.yml +++ b/.github/workflows/test-valgrind-memory.yml @@ -41,7 +41,7 @@ jobs: name: ${{ matrix.docker }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/test-valgrind.yml b/.github/workflows/test-valgrind.yml index 30caa0d4e..81cfb8456 100644 --- a/.github/workflows/test-valgrind.yml +++ b/.github/workflows/test-valgrind.yml @@ -39,7 +39,7 @@ jobs: name: ${{ matrix.docker }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 02d4da999..c4d0fa046 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -47,19 +47,19 @@ jobs: steps: - name: Checkout Pillow - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: persist-credentials: false - name: Checkout cached dependencies - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: persist-credentials: false repository: python-pillow/pillow-depends path: winbuild\depends - name: Checkout extra test images - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: persist-credentials: false repository: python-pillow/test-images diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ef7b34b8d..167faa239 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -65,7 +65,7 @@ jobs: name: ${{ matrix.os }} Python ${{ matrix.python-version }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index e33d74a81..fb71ead37 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -107,7 +107,7 @@ jobs: os: macos-15-intel cibw_arch: x86_64_iphonesimulator steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false submodules: true @@ -154,12 +154,12 @@ jobs: - cibw_arch: ARM64 os: windows-11-arm steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false - name: Checkout extra test images - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: persist-credentials: false repository: python-pillow/test-images @@ -235,7 +235,7 @@ jobs: if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false