Convert ICO to PNG
Convert an ICO to PNG by saving the image layer you need: Pillow and FFmpeg both preserved the default layer in these tests, while an explicit Pillow selection exported the smaller embedded image without resizing it.
Measured November 2, 2026 on Windows with Python 3.12.14, Pillow 12.3.0 and FFmpeg N-126374-g089a48eb36-20260831. These are local command-line conversions of three deliberately constructed ICO files, not tests of online converters or browsers.
Compare the saved outputsWhat the saved files showed
For the ascending-order ICO, Pillow default export produced a 621-byte PNG and FFmpeg default export produced a 902-byte PNG. Both were 256 by 256 pixels. Their complete file checksums differed, but the decoded RGBA checksums matched. The larger download did not contain a different pixel image in this pair.
The descending-order ICO produced the same 621-byte Pillow output and the same 902-byte FFmpeg output as its ascending counterpart. Each route reproduced its own output checksum across the reversed pair. In these files, reversing the directory did not make either default route export the first small image instead of the large one.
The single-layer control changed the comparison: Pillow wrote 125 bytes and FFmpeg wrote 148 bytes, both at 32 by 32 pixels. Their PNG checksums still differed, while their decoded RGBA checksums were identical. This control separates a difference in saved PNG bytes from a difference in selected image dimensions.
Explicit Pillow selection returned a 125-byte, 32 by 32 PNG from each of the three inputs. All three selected outputs shared the same full PNG checksum. Against the default 256 by 256 outputs from the two multiresolution inputs, these selected results had a different center color because they came from a different embedded layer, not a downscale.
Every saved output
Scroll the table sideways on a narrow screen. Alpha range uses 0 for fully transparent and 255 for opaque. Pixel difference compares the matching native input layer.
| Input | Route | Pixels | Bytes | Alpha | Different pixels | Output and complete SHA-256 |
|---|---|---|---|---|---|---|
| ascending.ico | pillow-default | 256 × 256 | 621 | 0–128 | 0 | PNG dc97981f9b57496d7a6b306f79e549243019f902ab12b675e12df658fa19943a |
| ascending.ico | ffmpeg-default | 256 × 256 | 902 | 0–128 | 0 | PNG 3b1ebfdaf24f4b70be388e694874dff16d57c2aba0670c088a529093a8cdacfc |
| ascending.ico | pillow-explicit-32 | 32 × 32 | 125 | 0–128 | 0 | PNG eca0c13372112082a048b23e5276d79c1de8fa6b782693f4b570bf3df0a5a8f0 |
| descending.ico | pillow-default | 256 × 256 | 621 | 0–128 | 0 | PNG dc97981f9b57496d7a6b306f79e549243019f902ab12b675e12df658fa19943a |
| descending.ico | ffmpeg-default | 256 × 256 | 902 | 0–128 | 0 | PNG 3b1ebfdaf24f4b70be388e694874dff16d57c2aba0670c088a529093a8cdacfc |
| descending.ico | pillow-explicit-32 | 32 × 32 | 125 | 0–128 | 0 | PNG eca0c13372112082a048b23e5276d79c1de8fa6b782693f4b570bf3df0a5a8f0 |
| single-32.ico | pillow-default | 32 × 32 | 125 | 0–128 | 0 | PNG eca0c13372112082a048b23e5276d79c1de8fa6b782693f4b570bf3df0a5a8f0 |
| single-32.ico | ffmpeg-default | 32 × 32 | 148 | 0–128 | 0 | PNG 52f36eadb56453979968a9719159925fcbc3df28fda9bd52db745754efb6f4c4 |
| single-32.ico | pillow-explicit-32 | 32 × 32 | 125 | 0–128 | 0 | PNG eca0c13372112082a048b23e5276d79c1de8fa6b782693f4b570bf3df0a5a8f0 |
Did transparency or edges change?
All nine exports retained an alpha channel. The default large outputs had 49152 fully transparent pixels and 16384 semitransparent pixels; every small output had 768 fully transparent pixels and 256 semitransparent pixels. None had an opaque pixel. Each export had zero differing RGBA pixels against its matching input layer, including that layer’s edges.
Here, unchanged edges means a complete pixel comparison found no changes at the selected native size. It does not mean every possible icon will behave identically. No browser rendering, visual scaling, bitmap-backed ICO, legacy transparency mask, conversion speed or service quota was tested.
Use the default export only when its reported dimensions are the dimensions you want. Choose an embedded size explicitly when you need that layer. Compare decoded pixels when checking image fidelity; retain the complete file checksum when checking whether two downloads are the same file. These checks answer different questions.
Reproduce this comparison
Download the three inputs and the reproduction script together with the fixed-input manifest. The script verifies each input checksum before conversion, saves separate outputs, and records the exact FFmpeg command and its diagnostic output. Run it with Python, Pillow and FFmpeg installed; it does not contact an online converter.
python run-local-benchmark.py
ffmpeg -hide_banner -loglevel info -y -i ascending.ico -frames:v 1 ascending-ffmpeg-default.png- ascending.ico
- descending.ico
- single-32.ico
- fixed-three-inputs.json
- run-local-benchmark.py
- results.json
- prose-audit.json
Limits of this test
The conclusions apply only to the downloadable fixtures, recorded commands and software versions. A smaller output file alone is not evidence of a sharper image. A smaller selected layer is not evidence that a converter destroyed the larger one. Keep the original ICO if you need the other embedded sizes later.
For the reverse operation, see the measured PNG to ICO comparison. The separate ICO entry-order experiment explains how the two multiresolution inputs were constructed; this page adds a second conversion tool, saved PNG byte comparisons, and the single-layer control.