Valer
Valer

Reputation: 93

How can I save the 32x32 icon from a .ico file that has the highest color depth possible as a .png using Pillow?

I'm trying to extract and save the 32x32 icon from a .ico file that contains multiple icons with multiple sizes and color depths using Pillow. The 32x32 icon is available in the following color depths: 32-bit, 8-bit and 4-bit.

I tried opening the icon file using Image.open(), then set its size to 32x32, and then save it as a .png file. I expect to get the icon with the highest color depth possible, but I'm getting the one with the lowest color depth possible.

Here's a minimal, reproducible example:

from PIL import Image

icon = Image.open("icon.ico")
icon.size = (32, 32)
icon.save("icon.png")

I'm expecting to get this: The 32x32 icon with the 32-bit color depth

But, I'm getting this: The 32x32 icon with the 4-bit color depth

You can get the .ico file here: https://www.mediafire.com/file/uls693wvjn3njqa/icon.ico/file

I also tried looking for similar questions on the internet, but I didn't find anything.

Is there a way I can tell Pillow to extract the 32x32 icon from a .ico file at the highest color depth possible without modifying my .ico file to exclude icons with lower color depths?

Upvotes: 3

Views: 85

Answers (2)

jsbueno
jsbueno

Reputation: 110591

WHile an ICO image loaded in PIL provide access to the individual images through the img.ico.frame call, there is one problem: upon loading, PIL will convert all loaded individual frames which are indexed into the RGBA format.

Fortunately, it will preserve a bpp attribute listing the original bitcount for each frame in a headers list stored in the img.ico.entry attribute.

Which means a small function which iterates the frame headers can find the needed information:

from PIL import Image

def get_max_bpp(ico, size=(32,32)):
    frames = [(index, header, ico.ico.frame(index))
        for index, header in enumerate(icon.ico.entry) 
        if header.width==size[0] and header.height == size[1]
    ] 
    frames.sort(key=lambda frame_info: frame_info[1].bpp, reverse=True)
    return frames[0][2]


icon = Image.open("icon.ico")

max_32 = get_max_bpp(icon)
max_32.save("icon.png")

So, a straight look at the .mode attribute of each frame won't help - all will show up as RGBA (at least for an ICO file which contains at least one 24 or 32bit frame. I had not tested with a file with indexed-only frames).

Upvotes: 3

Mark Setchell
Mark Setchell

Reputation: 207778

Your file contains your icon at 13 different sizes. You can tell that with ImageMagick like this:

magick identify icon.ico
icon.ico[0] ICO 32x32 32x32+0+0 4-bit sRGB 0.000u 0:00.003
icon.ico[1] ICO 16x16 16x16+0+0 4-bit sRGB 0.000u 0:00.002
icon.ico[2] ICO 48x48 48x48+0+0 8-bit sRGB 0.000u 0:00.002
icon.ico[3] ICO 32x32 32x32+0+0 8-bit sRGB 0.000u 0:00.002
icon.ico[4] ICO 16x16 16x16+0+0 8-bit sRGB 0.000u 0:00.002
icon.ico[5] PNG 256x256 256x256+0+0 8-bit sRGB 13527B 0.000u 0:00.002
icon.ico[6] ICO 64x64 64x64+0+0 8-bit sRGB 0.000u 0:00.002
icon.ico[7] ICO 48x48 48x48+0+0 8-bit sRGB 0.000u 0:00.001
icon.ico[8] ICO 40x40 40x40+0+0 8-bit sRGB 0.000u 0:00.001
icon.ico[9] ICO 32x32 32x32+0+0 8-bit sRGB 0.000u 0:00.000
icon.ico[10] ICO 24x24 24x24+0+0 8-bit sRGB 0.000u 0:00.000
icon.ico[11] ICO 20x20 20x20+0+0 8-bit sRGB 0.000u 0:00.000
icon.ico[12] ICO 16x16 16x16+0+0 8-bit sRGB 65021B 0.000u 0:00.000

When you open it with Pillow, you will get the highest (spatial) resolution, i.e. the 256x256 version.

If you want it at a different size, you will need to resize it:

from PIL import Image

im = Image.open('icon.ico')

# Check what Mark said is true
print(im)
# <PIL.IcoImagePlugin.IcoImageFile image mode=RGBA size=256x256 at 0x133D9AB10>

# Make 32x32 version
im32x32 = im.resize((32,32))

Note that you should use NEAREST NEIGHBOUR resampling when resizing if you do not wish to introduce new colours into your palette.

im32x32 = im.resize((32,32), Image.Resampling.NEAREST)

If you would like ImageMagick to tell you the scene/frame number, dimensions and number of unique colours in each icon in your file, use:

identify -format "%s %G %k\n" icon.ico 
0 32x32 10
1 16x16 10
2 48x48 253
3 32x32 190
4 16x16 115
5 256x256 1849
6 64x64 696
7 48x48 538
8 40x40 483
9 32x32 382
10 24x24 262
11 20x20 226
12 16x16 171

You can then extract icon 5 and save as a PNG with:

magick icon.ico[5] MostColours.png

Note that all ImageMagick operations I demonstrate above can be done equally with wand which is a Python binding to ImageMagick.

Upvotes: 2

Related Questions