How do I read a different size of an icon file?

345 Views Asked by At

I have a folder of icon files (ico) that I want to convert to PNG. Each icon has two sizes, each size is a different image. To be clear, the two sizes look different and are not just the same icon at different resolutions. I want to save the smaller version of each icon.

The PIL documentation states:

ICO is used to store icons on Windows. The largest available icon is read.

How do I get PIL to access the smaller version of the icon and not the largest?

I've searched and I can find lots of ways to save the different sizes of an icon but not how to read / open the smaller versions of an icon. I did find one similar question but the answers did not work for me.

Edit:
Here is an example icon. The larger version has a folder in the background and the smaller version is just the bolt. I want to export only the small version.

1

There are 1 best solutions below

2
Mark Setchell On BEST ANSWER

I had a try at this and wrote some code to:

  • open an ICO file
  • parse out the contained icons
  • create an in-memory ICO file for each one individually
  • open that with PIL and append the resulting PIL Image to a list

Code is as follows - there's a link describing the ICO format on Wikipedia here:

#!/usr/bin/env python3
# Mark Setchell
# https://en.wikipedia.org/wiki/ICO_(file_format)

import io
import struct
from PIL import Image

ICONDIRSIZE, ICONDIRENTRYSIZE = 6, 16

def extractIcons(filename):
    """Extract all icons from .ICO file, returning a list of PIL Images"""

    # List of PIL Images to return
    res = []

    # Open and slurp entire file
    d = open(filename, 'rb').read()

    # Extract ICONDIR, and unpack
    ICONDIR = d[:ICONDIRSIZE]
    hdrReserved, hdrImageType, hdrNumImages = struct.unpack('<HHH', ICONDIR)
    print(f'DEBUG: hdrReserved={hdrReserved}, hdrImageType={hdrImageType}, hdrNumImages={hdrNumImages}')

    for i in range(hdrNumImages):
       start = ICONDIRSIZE + i*ICONDIRENTRYSIZE
       ICONDIRENTRY = d[start:start+ICONDIRENTRYSIZE]
       width, height, palColours, reserved, planes, bpp, nBytes, offset = struct.unpack('<BBBBHHII', ICONDIRENTRY)
       print(f'DEBUG: Entry:{i}, width={width}, height={height}, palColours={palColours}, reserved={reserved}, planes={planes}, bpp={bpp}, nBytes={nBytes}, offset={offset}')
       # Make a new, in-memory ICO file with one single 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 = io.BytesIO(hdr + dirent +pxData)
       im = Image.open(inMemoryICO)
       res.append(im)
    return res

for i, icon in enumerate(extractIcons('example.ico')):
    icon.save(f'icon-{i}.png')

For your file it prints:

DEBUG: hdrReserved=0, hdrImageType=1, hdrNumImages=2
DEBUG: Entry:0, width=60, height=45, palColours=0, reserved=0, planes=1, bpp=32, nBytes=11200, offset=38
DEBUG: Entry:1, width=16, height=16, palColours=0, reserved=0, planes=1, bpp=32, nBytes=1128, offset=11238

And extracts:

-rw-r--r--     1 mark  staff        675  8 Nov 11:00 icon-0.png
-rw-r--r--     1 mark  staff        366  8 Nov 11:00 icon-1.png

enter image description here enter image description here