Программист, реверс-инженер
0,0
рейтинг
7 января 2015 в 17:45

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

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



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

На этом всё


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

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (42)

  • +4
    >Это нестандартное использование «плоского» ассемблера показывает…

    Вполне стандартное использование. В какой-то книжке Вирта форматы файлов RBNF описываются, и FASM создавался в похожей парадигме. Европейская школа программирования.
    • +4
      Спасибо за замечание. Но думаю, что многие разработчики не берут во внимание возможность использовать FASM в такой роли, поэтому я надеюсь вы не против, если я оставлю такую формулировку.
      • +1
        Да, естественно. Вы автор, вам и решать.
  • 0
    Про ХР Вы зря так… Пользоваться будут ещё лет.., ну десять, как минимум.
    • +3
      Сильно зависит от страны. В Китае — может быть. Там и IE6 до сих пор пользуются. Если брать только рунет, то ситуация достаточно прозрачна:

      • 0
        Если исключить домашние и личные компьютеры, картинка немного другая получится. Так что если у вас утилита для использования в рабочем процессе, стоит всё же подумать о поддержке.
        • +1
          Верно, именно поэтому на графике у Windows XP и Windows 7 сейчас (начало января) «провал», а Windows 8 осталась на месте (потому что её в основном дома используют).

          В моём случае программа изначально не поддерживала Windows XP из-за использования WASAPI. И она как раз для домашнего использования :)
  • –5
    Нечего вам делать
    • +2
      Считайте, что это был вызов. Разве не интересно иногда сделать что-то не совсем типичное?
      • –1
        Кто в наши дни считает байты?
        Сами себе создали проблему на ровном месте и сами ее героически решили.
        А создавать PNG встроенные в ICO, кстати, позволяет редактор иконок ArtIcons.
        • +11
          Кто в наши дни считает байты?
          Девиз «и так сойдёт!» — это не ко мне. Я в прошлом году написал пару программ под Dendy — вот уж где байты считал. Здесь же я даже особо и не усердствовал. Секции в исполняемом файле не объединял, необязательные ресурсы не удалял, выравнивание не уменьшал. А иконку было просто интересно сделать компактной, чтобы она весила хотя бы меньше самой программы.

          Я знаю, что «экономить байты» сейчас не в тренде. Встречал логотип на сайте объёмом 2 мегабайта (кто-то поленился уменьшить размер картинки), иконки разделов примерно такого же объёма каждая (потому что генерировать маленькие картинки лень). Даже некоторые авторы статей на Хабре иногда вставляют в статьи фотографии в огромном разрешении или вообще в PNG, из-за чего они весят по несколько мегабайт каждая и долго загружаются. Я считаю это халтурой и неприемлемым. Хорошо, что тут это случается нечасто.

          А создавать PNG встроенные в ICO, кстати, позволяет редактор иконок ArtIcons.
          Платная. А позволяет ли она оптимизировать PNG внешними утилитами? Это было ключевым требованием. Не каждый кодер PNG даёт хороший результат. Предпочитаю использовать для этих целей инструменты, которые зарекомендовали себя в этом.
          • +1
            Есть мнение, что лучше чем CQ заоптимизоровать png прямо в иконке не сумеет ни один другой инструмент.

            Это было «про-», а вот «контра-»: придется столкнуться с неинтуитивным интерфейсом, в котором главное — понять, что CQ лишь оптимизирует внутренние ресурсы иконке, но не позволяет собрать её с нуля и не позволяет добавить какие-либо форматы внутри; всё это нужно предварительно сделать какой-то внешней утилитой.
          • +1
            Даже некоторые авторы статей на Хабре...

            Я тоже всегда удивляюсь, когда на хабре вижу подобное. Если большие картинки можно списать на задел под ретину, то неверный выбор формата я не знаю на что списывать. Видеть графики, текст, клипарт и прочее подобное, пожатое JPEG-ом… или когда JPEG перезаписывают в PNG… Скорее всего, это лень, воткнул первое, что попало под руку — и так сойдет.

            Что касается оптимизации ICO, это возможно силами CQ. Процесс не интуитивный, да и сама возможность не очевидна, но это возможно. Собрать иконку не выйдет, можно лишь оптимизировать имеющийся в наличии файл. При открытии файла ICO появится дополнительное окно со списком, где будут все потроха:
            image
            Автоматически выбирается изображение с наибольшим разрешением, а чекбоксы выставляются на изображениях с максимальной глубиной цвета. Если не закрывать это окно, можно выполнить нужные манипуляции с каждым изображением, после нажатия кнопки записи (дискета на панели инструментов), поменяется статус (закрашенная звездочка), после чего в этом окне можно нажать на кнопку сохранения иконки.
            image
            Ваш способ более универсальный. Я лишь рассказал о возможностях инструмента, который вы уже используете.

            P.S. Судя по скрину в статье, у вас старая версия CQ. В текущей версии значительно доработан алгоритм квантования и добавилось несколько полезных мелочей.
            • 0
              Что касается оптимизации ICO, это возможно силами CQ
              Вот это неожиданность! Спасибо, в будущем буду пользоваться и этой возможностью.

              Кстати, если вдруг вы надумаете сделать возможность добавления/удаления изображений в ico файл, лучше добавить возможность и изменения порядка изображений. Я сталкивался с тем, что если все изображения в иконке в формате PNG, и расположены они по возрастанию, система почему-то иногда ошибочно выбирала самую маленькую версию вместо подходящей. При обратном же порядке — всегда выбиралась именно подходящая версия.

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

              P.S. Судя по скрину в статье, у вас старая версия CQ. В текущей версии значительно доработан алгоритм квантования и добавилось несколько полезных мелочей.
              Угу, я и сам заметил, что несколько месяцев назад вышла новая версия программы, но только уже после публикации статьи :)
  • 0
    к сожалению, я не нашёл какого-то открытого баг-трекера
    Microsoft Connect
    • +4
      Спасибо, но насколько я вижу, там нет баг-трекера для самой Windows.
      • 0
        А вы думали, так легко сообщить в MS об ошибке? Уж давно там все так огорожено, что не подступишься. Зачастую сетования на тему «у вас что-то не работает» оказывались ошибками самого программиста — либо использование недокументированного чего-либо, либо просто неправильное использование, баги в самих программах. Теперь надо прорваться через комьюнити, форумы и пр. Две ошибки — в API и одну в OLE интерфейсе одного их объекта мне так и не удалось отрепортить, как ни пытался. Обещания «мы с вами свяжемся дл уточнения» пока не исполнялись.
        А покупать платную поддержку чтобы им же их же баги сообщать неохота.
      • 0
        [шутка] Видимо, в Microsoft считают, что в Windows ошибок быть не может. :-) [/шутка]

        Можно попробовать использовать некое приложение «Feedback app», доступное для Windows 10 Technical Preview в рамках программы «Windows Insider Program». Кроме того, заявляется, что на официальных форумах сообщества можно получить комментарии в том числе от технических специалистов Microsoft:
        And check out the Technical Preview forum. This is a vibrant community where you can interact with Microsoft engineers and fellow Insiders.
        • 0
          Форумов там мильон, сложно определить тему с сообщением об ошибке в нужный раздел, да и комментарии специалистов встречаются не так часто.
          По поводу feedback для новой системы тоже имел опыт, через Connect, правда, а не специальное приложение: отрепортил во времена релиза 8 ошибку. Потом, спустя какое-то время, добавил, что поисследовал другие версии, и увидел что та же ошибка есть и в 7 и в XP (т.е. ошибка старая, но само место малоиспользуемое).
          Как вы думаете, какая была реакция? «Извините, но мы собираем ошибки касающиеся ТОЛЬКО 8ки, а так как ваша ошибка есть и в других версиях, то она не 8 specific, поэтому мы закрываем ваш багрепорт». Как и куда теперь им стучать — ни слова.
  • –1
    рограмма — 14КБ, иконка 16×16 + 32×32 + 48×48 пикселей — 15КБ. Какое расточительство! :) К счастью оказалось, что Windows (начиная с Vista) поддерживает PNG внутри ICO. Это как раз то, что нужно!


    Я конечно понимаю, что оптимизация — это здорово. Но чем Вас не устраивает 29КБ exe-шник? 21 век на дворе — 640 КБ на всех уже давно не хватает…
    • +11
      Ну у меня случился разрыв шаблона от понимания, что иконка программы весит больше, чем сама программа. Это как если бы на обложке книги было бы больше информации, чем в самой книге.

      Если бы там было бы всё сложно, я бы конечно не стал разбираться и тратить на это время, порядок объёмов всё же не такой большой. Но когда я краем глаза посмотрел спецификацию и увидел, как там всё просто — уже не смог устоять. Вызов был принят автоматически :)
      • 0
        В таком случае, я с трудом представляю что у Вас за программы такие…
        Я сам пишу на Си под микроконтроллеры. Так даже у меня шестнадцатеричный файл прошивки для 8-битного микроконтроллера весит 8-20КБ :)
        • +3
          На C++ я ничего большого не пишу. В Sound Keeper ≈850 строк кода. Для чего-то большого на десктопе я использую C#, а для веба использую вообще неправославный PHP :)
      • +2
        Так это нормально ведь, что ресурсы занимают больше места чем код. Звук СЕЕЕ-ГАААА при старте игры Sonic The Hedgehog 1/8 всего места на картридже занимает, и ничего. Раньше вы могли гордиться тем, что у вас программа меньше чем ее иконка, а теперь — нет :)
        Ну и почему бы тогда вообще все на FASM не писать. Только на ассемблере можно вписываться в невозможные ограничения. Напишите программу, которая при первом запуске сама себе процедурно генерирует красивую иконку.
        • +2
          Так это нормально ведь, что ресурсы занимают больше места чем код.
          Есть разница между просто программой и игрушкой. Плюс к этому, если есть возможность сжать ресурсы, но это не сделано — это халтура.
          Звук СЕЕЕ-ГАААА при старте игры Sonic The Hedgehog 1/8 всего места на картридже занимает, и ничего.
          Тут проблема явно была в том, что процессор Сеги не потянул бы в реальном времени декодирование чего-то серьёзного вроде MP3. По возможности и там ресурсы сжимались. На NES в играх даже строки иногда были упакованы каким-нибудь простым алгоритмом. Я, конечно, не призываю к такому драматизму, просто пример.
          Ну и почему бы тогда вообще все на FASM не писать.
          Ну если хотите таких крайностей — никто не запрещает вам писать всё на ассемблере. Есть разница между небольшими оптимизациями, которые не требуют много времени на реализацию, и между теми оптимизациями, которые требуют чрезвычайных усилий при минимальном выхлопе.
          Я взял огромные куски кода с MSDN, где активно используется COM/WASAPI, и переделал их под свою задачу. Вы представляете, сколько бы времени у меня занял перенос этого на ассемблер? Возможно даже заголовочные файлы WASAPI с необходимыми структурами самому пришлось бы полностью описывать.
          При помощи FASM я решаю другие задачи, которые удобнее всего решаются именно им. Например, он очень полезен при патчинге других приложений. Оказалось, что для сборки нужной мне иконки он также годится лучше всего :)
          Напишите программу, которая при первом запуске сама себе процедурно генерирует красивую иконку.
          Проводник не сможет отобразить такую иконку.
      • 0
        Ну у меня случился разрыв шаблона от понимания, что иконка программы весит больше, чем сама программа.

        Я как-то раз тоже дурью маялся. Мне нужно было подружить RunDll с ComCtl v6. Так вот у меня XML-ный манифест занимал больше места, чем программа, и вообще файл получился состоящим из паддингов.
      • 0
        А ведь в электронном оригинал-макете книги обложка «весит» гораздо больше, чем сам текст :)
  • +1
    Шикарно, спасибо. FASM хорош.
  • +3
    Чувствую скоро опять появятся «картинки смерти» в PNG из за корявой реализации в винде.
    Посмотрел картинку — поймал вирус :)
    • 0
      Сдвиг и какой-то мусор слева в маске как раз может быть вызван тем, что там ведётся некорректная работа с буфером. Если кто-то сможет расковырять этот баг и уронить с его помощью стандартную функцию декодирования — это просто замечательно. В таком случае быстрее исправят. Мало ведь кому понравится, что иконка может уронить Explorer.
  • –4
    Что творят люди, помнится я предложил чтоб защитить файл ресурсов от извлечения из программы, положить его не папку с файлами, а инкапсулировать в бинарник ввиде char []. Так на до мной смеялись, а тут такое — как к такому отнесся коллектив?
  • 0
    помнится я предложил чтоб защитить файл ресурсов от извлечения из программы, положить его не папку с файлами, а инкапсулировать в бинарник ввиде char []
    И в чём будет проблема найти этот массив по какому-то смещению и скопировать напрямую из бинарника?
    а тут такое — как к такому отнесся коллектив?
    Мелкие утилитки пишут обычно в одиночку. И на самом деле такой подход мне кажется более удобным. Раньше я сохранял каждый нужный размер иконки в PNG32 в полноценном графическом редакторе, затем по одному файлику вручную импортировал их в иконку при помощи бесплатной версии IcoFX. Сейчас же для того, чтобы собрать иконку из пачки PNG файлов, достаточно просто запустить один батник, подправив немного asm файл, если вдруг поменялся набор размеров (что бывает нечасто). Можно в принципе и написать небольшую программу, которая бы автоматически собирала из пачки PNG файлов иконку. Но вариант с FASM мне показался проще и быстрее. При желании можно и конвертацию в PNG8 тоже автоматизировать при помощи консольной truepng :) Есть правда вероятность, что может возникнуть описанная выше проблема с неправильной маской. Вот бы MS исправила эту проблему, вообще хорошо стало бы!
    • 0
      >>>И в чём будет проблема найти этот массив по какому-то смещению и скопировать напрямую из бинарника?

      В какую очередь вы будете искать ресурсы в самом бинарнике? По моему это по надежнее чем то что выбрали — запаковать файл архиватором и стереть у архива сигнатуру.
      • +1
        Сигнатуру в смысле заголовок? Ну да, можно, только анализ энтропии файла покажет, что в определенном месте лежат пожатые данные, а дальше уже дело техники понять, как они были упакованы.
        Но можете попробовать сделать то, что предлагаете, а потом скормить свою программу, например, binwalk
        • 0
          Ну да, заголовок — первые несколько байт файла. Должно распаковываться без проблем утилитами специализирующимися на «битых» архивах.
        • 0
          Чем можно провести такой анализ?
          • 0
            Я же говорил, например, binwalk.
            Он и энтропию посчитает и популярные форматы найдет внутри файла, даже распаковать может
            • 0
              Понятно, просто мне показалось почему-то, что последнее предложение не связано со вторым. Спасибо.
      • 0
        Смотря какие ресурсы. Уж картинки-то точно в первую очередь в секции ресурсов размещают. Как и курсоры, как и бинарные файлы.
  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      enddialog — это макрос, а не языковая конструкция. Там большая часть встроенных фишек реализована на макросах. И самому можно тоже мощные макросы писать.
  • 0
    Это всё делает же convert из пакета IMagick
    (Его так же можно попросить завернуть в ico заранее подготовленные png)

    convert icon.png -bordercolor white -border 0 \
              \( -clone 0 -resize 48x48 -colors 256 \) \
              \( -clone 0 -resize 32x32 -colors 256 \) \
              \( -clone 0 -resize 16x16 -colors 256 \) \
              \( -clone 0 -resize 256x256 -colors 256 \) \
              \( -clone 0 -resize 48x48 \) \
              \( -clone 0 -resize 32x32 \) \
              \( -clone 0 -resize 24x24 \) \
              \( -clone 0 -resize 16x16 \) \
              icon.ico
    

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.