Lint fixes

This commit is contained in:
Andrew Murray 2025-04-26 15:47:35 +10:00
parent 4c94fa4d13
commit 26be2ee1e4
2 changed files with 74 additions and 57 deletions

View File

@ -2,6 +2,7 @@ from __future__ import annotations
import struct import struct
from io import BytesIO from io import BytesIO
from typing import IO, Any
from PIL import BmpImagePlugin, CurImagePlugin, Image, ImageFile from PIL import BmpImagePlugin, CurImagePlugin, Image, ImageFile
from PIL._binary import i32le as i32 from PIL._binary import i32le as i32
@ -10,11 +11,11 @@ from PIL._binary import o16le as o16
from PIL._binary import o32le as o32 from PIL._binary import o32le as o32
def _accept(s): def _accept(prefix: bytes) -> bool:
return s[:4] == b"RIFF" return prefix[:4] == b"RIFF"
def _save_frame(im: Image.Image, fp: BytesIO, filename: str, info: dict): def _save_frame(im: Image.Image, fp: BytesIO, info: dict[str, Any]) -> None:
fp.write(b"\0\0\2\0") fp.write(b"\0\0\2\0")
bmp = True bmp = True
s = info.get( s = info.get(
@ -64,7 +65,9 @@ def _save_frame(im: Image.Image, fp: BytesIO, filename: str, info: dict):
if bits != 32: if bits != 32:
and_mask = Image.new("1", size) and_mask = Image.new("1", size)
ImageFile._save( ImageFile._save(
and_mask, image_io, [("raw", (0, 0) + size, 0, ("1", 0, -1))] and_mask,
image_io,
[ImageFile._Tile("raw", (0, 0) + size, 0, ("1", 0, -1))],
) )
else: else:
frame.alpha = True frame.alpha = True
@ -87,7 +90,7 @@ def _save_frame(im: Image.Image, fp: BytesIO, filename: str, info: dict):
fp.seek(current) fp.seek(current)
def _write_single_frame(im: Image.Image, fp: BytesIO, filename: str): def _write_single_frame(im: Image.Image, fp: IO[bytes]) -> None:
fp.write(b"anih") fp.write(b"anih")
anih = o32(36) + o32(36) + (o32(1) * 2) + (o32(0) * 4) + o32(60) + o32(1) anih = o32(36) + o32(36) + (o32(1) * 2) + (o32(0) * 4) + o32(60) + o32(1)
fp.write(anih) fp.write(anih)
@ -99,7 +102,7 @@ def _write_single_frame(im: Image.Image, fp: BytesIO, filename: str):
fp.write(b"icon" + o32(0)) fp.write(b"icon" + o32(0))
icon_offset = fp.tell() icon_offset = fp.tell()
with BytesIO(b"") as icon_fp: with BytesIO(b"") as icon_fp:
_save_frame(im, icon_fp, filename, im.encoderinfo) _save_frame(im, icon_fp, im.encoderinfo)
icon_fp.seek(0) icon_fp.seek(0)
icon_data = icon_fp.read() icon_data = icon_fp.read()
@ -117,7 +120,7 @@ def _write_single_frame(im: Image.Image, fp: BytesIO, filename: str):
fp.seek(fram_end) fp.seek(fram_end)
def _write_multiple_frames(im: Image.Image, fp: BytesIO, filename: str): def _write_multiple_frames(im: Image.Image, fp: IO[bytes]) -> None:
anih_offset = fp.tell() anih_offset = fp.tell()
fp.write(b"anih" + o32(36)) fp.write(b"anih" + o32(36))
fp.write(o32(0) * 9) fp.write(o32(0) * 9)
@ -132,7 +135,7 @@ def _write_multiple_frames(im: Image.Image, fp: BytesIO, filename: str):
fp.write(b"icon" + o32(0)) fp.write(b"icon" + o32(0))
icon_offset = fp.tell() icon_offset = fp.tell()
with BytesIO(b"") as icon_fp: with BytesIO(b"") as icon_fp:
_save_frame(frame, icon_fp, filename, im.encoderinfo) _save_frame(frame, icon_fp, im.encoderinfo)
icon_fp.seek(0) icon_fp.seek(0)
icon_data = icon_fp.read() icon_data = icon_fp.read()
@ -216,7 +219,7 @@ def _write_multiple_frames(im: Image.Image, fp: BytesIO, filename: str):
fp.seek(fram_end) fp.seek(fram_end)
def _write_info(im: Image.Image, fp: BytesIO, filename: str): def _write_info(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
fp.write(b"LIST" + o32(0)) fp.write(b"LIST" + o32(0))
list_offset = fp.tell() list_offset = fp.tell()
@ -263,7 +266,7 @@ def _write_info(im: Image.Image, fp: BytesIO, filename: str):
fp.seek(info_end) fp.seek(info_end)
def _save(im: Image.Image, fp: BytesIO, filename: str): def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
fp.write(b"RIFF\x00\x00\x00\x00") fp.write(b"RIFF\x00\x00\x00\x00")
riff_offset = fp.tell() riff_offset = fp.tell()
@ -272,9 +275,9 @@ def _save(im: Image.Image, fp: BytesIO, filename: str):
frames = im.encoderinfo.get("append_images", []) frames = im.encoderinfo.get("append_images", [])
if frames: if frames:
_write_multiple_frames(im, fp, filename) _write_multiple_frames(im, fp)
else: else:
_write_single_frame(im, fp, filename) _write_single_frame(im, fp)
pass pass
riff_end = fp.tell() riff_end = fp.tell()
@ -352,7 +355,8 @@ class AniFile:
if self.rate is None: if self.rate is None:
self.rate = [self.anih["iDispRate"] for i in range(self.anih["nFrames"])] self.rate = [self.anih["iDispRate"] for i in range(self.anih["nFrames"])]
def frame(self, frame): def frame(self, frame: int) -> CurImagePlugin.CurImageFile:
assert self.anih is not None
if frame > self.anih["nFrames"]: if frame > self.anih["nFrames"]:
msg = "Frame index out of animation bounds" msg = "Frame index out of animation bounds"
raise EOFError(msg) raise EOFError(msg)
@ -361,13 +365,12 @@ class AniFile:
self.buf.seek(offset) self.buf.seek(offset)
data = self.buf.read(size) data = self.buf.read(size)
im = CurImagePlugin.CurImageFile(BytesIO(data)) return CurImagePlugin.CurImageFile(BytesIO(data))
return im
def sizes(self): def sizes(self) -> list[int]:
return [data["size"] for data in self.image_data] return [data["size"] for data in self.image_data]
def hotspots(self): def hotspots(self) -> None:
pass pass
@ -398,10 +401,12 @@ class AniImageFile(ImageFile.ImageFile):
format = "ANI" format = "ANI"
format_description = "Windows Animated Cursor" format_description = "Windows Animated Cursor"
def _open(self): def _open(self) -> None:
self.ani = AniFile(self.fp) self.ani = AniFile(self.fp)
self.info["seq"] = self.ani.seq self.info["seq"] = self.ani.seq
self.info["rate"] = self.ani.rate self.info["rate"] = self.ani.rate
assert self.ani.anih is not None
self.info["frames"] = self.ani.anih["nFrames"] self.info["frames"] = self.ani.anih["nFrames"]
self.frame = 0 self.frame = 0
@ -410,24 +415,24 @@ class AniImageFile(ImageFile.ImageFile):
self.size = self.im.size self.size = self.im.size
@property @property
def size(self): def size(self) -> tuple[int, int]:
return self._size return self._size
@size.setter @size.setter
def size(self, value): def size(self, value: tuple[int, int]) -> None:
if value not in self.info["sizes"]: if value not in self.info["sizes"]:
msg = "This is not one of the allowed sizes of this image" msg = "This is not one of the allowed sizes of this image"
raise ValueError(msg) raise ValueError(msg)
self._size = value self._size = value
def load(self): def load(self) -> None:
im = self.ani.frame(self.frame) im = self.ani.frame(self.frame)
self.info["sizes"] = im.info["sizes"] self.info["sizes"] = im.info["sizes"]
self.info["hotspots"] = im.info["hotspots"] self.info["hotspots"] = im.info["hotspots"]
self.im = im.im self.im = im.im
self._mode = im.mode self._mode = im.mode
def seek(self, frame): def seek(self, frame: int) -> None:
if frame > self.info["frames"] - 1 or frame < 0: if frame > self.info["frames"] - 1 or frame < 0:
msg = "Frame index out of animation bounds" msg = "Frame index out of animation bounds"
raise EOFError(msg) raise EOFError(msg)

View File

@ -18,6 +18,7 @@
from __future__ import annotations from __future__ import annotations
from io import BytesIO from io import BytesIO
from typing import IO, NamedTuple
from . import BmpImagePlugin, IcoImagePlugin, Image, ImageFile from . import BmpImagePlugin, IcoImagePlugin, Image, ImageFile
from ._binary import i16le as i16 from ._binary import i16le as i16
@ -32,7 +33,7 @@ from ._binary import o32le as o32
_MAGIC = b"\x00\x00\x02\x00" _MAGIC = b"\x00\x00\x02\x00"
def _save(im: Image.Image, fp: BytesIO, filename: str): def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
fp.write(_MAGIC) fp.write(_MAGIC)
bmp = im.encoderinfo.get("bitmap_format", "") == "bmp" bmp = im.encoderinfo.get("bitmap_format", "") == "bmp"
s = im.encoderinfo.get( s = im.encoderinfo.get(
@ -82,7 +83,9 @@ def _save(im: Image.Image, fp: BytesIO, filename: str):
if bits != 32: if bits != 32:
and_mask = Image.new("1", size) and_mask = Image.new("1", size)
ImageFile._save( ImageFile._save(
and_mask, image_io, [("raw", (0, 0) + size, 0, ("1", 0, -1))] and_mask,
image_io,
[ImageFile._Tile("raw", (0, 0) + size, 0, ("1", 0, -1))],
) )
else: else:
frame.alpha = True frame.alpha = True
@ -109,10 +112,24 @@ def _accept(prefix: bytes) -> bool:
return prefix.startswith(_MAGIC) return prefix.startswith(_MAGIC)
class IconHeader(NamedTuple):
width: int
height: int
nb_color: int
reserved: int
bpp: int
x_hotspot: int
y_hotspot: int
size: int
offset: int
dim: tuple[int, int]
square: int
## ##
# Image plugin for Windows Cursor files. # Image plugin for Windows Cursor files.
class CurFile(IcoImagePlugin.IcoFile): class CurFile(IcoImagePlugin.IcoFile):
def __init__(self, buf: BytesIO()): def __init__(self, buf: IO[bytes]) -> None:
""" """
Parse image from file-like object containing cur file data Parse image from file-like object containing cur file data
""" """
@ -133,24 +150,12 @@ class CurFile(IcoImagePlugin.IcoFile):
for _ in range(self.nb_items): for _ in range(self.nb_items):
s = buf.read(16) s = buf.read(16)
icon_header = {
"width": s[0],
"height": s[1],
"nb_color": s[2], # No. of colors in image (0 if >=8bpp)
"reserved": s[3],
"x_hotspot": i16(s, 4),
"y_hotspot": i16(s, 6),
"size": i32(s, 8),
"offset": i32(s, 12),
}
# See Wikipedia # See Wikipedia
for j in ("width", "height"): width = s[0] or 256
if not icon_header[j]: height = s[1] or 256
icon_header[j] = 256
icon_header["dim"] = (icon_header["width"], icon_header["height"]) size = i32(s, 8)
icon_header["square"] = icon_header["width"] * icon_header["height"] square = width * height
# TODO: This needs further investigation. Cursor files do not really # TODO: This needs further investigation. Cursor files do not really
# specify their bpp like ICO's as those bits are used for the y_hotspot. # specify their bpp like ICO's as those bits are used for the y_hotspot.
@ -158,28 +163,35 @@ class CurFile(IcoImagePlugin.IcoFile):
# of pixels * 1bpp) and dividing by the number of pixels. This seems # of pixels * 1bpp) and dividing by the number of pixels. This seems
# to work well so far. # to work well so far.
BITMAP_INFO_HEADER_SIZE = 40 BITMAP_INFO_HEADER_SIZE = 40
bpp_without_and = ( bpp_without_and = ((size - BITMAP_INFO_HEADER_SIZE) * 8) // square
(icon_header["size"] - BITMAP_INFO_HEADER_SIZE) * 8
) // icon_header["square"]
if bpp_without_and != 32: if bpp_without_and != 32:
icon_header["bpp"] = ( bpp = ((size - BITMAP_INFO_HEADER_SIZE) * 8 - square) // square
(icon_header["size"] - BITMAP_INFO_HEADER_SIZE) * 8
- icon_header["square"]
) // icon_header["square"]
else: else:
icon_header["bpp"] = bpp_without_and bpp = bpp_without_and
icon_header = IconHeader(
width=width,
height=height,
nb_color=s[2], # No. of colors in image (0 if >=8bpp)
reserved=s[3],
bpp=bpp,
x_hotspot=i16(s, 4),
y_hotspot=i16(s, 6),
size=size,
offset=i32(s, 12),
dim=(width, height),
square=square,
)
self.entry.append(icon_header) self.entry.append(icon_header)
self.entry = sorted(self.entry, key=lambda x: x["square"]) self.entry = sorted(self.entry, key=lambda x: x.square, reverse=True)
self.entry.reverse()
def sizes(self): def sizes(self) -> list[tuple[int, int]]:
return [(h["width"], h["height"]) for h in self.entry] return [(h.width, h.height) for h in self.entry]
def hotspots(self): def hotspots(self) -> list[tuple[int, int]]:
return [(h["x_hotspot"], h["y_hotspot"]) for h in self.entry] return [(h.x_hotspot, h.y_hotspot) for h in self.entry]
class CurImageFile(IcoImagePlugin.IcoImageFile): class CurImageFile(IcoImagePlugin.IcoImageFile):
@ -213,7 +225,7 @@ class CurImageFile(IcoImagePlugin.IcoImageFile):
self.info["sizes"] = self.ico.sizes() self.info["sizes"] = self.ico.sizes()
self.info["hotspots"] = self.ico.hotspots() self.info["hotspots"] = self.ico.hotspots()
if len(self.ico.entry) > 0: if len(self.ico.entry) > 0:
self.size = self.ico.entry[0]["dim"] self.size = self.ico.entry[0].dim
else: else:
msg = "No cursors were found" msg = "No cursors were found"
raise TypeError(msg) raise TypeError(msg)