Вывод текста на дисплей 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МБ.
  • +5
  • 02 августа 2011, 21:38
  • angel5a
  • 2
Файлы в топике: Nokia1616.zip, DSC02935.JPG

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

RSS свернуть / развернуть
Мощно задвинул. Внушает.

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

Если будешь их править, то предварительно вступи в этот блог:
we.easyelectronics.ru/blog/lcd_gfx/

Т.к. если это не сделать, то после редактирования поста (вдруг чего забыл) он обратно улетит в твой персональный блог. А это плохо, т.к. ссылка на него изменится и если на статью кто ссылался, то при переходе получит 404.
+1
вот с фоткой уже другое дело, можно хоть визуально оценить, стоит ли связывацца
-2
На фотке всё смазано и блик на пол экрана от вспышки :)
Норм фотиком попробую снять, лучше должно быть.
0
Ничего удобнее BitFontCreator для формирования шрифта не встречал.
0
  • avatar
  • Leroy
  • 03 августа 2011, 14:30
Ну удобство это платное…
А открыть в нем существующий шрифт можно, или всё надо ручками рисовать? что то не нашел по сайту ответа.
0
Платное-то платное, но… Ну вы поняли. :)
А открыть можно любой шрифт и соответственно конвертировать в массив hex.
0
В нем шрифты получаются с разной шириной. И сверху, снизу куча пробелов. Только как заготовка для дальнейшей обработки годится.
0
Ну как ни странно в основном используется моноширинные тексты. И проще и меньше памяти, да и при выводе неожиданностей не возникает.
0
Все равно использовать получаемый на выходе шрифт без обработки напильником не получается.
В этом плане древний lcdicon лучше, жалко, что нем нет шрифтов шириной 16 и 32 точки.
0
Шрифты с разной шириной как раз-таки позволяют экономить память и приятны глазу.
0
Вот была бы триака какая или урезанная лицензия, я бы воспользовался. :)
Что шрифт открыт можно — хорошо, потому что самому всё рисовать долго выходит.
0
В ломанных версиях генерируются не все символы, если подскажите как это обойти буду очень признателен, так как тоже ничего более удобного найти не удалось…
0
Имел ввиду BitFontCreator
0
На электрониксе я нашел ломанную полную версию BitFontCreator — генерирует все символы.
0
Всегда можно свою генерилку шрифтов написать, с блэкджеком и шлюхами. Ничего сложного в этой задаче нет.
0
  • avatar
  • Vga
  • 05 августа 2011, 20:48
Вопрос не в сложности а в рациональности. я свою не стал писать, потому что нашел готовое. А не нашел бы в CodeBase, так всегда можно посетить сайты GameDev'а. У них тоже вопрос шрифтов актуальный, им текстуры шрифта нужны (OpenGL знаем что сам ричует 3D шрифты, но вопрос быстрого вывода — а это спрайты). С позволения сказать «скрипт» упаковки шрифта был написан «от нечего делать» и только после написания ещё пары утилит для сбора статистики по шрифту :)
0
Не OGL, а WGL. Но имеющиеся шрифты (wglFontBitmap и wglFontOutline, ЕМНИП) хрень, так что все пишут свои движки)
Но геймдеверские — все же несколько не то, у них другие требования (знаю — сам писал :).
Да и в общем-то задача генерации просто шрифта (без фокусов типа уникодных текстурных шрифтов, оптимальной укладки в атласы, раскраски, эффектов и так далее, что обычно и добавляет сложности геймдеверским генерилкам) укладывается в 2-3 сотни строк.
0
Нашел интересный вариант.
Генерирует шрифты вплоть до 32*32 (больше не умеет)
0
А какая лицензия не сказано ни слова…
0
В архиве «FontLeroy.pdf».
В нем написано:

this document and software may be freely
distributed provided attribution is retained.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.