Czcionka wektorowa

Powrót do spisu treści

Wymagane jest zapoznanie się z następującymi podrozdziałami:

P019 - Pierwszy program dla Windows
OL031 - instalacja biblioteki SDL w środowisku Dev-C++
OL032 - inicjalizacja biblioteki SDL
OL033 - powierzchnie graficzne w SDL
OL034 - przygotowanie plików własnej biblioteki graficznej
OL035 - rysowanie po powierzchni graficznej
OL036 - algorytm Bresenhama rysowania odcinka
OL037 - obcinanie grafiki do prostokąta
OL038 - podstawowe algorytmy wypełniania obszarów
OL039 - algorytm Smitha
OL040 - praca w środowisku sterowanym zdarzeniami
OL041 - czcionka bitmapowa

UWAGA!!!

Bieżące opracowanie NIE JEST KURSEM programowania biblioteki SDL. Są to materiały przeznaczone do zajęć na kole informatycznym w I LO w Tarnowie.

Przed pracą z tym rozdziałem utwórz w środowisku Dev-C++ nowy projekt SDL i dołącz do niego pliki SDL_gfx.cpp oraz SDL_gfx.h. Jeśli nie przeczytałeś zalecanych rozdziałów, koniecznie zrób to, a wszystko stanie się jasne. W przeciwnym razie odpuść sobie również ten rozdział. Opis funkcji bibliotecznych znajdziesz w podsumowaniu SDL007.

 

Artykuł nie jest już rozwijany


obrazek

Na poprzednich zajęciach utworzyliśmy prosty system rysowania czcionek bitmapowych, czyli takich, które przechowują kształt poszczególnych literek w matrycach bitmapowych. Czcionki bitmapowe doskonale nadają się do rysowania literek w ich naturalnej wielkości. Sytuacja się jednak pogarsza przy skalowaniu literek. Wtedy uwidaczniają się piksele zawarte w matrycach - czcionka po prostu wygląda nieładnie. Oczywiście można przygotować zestawy znaków bitmapowych obejmujące różne wielkości literek, np. matryce 8x8, 8x10, 9x12, 10x16 itd. To dużo pracy.

Na dzisiejszej lekcji wprowadzimy drugi rodzaj czcionek - czcionki wektorowe. Czcionki wektorowe można bez utraty jakości pwiększać. Nowe kroje pisma będą korzystały z tych samych funkcji co czcionki bitmapowe - po prostu dopiszemy w funkcjach obsługę krojów wektorowych. Dzięki temu rozwiązaniu program nie musi rozróżniać rodzaju czcionki - dokonywane to będzie automatycznie w funkcjach obsługi krojów pisma.

Na początek omówimy sposób rysowania literek wektorowych. Literki będą rysowane na prostokątnej siatce:

obrazek

W obrębie siatki rysujemy znak za pomocą ciągu łamanych:

obrazek

W powyższym przykładzie mamy dwie łamane, które zaznaczono dwoma różnymi kolorami kreski (w rzeczywistym znaku będzie to oczywiście jeden kolor). Łamane zbudowane są z ciągu odcinków połączonych końcami. Zatem łamaną można zdefiniować jako ciąg punktów wyznaczających kolejne wierzchołki. Dla narysowanej powyżej literki A będą to punkty:

łamana niebieska : (1,8) - (1,3) - (3,1) - (5,1) - (7,3) - (7,8)
łamana zielona : (1,6) - (7,6)

Łamane można w prosty sposób zdefiniować podając liczbę tworzących je punktów wierzchołkowych oraz kolejne współrzędne tych punktów. Definicje łamanych mogą występować jedna po drugiej. Ciąg ten będzie zakończony liczbą 0 - żadna łamana nie może przecież składać się z 0 wierzchołków. Według tej zasady przykładowa literka A będzie reprezentowana przez ciąg liczb:

6 1 8 1 3 3 1 5 1 7 3 7 8 2 1 6 7 6 0

Profesjonalne czcionki (np. Windows True Type) do opisu kształtu znaków wykorzystują krzywe wektorowe Beziera pozwalające na stosowanie różnych łuków. Dzięki temu czcionki nie posiadają kanciastych kształtów. Pamiętaj jednakże, iż nasz system jest systemem uproszczonym (nawet bardzo uproszczonym) i zrozumienie zasad jego działania nie wymaga doktoratu z grafiki komputerowej. Na nasze potrzeby zupełnie wystarczy, a zdolna osoba może uzyskiwać całkiem interesujące efekty!

W pamięci czcionka wektorowa będzie reprezentowana przez znaną nam z poprzedniej lekcji strukturę gfxFont.

typedef struct
{
  Uint32 type;
  Uint32 w, h;
  Uint8 * cp[256];
  Uint32  cw[256];
} gfxFont;  

Dla czcionki wektorowej poszczególne pola tej struktury posiadają następujące znaczenia:

type  - jeśli zawiera 0, to czcionka jest bitmapowa. Jeśli zawiera 1, to czcionka jest wektorowa.
w  -  określa szerokość siatki wektorowej
h  - określa wysokość siatki wektorowej
cp[ ]  - tablica 256 wskaźników definicji poszczególnych znaków. Definicja jest ciągiem łamanych zakończonym kodem 0.
cw[ ]  - tablica szerokości poszczególnych znaków. Indeks jest kodem ASCII znaku. Szerokość podana jest w pikselach i określa faktyczną szerokość danego znaku.

Różnice w stosunku do wersji bitmapowej obejmują pole w, które dla czcionki wektorowej wyraża maksymalną szerokość siatki wykorzystywanej do rysowania znaku oraz tablicę wskaźników cp[], które dla czcionki wektorowej wskazują ciągi łamanych tworzących znak a nie matryce bitmapowe.

Budowa pliku definiującego czcionkę wektorową

Plik
definicji czcionki
Opis
V
8 12
65 8
6 0 9 0 4 2 2 3 2 5 4 5 9
2 0 7 5 7
0
66 8
10 0 2 0 9 4 9 5 8 5 6 4 5 5 4 5 3 4 2 0 2
2 0 5 4 5
0
67 8
8 5 4 3 2 2 2 0 4 0 7 2 9 3 9 5 7
0
0

Powyższy plik definiuje trzy literki:

A - kod 65
B - kod 66
C - kod 67

Na początku pliku, w pierwszym wierszu podajemy rodzaj czcionki. Możliwe są dwa warianty:

B - czcionka bitmapowa
V - czcionka wektorowa, właściwie może tutaj być dowolna litera, różna od B.

W drugim wierszu są dwie liczby całkowite. Pierwsza z nich określa maksymalną szerokość siatki, na której rysowane są poszczególne literki. Druga liczba określa maksymalną wysokość tej siatki.

W następnych wierszach znajdują się definicje znaków. Każda definicja rozpoczyna się od dwóch liczb:
   - kodu ASCII literki
   - rzeczywistej szerokości znaku

Jeśli kod ASCII jest równy 0, to oznacza to koniec definicji. Literki nie muszą być definiowane alfabetycznie (chociaż to jest zalecane) i definicje mogą się powtarzać - w takim przypadku w strukturze gfxFont będzie umieszczona ostatnia definicja. Pozwala to na testowanie nowych kształtów literek bez utraty starych,

Definicja kształtu jest ciągiem łamanych zakończonym liczbą 0. Każda łamana rozpoczyna się od liczby informującej o liczbie wierzchołków łamanej, następnie występuje odpowiednia liczba współrzędnych x i y tych wierzchołków. Definicje łamanych można podawać w jednym lub w kilku wierszach - nie ma to większego znaczenia - dla czytelności proponuję jednak każdą łamaną umieszczać w osobnym wierszu. Zakres współrzędnych jest ograniczony od 0 do 255.

Plik czcionki wektorowej można przygotować w notatniku, jednakże jest to trudniejsze niż w przypadku pliku czcionki bitmapowej, ponieważ nie widzimy kształtów literek. Przydałby się tutaj program edytora czcionek - być może coś takiego zaprogramujemy.

Odczyt pliku z dysku

Do odczytania pliku czcionki wektorowej wykorzystamy funkcję gfxOpenFont(). Tworząc ją na poprzednich zajęciach zarezerwowaliśmy w niej miejsce dla fragmentu odczytującego czcionkę wektorową. Algorytm jest następujący:

Wejście

file  -  plik tekstowy zawierający definicję czcionki wektorowej - plik jest już otwarty i pierwszy znak odczytany

Wyjście

font  -  zainicjowana struktura gfxFont

Zmienne pomocnicze

code  - przechowuje kod przetwarzanego znaku
q  - lista przechowująca dane odczytane z pliku
plen  - przechowuje liczbę wierzchołków łamanej
x,y  - współrzędne wierzchołka łamanej

Lista kroków

K01 font→type ← 1 ; ustawiamy rodzaj struktury na czcionkę wektorową
K02: Odczytaj z file do font→w i font→h ; odczytujemy z pliku szerokość i wysokość siatki
K03:     Odczytaj z file do code ; czytamy kod znaku
K04:     Jeśli code = 0, zakończ ; koniec pliku, kończymy
K05:     Usuń zawartość font→cp[code] ; jeśli znak był już definiowany, usuwamy jego poprzednią definicję
K06     Odczytaj z file do font→cw[code] ; odczytujemy szerokość znaku
K07         Odczytaj z file do plen ; czytamy liczbę wierzchołków łamanej
K08         Dodaj plen na koniec q ; umieszczamy ją w kolejce
K09         Jeśli plen = 0, idź do K14 ; przerywamy pętlę przy końcu definicji znaku
K10         Powtórz plen razy kroki K11...K12  
K11             Odczytaj z file do x i y ; odczytujemy współrzędne wierzchołka
K12             Dodaj x i y na koniec q ; i umieszczamy je na liście
K13         Idź do K07 ; kontynuujemy pętlę odczytu łamanych
K14     Zarezerwuj w font→cp[code] pamięć
    o rozmiarze q
; miejsce na definicję znaku
K15     Przepisz q do font→cp[code] ; definicję przepisujemy do struktury font
K16     Wyzeruj q ; czyścimy listę
K17     Idź do K03 ; kontynuuje pętlę odczytu definicji znaków

Na początku pliku SDL_gfx.cpp dopisz czerwony tekst:

// I Liceum Ogólnokształcące
// w Tarnowie
// Koło informatyczne 2007
//
// Biblioteka procedur graficznych dla trybów 32 bitowych
//-------------------------------------------------------

#include <SDL/SDL_gfx.h>
#include <stack>
#include <fstream>
#include <list>

using namespace std;

Znajdź funkcję gfxOpenFont() i wyszukaj w niej pokazany na czerwono fragment :

    delete [] lbuf;  // zwalniamy pamięć
    delete [] tbuf;
  }
  else

  {  //*** tutaj obsługujemy czcionkę wektorową ***

  }

  file.close();    // zamykamy plik

  return font;
}

Poniższy tekst wpisz pomiędzy czerwone klamerki w funkcji gfxOpenFont().

    list<Uint8>q;                 // lista wierzchołków łamanych
    int plen, x, y;
    
    font->type = 1;               // typ - czcionka wektorowa
    file >> (font->w) >> (font->h); 
    while(true)                   // pętla odczytu definicji znaków
    {
      file >> code;
      if(!code) break;
      if(font->cp[code])
      {
        delete [] font->cp[code]; // usuwamy poprzedni wpis
        font->cp[code] = NULL;
      }
      file >> (font->cw[code]);
      while(true)                 // pętla odczytu łamanych
      {
        file >> plen;
        q.push_back(plen);
        if(!plen) break;
        for(i = 0; i < plen; i++) // pętla odczytu wierzchołków
        {
          file >> x >> y;
          q.push_back(x); q.push_back(y);
        }
      }
      cp = font->cp[code] = new Uint8[q.size()];      
      for(list<Uint8>::iterator z = q.begin(); z != q.end(); z++)
        * (cp++) = * z;
      q.clear();
    }

Rysowanie literek wektorowych na powierzchni graficznej

Poniżej umieściliśmy algorytm fragmentu funkcji gfxDrawChar(), który dotyczy omawianych tutaj liter wektorowych:

Wejście

screen  -  wskaźnik struktury SDL_Surface
font  - wskaźnik zainicjowanej struktury gfxFont
c  - znak do narysowania
x,y  - współrzędne lewego górnego narożnika siatki znaku
fcolor  - kolor tuszu.
bcolor  - kolor tła, jeśli równy -1, to nie będzie użyty - tło przezroczyste

Wyjście

Narysowanie znaku c na pozycji x,y powierzchni graficznej. Ponieważ funkcja jest bardzo prosta, cały znak powinien się mieścić w obszarze graficznym ekranu. Funkcja zwraca szerokość narysowanej literki, którą można wykorzystać do odpowiedniej zmiany współrzędnej x dla następnego znaku.

Zmienne pomocnicze

pp  - wskaźnik listy zawierającej definicje łamanych
plen  - przechowuje liczbę wierzchołków dla łamanej
xp,yp  - współrzędne wierzchołka łamanej
w  - zawiera szerokość narysowanego znaku

Lista kroków

K01 pp ←font→cp[c] ; w pp ustawiamy adres początku listy zawierającej definicje łamanych
K02: Jeśli pp = NULL, zakończ ; jeśli nie ma definicji łamanych, kończymy
K03:     w ← font→cw[c] ; do w wstawiamy szerokość znaku
K04:     Jeśli bcolor = -1, idź do K06 ; jeśli tło przezroczyste, nie rysujemy go
K05     Narysuj wypełniony prostokąt
    w kolorze bcolor na współrzędnych
    x,y i o wymiarach:
    szerokość = w, wysokość = font→h
; rysujemy prostokąt w kolorze tła pokrywający siatkę znaku
K06         Odczytaj plen z adresu pp ; odczytujemy liczbę wierzchołków w łamanej
K07         pp ← pp + 1 ; przesuwamy adres na następny element listy
K08         Jeśli plen = 0, zakończ ; jeśli koniec listy, kończymy przetwarzanie znaku
K09         Odczytaj xp z adresu pp ; pobieramy współrzędną x wierzchołka
K10         pp ← pp + 1 ; następny element listy
K11         Odczytaj yp z adresu pp ; pobieramy współrzędną y wierzchołka
K12         pp ← pp + 1  
K13         Ustaw początek linii na x+xp, y+yp     
K14         Narysuj punkt x+xp, y+yp w kolorze fcolor ; rysujemy punkt początkowy
K15         plen ← plen - 1 ; pobraliśmy jeden wierzchołek, zmniejszamy stan licznika
K16         Dopóki plen > 0, wykonuj kroki K17...K22 ; w pętli pobieramy kolejne wierzchołki łamanej i rysujemy do nich linie w kolorze fcolor
K17             Odczytaj xp z adresu pp  
K18             pp ← pp + 1  
K19             Odczytaj yp z adresu pp  
K20             pp ← pp + 1  
K21             Rysuj linię do x+xp,y+yp w kolorze fcolor  
K22             plen ← plen - 1  
K23 Zakończ  

Na podstawie powyższego algorytmu uzupełnimy funkcję gfxDrawChar() o obsługę czcionek wektorowych. W tym celu odszukaj w pliku SDL_gfx.cpp poniższy fragment funkcji gfxDrawChar():

 }
  else

  {  // *** wykonujemy część algorytmu dla czcionki wektorowej ***


  }

  return w; 
} 

Pomiędzy czerwone klamerki wpisz następujący kod:

    Uint8 * pp = font->cp[c];
    Uint32 plen, xp, yp;
    if(pp)
    {
      w = font->cw[c];
      if(bcolor != -1)  // rysujemy tło znaku
      {
        SDL_Rect r;
        r.x = x; r.y = y;
        r.w = w; r.h = font->h;
        SDL_FillRect(screen, &r, bcolor);
      }
      while(plen = * (pp++))
      {
        xp = x + * (pp++); yp = y + * (pp++);
        gfxMoveTo(xp, yp);
        gfxPlot(screen, xp, yp, fcolor);
        while(--plen)
        {
          xp = * (pp++); yp = * (pp++);
          gfxLineTo(screen, x + xp, y + yp, fcolor); 
        }
      } 
    }

Do przetestowania funkcji rysowania czcionek wektorowych przygotowaliśmy prosty program testowy, który odczytuje definicję czcionki z pliku vecprop9x12.fnt (skopiuj ten plik do swojego katalogu projektowego) i wyświetla wszystkie zdefiniowane w nim litery. Program kończy się po naciśnięciu klawisza ESC lub przycisku zamykania aplikacji.

// I Liceum Ogólnokształcące
// w Tarnowie
// Koło informatyczne
//
// P027 - wyświetlanie tekstu wektorowego
//---------------------------------------

#include <SDL/SDL_gfx.h>

const int SCRX = 320;      // stałe określające szerokość i wysokość
const int SCRY = 240;      // ekranu w pikselach

int main(int argc, char * argv[])
{

  if(SDL_Init(SDL_INIT_VIDEO)) exit(-1);

  atexit(SDL_Quit);

  SDL_Surface * screen;
  
  if(!(screen = SDL_SetVideoMode(SCRX, SCRY, 32, SDL_HWSURFACE))) exit(-1);

// odczytujemy z pliku definicje czcionki wektorowej

  gfxFont * font = gfxOpenFont("vecprop9x12.fnt");

  if(SDL_MUSTLOCK(screen)) if(SDL_LockSurface(screen) < 0) exit(-1);

// Wyświetlamy napis:
               
  char * t = "Przykładowa czcionka wektorowa";
                   
  Sint32 x = screen->w >> 1;
  Sint32 y = 0;
  
  gfxDrawText(screen, font, t, x - (gfxTextLength(font, t) >> 1), y, 0xffff00, -1);
    
// po kolei wyświetlamy wszystkie literki czcionki

  x = font->w;
  y = 3 * font->h;
  for(int i = 32; i < 256; i++)
  {
    if(gfxDrawChar(screen, font, i, x, y, 0xffffff, -1)) x += 2 * font->w;
    if(x >= screen->w - 2 * font->w)
    {
      x = font->w; y += 2 * font->h;     
    }
  }
  
  t = "(C)2007 Koło Informatyczne I-LO";
  gfxDrawText(screen, font, t, screen->w - gfxTextLength(font, t),
              screen->h - font->h, 0xff0000,-1);
              
  if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);

// odświeżamy ekran

  SDL_UpdateRect(screen, 0, 0, 0, 0); 

  bool running = true;
  SDL_Event event;
  
// czekamy na zakończenie działania programu

  while(running && SDL_WaitEvent(&event))
  {
      switch(event.type)
      {
        case SDL_KEYDOWN: if(event.key.keysym.sym != SDLK_ESCAPE) break;
        case SDL_QUIT:    running = false;
                          break;
      }
  }

// zamykamy czcionkę

  gfxCloseFont(font);

  return 0;
}

obrazek


Podsumowanie


   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2024 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.

Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl

W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe