about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/PIL/IcoImagePlugin.py
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/PIL/IcoImagePlugin.py
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-4a52a71956a8d46fcb7294ac71734504bb09bcc2.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/PIL/IcoImagePlugin.py')
-rw-r--r--.venv/lib/python3.12/site-packages/PIL/IcoImagePlugin.py381
1 files changed, 381 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/PIL/IcoImagePlugin.py b/.venv/lib/python3.12/site-packages/PIL/IcoImagePlugin.py
new file mode 100644
index 00000000..e879f180
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/PIL/IcoImagePlugin.py
@@ -0,0 +1,381 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# Windows Icon support for PIL
+#
+# History:
+#       96-05-27 fl     Created
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1996.
+#
+# See the README file for information on usage and redistribution.
+#
+
+# This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
+# <casadebender@gmail.com>.
+# https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki
+#
+# Icon format references:
+#   * https://en.wikipedia.org/wiki/ICO_(file_format)
+#   * https://msdn.microsoft.com/en-us/library/ms997538.aspx
+from __future__ import annotations
+
+import warnings
+from io import BytesIO
+from math import ceil, log
+from typing import IO, NamedTuple
+
+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: 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(
+        "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:
+                        # Another image has been supplied for this size
+                        # with a different bit depth
+                        frames.append(other_im)
+                        bits_used.append(bits)
+            break
+        else:
+            # TODO: invent a more convenient method for proportional scalings
+            frame = provided_im.copy()
+            frame.thumbnail(size, Image.Resampling.LANCZOS, reducing_gap=None)
+            frames.append(frame)
+    fp.write(o16(len(frames)))  # idCount(2)
+    offset = fp.tell() + len(frames) * 16
+    for frame in frames:
+        width, height = frame.size
+        # 0 means 256
+        fp.write(o8(width if width < 256 else 0))  # bWidth(1)
+        fp.write(o8(height if height < 256 else 0))  # bHeight(1)
+
+        bits, colors = BmpImagePlugin.SAVE[frame.mode][1:] if bmp else (32, 0)
+        fp.write(o8(colors))  # bColorCount(1)
+        fp.write(b"\0")  # bReserved(1)
+        fp.write(b"\0\0")  # wPlanes(2)
+        fp.write(o16(bits))  # wBitCount(2)
+
+        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,
+                    [ImageFile._Tile("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))  # dwBytesInRes(4)
+        fp.write(o32(offset))  # dwImageOffset(4)
+        current = fp.tell()
+        fp.seek(offset)
+        fp.write(image_bytes)
+        offset = offset + bytes_len
+        fp.seek(current)
+
+
+def _accept(prefix: bytes) -> bool:
+    return prefix[:4] == _MAGIC
+
+
+class IconHeader(NamedTuple):
+    width: int
+    height: int
+    nb_color: int
+    reserved: int
+    planes: int
+    bpp: int
+    size: int
+    offset: int
+    dim: tuple[int, int]
+    square: int
+    color_depth: int
+
+
+class IcoFile:
+    def __init__(self, buf: IO[bytes]) -> None:
+        """
+        Parse image from file-like object containing ico file data
+        """
+
+        # check magic
+        s = buf.read(6)
+        if not _accept(s):
+            msg = "not an ICO file"
+            raise SyntaxError(msg)
+
+        self.buf = buf
+        self.entry = []
+
+        # Number of items in file
+        self.nb_items = i16(s, 4)
+
+        # Get headers for each item
+        for i in range(self.nb_items):
+            s = buf.read(16)
+
+            # See Wikipedia
+            width = s[0] or 256
+            height = s[1] or 256
+
+            # No. of colors in image (0 if >=8bpp)
+            nb_color = s[2]
+            bpp = i16(s, 6)
+            icon_header = IconHeader(
+                width=width,
+                height=height,
+                nb_color=nb_color,
+                reserved=s[3],
+                planes=i16(s, 4),
+                bpp=i16(s, 6),
+                size=i32(s, 8),
+                offset=i32(s, 12),
+                dim=(width, height),
+                square=width * height,
+                # See Wikipedia notes about color depth.
+                # We need this just to differ images with equal sizes
+                color_depth=bpp or (nb_color != 0 and ceil(log(nb_color, 2))) or 256,
+            )
+
+            self.entry.append(icon_header)
+
+        self.entry = sorted(self.entry, key=lambda x: x.color_depth)
+        # ICO images are usually squares
+        self.entry = sorted(self.entry, key=lambda x: x.square, reverse=True)
+
+    def sizes(self) -> set[tuple[int, int]]:
+        """
+        Get a set of all available icon sizes and color depths.
+        """
+        return {(h.width, h.height) for h in self.entry}
+
+    def getentryindex(self, size: tuple[int, int], bpp: int | bool = False) -> int:
+        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: tuple[int, int], bpp: int | bool = False) -> Image.Image:
+        """
+        Get an image from the icon
+        """
+        return self.frame(self.getentryindex(size, bpp))
+
+    def frame(self, idx: int) -> Image.Image:
+        """
+        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)
+
+        im: Image.Image
+        if data[:8] == PngImagePlugin._MAGIC:
+            # png frame
+            im = PngImagePlugin.PngImageFile(self.buf)
+            Image._decompression_bomb_check(im.size)
+        else:
+            # XOR + AND mask bmp frame
+            im = BmpImagePlugin.DibImageFile(self.buf)
+            Image._decompression_bomb_check(im.size)
+
+            # change tile dimension to only encompass XOR image
+            im._size = (im.size[0], int(im.size[1] / 2))
+            d, e, o, a = im.tile[0]
+            im.tile[0] = ImageFile._Tile(d, (0, 0) + im.size, o, a)
+
+            # figure out where AND mask image starts
+            if header.bpp == 32:
+                # 32-bit color depth icon image allows semitransparent areas
+                # PIL's DIB format ignores transparency bits, recover them.
+                # The DIB is packed in BGRX byte order where X is the alpha
+                # channel.
+
+                # Back up to start of bmp data
+                self.buf.seek(o)
+                # extract every 4th byte (eg. 3,7,11,15,...)
+                alpha_bytes = self.buf.read(im.size[0] * im.size[1] * 4)[3::4]
+
+                # convert to an 8bpp grayscale image
+                try:
+                    mask = Image.frombuffer(
+                        "L",  # 8bpp
+                        im.size,  # (w, h)
+                        alpha_bytes,  # source chars
+                        "raw",  # raw decoder
+                        ("L", 0, -1),  # 8bpp inverted, unpadded, reversed
+                    )
+                except ValueError:
+                    if ImageFile.LOAD_TRUNCATED_IMAGES:
+                        mask = None
+                    else:
+                        raise
+            else:
+                # get AND image from end of bitmap
+                w = im.size[0]
+                if (w % 32) > 0:
+                    # bitmap row data is aligned to word boundaries
+                    w += 32 - (im.size[0] % 32)
+
+                # the total mask data is
+                # padded row size * height / bits per char
+
+                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)
+
+                # convert raw data to image
+                try:
+                    mask = Image.frombuffer(
+                        "1",  # 1 bpp
+                        im.size,  # (w, h)
+                        mask_data,  # source chars
+                        "raw",  # raw decoder
+                        ("1;I", int(w / 8), -1),  # 1bpp inverted, padded, reversed
+                    )
+                except ValueError:
+                    if ImageFile.LOAD_TRUNCATED_IMAGES:
+                        mask = None
+                    else:
+                        raise
+
+                # now we have two images, im is XOR image and mask is AND image
+
+            # apply mask image as alpha channel
+            if mask:
+                im = im.convert("RGBA")
+                im.putalpha(mask)
+
+        return im
+
+
+##
+# Image plugin for Windows Icon files.
+
+
+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) -> None:
+        self.ico = IcoFile(self.fp)
+        self.info["sizes"] = self.ico.sizes()
+        self.size = self.ico.entry[0].dim
+        self.load()
+
+    @property
+    def size(self) -> tuple[int, int]:
+        return self._size
+
+    @size.setter
+    def size(self, value: tuple[int, int]) -> None:
+        if value not in self.info["sizes"]:
+            msg = "This is not one of the allowed sizes of this image"
+            raise ValueError(msg)
+        self._size = value
+
+    def load(self) -> Image.core.PixelAccess | None:
+        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)
+        # if tile is PNG, it won't really be loaded yet
+        im.load()
+        self.im = im.im
+        self._mode = im.mode
+        if im.palette:
+            self.palette = im.palette
+        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
+        return None
+
+    def load_seek(self, pos: int) -> None:
+        # Flag the ImageFile.Parser so that it
+        # just does all the decode at the end.
+        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")