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!
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...
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
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
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.
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
Das einfachste wäre zusätzlich zu der Buchstabentabelle eine weitere zu verwenden, in der die Breite jedes Buchstaben angegeben ist.
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
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
Ü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
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
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
Sorry hab ich übersehen:
> Ist es möglich Dein Sourcecode zu bekommen?
Welchen ? den Fonteditor ?
Gruß hagen
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.