Native extraction of ICO

This commit is contained in:
hyugogirubato
2024-07-17 23:54:21 +02:00
parent eec284a3ef
commit 7fceb86a7b
+12 -48
View File
@@ -1,56 +1,11 @@
import struct
from enum import Enum from enum import Enum
from io import BytesIO from io import BytesIO
from PIL import Image from PIL import Image
from PIL.IcoImagePlugin import IcoFile
from path import Path from path import Path
def extract_ico(path: Path) -> list[Image]:
"""
Extracts all the images from an ICO file.
Args:
path (Path): Path to the ICO file.
Returns:
list[Image]: List of PIL Image objects representing the icons in the ICO file.
"""
# https://stackoverflow.com/questions/74341558/how-do-i-read-a-different-size-of-an-icon-file
# https://en.wikipedia.org/wiki/ICO_(file_format)
icons = []
# Constants for ICO file structure
ICONDIRSIZE = 6
ICONDIRENTRYSIZE = 16
# Read the entire ICO file content
d = path.read_bytes()
# Extract ICONDIR header
hdrReserved, hdrImageType, hdrNumImages = struct.unpack('<HHH', d[:ICONDIRSIZE])
# Process each image entry in the ICO file
for i in range(hdrNumImages):
start = ICONDIRSIZE + i * ICONDIRENTRYSIZE
ICONDIRENTRY = d[start:start + ICONDIRENTRYSIZE]
# Unpack ICONDIRENTRY fields
width, height, palColours, reserved, planes, bpp, nBytes, offset = struct.unpack('<BBBBHHII', ICONDIRENTRY)
# Create a new in-memory ICO file with one icon in it
hdr = struct.pack('<HHH', hdrReserved, hdrImageType, 1)
dirent = struct.pack('<BBBBHHII', width, height, palColours, reserved, planes, bpp, nBytes, ICONDIRSIZE + ICONDIRENTRYSIZE)
pxData = d[offset:offset + nBytes]
inMemoryICO = BytesIO(hdr + dirent + pxData)
# Open the image using PIL
im = Image.open(inMemoryICO)
icons.append(im)
return icons
def default_attr(value: any) -> any: def default_attr(value: any) -> any:
""" """
Provides a default value based on the expected type of an attribute. Provides a default value based on the expected type of an attribute.
@@ -139,7 +94,16 @@ def ico_to_png(path: Path) -> BytesIO:
Returns: Returns:
BytesIO: In-memory PNG file of the largest icon. BytesIO: In-memory PNG file of the largest icon.
""" """
icons = extract_ico(path) # Open the ICO file and read its content
image = Image.open(BytesIO(path.read_bytes()))
assert image.format == 'ICO', 'Unsupported image format'
# Get the ICO file object and find the largest icon size
icon: IcoFile = image.ico
max_size = max(icon.sizes(), key=lambda x: x[0] + x[1])
largest_image = icon.getimage(size=max_size)
# Save the largest icon as PNG format to an in-memory BytesIO object
content = BytesIO() content = BytesIO()
icons[-1].save(content, format='PNG') largest_image.save(content, format='PNG')
return content return content