Сборка ICO файла с иконками в формате PNG при помощи FASM

Иногда я пишу небольшие программы на C++, и часто выходит так, что иконка программы «весит» больше, чем собственно сама программа. Так же вышло и при написании Sound Keeper: программа — 14КБ, иконка 16×16 + 32×32 + 48×48 пикселей — 15КБ. Какое расточительство! К счастью оказалось, что Windows (начиная с Vista) поддерживает PNG внутри ICO. Это как раз то, что нужно! Но почему-то не нашлось программы, которая бы позволила самому оптимизировать файлы PNG и собрать из них файл ICO. Поскольку у файлов ICO очень простой формат, соберём его при помощи FASM. Это нестандартное использование «плоского» ассемблера показывает, что его можно применять в самых неожиданных ситуациях, и это работает!

Приступим

1. Создаём изображения для иконок в формате PNG. Например, сделаем два изображения с размерами 16×16 и 32×32 пикселей. Сохраним их в файлы icon16.png и icon32.png соответственно. Оптимизируем на свой вкус своими любимыми инструментами.

2. Следуя документации, создаём файл icopng.asm с примерно таким содержимым:
dw 0 ; reserved, must be 0
dw 1 ; icon type, must be 1
dw 2 ; number of images in file

; 1st icon header
db 32 ; width
db 32 ; height
db 0 ; no color palette
db 0 ; reserved, must be 0
dw 1 ; planes
dw 32 ; bits per pixel
dd icon32_end-icon32_start ; length
dd icon32_start ; offset

; 2nd icon header
db 16 ; width
db 16 ; height
db 0 ; no color palette
db 0 ; reserved, must be 0
dw 1 ; planes
dw 32 ; bits per pixel
dd icon16_end-icon16_start ; length
dd icon16_start ; offset

; 1st icon body
icon32_start:
file 'icon32.png'
icon32_end:

; 2nd icon body
icon16_start:
file 'icon16.png'
icon16_end:

Опытным путём было установлено, что лучше подключать изображения в обратном порядке, от больших к меньшим. При добавлении большего количества иконок не забывайте исправлять поле с общим количеством изображений в заголовке. Ошибка здесь может привести к самым неожиданным последствиям.

3. Собираем иконку командой:
fasm icopng.asm icopng.ico

4. В результате мы получили иконку с изображениями в формате PNG. У меня вместо 15КБ получился файл размером всего 3КБ.

Поддержка PNG8 с альфа-каналом

Согласно скудной документации, должны поддерживаться только изображения в формате PNG32. На практике система без проблем декодирует и изображения PNG8 даже с альфа-каналом. Разве что только просмотрщик картинок Windows не понимает PNG8 в иконках, видимо он не использует системные функции для декодирования и отрисовки иконки. Но вы же иконки делаете не для того, чтобы их в просмотрщике картинок смотреть, правда? :) Везде, где использутся стандартные функции Windows для загрузки и отображения иконки — PNG8 с альфа-каналом отображается так же, как и PNG32.

Важное замечание: даже у изображений в формате PNG8 в заголовке должно быть указано отсутствие палитры, потому что декодер PNG на выходе даёт TrueColor изображение с альфа-каналом, где могут встречаться любые цвета. То есть в примере вам нужно будет изменять только поля width и height. Остальное трогать не нужно.

Проблема в системном декодере PNG

Поддержка PNG в иконках в Windows Vista и новее по умолчанию используется для сохранения только больших иконок размером 256×256. Судя по всему, поддержку PNG внутри ICO тестировали плохо, поэтому есть кое-какие проблемы и обходные пути для них.

Обнаружилась какая-то ошибка в системном декодере PNG, из-за чего конкретный файл иконки может отображаться неправильно в некоторых местах (например, в диалоге свойств файла). Причём минимальное изменение параметров сжатия легко решает проблему.

Я провёл небольшое исследование на файле иконки, которая неправильно отображалась в диалоге свойств файла. Системная функция DrawIconEx имеет 3 режима отрисовки иконки: DI_NORMAL (с альфой), DI_IMAGE (без альфы) и DI_MASK (только маска).

Проблема с иконками

На этом изображении видно как одна и та же иконка вывелась в разных режимах, а под названием RESULT приведено то, как выглядит эта иконка в свойствах файла. Видно, что в режимах DI_NORMAL и DI_IMAGE система отрисовывает иконку правильно. Однако, в режиме DI_MASK вычисленная с целью совместимости маска почему-то сдвинута на три пикселя вправо, а также в первом столбце пикселей появился какой-то мусор — вероятно, следствие неправильной работы с буфером. Иконка в свойствах файла, как видно, имеет серьёзные артефакты как раз по очертаниям некорректной маски. Было бы хорошо сообщить об этой проблеме разработчикам Microsoft, но к сожалению, я не нашёл какого-то открытого баг-трекера.

Советы по оптимизации PNG

Рекомендую использовать программу Color Quantizer для преобразования файлов из PNG32 в PNG8 с альфа-каналом. Результирующий файл помимо прочего сжимается весьма эффективным алгоритмом, поэтому обычно дополнительная оптимизация не требуется.

Color Quantizer

На скриншоте красным выделен блок с выбором количества цветов и коэффицентом допустимых ошибок квантования. При возникновении описанной выше проблемы с некорректным отображением иконки в диалоге свойств файла, просто поменяйте параметры квантования. Например, незначительно измените значение коэффицента допустимых ошибок квантования.

На этом всё

Теперь и ваши компактные программы могут быть с красочными иконками всех необходимых размеров. Не нужно жертвовать прозрачностью или количеством вариантов иконки. Недостаток только один — пользователи Windows XP не увидят такую иконку. Но если учесть, что официальная поддержка Windows XP уже давно закончилась, и всё меньше компьютеров работает под управлением этой операционной системы, то не так всё плохо. Тем более, что авторы маленьких программ часто для экономии вообще не добавляют иконку.

Ссылки