| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | import warnings |
| | from io import BytesIO |
| | from math import ceil, log |
| |
|
| | from . import BmpImagePlugin, Image, ImageFile, PngImagePlugin |
| | from ._binary import i16le as i16 |
| | from ._binary import i32le as i32 |
| | from ._binary import o8 |
| | from ._binary import o16le as o16 |
| | from ._binary import o32le as o32 |
| |
|
| | |
| | |
| |
|
| | _MAGIC = b"\0\0\1\0" |
| |
|
| |
|
| | def _save(im, fp, filename): |
| | fp.write(_MAGIC) |
| | bmp = im.encoderinfo.get("bitmap_format") == "bmp" |
| | sizes = im.encoderinfo.get( |
| | "sizes", |
| | [(16, 16), (24, 24), (32, 32), (48, 48), (64, 64), (128, 128), (256, 256)], |
| | ) |
| | frames = [] |
| | provided_ims = [im] + im.encoderinfo.get("append_images", []) |
| | width, height = im.size |
| | for size in sorted(set(sizes)): |
| | if size[0] > width or size[1] > height or size[0] > 256 or size[1] > 256: |
| | continue |
| |
|
| | for provided_im in provided_ims: |
| | if provided_im.size != size: |
| | continue |
| | frames.append(provided_im) |
| | if bmp: |
| | bits = BmpImagePlugin.SAVE[provided_im.mode][1] |
| | bits_used = [bits] |
| | for other_im in provided_ims: |
| | if other_im.size != size: |
| | continue |
| | bits = BmpImagePlugin.SAVE[other_im.mode][1] |
| | if bits not in bits_used: |
| | |
| | |
| | frames.append(other_im) |
| | bits_used.append(bits) |
| | break |
| | else: |
| | |
| | frame = provided_im.copy() |
| | frame.thumbnail(size, Image.Resampling.LANCZOS, reducing_gap=None) |
| | frames.append(frame) |
| | fp.write(o16(len(frames))) |
| | offset = fp.tell() + len(frames) * 16 |
| | for frame in frames: |
| | width, height = frame.size |
| | |
| | fp.write(o8(width if width < 256 else 0)) |
| | fp.write(o8(height if height < 256 else 0)) |
| |
|
| | bits, colors = BmpImagePlugin.SAVE[frame.mode][1:] if bmp else (32, 0) |
| | fp.write(o8(colors)) |
| | fp.write(b"\0") |
| | fp.write(b"\0\0") |
| | fp.write(o16(bits)) |
| |
|
| | image_io = BytesIO() |
| | if bmp: |
| | frame.save(image_io, "dib") |
| |
|
| | if bits != 32: |
| | and_mask = Image.new("1", size) |
| | ImageFile._save( |
| | and_mask, image_io, [("raw", (0, 0) + size, 0, ("1", 0, -1))] |
| | ) |
| | else: |
| | frame.save(image_io, "png") |
| | image_io.seek(0) |
| | image_bytes = image_io.read() |
| | if bmp: |
| | image_bytes = image_bytes[:8] + o32(height * 2) + image_bytes[12:] |
| | bytes_len = len(image_bytes) |
| | fp.write(o32(bytes_len)) |
| | fp.write(o32(offset)) |
| | current = fp.tell() |
| | fp.seek(offset) |
| | fp.write(image_bytes) |
| | offset = offset + bytes_len |
| | fp.seek(current) |
| |
|
| |
|
| | def _accept(prefix): |
| | return prefix[:4] == _MAGIC |
| |
|
| |
|
| | class IcoFile: |
| | def __init__(self, buf): |
| | """ |
| | Parse image from file-like object containing ico file data |
| | """ |
| |
|
| | |
| | s = buf.read(6) |
| | if not _accept(s): |
| | raise SyntaxError("not an ICO file") |
| |
|
| | self.buf = buf |
| | self.entry = [] |
| |
|
| | |
| | self.nb_items = i16(s, 4) |
| |
|
| | |
| | for i in range(self.nb_items): |
| | s = buf.read(16) |
| |
|
| | icon_header = { |
| | "width": s[0], |
| | "height": s[1], |
| | "nb_color": s[2], |
| | "reserved": s[3], |
| | "planes": i16(s, 4), |
| | "bpp": i16(s, 6), |
| | "size": i32(s, 8), |
| | "offset": i32(s, 12), |
| | } |
| |
|
| | |
| | for j in ("width", "height"): |
| | if not icon_header[j]: |
| | icon_header[j] = 256 |
| |
|
| | |
| | |
| | icon_header["color_depth"] = ( |
| | icon_header["bpp"] |
| | or ( |
| | icon_header["nb_color"] != 0 |
| | and ceil(log(icon_header["nb_color"], 2)) |
| | ) |
| | or 256 |
| | ) |
| |
|
| | icon_header["dim"] = (icon_header["width"], icon_header["height"]) |
| | icon_header["square"] = icon_header["width"] * icon_header["height"] |
| |
|
| | self.entry.append(icon_header) |
| |
|
| | self.entry = sorted(self.entry, key=lambda x: x["color_depth"]) |
| | |
| | |
| | self.entry = sorted(self.entry, key=lambda x: x["square"]) |
| | self.entry.reverse() |
| |
|
| | def sizes(self): |
| | """ |
| | Get a list of all available icon sizes and color depths. |
| | """ |
| | return {(h["width"], h["height"]) for h in self.entry} |
| |
|
| | def getentryindex(self, size, bpp=False): |
| | for (i, h) in enumerate(self.entry): |
| | if size == h["dim"] and (bpp is False or bpp == h["color_depth"]): |
| | return i |
| | return 0 |
| |
|
| | def getimage(self, size, bpp=False): |
| | """ |
| | Get an image from the icon |
| | """ |
| | return self.frame(self.getentryindex(size, bpp)) |
| |
|
| | def frame(self, idx): |
| | """ |
| | Get an image from frame idx |
| | """ |
| |
|
| | header = self.entry[idx] |
| |
|
| | self.buf.seek(header["offset"]) |
| | data = self.buf.read(8) |
| | self.buf.seek(header["offset"]) |
| |
|
| | if data[:8] == PngImagePlugin._MAGIC: |
| | |
| | im = PngImagePlugin.PngImageFile(self.buf) |
| | Image._decompression_bomb_check(im.size) |
| | else: |
| | |
| | im = BmpImagePlugin.DibImageFile(self.buf) |
| | Image._decompression_bomb_check(im.size) |
| |
|
| | |
| | im._size = (im.size[0], int(im.size[1] / 2)) |
| | d, e, o, a = im.tile[0] |
| | im.tile[0] = d, (0, 0) + im.size, o, a |
| |
|
| | |
| | bpp = header["bpp"] |
| | if 32 == bpp: |
| | |
| | |
| | |
| | |
| |
|
| | |
| | self.buf.seek(o) |
| | |
| | alpha_bytes = self.buf.read(im.size[0] * im.size[1] * 4)[3::4] |
| |
|
| | |
| | mask = Image.frombuffer( |
| | "L", |
| | im.size, |
| | alpha_bytes, |
| | "raw", |
| | ("L", 0, -1), |
| | ) |
| | else: |
| | |
| | w = im.size[0] |
| | if (w % 32) > 0: |
| | |
| | w += 32 - (im.size[0] % 32) |
| |
|
| | |
| | |
| |
|
| | total_bytes = int((w * im.size[1]) / 8) |
| | and_mask_offset = header["offset"] + header["size"] - total_bytes |
| |
|
| | self.buf.seek(and_mask_offset) |
| | mask_data = self.buf.read(total_bytes) |
| |
|
| | |
| | mask = Image.frombuffer( |
| | "1", |
| | im.size, |
| | mask_data, |
| | "raw", |
| | ("1;I", int(w / 8), -1), |
| | ) |
| |
|
| | |
| |
|
| | |
| | im = im.convert("RGBA") |
| | im.putalpha(mask) |
| |
|
| | return im |
| |
|
| |
|
| | |
| | |
| |
|
| |
|
| | class IcoImageFile(ImageFile.ImageFile): |
| | """ |
| | PIL read-only image support for Microsoft Windows .ico files. |
| | |
| | By default the largest resolution image in the file will be loaded. This |
| | can be changed by altering the 'size' attribute before calling 'load'. |
| | |
| | The info dictionary has a key 'sizes' that is a list of the sizes available |
| | in the icon file. |
| | |
| | Handles classic, XP and Vista icon formats. |
| | |
| | When saving, PNG compression is used. Support for this was only added in |
| | Windows Vista. If you are unable to view the icon in Windows, convert the |
| | image to "RGBA" mode before saving. |
| | |
| | This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis |
| | <casadebender@gmail.com>. |
| | https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki |
| | """ |
| |
|
| | format = "ICO" |
| | format_description = "Windows Icon" |
| |
|
| | def _open(self): |
| | self.ico = IcoFile(self.fp) |
| | self.info["sizes"] = self.ico.sizes() |
| | self.size = self.ico.entry[0]["dim"] |
| | self.load() |
| |
|
| | @property |
| | def size(self): |
| | return self._size |
| |
|
| | @size.setter |
| | def size(self, value): |
| | if value not in self.info["sizes"]: |
| | raise ValueError("This is not one of the allowed sizes of this image") |
| | self._size = value |
| |
|
| | def load(self): |
| | if self.im is not None and self.im.size == self.size: |
| | |
| | return Image.Image.load(self) |
| | im = self.ico.getimage(self.size) |
| | |
| | im.load() |
| | self.im = im.im |
| | self.mode = im.mode |
| | if im.size != self.size: |
| | warnings.warn("Image was not the expected size") |
| |
|
| | index = self.ico.getentryindex(self.size) |
| | sizes = list(self.info["sizes"]) |
| | sizes[index] = im.size |
| | self.info["sizes"] = set(sizes) |
| |
|
| | self.size = im.size |
| |
|
| | def load_seek(self): |
| | |
| | |
| | pass |
| |
|
| |
|
| | |
| | |
| |
|
| |
|
| | Image.register_open(IcoImageFile.format, IcoImageFile, _accept) |
| | Image.register_save(IcoImageFile.format, _save) |
| | Image.register_extension(IcoImageFile.format, ".ico") |
| |
|
| | Image.register_mime(IcoImageFile.format, "image/x-icon") |
| |
|