Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added type hints to Image.__init__() #8279

Merged
merged 4 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions Tests/test_color_lut.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ def test_correct_args(
self, lut_mode: str, table_channels: int, table_size: int | tuple[int, int, int]
) -> None:
im = Image.new("RGB", (10, 10), 0)
assert im.im is not None
im.im.color_lut_3d(
lut_mode,
Image.Resampling.BILINEAR,
Expand All @@ -142,7 +141,6 @@ def test_wrong_mode(
) -> None:
with pytest.raises(ValueError, match="wrong mode"):
im = Image.new(image_mode, (10, 10), 0)
assert im.im is not None
im.im.color_lut_3d(
lut_mode,
Image.Resampling.BILINEAR,
Expand All @@ -162,7 +160,6 @@ def test_correct_mode(
self, image_mode: str, lut_mode: str, table_channels: int, table_size: int
) -> None:
im = Image.new(image_mode, (10, 10), 0)
assert im.im is not None
im.im.color_lut_3d(
lut_mode,
Image.Resampling.BILINEAR,
Expand Down
1 change: 1 addition & 0 deletions Tests/test_file_webp.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ def test_roundtrip_rgba_palette(self, tmp_path: Path) -> None:
temp_file = str(tmp_path / "temp.webp")
im = Image.new("RGBA", (1, 1)).convert("P")
assert im.mode == "P"
assert im.palette is not None
assert im.palette.mode == "RGBA"
im.save(temp_file)

Expand Down
6 changes: 6 additions & 0 deletions Tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,7 @@ def _make_new(
assert new_image.size == image.size
assert new_image.info == base_image.info
if palette_result is not None:
assert new_image.palette is not None
assert new_image.palette.tobytes() == palette_result.tobytes()
else:
assert new_image.palette is None
Expand Down Expand Up @@ -1002,12 +1003,14 @@ def test_has_transparency_data(self) -> None:
# P mode with RGBA palette
im = Image.new("RGBA", (1, 1)).convert("P")
assert im.mode == "P"
assert im.palette is not None
assert im.palette.mode == "RGBA"
assert im.has_transparency_data

def test_apply_transparency(self) -> None:
im = Image.new("P", (1, 1))
im.putpalette((0, 0, 0, 1, 1, 1))
assert im.palette is not None
assert im.palette.colors == {(0, 0, 0): 0, (1, 1, 1): 1}

# Test that no transformation is applied without transparency
Expand All @@ -1025,13 +1028,16 @@ def test_apply_transparency(self) -> None:
im.putpalette((0, 0, 0, 255, 1, 1, 1, 128), "RGBA")
im.info["transparency"] = 0
im.apply_transparency()
assert im.palette is not None
assert im.palette.colors == {(0, 0, 0, 0): 0, (1, 1, 1, 128): 1}

# Test that transparency bytes are applied
with Image.open("Tests/images/pil123p.png") as im:
assert isinstance(im.info["transparency"], bytes)
assert im.palette is not None
assert im.palette.colors[(27, 35, 6)] == 24
im.apply_transparency()
assert im.palette is not None
assert im.palette.colors[(27, 35, 6, 214)] == 24

def test_constants(self) -> None:
Expand Down
1 change: 1 addition & 0 deletions Tests/test_image_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,5 @@ def test_fromarray_palette() -> None:
out = Image.fromarray(a, "P")

# Assert that the Python and C palettes match
assert out.palette is not None
assert len(out.palette.colors) == len(out.im.getpalette()) / 3
1 change: 1 addition & 0 deletions Tests/test_image_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ def test_trns_RGB(tmp_path: Path) -> None:
def test_l_macro_rounding(convert_mode: str) -> None:
for mode in ("P", "PA"):
im = Image.new(mode, (1, 1))
assert im.palette is not None
im.palette.getcolor((0, 1, 2))

converted_im = im.convert(convert_mode)
Expand Down
1 change: 1 addition & 0 deletions Tests/test_image_putpalette.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def test_rgba_palette(mode: str, palette: tuple[int, ...]) -> None:
im = Image.new("P", (1, 1))
im.putpalette(palette, mode)
assert im.getpalette() == [1, 2, 3]
assert im.palette is not None
assert im.palette.colors == {(1, 2, 3, 4): 0}


Expand Down
4 changes: 4 additions & 0 deletions Tests/test_image_quantize.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def test_quantize_no_dither() -> None:

converted = image.quantize(dither=Image.Dither.NONE, palette=palette)
assert converted.mode == "P"
assert converted.palette is not None
assert converted.palette.palette == palette.palette.palette


Expand All @@ -81,6 +82,7 @@ def test_quantize_no_dither2() -> None:
palette.putpalette(data)
quantized = im.quantize(dither=Image.Dither.NONE, palette=palette)

assert quantized.palette is not None
assert tuple(quantized.palette.palette) == data

px = quantized.load()
Expand Down Expand Up @@ -117,6 +119,7 @@ def test_colors() -> None:
im = hopper()
colors = 2
converted = im.quantize(colors)
assert converted.palette is not None
assert len(converted.palette.palette) == colors * len("RGB")


Expand Down Expand Up @@ -147,6 +150,7 @@ def test_palette(method: Image.Quantize, color: tuple[int, ...]) -> None:
converted = im.quantize(method=method)
converted_px = converted.load()
assert converted_px is not None
assert converted.palette is not None
assert converted_px[0, 0] == converted.palette.colors[color]


Expand Down
2 changes: 1 addition & 1 deletion selftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def testimage() -> None:
or you call the "load" method:

>>> im = Image.open("Tests/images/hopper.ppm")
>>> print(im.im) # internal image attribute
>>> print(im._im) # internal image attribute
None
>>> a = im.load()
>>> type(im.im) # doctest: +ELLIPSIS
Expand Down
1 change: 1 addition & 0 deletions src/PIL/BlpImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
magic = b"BLP1" if im.encoderinfo.get("blp_version") == "BLP1" else b"BLP2"
fp.write(magic)

assert im.palette is not None
fp.write(struct.pack("<i", 1)) # Uncompressed or DirectX compression
fp.write(struct.pack("<b", Encoding.UNCOMPRESSED))
fp.write(struct.pack("<b", 1 if im.palette.mode == "RGBA" else 0))
Expand Down
2 changes: 2 additions & 0 deletions src/PIL/BmpImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ def _bitmap(self, header: int = 0, offset: int = 0) -> None:

# ------------------ Special case : header is reported 40, which
# ---------------------- is shorter than real size for bpp >= 16
assert isinstance(file_info["width"], int)
assert isinstance(file_info["height"], int)
self._size = file_info["width"], file_info["height"]

# ------- If color count was not found in the header, compute from bits
Expand Down
5 changes: 2 additions & 3 deletions src/PIL/EpsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,6 @@ def _open(self) -> None:
self.fp.seek(offset)

self._mode = "RGB"
self._size = None

byte_arr = bytearray(255)
bytes_mv = memoryview(byte_arr)
Expand Down Expand Up @@ -228,7 +227,7 @@ def _read_comment(s: str) -> bool:
if k == "BoundingBox":
if v == "(atend)":
reading_trailer_comments = True
elif not self._size or (trailer_reached and reading_trailer_comments):
elif not self.tile or (trailer_reached and reading_trailer_comments):
try:
# Note: The DSC spec says that BoundingBox
# fields should be integers, but some drivers
Expand Down Expand Up @@ -346,7 +345,7 @@ def _read_comment(s: str) -> bool:
trailer_reached = True
bytes_read = 0

if not self._size:
if not self.tile:
msg = "cannot determine EPS bounding box"
raise OSError(msg)

Expand Down
2 changes: 2 additions & 0 deletions src/PIL/FpxImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ def _open_index(self, index: int = 1) -> None:

# size (highest resolution)

assert isinstance(prop[0x1000002], int)
assert isinstance(prop[0x1000003], int)
self._size = prop[0x1000002], prop[0x1000003]

size = max(self.size)
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/GbrImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def _open(self) -> None:
self._data_size = width * height * color_depth

def load(self) -> Image.core.PixelAccess | None:
if not self.im:
if self._im is None:
self.im = Image.core.new(self.mode, self.size)
self.frombytes(self.fp.read(self._data_size))
return Image.Image.load(self)
Expand Down
49 changes: 32 additions & 17 deletions src/PIL/GifImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def seek(self, frame: int) -> None:
if not self._seek_check(frame):
return
if frame < self.__frame:
self.im = None
self._im = None
self._seek(0)

last_frame = self.__frame
Expand Down Expand Up @@ -320,11 +320,14 @@ def _seek(self, frame: int, update_image: bool = True) -> None:
else:
self._mode = "L"

if not palette and self.global_palette:
if palette:
self.palette = palette
elif self.global_palette:
from copy import copy

palette = copy(self.global_palette)
self.palette = palette
self.palette = copy(self.global_palette)
else:
self.palette = None
else:
if self.mode == "P":
if (
Expand Down Expand Up @@ -376,7 +379,7 @@ def _rgb(color: int) -> tuple[int, int, int]:
self.dispose = Image.core.fill(dispose_mode, dispose_size, color)
else:
# replace with previous contents
if self.im is not None:
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:
Expand Down Expand Up @@ -434,7 +437,7 @@ def load_prepare(self) -> None:
self.im = Image.core.fill("P", self.size, self._frame_transparency or 0)
self.im.putpalette("RGB", *self._frame_palette.getdata())
else:
self.im = None
self._im = None
self._mode = temp_mode
self._frame_palette = None

Expand Down Expand Up @@ -495,6 +498,7 @@ def _normalize_mode(im: Image.Image) -> Image.Image:
return im
if Image.getmodebase(im.mode) == "RGB":
im = im.convert("P", palette=Image.Palette.ADAPTIVE)
assert im.palette is not None
if im.palette.mode == "RGBA":
for rgba in im.palette.colors:
if rgba[3] == 0:
Expand Down Expand Up @@ -536,11 +540,11 @@ def _normalize_palette(
if not source_palette:
source_palette = bytearray(i // 3 for i in range(768))
im.palette = ImagePalette.ImagePalette("RGB", palette=source_palette)
assert source_palette is not None

used_palette_colors: list[int] | None
if palette:
used_palette_colors = []
assert source_palette is not None
used_palette_colors: list[int | None] = []
assert im.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)
Expand All @@ -553,20 +557,25 @@ def _normalize_palette(
if j not in used_palette_colors:
used_palette_colors[i] = j
break
im = im.remap_palette(used_palette_colors)
dest_map: list[int] = []
for index in used_palette_colors:
assert index is not None
dest_map.append(index)
im = im.remap_palette(dest_map)
else:
used_palette_colors = _get_optimize(im, info)
if used_palette_colors is not None:
im = im.remap_palette(used_palette_colors, source_palette)
optimized_palette_colors = _get_optimize(im, info)
if optimized_palette_colors is not None:
im = im.remap_palette(optimized_palette_colors, source_palette)
if "transparency" in info:
try:
info["transparency"] = used_palette_colors.index(
info["transparency"] = optimized_palette_colors.index(
info["transparency"]
)
except ValueError:
del info["transparency"]
return im

assert im.palette is not None
im.palette.palette = source_palette
return im

Expand All @@ -578,7 +587,8 @@ def _write_single_frame(
) -> None:
im_out = _normalize_mode(im)
for k, v in im_out.info.items():
im.encoderinfo.setdefault(k, v)
if isinstance(k, str):
im.encoderinfo.setdefault(k, v)
im_out = _normalize_palette(im_out, palette, im.encoderinfo)

for s in _get_global_header(im_out, im.encoderinfo):
Expand Down Expand Up @@ -632,7 +642,8 @@ def _write_multiple_frames(
for k, v in im_frame.info.items():
if k == "transparency":
continue
im.encoderinfo.setdefault(k, v)
if isinstance(k, str):
im.encoderinfo.setdefault(k, v)

encoderinfo = im.encoderinfo.copy()
if "transparency" in im_frame.info:
Expand Down Expand Up @@ -662,10 +673,12 @@ def _write_multiple_frames(
)
background = _get_background(im_frame, color)
background_im = Image.new("P", im_frame.size, background)
assert im_frames[0].im.palette is not None
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:
assert im_frame.palette is not None
try:
encoderinfo["transparency"] = (
im_frame.palette._new_color_index(im_frame)
Expand Down Expand Up @@ -903,6 +916,7 @@ def _get_optimize(im: Image.Image, info: dict[str, Any]) -> list[int] | None:
if optimise or max(used_palette_colors) >= len(used_palette_colors):
return used_palette_colors

assert im.palette is not None
num_palette_colors = len(im.palette.palette) // Image.getmodebands(
im.palette.mode
)
Expand Down Expand Up @@ -952,7 +966,7 @@ def _get_palette_bytes(im: Image.Image) -> bytes:
:param im: Image object
:returns: Bytes, len<=768 suitable for inclusion in gif header
"""
return im.palette.palette if im.palette else b""
return bytes(im.palette.palette) if im.palette else b""


def _get_background(
Expand All @@ -965,6 +979,7 @@ def _get_background(
# WebPImagePlugin stores an RGBA value in info["background"]
# So it must be converted to the same format as GifImagePlugin's
# info["background"] - a global color table index
assert im.palette is not None
try:
background = im.palette.getcolor(info_background, im)
except ValueError as e:
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/IcnsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ def load(self) -> Image.core.PixelAccess | None:
)

px = Image.Image.load(self)
if self.im is not None and self.im.size == self.size:
if self._im is not None and self.im.size == self.size:
# Already loaded
return px
self.load_prepare()
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/IcoImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ def size(self, value: tuple[int, int]) -> None:
self._size = value

def load(self) -> Image.core.PixelAccess | None:
if self.im is not None and self.im.size == self.size:
if self._im is not None and self.im.size == self.size:
# Already loaded
return Image.Image.load(self)
im = self.ico.getimage(self.size)
Expand Down
Loading
Loading