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:
But, I'm getting this:
You can get the .ico file here: .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?
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:
But, I'm getting this:
You can get the .ico file here: https://www.mediafire/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?
Share Improve this question edited Feb 17 at 16:12 Valer asked Feb 16 at 23:30 ValerValer 731 silver badge6 bronze badges 2- please provide us with the .ico image. – Mohammed Almalki Commented Feb 16 at 23:36
- @MohammedAlmalki You can get the .ico file here: mediafire/file/uls693wvjn3njqa/icon.ico/file – Valer Commented Feb 17 at 16:13
2 Answers
Reset to default 3WHile 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).
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.