Il compito più importante di qualunque canvas è di eseguire il rendering di sé
stesso nel canvas. Il rendering è un processo a due stadi, per motivi di
efficienza. Il primo stadio, implementato nel metodo update di GnomeCanvasItem garantisce che il rendering di ciascun
oggetto avvenga un'unica volta. L'idea è quella di eseguire le operazione di
calcolo più complesse all'interno del metodo update. Nel secondo stadio, invece, il canvas esegue
il rendering di sé stesso in un regione dello schermo. Il metodo render implementa questo stadio per gli oggetti
antialiased, mentre il metodo draw per gli
oggetti che sfruttano la modalitá GDK.
Il rendering in una sola occasione, cioè ogni volta che il canvas riceve un evento expose oppure quando si richiede esplicitamente il refresh, aggiungendo una funzione di idle per rimuove sé stessa dopo una singola chiamata. (Una funzione di idle si avvia quando nessun evento di GTK+ è in attesa e il flusso di esecuzione si trova all'interno del ciclo principale di GTK+; maggiori informazioni sono disponibili nel
la sezione Il ciclo principale nel il capitolo Fondamenti di GTK+). Il canvas mantiene una lista di regioni da ridisegnare e aggiunge un elemento ogni volta che riceve una richiesta di ridisegno, in modo che sappia quali aree devono essere ridisegnate quando il gestore dell'idle viene finalmente chiamato.
Gli oggetti del canvas posseggono un flag che indica lo stato e la necessità di essere aggiornati. Qualora un oggetti del canvas cambia (ad esempio, se impostate un nuovo colore di riempimento per un GnomeCanvasRect), questo chiamerà gnome_canvas_item_request_update() per impostare il flag di cui sopra per sé stesso e per il gruppo in cui è inserito, fino ad arrivare al gruppo root del canvas (Il widget GnomeCanvas è a conoscenza di un unico oggetto sul canvas, tutti gli altri sono gestiti ricorsivamente quando un funzione viene eseguita sul gruppo root).
Una volta che tutti gli oggetti sul canvas sono stati aggiornati, il processo di visualizzazione ha inizio. Il canvas crea un buffer RGB oppure GdkPixmap, converte la sua lista di regioni da disegnare in una lista delle dimensioni del buffer, quindi chiama le funzioni di disegno o rendering sul gruppo root del canvas per ciascun rettangolo. Una volta che tutti i rettangoli sono stati visualizzati, il buffer viene copiato sullo schermo.
Il metodo di aggiornamento viene usato principalmente dagli oggetti del canvas con antialias. libart_lgpl può precostruire un vettore da essere visualizzato, eseguendo tutte le trasformazioni necessarie. Il metodo di visualizzazione inserisce il risultato nel buffer RGB.
Il metodo di aggiornamento è uno dei due che GnomeCanvasRect e GnomeCanvasEllipse devono implementare in maniera differente. Ecco l'implementazione per GnomeCanvasRect:
Come potete vedere la prima operazione svolta da questa funzione è quella di chiamare una istruzione di aggiornamento condivisa tra GnomeCanvasRect e GnomeCanvasEllips. Ecco la funzione:
Viene utilizzato molto codice in questa funzione: il metodo di aggiornamento è sicuramente il più complicato di tutti, dato che svolge tutta la preparazione alla visualizzazione di un oggetto sul canvas. Notare il codice che dipende dal flag item->canvas->aa.
La prima operazione svolta da GnomeCanvasRE durante l'aggiornamento è quella di chiamare il metodo della sua classe superiore. Successivamente GnomeCanvasRE chiama una serire di funzioni di utilità per riempiere i propri contesti grafici con i valori corretti. L'implementazione di queste funzioni è molto semplice, e viene omessa in questa sezione.
Successivamente gnome_canvas_rect_update() continua a lavorare su dettagli specifici di GnomeCanvasRect. Vengono svolte numerose operazioni:
Il contenitore del canvas viene aggiornato. Ogni canvas ha associato un proprio contenitore. I metodi di disegno e visualizzazione del GnomeCanvasGroup utilizzano questo contenitore per determinare quali oggetti si trovano nell'area da disegnare. Il contenitore deve essere aggiornato sia in modalità GDK che antialiased.
In modalità antialiased viene creato un percorso di vettori ordinati, che consiste semplicemente in una seria di istruzioni di disegno, simili alle primitive di PostScript, che possono essere visualizzate in un buffer RGB da libart_lgpl.
In modalità antialiased, gli argomenti affine
e clip_path del metodo di aggiornamento
vengono utilizzati per manipolare i percorsi di vettori ordinati. Affine e
clip_path sono contenuti implicitamente per essere utilizzati dal metodo
di visualizzazione. Se non state utilizzando i vettori forniti da
libart_lgpl per i vostri oggetti del canvas,
dovrete arrangiarvi in un altro modo per assicurarvi che gli argomenti affine e clip_path vengano utilizzati durante la visualizzazione.
In entrambe le modalità, un aggiornamento viene richiesto per entrambe le regioni occupate dall'oggetto, più la regione che l'oggetto verrà a occupare.
La maggior parte di questo lavoro viene svolto dietro le quinte da funzioni di utilità, dichiarate in libgnomeui/gnome-canvas-util.h. gnome_canvas_update_bbox() imposta il nuovo contenitore per l'oggetto e richiede un aggiornamento sul vecchio e nuovo contenitore. Viene utilizzata in modalità GDK. (gnome_canvas_update_bbox() aspetta coordinate di tipo canvas. get_bounds() è una funzione banale per calcolare i bordi del rettangolo espressi in coordinate canvas.).
Una volta appreso cosa accade dietro le quinte, osserviamo l'implementazione di gnome_canvas_update_bbox():
void
gnome_canvas_update_bbox (GnomeCanvasItem *item,
int x1, int y1,
int x2, int y2)
{
gnome_canvas_request_redraw (item->canvas,
item->x1, item->y1,
item->x2, item->y2);
item->x1 = x1;
item->y1 = y1;
item->x2 = x2;
item->y2 = y2;
gnome_canvas_request_redraw (item->canvas,
item->x1, item->y1,
item->x2, item->y2);
}
Ovviamente siete liberi di implementare questa funzione manualmente.
In modalità GDK, questo è quanto accade: i contenitori vengono aggiornati e la
funzione ritorna. La modalità antialiased è leggermente più complessa, ma
vengono essenzialmente eseguite le stesse operazioni. In primo luogo,
gnome_canvas_item_reset_bounds() imposta il contenitore
dell'oggetto come un rettangolo vuoto. Successivamente, due percorsi di
vettori ordinati vengono preparati, uno per la parte solida del rettangolo
(se esiste), e uno per il bordo (se esiste). Questa operazione viene seguita
tutte le volte dalla stessa procedura. Primo, viene preparato un percorso di
vettori per libart_lgpl. Successivamente, il
percorso viene manipolato per trasformazione affine, e viene utilizzata la funzione gnome_canvas_item_update_svp_clip() per ridisegnare il percorso originale, eliminarlo, creare il nuovo percorso, richiedere l'aggiornamento di questo, e registrarlo per la visualizzazione. Se il riempimento o il bordo del rettangolo sono stati disattivati, viene richiesto l'aggiornamento per il vecchio percorso vettoriale, ma non ne viene creato uno nuovo.
Per chiarire questi avvenimenti, ecco l'implementazione di gnome_canvas_item_update_svp_clip():
Nuovamente, tutte queste funzioni sono dichiarate in libgnomeui/gnome-canvas-util.h, valide per qualunque oggetto del canvas. Ignorate tranquillamente i dettagli di implementazione, l'idea è quella di comprendere come vengono svolte le operazioni. Il codice è più semplice da capire se siete a conoscenza del fatto che un ArtDRect è un rettangolo definito da numeri a doppia precisione da libart_lgpl, e che un ArtUta è un array di microtiles, cioè una lista di piccole regioni dello spazio di disegno. (Il canvas in modalità antialiased tiene traccia delle regioni di disegno con un metodo piuttosto complicato. Notare che la U in Uta intende la lettera greca che rappresenta il prefisso micro, e non la lettera iniziale di una parola che inizia per U).
La responsabilità della richiesta di aggiornamento o disegno ricada sull'oggetto del canvas, quando le proprietà dell'oggetto stesso vengono modificata e lo schermo deve essere aggiornato. Questo è molto semplice. Ecco una porzione di codice da gnome_canvas_re_set_arg(), che imposta l'argomento y2:
case ARG_Y2:
re->y2 = GTK_VALUE_DOUBLE (*arg);
gnome_canvas_item_request_update (item);
break;
Dato che "y2" modifica la forma del rettangolo, il percorso deve essere ricreato ed è necessario un aggiornamento. Notare che la funzione gnome_canvas_item_request_update() imposta semplicemente un flag e inserisce un gestore, in modo da poter essere chiamata svariate volte senza perdita di prestazioni.
Non tutte le modifiche richiedono un aggiornmento. Un ridisegno può essere sufficiente, o forse l'argomento non è correlato al display. Tutto dipende dall'oggetto del canvas e da cosa viene esattamente modificato.
Il metodo di visualizzare viene condiviso fra GnomeCanvasRect e GnomeCanvasEllipse. Il suo fine è quello di inserire i due percorsi creati durante il metodo di aggiornamento all'interno del buffer RGB:
Come potete vedere, la maggior parte del lavoro viene svolta in gnome_canvas_render_svp(), sempre dichiarata in libgnomeui/gnome-canvas-util.h. Ecco l'implementazione:
Per compredere gnome_canvas_render_svp(), oppure per creare un proprio buffer di disegno RGB (senza utilizzare libart_lgpl), dovrete prima capire cosa rappresenta un GnomeCanvasBuf:
typedef struct {
guchar *buf;
int buf_rowstride;
ArtIRect rect;
guint32 bg_color;
unsigned int is_bg : 1;
unsigned int is_buf : 1;
} GnomeCanvasBuf;
Il membro buf è un buffer RGB, come descritto nel
la sezione Buffer RGB nel il capitolo I fondamenti di GDK. buf_rowstride è il rowstride del buffer, anche questo descritto nel
la sezione Buffer RGB nel il capitolo I fondamenti di GDK. Un ArtIRect rappresenta un rettangolo creato da numeri interi. rect definisce la regione di disegno espressa in coordinate canvas che questo buffer rappresenta. rect.x0 e rect.y0 sono i riferimenti del buffer e corrispondono alla riga 0, colonna 0 del buffer RGB. È possibile convertire le coordinate canvas a coordinate buffer sottraendo questi valori.
Come ottimizzazioni, il canvas non inizializza il buffer RGB utilizzando il colore di sfondo, poichè il primo oggetto del canvas può coprire l'intero buffer. Inoltre, se l'oggetto del canvas è il primo ad essere visualizzato, dovrete inserire dei valori in ogni pixel della regione di disegno definita dal membro rect del buffer. Se il vostro oggetto non copre nessun pixel, dovrete inserire in quest'ultimo il bg_color. bg_color è un valore RGB (non alpha). Se eseguite questa operazione manualmente, è possibile ottenere un valore RGB espresso in rgb in questo modo:
guchar r, g, b;
r = (rgb >> 16) & 0xff;
g = (rgb >> 8) & 0xff;
b = rgb & 0xff;
Tuttavia, viene fornita una funzione per rimpimento del GnomeCanvasBuf con il bg_color:
Come potete vedere dalla implementazione di gnome_canvas_buf_ensure_buf(), is_bg è un flag che indica che il buffer RGB contiene ancora spazzatura tratta dalla memoria. Non è ancora stato inizializzato attraverso i pixel RGB. is_buf indica the il buffer è stato inizializzato. Questi due flag sono esclusivi. Se il vostro oggetto riceve un buffer contenente il flag is_bg impostato, questo deve riempire il buffer, disattivare is_bg e impostare is_buf.
if (buf->is_bg)
{
gnome_canvas_buf_ensure_buf(buf);
buf->is_bg = FALSE;
}
Se avete un numero elevato di oggetti, la modalità RGB può fornire prestazioni mgliori della modalità GDK. Disegnare in un buffer RGB è una semplice assegnazione in un array, che risulta essere estremamente più veloce che eseguire una chiamata a GDK (dato che GDK deve contattare il server X e richiedere di eseguire l'operazione di disegno). Lo sforzo maggiore risulta essere la copia del buffer RGB sul server X una volta terminato il disegno. Tuttavia, la copia richiede lo stesso lasso di tempo indipendemente dal numero di oggetti presenti sul canvas, dato che l'operazione viene eseguita una sola volta, quando tutti gli oggetti sono ormai disegnati.
Questo è risultato essere un particolare vincente nell'applicazione chiamata
Guppi che stò attualmente scrivendo. Guppi è un programma di
disegno. Una delle operazioni che deve svolgere è quella di visualizzare
un plottaggio utilizzando decine di migliaia di punti. Ciascun punto è colorato. Se dovessi chiamare GDK per visualizzare ciascuno di questi, verrebbero eseguite migliaia di connessioni al server X, eventualmente attraverso una rete. Invece ho deciso di utilizzare il canvas in modalità RGB, con un oggetto canvas personalizzato che rappresenta il plottaggio. Questo mi permette di eseguire tutto il disegno sul client, quindi il buffer RGB viene copiato dal canvas sul server X in un'unica, singola operazione. Tutto questo è risultato molto veloce.
L'unica difficoltà riscontrata nell'utilizzo della visualizzazione tramite
buffer RGB è la necessità di disporre di una libreria di rasterizzazione compatibile con le primitive di disegno di GDK. libart_lgpl è una libreria di alta qualità, adatta a questo scopo, che fornisce anche antialiasing, e utilizzata per default dagli oggetti del canvas. Potete utilizzarla negli oggetti personalizzati che andrete a creare, ed è la scelta migliore se dovete disegnare centinaia di forme diverse.
Raph Levien, autore di libart_lgple
del modulo GdkRGB, mi informa che possono essere scritte funzioni con prestazioni ancora
migliori. Se avete bisogno di maggiori prestazioni, considerate questa opportunità.
Disegnare con GDK è molto più semplice che disegnare tramite libart_lgpl, ma è inoltre meno flessibile e produce risultati di bassa qualità. Ecco l'implementazione di GnomeCanvasRect per il metodo di disegno:
static void
gnome_canvas_rect_draw (GnomeCanvasItem *item, GdkDrawable *drawable,
int x, int y, int width, int height)
{
GnomeCanvasRE *re;
double i2w[6], w2c[6], i2c[6];
int x1, y1, x2, y2;
ArtPoint i1, i2;
ArtPoint c1, c2;
re = GNOME_CANVAS_RE (item);
/* Otteniamo le coordinate canvas dei pixel */
gnome_canvas_item_i2w_affine (item, i2w);
gnome_canvas_w2c_affine (item->canvas, w2c);
art_affine_multiply (i2c, i2w, w2c);
i1.x = re->x1;
i1.y = re->y1;
i2.x = re->x2;
i2.y = re->y2;
art_affine_point (&c1, &i1, i2c);
art_affine_point (&c2, &i2, i2c);
x1 = c1.x;
y1 = c1.y;
x2 = c2.x;
y2 = c2.y;
if (re->fill_set) {
if (re->fill_stipple)
gnome_canvas_set_stipple_origin (item->canvas, re->fill_gc);
gdk_draw_rectangle (drawable,
re->fill_gc,
TRUE,
x1 - x,
y1 - y,
x2 - x1 + 1,
y2 - y1 + 1);
}
if (re->outline_set) {
if (re->outline_stipple)
gnome_canvas_set_stipple_origin (item->canvas, re->outline_gc);
gdk_draw_rectangle (drawable,
re->outline_gc,
FALSE,
x1 - x,
y1 - y,
x2 - x1,
y2 - y1);
}
}
Il metodo di disegno riceve un buffer, i riferimenti a questo (x e y, le coordinate canvas del buffer) e la dimensione del buffer (width e height). I metodi di disegno per il GnomeCanvasRect ottengono le indicazioni necessarie, quindi le manipolano per creare una trasformazione affine oggetto-canvas. (Maggiori informazioni sulle trasformazioni affini nel
la sezione Trasformazioni affini nel il capitolo GnomeCanvas). In questo modo, converte i punti degli angoli del rettangolo in coordinate canvas, successivamente disegna il rettangolo, convertendo le coordinate canvas in coordinate buffer sottraendo i riferimenti del buffer stesso.