PREMESSE
Questa guida richiede la conoscenza del linguaggio C e dei concetti base della libSDL.
Consiglio, a riguardo, la lettura di un buon libro su tale linguaggio e delle prime guide di
M3xican
[2] su tale libreria, dal momento che non verranno fatti approfondimenti particolari su
questi due argomenti.
Segnalo, a titolo informativo, che la presente guida, ed il programma allegato, sono
stati realizzati su piattaforma Linux (MDK 10, con GCC 3.3.2 e libSDL 1.2.7).
Si segnala che l'articolo è stato realizzato durante lo sviluppo di
Mars, Land of No Mercy, un gioco
strategico a turni opensource.
SDL_ttf
Per disegnare del testo con le libSDL basta trattare le lettere come una qualsiasi altra immagine.
Ed infatti è così che si faceva, si disegnava un'immagine molto lunga, una striscia insomma, contenente in serie
tutte le lettere dell'alfabeto, i numeri, ed ogni carattere che l'autore si aspettava di usare nel suo programma.
Questo approccio, benché corretto dal punto di vista logico (era l'unico modo per scrivere con le libSDL),
aveva una seria limitazione, non era flessibile per niente.
Il font, lo stile, la dimensione ed il set di caratteri è fisso, se per caso l'autore si accorgeva di aver
bisogno di un altro carattere, di un font diverso, o più grande, o in corsivo, aveva bisogno di creare
un altra striscia, contenente le nuove lettere.
La SDL_ttf
[4] viene incontro a tutte queste problematiche, e, come vedremo, le risolve del tutto.
Consiglio vivamente la lettura della guida online
[5] della SDL_ttf per approfondire le molte funzioni
non usate né accennate in questo programma di esempio.
Sorgente
Ecco di seguito il sorgente del programma che andremo poi ad analizzare con calma nelle
prossime sezioni, dategli un primo sguardo.
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <SDL/SDL.h>
#include <SDL/SDL_ttf.h>
#define DIM_W 800
#define DIM_H 600
#define CENTER_X DIM_W/2
#define CENTER_Y DIM_H/2
# define M_PI 3.14159265358979323846 /* non tutti i math.h definiscono il pi
greco */
int main(int argc, char **argv)
{
SDL_Surface *screen;
Uint32 black;
TTF_Font *font;
int height, width;
SDL_Color text_color;
char *string, *default_str = "Scritta con movimento sinusoidale";
int size;
char letter[2];
SDL_Surface **letter_surf;
SDL_Rect *letter_rect;
char *font_file;
int i, dir=1;
double angle = 0;
int xpos, first_x;
int ypos;
/* Operiamo il parsing dei parametri dati da shell */
if (argc == 1) {
font_file = "Neuropol.ttf";
size = 20;
string = default_str;
}
else if (argc == 4) {
font_file = argv[1];
if ((sscanf(argv[2], "%d", &size)) != 1) {
printf("Il secondo argomento dev'essere un intero\n");
exit(-1);
}
string = argv[3];
}
else {
printf("La sintassi corretta è: %s font dimensione stringa\n", argv[0]);
exit(-1);
}
/* Allocazione dinamica delle strutture in base al numero di lettere */
letter_surf = (SDL_Surface **)malloc(sizeof(SDL_Surface *) * strlen(string))
;
letter_rect = (SDL_Rect *)malloc(sizeof(SDL_Rect) * strlen(string));
/* Inizializzazione del sottosistema video */
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
printf("Errore init SDL: %s\n", SDL_GetError());
exit (-1);
}
/* All'uscita del programma viene eseguito SDL_Quit per risistemare le cose
*/
atexit(SDL_Quit);
/* Apriamo una finestra per disegnarci dentro */
if ((screen = SDL_SetVideoMode(DIM_W, DIM_H, 0, SDL_HWSURFACE|SDL_DOUBLEBUF)
) == NULL) {
printf("Errore apertura video: %s\n", SDL_GetError());
exit (-1);
}
/* Inizializziamo la SDL_ttf */
if(TTF_Init()) {
printf("Errore TTF_Init: %s", TTF_GetError());
exit (-1);
}
/* Apriamo il font */
if((font = TTF_OpenFont(font_file, size)) == NULL) {
printf("Errore apertura del font: %s", TTF_GetError());
exit (-1);
}
/* Mappiamo il colore di sfondo e del testo */
black = SDL_MapRGB(screen->format, 0x00, 0x00, 0x00);
text_color.r = 0xf2;
text_color.g = 0x5b;
text_color.b = 0x09;
/* Ci tornerà utile conoscere le dimensioni della superficie dell'intero
testo */
TTF_SizeText(font, string, &width, &height);
/* Creazione delle superfici per le singole lettere */
for (i=0; i < strlen(string); i++) {
letter[0] = string[i];
letter[1] = '\0';
letter_surf[i] = TTF_RenderText_Blended(font, letter, text_color);
letter_rect[i].w = letter_surf[i]->w;
letter_rect[i].h = letter_surf[i]->h;
}
/* Scroll dal basso del testo */
ypos = screen->h;
xpos = CENTER_X - width/2;
while (ypos > CENTER_Y) {
for (i=0; i < strlen(string); i++) {
letter_rect[i].x = xpos;
letter_rect[i].y = ypos;
xpos += letter_rect[i].w;
SDL_BlitSurface(letter_surf[i], NULL, screen, &letter_rect[i]);
}
xpos = CENTER_X - (width/2);
ypos -= 2;
SDL_Flip(screen);
SDL_Delay(20);
for (i=0; i < strlen(string); i++)
SDL_FillRect(screen, &letter_rect[i], black);
}
/* Sinus scroller */
first_x = xpos;
while (angle <= 360*8) {
for (i=0; i < strlen(string); i++) {
if (i==0)
xpos = first_x;
letter_rect[i].x = xpos;
xpos += letter_rect[i].w;
ypos = CENTER_Y + sin(M_PI/180 * (angle + i*15)) * height;
letter_rect[i].y = ypos;
SDL_BlitSurface(letter_surf[i], NULL, screen, &letter_rect[i]);
}
angle += 7;
SDL_Flip(screen);
SDL_Delay(20);
/* Rimbalzo agli estremi laterali dello schermo */
if (xpos > screen->w)
dir = -1;
if (first_x < 0)
dir = 1;
first_x += dir*3;
for (i=0; i < strlen(string); i++)
SDL_FillRect(screen, &letter_rect[i], black);
}
/* Liberiamo le superfici */
for (i=0; i < strlen(string); i++)
SDL_FreeSurface(letter_surf[i]);
/* Liberiamo le strutture create dinamicamente */
free(letter_surf);
free(letter_rect);
/* Chiudiamo font e librerie */
TTF_CloseFont(font);
TTF_Quit();
SDL_Quit();
return 0;
}
Spiegazione del sorgente
Il main
In questa sezione verrà spiegato in dettaglio il sorgente che vi è stato
presentato poco sopra, con particolare attenzione ai concetti nuovi introdotti
dalla SDL_ttf.
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <SDL/SDL.h>
#include <SDL/SDL_ttf.h>
#define DIM_W 800
#define DIM_H 600
#define CENTER_X DIM_W/2
#define CENTER_Y DIM_H/2
# define M_PI 3.14159265358979323846 /* non tutti i math.h definiscono il pi
greco */
Le righe 1-5 includono gli header per le librerie che saranno usate, da notare la riga 5 con
l'include specifico per la SDL_ttf.
Le righe dalla 7 alla 11 rappresentano i define adoperati nel programma, la dimensione dello schermo, le
coordinate del suo centro, ed il valore del pi greco, che come si legge nel commento, non è definito
sempre all'interno del math.h.
int main(int argc, char **argv)
{
SDL_Surface *screen;
Uint32 black;
Inizia il main, con le dichiarazioni (righe 15 e 16) riguardanti la superficie dello schermo e
il suo colore di sfondo.
TTF_Font *font;
int height, width;
SDL_Color text_color;
char *string, *default_str = "Scritta con movimento sinusoidale";
int size;
char letter[2];
SDL_Surface **letter_surf;
SDL_Rect *letter_rect;
char *font_file;
Successivamente incontriamo le prime dichiarazioni relative alla SDL_ttf.
La riga 18 dichiara un puntatore alla struttura principale della libreria, la TTF_Font, quella che conterrà
tutti i dati sul font caricato, successivamente ecco due interi che andranno a memorizzare altezza e
larghezza della superficie contenente la stringa di testo da disegnare, poi una struttura SDL_Color con il
colore del testo.
La riga 21 dichiara la stringa contenente il testo da disegnare, string viene riempito con
l'argomento passato da shell, altrimenti viene usato la stringa di default, size è l'intero che indica a
quale grandezza caricare il font.
La righe 23 dichiara la ministringa da due caratteri che conterrà una singola lettera ed il
carattere di terminazione stringa, le due righe dopo, invece, dichiarano la superficie ed il rettangolo
di clip relative a tale lettera.
Per gestire il movimento sinusoidale del testo, abbiamo bisogno di poter controllare le singole lettere,
ecco perché useremo un array di superfici sdl e di rettangoli di clip. Notate, proprio perché andremo ad
usare un array, che è dichiarato un puntatore ai rettangoli ed un puntatore a puntatore per le superfici.
Per finire, alla riga 26 incontriamo un puntatore a carattere che conterrà il nome del font.
int i, dir=1;
double angle = 0;
int xpos, first_x;
int ypos;
Le righe 28-31 sono relative al codice che opera effettivamente il movimento sinusoidale e lo scroll
delle lettere.
Vengono dichiarati un contatore, una flag di direzione, un float double per l'angolo e degli interi che
memorizzano le coordinate della lettera da muovere in quella particolare iterazione del ciclo.
/* Operiamo il parsing dei parametri dati da shell */
if (argc == 1) {
font_file = "Neuropol.ttf";
size = 20;
string = default_str;
}
else if (argc == 4) {
font_file = argv[1];
if ((sscanf(argv[2], "%d", &size)) != 1) {
printf("Il secondo argomento dev'essere un intero\n");
exit(-1);
}
string = argv[3];
}
else {
printf("La sintassi corretta è: %s font dimensione stringa\n", argv[0]);
exit(-1);
}
Le righe 34-50, come spiega anche il commento, operano il parsing dei parametri dati dal terminale.
Se non ci sono argomenti da processare (righe 34-38), vengono assegnati i parametri di default per quanto
riguarda il nome del file del font, la sua dimensione e la stringa da animare.
Se invece vengono forniti tutti gli argomenti (righe 39-46), il programma assegnerà i parametri in
base a questi. Da notare un semplice check dell'argomento che specifica la dimensione del font fatto tramite
uno sscanf() al rigo 41.
In un caso diverso dai due precedenti (righe 47-50) viene stampato un messaggio informativo sulla
sintassi corretta da utilizzare.
/* Allocazione dinamica delle strutture in base al numero di lettere */
letter_surf = (SDL_Surface **)malloc(sizeof(SDL_Surface *) * strlen(string))
;
letter_rect = (SDL_Rect *)malloc(sizeof(SDL_Rect) * strlen(string));
Ecco l'allocazione dinamica degli array (di grandezza uguale al numero di lettere della stringa da
animare) ed il conseguente assegnamento dei puntatori per le superfici ed i rettangoli di clip delle lettere.
/* Inizializzazione del sottosistema video */
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
printf("Errore init SDL: %s\n", SDL_GetError());
exit (-1);
}
/* All'uscita del programma viene eseguito SDL_Quit per risistemare le cose
*/
atexit(SDL_Quit);
/* Apriamo una finestra per disegnarci dentro */
if ((screen = SDL_SetVideoMode(DIM_W, DIM_H, 0, SDL_HWSURFACE|SDL_DOUBLEBUF)
) == NULL) {
printf("Errore apertura video: %s\n", SDL_GetError());
exit (-1);
}
Ormai questo scorcio di codice dovrebbe esservi molto familiare, viene inizializzato il sottosistema
video delle SDL, fissata l'esecuzione della SDL_Quit() all'uscita del programma e aperta una finestra in base
ai define delle righe 7 ed 8.
/* Inizializziamo la SDL_ttf */
if(TTF_Init()) {
printf("Errore TTF_Init: %s", TTF_GetError());
exit (-1);
}
Ecco le prime due funzioni che incontriamo della SDL_ttf. La TTF_Init() inizializza la libreria, come
ci potevamo aspettare, ritorna 0 in caso di successo e -1 in caso contrario.
Se l'inizializzazione non va a buon fine, viene stampato un messaggio di errore usufruendo della TTF_GetError(),
funzione analoga alla SDL_GetError(), dopodiché si esce dal programma.
/* Apriamo il font */
if((font = TTF_OpenFont(font_file, size)) == NULL) {
printf("Errore apertura del font: %s", TTF_GetError());
exit (-1);
}
Dopo aver inizializzato la libreria, è tempo di caricare il font, lo facciamo con la TTF_OpenFont().
Il suo utilizzo è molto semplice, le diamo come parametri una stringa contenente il percorso del font e un
intero che indica la sua dimensione. Anche qui, in caso di errore, stampiamo un messaggio e poi usciamo.
/* Mappiamo il colore di sfondo e del testo */
black = SDL_MapRGB(screen->format, 0x00, 0x00, 0x00);
text_color.r = 0xf2;
text_color.g = 0x5b;
text_color.b = 0x09;
Al rigo 84 definiamo il colore nero, che sarà il colore di sfondo dello schermo, mentre alle righe
86-88 riempiamo la struttura text_color per definire il colore del testo.
/* Ci tornerà utile conoscere le dimensioni della superficie dell'intero
testo */
TTF_SizeText(font, string, &width, &height);
La TTF_SizeText(), dopo che gli avremo indicato la struttura del font appena caricato e la stringa che
useremo per il testo, calcolerà la dimensione della superficie SDL risultante, e ritornerà la sua larghezza ed
altezza in due interi, passati alla funzione per riferimento.
Da notare che per far si che tali dati siano corretti, quando stamperemo successivamente le singole lettere
della stringa, non dovremo aggiungere nessuno spazio tra loro, visto che questo è già presente nella superficie.
/* Creazione delle superfici per le singole lettere */
for (i=0; i < strlen(string); i++) {
letter[0] = string[i];
letter[1] = '\0';
letter_surf[i] = TTF_RenderText_Blended(font, letter, text_color);
letter_rect[i].w = letter_surf[i]->w;
letter_rect[i].h = letter_surf[i]->h;
}
Le righe 94-100 rappresentano un ciclo che serve a riempire effettivamente gli array di rettangoli e
superfici definiti all'inizio.
La prima cosa da fare è isolare le singole lettere in tante piccole stringhe (con il loro carattere di
terminazione), ed è quello che viene fatto alle righe 95-96.
La riga successiva merita più attenzione, perché è in questa che viene usata la funzione che
effettivamente traduce la stringa in superficie SDL, che "renderizza" le lettere.
Le funzioni principali per disegnare il testo sono 3, la TTF_RenderText_Solid(), la TTF_RenderText_Shaded(), e
la TTF_RenderText_Blended(). La prima disegna il testo in un sol colore, la seconda presenta una qualità maggiore,
ed usa due colori, uno per il background ed uno per il foreground, mentre la terza lo disegna usando l'alpha
blending per sfumarne il colore.
Poiché il testo che useremo non è dinamico, nel senso che non varia, viene renderizzato all'interno delle
superfici SDL solo una volta, possiamo usare la terza funzione, quella a qualità maggiore, ma anche la più
dispendiosa in termini di tempo e potenza di calcolo.
Passiamo alla funzione la struttura col font, la stringa contenente la singola lettera ed il colore per
il testo, ed essa ci restituisce un puntatore a superficie, che prontamente memorizziamo nell'array.
Successivamente (righe 98-99) fissiamo larghezza ed altezza di tale superficie nel rettangolo di clip.
/* Scroll dal basso del testo */
ypos = screen->h;
xpos = CENTER_X - width/2;
while (ypos > CENTER_Y) {
for (i=0; i < strlen(string); i++) {
letter_rect[i].x = xpos;
letter_rect[i].y = ypos;
xpos += letter_rect[i].w;
SDL_BlitSurface(letter_surf[i], NULL, screen, &letter_rect[i]);
}
xpos = CENTER_X - (width/2);
ypos -= 2;
SDL_Flip(screen);
SDL_Delay(20);
for (i=0; i < strlen(string); i++)
SDL_FillRect(screen, &letter_rect[i], black);
}
Ecco il pezzo di codice responsabile dello scroll dal basso del testo.
Alle righe 103 e 104 definiamo le coordinate iniziali del testo, ovvero in basso ed al centro, mentre dopo
(righe 105-120), impostiamo un ciclo while con condizione di controllo la posizione del testo rispetto al
centro verticale dello schermo.
Il ciclo for alle righe 106-111 realizza il blit delle superfici delle singole lettere sullo schermo,
la riga 112 imposta nuovamente la posizione orizzontale per blittare la stringa nella nuova posizione, mentre la
113 incrementa l'altezza della stessa per la prossima iterazione.
Le righe 115 e 116 non dovrebbero necessitare di spiegazione, semplicemente scrivono su schermo e
mantengono il fotogramma per 20ms. La 118 e la 119 cancellano la riga di testo, in modo che questa possa venire
ridisegnata più su, nell'iterazione successiva del ciclo while.
/* Sinus scroller */
first_x = xpos;
while (angle <= 360*8) {
for (i=0; i < strlen(string); i++) {
if (i==0)
xpos = first_x;
letter_rect[i].x = xpos;
xpos += letter_rect[i].w;
ypos = CENTER_Y + sin(M_PI/180 * (angle + i*15)) * height;
letter_rect[i].y = ypos;
SDL_BlitSurface(letter_surf[i], NULL, screen, &letter_rect[i]);
}
angle += 7;
SDL_Flip(screen);
SDL_Delay(20);
/* Rimbalzo agli estremi laterali dello schermo */
if (xpos > screen->w)
dir = -1;
if (first_x < 0)
dir = 1;
first_x += dir*3;
for (i=0; i < strlen(string); i++)
SDL_FillRect(screen, &letter_rect[i], black);
}
Qui si realizza il movimento sinusoidale delle lettere, il codice è abbastanza analogo al pezzo
precedente.
Alla riga 123 memorizziamo la posizione orizzontale della prima lettera.
In quella successiva inizia il ciclo while (righe 124-148) che ha come condizione di controllo l'angolo,
argomento della funzione seno.
Il ciclo for (righe 125-133) che blitta su schermo le lettere è molto simile al precedente, tranne
per le righe 126 e 127, che memorizzano la posizione orizzontale della prima lettera, e la riga 130,
responsabile del movimento oscillante tramite la sin().
La riga 134 incrementa l'angolo per l'iterazione successiva, le righe 136 e 137 sono identiche alle
115 e 116, le righe 140 e 144 realizzano lo scroll orizzontale mediante il controllo dei bordi e la flag dir,
e la modifica del valore intero first_x, variabile per la coordinata x della prima lettera del testo.
Le righe 146 e 147 hanno la stessa funzione della 118 e 119.
/* Liberiamo le superfici */
for (i=0; i < strlen(string); i++)
SDL_FreeSurface(letter_surf[i]);
/* Liberiamo le strutture create dinamicamente */
free(letter_surf);
free(letter_rect);
Le righe 151 e 152, con un ciclo for, liberano le superfici SDL puntate dall'array, mentre la 154 e
la 155 liberano la memoria allocata dinamicamente per i puntatori alle superfici SDL e per i rettangoli di clip.
/* Chiudiamo font e librerie */
TTF_CloseFont(font);
TTF_Quit();
SDL_Quit();
return 0;
}
Siamo arrivati alla fine del programma, è tempo di liberare la struttura TTF_Font con la TTF_CloseFont(),
chiudere la libreria SDL_ttf con la TTF_Quit(), la SDL con la SDL_Quit() e ritornare 0 per indicare la corretta
esecuzione del programma.
N.B.
C'è una funzione importante della SDL_ttf, che però non è stata usata in questo programma, utile per
gestire l'andata a capo, o comunque la stampa di più linee di testo. Si tratta della TTF_FontLineSkip(), che
prende come argomento il puntatore ad una struttura TTF_Font e restituisce un intero indicante la spaziatura
verticale raccomandata tra due linee di testo.
COMPILAZIONE
Per compilare il nostro sorgente è necessario utilizzare come segue il gcc:
gcc -s -o sinusfont sinusfont.c `sdl-config --cflags --libs` -lSDL_ttf
L'opzione -s effettuerà lo strip sull'eseguibile, rimuovendone le parti non
utilizzate e rendendolo quindi più piccolo.
L'opzione -o imposterà il nome dell'eseguibile in sinusfont.
`sdl-config --cflags --libs` ci fornirà tutte le opzioni necessarie per includere
ed utilizzare la libreria SDL.
-lSDL_ttf linkerà il nostro programma con la libreria SDL_ttf.