Шрифты. Больше хороших и разных



Работал я с отладочной платкой Open103V, у нее есть TFT экранчик 3.2" с тачскрином. Встал вопрос выводить красивые картинки и надписи. Картинки понятно откуда брать — есть microSD карточка, грузи-выводи-жди ответного гудка. А вот со шрифтами вышла некоторая заминочка…

По классике, даже в примерах SPL, есть вариант растрового шрифта заданный просто битовым массивом:

const uint16_t ASCII16x24_Table [] = {
/** 
  * @brief        Space ' '  
  */ 
         0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
         0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
         0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
/** 
  * @brief        '!'  
  */ 
         0x0000, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180,
         0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0000, 0x0000,
         0x0180, 0x0180, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
/
.....


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

И тут я подумал: раз уж все-равно есть подсистема работы с битмапами, почему бы туда не засунуть нужный мне шрифт?

Гуглил, нашел такую интересную программку:
UBFG — Ultimate Bitmap Font Generator



В принципе из скриншота уже понятно ее предназначение: она раскладывает максимально плотно символы в битмапе. Мало того, она даже осуществляет слияние символов (какие получится). Например, английской букве «A» и русской букве «А» будет соответствовать один и тот же участок. Ну или «е» и «ё».

Параллельно с битмапом генерируется и массив координат символов в таком формате:
Arial 9pt -- font name and size
Char  X pos   Y pos   Width   Height   Xoffset  Yoffset  Orig W   Orig H
32    0       0       0       0        3        14       3        14
97    90      36      5       7        1        4        7        14
98    0       41      5       9        1        2        7        14


Я сдеру картинку с пояснениями с сайта, за подробностями лучше к оригиналу:


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

Это исходный файл:
Arial 37pt
32	0	0	0	0	14	56	14	56	
97	0	183	23	27	2	18	27	56	
......
169	218	46	36	36	0	9	36	56	
174	292	44	36	36	0	9	36	56	
Arial 16pt
32	0	0	0	0	6	24	6	24	
97	310	268	10	11	1	8	12	24	
....
169	79	236	15	15	0	4	15	24	
174	225	237	15	15	0	4	15	24	

Это скрипт для awk
/Arial/ {
	if (first == 1) {
		first = 0;
	} else {
		print ("},"); 
		print(num_letters, "},");
	}

	num_letters = 0;

	print ("{ //", $0); 
	print("2101, {");
	next;
}

/^$/ {
	next;
}

{ 
	print( "{", $1, ",", $2, ",", $3, "," $4,",", $5, ",",$6,",", $7, ",",$8,"}," ); 
	num_letters++;
}

BEGIN {
	first = 1;
}

END {
	print("},");
	print(num_letters, "},");
}


Это то, что получилось (myfont.h):

{ // Arial 37pt
2101, { // это номер битмапа, потом поменять вручную
{ 32 , 0 , 0 ,0 , 0 , 14 , 56 , 14 },
{ 97 , 0 , 183 ,23 , 27 , 2 , 18 , 27 },
....

{ 169 , 218 , 46 ,36 , 36 , 0 , 9 , 36 },
{ 174 , 292 , 44 ,36 , 36 , 0 , 9 , 36 },
},
169 }, // это количество символов в шрифте
{ // Arial 16pt
2101, {
{ 32 , 0 , 0 ,0 , 0 , 6 , 24 , 6 },
{ 97 , 310 , 268 ,10 , 11 , 1 , 8 , 12 },
....
{ 169 , 79 , 236 ,15 , 15 , 0 , 4 , 15 },
{ 174 , 225 , 237 ,15 , 15 , 0 , 4 , 15 },
},
169 },


Теперь подключаем все это в проекте:

typedef struct {
	uint8_t ch;
	uint8_t posX;
	uint16_t posY;
	uint8_t W,H;
	uint8_t offsX,offsY;
	uint8_t rW,rH;
} t_FontLetter;

typedef struct {
	uint16_t img;
	t_FontLetter chars[250];
	uint8_t num_char;
} t_FontDescr;

#define NUM_FONTS 2

const t_FontDescr FontArr[NUM_FONTS]= {
#include "myfont.h"
};


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

Ну и теперь как это все использовать:

uint8_t BMP_DrawLetter(uint16_t X, uint16_t Y, uint8_t ch, uint8_t font,  GL_Color fColor, GL_Color bgColor) {
	uint8_t i;
	uint16_t tX,tY;

	for(i=0; i<FontArr[font].num_char; i++) { // находим в шрифте нужный символ
		if (FontArr[font].chars[i].ch == ch) {
			break;
		}
	}

	if (i<FontArr[font].num_char) {
		if (bgColor != GL_Transparent) { // если не прозрачный фон, то зарисуем все знакоместо
			for (tX=X; tX<X+FontArr[font].chars[i].rW; tX++) {
				for (tY=Y; tY<Y+FontArr[font].chars[i].rH; tY++) {
					GL_PutPixel(tX, tY, bgColor);
				}
			}
		}
		BMP_ShowFontGlyph(X+FontArr[font].chars[i].offsX, Y+FontArr[font].chars[i].offsY, FontArr[font].chars[i].posX, FontArr[font].chars[i].posY, FontArr[font].chars[i].W, FontArr[font].chars[i].H, FontArr[font].img, fColor, bgColor);
		return FontArr[font].chars[i].rW;
	}

	return 0;
}


Функция BMP_ShowFontGlyph имеет такой прототип:
void BMP_ShowFontGlyph(GL_Coordinate X, GL_Coordinate Y, GL_Coordinate gX, GL_Coordinate gY, GL_Coordinate gW, GL_Coordinate gH, uint16_t num, GL_Color fColor, GL_Color bgColor)

X,Y — координаты на экране
gX,gY — координаты глифа в битмапе
gW, gH — ширина и высота выводимого глифа
num — номер битмапа (они у меня под нумерами)
fColor, bgColor — цвета шрифта и фона, соответственно

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

P.S.
В принципе, таких программ есть больше одной, достаточно погуглить по «bitmaps font generator»

  • +2
  • 03 сентября 2012, 15:48
  • steel_ne

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

RSS свернуть / развернуть
Интересная штука. Попробую на досуге — как раз относительно недавно подбирал/искал шрифт покошернее для своего stm32mini с экранчиком — первый подцепленный выглядел конским и с ним «инфо-емкость» экрана была мизерной, а второй найденный наоборот — «сломай глаза» называется, не говоря уже об отсутствии кириллицы (а вручную попиксельно вбивать русскую часть раскладки у меня мазохизма не хватило =) )
0
  • avatar
  • j3v
  • 03 сентября 2012, 16:56
for(i=0; i<FontArr[font].num_char; i++) { // находим в шрифте нужный символ
                if (FontArr[font].chars[i].ch == ch) {
                        break;
                }
        }

Как-то не аккуратненько получается: искать каждый раз символ перебором. Уж лучше сделать массив для быстрого нахождения индекса символа по ASCII коду. А еще лучше, сразу правильно составить массив FontArr (хотя это может быть не эффективно, если нам не нужны все символы ASCII).
0
  • avatar
  • e_mc2
  • 03 сентября 2012, 17:10
Как-то не аккуратненько получается: искать каждый раз символ перебором.
Угу. Если символы упорядочены, то, как минимум, можно двоичный поиск завести.
0
Если символы упорядочены, то, как минимум, можно двоичный поиск завести.

Тоже вариант.
0
Вообще, если символы переменной длины, то правильнее иметь индекс, но это может быть накладно по памяти (примерно 1К на армах для 256 символов).
0
Ну, можно считерить, и хранить в массиве не абсолютные указатели а смещения относительно начала массива описания шрифта. Для смещения должно хватить 16 бит.
0
Тоже вариант.
0
Можно. Но в реальности из 8 шрифтов 6 содержали только цифры. Разного размера. Поэтому оверхед на двоичный поиск был бы слишком велик.

Да и по сравнению с выводом на экран эта часть алгоритма просто летает ))
0
Хмм. Это какой же оверхед на двоичный поиск?
+1
Оверхед — это ж не только скорость выполнения.
Во-первых, нет гарантии что в исходном файле символы идут по порядку. Значит надо организовать сортировку не порушив секций. Ну это не так страшно
Во-вторых размер кода двоичного поиска будет поболе, чем просто перебор в цикле. И подводные камни могут быть. То забудешь нулевой элемент проверить, то при делении на два не туда округлишь и один элемент выпадет.

А так — хоть B-tree организуй, главное чтобы оправдано было
0
Ну все правильно. Чё мне париться, оптимизировать код. Зачем вот эта однократная сортировка при создании программы, когда каждое из N устройств может выполнить тупой поиск. Да и вообще, можно же взять камень потолще и попроизводительнее, и батарейку вторую навесить, а если что и тележку с аккамулятором к моим супер-крутым наручным часам. Ничего, пользователь сможет заряжать аккамулятор от камаза каждых 2 часа.
0
для этого и выпускают камни потолще и попроизводительнее, не?
0
Для этого нам и надо покупать 4-х ядерные процессоры, что бы почитать электронную книжку с жуткими тормозами при перелистывании. не?
0
и покупают жеж. Раз есть спрос — будет предложение. Гикам — линукс на первопне.
0
Ну там заложено ошибочное решение при проектировании. Строго говоря, андроид не тормозной, а неотзывчивый — это немного разные вещи.
Впрочем, пример хороший — ТС тоже заложил сомнительное решение. Разве что не столь сложноисправимо впоследствии.
0
  • avatar
  • Vga
  • 04 сентября 2012, 16:18
Да я не про андроид а про стационарные компы и планшеты на виндах :)
Андроид на гиговом проце игры тянет нормально, и видео декодирует софтварно неплохо (не говоря уже о хардваре). А «неотзывчивость» андройда куда меньше тормозов винды. Им (а) хоть пользоваться можно, потому как «все обязаныиспользовать их тормозной сдк», а не городить свой «мегаскоростной» на поверку оказывающийся более глючным и тормозным.
0
там везде ошибочное решение при проектировании. «640К хватит всем».
0
Не надо катить на винду. Большинство утверждений о ее тормозах необоснованы, а многоядерные процы нужны ресурсоемким приложениям, вроде игр (планшетным еще далеко до технологий крусиса), архиваторов и кодирования/декодирования видео (гигагерцовый Cortex-A8 с VFPv3 и NEON с трудом декодирует H264 SD, а на HD 720p откровенно тормозит, на HD можно расчитывать только при аппаратном ускорении декодирования).
Ну и все мало-мальски требовательные приложения для ведра работают в нативном коде, а не далвике.
0
  • avatar
  • Vga
  • 04 сентября 2012, 21:05
Вы не поняли. Я качу не на винду, а на разработчиков приложений под неё.
Сама винда прада порою тоже выкидывает финты.
0
Нууу… Разработчики везде примерно одинаковы.
0
  • avatar
  • Vga
  • 05 сентября 2012, 10:27
Во-первых, нет гарантии что в исходном файле символы идут по порядку. Значит надо организовать сортировку не порушив секций. Ну это не так страшно
Это не оверхед.
Во-вторых размер кода двоичного поиска будет поболе, чем просто перебор в цикле.
Отличия от линейного кода мизерные.
И подводные камни могут быть. То забудешь нулевой элемент проверить, то при делении на два не туда округлишь и один элемент выпадет.
Вики поможет избежать подобных ошибок. Вот тело цикла поиска:

// continue searching while [imin,imax] is not empty
  while (imax >= imin)
    {
      /* calculate the midpoint for roughly equal partition */
      int imid = imin + ((imax - imin) / 2);
 
      // determine which subarray to search
      if      (A[imid] <  key)
        // change min index to search upper subarray
        imin = imid + 1;
      else if (A[imid] > key )
        // change max index to search lower subarray
        imax = imid - 1;
      else
        // key found at index imid
        return imid;
  }

Ты действительно думаешь, что одно сравнение и чуток арифметики существенно усложняют код и дают оверхед?
+1
оверхед — это накладные расходы. Появились расходы на сортировку — оверхед.
И оптимизировать надо то что тормозит, а не то, что можно оптимизировать
+1
Появились расходы на сортировку — оверхед.
Сортировка делается один раз и не самим микроконтроллером. А то так и форматирование исходника можно в оверхед записать.
И оптимизировать надо то что тормозит, а не то, что можно оптимизировать
Это, по большому счету, даже не оптимизация, а то, как это вообще надо писать. Скажем, если тебе в программе понадобилась сортировка, ты же не станешь ее сначала делать пузырьком, а только потом, если тормозит, заменять на что-то более продвинутое? Скорее всего просто возмешь стандартную функцию, которая реализует заведомо оптимальный для большинства случаев алгоритм. Ровно тоже самое и тут — поиск по упорядоченному массиву просто не нужно делать прямым перебором и все.
+1
Конечно. Но согласись, что без знания контекста разработки выдвигать рекомендации как-то «вакуумно-сферообразно».

Изначально шрифт рассчитывался только на цифры. Это, повторюсь, 12 символов в шести начертаниях. Двоичный поиск среди 12 элементов? До такого дзен-перфекционизма я еще не дошел.

Потом встал вопрос — а давайте и текст туда замонстрячим. Увеличил размерность и переформировал битмап. Работает? Зашибись. Есть проблемы поважнее.

Рефачить этот кусок буду, но потом. Либо когда буду отдельную свою библиотеку вылизывать, либо когда тормозить начнет именно в этом участке.
+1
Это, повторюсь, 12 символов
тогда почему не массив индексов? цифры идут подрят. знаки применания прямо перед ними. Зачем вы тогда втулили «оверхед» поиска, когда всё было как на ладони?
Потом встал вопрос — а давайте и текст туда замонстрячим.
И вы снова воткнули «оверхед» в виде линейного поиска, а не бинарный или массив индексов (а он по прежнему хорошо вписывается в модель).
Рефачить этот кусок буду, но потом.
Пока вы тут припераетесь, уже давно бы от рефачили. вам даже готовое решение на блюдечке принесли.

Мне вообще нравится как вы переворачиваете аргументы в свою пользу. Вам менеджером надо работать, полюбому. Будите за милую душу все недостатки впаривать за фичи и плюсы.
0
И вы снова воткнули «оверхед» в виде линейного поиска, а не бинарный или массив индексов (а он по прежнему хорошо вписывается в модель).
Что значит воткнул? Он уже был. На изменение кода я потратил 5 секунд — поправил дефайн. И?

Индексы уже не катят, потому что есть знаки препинания. Причем в разных начертаниях разные. ("+-" и ":.", если быть дотошным). Упростит индекс? Да ничуть.
0
Конечно. Но согласись, что без знания контекста разработки выдвигать рекомендации как-то «вакуумно-сферообразно».
Ну контекст-то более-менне известен.
Изначально шрифт рассчитывался только на цифры. Это, повторюсь, 12 символов в шести начертаниях. Двоичный поиск среди 12 элементов? До такого дзен-перфекционизма я еще не дошел.
Насколько я понимаю, линейный поиск проигрывает бинарному на массивах >5 елементов. На 12 это разница между (максимум) 4 сравнениями и (в среднем) 6. Хоть в бинарном поиске есть арифметика, но надо сильно давать по рукам компилятору, что бы он эту арифметику не запихал в регистры. А каждое сравнение это, как не крути, доступ к памяти. На МК, конечно, надо мерять, но, думаю, даже на таком размере массива выиграш будет. Тем более он будет после того, как количество символов увеличилось.
Рефачить этот кусок буду, но потом. Либо когда буду отдельную свою библиотеку вылизывать, либо когда тормозить начнет именно в этом участке.
Тоже вариант.
0
Слушай, а напомни мне плиз, чего это мы пишем на си, а не на асме? Там же можно оптимизировать по самое нехочу. И библиотеку SPL уродскую не использовать…
0
Там же можно оптимизировать по самое нехочу.
Ты путаешь теплое с мягким. Речь не о низкоуровневой оптимизации, а об алгоритмической, которая является высокоуровневой и делать ее, естественно, гораздо удобнее на более высокоуровневом языке, чем асм.

Беда со всеми этими догмами… Да, premature optimization is evil, но это касается только низкоуровневой оптимизации. Во всех остальных случаях речь идет уже не только и не столько об оптимизации, сколько о понимании поведения используемых алгоритмов и, по возможности, использовании оптимальных решений. Это неотъемлемая часть процесса дизайна, поскольку поведение и особенности используемых алгоритмов непосредственно оказывают влияние на дизайн программы, выбор подходов и способов решения задачи.
+2
ага, но только алгоритм уж больно низкоуровневый и локальный. И изменением структуры данных можно соптимизировать больше.

Например, использовать чистый индекс на 256 элементов. Тогда алгоритм поиска вроде как не нужен вообще. Да, получится разреженный массив, ну и фиг с ним.
0
Алгоритм поиска нужен, даже в случае массива индексов. И вырождается он в разадресацию массива.
И вот смотрите, разадресацию то вы используете сразу, а не проходите каждый индекс проверяя не совпадает ли он с искомым, ведь так? Я искренне на это надеюсь, а то встречал уже прецеденты.
Токая «оптимизация» у вас уже принята как за должное.
Точно так примите и алгоритмы быстрого поиска как за должное.
0
Алгоритм поиска нужен, даже в случае массива индексов. И вырождается он в разадресацию массива.

Какой это уже поиск. Так мы доступ к памяти по адресу поиском назовем. Ну нет серебряной пули. Вот вы все мне хором советуете двоичный поиск с его сложностью O(logN). Давайте пойдем дальше и используем хэш. Там сложность поиска вполне может быть O(1). И что, мне его в каждую бочку затычкой сувать?
Но лучше же? Принять за должное?
0
У нас есть пример кода, где используется подобный алгоритм поиска. Был он написан по тому же самому принципу «работает и ладно, а будет тормозить, оптимизируем».
И вот этот код тормозит. Он реально тормозит. Но оптимизировать его уже ни кто не берётся. Код «воспринят как должное» и скопирован по всему проекту. Каждая затычка использует свою такую часть кода (что и приводит в итоге к тормозам). Банально нет ресурсов на оптимизацию «именно в этом участке», потому как этот участок теперь везде.
0
Это удел любого проекта, над которым работает более одного человека. Накопится груз, изменится аппаратное обеспечение, да и просто требования к продукту — тогда все и перепишется. Или этот проект тихо похоронится, а выпустится новый, с требуемыми свистелками.
0
Это удел любого проекта, над которым работает более одного человека.
Вообще-то нет. Зависит от подходов. Скажем, у меня в текущем проекте такого балласта практически нет (точнее очень мало), хотя проект большой, сложный и работает над ним далеко не один человек.
0
Исключения подтверждают правило
0
А с чего ты решил, что это исключение? У нас таких проектов подавляющее большинство.
0
Это удел любого проекта, над которым работает более одного человека. Накопится груз, изменится аппаратное обеспечение, да и просто требования к продукту — тогда все и перепишется.

Нет, это не удел любого продукта. А вот ваш подход – «напишу как получится, а потом, если что, всегда можно отрефакторить код» как раз способствует «накоплению груза». Если каждый из разработчиков в большом проекте будет писать код по этому принципу, то такой проект обречен.
+1
«накоплению груза».
У «накопления» даже название есть — technical debt. Что, как бы, намекает.
0
Намекает. А там или ишак сдохнет, или халиф.
0
Нет. Долги надо отдавать, иначе набегут проценты и потом не расплатишься. То, что ты описывал — как раз тот случай.
0
Не, ну рассуждать о сферических разработчиках в вакууме — это оно завсегда можно. А в реальности обычно стоит выбор — или быстро работает сейчас, или ты без денег/выходных. Не надо рассказывать о плохих менеджерах проектов, потому что трейдофф не всегда упирается в это. Но он всегда есть.
0
Не, ну рассуждать о сферических разработчиках в вакууме — это оно завсегда можно.
У меня избыточный весь чуток есть, конечно, но сферическим назвать меня сложно. Да и обитаю я вовсе не в вакууме :)
А в реальности обычно стоит выбор — или быстро работает сейчас, или ты без денег/выходных. Не надо рассказывать о плохих менеджерах проектов, потому что трейдофф не всегда упирается в это. Но он всегда есть.
Дело не в ПМ-е, а в организации процесса в целом. Для вотерфолла и других «тяжелых» методологий то, что ты описал очень характерно. Еджайл методы этому подвержены в значительно меньшей степени.
0
Да и по сравнению с выводом на экран эта часть алгоритма просто летает ))

По поводу вывода на экран тоже не все «аккуратненько». Если bgColor != GL_Transparent то вы сначала закрашиваете фон, а потом поверх закрашенного фона рисуете символ. По сути, вы 2 раза пробегаетесь по прямоугольнику N X M пикселей (при втором проходе закрашивается не каждый пиксель, но все же). Ели немного переписать BMP_ShowFontGlyph(), можно это сделать за один проход.

Уж простите меня за мою дотошность.
0
В BMP_ShowFontGlyph() не выводятся фоновые точки.
0
Хм. Интересна позиция минусующих. Вы считаете что это «аккуратненько»?
0
Теперь я понял зачем армам столько памяти :)
+1
  • avatar
  • psv
  • 03 сентября 2012, 19:06
ещё непного и 3д игры на нем запустят. текстуры шрифта вот уже подсосали.
0
Игры, конечно, в принципе сделать можно. Но все же, без графического чипа очень, очень грустно. Даже в NES (Dendy), где проц хилее спектрумовского, а памяти пара килобайт был графический ускоритель.
0
  • avatar
  • Vga
  • 05 сентября 2012, 10:16
Можете поделиться библиотекой-драйвером для TFT экранчика? Буду очень благодарен!
0
  • avatar
  • Nemo
  • 05 сентября 2012, 13:25
lcd-image-converter не пробовали для создания шрифтов?
0
  • avatar
  • rius
  • 11 сентября 2012, 10:12
Приводить свой пока не буду, бо сильно неоптимальный, стыдно показать ))
Все еще стыдно?
И какое значение должно быть у GL_Transparent, чтобы выполнялось условие if (bgColor != GL_Transparent)? Больше максимально возможного в выбранной битности цвета? Например, если выбран RGB c 8-ю бит на каждый цвет, то значение больше 0xffffff?
0
  • avatar
  • DVF
  • 06 августа 2014, 16:59
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.