Сборка 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 уже давно закончилась, и всё меньше компьютеров работает под управлением этой операционной системы, то не так всё плохо. Тем более, что авторы маленьких программ часто для экономии вообще не добавляют иконку.

Ссылки

Комментарии временно закрыты.