Конвертер растровой графики для монохромного LCD (128х64)

Конвертер растровой графики для монохромного LCD (128х64)

Freeware bitmap converter for monochrome 128x64 LCD

История создания, практическое пособие и руководство

В продолжение маленьких историй о путешествиях по миру микроконтроллеров предлагается рассказ о моей простенькой, но полезной утилитке, позволяющей быстро получить код графического файла в виде массива байтов для встраивания в среду разработки программ для микроконтроллеров (на ассемблере или С/С++), для вывода изображения на экран LCD с разрешением в 128x64 точек.

О чем пойдет речь:
Зачем сейчас нужны эти монохромные LCD?
Постановка задачи
Что нашлось на просторах интернета
Делаем свой конвертор
Проверяем конвертор в действии, или Руководство по эксплуатации


Зачем сейчас еще нужны монохромные LCD дисплеи?

Конечно, при прочих равных условиях (разрешение, размер, цена, потребление энергии), монохромные дисплеи явно проигрывают своим цветным собратьям, прежде всего по потребительским качествам. И растущие объемы производства, постоянное развитие новых технологий вообще не оставляют им никаких шансов, по крайней мере, в сегменте бытового ширпотреба. Сейчас монохромные дисплеи в новых изделиях можно найти разве что в читалках электронных книг, переводчиках, электронной «бумаге» и самом дешевом мультимедийном секторе. Хотя иногда их ставят и в совсем не дешевые изделия, например, в продвинутые корпуса для сборки PC Home Theater.

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

Я тоже проголосовал деньгами за сохранение рынка монохромных дисплеев и уже их несколько штук скопилось, не считая недокументированного мусора от уже отживших свой век сотовых телефонов. Впрочем, и дисплеи с документами были обойдены моим вниманием, после написанной 9 месяцев назад статьи «Моя борьба», все уромянутые в ней дисплеи так больше никуда и не подключались.

Волею случая интерес к этой теме вновь появился и появилось желание обеспечить графическим материалом свои простенькие монохромные дисплеи с разрешением 128x64 точек. Под графическим материалом в данном случае понимаются шрифты с высотой символа более 16 пикселов и графические изображения.

Тип интерфейса в данном случае значения не имеет.

Постановка задачи

Требуется найти быстрый способ встроить в исходный файл программы для микроконтроллера несжатое растровое графическое изображение с глубиной цвета в один бит. Говоря проще, нужно иметь возможность сконвертировать файл из популярных многоцветных графических форматов в монохромный, отсечь все ненужные заголовки и метаданные исходного графического файла и записать его уже в текстовом виде, в формате последовательности hex-кодов, с необходимыми атрибутами для ассемблерной программы или программы на C/C++.

Эта потребность родилась после самостоятельного создания нескольких шрифтов для LCD, высота символов которых не превышает 8 точек. Была написана программа, выводящая в произвольную точку экрана сообщение шрифтом любого размера. Но вот создание большеразмерных шрифтов стало проблемой, так как требует серьезных художественных навыков, которыми господь просто не наделил. Вернее, они еще дремлют где-то очень глубоко.

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

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


Что нашлось на просторах интернета

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

Вот как выглядит интерфейс этой программы с загруженной картинкой размером 128х64:


(кликните по картинке для увеличения)

FastLCD

В программе много интересных возможностей в плане создания битовых карт, но нет опций конвертирования. Сохранение в виде программной вставки возможно только под два дисплея, один из который от Nokia 3310 c разрешением 84х48 точек, а второй требует шестибитовой сетки.

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

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

Для примера, конвертация описана на этом сайте, но это тоже не то, что мне надо было.

Возможно плохо искал, но так или иначе, а пришлось все делать самому.

Делаем свой конвертор

Люблю я С#. Поэтому будем делать на нем. Впрочем, легко и быстро эту безделушку можно сделать на любом языке общего назначения. У меня это все заняло полдня. А написание статьи об этом — в четыре раза больше.

Облегчим себе задачу. Программа будет воспринимать и обрабатывать графические файлы (bmp, jpeg, png) размером 128х64 точки. При попытке загрузить файл другого размера, программа предложить повторить загрузку. Поэтому заготовку другого размера придется сначала обработать в любом графическом редакторе. Глубина цвета значения не имеет. Теоретически, можно было бы снять ограничения на размер, как в большую, так и в меньшую стороны, однако это повлечет за собой выход на новый уровень универсальности с вытекающими временными затратами, так как захочется сделать больше, красивше, генерацию фонтов и т.д. Поэтому отложим другие размеры до лучших времен (хотя делать любые форматы и нарезки можно и на этой программе). Лучшие времена могут наступить в конце года, когда я планирую вплотную заняться цветными TFT-дисплеями. Но могут и не наступить никогда.

Пропускаем описание обычной инициализации и объявление переменных, сосредоточимся на узловых моментах (а в программе по сути и нет узлов, всего одна функция, кнопки да вызов диалоговых окон).

Алгоритм очень простой. Получаем через OpenFileDialog ofdисходный файл, делаем из вредности проверку на размер, затем выводим исходный файл на экран и приступаем к конвертации (DX и DY - константы, определяющие размер обрабатываемого изображения):
        private void ofd_FileOk(object sender, CancelEventArgs e)

{
filename = ofd.FileName.ToLowerInvariant().ToString();
Bitmap bmp = (Bitmap)Bitmap.FromFile(filename);

if ((bmp.Height != DY) || (bmp.Width != DX))
{
MessageBox.Show("Image size must be 128x64!",
"Bitmap converter", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}

bitmapIn = bmp;
picBoxInput.BackgroundImage = bitmapIn;
loaded = true;
ConvertToMonochrome();
}

Конвертация в черно-белый формат в происходит в единственной функции ConvertToMonochrome() и это тривиальная задача. Загруженный графический файл с точки зрения программной модели, представляет собой массив, доступ к каждому пикселу картинки осуществляется простым вызовом в цикле метода
Color col = bitmapIn.GetPixel(x, y);

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

Наша задача — заменить цвет каждого пиксела на черный или белый. Не стоить определять для этого выходной объект типа Bitmap с заранее установленным пиксельным форматом 1bpp, так как тогда будет трудно обойтись без интероперабельных функций, поскольку 1-битный формат является индексированным (так же, как и 8-битный), т.е. цвет получается из палетты, а метод SetPixel() с индексированными цветами не работает. К тому же выходной файл с однобитным цветом нам ни к чему на диске и отпадает необходимость работы с заголовками.

Поэтому создаем объект типа Bitmap, без явного задания глубины цвета, для формирования на экране монохромной картинки, но вместо исходного цвета надо будет установить новый, черный или белый, который дополнительно дублируем в битовый массив с размерностью, соответствующей числу пикселов изображения (128х64 = 8192). В программе будет добавлена возможность использовать два любых цвета, чтобы проверить, как будет выглядеть итоговая картинка на любом типе дисплея (негативном, позитивном) и с любым цветом подсветки, также добавим и кнопочку инверсии цвета.

Объявляем битовый массив:
            BitArray bits = new BitArray(DX * DY);

Нужно иметь в виду, что массив содержит данные типа Boolean и в языке С# автоматического кастинга на тип int, как в С, не будет.

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

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

Использовано два подхода, классический, с «правильными» весовыми коэффициентами цетовых составляющих R,G,B:
     int luminance = (int)(col.R * 0.299 + col.G * 0.587 + col.B * 0.114);

и относительное сравнение с яркостью, получаемой вызовом метода GetBrightness(). Можно считать, что во втором случае весовые коэффициенты будут выровнены, что будет очень кстати в некоторых ситуациях.

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

Решение о том, «загорится» ли точка на монохромном дисплее, принимается путем сравнения вычисленного нормализованного значения luminance или GetBrightness() с некоторым пороговым значением (threshold), обычно в программах конвертации устанавливаемым на половину максимального значения, которго может достигать luminance или GetBrightness(). Но такой подход часто дает неудовлетворительные результаты. Поэтому в нашей программе величина threshold регулируется во всем диапазоне изменения значений параметров luminance и GetBrightness(), а битовый, байтовый массивы и итоговая картинка пересчитываются автоматически при изменении любого параметра, влияющего на результат.

Таким образом, перебрав в цикле все пикселы исходной картинки, получаем битовый массив для монохромного дисплея и выходную картинку. Тело цикла такое:
   ...

bits[x + y * DX] = (luminance > threshold.Value) ? false : true;
bitmapOut.SetPixel(x, y, (luminance > threshold.Value) ? white : black);
...

Теперь нужно сконвертировать битовый массив в байтовый. Определяем выходной массив:
        byte[] bytes = new byte[DX * DY/ 8];

Если ширина экрана DX=128 точек, а высота DY=64 точки, то первый (т.е. нулевой по индексу) байт массива будет содержать в младшем разряде приведенное к целому типу значение bits[0+ 0*DX], в следующем bits[0 + 1*DX], затем bits[0 + 2*DX] и так далее до bits[0 + 7*DX]. То есть, сборка байтового массива — простая арифметика для младших классов:
            for (int y = 0; y < DY / 8; y++)

{
for (int x = 0; x < DX ; x++)
{
for (int z = 0; z < 8; z++)
{
if (cbInv.Checked)
{
if (!bits[x + z * DX + y * DX * 8]) bytes[x + y * DX] += (byte)(1 << z);
}
else
{
if (bits[x + z * DX + y * DX * 8]) bytes[x + y * DX] += (byte)(1 << z);
}
}
}
}

При желании даже этот код можно оптимизировать, да только незачем.

Параметр cbInv.Checked определяет, будут ли инвертированы монохромные цвета.

С вычислениями все. Для интеграции в какую-нибудь исходную программу среды разработки полученный массив объемом 1КБайт нужно перевести в текстовый вид и расставить необходимые знаки пунктуации и другую служебную информацию. Правильнее всего это делать с использованием объектов памяти и директивы using, собирая строку «на лету» с помощью класса StringBuilder, но в нашем простейшем случае можно обойтись и объектом типа RichTextBox, собирая текст в нем без визуализации. Ниже, для примера, приведен фрагмент цикла программы с выводом в текстовый файл строчки из 16 байтов в hex-формате, байты разделены запятыми:
                        for (int x1 = 0; x1 < 16; x1++)

{
rtb.AppendText("0x" + bytes[x1 + 16 * x + y * DX].ToString("X2") + ",");
}

Готовый блок текста в текстовый файл не записывается, так как незачем, а просто копируется в Clipboard, откуда его можно вставить в любой текстовый редактор любой среды разработки.

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


Проверяем конвертор в действии, или Руководство по эксплуатации

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

Сразу оговорюсь, что программа тестовая, без механизмов контроля и обработки исключений. Хотя у меня никаких глюков не проскакивало, какие-то нюансы могут всплыть при различной глубине цвета, так как проверялось только на исходных файлах с 24bpp.

Запускаем :


Окно программы

Видны все установки по умолчанию, то есть порог = 127, используется классичесое определение Luminance, генерируется программный фрагмент под ассемблер, изображение неинвертированное, цвет переднего плана у выходной картинки будет белым (W), а цвет заднего — черным (B). Нажатие кнопок «W» или «B» приводит к появлению диалогового окна выбора цвета и монохромные цвета можно переопределить, они будут видны рядом с кнопками, так как их можно устанавливать и без загрузки графического файла. Кнопка «D» возвращет черно-белую палитру.

Нажатие кнопки «Load» приводит к появлению стандартного диалога открытия файла. По умолчанию стоит фильтр на файлы с расширением bmp, jpg и png.

Откроем файл «google.bmp» , конвертация при этом происходит автоматически (все файлы данной главы находятся в архиве с программой, так что вы сможете все увидеть в деталях самостоятельно):




На экране видно, что время конвертации 31мс (на Windows XP SP3, Sempron 3100+). Получаемое монохромное изображение не соответствует оригиналу, так как при пороге 127 желтой буквы «о» совсем не видно. Переместим ползунок правее:




Ситуация не улучшилась, хотя у остальных букв пикселов прибавилось. Щелкнем по опции «Br» блока «Mode», не изменяя значения Threshold:




Желтая буква «о» появилась. Она появилась бы и в режиме «Lum», если продолжить сдвиг ползунка вправо. Другими словами, оптимальное качество изображения нужно подбирать, манипулируя ползунком и пробуя оба режима блока «Mode». В зависимости от вида изображения, ползунок иногда придется отодвигать очень далеко от нейтрального (127) положения.

Нажимаем флажок «Inv Col». Монохромное изображение инвертируется:




Если у вас LCD дисплей негативный, то для получения «неинвертированного» изображения нужно использовать программный код с установленным флажком «Inv Col».

Нажав кнопочку «Get Code», программный код (по умолчанию - для ассемблера AVR) записываем в Clipboard. Вставляем его в среду разработки AVR Studio 4.18:


Сгенерированный блок кода для ассемблера

Каждая строка содержит 16 байтов, 8 строк соответствуют первым 128 байтам экранной видеопамяти (хотя я обычно использую вспомогательные видеобуферы для реализации различных видеоэффектов). Всего 64 строки.

В случае, если в блоке «Output» выбрана опция «C/C++», программный код выглядит так:


Сгенерированный блок кода для С/С++

По большому счету, кнопочка «Get Code» совсем не нужна, так как копировать в Clipboard можно и в теле основной функции. Но кнопку решено было поставить для симметрии, поэтому две строчки кода копирования были выдраны из функции и отданы кнопке. Теперь надо ее обязательно нажимать. (Примерно по этой схеме создаются рабочие места для своих в госаппарате).

Посмотрим, что у нас получится в результате вывода этого кода на дисплей негативного типа DOGM128-6 (дисплей — белые пикселы на синем фоне. При белой подсветке. Но я сэкономил и купил вместо белой желто-зеленую, поэтому реальный цвет «черного» - грязное хаки, «белого» - грязный песок или светлая охра, фотоаппарат воспринимает его как зеленый).

Галочка инвертирования стоит:




Галочка инвертирования снята:




Запустим в программу крупный шрифт из графического редактора вкупе со святым словом:




И перенесем на LCD:




По виду окружности заметна растяжка по вертикали, так как пикселы дисплея не квадратные. Заметна и несущественная асимметричность мест, которые должны бы выглядеть симметрично. Это происходит из-за антиалиасинга в исходном файле, который не дает генерировать однородный контур. По возможности опцию антиалиасинга следует выключать при подготовке графики для преобразования к монохромному виду.

Лучше всего смотрятся графические изображения, расчитанные именно на разрешение LCD «пиксел в пиксел». Создадим пару таких рисунков в графическом редакторе Paint, взяв широкую кисть и проведя несколько горизонтальных полос:




Вот что получается на на экране LCD:





Некоторое подобие полутонов:




Переходим к фотореалистике. Фотография президента России, вероятно, была сделана в период, когда он еще работал в Газпроме:




Легкая небритость на оригинальной фотографии легко скрывается на монохромном дисплее. Учитывая, что у этого LCD пиксел не квадратный, а процентов на 10 вытянутый, вряд ли можно было бы, без сопоставления с оригинальным снимком, утверждать, кто изображен — Дмитрий Медведев или Муслим Магомаев в молодости :




Не могу не разместить рядом с президентом битмаповый шрифт с собственным ником (Бонасье и Франция! Это...сила!):







Переходим к менее значительным персонажам:







Кошка легко распознается и без исходного файла.

Конечно, существуют цветовые гаммы и сюжеты, которые очень плохо конвертируются в монохромный формат без предварительного преобразования цвета или градиента, например:




Цветы в монохромном варианте на цветы совсем не похожи и без оригинала не угадываются.




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

Две последних картинки выглядят на LCD совсем уж неприглядно, поэтому не приводятся.

Для формирования изображения на экране LCD использовался MCU AТmega32U4, работающий на частоте 8Мгц и программа, написанная на ассемблере. Пересылка полноформатной картинки из флэш-памяти контроллера в буфер (в оперативной памяти) занимает 1.2 мс. Запись из буфера во внутреннюю память дисплея занимает около 10мс.


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

Zauropod,
3 июня 2010 года.
Telegram-канал @overclockers_news - это удобный способ следить за новыми материалами на сайте. С картинками, расширенными описаниями и без рекламы.
Оценитe материал
рейтинг: 1.0 из 5
голосов: 1

Возможно вас заинтересует

Популярные новости

Сейчас обсуждают