From 56fa3c658a9c60facce7acbb3909855f7aea2340 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 9 Jun 2024 07:12:05 +1000 Subject: [PATCH 01/16] Added type hints --- src/PIL/GifImagePlugin.py | 15 +++++++++---- src/PIL/GimpGradientFile.py | 43 +++++++++++++++++++++++++++--------- src/PIL/Image.py | 2 +- src/PIL/ImageDraw2.py | 6 ++--- src/PIL/ImageTk.py | 6 ++--- src/PIL/Jpeg2KImagePlugin.py | 3 ++- src/PIL/PaletteFile.py | 10 +++++---- src/PIL/TiffImagePlugin.py | 4 ++-- 8 files changed, 61 insertions(+), 28 deletions(-) diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index e62852db31d..f41bc2b321d 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -558,7 +558,11 @@ def _normalize_palette(im, palette, info): return im -def _write_single_frame(im, fp, palette): +def _write_single_frame( + im: Image.Image, + fp: IO[bytes], + palette: bytes | bytearray | list[int] | ImagePalette.ImagePalette, +) -> None: im_out = _normalize_mode(im) for k, v in im_out.info.items(): im.encoderinfo.setdefault(k, v) @@ -579,7 +583,9 @@ def _write_single_frame(im, fp, palette): fp.write(b"\0") # end of image data -def _getbbox(base_im, im_frame): +def _getbbox( + base_im: Image.Image, im_frame: Image.Image +) -> tuple[Image.Image, tuple[int, int, int, int]]: if _get_palette_bytes(im_frame) != _get_palette_bytes(base_im): im_frame = im_frame.convert("RGBA") base_im = base_im.convert("RGBA") @@ -790,7 +796,7 @@ def _write_local_header(fp, im, offset, flags): fp.write(o8(8)) # bits -def _save_netpbm(im, fp, filename): +def _save_netpbm(im: Image.Image, fp: IO[bytes], filename: str) -> None: # Unused by default. # To use, uncomment the register_save call at the end of the file. # @@ -821,6 +827,7 @@ def _save_netpbm(im, fp, filename): ) # Allow ppmquant to receive SIGPIPE if ppmtogif exits + assert quant_proc.stdout is not None quant_proc.stdout.close() retcode = quant_proc.wait() @@ -1080,7 +1087,7 @@ def getdata(im, offset=(0, 0), **params): class Collector: data = [] - def write(self, data): + def write(self, data: bytes) -> None: self.data.append(data) im.load() # make sure raster data is available diff --git a/src/PIL/GimpGradientFile.py b/src/PIL/GimpGradientFile.py index 2d8c78ea91a..92068b904e7 100644 --- a/src/PIL/GimpGradientFile.py +++ b/src/PIL/GimpGradientFile.py @@ -21,6 +21,7 @@ from __future__ import annotations from math import log, pi, sin, sqrt +from typing import IO, Callable from ._binary import o8 @@ -28,7 +29,7 @@ """""" # Enable auto-doc for data member -def linear(middle, pos): +def linear(middle: float, pos: float) -> float: if pos <= middle: if middle < EPSILON: return 0.0 @@ -43,19 +44,19 @@ def linear(middle, pos): return 0.5 + 0.5 * pos / middle -def curved(middle, pos): +def curved(middle: float, pos: float) -> float: return pos ** (log(0.5) / log(max(middle, EPSILON))) -def sine(middle, pos): +def sine(middle: float, pos: float) -> float: return (sin((-pi / 2.0) + pi * linear(middle, pos)) + 1.0) / 2.0 -def sphere_increasing(middle, pos): +def sphere_increasing(middle: float, pos: float) -> float: return sqrt(1.0 - (linear(middle, pos) - 1.0) ** 2) -def sphere_decreasing(middle, pos): +def sphere_decreasing(middle: float, pos: float) -> float: return 1.0 - sqrt(1.0 - linear(middle, pos) ** 2) @@ -64,9 +65,22 @@ def sphere_decreasing(middle, pos): class GradientFile: - gradient = None - - def getpalette(self, entries=256): + gradient: ( + list[ + tuple[ + float, + float, + float, + list[float], + list[float], + Callable[[float, float], float], + ] + ] + | None + ) = None + + def getpalette(self, entries: int = 256) -> tuple[bytes, str]: + assert self.gradient is not None palette = [] ix = 0 @@ -101,7 +115,7 @@ def getpalette(self, entries=256): class GimpGradientFile(GradientFile): """File handler for GIMP's gradient format.""" - def __init__(self, fp): + def __init__(self, fp: IO[bytes]) -> None: if fp.readline()[:13] != b"GIMP Gradient": msg = "not a GIMP gradient file" raise SyntaxError(msg) @@ -114,7 +128,16 @@ def __init__(self, fp): count = int(line) - gradient = [] + gradient: list[ + tuple[ + float, + float, + float, + list[float], + list[float], + Callable[[float, float], float], + ] + ] = [] for i in range(count): s = fp.readline().split() diff --git a/src/PIL/Image.py b/src/PIL/Image.py index f61acc1d317..f6ffac8a77f 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2470,7 +2470,7 @@ def save( save_all = params.pop("save_all", False) self.encoderinfo = params - self.encoderconfig = () + self.encoderconfig: tuple[Any, ...] = () preinit() diff --git a/src/PIL/ImageDraw2.py b/src/PIL/ImageDraw2.py index 35ee5834e34..b42f5d9eac1 100644 --- a/src/PIL/ImageDraw2.py +++ b/src/PIL/ImageDraw2.py @@ -30,7 +30,7 @@ class Pen: """Stores an outline color and width.""" - def __init__(self, color, width=1, opacity=255): + def __init__(self, color: str, width: int = 1, opacity: int = 255) -> None: self.color = ImageColor.getrgb(color) self.width = width @@ -38,7 +38,7 @@ def __init__(self, color, width=1, opacity=255): class Brush: """Stores a fill color""" - def __init__(self, color, opacity=255): + def __init__(self, color: str, opacity: int = 255) -> None: self.color = ImageColor.getrgb(color) @@ -63,7 +63,7 @@ def __init__(self, image, size=None, color=None): self.image = image self.transform = None - def flush(self): + def flush(self) -> Image.Image: return self.image def render(self, op, xy, pen, brush=None): diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index 6e2e7db1e19..90defdbbc23 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -37,7 +37,7 @@ _pilbitmap_ok = None -def _pilbitmap_check(): +def _pilbitmap_check() -> int: global _pilbitmap_ok if _pilbitmap_ok is None: try: @@ -162,7 +162,7 @@ def height(self) -> int: """ return self.__size[1] - def paste(self, im): + def paste(self, im: Image.Image) -> None: """ Paste a PIL image into the photo image. Note that this can be very slow if the photo image is displayed. @@ -254,7 +254,7 @@ def __str__(self) -> str: return str(self.__photo) -def getimage(photo): +def getimage(photo: PhotoImage) -> Image.Image: """Copies the contents of a PhotoImage to a PIL image memory.""" im = Image.new("RGBA", (photo.width(), photo.height())) block = im.im diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index e6395b1cb96..5a0ef0d01a3 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -18,6 +18,7 @@ import io import os import struct +from typing import IO from . import Image, ImageFile, ImagePalette, _binary @@ -328,7 +329,7 @@ def _accept(prefix: bytes) -> bool: # Save support -def _save(im, fp, filename): +def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: # Get the keyword arguments info = im.encoderinfo diff --git a/src/PIL/PaletteFile.py b/src/PIL/PaletteFile.py index eaed5367c99..81652e5eec2 100644 --- a/src/PIL/PaletteFile.py +++ b/src/PIL/PaletteFile.py @@ -14,6 +14,8 @@ # from __future__ import annotations +from typing import IO + from ._binary import o8 @@ -22,8 +24,8 @@ class PaletteFile: rawmode = "RGB" - def __init__(self, fp): - self.palette = [(i, i, i) for i in range(256)] + def __init__(self, fp: IO[bytes]) -> None: + palette = [o8(i) * 3 for i in range(256)] while True: s = fp.readline() @@ -44,9 +46,9 @@ def __init__(self, fp): g = b = r if 0 <= i <= 255: - self.palette[i] = o8(r) + o8(g) + o8(b) + palette[i] = o8(r) + o8(g) + o8(b) - self.palette = b"".join(self.palette) + self.palette = b"".join(palette) def getpalette(self) -> tuple[bytes, str]: return self.palette, self.rawmode diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 04f36744bed..0b96017552a 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -50,7 +50,7 @@ from collections.abc import MutableMapping from fractions import Fraction from numbers import Number, Rational -from typing import TYPE_CHECKING, Any, Callable +from typing import IO, TYPE_CHECKING, Any, Callable from . import ExifTags, Image, ImageFile, ImageOps, ImagePalette, TiffTags from ._binary import i16be as i16 @@ -2149,7 +2149,7 @@ def fixOffsets(self, count, isShort=False, isLong=False): self.rewriteLastLong(offset) -def _save_all(im, fp, filename): +def _save_all(im: Image.Image, fp: IO[bytes], filename: str) -> None: encoderinfo = im.encoderinfo.copy() encoderconfig = im.encoderconfig append_images = list(encoderinfo.get("append_images", [])) From 56c79b6f523d2bb7733b9193e47fab2f63f5b546 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 9 Jun 2024 22:13:01 +1000 Subject: [PATCH 02/16] Simplified code --- src/PIL/GimpGradientFile.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/PIL/GimpGradientFile.py b/src/PIL/GimpGradientFile.py index 92068b904e7..220eac57e38 100644 --- a/src/PIL/GimpGradientFile.py +++ b/src/PIL/GimpGradientFile.py @@ -128,16 +128,7 @@ def __init__(self, fp: IO[bytes]) -> None: count = int(line) - gradient: list[ - tuple[ - float, - float, - float, - list[float], - list[float], - Callable[[float, float], float], - ] - ] = [] + self.gradient = [] for i in range(count): s = fp.readline().split() @@ -155,6 +146,4 @@ def __init__(self, fp: IO[bytes]) -> None: msg = "cannot handle HSV colour space" raise OSError(msg) - gradient.append((x0, x1, xm, rgb0, rgb1, segment)) - - self.gradient = gradient + self.gradient.append((x0, x1, xm, rgb0, rgb1, segment)) From e225f9f589e07d46ad5d1f79e7c233addf9c3836 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 10 Jun 2024 11:50:13 +1000 Subject: [PATCH 03/16] Deprecate ImageDraw.getdraw hints argument --- Tests/test_imagedraw.py | 5 +++++ docs/deprecations.rst | 7 +++++++ docs/releasenotes/10.4.0.rst | 5 +++++ src/PIL/ImageDraw.py | 24 ++++++++---------------- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index c221fe00829..61d7b5c6a18 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1624,3 +1624,8 @@ def test_incorrectly_ordered_coordinates(xy: tuple[int, int, int, int]) -> None: draw.rectangle(xy) with pytest.raises(ValueError): draw.rounded_rectangle(xy) + + +def test_getdraw(): + with pytest.warns(DeprecationWarning): + ImageDraw.getdraw(None, []) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index b2cd968fee3..8a03d858c71 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -115,6 +115,13 @@ Support for LibTIFF earlier than 4 Support for LibTIFF earlier than version 4 has been deprecated. Upgrade to a newer version of LibTIFF instead. +ImageDraw.getdraw hints argument +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 10.4.0 + +The ``hints`` argument in :py:meth:`~PIL.ImageDraw.getdraw()` has been deprecated. + Removed features ---------------- diff --git a/docs/releasenotes/10.4.0.rst b/docs/releasenotes/10.4.0.rst index e0d695a8bba..8c49e0842c5 100644 --- a/docs/releasenotes/10.4.0.rst +++ b/docs/releasenotes/10.4.0.rst @@ -34,6 +34,11 @@ Support for LibTIFF earlier than 4 Support for LibTIFF earlier than version 4 has been deprecated. Upgrade to a newer version of LibTIFF instead. +ImageDraw.getdraw hints argument +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``hints`` argument in :py:meth:`~PIL.ImageDraw.getdraw()` has been deprecated. + API Changes =========== diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 73ed3d4a9a6..ec15b535fbe 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -37,6 +37,7 @@ from typing import TYPE_CHECKING, AnyStr, Sequence, cast from . import Image, ImageColor +from ._deprecate import deprecate from ._typing import Coords """ @@ -902,26 +903,17 @@ def Draw(im, mode: str | None = None) -> ImageDraw: def getdraw(im=None, hints=None): """ - (Experimental) A more advanced 2D drawing interface for PIL images, - based on the WCK interface. - :param im: The image to draw in. - :param hints: An optional list of hints. + :param hints: An optional list of hints. Deprecated. :returns: A (drawing context, drawing resource factory) tuple. """ - # FIXME: this needs more work! - # FIXME: come up with a better 'hints' scheme. - handler = None - if not hints or "nicest" in hints: - try: - from . import _imagingagg as handler - except ImportError: - pass - if handler is None: - from . import ImageDraw2 as handler + if hints is not None: + deprecate("'hints' argument", 12) + from . import ImageDraw2 + if im: - im = handler.Draw(im) - return im, handler + im = ImageDraw2.Draw(im) + return im, ImageDraw2 def floodfill( From 2d1fe7572f461e13ed70f4f6162d5266d8440df0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 10 Jun 2024 14:15:28 +1000 Subject: [PATCH 04/16] Added type hints --- src/PIL/BlpImagePlugin.py | 15 ++++++++++----- src/PIL/BmpImagePlugin.py | 4 ++-- src/PIL/BufrStubImagePlugin.py | 2 +- src/PIL/DdsImagePlugin.py | 2 +- src/PIL/GifImagePlugin.py | 6 +++--- src/PIL/GribStubImagePlugin.py | 2 +- src/PIL/Hdf5StubImagePlugin.py | 2 +- src/PIL/IcnsImagePlugin.py | 19 +++++++++---------- src/PIL/IcoImagePlugin.py | 2 +- src/PIL/ImImagePlugin.py | 4 +++- src/PIL/Image.py | 26 +++++++++++++++++--------- src/PIL/ImageDraw.py | 16 ++++++++++------ src/PIL/ImageFile.py | 2 +- src/PIL/Jpeg2KImagePlugin.py | 6 ++++-- src/PIL/JpegImagePlugin.py | 6 +++--- src/PIL/MpoImagePlugin.py | 2 +- src/PIL/MspImagePlugin.py | 2 +- src/PIL/PcxImagePlugin.py | 2 +- src/PIL/PdfImagePlugin.py | 2 +- src/PIL/PdfParser.py | 13 ++++++------- src/PIL/PngImagePlugin.py | 2 +- src/PIL/PpmImagePlugin.py | 2 +- src/PIL/QoiImagePlugin.py | 16 ++++++++++------ src/PIL/SgiImagePlugin.py | 7 ++++--- src/PIL/SpiderImagePlugin.py | 7 ++++--- src/PIL/TgaImagePlugin.py | 2 +- src/PIL/TiffImagePlugin.py | 4 ++-- src/PIL/WebPImagePlugin.py | 8 ++++---- src/PIL/WmfImagePlugin.py | 2 +- src/PIL/XbmImagePlugin.py | 2 +- 30 files changed, 106 insertions(+), 81 deletions(-) diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index 2db115ccc4f..003fa9b2479 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -31,6 +31,7 @@ from __future__ import annotations +import abc import os import struct from enum import IntEnum @@ -276,7 +277,7 @@ def _open(self) -> None: class _BLPBaseDecoder(ImageFile.PyDecoder): _pulls_fd = True - def decode(self, buffer): + def decode(self, buffer: bytes) -> tuple[int, int]: try: self._read_blp_header() self._load() @@ -285,6 +286,10 @@ def decode(self, buffer): raise OSError(msg) from e return -1, 0 + @abc.abstractmethod + def _load(self) -> None: + pass + def _read_blp_header(self) -> None: assert self.fd is not None self.fd.seek(4) @@ -318,7 +323,7 @@ def _read_palette(self) -> list[tuple[int, int, int, int]]: ret.append((b, g, r, a)) return ret - def _read_bgra(self, palette): + def _read_bgra(self, palette: list[tuple[int, int, int, int]]) -> bytearray: data = bytearray() _data = BytesIO(self._safe_read(self._blp_lengths[0])) while True: @@ -327,7 +332,7 @@ def _read_bgra(self, palette): except struct.error: break b, g, r, a = palette[offset] - d = (r, g, b) + d: tuple[int, ...] = (r, g, b) if self._blp_alpha_depth: d += (a,) data.extend(d) @@ -431,7 +436,7 @@ def _write_palette(self) -> bytes: data += b"\x00" * 4 return data - def encode(self, bufsize): + def encode(self, bufsize: int) -> tuple[int, int, bytes]: palette_data = self._write_palette() offset = 20 + 16 * 4 * 2 + len(palette_data) @@ -449,7 +454,7 @@ def encode(self, bufsize): return len(data), 0, data -def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: if im.mode != "P": msg = "Unsupported BLP image mode" raise ValueError(msg) diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index 2df1d8d33bd..45c1ea941e8 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -395,12 +395,12 @@ def _open(self) -> None: } -def _dib_save(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _dib_save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: _save(im, fp, filename, False) def _save( - im: Image.Image, fp: IO[bytes], filename: str, bitmap_header: bool = True + im: Image.Image, fp: IO[bytes], filename: str | bytes, bitmap_header: bool = True ) -> None: try: rawmode, bits, colors = SAVE[im.mode] diff --git a/src/PIL/BufrStubImagePlugin.py b/src/PIL/BufrStubImagePlugin.py index 7388a2b8a3a..0ee2f653b2c 100644 --- a/src/PIL/BufrStubImagePlugin.py +++ b/src/PIL/BufrStubImagePlugin.py @@ -60,7 +60,7 @@ def _load(self) -> ImageFile.StubHandler | None: return _handler -def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: if _handler is None or not hasattr(_handler, "save"): msg = "BUFR save handler not installed" raise OSError(msg) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index a3efadb030d..861a1eca0cc 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -511,7 +511,7 @@ def decode(self, buffer): return -1, 0 -def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: if im.mode not in ("RGB", "RGBA", "L", "LA"): msg = f"cannot write mode {im.mode} as DDS" raise OSError(msg) diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index f41bc2b321d..a540595b85b 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -715,12 +715,12 @@ def _write_multiple_frames(im, fp, palette): return True -def _save_all(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: _save(im, fp, filename, save_all=True) def _save( - im: Image.Image, fp: IO[bytes], filename: str, save_all: bool = False + im: Image.Image, fp: IO[bytes], filename: str | bytes, save_all: bool = False ) -> None: # header if "palette" in im.encoderinfo or "palette" in im.info: @@ -796,7 +796,7 @@ def _write_local_header(fp, im, offset, flags): fp.write(o8(8)) # bits -def _save_netpbm(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _save_netpbm(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: # Unused by default. # To use, uncomment the register_save call at the end of the file. # diff --git a/src/PIL/GribStubImagePlugin.py b/src/PIL/GribStubImagePlugin.py index d3655f4ddc0..e9aa084b281 100644 --- a/src/PIL/GribStubImagePlugin.py +++ b/src/PIL/GribStubImagePlugin.py @@ -60,7 +60,7 @@ def _load(self) -> ImageFile.StubHandler | None: return _handler -def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: if _handler is None or not hasattr(_handler, "save"): msg = "GRIB save handler not installed" raise OSError(msg) diff --git a/src/PIL/Hdf5StubImagePlugin.py b/src/PIL/Hdf5StubImagePlugin.py index b789c215fee..cc9e73deb80 100644 --- a/src/PIL/Hdf5StubImagePlugin.py +++ b/src/PIL/Hdf5StubImagePlugin.py @@ -60,7 +60,7 @@ def _load(self) -> ImageFile.StubHandler | None: return _handler -def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: if _handler is None or not hasattr(_handler, "save"): msg = "HDF5 save handler not installed" raise OSError(msg) diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index 0a86ba883f1..2a89d498cbf 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -22,6 +22,7 @@ import os import struct import sys +from typing import IO from . import Image, ImageFile, PngImagePlugin, features @@ -312,7 +313,7 @@ def load(self): return px -def _save(im, fp, filename): +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: """ Saves the image as a series of PNG files, that are then combined into a .icns file. @@ -346,29 +347,27 @@ def _save(im, fp, filename): entries = [] for type, size in sizes.items(): stream = size_streams[size] - entries.append( - {"type": type, "size": HEADERSIZE + len(stream), "stream": stream} - ) + entries.append((type, HEADERSIZE + len(stream), stream)) # Header fp.write(MAGIC) file_length = HEADERSIZE # Header file_length += HEADERSIZE + 8 * len(entries) # TOC - file_length += sum(entry["size"] for entry in entries) + file_length += sum(entry[1] for entry in entries) fp.write(struct.pack(">i", file_length)) # TOC fp.write(b"TOC ") fp.write(struct.pack(">i", HEADERSIZE + len(entries) * HEADERSIZE)) for entry in entries: - fp.write(entry["type"]) - fp.write(struct.pack(">i", entry["size"])) + fp.write(entry[0]) + fp.write(struct.pack(">i", entry[1])) # Data for entry in entries: - fp.write(entry["type"]) - fp.write(struct.pack(">i", entry["size"])) - fp.write(entry["stream"]) + fp.write(entry[0]) + fp.write(struct.pack(">i", entry[1])) + fp.write(entry[2]) if hasattr(fp, "flush"): fp.flush() diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index af94e5a2e7f..227fcf35cbb 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -40,7 +40,7 @@ _MAGIC = b"\0\0\1\0" -def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: fp.write(_MAGIC) # (2+2) bmp = im.encoderinfo.get("bitmap_format") == "bmp" sizes = im.encoderinfo.get( diff --git a/src/PIL/ImImagePlugin.py b/src/PIL/ImImagePlugin.py index c98cfb0984b..015c2febea8 100644 --- a/src/PIL/ImImagePlugin.py +++ b/src/PIL/ImImagePlugin.py @@ -326,7 +326,7 @@ def tell(self) -> int: } -def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: try: image_type, rawmode = SAVE[im.mode] except KeyError as e: @@ -341,6 +341,8 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: # or: SyntaxError("not an IM file") # 8 characters are used for "Name: " and "\r\n" # Keep just the filename, ditch the potentially overlong path + if isinstance(filename, bytes): + filename = filename.decode("ascii") name, ext = os.path.splitext(os.path.basename(filename)) name = "".join([name[: 92 - len(ext)], ext]) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index f6ffac8a77f..af174861019 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -626,7 +626,7 @@ def _ensure_mutable(self) -> None: self.load() def _dump( - self, file: str | None = None, format: str | None = None, **options + self, file: str | None = None, format: str | None = None, **options: Any ) -> str: suffix = "" if format: @@ -649,10 +649,12 @@ def _dump( return filename - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if self.__class__ is not other.__class__: + return False + assert isinstance(other, Image) return ( - self.__class__ is other.__class__ - and self.mode == other.mode + self.mode == other.mode and self.size == other.size and self.info == other.info and self.getpalette() == other.getpalette() @@ -2965,7 +2967,7 @@ def transform( # Debugging -def _wedge(): +def _wedge() -> Image: """Create grayscale wedge (for debugging only)""" return Image()._new(core.wedge("L")) @@ -3566,7 +3568,9 @@ def register_mime(id: str, mimetype: str) -> None: MIME[id.upper()] = mimetype -def register_save(id: str, driver) -> None: +def register_save( + id: str, driver: Callable[[Image, IO[bytes], str | bytes], None] +) -> None: """ Registers an image save function. This function should not be used in application code. @@ -3577,7 +3581,9 @@ def register_save(id: str, driver) -> None: SAVE[id.upper()] = driver -def register_save_all(id: str, driver) -> None: +def register_save_all( + id: str, driver: Callable[[Image, IO[bytes], str | bytes], None] +) -> None: """ Registers an image function to save all the frames of a multiframe format. This function should not be @@ -3651,7 +3657,7 @@ def register_encoder(name: str, encoder: type[ImageFile.PyEncoder]) -> None: # Simple display support. -def _show(image, **options) -> None: +def _show(image: Image, **options: Any) -> None: from . import ImageShow ImageShow.show(image, **options) @@ -3661,7 +3667,9 @@ def _show(image, **options) -> None: # Effects -def effect_mandelbrot(size, extent, quality): +def effect_mandelbrot( + size: tuple[int, int], extent: tuple[int, int, int, int], quality: int +) -> Image: """ Generate a Mandelbrot set covering the given extent. diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 73ed3d4a9a6..01f99c11984 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -219,7 +219,9 @@ def line(self, xy: Coords, fill=None, width=0, joint=None) -> None: # This is a straight line, so no joint is required continue - def coord_at_angle(coord, angle): + def coord_at_angle( + coord: Sequence[float], angle: float + ) -> tuple[float, float]: x, y = coord angle -= 90 distance = width / 2 - 1 @@ -1109,11 +1111,13 @@ def _get_angles(n_sides: int, rotation: float) -> list[float]: return [_compute_polygon_vertex(angle) for angle in angles] -def _color_diff(color1, color2: float | tuple[int, ...]) -> float: +def _color_diff( + color1: float | tuple[int, ...], color2: float | tuple[int, ...] +) -> float: """ Uses 1-norm distance to calculate difference between two values. """ - if isinstance(color2, tuple): - return sum(abs(color1[i] - color2[i]) for i in range(0, len(color2))) - else: - return abs(color1 - color2) + first = color1 if isinstance(color1, tuple) else (color1,) + second = color2 if isinstance(color2, tuple) else (color2,) + + return sum(abs(first[i] - second[i]) for i in range(0, len(second))) diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index f0e49238760..6bef681e979 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -763,7 +763,7 @@ class PyEncoder(PyCodec): def pushes_fd(self): return self._pushes_fd - def encode(self, bufsize): + def encode(self, bufsize: int) -> tuple[int, int, bytes]: """ Override to perform the encoding process. diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index 5a0ef0d01a3..72c2cb85e3c 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -329,11 +329,13 @@ def _accept(prefix: bytes) -> bool: # Save support -def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: # Get the keyword arguments info = im.encoderinfo - if filename.endswith(".j2k") or info.get("no_jp2", False): + if isinstance(filename, str): + filename = filename.encode() + if filename.endswith(b".j2k") or info.get("no_jp2", False): kind = "j2k" else: kind = "jp2" diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 4d0b75e77ed..0c8a678887e 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -42,7 +42,7 @@ import sys import tempfile import warnings -from typing import Any +from typing import IO, Any from . import Image, ImageFile from ._binary import i16be as i16 @@ -644,7 +644,7 @@ def get_sampling(im): return samplings.get(sampling, -1) -def _save(im, fp, filename): +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: if im.width == 0 or im.height == 0: msg = "cannot write empty image as JPEG" raise ValueError(msg) @@ -827,7 +827,7 @@ def validate_qtables(qtables): ImageFile._save(im, fp, [("jpeg", (0, 0) + im.size, 0, rawmode)], bufsize) -def _save_cjpeg(im, fp, filename): +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]) diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index 6716722f204..152e19e2365 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -33,7 +33,7 @@ from ._binary import o32le -def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: JpegImagePlugin._save(im, fp, filename) diff --git a/src/PIL/MspImagePlugin.py b/src/PIL/MspImagePlugin.py index 65cc70624b7..0a75c868b97 100644 --- a/src/PIL/MspImagePlugin.py +++ b/src/PIL/MspImagePlugin.py @@ -164,7 +164,7 @@ def decode(self, buffer: bytes) -> tuple[int, int]: # write MSP files (uncompressed only) -def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: if im.mode != "1": msg = f"cannot write mode {im.mode} as MSP" raise OSError(msg) diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py index 026bfd9a01b..dd42003b5a3 100644 --- a/src/PIL/PcxImagePlugin.py +++ b/src/PIL/PcxImagePlugin.py @@ -144,7 +144,7 @@ def _open(self) -> None: } -def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: try: version, bits, planes, rawmode = SAVE[im.mode] except KeyError as e: diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py index ccd28f3434b..f0da1e04797 100644 --- a/src/PIL/PdfImagePlugin.py +++ b/src/PIL/PdfImagePlugin.py @@ -40,7 +40,7 @@ # 5. page contents -def _save_all(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: _save(im, fp, filename, save_all=True) diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index a6c24e67179..52e8358017a 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -76,7 +76,7 @@ class PdfFormatError(RuntimeError): pass -def check_format_condition(condition, error_message): +def check_format_condition(condition: bool, error_message: str) -> None: if not condition: raise PdfFormatError(error_message) @@ -93,12 +93,11 @@ def __str__(self) -> str: def __bytes__(self) -> bytes: return self.__str__().encode("us-ascii") - def __eq__(self, other): - return ( - other.__class__ is self.__class__ - and other.object_id == self.object_id - and other.generation == self.generation - ) + def __eq__(self, other: object) -> bool: + if self.__class__ is not other.__class__: + return False + assert isinstance(other, IndirectReference) + return other.object_id == self.object_id and other.generation == self.generation def __ne__(self, other): return not (self == other) diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 76ffdef3f55..9aaadb47d5e 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1234,7 +1234,7 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) seq_num = fdat_chunks.seq_num -def _save_all(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: _save(im, fp, filename, save_all=True) diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index 94bf430b825..16c9ccbba72 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -328,7 +328,7 @@ def decode(self, buffer: bytes) -> tuple[int, int]: # -------------------------------------------------------------------- -def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: if im.mode == "1": rawmode, head = "1;I", b"P4" elif im.mode == "L": diff --git a/src/PIL/QoiImagePlugin.py b/src/PIL/QoiImagePlugin.py index f2cf06d0dd7..202ef52d09b 100644 --- a/src/PIL/QoiImagePlugin.py +++ b/src/PIL/QoiImagePlugin.py @@ -37,6 +37,8 @@ def _open(self) -> None: class QoiDecoder(ImageFile.PyDecoder): _pulls_fd = True + _previous_pixel: bytes | bytearray | None = None + _previously_seen_pixels: dict[int, bytes | bytearray] = {} def _add_to_previous_pixels(self, value: bytes | bytearray) -> None: self._previous_pixel = value @@ -45,9 +47,10 @@ def _add_to_previous_pixels(self, value: bytes | bytearray) -> None: hash_value = (r * 3 + g * 5 + b * 7 + a * 11) % 64 self._previously_seen_pixels[hash_value] = value - def decode(self, buffer): + def decode(self, buffer: bytes) -> tuple[int, int]: + assert self.fd is not None + self._previously_seen_pixels = {} - self._previous_pixel = None self._add_to_previous_pixels(bytearray((0, 0, 0, 255))) data = bytearray() @@ -55,7 +58,8 @@ def decode(self, buffer): dest_length = self.state.xsize * self.state.ysize * bands while len(data) < dest_length: byte = self.fd.read(1)[0] - if byte == 0b11111110: # QOI_OP_RGB + value: bytes | bytearray + if byte == 0b11111110 and self._previous_pixel: # QOI_OP_RGB value = bytearray(self.fd.read(3)) + self._previous_pixel[3:] elif byte == 0b11111111: # QOI_OP_RGBA value = self.fd.read(4) @@ -66,7 +70,7 @@ def decode(self, buffer): value = self._previously_seen_pixels.get( op_index, bytearray((0, 0, 0, 0)) ) - elif op == 1: # QOI_OP_DIFF + elif op == 1 and self._previous_pixel: # QOI_OP_DIFF value = bytearray( ( (self._previous_pixel[0] + ((byte & 0b00110000) >> 4) - 2) @@ -77,7 +81,7 @@ def decode(self, buffer): self._previous_pixel[3], ) ) - elif op == 2: # QOI_OP_LUMA + elif op == 2 and self._previous_pixel: # QOI_OP_LUMA second_byte = self.fd.read(1)[0] diff_green = (byte & 0b00111111) - 32 diff_red = ((second_byte & 0b11110000) >> 4) - 8 @@ -90,7 +94,7 @@ def decode(self, buffer): ) ) value += self._previous_pixel[3:] - elif op == 3: # QOI_OP_RUN + elif op == 3 and self._previous_pixel: # QOI_OP_RUN run_length = (byte & 0b00111111) + 1 value = self._previous_pixel if bands == 3: diff --git a/src/PIL/SgiImagePlugin.py b/src/PIL/SgiImagePlugin.py index 7bd84ebd491..50d97910932 100644 --- a/src/PIL/SgiImagePlugin.py +++ b/src/PIL/SgiImagePlugin.py @@ -125,7 +125,7 @@ def _open(self) -> None: ] -def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: if im.mode not in {"RGB", "RGBA", "L"}: msg = "Unsupported SGI image mode" raise ValueError(msg) @@ -171,8 +171,9 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: # Maximum Byte value (255 = 8bits per pixel) pinmax = 255 # Image name (79 characters max, truncated below in write) - filename = os.path.basename(filename) - img_name = os.path.splitext(filename)[0].encode("ascii", "ignore") + img_name = os.path.splitext(os.path.basename(filename))[0] + if isinstance(img_name, str): + img_name = img_name.encode("ascii", "ignore") # Standard representation of pixel in the file colormap = 0 fp.write(struct.pack(">h", magic_number)) diff --git a/src/PIL/SpiderImagePlugin.py b/src/PIL/SpiderImagePlugin.py index 98dd91c0e24..a6cc00019da 100644 --- a/src/PIL/SpiderImagePlugin.py +++ b/src/PIL/SpiderImagePlugin.py @@ -263,7 +263,7 @@ def makeSpiderHeader(im: Image.Image) -> list[bytes]: return [struct.pack("f", v) for v in hdr] -def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: if im.mode[0] != "F": im = im.convert("F") @@ -279,9 +279,10 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]) -def _save_spider(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _save_spider(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: # get the filename extension and register it with Image - ext = os.path.splitext(filename)[1] + filename_ext = os.path.splitext(filename)[1] + ext = filename_ext.decode() if isinstance(filename_ext, bytes) else filename_ext Image.register_extension(SpiderImageFile.format, ext) _save(im, fp, filename) diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index 401a83f9fba..f16f075df05 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -178,7 +178,7 @@ def load_end(self) -> None: } -def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: try: rawmode, bits, colormaptype, imagetype = SAVE[im.mode] except KeyError as e: diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 0b96017552a..08ee506b162 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -387,7 +387,7 @@ def __repr__(self) -> str: def __hash__(self): return self._val.__hash__() - def __eq__(self, other): + def __eq__(self, other: object) -> bool: val = self._val if isinstance(other, IFDRational): other = other._val @@ -2149,7 +2149,7 @@ def fixOffsets(self, count, isShort=False, isLong=False): self.rewriteLastLong(offset) -def _save_all(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: encoderinfo = im.encoderinfo.copy() encoderconfig = im.encoderconfig append_images = list(encoderinfo.get("append_images", [])) diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 463d6a62398..97debc2edc9 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -1,7 +1,7 @@ from __future__ import annotations from io import BytesIO -from typing import Any +from typing import IO, Any from . import Image, ImageFile @@ -182,7 +182,7 @@ def tell(self) -> int: return self.__logical_frame -def _save_all(im, fp, filename): +def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: encoderinfo = im.encoderinfo.copy() append_images = list(encoderinfo.get("append_images", [])) @@ -195,7 +195,7 @@ def _save_all(im, fp, filename): _save(im, fp, filename) return - background = (0, 0, 0, 0) + background: int | tuple[int, ...] = (0, 0, 0, 0) if "background" in encoderinfo: background = encoderinfo["background"] elif "background" in im.info: @@ -325,7 +325,7 @@ def _save_all(im, fp, filename): fp.write(data) -def _save(im, fp, filename): +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: lossless = im.encoderinfo.get("lossless", False) quality = im.encoderinfo.get("quality", 80) alpha_quality = im.encoderinfo.get("alpha_quality", 100) diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py index a68f705a03c..3d5cddcc8f5 100644 --- a/src/PIL/WmfImagePlugin.py +++ b/src/PIL/WmfImagePlugin.py @@ -163,7 +163,7 @@ def load(self, dpi=None): return super().load() -def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: if _handler is None or not hasattr(_handler, "save"): msg = "WMF save handler not installed" raise OSError(msg) diff --git a/src/PIL/XbmImagePlugin.py b/src/PIL/XbmImagePlugin.py index eee7274361c..6d11bbfcf6b 100644 --- a/src/PIL/XbmImagePlugin.py +++ b/src/PIL/XbmImagePlugin.py @@ -70,7 +70,7 @@ def _open(self) -> None: self.tile = [("xbm", (0, 0) + self.size, m.end(), None)] -def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: if im.mode != "1": msg = f"cannot write mode {im.mode} as XBM" raise OSError(msg) From 9f831317fe98633214ad0266417d349b44e5d6bf Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Mon, 10 Jun 2024 16:47:18 +1000 Subject: [PATCH 05/16] Updated text Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- docs/deprecations.rst | 6 +++--- docs/releasenotes/10.4.0.rst | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 8a03d858c71..627672e1f3b 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -115,12 +115,12 @@ Support for LibTIFF earlier than 4 Support for LibTIFF earlier than version 4 has been deprecated. Upgrade to a newer version of LibTIFF instead. -ImageDraw.getdraw hints argument -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +ImageDraw.getdraw hints parameter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. deprecated:: 10.4.0 -The ``hints`` argument in :py:meth:`~PIL.ImageDraw.getdraw()` has been deprecated. +The ``hints`` parameter in :py:meth:`~PIL.ImageDraw.getdraw()` has been deprecated. Removed features ---------------- diff --git a/docs/releasenotes/10.4.0.rst b/docs/releasenotes/10.4.0.rst index 8c49e0842c5..44727efd41f 100644 --- a/docs/releasenotes/10.4.0.rst +++ b/docs/releasenotes/10.4.0.rst @@ -34,10 +34,10 @@ Support for LibTIFF earlier than 4 Support for LibTIFF earlier than version 4 has been deprecated. Upgrade to a newer version of LibTIFF instead. -ImageDraw.getdraw hints argument -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +ImageDraw.getdraw hints parameter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The ``hints`` argument in :py:meth:`~PIL.ImageDraw.getdraw()` has been deprecated. +The ``hints`` parameter in :py:meth:`~PIL.ImageDraw.getdraw()` has been deprecated. API Changes =========== From 4679e4bf9e542ffae8c81e68603d5c944108389f Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Mon, 10 Jun 2024 16:47:52 +1000 Subject: [PATCH 06/16] Updated deprecation warning --- src/PIL/ImageDraw.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index ec15b535fbe..23d7e6973b4 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -908,7 +908,7 @@ def getdraw(im=None, hints=None): :returns: A (drawing context, drawing resource factory) tuple. """ if hints is not None: - deprecate("'hints' argument", 12) + deprecate("'hints' parameter", 12) from . import ImageDraw2 if im: From 8e8ee1e4c4b8037c9b755e2ba26a7297dfa5d6ac Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 10 Jun 2024 17:38:17 +1000 Subject: [PATCH 07/16] Accept 't' suffix for libtiff version --- Tests/test_features.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Tests/test_features.py b/Tests/test_features.py index de418115ee0..b7eefa09ae1 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -38,7 +38,9 @@ def test(name: str, function: Callable[[str], str | None]) -> None: assert function(name) == version if name != "PIL": if name == "zlib" and version is not None: - version = version.replace(".zlib-ng", "") + version = re.sub(".zlib-ng$", "", version) + elif name == "libtiff" and version is not None: + version = re.sub("t$", "", version) assert version is None or re.search(r"\d+(\.\d+)*$", version) for module in features.modules: From 9afe9d2769d9241385a4fd8f8e2376fbdca74981 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 10 Jun 2024 16:08:06 +1000 Subject: [PATCH 08/16] Added type hints --- Tests/test_file_gif.py | 9 +- src/PIL/GifImagePlugin.py | 206 +++++++++++++++++++++++--------------- src/PIL/Image.py | 20 ++-- src/PIL/ImageOps.py | 2 +- src/PIL/ImagePalette.py | 13 ++- 5 files changed, 154 insertions(+), 96 deletions(-) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 4e790926bfc..e19c88a47c4 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -53,6 +53,7 @@ def test_closed_file() -> None: def test_seek_after_close() -> None: im = Image.open("Tests/images/iss634.gif") + assert isinstance(im, GifImagePlugin.GifImageFile) im.load() im.close() @@ -377,7 +378,8 @@ def test_save_netpbm_bmp_mode(tmp_path: Path) -> None: img = img.convert("RGB") tempfile = str(tmp_path / "temp.gif") - GifImagePlugin._save_netpbm(img, 0, tempfile) + b = BytesIO() + GifImagePlugin._save_netpbm(img, b, tempfile) with Image.open(tempfile) as reloaded: assert_image_similar(img, reloaded.convert("RGB"), 0) @@ -388,7 +390,8 @@ def test_save_netpbm_l_mode(tmp_path: Path) -> None: img = img.convert("L") tempfile = str(tmp_path / "temp.gif") - GifImagePlugin._save_netpbm(img, 0, tempfile) + b = BytesIO() + GifImagePlugin._save_netpbm(img, b, tempfile) with Image.open(tempfile) as reloaded: assert_image_similar(img, reloaded.convert("L"), 0) @@ -648,7 +651,7 @@ def test_dispose2_palette(tmp_path: Path) -> None: assert rgb_img.getpixel((50, 50)) == circle # Check that frame transparency wasn't added unnecessarily - assert img._frame_transparency is None + assert getattr(img, "_frame_transparency") is None def test_dispose2_diff(tmp_path: Path) -> None: diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index a540595b85b..a305e8de668 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -29,9 +29,10 @@ import math import os import subprocess +import sys from enum import IntEnum from functools import cached_property -from typing import IO +from typing import IO, TYPE_CHECKING, Any, List, Literal, NamedTuple, Union from . import ( Image, @@ -46,6 +47,9 @@ from ._binary import o8 from ._binary import o16le as o16 +if TYPE_CHECKING: + from . import _imaging + class LoadingStrategy(IntEnum): """.. versionadded:: 9.1.0""" @@ -118,7 +122,7 @@ def _open(self) -> None: self._seek(0) # get ready to read first frame @property - def n_frames(self): + def n_frames(self) -> int: if self._n_frames is None: current = self.tell() try: @@ -163,11 +167,11 @@ def seek(self, frame: int) -> None: msg = "no more images in GIF file" raise EOFError(msg) from e - def _seek(self, frame, update_image=True): + def _seek(self, frame: int, update_image: bool = True) -> None: if frame == 0: # rewind self.__offset = 0 - self.dispose = None + self.dispose: _imaging.ImagingCore | None = None self.__frame = -1 self._fp.seek(self.__rewind) self.disposal_method = 0 @@ -195,9 +199,9 @@ def _seek(self, frame, update_image=True): msg = "no more images in GIF file" raise EOFError(msg) - palette = None + palette: ImagePalette.ImagePalette | Literal[False] | None = None - info = {} + info: dict[str, Any] = {} frame_transparency = None interlace = None frame_dispose_extent = None @@ -213,7 +217,7 @@ def _seek(self, frame, update_image=True): # s = self.fp.read(1) block = self.data() - if s[0] == 249: + if s[0] == 249 and block is not None: # # graphic control extension # @@ -249,14 +253,14 @@ def _seek(self, frame, update_image=True): info["comment"] = comment s = None continue - elif s[0] == 255 and frame == 0: + elif s[0] == 255 and frame == 0 and block is not None: # # application extension # info["extension"] = block, self.fp.tell() if block[:11] == b"NETSCAPE2.0": block = self.data() - if len(block) >= 3 and block[0] == 1: + if block and len(block) >= 3 and block[0] == 1: self.info["loop"] = i16(block, 1) while self.data(): pass @@ -345,51 +349,52 @@ def _rgb(color: int) -> tuple[int, int, int]: else: return (color, color, color) + self.dispose = None self.dispose_extent = frame_dispose_extent - try: - if self.disposal_method < 2: - # do not dispose or none specified - self.dispose = None - elif self.disposal_method == 2: - # replace with background colour - - # only dispose the extent in this frame - x0, y0, x1, y1 = self.dispose_extent - dispose_size = (x1 - x0, y1 - y0) - - Image._decompression_bomb_check(dispose_size) - - # by convention, attempt to use transparency first - dispose_mode = "P" - color = self.info.get("transparency", frame_transparency) - if color is not None: - if self.mode in ("RGB", "RGBA"): - dispose_mode = "RGBA" - color = _rgb(color) + (0,) - else: - color = self.info.get("background", 0) - if self.mode in ("RGB", "RGBA"): - dispose_mode = "RGB" - color = _rgb(color) - self.dispose = Image.core.fill(dispose_mode, dispose_size, color) - else: - # replace with previous contents - if self.im is not None: + if self.dispose_extent and self.disposal_method >= 2: + try: + if self.disposal_method == 2: + # replace with background colour + # only dispose the extent in this frame - self.dispose = self._crop(self.im, self.dispose_extent) - elif frame_transparency is not None: x0, y0, x1, y1 = self.dispose_extent dispose_size = (x1 - x0, y1 - y0) Image._decompression_bomb_check(dispose_size) + + # by convention, attempt to use transparency first dispose_mode = "P" - color = frame_transparency - if self.mode in ("RGB", "RGBA"): - dispose_mode = "RGBA" - color = _rgb(frame_transparency) + (0,) + color = self.info.get("transparency", frame_transparency) + if color is not None: + if self.mode in ("RGB", "RGBA"): + dispose_mode = "RGBA" + color = _rgb(color) + (0,) + else: + color = self.info.get("background", 0) + if self.mode in ("RGB", "RGBA"): + dispose_mode = "RGB" + color = _rgb(color) self.dispose = Image.core.fill(dispose_mode, dispose_size, color) - except AttributeError: - pass + else: + # replace with previous contents + if self.im is not None: + # only dispose the extent in this frame + self.dispose = self._crop(self.im, self.dispose_extent) + elif frame_transparency is not None: + x0, y0, x1, y1 = self.dispose_extent + dispose_size = (x1 - x0, y1 - y0) + + Image._decompression_bomb_check(dispose_size) + dispose_mode = "P" + color = frame_transparency + if self.mode in ("RGB", "RGBA"): + dispose_mode = "RGBA" + color = _rgb(frame_transparency) + (0,) + self.dispose = Image.core.fill( + dispose_mode, dispose_size, color + ) + except AttributeError: + pass if interlace is not None: transparency = -1 @@ -498,7 +503,12 @@ def _normalize_mode(im: Image.Image) -> Image.Image: return im.convert("L") -def _normalize_palette(im, palette, info): +_Palette = Union[bytes, bytearray, List[int], ImagePalette.ImagePalette] + + +def _normalize_palette( + im: Image.Image, palette: _Palette | None, info: dict[str, Any] +) -> Image.Image: """ Normalizes the palette for image. - Sets the palette to the incoming palette, if provided. @@ -526,8 +536,10 @@ def _normalize_palette(im, palette, info): source_palette = bytearray(i // 3 for i in range(768)) im.palette = ImagePalette.ImagePalette("RGB", palette=source_palette) + used_palette_colors: list[int] | None if palette: used_palette_colors = [] + assert source_palette is not None for i in range(0, len(source_palette), 3): source_color = tuple(source_palette[i : i + 3]) index = im.palette.colors.get(source_color) @@ -561,7 +573,7 @@ def _normalize_palette(im, palette, info): def _write_single_frame( im: Image.Image, fp: IO[bytes], - palette: bytes | bytearray | list[int] | ImagePalette.ImagePalette, + palette: _Palette | None, ) -> None: im_out = _normalize_mode(im) for k, v in im_out.info.items(): @@ -585,7 +597,7 @@ def _write_single_frame( def _getbbox( base_im: Image.Image, im_frame: Image.Image -) -> tuple[Image.Image, tuple[int, int, int, int]]: +) -> tuple[Image.Image, tuple[int, int, int, int] | None]: if _get_palette_bytes(im_frame) != _get_palette_bytes(base_im): im_frame = im_frame.convert("RGBA") base_im = base_im.convert("RGBA") @@ -593,12 +605,20 @@ def _getbbox( return delta, delta.getbbox(alpha_only=False) -def _write_multiple_frames(im, fp, palette): +class _Frame(NamedTuple): + im: Image.Image + bbox: tuple[int, int, int, int] | None + encoderinfo: dict[str, Any] + + +def _write_multiple_frames( + im: Image.Image, fp: IO[bytes], palette: _Palette | None +) -> bool: duration = im.encoderinfo.get("duration") disposal = im.encoderinfo.get("disposal", im.info.get("disposal")) - im_frames = [] - previous_im = None + im_frames: list[_Frame] = [] + previous_im: Image.Image | None = None frame_count = 0 background_im = None for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])): @@ -624,24 +644,22 @@ def _write_multiple_frames(im, fp, palette): frame_count += 1 diff_frame = None - if im_frames: + if im_frames and previous_im: # delta frame delta, bbox = _getbbox(previous_im, im_frame) if not bbox: # This frame is identical to the previous frame if encoderinfo.get("duration"): - im_frames[-1]["encoderinfo"]["duration"] += encoderinfo[ - "duration" - ] + im_frames[-1].encoderinfo["duration"] += encoderinfo["duration"] continue - if im_frames[-1]["encoderinfo"].get("disposal") == 2: + if im_frames[-1].encoderinfo.get("disposal") == 2: if background_im is None: color = im.encoderinfo.get( "transparency", im.info.get("transparency", (0, 0, 0)) ) background = _get_background(im_frame, color) background_im = Image.new("P", im_frame.size, background) - background_im.putpalette(im_frames[0]["im"].palette) + background_im.putpalette(im_frames[0].im.palette) bbox = _getbbox(background_im, im_frame)[1] elif encoderinfo.get("optimize") and im_frame.mode != "1": if "transparency" not in encoderinfo: @@ -687,31 +705,29 @@ def _write_multiple_frames(im, fp, palette): else: bbox = None previous_im = im_frame - im_frames.append( - {"im": diff_frame or im_frame, "bbox": bbox, "encoderinfo": encoderinfo} - ) + im_frames.append(_Frame(diff_frame or im_frame, bbox, encoderinfo)) if len(im_frames) == 1: if "duration" in im.encoderinfo: # Since multiple frames will not be written, use the combined duration - im.encoderinfo["duration"] = im_frames[0]["encoderinfo"]["duration"] - return + im.encoderinfo["duration"] = im_frames[0].encoderinfo["duration"] + return False for frame_data in im_frames: - im_frame = frame_data["im"] - if not frame_data["bbox"]: + im_frame = frame_data.im + if not frame_data.bbox: # global header - for s in _get_global_header(im_frame, frame_data["encoderinfo"]): + for s in _get_global_header(im_frame, frame_data.encoderinfo): fp.write(s) offset = (0, 0) else: # compress difference if not palette: - frame_data["encoderinfo"]["include_color_table"] = True + frame_data.encoderinfo["include_color_table"] = True - im_frame = im_frame.crop(frame_data["bbox"]) - offset = frame_data["bbox"][:2] - _write_frame_data(fp, im_frame, offset, frame_data["encoderinfo"]) + im_frame = im_frame.crop(frame_data.bbox) + offset = frame_data.bbox[:2] + _write_frame_data(fp, im_frame, offset, frame_data.encoderinfo) return True @@ -748,7 +764,9 @@ def get_interlace(im: Image.Image) -> int: return interlace -def _write_local_header(fp, im, offset, flags): +def _write_local_header( + fp: IO[bytes], im: Image.Image, offset: tuple[int, int], flags: int +) -> None: try: transparency = im.encoderinfo["transparency"] except KeyError: @@ -849,7 +867,7 @@ def _save_netpbm(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: _FORCE_OPTIMIZE = False -def _get_optimize(im, info): +def _get_optimize(im: Image.Image, info: dict[str, Any]) -> list[int] | None: """ Palette optimization is a potentially expensive operation. @@ -893,6 +911,7 @@ def _get_optimize(im, info): and current_palette_size > 2 ): return used_palette_colors + return None def _get_color_table_size(palette_bytes: bytes) -> int: @@ -933,7 +952,10 @@ def _get_palette_bytes(im: Image.Image) -> bytes: return im.palette.palette if im.palette else b"" -def _get_background(im, info_background): +def _get_background( + im: Image.Image, + info_background: int | tuple[int, int, int] | tuple[int, int, int, int] | None, +) -> int: background = 0 if info_background: if isinstance(info_background, tuple): @@ -956,7 +978,7 @@ def _get_background(im, info_background): return background -def _get_global_header(im, info): +def _get_global_header(im: Image.Image, info: dict[str, Any]) -> list[bytes]: """Return a list of strings representing a GIF header""" # Header Block @@ -1018,7 +1040,12 @@ def _get_global_header(im, info): return header -def _write_frame_data(fp, im_frame, offset, params): +def _write_frame_data( + fp: IO[bytes], + im_frame: Image.Image, + offset: tuple[int, int], + params: dict[str, Any], +) -> None: try: im_frame.encoderinfo = params @@ -1038,7 +1065,9 @@ def _write_frame_data(fp, im_frame, offset, params): # Legacy GIF utilities -def getheader(im, palette=None, info=None): +def getheader( + im: Image.Image, palette: _Palette | None = None, info: dict[str, Any] | None = None +) -> tuple[list[bytes], list[int] | None]: """ Legacy Method to get Gif data from image. @@ -1050,11 +1079,11 @@ def getheader(im, palette=None, info=None): :returns: tuple of(list of header items, optimized palette) """ - used_palette_colors = _get_optimize(im, info) - if info is None: info = {} + used_palette_colors = _get_optimize(im, info) + if "background" not in info and "background" in im.info: info["background"] = im.info["background"] @@ -1066,7 +1095,9 @@ def getheader(im, palette=None, info=None): return header, used_palette_colors -def getdata(im, offset=(0, 0), **params): +def getdata( + im: Image.Image, offset: tuple[int, int] = (0, 0), **params: Any +) -> list[bytes]: """ Legacy Method @@ -1083,12 +1114,23 @@ def getdata(im, offset=(0, 0), **params): :returns: List of bytes containing GIF encoded frame data """ + from io import BytesIO - class Collector: + class Collector(BytesIO): data = [] - def write(self, data: bytes) -> None: - self.data.append(data) + if sys.version_info >= (3, 12): + from collections.abc import Buffer + + def write(self, data: Buffer) -> int: + self.data.append(data) + return len(data) + + else: + + def write(self, data: Any) -> int: + self.data.append(data) + return len(data) im.load() # make sure raster data is available diff --git a/src/PIL/Image.py b/src/PIL/Image.py index af174861019..bdd869ccc15 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -41,7 +41,7 @@ from collections.abc import Callable, MutableMapping from enum import IntEnum from types import ModuleType -from typing import IO, TYPE_CHECKING, Any, Literal, Protocol, Sequence, cast +from typing import IO, TYPE_CHECKING, Any, Literal, Protocol, Sequence, Tuple, cast # VERSION was removed in Pillow 6.0.0. # PILLOW_VERSION was removed in Pillow 9.0.0. @@ -1367,7 +1367,7 @@ def getbands(self) -> tuple[str, ...]: """ return ImageMode.getmode(self.mode).bands - def getbbox(self, *, alpha_only: bool = True) -> tuple[int, int, int, int]: + def getbbox(self, *, alpha_only: bool = True) -> tuple[int, int, int, int] | None: """ Calculates the bounding box of the non-zero regions in the image. @@ -3029,12 +3029,18 @@ def new( color = ImageColor.getcolor(color, mode) im = Image() - if mode == "P" and isinstance(color, (list, tuple)) and len(color) in [3, 4]: - # RGB or RGBA value for a P image - from . import ImagePalette + if ( + mode == "P" + and isinstance(color, (list, tuple)) + and all(isinstance(i, int) for i in color) + ): + color_ints: tuple[int, ...] = cast(Tuple[int, ...], tuple(color)) + if len(color_ints) == 3 or len(color_ints) == 4: + # RGB or RGBA value for a P image + from . import ImagePalette - im.palette = ImagePalette.ImagePalette() - color = im.palette.getcolor(color) + im.palette = ImagePalette.ImagePalette() + color = im.palette.getcolor(color_ints) return im._new(core.fill(mode, size, color)) diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 33db8fa50c7..cbe189cc926 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -497,7 +497,7 @@ def expand( color = _color(fill, image.mode) if image.palette: palette = ImagePalette.ImagePalette(palette=image.getpalette()) - if isinstance(color, tuple): + if isinstance(color, tuple) and (len(color) == 3 or len(color) == 4): color = palette.getcolor(color) else: palette = None diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index 057ccd1d7c4..1ff05a3eff1 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -18,10 +18,13 @@ from __future__ import annotations import array -from typing import IO, Sequence +from typing import IO, TYPE_CHECKING, Sequence from . import GimpGradientFile, GimpPaletteFile, ImageColor, PaletteFile +if TYPE_CHECKING: + from . import Image + class ImagePalette: """ @@ -128,7 +131,11 @@ def _new_color_index(self, image=None, e=None): raise ValueError(msg) from e return index - def getcolor(self, color, image=None) -> int: + def getcolor( + self, + color: tuple[int, int, int] | tuple[int, int, int, int], + image: Image.Image | None = None, + ) -> int: """Given an rgb tuple, allocate palette entry. .. warning:: This method is experimental. @@ -163,7 +170,7 @@ def getcolor(self, color, image=None) -> int: self.dirty = 1 return index else: - msg = f"unknown color specifier: {repr(color)}" + msg = f"unknown color specifier: {repr(color)}" # type: ignore[unreachable] raise ValueError(msg) def save(self, fp: str | IO[str]) -> None: From 84b284723259f2a7d1e079daea0670e0138c4980 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 11 Jun 2024 07:15:47 +1000 Subject: [PATCH 09/16] Accept 't' suffix for libtiff version --- Tests/test_file_libtiff.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 22bcd285622..fe9d017c086 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -54,7 +54,7 @@ class TestFileLibTiff(LibTiffTestCase): def test_version(self) -> None: version = features.version_codec("libtiff") assert version is not None - assert re.search(r"\d+\.\d+\.\d+$", version) + assert re.search(r"\d+\.\d+\.\d+t?$", version) def test_g4_tiff(self, tmp_path: Path) -> None: """Test the ordinary file path load path""" From 474ef6ff8d152effba530f364d7c3c5a0653358f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 11 Jun 2024 00:01:02 +0000 Subject: [PATCH 10/16] Update dependency cibuildwheel to v2.19.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 7e257b75cf3..bf1d1315bfc 100644 --- a/.ci/requirements-cibw.txt +++ b/.ci/requirements-cibw.txt @@ -1 +1 @@ -cibuildwheel==2.18.1 +cibuildwheel==2.19.0 From 780d85b667f3201c02c1563ca7c09581f8871237 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 11 Jun 2024 23:18:11 +1000 Subject: [PATCH 11/16] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index dc4016d76f6..d7231ebeabf 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 10.4.0 (unreleased) ------------------- +- Accept 't' suffix for libtiff version #8126, #8129 + [radarhere] + +- Deprecate ImageDraw.getdraw hints parameter #8124 + [radarhere, hugovk] + - Added ImageDraw circle() #8085 [void4, hugovk, radarhere] From 1eb960f7e3e662b489c7cfc2b97b88e5317ffff5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 11 Jun 2024 23:26:00 +1000 Subject: [PATCH 12/16] Added type hints --- src/PIL/BlpImagePlugin.py | 20 ++++++++-------- src/PIL/BmpImagePlugin.py | 3 ++- src/PIL/DdsImagePlugin.py | 3 ++- src/PIL/EpsImagePlugin.py | 45 ++++++++++++++++++------------------ src/PIL/FitsImagePlugin.py | 2 +- src/PIL/FpxImagePlugin.py | 2 +- src/PIL/ImageFile.py | 2 +- src/PIL/Jpeg2KImagePlugin.py | 15 ++++++------ src/PIL/MicImagePlugin.py | 2 +- src/PIL/MpoImagePlugin.py | 15 ++++-------- src/PIL/PSDraw.py | 2 +- src/PIL/PalmImagePlugin.py | 10 ++++---- src/PIL/PdfParser.py | 3 +-- src/PIL/PngImagePlugin.py | 2 +- src/PIL/TarIO.py | 8 +------ src/PIL/TiffImagePlugin.py | 29 +++++++++++------------ 16 files changed, 78 insertions(+), 85 deletions(-) diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index 003fa9b2479..59246c6e2e1 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -61,7 +61,9 @@ def unpack_565(i: int) -> tuple[int, int, int]: return ((i >> 11) & 0x1F) << 3, ((i >> 5) & 0x3F) << 2, (i & 0x1F) << 3 -def decode_dxt1(data, alpha=False): +def decode_dxt1( + data: bytes, alpha: bool = False +) -> tuple[bytearray, bytearray, bytearray, bytearray]: """ input: one "row" of data (i.e. will produce 4*width pixels) """ @@ -69,9 +71,9 @@ def decode_dxt1(data, alpha=False): blocks = len(data) // 8 # number of blocks in row ret = (bytearray(), bytearray(), bytearray(), bytearray()) - for block in range(blocks): + for block_index in range(blocks): # Decode next 8-byte block. - idx = block * 8 + idx = block_index * 8 color0, color1, bits = struct.unpack_from(" tuple[bytearray, bytearray, bytearray, bytearray]: """ input: one "row" of data (i.e. will produce 4*width pixels) """ @@ -124,8 +126,8 @@ def decode_dxt3(data): blocks = len(data) // 16 # number of blocks in row ret = (bytearray(), bytearray(), bytearray(), bytearray()) - for block in range(blocks): - idx = block * 16 + for block_index in range(blocks): + idx = block_index * 16 block = data[idx : idx + 16] # Decode next 16-byte block. bits = struct.unpack_from("<8B", block) @@ -169,7 +171,7 @@ def decode_dxt3(data): return ret -def decode_dxt5(data): +def decode_dxt5(data: bytes) -> tuple[bytearray, bytearray, bytearray, bytearray]: """ input: one "row" of data (i.e. will produce 4 * width pixels) """ @@ -177,8 +179,8 @@ def decode_dxt5(data): blocks = len(data) // 16 # number of blocks in row ret = (bytearray(), bytearray(), bytearray(), bytearray()) - for block in range(blocks): - idx = block * 16 + for block_index in range(blocks): + idx = block_index * 16 block = data[idx : idx + 16] # Decode next 16-byte block. a0, a1 = struct.unpack_from(" None: class BmpRleDecoder(ImageFile.PyDecoder): _pulls_fd = True - def decode(self, buffer): + def decode(self, buffer: bytes) -> tuple[int, int]: + assert self.fd is not None rle4 = self.args[1] data = bytearray() x = 0 diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 861a1eca0cc..e7472700765 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -480,7 +480,8 @@ def load_seek(self, pos: int) -> None: class DdsRgbDecoder(ImageFile.PyDecoder): _pulls_fd = True - def decode(self, buffer): + def decode(self, buffer: bytes) -> tuple[int, int]: + assert self.fd is not None bitcount, masks = self.args # Some masks will be padded with zeros, e.g. R 0b11 G 0b1100 diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index d24a2ba80f0..380b1cf0ec4 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -27,6 +27,7 @@ import subprocess import sys import tempfile +from typing import IO from . import Image, ImageFile from ._binary import i32le as i32 @@ -236,7 +237,7 @@ def check_required_header_comments() -> None: msg = 'EPS header missing "%%BoundingBox" comment' raise SyntaxError(msg) - def _read_comment(s): + def _read_comment(s: str) -> bool: nonlocal reading_trailer_comments try: m = split.match(s) @@ -244,27 +245,25 @@ def _read_comment(s): msg = "not an EPS file" raise SyntaxError(msg) from e - if m: - k, v = m.group(1, 2) - self.info[k] = v - if k == "BoundingBox": - if v == "(atend)": - reading_trailer_comments = True - elif not self._size or ( - trailer_reached and reading_trailer_comments - ): - try: - # Note: The DSC spec says that BoundingBox - # fields should be integers, but some drivers - # put floating point values there anyway. - box = [int(float(i)) for i in v.split()] - self._size = box[2] - box[0], box[3] - box[1] - self.tile = [ - ("eps", (0, 0) + self.size, offset, (length, box)) - ] - except Exception: - pass - return True + if not m: + return False + + k, v = m.group(1, 2) + self.info[k] = v + if k == "BoundingBox": + if v == "(atend)": + reading_trailer_comments = True + elif not self._size or (trailer_reached and reading_trailer_comments): + try: + # Note: The DSC spec says that BoundingBox + # fields should be integers, but some drivers + # put floating point values there anyway. + box = [int(float(i)) for i in v.split()] + self._size = box[2] - box[0], box[3] - box[1] + self.tile = [("eps", (0, 0) + self.size, offset, (length, box))] + except Exception: + pass + return True while True: byte = self.fp.read(1) @@ -413,7 +412,7 @@ def load_seek(self, pos: int) -> None: # -------------------------------------------------------------------- -def _save(im, fp, filename, eps=1): +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes, eps: int = 1) -> None: """EPS Writer for the Python Imaging Library.""" # make sure image data is available diff --git a/src/PIL/FitsImagePlugin.py b/src/PIL/FitsImagePlugin.py index 07191892506..a169b6083e9 100644 --- a/src/PIL/FitsImagePlugin.py +++ b/src/PIL/FitsImagePlugin.py @@ -122,7 +122,7 @@ def _parse_headers( class FitsGzipDecoder(ImageFile.PyDecoder): _pulls_fd = True - def decode(self, buffer): + def decode(self, buffer: bytes) -> tuple[int, int]: assert self.fd is not None value = gzip.decompress(self.fd.read()) diff --git a/src/PIL/FpxImagePlugin.py b/src/PIL/FpxImagePlugin.py index b3e6c6e362b..c1927bd26aa 100644 --- a/src/PIL/FpxImagePlugin.py +++ b/src/PIL/FpxImagePlugin.py @@ -241,7 +241,7 @@ def close(self) -> None: self.ole.close() super().close() - def __exit__(self, *args): + def __exit__(self, *args: object) -> None: self.ole.close() super().__exit__() diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 6bef681e979..5d67409ea45 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -487,7 +487,7 @@ def feed(self, data): def __enter__(self): return self - def __exit__(self, *args): + def __exit__(self, *args: object) -> None: self.close() def close(self): diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index 72c2cb85e3c..60f3bff0acb 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -18,7 +18,7 @@ import io import os import struct -from typing import IO +from typing import IO, Tuple, cast from . import Image, ImageFile, ImagePalette, _binary @@ -59,7 +59,7 @@ def _read_bytes(self, num_bytes: int) -> bytes: self.remaining_in_box -= num_bytes return data - def read_fields(self, field_format): + def read_fields(self, field_format: str) -> tuple[int | bytes, ...]: size = struct.calcsize(field_format) data = self._read_bytes(size) return struct.unpack(field_format, data) @@ -82,9 +82,9 @@ def next_box_type(self) -> bytes: self.remaining_in_box = -1 # Read the length and type of the next box - lbox, tbox = self.read_fields(">I4s") + lbox, tbox = cast(Tuple[int, bytes], self.read_fields(">I4s")) if lbox == 1: - lbox = self.read_fields(">Q")[0] + lbox = cast(int, self.read_fields(">Q")[0]) hlen = 16 else: hlen = 8 @@ -127,12 +127,13 @@ def _parse_codestream(fp): return size, mode -def _res_to_dpi(num, denom, exp): +def _res_to_dpi(num: int, denom: int, exp: int) -> float | None: """Convert JPEG2000's (numerator, denominator, exponent-base-10) resolution, calculated as (num / denom) * 10^exp and stored in dots per meter, to floating-point dots per inch.""" - if denom != 0: - return (254 * num * (10**exp)) / (10000 * denom) + if denom == 0: + return None + return (254 * num * (10**exp)) / (10000 * denom) def _parse_jp2_header(fp): diff --git a/src/PIL/MicImagePlugin.py b/src/PIL/MicImagePlugin.py index 5aef94dfbff..ed2ea2849d0 100644 --- a/src/PIL/MicImagePlugin.py +++ b/src/PIL/MicImagePlugin.py @@ -93,7 +93,7 @@ def close(self) -> None: self.ole.close() super().close() - def __exit__(self, *args): + def __exit__(self, *args: object) -> None: self.__fp.close() self.ole.close() super().__exit__() diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index 152e19e2365..f21570661f8 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -37,19 +37,14 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: JpegImagePlugin._save(im, fp, filename) -def _save_all(im, fp, filename): +def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: append_images = im.encoderinfo.get("append_images", []) - if not append_images: - try: - animated = im.is_animated - except AttributeError: - animated = False - if not animated: - _save(im, fp, filename) - return + if not append_images and not getattr(im, "is_animated", False): + _save(im, fp, filename) + return mpf_offset = 28 - offsets = [] + offsets: list[int] = [] for imSequence in itertools.chain([im], append_images): for im_frame in ImageSequence.Iterator(imSequence): if not offsets: diff --git a/src/PIL/PSDraw.py b/src/PIL/PSDraw.py index 4e2b9788ef3..673eae1d12c 100644 --- a/src/PIL/PSDraw.py +++ b/src/PIL/PSDraw.py @@ -138,7 +138,7 @@ def image( sx = x / im.size[0] sy = y / im.size[1] self.fp.write(b"%f %f scale\n" % (sx, sy)) - EpsImagePlugin._save(im, self.fp, None, 0) + EpsImagePlugin._save(im, self.fp, "", 0) self.fp.write(b"\ngrestore\n") diff --git a/src/PIL/PalmImagePlugin.py b/src/PIL/PalmImagePlugin.py index 85f9fe1bf61..fc83918b5b4 100644 --- a/src/PIL/PalmImagePlugin.py +++ b/src/PIL/PalmImagePlugin.py @@ -8,6 +8,8 @@ ## from __future__ import annotations +from typing import IO + from . import Image, ImageFile from ._binary import o8 from ._binary import o16be as o16b @@ -82,10 +84,10 @@ # so build a prototype image to be used for palette resampling -def build_prototype_image(): +def build_prototype_image() -> Image.Image: image = Image.new("L", (1, len(_Palm8BitColormapValues))) image.putdata(list(range(len(_Palm8BitColormapValues)))) - palettedata = () + palettedata: tuple[int, ...] = () for colormapValue in _Palm8BitColormapValues: palettedata += colormapValue palettedata += (0, 0, 0) * (256 - len(_Palm8BitColormapValues)) @@ -112,7 +114,7 @@ def build_prototype_image(): # (Internal) Image save plugin for the Palm format. -def _save(im, fp, filename): +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: if im.mode == "P": # we assume this is a color Palm image with the standard colormap, # unless the "info" dict has a "custom-colormap" field @@ -141,7 +143,7 @@ def _save(im, fp, filename): raise OSError(msg) # we ignore the palette here - im.mode = "P" + im._mode = "P" rawmode = f"P;{bpp}" version = 1 diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index 52e8358017a..9e22313475e 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -404,9 +404,8 @@ def __init__(self, filename=None, f=None, buf=None, start_offset=0, mode="rb"): def __enter__(self) -> PdfParser: return self - def __exit__(self, exc_type, exc_value, traceback): + def __exit__(self, *args: object) -> None: self.close() - return False # do not suppress exceptions def start_writing(self) -> None: self.close_buf() diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 9aaadb47d5e..ba95980653a 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -178,7 +178,7 @@ def read(self) -> tuple[bytes, int, int]: def __enter__(self) -> ChunkStream: return self - def __exit__(self, *args): + def __exit__(self, *args: object) -> None: self.close() def close(self) -> None: diff --git a/src/PIL/TarIO.py b/src/PIL/TarIO.py index 7470663b4a1..cba26d4b059 100644 --- a/src/PIL/TarIO.py +++ b/src/PIL/TarIO.py @@ -16,7 +16,6 @@ from __future__ import annotations import io -from types import TracebackType from . import ContainerIO @@ -61,12 +60,7 @@ def __init__(self, tarfile: str, file: str) -> None: def __enter__(self) -> TarIO: return self - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: + def __exit__(self, *args: object) -> None: self.close() def close(self) -> None: diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 08ee506b162..702d8f33b5b 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -717,7 +717,7 @@ def _setitem(self, tag, value, legacy_api): # Unspec'd, and length > 1 dest[tag] = values - def __delitem__(self, tag): + def __delitem__(self, tag: int) -> None: self._tags_v2.pop(tag, None) self._tags_v1.pop(tag, None) self._tagdata.pop(tag, None) @@ -1106,7 +1106,7 @@ def __init__(self, fp=None, filename=None): super().__init__(fp, filename) - def _open(self): + def _open(self) -> None: """Open the first image in a TIFF file""" # Header @@ -1123,8 +1123,8 @@ def _open(self): self.__first = self.__next = self.tag_v2.next self.__frame = -1 self._fp = self.fp - self._frame_pos = [] - self._n_frames = None + self._frame_pos: list[int] = [] + self._n_frames: int | None = None logger.debug("*** TiffImageFile._open ***") logger.debug("- __first: %s", self.__first) @@ -1998,10 +1998,9 @@ def newFrame(self) -> None: def __enter__(self) -> AppendingTiffWriter: return self - def __exit__(self, exc_type, exc_value, traceback): + def __exit__(self, *args: object) -> None: if self.close_fp: self.close() - return False def tell(self) -> int: return self.f.tell() - self.offsetOfNewPage @@ -2043,42 +2042,42 @@ def skipIFDs(self) -> None: def write(self, data): return self.f.write(data) - def readShort(self): + def readShort(self) -> int: (value,) = struct.unpack(self.shortFmt, self.f.read(2)) return value - def readLong(self): + def readLong(self) -> int: (value,) = struct.unpack(self.longFmt, self.f.read(4)) return value - def rewriteLastShortToLong(self, value): + def rewriteLastShortToLong(self, value: int) -> None: self.f.seek(-2, os.SEEK_CUR) bytes_written = self.f.write(struct.pack(self.longFmt, value)) if bytes_written is not None and bytes_written != 4: msg = f"wrote only {bytes_written} bytes but wanted 4" raise RuntimeError(msg) - def rewriteLastShort(self, value): + def rewriteLastShort(self, value: int) -> None: self.f.seek(-2, os.SEEK_CUR) bytes_written = self.f.write(struct.pack(self.shortFmt, value)) if bytes_written is not None and bytes_written != 2: msg = f"wrote only {bytes_written} bytes but wanted 2" raise RuntimeError(msg) - def rewriteLastLong(self, value): + def rewriteLastLong(self, value: int) -> None: self.f.seek(-4, os.SEEK_CUR) bytes_written = self.f.write(struct.pack(self.longFmt, value)) if bytes_written is not None and bytes_written != 4: msg = f"wrote only {bytes_written} bytes but wanted 4" raise RuntimeError(msg) - def writeShort(self, value): + def writeShort(self, value: int) -> None: bytes_written = self.f.write(struct.pack(self.shortFmt, value)) if bytes_written is not None and bytes_written != 2: msg = f"wrote only {bytes_written} bytes but wanted 2" raise RuntimeError(msg) - def writeLong(self, value): + def writeLong(self, value: int) -> None: bytes_written = self.f.write(struct.pack(self.longFmt, value)) if bytes_written is not None and bytes_written != 4: msg = f"wrote only {bytes_written} bytes but wanted 4" @@ -2097,9 +2096,9 @@ def fixIFD(self) -> None: field_size = self.fieldSizes[field_type] total_size = field_size * count is_local = total_size <= 4 + offset: int | None if not is_local: - offset = self.readLong() - offset += self.offsetOfNewPage + offset = self.readLong() + self.offsetOfNewPage self.rewriteLastLong(offset) if tag in self.Tags: From c9a9e81749c12fdc4c7187284fe661810c9bc5c4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 13 Jun 2024 00:03:16 +1000 Subject: [PATCH 13/16] Use latest Ubuntu --- .readthedocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index b83ba05b128..def6282dd56 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -3,7 +3,7 @@ version: 2 formats: [pdf] build: - os: ubuntu-22.04 + os: ubuntu-lts-latest tools: python: "3" jobs: From 05a70e7861f37d2eb65be0186345ea8ea62fc3fe Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 14 Jun 2024 20:59:12 +1000 Subject: [PATCH 14/16] Corrected Ghostscript path --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 92a04a02154..6ce5200b6ea 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -35,7 +35,7 @@ install: - curl -fsSL -o nasm-win64.zip https://raw.githubusercontent.com/python-pillow/pillow-depends/main/nasm-2.16.03-win64.zip - 7z x nasm-win64.zip -oc:\ - choco install ghostscript --version=10.3.1 -- path c:\nasm-2.16.03;C:\Program Files\gs\gs10.00.0\bin;%PATH% +- path c:\nasm-2.16.03;C:\Program Files\gs\gs10.03.1\bin;%PATH% - cd c:\pillow\winbuild\ - ps: | c:\python38\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\ From dfd53564ff6a3fc7d35a5884bc0ef03939bcec0a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 15 Jun 2024 11:51:02 +1000 Subject: [PATCH 15/16] Ignore brew dependencies for libraqm on macOS 13 --- .github/workflows/macos-install.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/macos-install.sh b/.github/workflows/macos-install.sh index 28124d7f759..f8f191d387a 100755 --- a/.github/workflows/macos-install.sh +++ b/.github/workflows/macos-install.sh @@ -7,11 +7,15 @@ brew install \ ghostscript \ libimagequant \ libjpeg \ - libraqm \ libtiff \ little-cms2 \ openjpeg \ webp +if [[ "$ImageOS" == "macos13" ]]; then + brew install --ignore-dependencies libraqm +else + brew install libraqm +fi export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig" # TODO Update condition when cffi supports 3.13 From 9b6dbba4f07206c31e4e1b44175e8e5bc23b9003 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sat, 27 Apr 2024 17:44:57 -0500 Subject: [PATCH 16/16] parametrize test_lib_pack.py --- Tests/test_lib_pack.py | 1451 ++++++++++++++++++++-------------------- 1 file changed, 733 insertions(+), 718 deletions(-) diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index b4a300d0c59..ab9acd208e5 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -1,820 +1,835 @@ from __future__ import annotations -import sys - import pytest from PIL import Image +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( +def get_pack_parameters() -> list[str, str, int | bytes, list[float | tuple[int, ...]]]: + params = [] + + # 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: int | bytes, 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() -> ( + list[str, str, int | bytes, list[float | tuple[int, ...]]] +): + params = [] + + # 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: int | bytes, 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)