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


Работал я с отладочной платкой 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
Интересная штука. Попробую на досуге — как раз относительно недавно подбирал/искал шрифт покошернее для своего stm32mini с экранчиком — первый подцепленный выглядел конским и с ним «инфо-емкость» экрана была мизерной, а второй найденный наоборот — «сломай глаза» называется, не говоря уже об отсутствии кириллицы (а вручную попиксельно вбивать русскую часть раскладки у меня мазохизма не хватило =) )
for(i=0; i<FontArr[font].num_char; i++) { // находим в шрифте нужный символ
if (FontArr[font].chars[i].ch == ch) {
break;
}
}
Как-то не аккуратненько получается: искать каждый раз символ перебором. Уж лучше сделать массив для быстрого нахождения индекса символа по ASCII коду. А еще лучше, сразу правильно составить массив FontArr (хотя это может быть не эффективно, если нам не нужны все символы ASCII).
Как-то не аккуратненько получается: искать каждый раз символ перебором.Угу. Если символы упорядочены, то, как минимум, можно двоичный поиск завести.
Можно. Но в реальности из 8 шрифтов 6 содержали только цифры. Разного размера. Поэтому оверхед на двоичный поиск был бы слишком велик.
Да и по сравнению с выводом на экран эта часть алгоритма просто летает ))
Да и по сравнению с выводом на экран эта часть алгоритма просто летает ))
Оверхед — это ж не только скорость выполнения.
Во-первых, нет гарантии что в исходном файле символы идут по порядку. Значит надо организовать сортировку не порушив секций. Ну это не так страшно
Во-вторых размер кода двоичного поиска будет поболе, чем просто перебор в цикле. И подводные камни могут быть. То забудешь нулевой элемент проверить, то при делении на два не туда округлишь и один элемент выпадет.
А так — хоть B-tree организуй, главное чтобы оправдано было
Во-первых, нет гарантии что в исходном файле символы идут по порядку. Значит надо организовать сортировку не порушив секций. Ну это не так страшно
Во-вторых размер кода двоичного поиска будет поболе, чем просто перебор в цикле. И подводные камни могут быть. То забудешь нулевой элемент проверить, то при делении на два не туда округлишь и один элемент выпадет.
А так — хоть B-tree организуй, главное чтобы оправдано было
Ну все правильно. Чё мне париться, оптимизировать код. Зачем вот эта однократная сортировка при создании программы, когда каждое из N устройств может выполнить тупой поиск. Да и вообще, можно же взять камень потолще и попроизводительнее, и батарейку вторую навесить, а если что и тележку с аккамулятором к моим супер-крутым наручным часам. Ничего, пользователь сможет заряжать аккамулятор от камаза каждых 2 часа.
Для этого нам и надо покупать 4-х ядерные процессоры, что бы почитать электронную книжку с жуткими тормозами при перелистывании. не?
Да я не про андроид а про стационарные компы и планшеты на виндах :)
Андроид на гиговом проце игры тянет нормально, и видео декодирует софтварно неплохо (не говоря уже о хардваре). А «неотзывчивость» андройда куда меньше тормозов винды. Им (а) хоть пользоваться можно, потому как «все обязаныиспользовать их тормозной сдк», а не городить свой «мегаскоростной» на поверку оказывающийся более глючным и тормозным.
Андроид на гиговом проце игры тянет нормально, и видео декодирует софтварно неплохо (не говоря уже о хардваре). А «неотзывчивость» андройда куда меньше тормозов винды. Им (а) хоть пользоваться можно, потому как «все обязаныиспользовать их тормозной сдк», а не городить свой «мегаскоростной» на поверку оказывающийся более глючным и тормозным.
Не надо катить на винду. Большинство утверждений о ее тормозах необоснованы, а многоядерные процы нужны ресурсоемким приложениям, вроде игр (планшетным еще далеко до технологий крусиса), архиваторов и кодирования/декодирования видео (гигагерцовый Cortex-A8 с VFPv3 и NEON с трудом декодирует H264 SD, а на HD 720p откровенно тормозит, на HD можно расчитывать только при аппаратном ускорении декодирования).
Ну и все мало-мальски требовательные приложения для ведра работают в нативном коде, а не далвике.
Ну и все мало-мальски требовательные приложения для ведра работают в нативном коде, а не далвике.
Во-первых, нет гарантии что в исходном файле символы идут по порядку. Значит надо организовать сортировку не порушив секций. Ну это не так страшноЭто не оверхед.
Во-вторых размер кода двоичного поиска будет поболе, чем просто перебор в цикле.Отличия от линейного кода мизерные.
И подводные камни могут быть. То забудешь нулевой элемент проверить, то при делении на два не туда округлишь и один элемент выпадет.Вики поможет избежать подобных ошибок. Вот тело цикла поиска:
// 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;
}
Ты действительно думаешь, что одно сравнение и чуток арифметики существенно усложняют код и дают оверхед?
оверхед — это накладные расходы. Появились расходы на сортировку — оверхед.
И оптимизировать надо то что тормозит, а не то, что можно оптимизировать
И оптимизировать надо то что тормозит, а не то, что можно оптимизировать
Появились расходы на сортировку — оверхед.Сортировка делается один раз и не самим микроконтроллером. А то так и форматирование исходника можно в оверхед записать.
И оптимизировать надо то что тормозит, а не то, что можно оптимизироватьЭто, по большому счету, даже не оптимизация, а то, как это вообще надо писать. Скажем, если тебе в программе понадобилась сортировка, ты же не станешь ее сначала делать пузырьком, а только потом, если тормозит, заменять на что-то более продвинутое? Скорее всего просто возмешь стандартную функцию, которая реализует заведомо оптимальный для большинства случаев алгоритм. Ровно тоже самое и тут — поиск по упорядоченному массиву просто не нужно делать прямым перебором и все.
Конечно. Но согласись, что без знания контекста разработки выдвигать рекомендации как-то «вакуумно-сферообразно».
Изначально шрифт рассчитывался только на цифры. Это, повторюсь, 12 символов в шести начертаниях. Двоичный поиск среди 12 элементов? До такого дзен-перфекционизма я еще не дошел.
Потом встал вопрос — а давайте и текст туда замонстрячим. Увеличил размерность и переформировал битмап. Работает? Зашибись. Есть проблемы поважнее.
Рефачить этот кусок буду, но потом. Либо когда буду отдельную свою библиотеку вылизывать, либо когда тормозить начнет именно в этом участке.
Изначально шрифт рассчитывался только на цифры. Это, повторюсь, 12 символов в шести начертаниях. Двоичный поиск среди 12 элементов? До такого дзен-перфекционизма я еще не дошел.
Потом встал вопрос — а давайте и текст туда замонстрячим. Увеличил размерность и переформировал битмап. Работает? Зашибись. Есть проблемы поважнее.
Рефачить этот кусок буду, но потом. Либо когда буду отдельную свою библиотеку вылизывать, либо когда тормозить начнет именно в этом участке.
Это, повторюсь, 12 символовтогда почему не массив индексов? цифры идут подрят. знаки применания прямо перед ними. Зачем вы тогда втулили «оверхед» поиска, когда всё было как на ладони?
Потом встал вопрос — а давайте и текст туда замонстрячим.И вы снова воткнули «оверхед» в виде линейного поиска, а не бинарный или массив индексов (а он по прежнему хорошо вписывается в модель).
Рефачить этот кусок буду, но потом.Пока вы тут припераетесь, уже давно бы от рефачили. вам даже готовое решение на блюдечке принесли.
Мне вообще нравится как вы переворачиваете аргументы в свою пользу. Вам менеджером надо работать, полюбому. Будите за милую душу все недостатки впаривать за фичи и плюсы.
И вы снова воткнули «оверхед» в виде линейного поиска, а не бинарный или массив индексов (а он по прежнему хорошо вписывается в модель).Что значит воткнул? Он уже был. На изменение кода я потратил 5 секунд — поправил дефайн. И?
Индексы уже не катят, потому что есть знаки препинания. Причем в разных начертаниях разные. ("+-" и ":.", если быть дотошным). Упростит индекс? Да ничуть.
Конечно. Но согласись, что без знания контекста разработки выдвигать рекомендации как-то «вакуумно-сферообразно».Ну контекст-то более-менне известен.
Изначально шрифт рассчитывался только на цифры. Это, повторюсь, 12 символов в шести начертаниях. Двоичный поиск среди 12 элементов? До такого дзен-перфекционизма я еще не дошел.Насколько я понимаю, линейный поиск проигрывает бинарному на массивах >5 елементов. На 12 это разница между (максимум) 4 сравнениями и (в среднем) 6. Хоть в бинарном поиске есть арифметика, но надо сильно давать по рукам компилятору, что бы он эту арифметику не запихал в регистры. А каждое сравнение это, как не крути, доступ к памяти. На МК, конечно, надо мерять, но, думаю, даже на таком размере массива выиграш будет. Тем более он будет после того, как количество символов увеличилось.
Рефачить этот кусок буду, но потом. Либо когда буду отдельную свою библиотеку вылизывать, либо когда тормозить начнет именно в этом участке.Тоже вариант.
Там же можно оптимизировать по самое нехочу.Ты путаешь теплое с мягким. Речь не о низкоуровневой оптимизации, а об алгоритмической, которая является высокоуровневой и делать ее, естественно, гораздо удобнее на более высокоуровневом языке, чем асм.
Беда со всеми этими догмами… Да, premature optimization is evil, но это касается только низкоуровневой оптимизации. Во всех остальных случаях речь идет уже не только и не столько об оптимизации, сколько о понимании поведения используемых алгоритмов и, по возможности, использовании оптимальных решений. Это неотъемлемая часть процесса дизайна, поскольку поведение и особенности используемых алгоритмов непосредственно оказывают влияние на дизайн программы, выбор подходов и способов решения задачи.
Алгоритм поиска нужен, даже в случае массива индексов. И вырождается он в разадресацию массива.
И вот смотрите, разадресацию то вы используете сразу, а не проходите каждый индекс проверяя не совпадает ли он с искомым, ведь так? Я искренне на это надеюсь, а то встречал уже прецеденты.
Токая «оптимизация» у вас уже принята как за должное.
Точно так примите и алгоритмы быстрого поиска как за должное.
И вот смотрите, разадресацию то вы используете сразу, а не проходите каждый индекс проверяя не совпадает ли он с искомым, ведь так? Я искренне на это надеюсь, а то встречал уже прецеденты.
Токая «оптимизация» у вас уже принята как за должное.
Точно так примите и алгоритмы быстрого поиска как за должное.
Алгоритм поиска нужен, даже в случае массива индексов. И вырождается он в разадресацию массива.
Какой это уже поиск. Так мы доступ к памяти по адресу поиском назовем. Ну нет серебряной пули. Вот вы все мне хором советуете двоичный поиск с его сложностью O(logN). Давайте пойдем дальше и используем хэш. Там сложность поиска вполне может быть O(1). И что, мне его в каждую бочку затычкой сувать?
Но лучше же? Принять за должное?
У нас есть пример кода, где используется подобный алгоритм поиска. Был он написан по тому же самому принципу «работает и ладно, а будет тормозить, оптимизируем».
И вот этот код тормозит. Он реально тормозит. Но оптимизировать его уже ни кто не берётся. Код «воспринят как должное» и скопирован по всему проекту. Каждая затычка использует свою такую часть кода (что и приводит в итоге к тормозам). Банально нет ресурсов на оптимизацию «именно в этом участке», потому как этот участок теперь везде.
И вот этот код тормозит. Он реально тормозит. Но оптимизировать его уже ни кто не берётся. Код «воспринят как должное» и скопирован по всему проекту. Каждая затычка использует свою такую часть кода (что и приводит в итоге к тормозам). Банально нет ресурсов на оптимизацию «именно в этом участке», потому как этот участок теперь везде.
Это удел любого проекта, над которым работает более одного человека. Накопится груз, изменится аппаратное обеспечение, да и просто требования к продукту — тогда все и перепишется.
Нет, это не удел любого продукта. А вот ваш подход – «напишу как получится, а потом, если что, всегда можно отрефакторить код» как раз способствует «накоплению груза». Если каждый из разработчиков в большом проекте будет писать код по этому принципу, то такой проект обречен.
Не, ну рассуждать о сферических разработчиках в вакууме — это оно завсегда можно.У меня избыточный весь чуток есть, конечно, но сферическим назвать меня сложно. Да и обитаю я вовсе не в вакууме :)
А в реальности обычно стоит выбор — или быстро работает сейчас, или ты без денег/выходных. Не надо рассказывать о плохих менеджерах проектов, потому что трейдофф не всегда упирается в это. Но он всегда есть.Дело не в ПМ-е, а в организации процесса в целом. Для вотерфолла и других «тяжелых» методологий то, что ты описал очень характерно. Еджайл методы этому подвержены в значительно меньшей степени.
Да и по сравнению с выводом на экран эта часть алгоритма просто летает ))
По поводу вывода на экран тоже не все «аккуратненько». Если bgColor != GL_Transparent то вы сначала закрашиваете фон, а потом поверх закрашенного фона рисуете символ. По сути, вы 2 раза пробегаетесь по прямоугольнику N X M пикселей (при втором проходе закрашивается не каждый пиксель, но все же). Ели немного переписать BMP_ShowFontGlyph(), можно это сделать за один проход.
Уж простите меня за мою дотошность.
Приводить свой пока не буду, бо сильно неоптимальный, стыдно показать ))Все еще стыдно?
И какое значение должно быть у GL_Transparent, чтобы выполнялось условие if (bgColor != GL_Transparent)? Больше максимально возможного в выбранной битности цвета? Например, если выбран RGB c 8-ю бит на каждый цвет, то значение больше 0xffffff?
Комментарии (52)
RSS свернуть / развернуть