Forum: Mikrocontroller und Digitale Elektronik true type Schrift für Graphik LCD


von Felix Ziegler (Gast)


Lesenswert?

Hallo

Hat jenamd Infos zu True Type Schriften auf einem Graphik-LCD?

Ich verwende zur Zeit einen selbst gemachten Treiber mit 6x8 Font.
Da ich viel Text anzeigen muss, möchte ich mit einem TrueType-Font
etwas mehr Zeichen auf eine Zeile bringen.

Wer kann helfen?

Vielen Dank!

von Benedikt (Gast)


Lesenswert?

Die kleinste Schriftart mit der man gut lesbare Buchstaben darstellen
kann ist 5x7, dazu kommt noch ein Zwischenraum zwischen den Zeichen,
macht 6x8. Ich denke damit ist die Frage geklärt...

von Hagen (Gast)


Lesenswert?

Was du suchst ist nicht TrueType sondern ein proportionaler Font. Dabei
wird zu jedem Zeichen gespeichert wieviel Pixel breit es ist. Ich
vermute du benutzt momentan einen fixed Pitch Font, d.h. alle Zeichen
sind zB. 5x7 Pixel breit. Nun proportionale Fonts haben den Vorteil das
sie ca. 1.5 bis 2 mal mehr Zeichen auf gleicher Pixelbreite zulassen und
zusätzlich sind sie für uns Menschen denoch leichter zu lesen als fixed
Pitch Fonts.


Gruß Hagen

von Felix Ziegler (Gast)


Lesenswert?

nein, so einfach ist es eben nicht.
Bei einer TrueType-Schrift, ist nicht jedes Zeichen gleich lang, ein
"i" ist viel schmaler als ein "w". Dadurch kann mehr Text
dargestellt werden.

Gruss Felix

von Läubi (Gast)


Lesenswert?

Das sit so aber auch nicht Richtig.
Dies ist nicht bei allen Truetype Schriften!!
Es gibt diese LetterSpacing verfahren. ABer TrueType heißt einfach ddas
der Font nicht als 'Pixel' sondern als Vektor beschreibung vorliegt,
d.h. beleibig ohne Qualitätsverlsute skalliert werden kann.

von Felix Ziegler (Gast)


Lesenswert?

Ja, Danke Hagen
Was ich such sind proportionale Fonts. Habe wohl nicht den genauen
Begriff getroffen......

Hast Du C-source von so einem Font?

Danke

von Benedikt (Gast)


Lesenswert?

Das einfachste wäre zusätzlich zu der Buchstabentabelle eine weitere zu
verwenden, in der die Breite jedes Buchstaben angegeben ist.

von Hagen (Gast)


Lesenswert?

Hier im Forum habe ich meine GLCD Library veröffentlicht, fürs Nokia
Color LCD, suche in der CodeLib nach Nokia+GLCD. Diese Library enthält
im Source einen Fonttreiber der mehrfarbige, proportionale und fixe
Fonts die sogar noch komprimiert sind darstellen kann. Allerdings ist
der Source in Assembler ! Desweiteren enthält sie auch eine Windows
Anwendung zur Font Erstellung, den FontEditor. Dieser erzeugt C Header
Dateien für die Font Daten und kann auch Windows TrueType Fonts
importieren. Die Fonts selber können 1 Bit = Monochrome, 2,3,4 bis 8
Bit Farbtiefe ermöglichen, also mehrfarbig. Die Fontdaten selber sind
komprimiert, mit einem einfachen RLE Verfahren mit 4 Byte Huffman
Tabelle.
Es gibt nun für diese Source in Assembler die vorherige Version zum
download die eben noch in C geschrieben war. Einfach mal suchen.

Gruß Hagen

von Hagen (Gast)


Lesenswert?

Nochwas zu den einzelnen Font Typen:

Fixed Pitch Fonts speichern die Zeichen alle in gleicher Pixelbreite
und Pixelhöhe. Proportionale Fonts speichern die Zeichen in gleicher
Pixelhöhe aber mit verschiedenen Pixelbreiten. Beides sind Bitmap Fonts
und keine TrueTypes. Jeder Proportionel Font kann mit entsprechendem
Treiber auch als fixed Pitch Font dargstellt werden. Meine GLCD macht
genau das.

True Type nun wieder ist eine ganz andere Technik. Statt die Pixel zu
den Zeichen zu speichern, werden die Vektoren der einzelnen Linien,
Bögen, Kreise usw. als Fontdaten gespeichert. Während der Anzeige eines
Zeichens wird nun diese Vektorgrafik des Zeichens scaliert dargstellt.
TrueTypes sind demzufolge für kleine Displays, auf den doch kleinen
AVR's viel zu überdimensioniert. Genaugenommen werden sogar die
Vektoren in einem Buffer scaliert als Bitmap gespeichert. Windows macht
dies in seinem integrierten Font Cache des TTF Mappers.

Gruß Hagen

von Hagen (Gast)


Lesenswert?

Übrigens was Benedikt sagt ist richtig.
Bei Proportionalen Fonts sollten die Fontdaten getrennt von der
Zeichen-Breiten-Tabelle gespeichert werden. Man muß nämlich sehr häufig
als Zeichenoperation auch nur die Breite in Pixeln eines Textes
ausrechnen. Mit getrennten Tabellen ist dies wesentlich effizienter im
Speicher zu halten, als wenn man zu jedem Zeichen bei den Zeichendaten
auch die Breite speichert. Es ist nämlich so das bei proportionalen
Fonts sehr häufig die Zeichen unterschiedliche Datengrößen besitzen.
Mit getrennter Breitentabelle im Header des Fontes, mit zB. 256
Einträgen kann man viel effizienter die Breite eines ganzen Textes in
Pixeln ausrechnen.
Auch über die Speicherung der Bitmap Daten der einzelnen Zeichen sollte
man sich Gedanken machen. Da die Zeichenhöhe fixiert ist ist es am
besten die Bitmapdaten Spaltenweise in den Bytes zu kodieren. Zb. in
einem 1 Bit Monochromen Font wird 1 Pixel in einem Bit gespeichert. Nun
speichert man das Zeichen von Links nach Rechts Spalte für Spalte von
oben nach unten die Bits der einzelnen Pixel. Dies stellte sich im
Falle des Displaycontrollers der Nokia Display als am effizientesten
heraus. Fast jeder gute Displaycontroller kann so programmiert werden
das er in einem Speicherbereich die Addresszeiger der X,Y Koordinaten
selbständig automatisch inkrmentiert. D.h. nach jeder Übertragung der
Pixeldaten werden diese Speicherkoordinaten automatisch auf den
nächsten Pixel gesetzt. Nun braucht man nur noch diesen Speicherzugriff
auf den Controller so einzustellen das er Spalten weise die Zeilen
inkremeniert. Das Speicherfenster wird dabei so gewählt das es exakt
die Zeichenhöhe in Pixel hat.

Gruß Hagen

von Felix Ziegler (Gast)


Lesenswert?

Hallo Hagen

Danke für den Hinweis, habe Deine Fontdateien schon angeschaut.
Das ist in etwa das was ich brauche. Auch der Fonteditor ist supper!

Wenn ich die Font.h Dateien anschaue, frage ich mich wie Du die Daten
zu jedem Zeichen findest, da ja nur die jeweils benötigen Spalten
gespeichert sind. Zählst Du jeweils die Breiten zusammen, oder legst Du
zur Laufzeit eine Tabelle an?

Ist es möglich Dein Sourcecode zu bekommen?

Gruss Felix

von Hagen (Gast)


Lesenswert?

Ok, du must erstmal zwischen unkomprimierten und komprimierten Fonts
unterscheiden.

Alle Fonts haben eine Header der je nach Fonttyp sich leicht
unterscheidet.

    struct FONT {
       uint16_t   FontSize;
       uint8_t    Width;
       uint8_t    Height;
       uint8_t    BitsPerPixels;
       uint8_t    FirstChar;
       uint8_t    LastChar;
       uint8_t    CharWidths[font_Last_Char - font_First_Char +1];

       uint8_t    BytePadding;
       uint8_t    RLETable[3];
       uint8_t    CharSize[font_Last_Char - font_First_Char +1];

       uint8_t    Data[];
    }

FontSize, Width, Height, BitsPerPixel, FirstChar, LastChar, CharWidths
und Data[] sind für beide Fonttypen gleiche. Nur falls der Font
komprimierte Daten enthält wird zusätzlich BytePadding, RLETable[] und
CharSize[] zusätzlich benötigt.

Nun FontSize ist die Größe in Bytes über die kompletten Font Daten
inklusive Header. Damit wird es also möglich im Speicher viele solche
Fonts einfach nacheinander zu speichern. Man benötigt nur die Addresse
zum ersten Font und kann nun mit Hilfe von FontSize von ersten Font zum
nächsten usw. springen.

Width gibt die Pixelbreite der Zeichen im Font an. Sollte also der Font
benutzt werden um einen Text mit fixed Pitch zu zeichnen so wird nun
Width benutzt. Dabei wird aus CharWidths[] die Pixelbreite des Zeichens
geholt. Falls diese kleiner Width ist so wird nun OffsetX = (Width -
CharWidth[Zaichen]) / 2 berechnet um dieses Zeichen so zu zentrieren
das man eine Darstellung erreicht wie mit einem fixed Font.
Sollte man proportional Zeichnen woll so muß man OffsetX einfach auf 0
setzen und man zeichnet das Zeichen mit seine vordefinierten Breite.

Height gib die Höhe der Zeichen im Font an. Per Definition unterstütze
ich nur Fonts mit fixed Hight, alle zeichen haben gleiche Höhe.

BitsPerPixel bestimmt die Farbtiefe, also die Bits die pro Pixel in
Data[] gespeichert werden müssen. Somit wäre BitsPerPixel = 2 bei einem
4 Farben Font, Farbanzahl = 2^BitsPerPixel. Mit 2^BitsPerPixel-1 erhält
man die AND Maske mit der man die aktuellen Datenbits verküpfen muß um
einen Index in eine Farbtabelle zu berechnen. Dies bedeutet das die
tatsächliche Farbe des Fonts über eine Lookuptabelle zur Laufzeit
verändert werden kann.

FirstChar und LastChar definieren den Bereich der Zeichen die im Font
gesepichert wurden. Statt also immer mit einen 256 Zeichen Font zu
kalkulieren wird über diese beiden Werte die effektive Datenmenge
beschränkt. In den meisten Fällen nutzt man in einem Font zB. die
Zeichen #32 bis #127, also nur 96 Stück. Somit werden die nachfolgenden
Arrays[] wir CharWidth[] oder CharSize[] auf 96 Bytes beschränkt statt
eben immer 256 Bytes zu verschwenden. Ideal ist dies bei Symbol/Icons
Fonts, die ja dann nur 1 bis 16 Zeichen enthielten. Man spart somit 1.)
Speicherplatz und 2.) kann viel schneller überprüfen ob ein Zeichen
tatsächlich im Font gespeichert wurde. D.h. ein gültiges Zeichen muß im
Bereich FirstChar bis inklusice LastChar liegen.

CharWidths[] wiederum gibt nun für alle gespeicherten Zeichen die
Pixelbreite vor. Sollte an CharWidth[Zeichen - FirstChar] == 0 stehen
so wurde dieses Zeichen ebenfalls nicht definiert im Font.

Bei unkomprimierten Fonts folgen nun die Font Daten in data[]. Diese
bestehen aus den sequentiellen Bits OHNE Padding etc. Dh. in zB. einem
Monochromen 5x7 Font benötigt man für 1 zeichen also 5x7 = 35 Bits.
Statt nun diese 35 Bits auf 5 Bytes aufzuteilen, also 5 Bits zu
verschwenden beginnt bei meinen Fonts sofort nach dem 35'ten Bit das
nächste Zeichen !! Somit wiederum Speicher gespart. Allerdings erhöht
sich der Aufwand an die richtige Bit-Position zum Zeichen in Data[] zu
springen. Dies erscheint einem nur auf dem ersten Blick so denn im
Grunde kann man dies sehr effizient machen. Da wir ja sowieso einen
Proportionalen Font speichern, also jedes Zeichen kann unterschiedlich
breit sein und somit auch unterschiedlich in der Pixelgröße =
Speichergröße, muß man so oder so ein Seek in die Data[] durchführen.
Bei unkomprimierten Fonts macht man nun folgendes: man Addiert alls
CharWidths[] von 0 bis Zeichen - FirstChar zusammen. Also

PixelBreiten = 0;
for (i = 0; i < Zeichen - FirstChar; i++)
  PixelBreiten += CharWidth[i];

Nun multipliziert man einfach mit Height, also

  BitIndex = PixelBreiten * Height;

Schon haben wir den Bitindex in Data[] zum aktuellen Zeichen. Dieser
wird nun mit

  ByteIndex = BitIndex / 8;
  BitShift = BitIndex mod 8;

In eine Speicheradresse in Data[] umgerechnet. Das erste Byte was man
nun aus dem Speicher lädt muß nur noch Bit-korregiert werden, sprich

  FirstByte = Data[ByteIndex] >> (8 - BitShift);

So, nun steht im FirstByte an Bitindex 0 auch das 1 Bit des aktuellen
Zeichens. Man kann nun einfach nacheinander alle Bits/Bytes nachlade n
und als Farbpixel ausgeben, so lange bis man CharWidth[Zeiche] * Height
Pixel ausgegeben hat.

Wie gesagt dies trifft alles auf unkompirmierte Fonts zu.

Bei komprimierten Fonts kommen noch die Felder BytePadding, CharSizes[]
und RLETable[] im Font Header hinzu. Um einen komprimierten Font von
einem unkomprimeirten zu unterscheiden wurde einfach das oberste Bit
von BitsPerPixel mißbraucht. Ist diese gesetzt, also BitsPerPixel &
0x80 != 0 dann ist der Font komprimiert.

Nun, bei komprimierten Fonts können die einzelnen Zeichen nun
zusätzlich auf grund der Komprimierung sich erheblich in der Größe
unterscheiden, man kann also nicht mehr wie bei Fixen Fonts so ohne
weiteres die Speichergröße des Zeichens ausrechnen um die Zeichendaten
in Data[] zu finden. Deshalb wird CharSize[] benötigt um an die
richtige Stelle in Data[] zu positionieren. In CharSize[] steht also zu
jedem zeichen wieviele Bytes das Zzeichen an Daten benötigt.
ByteSizeZeichen = CharSize[Zeichen] * BytePadding. BytePadding wird
nötig weil es durchaus sein kann das ein großer Font pro zeichen mehr
als 256 Bytes benötigt und somit CharSize[] -> UInt8_t nicht mehr
ausreichen würde. Der FontEditor macht dann folgendes: er erhöht
BytePadding um +1und dividiert die Speichergrößen in bytes durch
2.Falls nun das größte Zeichen im Font 320 Bytes benötigen würde, so
würde BytePadding == 2 sein und in CharSizes[] die reale Speichergröße
der Zeichen dividiert durch 2 stehen. Im Beispiel von 320 Bytes stände
also in BytePadding == 2 und in CharSize[Zeichen] = 160.
Somit verhindern wir das wir nur kleine Zeichen im Font speichern
könnten oder das wir unnötiger Weise das Array CharSize[] als UInt16_t
oder sogar UInt32_t deklarieren müssten.
Alle bisherigen Operationen lassen sich immer noch sehr effizient im
AVR implementieren, also es werden nur Shift oder Muls benötigt.

Nun bei komprimierten Fonts muß man also nur

Index = Font.Data;
for (i = 0; i < Zeichen - FirstChar; i++)
  Index += CharWidth[i];
Index *= BytePadding;

berechnen um auf das 1. Byte desd Zeichens in Data[] zu zeigen.

Die Komprimierung der Daten erfolgt folgender maßen. Man benutzt eine
RLE = Run Lenght Encoding = Längen Kodierung. Angenommen wir haben
einen 4 farbigen Font, also 2 Bits pro Pixel sind nötig um 4 Farben zu
kodieren. Wir analysieren nun Spalte für Spalte Pixel für PIxel das
Zeichen von Oben nach Unten. Dabei zählen wir solange einen Counter
hoch wie die Pixelfarbe die gleiche ist. Angenommen wir würden auf 7
gleichfarbige Pixel nacheinander treffen dann bedeutet dies das RLE nur
speichert 7 mal Farbe x. Es stellt sich nun die Frage WIE man diese 7
als Zahl in den Bit-Datenstrom speichert. Von vornherein wird in meiner
Lib davon ausgegangen das es nur 4 verschiedene Längenangaben geben
kann, also 2 Bits werden benötigt. Somit besteht ein Datenpacket aus 4
Bits, 2 Bits Längenangabe und 2 Bits Farbwert bei einem 4 Farbenfont.
Nun, die 2 Bit Längenangabe könnte so kodiert worden sein {1,2,4,8}
d.h. Länge 0b00 = 1 Pixel, 0b01 = 2 Pixel, 0b10=4 Pixel, 0b11=8 Pixel
mit gleicher Farbe. Im Beispiel von 7 Pixeln gleicher Farbe müsste man
also zerlegen in 4 Pixel + 2 Pixel + 1 Pixel also  10 cc 01 cc 00 cc
und würde somit 12 Bits benötigen um 7 Pixel a 2 Bit zu speichern. Die
7 Pixel Länge wäre aus Sicht der RLETabele[1,2,4,8] schon ein
ziemlicher Worstcase, also schlechter Fall. Denoch statt 7 * 2 = 14 Bit
benötigten wir nur 12 Bit bei komprimierten Daten. Da aber RLETable[]
eben im Font gespeichert wird UND man davon ausgeht das RLETabel[0] = 1
also immer 1 Pixel Länge angibt, wird es möglich diese RLETable[] eben
durch den FontEditor so zu berechnen das dort die Längenangaben stehen
die die maximal besten Komprimierung ergibt. Im Beispiel der 7 Pixel
Länge wäre es nun so das 99% aller Pixellängen gleicher Farbe 7 Pixel
lang wären. Logisch das dann in RLETable[] eben nicht mehr {1,2,4,8}
sondern zB. {1,3,7,15} stehen würden. In diesem Falle wird in den Daten
statt 10 cc 01 cc 00 cc in Bits eben nur noch 10 cc stehen, da ja 0b10
die 7 Pixel Länge kodiert.

D.h. willst du komprimierte Fonts darstellen so sähe dies in entwa so
aus:

BitsInData = ???;
BitsData = ???;  // 16 oder sogar 32 Bit nötig
BitsMask = 0xFF >> (8 - Font.BitsPerPixel);
PixelCount = 0;
PixelColor = 0;
for (x = 0; x < Font.CharWidth[Zeichen]; x++) {
  for (y = 0; y < Font.Height; y++) {
    if (PixelCount == 0) {
      if (BitsInData < 8) {
        BitsData = BitsInData | (Data^ << BitsInData);
        BitsInData += 8;
        Data++;
      }
      if (Font.Compressed) {
      // komprimierte Fonts
        PixelCount = RLETable[BitsData and 3];
        BitsData >>= 2;
        BitsInData -= 2;
        PixelColor = ColorTable[BitsData and BitsMask];
      } else {
      // unkomprimierte Fonts
        PixelCount = 1;
        PixelColor = ColorTable[BitsData and BitsMask];
      }
      BitsData >>= Font.BitsPerPixel;
      BitsInData -= Font.BitsPerPixel;
    }
    if (PixelColor != TRANSPARENT)
      SetPixel(x, y, PixelColor);
    PixelCount--;
  }
}

Dies wären die innersten Schleifen, wobei Data ein Zeiger auf die Daten
des Zeichens schon vorher korrekt berechnet werden muß. Das gleiche gilt
für BitsInData und BitsData. Bei komprimierten Font sind beide Wert ja
immer 0, aber bei unkomprimierten Fonts muß man das 1 Byte auslesen das
die Bits des Zeichen ja nicht an bytegrenze ausgerichtet sein müssen.

Tjo, das wars erstmal. Im Grunde garnicht soooo kompliziert, nur
umständlich von mir ausgedrückt ;)

Achso: natürlich sollte man auf SetPixel(X,Y,Color) verzichten können.
In meiner Lib wird nicht JEDESMALS die Speicheradressen an den
Controller gesendet sondern nur eimalig ein rechteckiger Bereich im RAM
Speicher des LCD selektiert. Desweitern wird der Adressmodus so gesetzt
das nach jedem Senden eines Pixel automatisch Y +1 erhöht wird und
falls dies Font.heightmal gemacht wurde so wird X +1 erhöt und Y -
Font.Height dekrementiert. Das heist der Controller kann einen
recheckigen Bildschirmbereich Pixelweise selektieren und inkremeniert
dann selbstständig die Speicheradresse nach jedem Pixel. Dies bedeutet
eine 2-4 fache Beschleunigung in der Datenübertragung !
Zusätzlich kann man nun noch ein Clipping einbauen, wie in meiner Lib.
D.h. man kann JEDE Ausgabe auf dem LCD beschneiden innerhalb eines
Rechtecks. Alle Ausgaben ausserhalb dieses Rechteckes werden
beschnitten, also ge-clipt ;) Solche Operationen sind immer dann
sinnvoll wenn man Windows auf dem LCD darstellen will. Man kann so ein
und dieselbe komplexe Zeichenroutine, die Texte/Linien/Kreise etc in
einem Window ausgibt, und dann über das Clipping nur teilweise ungültig
gewordene Bereiche neu-zeichnen lassen. Also wie bei MS Windows
überlappt ein Popup-Menu mein Hauptfenster. Wird das Popup-menu
unsichtbar gemacht so bedeutet dies das man dessen rechteckigen Bereich
im Haupt-Fenster neu darstellen muß. Über das Clipping justiert man nun
exakt diesem Bereich und ruft dann die reguläre Zeichenroutine des
Haupt-Fensters auf. Da über das Clipping nur der kleinere Bereich
gezeichnet wird steigt die Performance in der Neudarstellung. Will man
also komplexere grafische Operationen mit einer Grafik Lib durchführen
so sollte diese Lib auf jeden Fall auch ein Clipping enthalten.

Man, das war schon wieder mal viel zu viel Text, sorry.

Gruß Hagen

von Hagen (Gast)


Lesenswert?

Sorry hab ich übersehen:

> Ist es möglich Dein Sourcecode zu bekommen?

Welchen ? den Fonteditor ?

Gruß hagen

von Felix Ziegler (Gast)


Lesenswert?

Hallo Hagen

Vielen Dank für Deine Hilfe.
Ich hatte anfangs etwas bedenken, wenn mit einer Schlaufe jedes mal
alle CharWidth[i] zusammen gezählt werden.
Ich überlege mir da eine weitere Tabelle mit den Startadressen der
Pixeldaten anzulegen. Denn bei mir ist Rechenzeit wichtiger als
Speicherplatz, da der uP ja noch anderes zu machen hat.
Aber das sind Optimierungen die dann immer noch gemacht werden können.

Ich bin am Sourcecode des GLCD Library interesiert, da er mir die
implementierung meines Treibers erleichtern würde.
Aber ich denke mit Deinen obigen erläuterungen schaffe ich das schon.
Ist ja nocht meine erste Software.

Vielen Danke und freudliche Grüsse

Felix

PS: bin bis 5.8.04 weg...

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.