diff --git a/.ci/install.sh b/.ci/install.sh index 1eb098be9c1..8e65f64c447 100755 --- a/.ci/install.sh +++ b/.ci/install.sh @@ -37,12 +37,18 @@ python3 -m pip install -U pytest-timeout python3 -m pip install pyroma if [[ $(uname) != CYGWIN* ]]; then - python3 -m pip install numpy + # TODO Update condition when NumPy supports free-threading + if [[ "$PYTHON_GIL" == "0" ]]; then + python3 -m pip install numpy --index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple + else + python3 -m pip install numpy + fi # 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 - python3 -m pip install pyqt6 + # 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 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0972459b0fb..6e63333b0da 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -50,26 +50,24 @@ jobs: "3.9", ] include: - - python-version: "3.11" - PYTHONOPTIMIZE: 1 - REVERSE: "--reverse" - - python-version: "3.10" - PYTHONOPTIMIZE: 2 + - { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" } + - { python-version: "3.10", PYTHONOPTIMIZE: 2 } + # Free-threaded + - { os: "ubuntu-latest", python-version: "3.13-dev", disable-gil: true } # M1 only available for 3.10+ - - os: "macos-13" - python-version: "3.9" + - { os: "macos-13", python-version: "3.9" } exclude: - - os: "macos-14" - python-version: "3.9" + - { os: "macos-14", python-version: "3.9" } runs-on: ${{ matrix.os }} - name: ${{ matrix.os }} Python ${{ matrix.python-version }} + name: ${{ matrix.os }} Python ${{ matrix.python-version }} ${{ matrix.disable-gil && 'free-threaded' || '' }} steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 + if: "${{ !matrix.disable-gil }}" with: python-version: ${{ matrix.python-version }} allow-prereleases: true @@ -78,6 +76,18 @@ jobs: ".ci/*.sh" "pyproject.toml" + - name: Set up Python ${{ matrix.python-version }} (free-threaded) + uses: deadsnakes/action@v3.1.0 + if: "${{ matrix.disable-gil }}" + with: + python-version: ${{ matrix.python-version }} + nogil: ${{ matrix.disable-gil }} + + - name: Set PYTHON_GIL + if: "${{ matrix.disable-gil }}" + run: | + echo "PYTHON_GIL=0" >> $GITHUB_ENV + - name: Build system information run: python3 .github/workflows/system-info.py diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 39b9c60b756..5402fcb440d 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -2,11 +2,11 @@ import warnings from io import BytesIO -from typing import Any, cast +from typing import Any import pytest -from PIL import Image, MpoImagePlugin +from PIL import Image, ImageFile, MpoImagePlugin from .helper import ( assert_image_equal, @@ -20,11 +20,11 @@ pytestmark = skip_unless_feature("jpg") -def roundtrip(im: Image.Image, **options: Any) -> MpoImagePlugin.MpoImageFile: +def roundtrip(im: Image.Image, **options: Any) -> ImageFile.ImageFile: out = BytesIO() im.save(out, "MPO", **options) out.seek(0) - return cast(MpoImagePlugin.MpoImageFile, Image.open(out)) + return Image.open(out) @pytest.mark.parametrize("test_file", test_files) @@ -226,6 +226,12 @@ def test_eoferror() -> None: im.seek(n_frames - 1) +def test_adopt_jpeg() -> None: + with Image.open("Tests/images/hopper.jpg") as im: + with pytest.raises(ValueError): + MpoImagePlugin.MpoImageFile.adopt(im) + + def test_ultra_hdr() -> None: with Image.open("Tests/images/ultrahdr.jpg") as im: assert im.format == "JPEG" @@ -275,6 +281,8 @@ def test_save_all() -> None: im_reloaded = roundtrip(im, save_all=True, append_images=[im2]) assert_image_equal(im, im_reloaded) + assert isinstance(im_reloaded, MpoImagePlugin.MpoImageFile) + assert im_reloaded.mpinfo is not None assert im_reloaded.mpinfo[45056] == b"0100" im_reloaded.seek(1) diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index bb686bb3b90..b996860ce2c 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -90,6 +90,7 @@ def test_ico(self) -> None: data = f.read() with ImageFile.Parser() as p: p.feed(data) + assert p.image is not None assert (48, 48) == p.image.size @skip_unless_feature("webp") @@ -103,6 +104,7 @@ def test_incremental_webp(self) -> None: assert not p.image p.feed(f.read()) + assert p.image is not None assert (128, 128) == p.image.size @skip_unless_feature("zlib") @@ -393,8 +395,9 @@ def test_encode(self) -> None: with pytest.raises(NotImplementedError): encoder.encode_to_pyfd() + fh = BytesIO() with pytest.raises(NotImplementedError): - encoder.encode_to_file(None, None) + encoder.encode_to_file(fh, 0) def test_zero_height(self) -> None: with pytest.raises(UnidentifiedImageError): diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index f84c6c03a37..4484dca1064 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -109,3 +109,6 @@ def test_bitmapimage() -> None: # reloaded = ImageTk.getimage(im_tk) # assert_image_equal(reloaded, im) + + with pytest.raises(ValueError): + ImageTk.BitmapImage() diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index b4a300d0c59..a66bf97087e 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -1,820 +1,839 @@ from __future__ import annotations -import sys +from typing import Union import pytest from PIL import Image -X = 255 +from .helper import is_big_endian +X = 255 -class TestLibPack: - def assert_pack( - self, - mode: str, - rawmode: str, - data: int | bytes, - *pixels: float | tuple[int, ...], - ) -> None: - """ - data - either raw bytes with data or just number of bytes in rawmode. - """ - im = Image.new(mode, (len(pixels), 1)) - for x, pixel in enumerate(pixels): - im.putpixel((x, 0), pixel) - - if isinstance(data, int): - data_len = data * len(pixels) - data = bytes(range(1, data_len + 1)) - - assert data == im.tobytes("raw", rawmode) - - def test_1(self) -> None: - self.assert_pack("1", "1", b"\x01", 0, 0, 0, 0, 0, 0, 0, X) - self.assert_pack("1", "1;I", b"\x01", X, X, X, X, X, X, X, 0) - self.assert_pack("1", "1;R", b"\x01", X, 0, 0, 0, 0, 0, 0, 0) - self.assert_pack("1", "1;IR", b"\x01", 0, X, X, X, X, X, X, X) - - self.assert_pack("1", "1", b"\xaa", X, 0, X, 0, X, 0, X, 0) - self.assert_pack("1", "1;I", b"\xaa", 0, X, 0, X, 0, X, 0, X) - self.assert_pack("1", "1;R", b"\xaa", 0, X, 0, X, 0, X, 0, X) - self.assert_pack("1", "1;IR", b"\xaa", X, 0, X, 0, X, 0, X, 0) - - self.assert_pack("1", "L", b"\xff\x00\x00\xff\x00\x00", X, 0, 0, X, 0, 0) - - def test_L(self) -> None: - self.assert_pack("L", "L", 1, 1, 2, 3, 4) - self.assert_pack("L", "L;16", b"\x00\xc6\x00\xaf", 198, 175) - self.assert_pack("L", "L;16B", b"\xc6\x00\xaf\x00", 198, 175) - - def test_LA(self) -> None: - self.assert_pack("LA", "LA", 2, (1, 2), (3, 4), (5, 6)) - self.assert_pack("LA", "LA;L", 2, (1, 4), (2, 5), (3, 6)) - - def test_La(self) -> None: - self.assert_pack("La", "La", 2, (1, 2), (3, 4), (5, 6)) - - def test_P(self) -> None: - self.assert_pack("P", "P;1", b"\xe4", 1, 1, 1, 0, 0, 255, 0, 0) - self.assert_pack("P", "P;2", b"\xe4", 3, 2, 1, 0) - self.assert_pack("P", "P;4", b"\x02\xef", 0, 2, 14, 15) - self.assert_pack("P", "P", 1, 1, 2, 3, 4) - - def test_PA(self) -> None: - self.assert_pack("PA", "PA", 2, (1, 2), (3, 4), (5, 6)) - self.assert_pack("PA", "PA;L", 2, (1, 4), (2, 5), (3, 6)) - - def test_RGB(self) -> None: - self.assert_pack("RGB", "RGB", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) - self.assert_pack( - "RGB", "RGBX", b"\x01\x02\x03\xff\x05\x06\x07\xff", (1, 2, 3), (5, 6, 7) - ) - self.assert_pack( - "RGB", "XRGB", b"\x00\x02\x03\x04\x00\x06\x07\x08", (2, 3, 4), (6, 7, 8) - ) - self.assert_pack("RGB", "BGR", 3, (3, 2, 1), (6, 5, 4), (9, 8, 7)) - self.assert_pack( - "RGB", "BGRX", b"\x01\x02\x03\x00\x05\x06\x07\x00", (3, 2, 1), (7, 6, 5) - ) - self.assert_pack( - "RGB", "XBGR", b"\x00\x02\x03\x04\x00\x06\x07\x08", (4, 3, 2), (8, 7, 6) - ) - self.assert_pack("RGB", "RGB;L", 3, (1, 4, 7), (2, 5, 8), (3, 6, 9)) - self.assert_pack("RGB", "R", 1, (1, 9, 9), (2, 9, 9), (3, 9, 9)) - self.assert_pack("RGB", "G", 1, (9, 1, 9), (9, 2, 9), (9, 3, 9)) - self.assert_pack("RGB", "B", 1, (9, 9, 1), (9, 9, 2), (9, 9, 3)) - - def test_RGBA(self) -> None: - self.assert_pack("RGBA", "RGBA", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) - self.assert_pack( - "RGBA", "RGBA;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12) - ) - self.assert_pack("RGBA", "RGB", 3, (1, 2, 3, 14), (4, 5, 6, 15), (7, 8, 9, 16)) - self.assert_pack("RGBA", "BGR", 3, (3, 2, 1, 14), (6, 5, 4, 15), (9, 8, 7, 16)) - self.assert_pack("RGBA", "BGRA", 4, (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)) - self.assert_pack("RGBA", "ABGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)) - self.assert_pack( +UnPackParamTypes = list[ + tuple[str, str, Union[bytes, int], list[Union[float, tuple[int, ...]]]] +] + + +def get_pack_parameters() -> UnPackParamTypes: + params: UnPackParamTypes = [] + + # Mode 1 + params += [ + ("1", "1", b"\x01", [0, 0, 0, 0, 0, 0, 0, X]), + ("1", "1;I", b"\x01", [X, X, X, X, X, X, X, 0]), + ("1", "1;R", b"\x01", [X, 0, 0, 0, 0, 0, 0, 0]), + ("1", "1;IR", b"\x01", [0, X, X, X, X, X, X, X]), + ("1", "1", b"\xaa", [X, 0, X, 0, X, 0, X, 0]), + ("1", "1;I", b"\xaa", [0, X, 0, X, 0, X, 0, X]), + ("1", "1;R", b"\xaa", [0, X, 0, X, 0, X, 0, X]), + ("1", "1;IR", b"\xaa", [X, 0, X, 0, X, 0, X, 0]), + ("1", "L", b"\xff\x00\x00\xff\x00\x00", [X, 0, 0, X, 0, 0]), + ] + + # Mode L + params += [ + ("L", "L", 1, [1, 2, 3, 4]), + ("L", "L;16", b"\x00\xc6\x00\xaf", [198, 175]), + ("L", "L;16B", b"\xc6\x00\xaf\x00", [198, 175]), + ] + + # Mode LA + params += [ + ("LA", "LA", 2, [(1, 2), (3, 4), (5, 6)]), + ("LA", "LA;L", 2, [(1, 4), (2, 5), (3, 6)]), + ] + + # Mode La + params += [("La", "La", 2, [(1, 2), (3, 4), (5, 6)])] + + # Mode P + params += [ + ("P", "P;1", b"\xe4", [1, 1, 1, 0, 0, 255, 0, 0]), + ("P", "P;2", b"\xe4", [3, 2, 1, 0]), + ("P", "P;4", b"\x02\xef", [0, 2, 14, 15]), + ("P", "P", 1, [1, 2, 3, 4]), + ] + + # Mode PA + params += [ + ("PA", "PA", 2, [(1, 2), (3, 4), (5, 6)]), + ("PA", "PA;L", 2, [(1, 4), (2, 5), (3, 6)]), + ] + + # Mode RGB + params += [ + ("RGB", "RGB", 3, [(1, 2, 3), (4, 5, 6), (7, 8, 9)]), + ("RGB", "RGBX", b"\x01\x02\x03\xff\x05\x06\x07\xff", [(1, 2, 3), (5, 6, 7)]), + ("RGB", "XRGB", b"\x00\x02\x03\x04\x00\x06\x07\x08", [(2, 3, 4), (6, 7, 8)]), + ("RGB", "BGR", 3, [(3, 2, 1), (6, 5, 4), (9, 8, 7)]), + ("RGB", "BGRX", b"\x01\x02\x03\x00\x05\x06\x07\x00", [(3, 2, 1), (7, 6, 5)]), + ("RGB", "XBGR", b"\x00\x02\x03\x04\x00\x06\x07\x08", [(4, 3, 2), (8, 7, 6)]), + ("RGB", "RGB;L", 3, [(1, 4, 7), (2, 5, 8), (3, 6, 9)]), + ("RGB", "R", 1, [(1, 9, 9), (2, 9, 9), (3, 9, 9)]), + ("RGB", "G", 1, [(9, 1, 9), (9, 2, 9), (9, 3, 9)]), + ("RGB", "B", 1, [(9, 9, 1), (9, 9, 2), (9, 9, 3)]), + ] + + # Mode RGBA + params += [ + ("RGBA", "RGBA", 4, [(1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)]), + ("RGBA", "RGBA;L", 4, [(1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)]), + ("RGBA", "RGB", 3, [(1, 2, 3, 14), (4, 5, 6, 15), (7, 8, 9, 16)]), + ("RGBA", "BGR", 3, [(3, 2, 1, 14), (6, 5, 4, 15), (9, 8, 7, 16)]), + ("RGBA", "BGRA", 4, [(3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)]), + ("RGBA", "ABGR", 4, [(4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)]), + ( "RGBA", "BGRa", 4, - (191, 127, 63, 4), - (223, 191, 159, 8), - (233, 212, 191, 12), - ) - self.assert_pack("RGBA", "R", 1, (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)) - self.assert_pack("RGBA", "G", 1, (6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9)) - self.assert_pack("RGBA", "B", 1, (6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)) - self.assert_pack("RGBA", "A", 1, (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)) - - def test_RGBa(self) -> None: - self.assert_pack("RGBa", "RGBa", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) - self.assert_pack("RGBa", "BGRa", 4, (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)) - self.assert_pack("RGBa", "aBGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)) - - def test_RGBX(self) -> None: - self.assert_pack("RGBX", "RGBX", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) - self.assert_pack( - "RGBX", "RGBX;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12) - ) - self.assert_pack("RGBX", "RGB", 3, (1, 2, 3, X), (4, 5, 6, X), (7, 8, 9, X)) - self.assert_pack("RGBX", "BGR", 3, (3, 2, 1, X), (6, 5, 4, X), (9, 8, 7, X)) - self.assert_pack( + [(191, 127, 63, 4), (223, 191, 159, 8), (233, 212, 191, 12)], + ), + ("RGBA", "R", 1, [(1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)]), + ("RGBA", "G", 1, [(6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9)]), + ("RGBA", "B", 1, [(6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)]), + ("RGBA", "A", 1, [(6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)]), + ] + + # Mode RGBa + params += [ + ("RGBa", "RGBa", 4, [(1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)]), + ("RGBa", "BGRa", 4, [(3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)]), + ("RGBa", "aBGR", 4, [(4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)]), + ] + + # Mode RGBX + params += [ + ("RGBX", "RGBX", 4, [(1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)]), + ("RGBX", "RGBX;L", 4, [(1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)]), + ("RGBX", "RGB", 3, [(1, 2, 3, X), (4, 5, 6, X), (7, 8, 9, X)]), + ("RGBX", "BGR", 3, [(3, 2, 1, X), (6, 5, 4, X), (9, 8, 7, X)]), + ( "RGBX", "BGRX", b"\x01\x02\x03\x00\x05\x06\x07\x00\t\n\x0b\x00", - (3, 2, 1, X), - (7, 6, 5, X), - (11, 10, 9, X), - ) - self.assert_pack( + [(3, 2, 1, X), (7, 6, 5, X), (11, 10, 9, X)], + ), + ( "RGBX", "XBGR", b"\x00\x02\x03\x04\x00\x06\x07\x08\x00\n\x0b\x0c", - (4, 3, 2, X), - (8, 7, 6, X), - (12, 11, 10, X), - ) - self.assert_pack("RGBX", "R", 1, (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)) - self.assert_pack("RGBX", "G", 1, (6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9)) - self.assert_pack("RGBX", "B", 1, (6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)) - self.assert_pack("RGBX", "X", 1, (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)) - - def test_CMYK(self) -> None: - self.assert_pack("CMYK", "CMYK", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) - self.assert_pack( + [(4, 3, 2, X), (8, 7, 6, X), (12, 11, 10, X)], + ), + ("RGBX", "R", 1, [(1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)]), + ("RGBX", "G", 1, [(6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9)]), + ("RGBX", "B", 1, [(6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)]), + ("RGBX", "X", 1, [(6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)]), + ] + + # Mode CMYK + params += [ + ("CMYK", "CMYK", 4, [(1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)]), + ( "CMYK", "CMYK;I", 4, - (254, 253, 252, 251), - (250, 249, 248, 247), - (246, 245, 244, 243), - ) - self.assert_pack( - "CMYK", "CMYK;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12) - ) - self.assert_pack("CMYK", "K", 1, (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)) - - def test_YCbCr(self) -> None: - self.assert_pack("YCbCr", "YCbCr", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) - self.assert_pack("YCbCr", "YCbCr;L", 3, (1, 4, 7), (2, 5, 8), (3, 6, 9)) - self.assert_pack( + [(254, 253, 252, 251), (250, 249, 248, 247), (246, 245, 244, 243)], + ), + ("CMYK", "CMYK;L", 4, [(1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)]), + ("CMYK", "K", 1, [(6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)]), + ] + + # Mode YCbCr + params += [ + ("YCbCr", "YCbCr", 3, [(1, 2, 3), (4, 5, 6), (7, 8, 9)]), + ("YCbCr", "YCbCr;L", 3, [(1, 4, 7), (2, 5, 8), (3, 6, 9)]), + ( "YCbCr", "YCbCrX", b"\x01\x02\x03\xff\x05\x06\x07\xff\t\n\x0b\xff", - (1, 2, 3), - (5, 6, 7), - (9, 10, 11), - ) - self.assert_pack( + [(1, 2, 3), (5, 6, 7), (9, 10, 11)], + ), + ( "YCbCr", "YCbCrK", b"\x01\x02\x03\xff\x05\x06\x07\xff\t\n\x0b\xff", - (1, 2, 3), - (5, 6, 7), - (9, 10, 11), - ) - self.assert_pack("YCbCr", "Y", 1, (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)) - self.assert_pack("YCbCr", "Cb", 1, (6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9)) - self.assert_pack("YCbCr", "Cr", 1, (6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)) - - def test_LAB(self) -> None: - self.assert_pack("LAB", "LAB", 3, (1, 130, 131), (4, 133, 134), (7, 136, 137)) - self.assert_pack("LAB", "L", 1, (1, 9, 9), (2, 9, 9), (3, 9, 9)) - self.assert_pack("LAB", "A", 1, (9, 1, 9), (9, 2, 9), (9, 3, 9)) - self.assert_pack("LAB", "B", 1, (9, 9, 1), (9, 9, 2), (9, 9, 3)) - - def test_HSV(self) -> None: - self.assert_pack("HSV", "HSV", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) - self.assert_pack("HSV", "H", 1, (1, 9, 9), (2, 9, 9), (3, 9, 9)) - self.assert_pack("HSV", "S", 1, (9, 1, 9), (9, 2, 9), (9, 3, 9)) - self.assert_pack("HSV", "V", 1, (9, 9, 1), (9, 9, 2), (9, 9, 3)) - - def test_I(self) -> None: - self.assert_pack("I", "I;16B", 2, 0x0102, 0x0304) - self.assert_pack( - "I", "I;32S", b"\x83\x00\x00\x01\x01\x00\x00\x83", 0x01000083, -2097151999 - ) - - if sys.byteorder == "little": - self.assert_pack("I", "I", 4, 0x04030201, 0x08070605) - self.assert_pack( + [(1, 2, 3), (5, 6, 7), (9, 10, 11)], + ), + ("YCbCr", "Y", 1, [(1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)]), + ("YCbCr", "Cb", 1, [(6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9)]), + ("YCbCr", "Cr", 1, [(6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)]), + ] + + # Mode LAB + params += [ + ("LAB", "LAB", 3, [(1, 130, 131), (4, 133, 134), (7, 136, 137)]), + ("LAB", "L", 1, [(1, 9, 9), (2, 9, 9), (3, 9, 9)]), + ("LAB", "A", 1, [(9, 1, 9), (9, 2, 9), (9, 3, 9)]), + ("LAB", "B", 1, [(9, 9, 1), (9, 9, 2), (9, 9, 3)]), + ] + + # Mode HSV + params += [ + ("HSV", "HSV", 3, [(1, 2, 3), (4, 5, 6), (7, 8, 9)]), + ("HSV", "H", 1, [(1, 9, 9), (2, 9, 9), (3, 9, 9)]), + ("HSV", "S", 1, [(9, 1, 9), (9, 2, 9), (9, 3, 9)]), + ("HSV", "V", 1, [(9, 9, 1), (9, 9, 2), (9, 9, 3)]), + ] + + # Mode I + params += [ + ("I", "I;16B", 2, [0x0102, 0x0304]), + ("I", "I;32S", b"\x83\x00\x00\x01\x01\x00\x00\x83", [0x01000083, -2097151999]), + ] + if is_big_endian(): + params += [ + ("I", "I", 4, [0x01020304, 0x05060708]), + ( "I", "I;32NS", b"\x83\x00\x00\x01\x01\x00\x00\x83", - 0x01000083, - -2097151999, - ) - else: - self.assert_pack("I", "I", 4, 0x01020304, 0x05060708) - self.assert_pack( + [-2097151999, 0x01000083], + ), + ] + else: + params += [ + ("I", "I", 4, [0x04030201, 0x08070605]), + ( "I", "I;32NS", b"\x83\x00\x00\x01\x01\x00\x00\x83", - -2097151999, - 0x01000083, - ) - - def test_I16(self) -> None: - if sys.byteorder == "little": - self.assert_pack("I;16N", "I;16N", 2, 0x0201, 0x0403, 0x0605) - else: - self.assert_pack("I;16N", "I;16N", 2, 0x0102, 0x0304, 0x0506) - - def test_F_float(self) -> None: - self.assert_pack("F", "F;32F", 4, 1.539989614439558e-36, 4.063216068939723e-34) - - if sys.byteorder == "little": - self.assert_pack("F", "F", 4, 1.539989614439558e-36, 4.063216068939723e-34) - self.assert_pack( - "F", "F;32NF", 4, 1.539989614439558e-36, 4.063216068939723e-34 - ) - else: - self.assert_pack("F", "F", 4, 2.387939260590663e-38, 6.301941157072183e-36) - self.assert_pack( - "F", "F;32NF", 4, 2.387939260590663e-38, 6.301941157072183e-36 - ) - - -class TestLibUnpack: - def assert_unpack( - self, - mode: str, - rawmode: str, - data: int | bytes, - *pixels: float | tuple[int, ...], - ) -> None: - """ - data - either raw bytes with data or just number of bytes in rawmode. - """ - if isinstance(data, int): - data_len = data * len(pixels) - data = bytes(range(1, data_len + 1)) - - im = Image.frombytes(mode, (len(pixels), 1), data, "raw", rawmode, 0, 1) - - for x, pixel in enumerate(pixels): - assert pixel == im.getpixel((x, 0)) - - def test_1(self) -> None: - self.assert_unpack("1", "1", b"\x01", 0, 0, 0, 0, 0, 0, 0, X) - self.assert_unpack("1", "1;I", b"\x01", X, X, X, X, X, X, X, 0) - self.assert_unpack("1", "1;R", b"\x01", X, 0, 0, 0, 0, 0, 0, 0) - self.assert_unpack("1", "1;IR", b"\x01", 0, X, X, X, X, X, X, X) - - self.assert_unpack("1", "1", b"\xaa", X, 0, X, 0, X, 0, X, 0) - self.assert_unpack("1", "1;I", b"\xaa", 0, X, 0, X, 0, X, 0, X) - self.assert_unpack("1", "1;R", b"\xaa", 0, X, 0, X, 0, X, 0, X) - self.assert_unpack("1", "1;IR", b"\xaa", X, 0, X, 0, X, 0, X, 0) - - self.assert_unpack("1", "1;8", b"\x00\x01\x02\xff", 0, X, X, X) - - def test_L(self) -> None: - self.assert_unpack("L", "L;2", b"\xe4", 255, 170, 85, 0) - self.assert_unpack("L", "L;2I", b"\xe4", 0, 85, 170, 255) - self.assert_unpack("L", "L;2R", b"\xe4", 0, 170, 85, 255) - self.assert_unpack("L", "L;2IR", b"\xe4", 255, 85, 170, 0) - - self.assert_unpack("L", "L;4", b"\x02\xef", 0, 34, 238, 255) - self.assert_unpack("L", "L;4I", b"\x02\xef", 255, 221, 17, 0) - self.assert_unpack("L", "L;4R", b"\x02\xef", 68, 0, 255, 119) - self.assert_unpack("L", "L;4IR", b"\x02\xef", 187, 255, 0, 136) - - self.assert_unpack("L", "L", 1, 1, 2, 3, 4) - self.assert_unpack("L", "L;I", 1, 254, 253, 252, 251) - self.assert_unpack("L", "L;R", 1, 128, 64, 192, 32) - self.assert_unpack("L", "L;16", 2, 2, 4, 6, 8) - self.assert_unpack("L", "L;16B", 2, 1, 3, 5, 7) - self.assert_unpack("L", "L;16", b"\x00\xc6\x00\xaf", 198, 175) - self.assert_unpack("L", "L;16B", b"\xc6\x00\xaf\x00", 198, 175) - - def test_LA(self) -> None: - self.assert_unpack("LA", "LA", 2, (1, 2), (3, 4), (5, 6)) - self.assert_unpack("LA", "LA;L", 2, (1, 4), (2, 5), (3, 6)) - - def test_La(self) -> None: - self.assert_unpack("La", "La", 2, (1, 2), (3, 4), (5, 6)) - - def test_P(self) -> None: - self.assert_unpack("P", "P;1", b"\xe4", 1, 1, 1, 0, 0, 1, 0, 0) - self.assert_unpack("P", "P;2", b"\xe4", 3, 2, 1, 0) + [0x01000083, -2097151999], + ), + ] + + # Mode I;16 + if is_big_endian(): + params += [("I;16N", "I;16N", 2, [0x0102, 0x0304, 0x0506])] + else: + params += [("I;16N", "I;16N", 2, [0x0201, 0x0403, 0x0605])] + + # Mode F float + params += [ + ("F", "F;32F", 4, [1.539989614439558e-36, 4.063216068939723e-34]), + ] + if is_big_endian(): + params += [ + ("F", "F", 4, [2.387939260590663e-38, 6.301941157072183e-36]), + ("F", "F;32NF", 4, [2.387939260590663e-38, 6.301941157072183e-36]), + ] + else: + params += [ + ("F", "F", 4, [1.539989614439558e-36, 4.063216068939723e-34]), + ("F", "F;32NF", 4, [1.539989614439558e-36, 4.063216068939723e-34]), + ] + + return params + + +@pytest.mark.parametrize("mode, rawmode, data, pixels", get_pack_parameters()) +def test_pack( + mode: str, rawmode: str, data: bytes | int, pixels: list[float | tuple[int, ...]] +) -> None: + """ + Test packing from {mode} to {rawmode}. + data - Either the number of bytes in a pixel of the rawmode, or a byte string. + If a number is given, bytes will be generated starting at 1. + The byte string will be compared against the pack result. + pixels - The pixels to populate the starting image with. + """ + im = Image.new(mode, (len(pixels), 1)) + for x, pixel in enumerate(pixels): + im.putpixel((x, 0), pixel) + + if isinstance(data, int): + data_len = data * len(pixels) + data = bytes(range(1, data_len + 1)) + + assert data == im.tobytes("raw", rawmode) + + +def get_unpack_parameters() -> UnPackParamTypes: + params: UnPackParamTypes = [] + + # Mode 1 + params += [ + ("1", "1", b"\x01", [0, 0, 0, 0, 0, 0, 0, X]), + ("1", "1;I", b"\x01", [X, X, X, X, X, X, X, 0]), + ("1", "1;R", b"\x01", [X, 0, 0, 0, 0, 0, 0, 0]), + ("1", "1;IR", b"\x01", [0, X, X, X, X, X, X, X]), + ("1", "1", b"\xaa", [X, 0, X, 0, X, 0, X, 0]), + ("1", "1;I", b"\xaa", [0, X, 0, X, 0, X, 0, X]), + ("1", "1;R", b"\xaa", [0, X, 0, X, 0, X, 0, X]), + ("1", "1;IR", b"\xaa", [X, 0, X, 0, X, 0, X, 0]), + ("1", "1;8", b"\x00\x01\x02\xff", [0, X, X, X]), + ] + + # Mode L + params += [ + ("L", "L;2", b"\xe4", [255, 170, 85, 0]), + ("L", "L;2I", b"\xe4", [0, 85, 170, 255]), + ("L", "L;2R", b"\xe4", [0, 170, 85, 255]), + ("L", "L;2IR", b"\xe4", [255, 85, 170, 0]), + ("L", "L;4", b"\x02\xef", [0, 34, 238, 255]), + ("L", "L;4I", b"\x02\xef", [255, 221, 17, 0]), + ("L", "L;4R", b"\x02\xef", [68, 0, 255, 119]), + ("L", "L;4IR", b"\x02\xef", [187, 255, 0, 136]), + ("L", "L", 1, [1, 2, 3, 4]), + ("L", "L;I", 1, [254, 253, 252, 251]), + ("L", "L;R", 1, [128, 64, 192, 32]), + ("L", "L;16", 2, [2, 4, 6, 8]), + ("L", "L;16B", 2, [1, 3, 5, 7]), + ("L", "L;16", b"\x00\xc6\x00\xaf", [198, 175]), + ("L", "L;16B", b"\xc6\x00\xaf\x00", [198, 175]), + ] + + # Mode LA + params += [ + ("LA", "LA", 2, [(1, 2), (3, 4), (5, 6)]), + ("LA", "LA;L", 2, [(1, 4), (2, 5), (3, 6)]), + ] + + # Mode La + params += [ + ("La", "La", 2, [(1, 2), (3, 4), (5, 6)]), + ] + + # Mode P + params += [ + ("P", "P;1", b"\xe4", [1, 1, 1, 0, 0, 1, 0, 0]), + ("P", "P;2", b"\xe4", [3, 2, 1, 0]), # erroneous? - # self.assert_unpack("P", "P;2L", b'\xe4', 1, 1, 1, 0) - self.assert_unpack("P", "P;4", b"\x02\xef", 0, 2, 14, 15) + # ("P", "P;2L", b"\xe4", [1, 1, 1, 0]), + ("P", "P;4", b"\x02\xef", [0, 2, 14, 15]), # erroneous? - # self.assert_unpack("P", "P;4L", b'\x02\xef', 2, 10, 10, 0) - self.assert_unpack("P", "P", 1, 1, 2, 3, 4) - self.assert_unpack("P", "P;R", 1, 128, 64, 192, 32) - - def test_PA(self) -> None: - self.assert_unpack("PA", "PA", 2, (1, 2), (3, 4), (5, 6)) - self.assert_unpack("PA", "PA;L", 2, (1, 4), (2, 5), (3, 6)) - - def test_RGB(self) -> None: - self.assert_unpack("RGB", "RGB", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) - self.assert_unpack("RGB", "RGB;L", 3, (1, 4, 7), (2, 5, 8), (3, 6, 9)) - self.assert_unpack("RGB", "RGB;R", 3, (128, 64, 192), (32, 160, 96)) - self.assert_unpack("RGB", "RGB;16L", 6, (2, 4, 6), (8, 10, 12)) - self.assert_unpack("RGB", "RGB;16B", 6, (1, 3, 5), (7, 9, 11)) - self.assert_unpack("RGB", "BGR", 3, (3, 2, 1), (6, 5, 4), (9, 8, 7)) - self.assert_unpack("RGB", "RGB;15", 2, (8, 131, 0), (24, 0, 8)) - self.assert_unpack("RGB", "BGR;15", 2, (0, 131, 8), (8, 0, 24)) - self.assert_unpack("RGB", "RGB;16", 2, (8, 64, 0), (24, 129, 0)) - self.assert_unpack("RGB", "BGR;16", 2, (0, 64, 8), (0, 129, 24)) - self.assert_unpack("RGB", "RGB;4B", 2, (17, 0, 34), (51, 0, 68)) - self.assert_unpack("RGB", "RGBX", 4, (1, 2, 3), (5, 6, 7), (9, 10, 11)) - self.assert_unpack("RGB", "RGBX;L", 4, (1, 4, 7), (2, 5, 8), (3, 6, 9)) - self.assert_unpack("RGB", "BGRX", 4, (3, 2, 1), (7, 6, 5), (11, 10, 9)) - self.assert_unpack("RGB", "XRGB", 4, (2, 3, 4), (6, 7, 8), (10, 11, 12)) - self.assert_unpack("RGB", "XBGR", 4, (4, 3, 2), (8, 7, 6), (12, 11, 10)) - self.assert_unpack( + # ("P", "P;4L", b"\x02\xef", [2, 10, 10, 0]), + ("P", "P", 1, [1, 2, 3, 4]), + ("P", "P;R", 1, [128, 64, 192, 32]), + ] + + # Mode PA + params += [ + ("PA", "PA", 2, [(1, 2), (3, 4), (5, 6)]), + ("PA", "PA;L", 2, [(1, 4), (2, 5), (3, 6)]), + ] + + # Mode RGB + params += [ + ("RGB", "RGB", 3, [(1, 2, 3), (4, 5, 6), (7, 8, 9)]), + ("RGB", "RGB;L", 3, [(1, 4, 7), (2, 5, 8), (3, 6, 9)]), + ("RGB", "RGB;R", 3, [(128, 64, 192), (32, 160, 96)]), + ("RGB", "RGB;16L", 6, [(2, 4, 6), (8, 10, 12)]), + ("RGB", "RGB;16B", 6, [(1, 3, 5), (7, 9, 11)]), + ("RGB", "BGR", 3, [(3, 2, 1), (6, 5, 4), (9, 8, 7)]), + ("RGB", "RGB;15", 2, [(8, 131, 0), (24, 0, 8)]), + ("RGB", "BGR;15", 2, [(0, 131, 8), (8, 0, 24)]), + ("RGB", "RGB;16", 2, [(8, 64, 0), (24, 129, 0)]), + ("RGB", "BGR;16", 2, [(0, 64, 8), (0, 129, 24)]), + ("RGB", "RGB;4B", 2, [(17, 0, 34), (51, 0, 68)]), + ("RGB", "RGBX", 4, [(1, 2, 3), (5, 6, 7), (9, 10, 11)]), + ("RGB", "RGBX;L", 4, [(1, 4, 7), (2, 5, 8), (3, 6, 9)]), + ("RGB", "BGRX", 4, [(3, 2, 1), (7, 6, 5), (11, 10, 9)]), + ("RGB", "XRGB", 4, [(2, 3, 4), (6, 7, 8), (10, 11, 12)]), + ("RGB", "XBGR", 4, [(4, 3, 2), (8, 7, 6), (12, 11, 10)]), + ( "RGB", "YCC;P", b"D]\x9c\x82\x1a\x91\xfaOC\xe7J\x12", # random data - (127, 102, 0), - (192, 227, 0), - (213, 255, 170), - (98, 255, 133), - ) - self.assert_unpack("RGB", "R", 1, (1, 0, 0), (2, 0, 0), (3, 0, 0)) - self.assert_unpack("RGB", "G", 1, (0, 1, 0), (0, 2, 0), (0, 3, 0)) - self.assert_unpack("RGB", "B", 1, (0, 0, 1), (0, 0, 2), (0, 0, 3)) - - self.assert_unpack("RGB", "R;16B", 2, (1, 0, 0), (3, 0, 0), (5, 0, 0)) - self.assert_unpack("RGB", "G;16B", 2, (0, 1, 0), (0, 3, 0), (0, 5, 0)) - self.assert_unpack("RGB", "B;16B", 2, (0, 0, 1), (0, 0, 3), (0, 0, 5)) - - self.assert_unpack("RGB", "R;16L", 2, (2, 0, 0), (4, 0, 0), (6, 0, 0)) - self.assert_unpack("RGB", "G;16L", 2, (0, 2, 0), (0, 4, 0), (0, 6, 0)) - self.assert_unpack("RGB", "B;16L", 2, (0, 0, 2), (0, 0, 4), (0, 0, 6)) - - if sys.byteorder == "little": - self.assert_unpack("RGB", "R;16N", 2, (2, 0, 0), (4, 0, 0), (6, 0, 0)) - self.assert_unpack("RGB", "G;16N", 2, (0, 2, 0), (0, 4, 0), (0, 6, 0)) - self.assert_unpack("RGB", "B;16N", 2, (0, 0, 2), (0, 0, 4), (0, 0, 6)) - else: - self.assert_unpack("RGB", "R;16N", 2, (1, 0, 0), (3, 0, 0), (5, 0, 0)) - self.assert_unpack("RGB", "G;16N", 2, (0, 1, 0), (0, 3, 0), (0, 5, 0)) - self.assert_unpack("RGB", "B;16N", 2, (0, 0, 1), (0, 0, 3), (0, 0, 5)) - - self.assert_unpack( - "RGB", "CMYK", 4, (250, 249, 248), (242, 241, 240), (234, 233, 233) - ) - - def test_BGR(self) -> None: - with pytest.warns(DeprecationWarning): - self.assert_unpack( - "BGR;15", "BGR;15", 3, (8, 131, 0), (24, 0, 8), (41, 131, 8) - ) - self.assert_unpack( - "BGR;16", "BGR;16", 3, (8, 64, 0), (24, 129, 0), (41, 194, 0) - ) - self.assert_unpack("BGR;24", "BGR;24", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) - - def test_RGBA(self) -> None: - self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6)) - self.assert_unpack( - "RGBA", "LA;16B", 4, (1, 1, 1, 3), (5, 5, 5, 7), (9, 9, 9, 11) - ) - self.assert_unpack( - "RGBA", "RGBA", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12) - ) - self.assert_unpack( - "RGBA", "RGBAX", 5, (1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14) - ) - self.assert_unpack( - "RGBA", "RGBAXX", 6, (1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16) - ) - self.assert_unpack( + [(127, 102, 0), (192, 227, 0), (213, 255, 170), (98, 255, 133)], + ), + ("RGB", "R", 1, [(1, 0, 0), (2, 0, 0), (3, 0, 0)]), + ("RGB", "G", 1, [(0, 1, 0), (0, 2, 0), (0, 3, 0)]), + ("RGB", "B", 1, [(0, 0, 1), (0, 0, 2), (0, 0, 3)]), + ("RGB", "R;16B", 2, [(1, 0, 0), (3, 0, 0), (5, 0, 0)]), + ("RGB", "G;16B", 2, [(0, 1, 0), (0, 3, 0), (0, 5, 0)]), + ("RGB", "B;16B", 2, [(0, 0, 1), (0, 0, 3), (0, 0, 5)]), + ("RGB", "R;16L", 2, [(2, 0, 0), (4, 0, 0), (6, 0, 0)]), + ("RGB", "G;16L", 2, [(0, 2, 0), (0, 4, 0), (0, 6, 0)]), + ("RGB", "B;16L", 2, [(0, 0, 2), (0, 0, 4), (0, 0, 6)]), + ] + + if is_big_endian(): + params += [ + ("RGB", "R;16N", 2, [(1, 0, 0), (3, 0, 0), (5, 0, 0)]), + ("RGB", "G;16N", 2, [(0, 1, 0), (0, 3, 0), (0, 5, 0)]), + ("RGB", "B;16N", 2, [(0, 0, 1), (0, 0, 3), (0, 0, 5)]), + ] + else: + params += [ + ("RGB", "R;16N", 2, [(2, 0, 0), (4, 0, 0), (6, 0, 0)]), + ("RGB", "G;16N", 2, [(0, 2, 0), (0, 4, 0), (0, 6, 0)]), + ("RGB", "B;16N", 2, [(0, 0, 2), (0, 0, 4), (0, 0, 6)]), + ] + + params += [ + ("RGB", "CMYK", 4, [(250, 249, 248), (242, 241, 240), (234, 233, 233)]), + ] + + # Mode RGBA + params += [ + ("RGBA", "LA", 2, [(1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6)]), + ("RGBA", "LA;16B", 4, [(1, 1, 1, 3), (5, 5, 5, 7), (9, 9, 9, 11)]), + ("RGBA", "RGBA", 4, [(1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)]), + ("RGBA", "RGBAX", 5, [(1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14)]), + ("RGBA", "RGBAXX", 6, [(1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16)]), + ( "RGBA", "RGBa", 4, - (63, 127, 191, 4), - (159, 191, 223, 8), - (191, 212, 233, 12), - ) - self.assert_unpack( + [ + (63, 127, 191, 4), + (159, 191, 223, 8), + (191, 212, 233, 12), + ], + ), + ( "RGBA", "RGBa", b"\x01\x02\x03\x00\x10\x20\x30\x7f\x10\x20\x30\xff", - (0, 0, 0, 0), - (32, 64, 96, 127), - (16, 32, 48, 255), - ) - self.assert_unpack( + [ + (0, 0, 0, 0), + (32, 64, 96, 127), + (16, 32, 48, 255), + ], + ), + ( "RGBA", "RGBaX", b"\x01\x02\x03\x00-\x10\x20\x30\x7f-\x10\x20\x30\xff-", - (0, 0, 0, 0), - (32, 64, 96, 127), - (16, 32, 48, 255), - ) - self.assert_unpack( + [ + (0, 0, 0, 0), + (32, 64, 96, 127), + (16, 32, 48, 255), + ], + ), + ( "RGBA", "RGBaXX", b"\x01\x02\x03\x00==\x10\x20\x30\x7f!!\x10\x20\x30\xff??", - (0, 0, 0, 0), - (32, 64, 96, 127), - (16, 32, 48, 255), - ) - self.assert_unpack( + [ + (0, 0, 0, 0), + (32, 64, 96, 127), + (16, 32, 48, 255), + ], + ), + ( "RGBA", "RGBa;16L", 8, - (63, 127, 191, 8), - (159, 191, 223, 16), - (191, 212, 233, 24), - ) - self.assert_unpack( + [ + (63, 127, 191, 8), + (159, 191, 223, 16), + (191, 212, 233, 24), + ], + ), + ( "RGBA", "RGBa;16L", b"\x88\x01\x88\x02\x88\x03\x88\x00\x88\x10\x88\x20\x88\x30\x88\xff", - (0, 0, 0, 0), - (16, 32, 48, 255), - ) - self.assert_unpack( + [ + (0, 0, 0, 0), + (16, 32, 48, 255), + ], + ), + ( "RGBA", "RGBa;16B", 8, - (36, 109, 182, 7), - (153, 187, 221, 15), - (188, 210, 232, 23), - ) - self.assert_unpack( + [ + (36, 109, 182, 7), + (153, 187, 221, 15), + (188, 210, 232, 23), + ], + ), + ( "RGBA", "RGBa;16B", b"\x01\x88\x02\x88\x03\x88\x00\x88\x10\x88\x20\x88\x30\x88\xff\x88", - (0, 0, 0, 0), - (16, 32, 48, 255), - ) - self.assert_unpack( + [ + (0, 0, 0, 0), + (16, 32, 48, 255), + ], + ), + ( "RGBA", "BGRa", 4, - (191, 127, 63, 4), - (223, 191, 159, 8), - (233, 212, 191, 12), - ) - self.assert_unpack( + [ + (191, 127, 63, 4), + (223, 191, 159, 8), + (233, 212, 191, 12), + ], + ), + ( "RGBA", "BGRa", b"\x01\x02\x03\x00\x10\x20\x30\xff", - (0, 0, 0, 0), - (48, 32, 16, 255), - ) - self.assert_unpack( + [ + (0, 0, 0, 0), + (48, 32, 16, 255), + ], + ), + ( "RGBA", "RGBA;I", 4, - (254, 253, 252, 4), - (250, 249, 248, 8), - (246, 245, 244, 12), - ) - self.assert_unpack( - "RGBA", "RGBA;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12) - ) - self.assert_unpack("RGBA", "RGBA;15", 2, (8, 131, 0, 0), (24, 0, 8, 0)) - self.assert_unpack("RGBA", "BGRA;15", 2, (0, 131, 8, 0), (8, 0, 24, 0)) - self.assert_unpack("RGBA", "RGBA;4B", 2, (17, 0, 34, 0), (51, 0, 68, 0)) - self.assert_unpack("RGBA", "RGBA;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16)) - self.assert_unpack("RGBA", "RGBA;16B", 8, (1, 3, 5, 7), (9, 11, 13, 15)) - self.assert_unpack("RGBA", "BGRA;16L", 8, (6, 4, 2, 8), (14, 12, 10, 16)) - self.assert_unpack("RGBA", "BGRA;16B", 8, (5, 3, 1, 7), (13, 11, 9, 15)) - self.assert_unpack( - "RGBA", "BGRA", 4, (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12) - ) - self.assert_unpack( - "RGBA", "ARGB", 4, (2, 3, 4, 1), (6, 7, 8, 5), (10, 11, 12, 9) - ) - self.assert_unpack( - "RGBA", "ABGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9) - ) - self.assert_unpack( + [ + (254, 253, 252, 4), + (250, 249, 248, 8), + (246, 245, 244, 12), + ], + ), + ("RGBA", "RGBA;L", 4, [(1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)]), + ("RGBA", "RGBA;15", 2, [(8, 131, 0, 0), (24, 0, 8, 0)]), + ("RGBA", "BGRA;15", 2, [(0, 131, 8, 0), (8, 0, 24, 0)]), + ("RGBA", "RGBA;4B", 2, [(17, 0, 34, 0), (51, 0, 68, 0)]), + ("RGBA", "RGBA;16L", 8, [(2, 4, 6, 8), (10, 12, 14, 16)]), + ("RGBA", "RGBA;16B", 8, [(1, 3, 5, 7), (9, 11, 13, 15)]), + ("RGBA", "BGRA;16L", 8, [(6, 4, 2, 8), (14, 12, 10, 16)]), + ("RGBA", "BGRA;16B", 8, [(5, 3, 1, 7), (13, 11, 9, 15)]), + ("RGBA", "BGRA", 4, [(3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)]), + ("RGBA", "ARGB", 4, [(2, 3, 4, 1), (6, 7, 8, 5), (10, 11, 12, 9)]), + ("RGBA", "ABGR", 4, [(4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)]), + ( "RGBA", "YCCA;P", b"]bE\x04\xdd\xbej\xed57T\xce\xac\xce:\x11", # random data - (0, 161, 0, 4), - (255, 255, 255, 237), - (27, 158, 0, 206), - (0, 118, 0, 17), - ) - self.assert_unpack("RGBA", "R", 1, (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)) - self.assert_unpack("RGBA", "G", 1, (0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0)) - self.assert_unpack("RGBA", "B", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)) - self.assert_unpack("RGBA", "A", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) - - self.assert_unpack("RGBA", "R;16B", 2, (1, 0, 0, 0), (3, 0, 0, 0), (5, 0, 0, 0)) - self.assert_unpack("RGBA", "G;16B", 2, (0, 1, 0, 0), (0, 3, 0, 0), (0, 5, 0, 0)) - self.assert_unpack("RGBA", "B;16B", 2, (0, 0, 1, 0), (0, 0, 3, 0), (0, 0, 5, 0)) - self.assert_unpack("RGBA", "A;16B", 2, (0, 0, 0, 1), (0, 0, 0, 3), (0, 0, 0, 5)) - - self.assert_unpack("RGBA", "R;16L", 2, (2, 0, 0, 0), (4, 0, 0, 0), (6, 0, 0, 0)) - self.assert_unpack("RGBA", "G;16L", 2, (0, 2, 0, 0), (0, 4, 0, 0), (0, 6, 0, 0)) - self.assert_unpack("RGBA", "B;16L", 2, (0, 0, 2, 0), (0, 0, 4, 0), (0, 0, 6, 0)) - self.assert_unpack("RGBA", "A;16L", 2, (0, 0, 0, 2), (0, 0, 0, 4), (0, 0, 0, 6)) - - if sys.byteorder == "little": - self.assert_unpack( - "RGBA", "R;16N", 2, (2, 0, 0, 0), (4, 0, 0, 0), (6, 0, 0, 0) - ) - self.assert_unpack( - "RGBA", "G;16N", 2, (0, 2, 0, 0), (0, 4, 0, 0), (0, 6, 0, 0) - ) - self.assert_unpack( - "RGBA", "B;16N", 2, (0, 0, 2, 0), (0, 0, 4, 0), (0, 0, 6, 0) - ) - self.assert_unpack( - "RGBA", "A;16N", 2, (0, 0, 0, 2), (0, 0, 0, 4), (0, 0, 0, 6) - ) - else: - self.assert_unpack( - "RGBA", "R;16N", 2, (1, 0, 0, 0), (3, 0, 0, 0), (5, 0, 0, 0) - ) - self.assert_unpack( - "RGBA", "G;16N", 2, (0, 1, 0, 0), (0, 3, 0, 0), (0, 5, 0, 0) - ) - self.assert_unpack( - "RGBA", "B;16N", 2, (0, 0, 1, 0), (0, 0, 3, 0), (0, 0, 5, 0) - ) - self.assert_unpack( - "RGBA", "A;16N", 2, (0, 0, 0, 1), (0, 0, 0, 3), (0, 0, 0, 5) - ) - - def test_RGBa(self) -> None: - self.assert_unpack( - "RGBa", "RGBa", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12) - ) - self.assert_unpack( - "RGBa", "BGRa", 4, (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12) - ) - self.assert_unpack( - "RGBa", "aRGB", 4, (2, 3, 4, 1), (6, 7, 8, 5), (10, 11, 12, 9) - ) - self.assert_unpack( - "RGBa", "aBGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9) - ) - - def test_RGBX(self) -> None: - self.assert_unpack("RGBX", "RGB", 3, (1, 2, 3, X), (4, 5, 6, X), (7, 8, 9, X)) - self.assert_unpack("RGBX", "RGB;L", 3, (1, 4, 7, X), (2, 5, 8, X), (3, 6, 9, X)) - self.assert_unpack("RGBX", "RGB;16B", 6, (1, 3, 5, X), (7, 9, 11, X)) - self.assert_unpack("RGBX", "BGR", 3, (3, 2, 1, X), (6, 5, 4, X), (9, 8, 7, X)) - self.assert_unpack("RGBX", "RGB;15", 2, (8, 131, 0, X), (24, 0, 8, X)) - self.assert_unpack("RGBX", "BGR;15", 2, (0, 131, 8, X), (8, 0, 24, X)) - self.assert_unpack("RGBX", "RGB;4B", 2, (17, 0, 34, X), (51, 0, 68, X)) - self.assert_unpack( - "RGBX", "RGBX", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12) - ) - self.assert_unpack( - "RGBX", "RGBXX", 5, (1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14) - ) - self.assert_unpack( - "RGBX", "RGBXXX", 6, (1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16) - ) - self.assert_unpack( - "RGBX", "RGBX;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12) - ) - self.assert_unpack("RGBX", "RGBX;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16)) - self.assert_unpack("RGBX", "RGBX;16B", 8, (1, 3, 5, 7), (9, 11, 13, 15)) - self.assert_unpack( - "RGBX", "BGRX", 4, (3, 2, 1, X), (7, 6, 5, X), (11, 10, 9, X) - ) - self.assert_unpack( - "RGBX", "XRGB", 4, (2, 3, 4, X), (6, 7, 8, X), (10, 11, 12, X) - ) - self.assert_unpack( - "RGBX", "XBGR", 4, (4, 3, 2, X), (8, 7, 6, X), (12, 11, 10, X) - ) - self.assert_unpack( + [ + (0, 161, 0, 4), + (255, 255, 255, 237), + (27, 158, 0, 206), + (0, 118, 0, 17), + ], + ), + ("RGBA", "R", 1, [(1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)]), + ("RGBA", "G", 1, [(0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0)]), + ("RGBA", "B", 1, [(0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)]), + ("RGBA", "A", 1, [(0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)]), + ("RGBA", "R;16B", 2, [(1, 0, 0, 0), (3, 0, 0, 0), (5, 0, 0, 0)]), + ("RGBA", "G;16B", 2, [(0, 1, 0, 0), (0, 3, 0, 0), (0, 5, 0, 0)]), + ("RGBA", "B;16B", 2, [(0, 0, 1, 0), (0, 0, 3, 0), (0, 0, 5, 0)]), + ("RGBA", "A;16B", 2, [(0, 0, 0, 1), (0, 0, 0, 3), (0, 0, 0, 5)]), + ("RGBA", "R;16L", 2, [(2, 0, 0, 0), (4, 0, 0, 0), (6, 0, 0, 0)]), + ("RGBA", "G;16L", 2, [(0, 2, 0, 0), (0, 4, 0, 0), (0, 6, 0, 0)]), + ("RGBA", "B;16L", 2, [(0, 0, 2, 0), (0, 0, 4, 0), (0, 0, 6, 0)]), + ("RGBA", "A;16L", 2, [(0, 0, 0, 2), (0, 0, 0, 4), (0, 0, 0, 6)]), + ] + + if is_big_endian(): + params += [ + ("RGBA", "R;16N", 2, [(1, 0, 0, 0), (3, 0, 0, 0), (5, 0, 0, 0)]), + ("RGBA", "G;16N", 2, [(0, 1, 0, 0), (0, 3, 0, 0), (0, 5, 0, 0)]), + ("RGBA", "B;16N", 2, [(0, 0, 1, 0), (0, 0, 3, 0), (0, 0, 5, 0)]), + ("RGBA", "A;16N", 2, [(0, 0, 0, 1), (0, 0, 0, 3), (0, 0, 0, 5)]), + ] + else: + params += [ + ("RGBA", "R;16N", 2, [(2, 0, 0, 0), (4, 0, 0, 0), (6, 0, 0, 0)]), + ("RGBA", "G;16N", 2, [(0, 2, 0, 0), (0, 4, 0, 0), (0, 6, 0, 0)]), + ("RGBA", "B;16N", 2, [(0, 0, 2, 0), (0, 0, 4, 0), (0, 0, 6, 0)]), + ("RGBA", "A;16N", 2, [(0, 0, 0, 2), (0, 0, 0, 4), (0, 0, 0, 6)]), + ] + + # Mode RGBa + params += [ + ("RGBa", "RGBa", 4, [(1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)]), + ("RGBa", "BGRa", 4, [(3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)]), + ("RGBa", "aRGB", 4, [(2, 3, 4, 1), (6, 7, 8, 5), (10, 11, 12, 9)]), + ("RGBa", "aBGR", 4, [(4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)]), + ] + + # Mode RGBX + params += [ + ("RGBX", "RGB", 3, [(1, 2, 3, X), (4, 5, 6, X), (7, 8, 9, X)]), + ("RGBX", "RGB;L", 3, [(1, 4, 7, X), (2, 5, 8, X), (3, 6, 9, X)]), + ("RGBX", "RGB;16B", 6, [(1, 3, 5, X), (7, 9, 11, X)]), + ("RGBX", "BGR", 3, [(3, 2, 1, X), (6, 5, 4, X), (9, 8, 7, X)]), + ("RGBX", "RGB;15", 2, [(8, 131, 0, X), (24, 0, 8, X)]), + ("RGBX", "BGR;15", 2, [(0, 131, 8, X), (8, 0, 24, X)]), + ("RGBX", "RGB;4B", 2, [(17, 0, 34, X), (51, 0, 68, X)]), + ("RGBX", "RGBX", 4, [(1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)]), + ("RGBX", "RGBXX", 5, [(1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14)]), + ("RGBX", "RGBXXX", 6, [(1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16)]), + ("RGBX", "RGBX;L", 4, [(1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)]), + ("RGBX", "RGBX;16L", 8, [(2, 4, 6, 8), (10, 12, 14, 16)]), + ("RGBX", "RGBX;16B", 8, [(1, 3, 5, 7), (9, 11, 13, 15)]), + ("RGBX", "BGRX", 4, [(3, 2, 1, X), (7, 6, 5, X), (11, 10, 9, X)]), + ("RGBX", "XRGB", 4, [(2, 3, 4, X), (6, 7, 8, X), (10, 11, 12, X)]), + ("RGBX", "XBGR", 4, [(4, 3, 2, X), (8, 7, 6, X), (12, 11, 10, X)]), + ( "RGBX", "YCC;P", b"D]\x9c\x82\x1a\x91\xfaOC\xe7J\x12", # random data - (127, 102, 0, X), - (192, 227, 0, X), - (213, 255, 170, X), - (98, 255, 133, X), - ) - self.assert_unpack("RGBX", "R", 1, (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)) - self.assert_unpack("RGBX", "G", 1, (0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0)) - self.assert_unpack("RGBX", "B", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)) - self.assert_unpack("RGBX", "X", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) - - def test_CMYK(self) -> None: - self.assert_unpack( - "CMYK", "CMYK", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12) - ) - self.assert_unpack( - "CMYK", "CMYKX", 5, (1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14) - ) - self.assert_unpack( - "CMYK", "CMYKXX", 6, (1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16) - ) - self.assert_unpack( + [ + (127, 102, 0, X), + (192, 227, 0, X), + (213, 255, 170, X), + (98, 255, 133, X), + ], + ), + ("RGBX", "R", 1, [(1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)]), + ("RGBX", "G", 1, [(0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0)]), + ("RGBX", "B", 1, [(0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)]), + ("RGBX", "X", 1, [(0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)]), + ] + + # Mode CMYK + params += [ + ("CMYK", "CMYK", 4, [(1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)]), + ("CMYK", "CMYKX", 5, [(1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14)]), + ("CMYK", "CMYKXX", 6, [(1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16)]), + ( "CMYK", "CMYK;I", 4, - (254, 253, 252, 251), - (250, 249, 248, 247), - (246, 245, 244, 243), - ) - self.assert_unpack( - "CMYK", "CMYK;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12) - ) - self.assert_unpack("CMYK", "C", 1, (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)) - self.assert_unpack("CMYK", "M", 1, (0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0)) - self.assert_unpack("CMYK", "Y", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)) - self.assert_unpack("CMYK", "K", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) - self.assert_unpack( - "CMYK", "C;I", 1, (254, 0, 0, 0), (253, 0, 0, 0), (252, 0, 0, 0) - ) - self.assert_unpack( - "CMYK", "M;I", 1, (0, 254, 0, 0), (0, 253, 0, 0), (0, 252, 0, 0) - ) - self.assert_unpack( - "CMYK", "Y;I", 1, (0, 0, 254, 0), (0, 0, 253, 0), (0, 0, 252, 0) - ) - self.assert_unpack( - "CMYK", "K;I", 1, (0, 0, 0, 254), (0, 0, 0, 253), (0, 0, 0, 252) - ) - - def test_YCbCr(self) -> None: - self.assert_unpack("YCbCr", "YCbCr", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) - self.assert_unpack("YCbCr", "YCbCr;L", 3, (1, 4, 7), (2, 5, 8), (3, 6, 9)) - self.assert_unpack("YCbCr", "YCbCrK", 4, (1, 2, 3), (5, 6, 7), (9, 10, 11)) - self.assert_unpack("YCbCr", "YCbCrX", 4, (1, 2, 3), (5, 6, 7), (9, 10, 11)) - - def test_LAB(self) -> None: - self.assert_unpack("LAB", "LAB", 3, (1, 130, 131), (4, 133, 134), (7, 136, 137)) - self.assert_unpack("LAB", "L", 1, (1, 0, 0), (2, 0, 0), (3, 0, 0)) - self.assert_unpack("LAB", "A", 1, (0, 1, 0), (0, 2, 0), (0, 3, 0)) - self.assert_unpack("LAB", "B", 1, (0, 0, 1), (0, 0, 2), (0, 0, 3)) - - def test_HSV(self) -> None: - self.assert_unpack("HSV", "HSV", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) - self.assert_unpack("HSV", "H", 1, (1, 0, 0), (2, 0, 0), (3, 0, 0)) - self.assert_unpack("HSV", "S", 1, (0, 1, 0), (0, 2, 0), (0, 3, 0)) - self.assert_unpack("HSV", "V", 1, (0, 0, 1), (0, 0, 2), (0, 0, 3)) - - def test_I(self) -> None: - self.assert_unpack("I", "I;8", 1, 0x01, 0x02, 0x03, 0x04) - self.assert_unpack("I", "I;8S", b"\x01\x83", 1, -125) - self.assert_unpack("I", "I;16", 2, 0x0201, 0x0403) - self.assert_unpack("I", "I;16S", b"\x83\x01\x01\x83", 0x0183, -31999) - self.assert_unpack("I", "I;16B", 2, 0x0102, 0x0304) - self.assert_unpack("I", "I;16BS", b"\x83\x01\x01\x83", -31999, 0x0183) - self.assert_unpack("I", "I;32", 4, 0x04030201, 0x08070605) - self.assert_unpack( - "I", "I;32S", b"\x83\x00\x00\x01\x01\x00\x00\x83", 0x01000083, -2097151999 - ) - self.assert_unpack("I", "I;32B", 4, 0x01020304, 0x05060708) - self.assert_unpack( - "I", "I;32BS", b"\x83\x00\x00\x01\x01\x00\x00\x83", -2097151999, 0x01000083 - ) - - if sys.byteorder == "little": - self.assert_unpack("I", "I", 4, 0x04030201, 0x08070605) - self.assert_unpack("I", "I;16N", 2, 0x0201, 0x0403) - self.assert_unpack("I", "I;16NS", b"\x83\x01\x01\x83", 0x0183, -31999) - self.assert_unpack("I", "I;32N", 4, 0x04030201, 0x08070605) - self.assert_unpack( + [ + (254, 253, 252, 251), + (250, 249, 248, 247), + (246, 245, 244, 243), + ], + ), + ("CMYK", "CMYK;L", 4, [(1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)]), + ("CMYK", "C", 1, [(1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)]), + ("CMYK", "M", 1, [(0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0)]), + ("CMYK", "Y", 1, [(0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)]), + ("CMYK", "K", 1, [(0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)]), + ("CMYK", "C;I", 1, [(254, 0, 0, 0), (253, 0, 0, 0), (252, 0, 0, 0)]), + ("CMYK", "M;I", 1, [(0, 254, 0, 0), (0, 253, 0, 0), (0, 252, 0, 0)]), + ("CMYK", "Y;I", 1, [(0, 0, 254, 0), (0, 0, 253, 0), (0, 0, 252, 0)]), + ("CMYK", "K;I", 1, [(0, 0, 0, 254), (0, 0, 0, 253), (0, 0, 0, 252)]), + ] + + # Mode YCbCr + params += [ + ("YCbCr", "YCbCr", 3, [(1, 2, 3), (4, 5, 6), (7, 8, 9)]), + ("YCbCr", "YCbCr;L", 3, [(1, 4, 7), (2, 5, 8), (3, 6, 9)]), + ("YCbCr", "YCbCrK", 4, [(1, 2, 3), (5, 6, 7), (9, 10, 11)]), + ("YCbCr", "YCbCrX", 4, [(1, 2, 3), (5, 6, 7), (9, 10, 11)]), + ] + + # Mode LAB + params += [ + ("LAB", "LAB", 3, [(1, 130, 131), (4, 133, 134), (7, 136, 137)]), + ("LAB", "L", 1, [(1, 0, 0), (2, 0, 0), (3, 0, 0)]), + ("LAB", "A", 1, [(0, 1, 0), (0, 2, 0), (0, 3, 0)]), + ("LAB", "B", 1, [(0, 0, 1), (0, 0, 2), (0, 0, 3)]), + ] + + # Mode HSV + params += [ + ("HSV", "HSV", 3, [(1, 2, 3), (4, 5, 6), (7, 8, 9)]), + ("HSV", "H", 1, [(1, 0, 0), (2, 0, 0), (3, 0, 0)]), + ("HSV", "S", 1, [(0, 1, 0), (0, 2, 0), (0, 3, 0)]), + ("HSV", "V", 1, [(0, 0, 1), (0, 0, 2), (0, 0, 3)]), + ] + + # Mode I + params += [ + ("I", "I;8", 1, [0x01, 0x02, 0x03, 0x04]), + ("I", "I;8S", b"\x01\x83", [1, -125]), + ("I", "I;16", 2, [0x0201, 0x0403]), + ("I", "I;16S", b"\x83\x01\x01\x83", [0x0183, -31999]), + ("I", "I;16B", 2, [0x0102, 0x0304]), + ("I", "I;16BS", b"\x83\x01\x01\x83", [-31999, 0x0183]), + ("I", "I;32", 4, [0x04030201, 0x08070605]), + ("I", "I;32S", b"\x83\x00\x00\x01\x01\x00\x00\x83", [0x01000083, -2097151999]), + ("I", "I;32B", 4, [0x01020304, 0x05060708]), + ("I", "I;32BS", b"\x83\x00\x00\x01\x01\x00\x00\x83", [-2097151999, 0x01000083]), + ] + if is_big_endian(): + params += [ + ("I", "I", 4, [0x01020304, 0x05060708]), + ("I", "I;16N", 2, [0x0102, 0x0304]), + ("I", "I;16NS", b"\x83\x01\x01\x83", [-31999, 0x0183]), + ("I", "I;32N", 4, [0x01020304, 0x05060708]), + ( "I", "I;32NS", b"\x83\x00\x00\x01\x01\x00\x00\x83", - 0x01000083, - -2097151999, - ) - else: - self.assert_unpack("I", "I", 4, 0x01020304, 0x05060708) - self.assert_unpack("I", "I;16N", 2, 0x0102, 0x0304) - self.assert_unpack("I", "I;16NS", b"\x83\x01\x01\x83", -31999, 0x0183) - self.assert_unpack("I", "I;32N", 4, 0x01020304, 0x05060708) - self.assert_unpack( + [ + -2097151999, + 0x01000083, + ], + ), + ] + else: + params += [ + ("I", "I", 4, [0x04030201, 0x08070605]), + ("I", "I;16N", 2, [0x0201, 0x0403]), + ("I", "I;16NS", b"\x83\x01\x01\x83", [0x0183, -31999]), + ("I", "I;32N", 4, [0x04030201, 0x08070605]), + ( "I", "I;32NS", b"\x83\x00\x00\x01\x01\x00\x00\x83", - -2097151999, - 0x01000083, - ) - - def test_F_int(self) -> None: - self.assert_unpack("F", "F;8", 1, 0x01, 0x02, 0x03, 0x04) - self.assert_unpack("F", "F;8S", b"\x01\x83", 1, -125) - self.assert_unpack("F", "F;16", 2, 0x0201, 0x0403) - self.assert_unpack("F", "F;16S", b"\x83\x01\x01\x83", 0x0183, -31999) - self.assert_unpack("F", "F;16B", 2, 0x0102, 0x0304) - self.assert_unpack("F", "F;16BS", b"\x83\x01\x01\x83", -31999, 0x0183) - self.assert_unpack("F", "F;32", 4, 67305984, 134678016) - self.assert_unpack( - "F", "F;32S", b"\x83\x00\x00\x01\x01\x00\x00\x83", 16777348, -2097152000 - ) - self.assert_unpack("F", "F;32B", 4, 0x01020304, 0x05060708) - self.assert_unpack( - "F", "F;32BS", b"\x83\x00\x00\x01\x01\x00\x00\x83", -2097152000, 16777348 - ) - - if sys.byteorder == "little": - self.assert_unpack("F", "F;16N", 2, 0x0201, 0x0403) - self.assert_unpack("F", "F;16NS", b"\x83\x01\x01\x83", 0x0183, -31999) - self.assert_unpack("F", "F;32N", 4, 67305984, 134678016) - self.assert_unpack( + [ + 0x01000083, + -2097151999, + ], + ), + ] + + # Mode F int + params += [ + ("F", "F;8", 1, [0x01, 0x02, 0x03, 0x04]), + ("F", "F;8S", b"\x01\x83", [1, -125]), + ("F", "F;16", 2, [0x0201, 0x0403]), + ("F", "F;16S", b"\x83\x01\x01\x83", [0x0183, -31999]), + ("F", "F;16B", 2, [0x0102, 0x0304]), + ("F", "F;16BS", b"\x83\x01\x01\x83", [-31999, 0x0183]), + ("F", "F;32", 4, [67305984, 134678016]), + ("F", "F;32S", b"\x83\x00\x00\x01\x01\x00\x00\x83", [16777348, -2097152000]), + ("F", "F;32B", 4, [0x01020304, 0x05060708]), + ("F", "F;32BS", b"\x83\x00\x00\x01\x01\x00\x00\x83", [-2097152000, 16777348]), + ] + if is_big_endian(): + params += [ + ("F", "F;16N", 2, [0x0102, 0x0304]), + ("F", "F;16NS", b"\x83\x01\x01\x83", [-31999, 0x0183]), + ("F", "F;32N", 4, [0x01020304, 0x05060708]), + ( "F", "F;32NS", b"\x83\x00\x00\x01\x01\x00\x00\x83", - 16777348, - -2097152000, - ) - else: - self.assert_unpack("F", "F;16N", 2, 0x0102, 0x0304) - self.assert_unpack("F", "F;16NS", b"\x83\x01\x01\x83", -31999, 0x0183) - self.assert_unpack("F", "F;32N", 4, 0x01020304, 0x05060708) - self.assert_unpack( + [-2097152000, 16777348], + ), + ] + else: + params += [ + ("F", "F;16N", 2, [0x0201, 0x0403]), + ("F", "F;16NS", b"\x83\x01\x01\x83", [0x0183, -31999]), + ("F", "F;32N", 4, [67305984, 134678016]), + ( "F", "F;32NS", b"\x83\x00\x00\x01\x01\x00\x00\x83", - -2097152000, - 16777348, - ) - - def test_F_float(self) -> None: - self.assert_unpack( - "F", "F;32F", 4, 1.539989614439558e-36, 4.063216068939723e-34 - ) - self.assert_unpack( - "F", "F;32BF", 4, 2.387939260590663e-38, 6.301941157072183e-36 - ) - self.assert_unpack( + [16777348, -2097152000], + ), + ] + + # Mode F float + params += [ + ("F", "F;32F", 4, [1.539989614439558e-36, 4.063216068939723e-34]), + ("F", "F;32BF", 4, [2.387939260590663e-38, 6.301941157072183e-36]), + ( "F", "F;64F", b"333333\xc3?\x00\x00\x00\x00\x00J\x93\xc0", # by struct.pack - 0.15000000596046448, - -1234.5, - ) - self.assert_unpack( + [0.15000000596046448, -1234.5], + ), + ( "F", "F;64BF", b"?\xc3333333\xc0\x93J\x00\x00\x00\x00\x00", # by struct.pack - 0.15000000596046448, - -1234.5, - ) - - if sys.byteorder == "little": - self.assert_unpack( - "F", "F", 4, 1.539989614439558e-36, 4.063216068939723e-34 - ) - self.assert_unpack( - "F", "F;32NF", 4, 1.539989614439558e-36, 4.063216068939723e-34 - ) - self.assert_unpack( + [0.15000000596046448, -1234.5], + ), + ] + if is_big_endian(): + params += [ + ("F", "F", 4, [2.387939260590663e-38, 6.301941157072183e-36]), + ("F", "F;32NF", 4, [2.387939260590663e-38, 6.301941157072183e-36]), + ( "F", "F;64NF", - b"333333\xc3?\x00\x00\x00\x00\x00J\x93\xc0", - 0.15000000596046448, - -1234.5, - ) - else: - self.assert_unpack( - "F", "F", 4, 2.387939260590663e-38, 6.301941157072183e-36 - ) - self.assert_unpack( - "F", "F;32NF", 4, 2.387939260590663e-38, 6.301941157072183e-36 - ) - self.assert_unpack( + b"?\xc3333333\xc0\x93J\x00\x00\x00\x00\x00", + [0.15000000596046448, -1234.5], + ), + ] + else: + params += [ + ("F", "F", 4, [1.539989614439558e-36, 4.063216068939723e-34]), + ("F", "F;32NF", 4, [1.539989614439558e-36, 4.063216068939723e-34]), + ( "F", "F;64NF", - b"?\xc3333333\xc0\x93J\x00\x00\x00\x00\x00", - 0.15000000596046448, - -1234.5, - ) - - def test_I16(self) -> None: - self.assert_unpack("I;16", "I;16", 2, 0x0201, 0x0403, 0x0605) - self.assert_unpack("I;16", "I;16B", 2, 0x0102, 0x0304, 0x0506) - self.assert_unpack("I;16B", "I;16B", 2, 0x0102, 0x0304, 0x0506) - self.assert_unpack("I;16L", "I;16L", 2, 0x0201, 0x0403, 0x0605) - self.assert_unpack("I;16", "I;12", 2, 0x0010, 0x0203, 0x0040) - if sys.byteorder == "little": - self.assert_unpack("I;16", "I;16N", 2, 0x0201, 0x0403, 0x0605) - self.assert_unpack("I;16B", "I;16N", 2, 0x0201, 0x0403, 0x0605) - self.assert_unpack("I;16L", "I;16N", 2, 0x0201, 0x0403, 0x0605) - self.assert_unpack("I;16N", "I;16N", 2, 0x0201, 0x0403, 0x0605) - else: - self.assert_unpack("I;16", "I;16N", 2, 0x0102, 0x0304, 0x0506) - self.assert_unpack("I;16B", "I;16N", 2, 0x0102, 0x0304, 0x0506) - self.assert_unpack("I;16L", "I;16N", 2, 0x0102, 0x0304, 0x0506) - self.assert_unpack("I;16N", "I;16N", 2, 0x0102, 0x0304, 0x0506) - - def test_CMYK16(self) -> None: - self.assert_unpack("CMYK", "CMYK;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16)) - self.assert_unpack("CMYK", "CMYK;16B", 8, (1, 3, 5, 7), (9, 11, 13, 15)) - if sys.byteorder == "little": - self.assert_unpack("CMYK", "CMYK;16N", 8, (2, 4, 6, 8), (10, 12, 14, 16)) - else: - self.assert_unpack("CMYK", "CMYK;16N", 8, (1, 3, 5, 7), (9, 11, 13, 15)) - - def test_value_error(self) -> None: - with pytest.raises(ValueError): - self.assert_unpack("L", "L", 0, 0) - with pytest.raises(ValueError): - self.assert_unpack("RGB", "RGB", 2, 0) - with pytest.raises(ValueError): - self.assert_unpack("CMYK", "CMYK", 2, 0) + b"333333\xc3?\x00\x00\x00\x00\x00J\x93\xc0", + [0.15000000596046448, -1234.5], + ), + ] + + # Mode I;16 + params += [ + ("I;16", "I;16", 2, [0x0201, 0x0403, 0x0605]), + ("I;16", "I;16B", 2, [0x0102, 0x0304, 0x0506]), + ("I;16B", "I;16B", 2, [0x0102, 0x0304, 0x0506]), + ("I;16L", "I;16L", 2, [0x0201, 0x0403, 0x0605]), + ("I;16", "I;12", 2, [0x0010, 0x0203, 0x0040]), + ] + if is_big_endian(): + params += [ + ("I;16", "I;16N", 2, [0x0102, 0x0304, 0x0506]), + ("I;16B", "I;16N", 2, [0x0102, 0x0304, 0x0506]), + ("I;16L", "I;16N", 2, [0x0102, 0x0304, 0x0506]), + ("I;16N", "I;16N", 2, [0x0102, 0x0304, 0x0506]), + ] + else: + params += [ + ("I;16", "I;16N", 2, [0x0201, 0x0403, 0x0605]), + ("I;16B", "I;16N", 2, [0x0201, 0x0403, 0x0605]), + ("I;16L", "I;16N", 2, [0x0201, 0x0403, 0x0605]), + ("I;16N", "I;16N", 2, [0x0201, 0x0403, 0x0605]), + ] + + # Mode CMYK + params += [ + ("CMYK", "CMYK;16L", 8, [(2, 4, 6, 8), (10, 12, 14, 16)]), + ("CMYK", "CMYK;16B", 8, [(1, 3, 5, 7), (9, 11, 13, 15)]), + ] + if is_big_endian(): + params += [ + ("CMYK", "CMYK;16N", 8, [(1, 3, 5, 7), (9, 11, 13, 15)]), + ] + else: + params += [ + ("CMYK", "CMYK;16N", 8, [(2, 4, 6, 8), (10, 12, 14, 16)]), + ] + + return params + + +@pytest.mark.parametrize("mode, rawmode, data, pixels", get_unpack_parameters()) +def test_unpack( + mode: str, rawmode: str, data: bytes | int, pixels: list[float | tuple[int, ...]] +) -> None: + """ + Test unpacking from {rawmode} to {mode}. + data - Either the number of bytes in a pixel of the rawmode, or a byte string. + If a number is given, bytes will be generated starting at 1. + The byte string will be used to populate the starting image. + pixels - The pixels to be compared against the image pixels after unpacking. + """ + if isinstance(data, int): + data_len = data * len(pixels) + data = bytes(range(1, data_len + 1)) + + im = Image.frombytes(mode, (len(pixels), 1), data, "raw", rawmode, 0, 1) + + for x, pixel in enumerate(pixels): + assert pixel == im.getpixel((x, 0)) + + +@pytest.mark.parametrize( + "mode, rawmode, data, pixels", + ( + # Mode BGR + ("BGR;15", "BGR;15", 3, [(8, 131, 0), (24, 0, 8), (41, 131, 8)]), + ("BGR;16", "BGR;16", 3, [(8, 64, 0), (24, 129, 0), (41, 194, 0)]), + ("BGR;24", "BGR;24", 3, [(1, 2, 3), (4, 5, 6), (7, 8, 9)]), + ), +) +def test_unpack_deprecated( + mode: str, rawmode: str, data: int | bytes, pixels: list[float | tuple[int, ...]] +) -> None: + with pytest.warns(DeprecationWarning): + test_unpack(mode, rawmode, data, pixels) + + +@pytest.mark.parametrize( + "mode, rawmode, data, pixels", + ( + ("L", "L", 0, [0]), + ("RGB", "RGB", 2, [0]), + ("CMYK", "CMYK", 2, [0]), + ), +) +def test_unpack_valueerror( + mode: str, rawmode: str, data: int | bytes, pixels: list[float | tuple[int, ...]] +) -> None: + with pytest.raises(ValueError): + test_unpack(mode, rawmode, data, pixels) diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index b9cefafdd07..6d71049a9d5 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -313,6 +313,7 @@ def _read_blp_header(self) -> None: self._blp_lengths = struct.unpack("<16I", self._safe_read(16 * 4)) def _safe_read(self, length: int) -> bytes: + assert self.fd is not None return ImageFile._safe_read(self.fd, length) def _read_palette(self) -> list[tuple[int, int, int, int]]: diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index ef204533712..48bdd98307c 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -25,7 +25,7 @@ from __future__ import annotations import os -from typing import IO +from typing import IO, Any from . import Image, ImageFile, ImagePalette from ._binary import i16le as i16 @@ -72,16 +72,20 @@ class BmpImageFile(ImageFile.ImageFile): for k, v in COMPRESSIONS.items(): vars()[k] = v - def _bitmap(self, header=0, offset=0): + def _bitmap(self, header: int = 0, offset: int = 0) -> None: """Read relevant info about the BMP""" read, seek = self.fp.read, self.fp.seek if header: seek(header) # read bmp header size @offset 14 (this is part of the header size) - file_info = {"header_size": i32(read(4)), "direction": -1} + file_info: dict[str, bool | int | tuple[int, ...]] = { + "header_size": i32(read(4)), + "direction": -1, + } # -------------------- If requested, read header at a specific position # read the rest of the bmp header, without its size + assert isinstance(file_info["header_size"], int) header_data = ImageFile._safe_read(self.fp, file_info["header_size"] - 4) # ------------------------------- Windows Bitmap v2, IBM OS/2 Bitmap v1 @@ -92,7 +96,7 @@ def _bitmap(self, header=0, offset=0): file_info["height"] = i16(header_data, 2) file_info["planes"] = i16(header_data, 4) file_info["bits"] = i16(header_data, 6) - file_info["compression"] = self.RAW + file_info["compression"] = self.COMPRESSIONS["RAW"] file_info["palette_padding"] = 3 # --------------------------------------------- Windows Bitmap v3 to v5 @@ -122,8 +126,9 @@ def _bitmap(self, header=0, offset=0): ) file_info["colors"] = i32(header_data, 28) file_info["palette_padding"] = 4 + assert isinstance(file_info["pixels_per_meter"], tuple) self.info["dpi"] = tuple(x / 39.3701 for x in file_info["pixels_per_meter"]) - if file_info["compression"] == self.BITFIELDS: + if file_info["compression"] == self.COMPRESSIONS["BITFIELDS"]: masks = ["r_mask", "g_mask", "b_mask"] if len(header_data) >= 48: if len(header_data) >= 52: @@ -144,6 +149,10 @@ def _bitmap(self, header=0, offset=0): file_info["a_mask"] = 0x0 for mask in masks: file_info[mask] = i32(read(4)) + assert isinstance(file_info["r_mask"], int) + assert isinstance(file_info["g_mask"], int) + assert isinstance(file_info["b_mask"], int) + assert isinstance(file_info["a_mask"], int) file_info["rgb_mask"] = ( file_info["r_mask"], file_info["g_mask"], @@ -164,24 +173,26 @@ def _bitmap(self, header=0, offset=0): self._size = file_info["width"], file_info["height"] # ------- If color count was not found in the header, compute from bits + assert isinstance(file_info["bits"], int) file_info["colors"] = ( file_info["colors"] if file_info.get("colors", 0) else (1 << file_info["bits"]) ) + assert isinstance(file_info["colors"], int) if offset == 14 + file_info["header_size"] and file_info["bits"] <= 8: offset += 4 * file_info["colors"] # ---------------------- Check bit depth for unusual unsupported values - self._mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None)) - if self.mode is None: + self._mode, raw_mode = BIT2MODE.get(file_info["bits"], ("", "")) + if not self.mode: msg = f"Unsupported BMP pixel depth ({file_info['bits']})" raise OSError(msg) # ---------------- Process BMP with Bitfields compression (not palette) decoder_name = "raw" - if file_info["compression"] == self.BITFIELDS: - SUPPORTED = { + if file_info["compression"] == self.COMPRESSIONS["BITFIELDS"]: + SUPPORTED: dict[int, list[tuple[int, ...]]] = { 32: [ (0xFF0000, 0xFF00, 0xFF, 0x0), (0xFF000000, 0xFF0000, 0xFF00, 0x0), @@ -213,12 +224,14 @@ def _bitmap(self, header=0, offset=0): file_info["bits"] == 32 and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]] ): + assert isinstance(file_info["rgba_mask"], tuple) raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])] self._mode = "RGBA" if "A" in raw_mode else self.mode elif ( file_info["bits"] in (24, 16) and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]] ): + assert isinstance(file_info["rgb_mask"], tuple) raw_mode = MASK_MODES[(file_info["bits"], file_info["rgb_mask"])] else: msg = "Unsupported BMP bitfields layout" @@ -226,10 +239,13 @@ def _bitmap(self, header=0, offset=0): else: msg = "Unsupported BMP bitfields layout" raise OSError(msg) - elif file_info["compression"] == self.RAW: + elif file_info["compression"] == self.COMPRESSIONS["RAW"]: if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset raw_mode, self._mode = "BGRA", "RGBA" - elif file_info["compression"] in (self.RLE8, self.RLE4): + elif file_info["compression"] in ( + self.COMPRESSIONS["RLE8"], + self.COMPRESSIONS["RLE4"], + ): decoder_name = "bmp_rle" else: msg = f"Unsupported BMP compression ({file_info['compression']})" @@ -242,6 +258,7 @@ def _bitmap(self, header=0, offset=0): msg = f"Unsupported BMP Palette size ({file_info['colors']})" raise OSError(msg) else: + assert isinstance(file_info["palette_padding"], int) padding = file_info["palette_padding"] palette = read(padding * file_info["colors"]) grayscale = True @@ -269,10 +286,11 @@ def _bitmap(self, header=0, offset=0): # ---------------------------- Finally set the tile data for the plugin self.info["compression"] = file_info["compression"] - args = [raw_mode] + args: list[Any] = [raw_mode] if decoder_name == "bmp_rle": - args.append(file_info["compression"] == self.RLE4) + args.append(file_info["compression"] == self.COMPRESSIONS["RLE4"]) else: + assert isinstance(file_info["width"], int) args.append(((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3)) args.append(file_info["direction"]) self.tile = [ diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 69e7ee54811..99d7e73f111 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -485,7 +485,7 @@ def feed(self, data): self.image = im - def __enter__(self): + def __enter__(self) -> Parser: return self def __exit__(self, *args: object) -> None: @@ -580,7 +580,7 @@ def _encode_tile(im, fp, tile: list[_Tile], bufsize, fh, exc=None): encoder.cleanup() -def _safe_read(fp, size): +def _safe_read(fp: IO[bytes], size: int) -> bytes: """ Reads large blocks in a safe way. Unlike fp.read(n), this function doesn't trust the user. If the requested size is larger than @@ -601,18 +601,18 @@ def _safe_read(fp, size): msg = "Truncated File Read" raise OSError(msg) return data - data = [] + blocks: list[bytes] = [] remaining_size = size while remaining_size > 0: block = fp.read(min(remaining_size, SAFEBLOCK)) if not block: break - data.append(block) + blocks.append(block) remaining_size -= len(block) - if sum(len(d) for d in data) < size: + if sum(len(block) for block in blocks) < size: msg = "Truncated File Read" raise OSError(msg) - return b"".join(data) + return b"".join(blocks) class PyCodecState: @@ -636,7 +636,7 @@ def __init__(self, mode, *args): self.mode = mode self.init(args) - def init(self, args): + def init(self, args) -> None: """ Override to perform codec specific initialization @@ -653,7 +653,7 @@ def cleanup(self) -> None: """ pass - def setfd(self, fd): + def setfd(self, fd) -> None: """ Called from ImageFile to set the Python file-like object @@ -793,7 +793,7 @@ def encode_to_pyfd(self) -> tuple[int, int]: self.fd.write(data) return bytes_consumed, errcode - def encode_to_file(self, fh, bufsize): + def encode_to_file(self, fh: IO[bytes], bufsize: int) -> int: """ :param fh: File handle. :param bufsize: Buffer size. diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index 6aa70ced3e6..6b13e57a076 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -28,7 +28,7 @@ import tkinter from io import BytesIO -from typing import Any +from typing import TYPE_CHECKING, Any, cast from . import Image, ImageFile @@ -61,7 +61,9 @@ def _get_image_from_kw(kw: dict[str, Any]) -> ImageFile.ImageFile | None: return Image.open(source) -def _pyimagingtkcall(command, photo, id): +def _pyimagingtkcall( + command: str, photo: PhotoImage | tkinter.PhotoImage, id: int +) -> None: tk = photo.tk try: tk.call(command, photo, id) @@ -215,11 +217,14 @@ class BitmapImage: :param image: A PIL image. """ - def __init__(self, image=None, **kw): + def __init__(self, image: Image.Image | None = None, **kw: Any) -> None: # Tk compatibility: file or data if image is None: image = _get_image_from_kw(kw) + if image is None: + msg = "Image is required" + raise ValueError(msg) self.__mode = image.mode self.__size = image.size @@ -278,18 +283,23 @@ def getimage(photo: PhotoImage) -> Image.Image: return im -def _show(image, title): +def _show(image: Image.Image, title: str | None) -> None: """Helper for the Image.show method.""" class UI(tkinter.Label): - def __init__(self, master, im): + def __init__(self, master: tkinter.Toplevel, im: Image.Image) -> None: + self.image: BitmapImage | PhotoImage if im.mode == "1": self.image = BitmapImage(im, foreground="white", master=master) else: self.image = PhotoImage(im, master=master) - super().__init__(master, image=self.image, bg="black", bd=0) + if TYPE_CHECKING: + image = cast(tkinter._Image, self.image) + else: + image = self.image + super().__init__(master, image=image, bg="black", bd=0) - if not tkinter._default_root: + if not getattr(tkinter, "_default_root"): msg = "tkinter not initialized" raise OSError(msg) top = tkinter.Toplevel() diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index 992b9ccaf31..eeec41686ca 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -29,7 +29,7 @@ class BoxReader: and to easily step into and read sub-boxes. """ - def __init__(self, fp, length=-1): + def __init__(self, fp: IO[bytes], length: int = -1) -> None: self.fp = fp self.has_length = length >= 0 self.length = length @@ -97,7 +97,7 @@ def next_box_type(self) -> bytes: return tbox -def _parse_codestream(fp) -> tuple[tuple[int, int], str]: +def _parse_codestream(fp: IO[bytes]) -> tuple[tuple[int, int], str]: """Parse the JPEG 2000 codestream to extract the size and component count from the SIZ marker segment, returning a PIL (size, mode) tuple.""" @@ -137,7 +137,15 @@ def _res_to_dpi(num: int, denom: int, exp: int) -> float | None: return (254 * num * (10**exp)) / (10000 * denom) -def _parse_jp2_header(fp): +def _parse_jp2_header( + fp: IO[bytes], +) -> tuple[ + tuple[int, int], + str, + str | None, + tuple[float, float] | None, + ImagePalette.ImagePalette | None, +]: """Parse the JP2 header box to extract size, component count, color space information, and optionally DPI information, returning a (size, mode, mimetype, dpi) tuple.""" @@ -155,6 +163,7 @@ def _parse_jp2_header(fp): elif tbox == b"ftyp": if reader.read_fields(">4s")[0] == b"jpx ": mimetype = "image/jpx" + assert header is not None size = None mode = None @@ -168,6 +177,9 @@ def _parse_jp2_header(fp): if tbox == b"ihdr": height, width, nc, bpc = header.read_fields(">IIHB") + assert isinstance(height, int) + assert isinstance(width, int) + assert isinstance(bpc, int) size = (width, height) if nc == 1 and (bpc & 0x7F) > 8: mode = "I;16" @@ -185,11 +197,21 @@ def _parse_jp2_header(fp): mode = "CMYK" elif tbox == b"pclr" and mode in ("L", "LA"): ne, npc = header.read_fields(">HB") - bitdepths = header.read_fields(">" + ("B" * npc)) - if max(bitdepths) <= 8: + assert isinstance(ne, int) + assert isinstance(npc, int) + max_bitdepth = 0 + for bitdepth in header.read_fields(">" + ("B" * npc)): + assert isinstance(bitdepth, int) + if bitdepth > max_bitdepth: + max_bitdepth = bitdepth + if max_bitdepth <= 8: palette = ImagePalette.ImagePalette() for i in range(ne): - palette.getcolor(header.read_fields(">" + ("B" * npc))) + color: list[int] = [] + for value in header.read_fields(">" + ("B" * npc)): + assert isinstance(value, int) + color.append(value) + palette.getcolor(tuple(color)) mode = "P" if mode == "L" else "PA" elif tbox == b"res ": res = header.read_boxes() @@ -197,6 +219,12 @@ def _parse_jp2_header(fp): tres = res.next_box_type() if tres == b"resc": vrcn, vrcd, hrcn, hrcd, vrce, hrce = res.read_fields(">HHHHBB") + assert isinstance(vrcn, int) + assert isinstance(vrcd, int) + assert isinstance(hrcn, int) + assert isinstance(hrcd, int) + assert isinstance(vrce, int) + assert isinstance(hrce, int) hres = _res_to_dpi(hrcn, hrcd, hrce) vres = _res_to_dpi(vrcn, vrcd, vrce) if hres is not None and vres is not None: diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index b15bf06d2b1..4916727beae 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -60,7 +60,7 @@ def Skip(self: JpegImageFile, marker: int) -> None: ImageFile._safe_read(self.fp, n) -def APP(self, marker): +def APP(self: JpegImageFile, marker: int) -> None: # # Application marker. Store these in the APP dictionary. # Also look for well-known application markers. @@ -133,13 +133,14 @@ def APP(self, marker): offset += 4 data = s[offset : offset + size] if code == 0x03ED: # ResolutionInfo - data = { + photoshop[code] = { "XResolution": i32(data, 0) / 65536, "DisplayedUnitsX": i16(data, 4), "YResolution": i32(data, 8) / 65536, "DisplayedUnitsY": i16(data, 12), } - photoshop[code] = data + else: + photoshop[code] = data offset += size offset += offset & 1 # align except struct.error: @@ -338,6 +339,7 @@ def _open(self): # Create attributes self.bits = self.layers = 0 + self._exif_offset = 0 # JPEG specifics (internal) self.layer = [] @@ -498,17 +500,17 @@ def _read_dpi_from_exif(self) -> None: ): self.info["dpi"] = 72, 72 - def _getmp(self): + def _getmp(self) -> dict[int, Any] | None: return _getmp(self) -def _getexif(self) -> dict[str, Any] | None: +def _getexif(self: JpegImageFile) -> dict[str, Any] | None: if "exif" not in self.info: return None return self.getexif()._get_merged_dict() -def _getmp(self): +def _getmp(self: JpegImageFile) -> dict[int, Any] | None: # Extract MP information. This method was inspired by the "highly # experimental" _getexif version that's been in use for years now, # itself based on the ImageFileDirectory class in the TIFF plugin. @@ -616,7 +618,7 @@ def _getmp(self): # fmt: on -def get_sampling(im): +def get_sampling(im: Image.Image) -> int: # There's no subsampling when images have only 1 layer # (grayscale images) or when they are CMYK (4 layers), # so set subsampling to the default value. @@ -624,7 +626,7 @@ def get_sampling(im): # NOTE: currently Pillow can't encode JPEG to YCCK format. # If YCCK support is added in the future, subsampling code will have # to be updated (here and in JpegEncode.c) to deal with 4 layers. - if not hasattr(im, "layers") or im.layers in (1, 4): + if not isinstance(im, JpegImageFile) or im.layers in (1, 4): return -1 sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3] return samplings.get(sampling, -1) diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index f21570661f8..5ed9f56a164 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -22,7 +22,7 @@ import itertools import os import struct -from typing import IO +from typing import IO, Any, cast from . import ( Image, @@ -101,8 +101,11 @@ def _open(self) -> None: JpegImagePlugin.JpegImageFile._open(self) self._after_jpeg_open() - def _after_jpeg_open(self, mpheader=None): + def _after_jpeg_open(self, mpheader: dict[int, Any] | None = None) -> None: self.mpinfo = mpheader if mpheader is not None else self._getmp() + if self.mpinfo is None: + msg = "Image appears to be a malformed MPO file" + raise ValueError(msg) self.n_frames = self.mpinfo[0xB001] self.__mpoffsets = [ mpent["DataOffset"] + self.info["mpoffset"] for mpent in self.mpinfo[0xB002] @@ -149,7 +152,10 @@ def tell(self) -> int: return self.__frame @staticmethod - def adopt(jpeg_instance, mpheader=None): + def adopt( + jpeg_instance: JpegImagePlugin.JpegImageFile, + mpheader: dict[int, Any] | None = None, + ) -> MpoImageFile: """ Transform the instance of JpegImageFile into an instance of MpoImageFile. @@ -161,8 +167,9 @@ def adopt(jpeg_instance, mpheader=None): double call to _open. """ jpeg_instance.__class__ = MpoImageFile - jpeg_instance._after_jpeg_open(mpheader) - return jpeg_instance + mpo_instance = cast(MpoImageFile, jpeg_instance) + mpo_instance._after_jpeg_open(mpheader) + return mpo_instance # --------------------------------------------------------------------- diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 34ea77c5e01..fa117d19ae1 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -230,6 +230,7 @@ def verify(self, endchunk: bytes = b"IEND") -> list[bytes]: cids = [] + assert self.fp is not None while True: try: cid, pos, length = self.read() @@ -407,6 +408,7 @@ def rewind(self) -> None: def chunk_iCCP(self, pos: int, length: int) -> bytes: # ICC profile + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) # according to PNG spec, the iCCP chunk contains: # Profile name 1-79 bytes (character string) @@ -434,6 +436,7 @@ def chunk_iCCP(self, pos: int, length: int) -> bytes: def chunk_IHDR(self, pos: int, length: int) -> bytes: # image header + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) if length < 13: if ImageFile.LOAD_TRUNCATED_IMAGES: @@ -471,6 +474,7 @@ def chunk_IEND(self, pos: int, length: int) -> NoReturn: def chunk_PLTE(self, pos: int, length: int) -> bytes: # palette + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) if self.im_mode == "P": self.im_palette = "RGB", s @@ -478,6 +482,7 @@ def chunk_PLTE(self, pos: int, length: int) -> bytes: def chunk_tRNS(self, pos: int, length: int) -> bytes: # transparency + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) if self.im_mode == "P": if _simple_palette.match(s): @@ -498,6 +503,7 @@ def chunk_tRNS(self, pos: int, length: int) -> bytes: def chunk_gAMA(self, pos: int, length: int) -> bytes: # gamma setting + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) self.im_info["gamma"] = i32(s) / 100000.0 return s @@ -506,6 +512,7 @@ def chunk_cHRM(self, pos: int, length: int) -> bytes: # chromaticity, 8 unsigned ints, actual value is scaled by 100,000 # WP x,y, Red x,y, Green x,y Blue x,y + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) raw_vals = struct.unpack(">%dI" % (len(s) // 4), s) self.im_info["chromaticity"] = tuple(elt / 100000.0 for elt in raw_vals) @@ -518,6 +525,7 @@ def chunk_sRGB(self, pos: int, length: int) -> bytes: # 2 saturation # 3 absolute colorimetric + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) if length < 1: if ImageFile.LOAD_TRUNCATED_IMAGES: @@ -529,6 +537,7 @@ def chunk_sRGB(self, pos: int, length: int) -> bytes: def chunk_pHYs(self, pos: int, length: int) -> bytes: # pixels per unit + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) if length < 9: if ImageFile.LOAD_TRUNCATED_IMAGES: @@ -546,6 +555,7 @@ def chunk_pHYs(self, pos: int, length: int) -> bytes: def chunk_tEXt(self, pos: int, length: int) -> bytes: # text + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) try: k, v = s.split(b"\0", 1) @@ -554,17 +564,18 @@ def chunk_tEXt(self, pos: int, length: int) -> bytes: k = s v = b"" if k: - k = k.decode("latin-1", "strict") + k_str = k.decode("latin-1", "strict") v_str = v.decode("latin-1", "replace") - self.im_info[k] = v if k == "exif" else v_str - self.im_text[k] = v_str + self.im_info[k_str] = v if k == b"exif" else v_str + self.im_text[k_str] = v_str self.check_text_memory(len(v_str)) return s def chunk_zTXt(self, pos: int, length: int) -> bytes: # compressed text + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) try: k, v = s.split(b"\0", 1) @@ -589,16 +600,17 @@ def chunk_zTXt(self, pos: int, length: int) -> bytes: v = b"" if k: - k = k.decode("latin-1", "strict") - v = v.decode("latin-1", "replace") + k_str = k.decode("latin-1", "strict") + v_str = v.decode("latin-1", "replace") - self.im_info[k] = self.im_text[k] = v - self.check_text_memory(len(v)) + self.im_info[k_str] = self.im_text[k_str] = v_str + self.check_text_memory(len(v_str)) return s def chunk_iTXt(self, pos: int, length: int) -> bytes: # international text + assert self.fp is not None r = s = ImageFile._safe_read(self.fp, length) try: k, r = r.split(b"\0", 1) @@ -627,25 +639,27 @@ def chunk_iTXt(self, pos: int, length: int) -> bytes: if k == b"XML:com.adobe.xmp": self.im_info["xmp"] = v try: - k = k.decode("latin-1", "strict") - lang = lang.decode("utf-8", "strict") - tk = tk.decode("utf-8", "strict") - v = v.decode("utf-8", "strict") + k_str = k.decode("latin-1", "strict") + lang_str = lang.decode("utf-8", "strict") + tk_str = tk.decode("utf-8", "strict") + v_str = v.decode("utf-8", "strict") except UnicodeError: return s - self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk) - self.check_text_memory(len(v)) + self.im_info[k_str] = self.im_text[k_str] = iTXt(v_str, lang_str, tk_str) + self.check_text_memory(len(v_str)) return s def chunk_eXIf(self, pos: int, length: int) -> bytes: + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) self.im_info["exif"] = b"Exif\x00\x00" + s return s # APNG chunks def chunk_acTL(self, pos: int, length: int) -> bytes: + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) if length < 8: if ImageFile.LOAD_TRUNCATED_IMAGES: @@ -666,6 +680,7 @@ def chunk_acTL(self, pos: int, length: int) -> bytes: return s def chunk_fcTL(self, pos: int, length: int) -> bytes: + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) if length < 26: if ImageFile.LOAD_TRUNCATED_IMAGES: @@ -695,6 +710,7 @@ def chunk_fcTL(self, pos: int, length: int) -> bytes: return s def chunk_fdAT(self, pos: int, length: int) -> bytes: + assert self.fp is not None if length < 4: if ImageFile.LOAD_TRUNCATED_IMAGES: s = ImageFile._safe_read(self.fp, length) diff --git a/src/PIL/PsdImagePlugin.py b/src/PIL/PsdImagePlugin.py index edf698bf02d..31dfd4d1234 100644 --- a/src/PIL/PsdImagePlugin.py +++ b/src/PIL/PsdImagePlugin.py @@ -185,7 +185,7 @@ def _layerinfo(fp, ct_bytes): # read layerinfo block layers = [] - def read(size): + def read(size: int) -> bytes: return ImageFile._safe_read(fp, size) ct = si16(read(2)) diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 530b88c8bd6..011de9c6ac0 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -115,7 +115,7 @@ def _reset(self, reset: bool = True) -> None: self.__loaded = -1 self.__timestamp = 0 - def _get_next(self): + def _get_next(self) -> tuple[bytes, int, int]: # Get next frame ret = self._decoder.get_next() self.__physical_frame += 1 diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py index 3d5cddcc8f5..68f8a74f599 100644 --- a/src/PIL/WmfImagePlugin.py +++ b/src/PIL/WmfImagePlugin.py @@ -152,7 +152,7 @@ def _open(self) -> None: def _load(self) -> ImageFile.StubHandler | None: return _handler - def load(self, dpi=None): + def load(self, dpi: int | None = None) -> Image.core.PixelAccess | None: if dpi is not None and self._inch is not None: self.info["dpi"] = dpi x0, y0, x1, y1 = self.info["wmf_bbox"] diff --git a/src/PIL/_imagingtk.pyi b/src/PIL/_imagingtk.pyi new file mode 100644 index 00000000000..e27843e5338 --- /dev/null +++ b/src/PIL/_imagingtk.pyi @@ -0,0 +1,3 @@ +from typing import Any + +def __getattr__(name: str) -> Any: ...