Емельянов Эдуард Владимирович (eddy_em) wrote,
Емельянов Эдуард Владимирович
eddy_em

Category:

Простенький подход к шрифтам для микроконтроллеров

Если в шрифте определять все 256 символов, он будет занимать приличное количество флеша, который можно использовать в более благих целях. Поэтому я решил подойти со стороны индексирования лишь тех символов, которые реально в шрифте определены (а в тех же МК зачастую определяется лишь очень скудный набор - все символы таблицы КОИ8-Р обычно не нужны).
Мне как-то mbr подсказал программку "lcd image converter" (в ответ на мое описание веб-морды велосипеда для генерирования шрифтов). Пока вожусь со светодиодной панелью, мне понадобилось сделать набор из нескольких шрифтов для цифр разного размера, вот я и вспомнил про эту штуку. Она достаточно хорошо преобразует в растр разные шрифты, а дальше остается лишь набить вручную все в файле с исходниками.
Саму идею (как и основной шрифт величиной 12 пикселей) я стащил где-то на просторах интернета (Nadyrshin Ruslan, youtube). Но там подход был "в лоб": надо 256 символов — значит, столько места и займем (хоть половина пустая). Меняем подход.
Итак, нужно нам не так уж и много. Сначала заведем заголовочный файл fonts.h, где определим перечень имеющихся шрифтов, базовые функции и структуру, характеризующую шрифт:
// type for font choosing
typedef enum{
    FONT_T_MIN = -1,    // no fonts <= this
    FONT14,             // 16x16, font height near 14px
    FONTN16,            // numbers 16x8, font height 16px
    FONTN10,            // numbers 10x8, font height 10px
    FONT_T_MAX          // no fonts >= this
} font_t;

int choose_font(font_t newfont);
const uint8_t *font_char(uint8_t Char);

typedef struct{
    const uint8_t *font;        // font inself
    const uint8_t *enctable;    // font encoding table
    uint8_t height;             // full font matrix height
    uint8_t bytes;              // amount of bytes in font matrix
    uint8_t baseline;           // baseline position (coordinate from bottom line)
} afont;

extern const afont *curfont;

Глобальная постоянная curfont является указателем на текущий используемый шрифт (задается он командой choose_font, далее описываю содержимое файла fonts.c):
int choose_font(font_t newfont){
    if(newfont >= FONT_T_MAX || newfont <= FONT_T_MIN) return 1;
    curfont = &FONTS[newfont];
    return 0;
}

Если шрифт не найден, возвращается единица. Если все ОК — нуль.
Функция font_char позволяет получить доступ к нужному символу:
const uint8_t *font_char(uint8_t Char){
    uint8_t idx = curfont->enctable[Char];
    if(!idx) return NULL; // no this character in font
    return &(curfont->font[idx*(curfont->bytes+1)]);
}

Если символ не найден, возвращается NULL. Если найден — указатель на него (первым байтом идет ширина символа в пикселях, далее — данные самого символа).
Еще в файл fonts.c нужно поместить массив — все существующие шрифты:
static const afont FONTS[] = {
    [FONT14] = {font14_table, font14_encoding, FONT14HEIGHT, FONT14BYTES, FONT14BASELINE},
    [FONTN16] = {fontNumb16_table, fontNumb16_encoding, FONTNUMB16HEIGHT, FONTNUMB16BYTES, FONTNUMB16BASELINE},
    [FONTN10] = {fontNumb10_table, fontNumb10_encoding, FONTNUMB10HEIGHT, FONTNUMB10BYTES, FONTNUMB10BASELINE},
};
const afont *curfont = &FONTS[FONT14];

(ну и сам указатель на текущий шрифт).
Но в самом начале этого файла надо перечислить дефайны для удобства рисования. Я не буду все эти 256 строк включать, а только приведу скрипт, генерирующий их:
#!/bin/bash

function bits(){
    Ans=""
    for x in $(seq 7 -1 0); do
        B=$((1<<$x))
        if [ $(($1&$B)) -ne 0 ]; then Ans="${Ans}X"
        else Ans="${Ans}_"
        fi
    done
    echo $Ans
}

for x in $(seq 0 255); do
    printf "#define $(bits $x)\t0x%02x\n" $x
done

Сразу после этого включаем все файлы со шрифтами:
#include "font14.h"
#include "fontNumb16.h"
#include "fontNumb10.h"

Ну а потом уже пишем то, что я выше привел.

Содержимое каждого файла со шрифтами несложно нарисовать. Во-первых, это нужные дефайны:
#define FONTNUMB10BYTES         10
#define FONTNUMB10HEIGHT        10
#define FONTNUMB10BASELINE      0

Во-вторых, таблица для перекодировки (своя для каждого шрифта):
const uint8_t fontNumb10_encoding[256] = {
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0..31
      1,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  13,   0, // 47
      2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,   0,   0,   0,   0,   0, // 63
      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, // 79
      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, // 95
      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, // 111
      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, // 127
      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, // 143
      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, // 159
      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, // 175
      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, // 191
      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, // 207
      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, // 223
      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, // 239
      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, // 255
};

Здесь определены маленькие цифры (10 пикселей высотой), поэтому почти вся таблица — нули. Зато очень приличная экономия памяти получается.
Сам этот шрифт — в константе fontNumb10_table[]. Первым ее элементом обязательно должен идти символ-пустышка с нулевой шириной, а дальше — определяемые символы. Скажем, для этого шрифта начало данного массива имеет вид
const uint8_t fontNumb10_table[] = {
    // 0x00 - empty
        0,
        ________,
        ________,
        ________,
        ________,
        ________,
        ________,
        ________,
        ________,
        ________,
        ________,
    // 0x20 - ' '
        4,
        ________,
        ________,
        ________,
        ________,
        ________,
        ________,
        ________,
        ________,
        ________,
        ________,
    // 0x30 - '0'
        7,
        __XX____,
        _X__X___,
        X____X__,
        X____X__,
        X____X__,
        X____X__,
        X____X__,
        X____X__,
        _X__X___,
        __XX____,

Руслан Надыршин подсказал превосходнейший способ визуализации данных! Мы просто определяем 256 макросов для каждого числа, вместо нуля симпол подчеркивания, вместо единицы — X. И можно прямо в исходниках рисовать символы.
Другой шрифт с цифрами имеет высоту 16 пикселей — на всю высоту экрана. Поэтому там символы выглядят как-то так:
    // 0x32 - '2'
       7,
       __XX____,
       _XXXX___,
       XX_XXX__,
       X___XX__,
       ____XX__,
       ____XX__,
       ____XX__,
       ____XX__,
       ___XX___,
       ___X____,
       __XX____,
       __X_____,
       _XX_____,
       XX______,
       XX___X__,
       XXXXXX__,

А в оригинальном шрифте буквы занимают по высоте почти все 16 пикселей (у некоторых вниз выползает хвостик, у некоторых — вверх). И выглядят они уже вот так:
    // 0x40
     ,
       16,
       _____XXX,XXX_____,
       ___XXXXX,XXXXX___,
       __XXX___,___XXX__,
       _XXX__XX,X_XXXX__,
       _XX_XXXX,XXXX_XX_,
       XXX_XX__,_XXX_XX_,
       XX_XX___,_XX__XX_,
       XX_XX___,_XX__XX_,
       XX_XX___,_XX__XX_,
       XX_XX___,XXX_XX__,
       XX_XXXXX,XXXXX___,
       _XX_XXXX,_XXX____,
       _XXX____,_____XX_,
       __XXX___,___XXX__,
       ___XXXXX,XXXXX___,
       _____XXX,XXX_____

Здесь, правда, приходится разделять байты запятыми, что немножко ломает наглядность. Но определять 65536 макросов что-то не хочется: боюсь, что сборка в этом случае затянется...
Вот на таких широченных шрифтах 16х16 пикселей экономия флеша при индексации получается вполне приличной: ведь один символ занимает 33 байта, а таблица с индексами — 256. Если в шрифте не определено 8 символов, индексация уже экономит немного места. А если там определено лишь полтора десятка, так совсем прилично!
Tags: c, stm32
Subscribe

  • Темы-2

    Некоторые испугались, прочитав предыдущие темы. Повторяю: темы для работы в течение всей школы (три года). А вот — их части, которые можно осилить за…

  • Темы для творческих работ школьников

    В связи с возможным проведением очной весенней школы АФШ (детей набрали еще прошлым летом, но пока очно не было возможности встретиться из-за…

  • Бюджетная читалка с алиэкспресса

    Долго искал вменяемые электронные читалки, но за формат примерно А4 просят около $900, вообще озверели. ОК, решил взять мелкую дешевую читалку, чтобы…

promo eddy_em august 17, 2019 12:33 3
Buy for 10 tokens
Юра намедни напечатал корпус для хронометра. Для первого блина получилось неплохо: И еще немного фотографий:
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

  • 0 comments