Недавно мне прилетел багрепорт на модуль конвертации любой картинки в ICO: почему-то в итоговом изображении цвета искажались (внимание на скрин). И это странно, потому что под macOS и Linux такой проблемы не возникает, и под капотом я использую достаточно высокоуровневую библиотеку Skia. Как этот багфикс превратился в увлекатейнейшее историческое погружение я вас сейчас и расскажу.

Введение

Для начала давайте внимательнее посмотрим на результат проблемы. Если приглядеться, то можно увидеть, что цвета здесь не инвертированы (чёрный цвет не стал белым), они будто перепутаны. И ведь действительно, пиксели в изображении можно представить в разных форматах: в каких-то случаях на каждый пиксель можно отдать по 16 бит, тогда получится 65536 цветов, но лучше будет отдать по 32 бита, тогда получится 16 млн цветов, а можно даже все 64 или 128 бит! Любой каприз за ваши байты. Но, кажется, я увожу вас от сути.

Самое интересное, что может быть не только разная размерность, но и разный порядок цветовых каналов. Например, для кодирования 32-битного цвета может использоваться формат RGBA8888 или BGRA8888. То что "8" – это количество битов на цвет, а вот RGBA/BGRA – как раз порядок red, green, blue и alpha каналов. Интересно. А ещё теперь стало очевидно, что искажения на скрине вызваны неверным порядком. Но как он получился неверным?

Дело в том, что ICO – своего рода контейнер из изображений с разными размерами. Можно буквально под 16х16 сделать одну иконку (более читаемую и простую), а под 256х256 другую. Но важно здесь то, что изображения можно кодировать по-разному, и традиционно всё что меньше 256х256 кодируется в формате BMP для лучшей совместимости. Всё что больше в формате PNG из-за лучшего сжатия. А в BMP цвета кодируются как раз в BGRA (или BGR)!

Но до сего момента всё остаётся однозначным: BMP в BGRA, то есть обратный порядок. Почему же разница вылезает на Windows и macOS? Видимо, есть какая-то платформозависимая история?

Короткая историческая справка

Есть! Для начала как факт: исторически Windows придерживалась формата BGRA, а MacOS RGBA. И вы наверняка начали догадываться, что разница в порядке байт была не просто так!

Во времена расцвета Windows большинство компьютеров было на Intel процессорах, а вот Apple предпочитала процессоры Motorola. И у этих двух архитектур есть одно из ключевых отличий: порядок байт! У Intel это little-endian, а Motorola (PowerPC) big-endian. Так вот откуда растут ноги!

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

Вместо заключения

Если вдруг у вас не сложился пазл, то вот финальное объяснение: я считывал изображение, которое выбирал пользователь. Но при этом разработка велась на маке, поэтому я получал цвета в привычном для мака формате: в RGBA. Если я хочу адаптировать их под BMP, то мне нужно поменять R и B каналы местами, чтобы получилось BGRA, что я и делал. Но на Windows это не нужно! Ведь Windows изначально предоставляла мне цвета в формате BGRA, и я их менял местами как бы ещё раз. Отсюда и возникала такая путаница!