mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-25 17:36:18 +03:00
Merge pull request #8279 from radarhere/type_hint_init
This commit is contained in:
commit
1bf9fb4e40
|
@ -120,7 +120,6 @@ class TestColorLut3DCoreAPI:
|
||||||
self, lut_mode: str, table_channels: int, table_size: int | tuple[int, int, int]
|
self, lut_mode: str, table_channels: int, table_size: int | tuple[int, int, int]
|
||||||
) -> None:
|
) -> None:
|
||||||
im = Image.new("RGB", (10, 10), 0)
|
im = Image.new("RGB", (10, 10), 0)
|
||||||
assert im.im is not None
|
|
||||||
im.im.color_lut_3d(
|
im.im.color_lut_3d(
|
||||||
lut_mode,
|
lut_mode,
|
||||||
Image.Resampling.BILINEAR,
|
Image.Resampling.BILINEAR,
|
||||||
|
@ -142,7 +141,6 @@ class TestColorLut3DCoreAPI:
|
||||||
) -> None:
|
) -> None:
|
||||||
with pytest.raises(ValueError, match="wrong mode"):
|
with pytest.raises(ValueError, match="wrong mode"):
|
||||||
im = Image.new(image_mode, (10, 10), 0)
|
im = Image.new(image_mode, (10, 10), 0)
|
||||||
assert im.im is not None
|
|
||||||
im.im.color_lut_3d(
|
im.im.color_lut_3d(
|
||||||
lut_mode,
|
lut_mode,
|
||||||
Image.Resampling.BILINEAR,
|
Image.Resampling.BILINEAR,
|
||||||
|
@ -162,7 +160,6 @@ class TestColorLut3DCoreAPI:
|
||||||
self, image_mode: str, lut_mode: str, table_channels: int, table_size: int
|
self, image_mode: str, lut_mode: str, table_channels: int, table_size: int
|
||||||
) -> None:
|
) -> None:
|
||||||
im = Image.new(image_mode, (10, 10), 0)
|
im = Image.new(image_mode, (10, 10), 0)
|
||||||
assert im.im is not None
|
|
||||||
im.im.color_lut_3d(
|
im.im.color_lut_3d(
|
||||||
lut_mode,
|
lut_mode,
|
||||||
Image.Resampling.BILINEAR,
|
Image.Resampling.BILINEAR,
|
||||||
|
|
|
@ -248,6 +248,7 @@ class TestFileWebp:
|
||||||
temp_file = str(tmp_path / "temp.webp")
|
temp_file = str(tmp_path / "temp.webp")
|
||||||
im = Image.new("RGBA", (1, 1)).convert("P")
|
im = Image.new("RGBA", (1, 1)).convert("P")
|
||||||
assert im.mode == "P"
|
assert im.mode == "P"
|
||||||
|
assert im.palette is not None
|
||||||
assert im.palette.mode == "RGBA"
|
assert im.palette.mode == "RGBA"
|
||||||
im.save(temp_file)
|
im.save(temp_file)
|
||||||
|
|
||||||
|
|
|
@ -705,6 +705,7 @@ class TestImage:
|
||||||
assert new_image.size == image.size
|
assert new_image.size == image.size
|
||||||
assert new_image.info == base_image.info
|
assert new_image.info == base_image.info
|
||||||
if palette_result is not None:
|
if palette_result is not None:
|
||||||
|
assert new_image.palette is not None
|
||||||
assert new_image.palette.tobytes() == palette_result.tobytes()
|
assert new_image.palette.tobytes() == palette_result.tobytes()
|
||||||
else:
|
else:
|
||||||
assert new_image.palette is None
|
assert new_image.palette is None
|
||||||
|
@ -1002,12 +1003,14 @@ class TestImage:
|
||||||
# P mode with RGBA palette
|
# P mode with RGBA palette
|
||||||
im = Image.new("RGBA", (1, 1)).convert("P")
|
im = Image.new("RGBA", (1, 1)).convert("P")
|
||||||
assert im.mode == "P"
|
assert im.mode == "P"
|
||||||
|
assert im.palette is not None
|
||||||
assert im.palette.mode == "RGBA"
|
assert im.palette.mode == "RGBA"
|
||||||
assert im.has_transparency_data
|
assert im.has_transparency_data
|
||||||
|
|
||||||
def test_apply_transparency(self) -> None:
|
def test_apply_transparency(self) -> None:
|
||||||
im = Image.new("P", (1, 1))
|
im = Image.new("P", (1, 1))
|
||||||
im.putpalette((0, 0, 0, 1, 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}
|
assert im.palette.colors == {(0, 0, 0): 0, (1, 1, 1): 1}
|
||||||
|
|
||||||
# Test that no transformation is applied without transparency
|
# Test that no transformation is applied without transparency
|
||||||
|
@ -1025,13 +1028,16 @@ class TestImage:
|
||||||
im.putpalette((0, 0, 0, 255, 1, 1, 1, 128), "RGBA")
|
im.putpalette((0, 0, 0, 255, 1, 1, 1, 128), "RGBA")
|
||||||
im.info["transparency"] = 0
|
im.info["transparency"] = 0
|
||||||
im.apply_transparency()
|
im.apply_transparency()
|
||||||
|
assert im.palette is not None
|
||||||
assert im.palette.colors == {(0, 0, 0, 0): 0, (1, 1, 1, 128): 1}
|
assert im.palette.colors == {(0, 0, 0, 0): 0, (1, 1, 1, 128): 1}
|
||||||
|
|
||||||
# Test that transparency bytes are applied
|
# Test that transparency bytes are applied
|
||||||
with Image.open("Tests/images/pil123p.png") as im:
|
with Image.open("Tests/images/pil123p.png") as im:
|
||||||
assert isinstance(im.info["transparency"], bytes)
|
assert isinstance(im.info["transparency"], bytes)
|
||||||
|
assert im.palette is not None
|
||||||
assert im.palette.colors[(27, 35, 6)] == 24
|
assert im.palette.colors[(27, 35, 6)] == 24
|
||||||
im.apply_transparency()
|
im.apply_transparency()
|
||||||
|
assert im.palette is not None
|
||||||
assert im.palette.colors[(27, 35, 6, 214)] == 24
|
assert im.palette.colors[(27, 35, 6, 214)] == 24
|
||||||
|
|
||||||
def test_constants(self) -> None:
|
def test_constants(self) -> None:
|
||||||
|
|
|
@ -113,4 +113,5 @@ def test_fromarray_palette() -> None:
|
||||||
out = Image.fromarray(a, "P")
|
out = Image.fromarray(a, "P")
|
||||||
|
|
||||||
# Assert that the Python and C palettes match
|
# Assert that the Python and C palettes match
|
||||||
|
assert out.palette is not None
|
||||||
assert len(out.palette.colors) == len(out.im.getpalette()) / 3
|
assert len(out.palette.colors) == len(out.im.getpalette()) / 3
|
||||||
|
|
|
@ -218,6 +218,7 @@ def test_trns_RGB(tmp_path: Path) -> None:
|
||||||
def test_l_macro_rounding(convert_mode: str) -> None:
|
def test_l_macro_rounding(convert_mode: str) -> None:
|
||||||
for mode in ("P", "PA"):
|
for mode in ("P", "PA"):
|
||||||
im = Image.new(mode, (1, 1))
|
im = Image.new(mode, (1, 1))
|
||||||
|
assert im.palette is not None
|
||||||
im.palette.getcolor((0, 1, 2))
|
im.palette.getcolor((0, 1, 2))
|
||||||
|
|
||||||
converted_im = im.convert(convert_mode)
|
converted_im = im.convert(convert_mode)
|
||||||
|
|
|
@ -86,6 +86,7 @@ def test_rgba_palette(mode: str, palette: tuple[int, ...]) -> None:
|
||||||
im = Image.new("P", (1, 1))
|
im = Image.new("P", (1, 1))
|
||||||
im.putpalette(palette, mode)
|
im.putpalette(palette, mode)
|
||||||
assert im.getpalette() == [1, 2, 3]
|
assert im.getpalette() == [1, 2, 3]
|
||||||
|
assert im.palette is not None
|
||||||
assert im.palette.colors == {(1, 2, 3, 4): 0}
|
assert im.palette.colors == {(1, 2, 3, 4): 0}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,7 @@ def test_quantize_no_dither() -> None:
|
||||||
|
|
||||||
converted = image.quantize(dither=Image.Dither.NONE, palette=palette)
|
converted = image.quantize(dither=Image.Dither.NONE, palette=palette)
|
||||||
assert converted.mode == "P"
|
assert converted.mode == "P"
|
||||||
|
assert converted.palette is not None
|
||||||
assert converted.palette.palette == palette.palette.palette
|
assert converted.palette.palette == palette.palette.palette
|
||||||
|
|
||||||
|
|
||||||
|
@ -81,6 +82,7 @@ def test_quantize_no_dither2() -> None:
|
||||||
palette.putpalette(data)
|
palette.putpalette(data)
|
||||||
quantized = im.quantize(dither=Image.Dither.NONE, palette=palette)
|
quantized = im.quantize(dither=Image.Dither.NONE, palette=palette)
|
||||||
|
|
||||||
|
assert quantized.palette is not None
|
||||||
assert tuple(quantized.palette.palette) == data
|
assert tuple(quantized.palette.palette) == data
|
||||||
|
|
||||||
px = quantized.load()
|
px = quantized.load()
|
||||||
|
@ -117,6 +119,7 @@ def test_colors() -> None:
|
||||||
im = hopper()
|
im = hopper()
|
||||||
colors = 2
|
colors = 2
|
||||||
converted = im.quantize(colors)
|
converted = im.quantize(colors)
|
||||||
|
assert converted.palette is not None
|
||||||
assert len(converted.palette.palette) == colors * len("RGB")
|
assert len(converted.palette.palette) == colors * len("RGB")
|
||||||
|
|
||||||
|
|
||||||
|
@ -147,6 +150,7 @@ def test_palette(method: Image.Quantize, color: tuple[int, ...]) -> None:
|
||||||
converted = im.quantize(method=method)
|
converted = im.quantize(method=method)
|
||||||
converted_px = converted.load()
|
converted_px = converted.load()
|
||||||
assert converted_px is not None
|
assert converted_px is not None
|
||||||
|
assert converted.palette is not None
|
||||||
assert converted_px[0, 0] == converted.palette.colors[color]
|
assert converted_px[0, 0] == converted.palette.colors[color]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ def testimage() -> None:
|
||||||
or you call the "load" method:
|
or you call the "load" method:
|
||||||
|
|
||||||
>>> im = Image.open("Tests/images/hopper.ppm")
|
>>> im = Image.open("Tests/images/hopper.ppm")
|
||||||
>>> print(im.im) # internal image attribute
|
>>> print(im._im) # internal image attribute
|
||||||
None
|
None
|
||||||
>>> a = im.load()
|
>>> a = im.load()
|
||||||
>>> type(im.im) # doctest: +ELLIPSIS
|
>>> type(im.im) # doctest: +ELLIPSIS
|
||||||
|
|
|
@ -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"
|
magic = b"BLP1" if im.encoderinfo.get("blp_version") == "BLP1" else b"BLP2"
|
||||||
fp.write(magic)
|
fp.write(magic)
|
||||||
|
|
||||||
|
assert im.palette is not None
|
||||||
fp.write(struct.pack("<i", 1)) # Uncompressed or DirectX compression
|
fp.write(struct.pack("<i", 1)) # Uncompressed or DirectX compression
|
||||||
fp.write(struct.pack("<b", Encoding.UNCOMPRESSED))
|
fp.write(struct.pack("<b", Encoding.UNCOMPRESSED))
|
||||||
fp.write(struct.pack("<b", 1 if im.palette.mode == "RGBA" else 0))
|
fp.write(struct.pack("<b", 1 if im.palette.mode == "RGBA" else 0))
|
||||||
|
|
|
@ -170,6 +170,8 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
# ------------------ Special case : header is reported 40, which
|
# ------------------ Special case : header is reported 40, which
|
||||||
# ---------------------- is shorter than real size for bpp >= 16
|
# ---------------------- 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"]
|
self._size = file_info["width"], file_info["height"]
|
||||||
|
|
||||||
# ------- If color count was not found in the header, compute from bits
|
# ------- If color count was not found in the header, compute from bits
|
||||||
|
|
|
@ -190,7 +190,6 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
self.fp.seek(offset)
|
self.fp.seek(offset)
|
||||||
|
|
||||||
self._mode = "RGB"
|
self._mode = "RGB"
|
||||||
self._size = None
|
|
||||||
|
|
||||||
byte_arr = bytearray(255)
|
byte_arr = bytearray(255)
|
||||||
bytes_mv = memoryview(byte_arr)
|
bytes_mv = memoryview(byte_arr)
|
||||||
|
@ -228,7 +227,7 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
if k == "BoundingBox":
|
if k == "BoundingBox":
|
||||||
if v == "(atend)":
|
if v == "(atend)":
|
||||||
reading_trailer_comments = True
|
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:
|
try:
|
||||||
# Note: The DSC spec says that BoundingBox
|
# Note: The DSC spec says that BoundingBox
|
||||||
# fields should be integers, but some drivers
|
# fields should be integers, but some drivers
|
||||||
|
@ -346,7 +345,7 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
trailer_reached = True
|
trailer_reached = True
|
||||||
bytes_read = 0
|
bytes_read = 0
|
||||||
|
|
||||||
if not self._size:
|
if not self.tile:
|
||||||
msg = "cannot determine EPS bounding box"
|
msg = "cannot determine EPS bounding box"
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,8 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
# size (highest resolution)
|
# size (highest resolution)
|
||||||
|
|
||||||
|
assert isinstance(prop[0x1000002], int)
|
||||||
|
assert isinstance(prop[0x1000003], int)
|
||||||
self._size = prop[0x1000002], prop[0x1000003]
|
self._size = prop[0x1000002], prop[0x1000003]
|
||||||
|
|
||||||
size = max(self.size)
|
size = max(self.size)
|
||||||
|
|
|
@ -89,7 +89,7 @@ class GbrImageFile(ImageFile.ImageFile):
|
||||||
self._data_size = width * height * color_depth
|
self._data_size = width * height * color_depth
|
||||||
|
|
||||||
def load(self) -> Image.core.PixelAccess | None:
|
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.im = Image.core.new(self.mode, self.size)
|
||||||
self.frombytes(self.fp.read(self._data_size))
|
self.frombytes(self.fp.read(self._data_size))
|
||||||
return Image.Image.load(self)
|
return Image.Image.load(self)
|
||||||
|
|
|
@ -155,7 +155,7 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
if not self._seek_check(frame):
|
if not self._seek_check(frame):
|
||||||
return
|
return
|
||||||
if frame < self.__frame:
|
if frame < self.__frame:
|
||||||
self.im = None
|
self._im = None
|
||||||
self._seek(0)
|
self._seek(0)
|
||||||
|
|
||||||
last_frame = self.__frame
|
last_frame = self.__frame
|
||||||
|
@ -320,11 +320,14 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
else:
|
else:
|
||||||
self._mode = "L"
|
self._mode = "L"
|
||||||
|
|
||||||
if not palette and self.global_palette:
|
if palette:
|
||||||
|
self.palette = palette
|
||||||
|
elif self.global_palette:
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
|
||||||
palette = copy(self.global_palette)
|
self.palette = copy(self.global_palette)
|
||||||
self.palette = palette
|
else:
|
||||||
|
self.palette = None
|
||||||
else:
|
else:
|
||||||
if self.mode == "P":
|
if self.mode == "P":
|
||||||
if (
|
if (
|
||||||
|
@ -376,7 +379,7 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
self.dispose = Image.core.fill(dispose_mode, dispose_size, color)
|
self.dispose = Image.core.fill(dispose_mode, dispose_size, color)
|
||||||
else:
|
else:
|
||||||
# replace with previous contents
|
# replace with previous contents
|
||||||
if self.im is not None:
|
if self._im is not None:
|
||||||
# only dispose the extent in this frame
|
# only dispose the extent in this frame
|
||||||
self.dispose = self._crop(self.im, self.dispose_extent)
|
self.dispose = self._crop(self.im, self.dispose_extent)
|
||||||
elif frame_transparency is not None:
|
elif frame_transparency is not None:
|
||||||
|
@ -434,7 +437,7 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
self.im = Image.core.fill("P", self.size, self._frame_transparency or 0)
|
self.im = Image.core.fill("P", self.size, self._frame_transparency or 0)
|
||||||
self.im.putpalette("RGB", *self._frame_palette.getdata())
|
self.im.putpalette("RGB", *self._frame_palette.getdata())
|
||||||
else:
|
else:
|
||||||
self.im = None
|
self._im = None
|
||||||
self._mode = temp_mode
|
self._mode = temp_mode
|
||||||
self._frame_palette = None
|
self._frame_palette = None
|
||||||
|
|
||||||
|
@ -495,6 +498,7 @@ def _normalize_mode(im: Image.Image) -> Image.Image:
|
||||||
return im
|
return im
|
||||||
if Image.getmodebase(im.mode) == "RGB":
|
if Image.getmodebase(im.mode) == "RGB":
|
||||||
im = im.convert("P", palette=Image.Palette.ADAPTIVE)
|
im = im.convert("P", palette=Image.Palette.ADAPTIVE)
|
||||||
|
assert im.palette is not None
|
||||||
if im.palette.mode == "RGBA":
|
if im.palette.mode == "RGBA":
|
||||||
for rgba in im.palette.colors:
|
for rgba in im.palette.colors:
|
||||||
if rgba[3] == 0:
|
if rgba[3] == 0:
|
||||||
|
@ -536,11 +540,11 @@ def _normalize_palette(
|
||||||
if not source_palette:
|
if not source_palette:
|
||||||
source_palette = bytearray(i // 3 for i in range(768))
|
source_palette = bytearray(i // 3 for i in range(768))
|
||||||
im.palette = ImagePalette.ImagePalette("RGB", palette=source_palette)
|
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
|
assert source_palette is not None
|
||||||
|
|
||||||
|
if palette:
|
||||||
|
used_palette_colors: list[int | None] = []
|
||||||
|
assert im.palette is not None
|
||||||
for i in range(0, len(source_palette), 3):
|
for i in range(0, len(source_palette), 3):
|
||||||
source_color = tuple(source_palette[i : i + 3])
|
source_color = tuple(source_palette[i : i + 3])
|
||||||
index = im.palette.colors.get(source_color)
|
index = im.palette.colors.get(source_color)
|
||||||
|
@ -553,20 +557,25 @@ def _normalize_palette(
|
||||||
if j not in used_palette_colors:
|
if j not in used_palette_colors:
|
||||||
used_palette_colors[i] = j
|
used_palette_colors[i] = j
|
||||||
break
|
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:
|
else:
|
||||||
used_palette_colors = _get_optimize(im, info)
|
optimized_palette_colors = _get_optimize(im, info)
|
||||||
if used_palette_colors is not None:
|
if optimized_palette_colors is not None:
|
||||||
im = im.remap_palette(used_palette_colors, source_palette)
|
im = im.remap_palette(optimized_palette_colors, source_palette)
|
||||||
if "transparency" in info:
|
if "transparency" in info:
|
||||||
try:
|
try:
|
||||||
info["transparency"] = used_palette_colors.index(
|
info["transparency"] = optimized_palette_colors.index(
|
||||||
info["transparency"]
|
info["transparency"]
|
||||||
)
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
del info["transparency"]
|
del info["transparency"]
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
assert im.palette is not None
|
||||||
im.palette.palette = source_palette
|
im.palette.palette = source_palette
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
@ -578,6 +587,7 @@ def _write_single_frame(
|
||||||
) -> None:
|
) -> None:
|
||||||
im_out = _normalize_mode(im)
|
im_out = _normalize_mode(im)
|
||||||
for k, v in im_out.info.items():
|
for k, v in im_out.info.items():
|
||||||
|
if isinstance(k, str):
|
||||||
im.encoderinfo.setdefault(k, v)
|
im.encoderinfo.setdefault(k, v)
|
||||||
im_out = _normalize_palette(im_out, palette, im.encoderinfo)
|
im_out = _normalize_palette(im_out, palette, im.encoderinfo)
|
||||||
|
|
||||||
|
@ -632,6 +642,7 @@ def _write_multiple_frames(
|
||||||
for k, v in im_frame.info.items():
|
for k, v in im_frame.info.items():
|
||||||
if k == "transparency":
|
if k == "transparency":
|
||||||
continue
|
continue
|
||||||
|
if isinstance(k, str):
|
||||||
im.encoderinfo.setdefault(k, v)
|
im.encoderinfo.setdefault(k, v)
|
||||||
|
|
||||||
encoderinfo = im.encoderinfo.copy()
|
encoderinfo = im.encoderinfo.copy()
|
||||||
|
@ -662,10 +673,12 @@ def _write_multiple_frames(
|
||||||
)
|
)
|
||||||
background = _get_background(im_frame, color)
|
background = _get_background(im_frame, color)
|
||||||
background_im = Image.new("P", im_frame.size, background)
|
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)
|
background_im.putpalette(im_frames[0].im.palette)
|
||||||
bbox = _getbbox(background_im, im_frame)[1]
|
bbox = _getbbox(background_im, im_frame)[1]
|
||||||
elif encoderinfo.get("optimize") and im_frame.mode != "1":
|
elif encoderinfo.get("optimize") and im_frame.mode != "1":
|
||||||
if "transparency" not in encoderinfo:
|
if "transparency" not in encoderinfo:
|
||||||
|
assert im_frame.palette is not None
|
||||||
try:
|
try:
|
||||||
encoderinfo["transparency"] = (
|
encoderinfo["transparency"] = (
|
||||||
im_frame.palette._new_color_index(im_frame)
|
im_frame.palette._new_color_index(im_frame)
|
||||||
|
@ -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):
|
if optimise or max(used_palette_colors) >= len(used_palette_colors):
|
||||||
return used_palette_colors
|
return used_palette_colors
|
||||||
|
|
||||||
|
assert im.palette is not None
|
||||||
num_palette_colors = len(im.palette.palette) // Image.getmodebands(
|
num_palette_colors = len(im.palette.palette) // Image.getmodebands(
|
||||||
im.palette.mode
|
im.palette.mode
|
||||||
)
|
)
|
||||||
|
@ -952,7 +966,7 @@ def _get_palette_bytes(im: Image.Image) -> bytes:
|
||||||
:param im: Image object
|
:param im: Image object
|
||||||
:returns: Bytes, len<=768 suitable for inclusion in gif header
|
: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(
|
def _get_background(
|
||||||
|
@ -965,6 +979,7 @@ def _get_background(
|
||||||
# WebPImagePlugin stores an RGBA value in info["background"]
|
# WebPImagePlugin stores an RGBA value in info["background"]
|
||||||
# So it must be converted to the same format as GifImagePlugin's
|
# So it must be converted to the same format as GifImagePlugin's
|
||||||
# info["background"] - a global color table index
|
# info["background"] - a global color table index
|
||||||
|
assert im.palette is not None
|
||||||
try:
|
try:
|
||||||
background = im.palette.getcolor(info_background, im)
|
background = im.palette.getcolor(info_background, im)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
|
|
@ -308,7 +308,7 @@ class IcnsImageFile(ImageFile.ImageFile):
|
||||||
)
|
)
|
||||||
|
|
||||||
px = Image.Image.load(self)
|
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
|
# Already loaded
|
||||||
return px
|
return px
|
||||||
self.load_prepare()
|
self.load_prepare()
|
||||||
|
|
|
@ -330,7 +330,7 @@ class IcoImageFile(ImageFile.ImageFile):
|
||||||
self._size = value
|
self._size = value
|
||||||
|
|
||||||
def load(self) -> Image.core.PixelAccess | None:
|
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
|
# Already loaded
|
||||||
return Image.Image.load(self)
|
return Image.Image.load(self)
|
||||||
im = self.ico.getimage(self.size)
|
im = self.ico.getimage(self.size)
|
||||||
|
|
|
@ -545,16 +545,27 @@ class Image:
|
||||||
format_description: str | None = None
|
format_description: str | None = None
|
||||||
_close_exclusive_fp_after_loading = True
|
_close_exclusive_fp_after_loading = True
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
# FIXME: take "new" parameters / other image?
|
# FIXME: take "new" parameters / other image?
|
||||||
# FIXME: turn mode and size into delegating properties?
|
# FIXME: turn mode and size into delegating properties?
|
||||||
self.im = None
|
self._im: core.ImagingCore | DeferredError | None = None
|
||||||
self._mode = ""
|
self._mode = ""
|
||||||
self._size = (0, 0)
|
self._size = (0, 0)
|
||||||
self.palette = None
|
self.palette: ImagePalette.ImagePalette | None = None
|
||||||
self.info = {}
|
self.info: dict[str | tuple[int, int], Any] = {}
|
||||||
self.readonly = 0
|
self.readonly = 0
|
||||||
self._exif = None
|
self._exif: Exif | None = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def im(self) -> core.ImagingCore:
|
||||||
|
if isinstance(self._im, DeferredError):
|
||||||
|
raise self._im.ex
|
||||||
|
assert self._im is not None
|
||||||
|
return self._im
|
||||||
|
|
||||||
|
@im.setter
|
||||||
|
def im(self, im: core.ImagingCore) -> None:
|
||||||
|
self._im = im
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def width(self) -> int:
|
def width(self) -> int:
|
||||||
|
@ -630,7 +641,7 @@ class Image:
|
||||||
# Instead of simply setting to None, we're setting up a
|
# Instead of simply setting to None, we're setting up a
|
||||||
# deferred error that will better explain that the core image
|
# deferred error that will better explain that the core image
|
||||||
# object is gone.
|
# object is gone.
|
||||||
self.im = DeferredError(ValueError("Operation on closed image"))
|
self._im = DeferredError(ValueError("Operation on closed image"))
|
||||||
|
|
||||||
def _copy(self) -> None:
|
def _copy(self) -> None:
|
||||||
self.load()
|
self.load()
|
||||||
|
@ -888,7 +899,7 @@ class Image:
|
||||||
:returns: An image access object.
|
:returns: An image access object.
|
||||||
:rtype: :py:class:`.PixelAccess`
|
:rtype: :py:class:`.PixelAccess`
|
||||||
"""
|
"""
|
||||||
if self.im is not None and self.palette and self.palette.dirty:
|
if self._im is not None and self.palette and self.palette.dirty:
|
||||||
# realize palette
|
# realize palette
|
||||||
mode, arr = self.palette.getdata()
|
mode, arr = self.palette.getdata()
|
||||||
self.im.putpalette(self.palette.mode, mode, arr)
|
self.im.putpalette(self.palette.mode, mode, arr)
|
||||||
|
@ -905,7 +916,7 @@ class Image:
|
||||||
self.palette.mode, self.palette.mode
|
self.palette.mode, self.palette.mode
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.im is not None:
|
if self._im is not None:
|
||||||
return self.im.pixel_access(self.readonly)
|
return self.im.pixel_access(self.readonly)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -1046,9 +1057,11 @@ class Image:
|
||||||
# use existing conversions
|
# use existing conversions
|
||||||
trns_im = new(self.mode, (1, 1))
|
trns_im = new(self.mode, (1, 1))
|
||||||
if self.mode == "P":
|
if self.mode == "P":
|
||||||
|
assert self.palette is not None
|
||||||
trns_im.putpalette(self.palette)
|
trns_im.putpalette(self.palette)
|
||||||
if isinstance(t, tuple):
|
if isinstance(t, tuple):
|
||||||
err = "Couldn't allocate a palette color for transparency"
|
err = "Couldn't allocate a palette color for transparency"
|
||||||
|
assert trns_im.palette is not None
|
||||||
try:
|
try:
|
||||||
t = trns_im.palette.getcolor(t, self)
|
t = trns_im.palette.getcolor(t, self)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
@ -1150,7 +1163,9 @@ class Image:
|
||||||
if trns is not None:
|
if trns is not None:
|
||||||
if new_im.mode == "P" and new_im.palette:
|
if new_im.mode == "P" and new_im.palette:
|
||||||
try:
|
try:
|
||||||
new_im.info["transparency"] = new_im.palette.getcolor(trns, new_im)
|
new_im.info["transparency"] = new_im.palette.getcolor(
|
||||||
|
cast(tuple[int, ...], trns), new_im # trns was converted to RGB
|
||||||
|
)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
del new_im.info["transparency"]
|
del new_im.info["transparency"]
|
||||||
if str(e) != "cannot allocate more than 256 colors":
|
if str(e) != "cannot allocate more than 256 colors":
|
||||||
|
@ -1228,6 +1243,7 @@ class Image:
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
im = self.im.convert("P", dither, palette.im)
|
im = self.im.convert("P", dither, palette.im)
|
||||||
new_im = self._new(im)
|
new_im = self._new(im)
|
||||||
|
assert palette.palette is not None
|
||||||
new_im.palette = palette.palette.copy()
|
new_im.palette = palette.palette.copy()
|
||||||
return new_im
|
return new_im
|
||||||
|
|
||||||
|
@ -1626,11 +1642,15 @@ class Image:
|
||||||
|
|
||||||
:returns: A boolean.
|
:returns: A boolean.
|
||||||
"""
|
"""
|
||||||
return (
|
if (
|
||||||
self.mode in ("LA", "La", "PA", "RGBA", "RGBa")
|
self.mode in ("LA", "La", "PA", "RGBA", "RGBa")
|
||||||
or (self.mode == "P" and self.palette.mode.endswith("A"))
|
|
||||||
or "transparency" in self.info
|
or "transparency" in self.info
|
||||||
)
|
):
|
||||||
|
return True
|
||||||
|
if self.mode == "P":
|
||||||
|
assert self.palette is not None
|
||||||
|
return self.palette.mode.endswith("A")
|
||||||
|
return False
|
||||||
|
|
||||||
def apply_transparency(self) -> None:
|
def apply_transparency(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -1813,26 +1833,30 @@ class Image:
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
box += (box[0] + size[0], box[1] + size[1])
|
box += (box[0] + size[0], box[1] + size[1])
|
||||||
|
|
||||||
|
source: core.ImagingCore | str | float | tuple[float, ...]
|
||||||
if isinstance(im, str):
|
if isinstance(im, str):
|
||||||
from . import ImageColor
|
from . import ImageColor
|
||||||
|
|
||||||
im = ImageColor.getcolor(im, self.mode)
|
source = ImageColor.getcolor(im, self.mode)
|
||||||
|
|
||||||
elif isImageType(im):
|
elif isImageType(im):
|
||||||
im.load()
|
im.load()
|
||||||
if self.mode != im.mode:
|
if self.mode != im.mode:
|
||||||
if self.mode != "RGB" or im.mode not in ("LA", "RGBA", "RGBa"):
|
if self.mode != "RGB" or im.mode not in ("LA", "RGBA", "RGBa"):
|
||||||
# should use an adapter for this!
|
# should use an adapter for this!
|
||||||
im = im.convert(self.mode)
|
im = im.convert(self.mode)
|
||||||
im = im.im
|
source = im.im
|
||||||
|
elif isinstance(im, tuple):
|
||||||
|
source = im
|
||||||
|
else:
|
||||||
|
source = cast(float, im)
|
||||||
|
|
||||||
self._ensure_mutable()
|
self._ensure_mutable()
|
||||||
|
|
||||||
if mask:
|
if mask:
|
||||||
mask.load()
|
mask.load()
|
||||||
self.im.paste(im, box, mask.im)
|
self.im.paste(source, box, mask.im)
|
||||||
else:
|
else:
|
||||||
self.im.paste(im, box)
|
self.im.paste(source, box)
|
||||||
|
|
||||||
def alpha_composite(
|
def alpha_composite(
|
||||||
self, im: Image, dest: Sequence[int] = (0, 0), source: Sequence[int] = (0, 0)
|
self, im: Image, dest: Sequence[int] = (0, 0), source: Sequence[int] = (0, 0)
|
||||||
|
@ -2109,7 +2133,8 @@ class Image:
|
||||||
if self.mode == "PA":
|
if self.mode == "PA":
|
||||||
alpha = value[3] if len(value) == 4 else 255
|
alpha = value[3] if len(value) == 4 else 255
|
||||||
value = value[:3]
|
value = value[:3]
|
||||||
palette_index = self.palette.getcolor(value, self)
|
assert self.palette is not None
|
||||||
|
palette_index = self.palette.getcolor(tuple(value), self)
|
||||||
value = (palette_index, alpha) if self.mode == "PA" else palette_index
|
value = (palette_index, alpha) if self.mode == "PA" else palette_index
|
||||||
return self.im.putpixel(xy, value)
|
return self.im.putpixel(xy, value)
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import math
|
import math
|
||||||
import numbers
|
|
||||||
import struct
|
import struct
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
|
@ -160,13 +159,13 @@ class ImageDraw:
|
||||||
if ink is not None:
|
if ink is not None:
|
||||||
if isinstance(ink, str):
|
if isinstance(ink, str):
|
||||||
ink = ImageColor.getcolor(ink, self.mode)
|
ink = ImageColor.getcolor(ink, self.mode)
|
||||||
if self.palette and not isinstance(ink, numbers.Number):
|
if self.palette and isinstance(ink, tuple):
|
||||||
ink = self.palette.getcolor(ink, self._image)
|
ink = self.palette.getcolor(ink, self._image)
|
||||||
result_ink = self.draw.draw_ink(ink)
|
result_ink = self.draw.draw_ink(ink)
|
||||||
if fill is not None:
|
if fill is not None:
|
||||||
if isinstance(fill, str):
|
if isinstance(fill, str):
|
||||||
fill = ImageColor.getcolor(fill, self.mode)
|
fill = ImageColor.getcolor(fill, self.mode)
|
||||||
if self.palette and not isinstance(fill, numbers.Number):
|
if self.palette and isinstance(fill, tuple):
|
||||||
fill = self.palette.getcolor(fill, self._image)
|
fill = self.palette.getcolor(fill, self._image)
|
||||||
result_fill = self.draw.draw_ink(fill)
|
result_fill = self.draw.draw_ink(fill)
|
||||||
return result_ink, result_fill
|
return result_ink, result_fill
|
||||||
|
|
|
@ -313,7 +313,7 @@ class ImageFile(Image.Image):
|
||||||
|
|
||||||
def load_prepare(self) -> None:
|
def load_prepare(self) -> None:
|
||||||
# create image memory if necessary
|
# create image memory if necessary
|
||||||
if not self.im or self.im.mode != self.mode or self.im.size != self.size:
|
if self._im is None or self.im.mode != self.mode or self.im.size != self.size:
|
||||||
self.im = Image.core.new(self.mode, self.size)
|
self.im = Image.core.new(self.mode, self.size)
|
||||||
# create palette (optional)
|
# create palette (optional)
|
||||||
if self.mode == "P":
|
if self.mode == "P":
|
||||||
|
|
|
@ -127,10 +127,7 @@ class PhotoImage:
|
||||||
# palette mapped data
|
# palette mapped data
|
||||||
image.apply_transparency()
|
image.apply_transparency()
|
||||||
image.load()
|
image.load()
|
||||||
try:
|
mode = image.palette.mode if image.palette else "RGB"
|
||||||
mode = image.palette.mode
|
|
||||||
except AttributeError:
|
|
||||||
mode = "RGB" # default
|
|
||||||
size = image.size
|
size = image.size
|
||||||
kw["width"], kw["height"] = size
|
kw["width"], kw["height"] = size
|
||||||
|
|
||||||
|
|
|
@ -199,9 +199,13 @@ def getiptcinfo(
|
||||||
|
|
||||||
data = None
|
data = None
|
||||||
|
|
||||||
|
info: dict[tuple[int, int], bytes | list[bytes]] = {}
|
||||||
if isinstance(im, IptcImageFile):
|
if isinstance(im, IptcImageFile):
|
||||||
# return info dictionary right away
|
# return info dictionary right away
|
||||||
return im.info
|
for k, v in im.info.items():
|
||||||
|
if isinstance(k, tuple):
|
||||||
|
info[k] = v
|
||||||
|
return info
|
||||||
|
|
||||||
elif isinstance(im, JpegImagePlugin.JpegImageFile):
|
elif isinstance(im, JpegImagePlugin.JpegImageFile):
|
||||||
# extract the IPTC/NAA resource
|
# extract the IPTC/NAA resource
|
||||||
|
@ -237,4 +241,7 @@ def getiptcinfo(
|
||||||
except (IndexError, KeyError):
|
except (IndexError, KeyError):
|
||||||
pass # expected failure
|
pass # expected failure
|
||||||
|
|
||||||
return iptc_im.info
|
for k, v in iptc_im.info.items():
|
||||||
|
if isinstance(k, tuple):
|
||||||
|
info[k] = v
|
||||||
|
return info
|
||||||
|
|
|
@ -173,6 +173,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
|
|
||||||
flags = 0
|
flags = 0
|
||||||
if im.mode == "P" and "custom-colormap" in im.info:
|
if im.mode == "P" and "custom-colormap" in im.info:
|
||||||
|
assert im.palette is not None
|
||||||
flags = flags & _FLAGS["custom-colormap"]
|
flags = flags & _FLAGS["custom-colormap"]
|
||||||
colormapsize = 4 * 256 + 2
|
colormapsize = 4 * 256 + 2
|
||||||
colormapmode = im.palette.mode
|
colormapmode = im.palette.mode
|
||||||
|
|
|
@ -52,8 +52,6 @@ class PcdImageFile(ImageFile.ImageFile):
|
||||||
def load_end(self) -> None:
|
def load_end(self) -> None:
|
||||||
if self.tile_post_rotate:
|
if self.tile_post_rotate:
|
||||||
# Handle rotated PCDs
|
# Handle rotated PCDs
|
||||||
assert self.im is not None
|
|
||||||
|
|
||||||
self.im = self.im.rotate(self.tile_post_rotate)
|
self.im = self.im.rotate(self.tile_post_rotate)
|
||||||
self._size = self.im.size
|
self._size = self.im.size
|
||||||
|
|
||||||
|
|
|
@ -204,8 +204,6 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
|
|
||||||
if im.mode == "P":
|
if im.mode == "P":
|
||||||
# colour palette
|
# colour palette
|
||||||
assert im.im is not None
|
|
||||||
|
|
||||||
fp.write(o8(12))
|
fp.write(o8(12))
|
||||||
palette = im.im.getpalette("RGB", "RGB")
|
palette = im.im.getpalette("RGB", "RGB")
|
||||||
palette += b"\x00" * (768 - len(palette))
|
palette += b"\x00" * (768 - len(palette))
|
||||||
|
|
|
@ -863,12 +863,13 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
assert self.png is not None
|
assert self.png is not None
|
||||||
|
|
||||||
self.dispose: _imaging.ImagingCore | None
|
self.dispose: _imaging.ImagingCore | None
|
||||||
|
dispose_extent = None
|
||||||
if frame == 0:
|
if frame == 0:
|
||||||
if rewind:
|
if rewind:
|
||||||
self._fp.seek(self.__rewind)
|
self._fp.seek(self.__rewind)
|
||||||
self.png.rewind()
|
self.png.rewind()
|
||||||
self.__prepare_idat = self.__rewind_idat
|
self.__prepare_idat = self.__rewind_idat
|
||||||
self.im = None
|
self._im = None
|
||||||
self.info = self.png.im_info
|
self.info = self.png.im_info
|
||||||
self.tile = self.png.im_tile
|
self.tile = self.png.im_tile
|
||||||
self.fp = self._fp
|
self.fp = self._fp
|
||||||
|
@ -877,7 +878,7 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
self.default_image = self.info.get("default_image", False)
|
self.default_image = self.info.get("default_image", False)
|
||||||
self.dispose_op = self.info.get("disposal")
|
self.dispose_op = self.info.get("disposal")
|
||||||
self.blend_op = self.info.get("blend")
|
self.blend_op = self.info.get("blend")
|
||||||
self.dispose_extent = self.info.get("bbox")
|
dispose_extent = self.info.get("bbox")
|
||||||
self.__frame = 0
|
self.__frame = 0
|
||||||
else:
|
else:
|
||||||
if frame != self.__frame + 1:
|
if frame != self.__frame + 1:
|
||||||
|
@ -935,11 +936,13 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
self.tile = self.png.im_tile
|
self.tile = self.png.im_tile
|
||||||
self.dispose_op = self.info.get("disposal")
|
self.dispose_op = self.info.get("disposal")
|
||||||
self.blend_op = self.info.get("blend")
|
self.blend_op = self.info.get("blend")
|
||||||
self.dispose_extent = self.info.get("bbox")
|
dispose_extent = self.info.get("bbox")
|
||||||
|
|
||||||
if not self.tile:
|
if not self.tile:
|
||||||
msg = "image not found in APNG frame"
|
msg = "image not found in APNG frame"
|
||||||
raise EOFError(msg)
|
raise EOFError(msg)
|
||||||
|
if dispose_extent:
|
||||||
|
self.dispose_extent: tuple[float, float, float, float] = dispose_extent
|
||||||
|
|
||||||
# setup frame disposal (actual disposal done when needed in the next _seek())
|
# setup frame disposal (actual disposal done when needed in the next _seek())
|
||||||
if self._prev_im is None and self.dispose_op == Disposal.OP_PREVIOUS:
|
if self._prev_im is None and self.dispose_op == Disposal.OP_PREVIOUS:
|
||||||
|
|
|
@ -26,7 +26,7 @@ class QoiImageFile(ImageFile.ImageFile):
|
||||||
msg = "not a QOI file"
|
msg = "not a QOI file"
|
||||||
raise SyntaxError(msg)
|
raise SyntaxError(msg)
|
||||||
|
|
||||||
self._size = tuple(i32(self.fp.read(4)) for i in range(2))
|
self._size = i32(self.fp.read(4)), i32(self.fp.read(4))
|
||||||
|
|
||||||
channels = self.fp.read(1)[0]
|
channels = self.fp.read(1)[0]
|
||||||
self._mode = "RGB" if channels == 3 else "RGBA"
|
self._mode = "RGB" if channels == 3 else "RGBA"
|
||||||
|
|
|
@ -158,7 +158,6 @@ class TgaImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
def load_end(self) -> None:
|
def load_end(self) -> None:
|
||||||
if self._flip_horizontally:
|
if self._flip_horizontally:
|
||||||
assert self.im is not None
|
|
||||||
self.im = self.im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
|
self.im = self.im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
|
||||||
|
|
||||||
|
|
||||||
|
@ -200,7 +199,6 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
warnings.warn("id_section has been trimmed to 255 characters")
|
warnings.warn("id_section has been trimmed to 255 characters")
|
||||||
|
|
||||||
if colormaptype:
|
if colormaptype:
|
||||||
assert im.im is not None
|
|
||||||
palette = im.im.getpalette("RGB", "BGR")
|
palette = im.im.getpalette("RGB", "BGR")
|
||||||
colormaplength, colormapentry = len(palette) // 3, 24
|
colormaplength, colormapentry = len(palette) // 3, 24
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -54,7 +54,7 @@ class WalImageFile(ImageFile.ImageFile):
|
||||||
self.info["next_name"] = next_name
|
self.info["next_name"] = next_name
|
||||||
|
|
||||||
def load(self) -> Image.core.PixelAccess | None:
|
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.im = Image.core.new(self.mode, self.size)
|
||||||
self.frombytes(self.fp.read(self.size[0] * self.size[1]))
|
self.frombytes(self.fp.read(self.size[0] * self.size[1]))
|
||||||
self.putpalette(quake2palette)
|
self.putpalette(quake2palette)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user