pythoniconspython-imaging-libraryico

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


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.


Solution

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

    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