Вывод текста на дисплей Nokia 1616
Завершающая Статья по работе с дисплеем от Nokia 1616
Образы символов шрифта
Для того что бы выводить текст нам понадобятся изображения символов шрифта (по сути те же самые иконки из предидущего поста, разве что размер не так жёстко фиксирован). Для их получения отправляемся за проектом LPC1343 CodeBase и используем утилиту TheDotFactory. С её помощью вы можете сформировать «образы» любых, установленных в системе шрифтов. Сама утилита «качественно» и просто поддерживает только латинницу. Русские символы ей тоже можно сгенерировать, но иногда прийдется допиливать напильником таблицу символов.
Упаковываем образы символов шрифта
Естественно мне не понравилось что в символах строки выравнены на границу байта. Это ж сколько пустого места остаётся. Например, для 95 символов шрифта Tahoma 8pt получается избыточной информации 3740 бит (~467 байт) что составляет 43.4% от общего размера данных (1078 байт).
Да, мне было нечего делать и в итоге с помощью компилятора и небольшого кода:
#include <stdlib.h>
#include <stdio.h>
typedef struct {
unsigned char width;
unsigned short offset;
} FONT_CHAR_INFO;
// Character bitmaps for Ubuntu 10pt
const unsigned char tahoma8ptCharBitmaps[] = { ... };
// Character descriptors for Ubuntu 10pt
const FONT_CHAR_INFO tahoma8ptCharDescriptors[] = { ... };
#define font tahoma8ptCharBitmaps
#define fontinfo tahoma8ptCharDescriptors
#define count sizeof(fontinfo)/ sizeof(fontinfo[0])
unsigned int offset = 0;
unsigned int height = 11;
unsigned int firstchar = ' ';
unsigned int convert(unsigned char *res, const unsigned char *data, unsigned int width) {
unsigned char mask = 1, out = 0;
unsigned int n = 0, size = 0;
int i, j;
for(i = 0; i < height; i++) {
for(j = 0; j < width; j++) {
if(!(j&0x07) ) mask = *data++;
out <<= 1; n++;
out |= (mask & 0x80) ? 1 : 0;
mask<<=1;
if(n == 8) {
res[size++] = out;
out = n = 0;
}
}
}
if(n) {
while(n < 8) { out <<= 1; n++; }
res[size++] = out;
}
return size;
}
int main(int argc, char *argv[]) {
FONT_CHAR_INFO fiout[256];
unsigned char res[32];
unsigned int pos = 0;
unsigned int size = 0;
int i, j, k;
PrintStat();
printf("uint8_t font[] = {\n");
for(i = 0; i < count; i++) {
fiout[i].width = fontinfo[i].width;
fiout[i].offset = pos;
size = convert(&res[0], &font[fontinfo[i].offset], fontinfo[i].width);
pos += size;
printf("\t");
for(j = 0; j < size; j++) printf("0x%02x, ", res[j]);
printf("\n");
}
printf("};\n\n");
printf("FONT_CHAR_INFO fontinfo[] = {\n");
for(i = 0; i < count; i++) {
k = i + firstchar;
printf( "\t{%d, %d},\t// 0x%02x %3d %c\n",
fiout[i].width, fiout[i].offset + offset,
k, k, k >= 32 ? k : '.'
);
}
printf("};\n\n");
return EXIT_SUCCESS;
}
Переводим выравнивание на границу байта со строк на символ целиком и шрифт упаковывается до 650 байт, а избыточной информации получатся 316 бит (~39.4 байт) что составляет 6.1%. Это уже гораздо лучше. Ну, или по крайне мере терпимо. А то что внешний вид исходного кода потерялся - думаю переживём. Главное здесь что код вывода символа притерпит минимум изменений. Но а поскольку постоянно хочется иметь что-то универсальное, то код написан с возможности работы как с упакованными, так и не упакованными шрифтами. Нуда, ещё и моноширинные поддерживать умеем, но не будем вдаваться в подробности.
Всю эту информацию о шрифте надо сохранять, для чего дорабатываем структуру описания шрифта.
typedef struct __tagFONT_INFO
{
uint8_t u8Height; ///< Высота символов
uint8_t u8FirstChar; ///< индекс первого символа
uint8_t u8LastChar; ///< Индекс последнего символа
uint8_t u8Flags; ///< Флаги шрифта
const FONT_CHAR_INFO *asFontCharInfo; ///< таблица информации о символах
const uint8_t *au8FontTable; ///< Массив данных шрифта
} FONT_INFO;
В архиве прилагается парочка шрифтов для ознакомления с соответствующими им описателями.
Вывод символов
И так, сердцем по выводу текста, является функция вывода одного символа. Она похожа на вывод иконки, разве что узнает размеры самостоятельно и учитывает разные способы выравнивания.
const FONT_INFO *font = 0; // Текущий шрифт для вывода
uint32_t colorFace; // цвет текста/пера
uint32_t colorGround; // цвет фона/заливки
#define CHAR_WIDTH(c) (font->u8Flags & FONT_FIXEDWIDTH ? ((int)font->asFontCharInfo)&0x0FF : font->asFontCharInfo[(c) - font->u8FirstChar].width)
#define CHAR_BITMAP(c) (&font->au8FontTable[font->u8Flags & FONT_FIXEDWIDTH ? (c - font->u8FirstChar)*(((int)font->asFontCharInfo)&0x0FF) : font->asFontCharInfo[c - font->u8FirstChar].start])
uint8_t DrawChar(int16_t x, int16_t y, uint8_t c)
{
if(!font) return 0;
if(c < font->u8FirstChar || c > font->u8LastChar) return 0;
// Получаем информацию о символе
uint8_t width = CHAR_WIDTH(c);
uint8_t height = font->u8Height;
// Установка области вывода
if(BeginDraw(x, y, width, height)) {
const uint8_t *ptr = CHAR_BITMAP(c);
// вывод символа
uint8_t i, j, k, mask;
k = 0;
for(i = 0; i < height; ++i) {
for(j = 0; j < width; ++j) {
if(!(k&0x07) ) mask = *ptr++;
NextPoint( (mask & 0x80) ? colorFace : colorGround );
mask<<=1;
k++;
}
if(!(font->u8Flags & FONT_PACKEDDATA) ) k = 0;
}
EndDraw();
}
return width;
}
Была идея вообще из иконок сделать шрифт. Идея вполне разумная и реализация имеет право на существование. Но не сделал, что бы не заминять установленный шрифт, ведь вывод текста и иконок может идти поочерёдно. Это конешно решается запоминанием раннее установленного шрифта. Вообщем не помню я почему не сделал так :)
Вывод строки
Ясное дело что посимвольно выводить не удобно, надо чего-то большего. Например вывода строки:
#define CHAR_SPACE (font->u8Flags & FONT_FIXEDWIDTH ? 1 : 2)
uint16_t DrawString(int16_t x, int16_t y, const char *str)
{
if(!font || !str) return 0;
int16_t remx = x;
uint8_t w = 0;
uint8_t height = font->u8Height;
while(*str) {
if(w) {
// Промежуток между символами
Fill(x, y, CHAR_SPACE, height, colorGround);
x += CHAR_SPACE;
}
if(x > displayWidth) break;
w = DrawChar(x, y, *str);
x += w;
str++;
}
return x - remx;
}
Расстояние между символами по хорошему тоже в харрактеристику шрифта надо поместить, но пока так. Посколько шрифты имеют разную длину символов, то при выводе разных строк без предварительного стирания, у нас останется мусор от прошлого вывода. Если предварительно стереть всё пространство, то возникнет неприятное мерцание даже при выводе одной и той же строки. По этому полезной будет функция вывода строки в область:
uint16_t DrawText(const RECT *r, uint16_t flags, const char* str)
{
uint16_t count;
int16_t x, y;
if(!r) return 0;
m_lcdSetClip(r);
uint16_t w, h;
w = GetStringWidth(str);
h = GetFontHeight(str);
// учёт флагов вывода и т.д.
if(flags & DT_CENTER) { // Выравнивание по центру
x = (r->right + r->left + 1 - w) / 2;
} else if(flags & DT_RIGHT) { // Выравнивание по правой стороне
x = r->right + 1 - w;
} else { // Выравнивание по левой стороне (по умолчанию)
x = r->left;
}
if(flags & DT_VCENTER) { // Выравнивание по центру по вертикали
y = (r->bottom + r->top + 1 - h) / 2;
} else if(flags & DT_BOTTOM) { // Выравнивание к верху
y = r->bottom + 1 - h;
} else { // Выравнивание к низу (по умолчанию)
y = r->top;
}
if(flags & DT_FILL) {
// Медленно, но для начала пойдет
if(r->top < y) Fill(r->left, r->top, r->right - r->left + 1, y - r->top, colorGround); // верх
if(r->left < x) Fill(r->left, y, x - r->left, h, colorGround); // лево
}
count = DrawString(x, y, str);
if(flags & DT_FILL) {
// Медленно, но для начала пойдет
if(r->right >= x + w) Fill(x + w, y, r->right - (x + w) + 1, h, colorGround); // право
if(r->bottom >= y + h) Fill(r->left, y + h, r->right - r->left + 1, r->bottom - (y + h) + 1, colorGround); // низ
}
m_lcdResetClip();
return count;
}
Тут нам пригадилалась установка области отсечения. Видь если область недостаточного размера для вмещения строки и не прилегает к границе дисплея, то при выводе мы вылезем куда не положено. Можно было бы проверяьт при выводе каждого символа, но зачем, если уже есть более подходящее средство.
Ну и выводим:
RECT rect;
RECT_set(rect, 0, 15, 39, 28);
DrawText(&rect, DT_RIGHT | DT_FILL, "WiFi");
Фотка кликабельна на 2304х3072 пикселей 2.62МБ.
Видео одного из проектов на данном дисплее прилагается.
Вот собственно и всё.
Файлы: Nokia1616.zip, DSC02935.JPG
< Предидущая