Tutorial su wxPython

Marco Barisione


Capitolo 9

Gestione delle imagini




wxImage

Questa classe gestisce delle immagini indipendenti dalla piattaforma, viene usata per caricare in memoria immagini memorizzate su file o per salvare delle immagini verso file. wxImage ha alcuni metodi utili per modifcare l'immagine (ad esempio modificandone le dimensioni o ruotandola) ma nessun metodo per disegnarci altre figure o per disegnare l'immagine in una finestra, per questo sono necessari wxBitmap e i DC.
I tipi supportati sono:   Vediamo come può essere usata la classe wxImage con un esempio di un programma che (con le opportune modifiche) potrebbe essere utile: tutti i file che corrispondono alla stringa passata sulla linea di comando allo script (o tutti i file con estensione ".bmp" se la linea di comando è vuota) vengono aperti, salvati come immagini PNG, poi ridotti a dimensioni 100 x 100 e infine salvati come immagini JPEG.

01: wxInitAllImageHandlers()
02: if len(sys.argv) == 2:
03:     # Se sulla riga di comando viene passato un
04:     # argomento si cerca fra i file che corrispondono
05:     # all'argomento...
06:     pathname = sys.argv[1]
07: else:
08:     # ...altrimenti si aprono solo i file con estensione ".bmp".
09:     pathname = "*.bmp"
10: # glob.glob(pathname) restituisce una lista che contiene tutti
11: # i file (o directory) che corrispondono a pathname.
12: for filename in glob.glob(pathname):
13:     image = wxImage(filename, wxBITMAP_TYPE_ANY)
14:     # I metodi di wxImage GetWidth e GetHeight ritornano entrambi
15:     # un intero, che indica rispettivamente larghezza e altezza
16:     # dell'immagine
17:     print "'%s' caricato, larghezza = %d, altezza = %d" % \
18:         (filename, image.GetWidth(), image.GetHeight())
19:     image.SaveFile(filename + ".png", wxBITMAP_TYPE_PNG)
20:     image.Rescale(100, 100)
21:     image.SaveFile(filename + ".jpg", wxBITMAP_TYPE_JPEG)
esempio 9.1 [visualizza esempio completo]

torna ad inizio pagina



wxBitmap

Offre le stesse capacità di caricamento e salvataggio di wxImage ma aggiunge il supporto per alcuni file; per il caricamento sono accettati (oltre a quelli validi per wxImage) i valori wxBITMAP_TYPE_XBM e (solo su windows) wxBITMAP_TYPE_RESOURCE, wxBITMAP_TYPE_BMP_RESOURCE; questi ultimi due vengono usati per caricare immagini da file eseguibili (tipicamente .exe o .dll), probabilmente non vi serviranno se non in rari casi.
Per il salvataggio invece sono supportati anche wxBITMAP_TYPE_BMP, wxBITMAP_TYPE_GIF (MA NE SIAMO PROPRIO SICURI?) e wxBITMAP_TYPE_XBM.
wxBitmap però non supporta le utili operazioni che possono essere eseguite con wxImage (come modificare le dimensioni dell'immagine) ma si dimostra particolarmente utile in congiunzione con i wxDC (di cui si parlerà più avanti) per cui sarà ad esempio possibile disegnare immagini sulle finestre o su altre immagini.
Non sempre è comunque necessario creare una wxBitmap da un file già esistente, ad esempio si può avere l'esigenza di creare semplicemente una wxBitmap vuota, per questo esiste wxEmptyBitmap che accetta come argomenti la larghezza in pixel dell'immagine, la larghezza e infine un argomento opzionale che indica la profondità dell'immagine. Bisogna però ricordarsi che se viene creata un'immagine vuota questa conterrà dati "casuali", potrebbe essere tutta nera, contenere pixel di colori casuali o anche contenere parti di immagini usate precedentemente in altre parti del programma in modo imprevedibile.
torna ad inizio pagina



wxMask

Durante le operazioni di disegno un'esigenza frequente è quella di disegnare solo una parte non rettangolare di immagine lasciando le altri parti trasparenti, questo effetto può essere raggiunto facilmente in wxPython usando la classe wxMask.
Vi sono due possibili costrottori:
torna ad inizio pagina



wxFont

I font sono un'entità di cui probabilmente qualunque utente di computer ha già conoscenza, i font vengono rappresentati in wxPython dalla classe wxFont. Il metodo più importante di wxFont è il costruttore che accetta i seguenti paramentri:
torna ad inizio pagina



I colori

I singoli colori sono rappresentati in wxPython dalla classe wxColour di cui può essere creata un'istanza in diversi modi, i due principali sono attraverso:
torna ad inizio pagina



Penne e pennelli

Prima di parlare di come disegnare sulle finestre è necessario introdurre due oggetti fondamentali wxPen e wxBrush.
Le wxPen sono usate per disegnare linee o bordi di figure geometriche, le caratteristiche principali delle penne sono il colore, la larghezza e lo stile; Questi parametri vengono impostati durante la creazione dell'istanza di wxPen (o rispettivamente attraverso i metodi SetColour, SetWidth e SetStyle).
Gli argomenti del costruttore sono: Per comodità sono già costruite alcune penne di larghezza 1 con stile wxSOLID dei principali colori; queste sono: wxRED_PEN, wxCYAN_PEN, wxGREEN_PEN, wxBLACK_PEN, wxWHITE_PEN, wxTRANSPARENT_PEN, wxBLACK_DASHED_PEN, wxGREY_PEN, wxMEDIUM_GREY_PEN, wxLIGHT_GREY_PEN.
I wxBrush sono usati per il riempimento di figure geometriche. Le caratteristiche principali dei pennelli sono colore e stile, passati al costruttore o impostati rispettivamente con i metodi SetColour e SetStyle. Gli argomenti del costruttore sono quindi: Come per wxPen esistono già delle istanze predefinite di wxBrush che corrispondono a pennelli con riempimento uniforme nei colori principali: wxBLUE_BRUSH, wxGREEN_BRUSH, wxWHITE_BRUSH, wxBLACK_BRUSH, wxGREY_BRUSH, wxMEDIUM_GREY_BRUSH, wxLIGHT_GREY_BRUSH, wxTRANSPARENT_BRUSH, wxCYAN_BRUSH, wxRED_BRUSH.
torna ad inizio pagina



wxDC

Un wxDC è un "device context" cioè un oggetto su cui si possono disegnare immagini, testo o figure geometriche.
wxDC è un'astrazione che può rappresentare molti dispositivi di output (uno schermo o la stampante ad esempio) permettendo così di usare lo stesso codice in più situazioni. Per permettere ciò ci sono più classi derivate da wxDC che aggiungono i pochi metodi necessari in quel contesto, le principali sono: I disegni fatti su una finestra vengono però cancellati se la finestra viene nascosta per cui c'è la necessità di ridisegnare le parti cancellate. Per questo si può usare l'event handler EVT_PAINT che richiama una funzione scelta dall'utente quando serve, in questo caso è però consigliato usare wxPaintDC invece di wxClientDC, in questo modo le operazioni di disegno possono essere accelerate perché viene permesso il disegno solo nelle parti che ne hanno realmente bisogno.
Per eseguire le operazioni di disegno è necessario specificare le coordinate in cui esse avvengono, queste cordinate normalmente hanno valore 0, 0 nell'angolo superiore sinistro del wxDC, l'asse delle ascisse è orientato verso destra (cioè muovendosi verso destra il valore della coordinata orizzontale aumenta) mentre quelle delle ordinate è orientato verso il basso (cioè muovendosi verso il basso il valore della coordinata verticale aumenta).

01: class MiaDialog(wxDialog):
02:     def __init__(self):
03:         wxDialog.__init__(self, None, -1, "Prova di disegno",
04:                           size=(400, 200))
05:         wxInitAllImageHandlers()
06:         EVT_PAINT(self, self.OnPaint)
07:         # Carica "immagine.bmp" in memoria.
08:         self.bmp = wxBitmap("immagine.bmp", wxBITMAP_TYPE_BMP)
09:         # Viene creata una maschera per l'immagine appena caricata.
10:         mask = wxMaskColour(self.bmp, "magenta")
11:         # La maschera viene associata all'immagine.
12:         self.bmp.SetMask(mask)
13:
14:     def OnPaint(self, event):
15:         # Crea il DC e lo prepara per il disegno.
16:         dc = wxPaintDC(self)
17:         dc.BeginDrawing()
18:         # Disegna un rettangolo verde con bordo giallo.
19:         penna_gialla_spessa = wxPen("yellow", 5, wxSOLID)
20:         dc.SetPen(penna_gialla_spessa)
21:         dc.SetBrush(wxGREEN_BRUSH)
22:         dc.DrawRectangle(5, 5, 100, 100)
23:         # Disegna una linea molto larga.
24:         penna_molto_spessa = wxPen(wxColour(180, 27, 93), 18, wxNORMAL)
25:         dc.SetPen(penna_molto_spessa)
26:         dc.DrawLine(50, 50, 200, 90)
27:         # Disegna un'immagine con una maschera trasparente.
28:         dc.DrawBitmap(self.bmp, 10, 20, 1)
29:         # Disegna un'immagine senza la maschera trasparente.
30:         dc.DrawBitmap(self.bmp, 250, 80)
31:         # Scrive "Hello world" con un carattere grande, in corsivo
32:         # e grassetto, sottolineato (solo su Windows) e di tipo
33:         # decorativo.
34:         font_grande = wxFont(20, wxDECORATIVE, wxITALIC, wxBOLD, 1)
35:         dc.SetFont(font_grande)
36:         dc.DrawText("Hello world", 120, 0)
37:         # Scrive "Hello world" con un carattere piccolo, con
38:         # larghezza fissa, di colore rosso e sfondo nero.
39:         font_piccolo = wxFont(10, wxMODERN, wxNORMAL, wxNORMAL)
40:         dc.SetBackgroundMode(wxSOLID)
41:         dc.SetTextBackground(wxBLACK)
42:         dc.SetTextForeground(wxRED)
43:         dc.SetFont(font_piccolo)
44:         dc.DrawText("Hello world", 10, 120)
45:         # Finisce le operazioni di disegno.
46:         dc.EndDrawing()
esempio 9.2 [visualizza esempio completo]




immagine 9.1


Un metodo sicuramente molto utile e frequentemente usato dei wxDC (specialmente con i wxMemoryDC) è Blit, questa funzione permette di copiare parte di un wxDC su un altro wxDC, accetta i seguenti 11 argomenti:
torna ad inizio pagina



wxMemoryDC

Spesso si ha la necessità di disegnare immagini senza farlo su una finestra, per questo si può usare la classe wxMemoryDC. Poiché wxMemoryDC è derivata da wxDC si hanno a disposizione tutti i metodi già esposti nella sezione precedente.
Il costruttore non accetta parametri e crea un'immagine bianca e nera 1 per 1, è quindi necessario selezionare una wxBitmap nel DC con il metodo SelectObject che accetta come solo argomento un'istanza di wxBitmap. Dopo che una bitmap è stata selezionata in un DC tutte le operazioni di disegno la modificheranno.
Un motivo tipico in cui serve usare un wxMemoryDC è quando si devono eseguire delle operazioni di disegno lunghe e in cui vi sono elementi che si sovrappongono, in questo caso disegnando direttamente sullo schermo l'utente può vedere dei flash. Nell'esempio seguente si può vedere questo effetto e come evitarlo usando un wxMemoryDC.

01: class MiaDialog(wxDialog):
02:     def __init__(self):
03:         wxDialog.__init__(self, None, -1,
04:                           "Prova di disegno con wxMemoryDC",
05:                           size=(350, 250))
06:         # E` possibile scegliere se usare un wxMemoryDC o no.
07:         self.radio_box =wxRadioBox(self, -1, "Usare un wxMemoryDC?",
08:                                    (10, 160), (150, 40),
09:                                    choices=["No", "Sì"])
10:         # Il pulsante permette di ridisegnare la finestra.
11:         BTN_ID = wxNewId()
12:         btn = wxButton(self, BTN_ID, "Ridisegna",
13:                        (170, 160), (-1, 40))
14:         EVT_BUTTON(self, BTN_ID, self.OnButtonDown)
15:
16:     def OnButtonDown(self, event):
17:         # Il pulsante è stato premuto, ridisegna la
18:         # finestra, se è selezionato "No" viene ritornato 0
19:         # da GetSelection perché "No" è il primo controllo,
20:         # altrimenti viene ritornato "1" perché il radiobutton
21:         # "Sì" è il secondo pulsante del radiobox. (?)
22:         usa_memory_dc = self.radio_box.GetSelection()
23:         self.Ridisegna(usa_memory_dc)
24:
25:     def Ridisegna(self, usa_memory_dc):
26:         # Crea un wxClientDC ed inizia le operazioni di disegno.
27:         client_dc = wxClientDC(self)
28:         client_dc.BeginDrawing()
29:         # Larghezza ed altezza del rettangolo in cui avvengono le
30:         # operazioni di disegno.
31:         larghezza = 300
32:         altezza = 150
33:         # Le operazioni di disegno avvengono su dest_dc che si
34:         # riferisce a diversi wxDC in base a usa_memory_dc
35:         if usa_memory_dc:
36:             # Se usa_memory_dc è 1 allora dest_dc è un wxMemoryDC.
37:             dest_dc = wxMemoryDC()
38:             # Viene creata una bitmap che viene poi selezionata
39:             # nel wxMemoryDC.
40:             bmp = wxEmptyBitmap(larghezza, altezza)
41:             dest_dc.SelectObject(bmp)
42:             dest_dc.BeginDrawing()
43:         else:
44:             # Se usa_memory_dc è 0 allora viene usato client_dc.
45:             dest_dc = client_dc
46:         # Lista dei possibili colori fra cui scegliere per
47:         # disegnare i cerchi.
48:         lista_colori = ("BLUE",
49:                         "BLUE VIOLET",
50:                         "BROWN",
51:                         "CYAN",
52:                         "DARK GREY",
53:                         "DARK GREEN",
54:                         "GOLD",
55:                         "GREY",
56:                         "GREEN",
57:                         "MAGENTA",
58:                         "NAVY",
59:                         "PINK",
60:                         "RED",
61:                         "SKY BLUE",
62:                         "VIOLET",
63:                         "YELLOW",
64:                         "PALE GREEN")
65:         # Disegna 1000 cerchi di colore, dimensione e posizione casuale
66:         for i in range(1000):
67:             # Il colore è estratto fra la lista dei possibili colori,
68:             # random.randrange(min, max) ritorna un numero casuale
69:             # compreso fra min e max.
70:             n_colore = random.randrange(0, len(lista_colori))
71:             colore = lista_colori[n_colore]
72:             # Vengono creati e selezionati nel wxDC i pennelli e le penne
73:             # di colore casuale.
74:             penna = wxPen(colore, 1, wxSOLID)
75:             dest_dc.SetPen(penna)
76:             pennello = wxBrush(colore, wxSOLID)
77:             dest_dc.SetBrush(pennello)
78:             # Diametro del cerchio.
79:             dim = random.randrange(5, 30)
80:             # Posizione in cui disegnare il cerchio, viene usato
81:             # "- dim" perxhé altrimenti le operazioni di disegno
82:             # potrebbero avvenire fuori dal rettangolo scelto.
83:             pos_x = random.randrange(0, larghezza - dim)
84:             pos_y = random.randrange(0, altezza - dim)
85:             # Disegna il cerchio.
86:             dest_dc.DrawEllipse(pos_x, pos_y, dim, dim)
87:         if usa_memory_dc:
88:             # I 500 cerchi sono stati disegnati sul wxMemporyDC,
89:             # adesso devono essere ricopiati su client_dc.
90:             client_dc.Blit(0, 0, larghezza, altezza, dest_dc, 0, 0)
91:             dest_dc.EndDrawing()
92:         client_dc.EndDrawing()
esempio 9.3 [visualizza esempio completo]

Il risultato finale del codice è, come si può vedere dall'immagine seguente, identico nei due casi eccetto che per lo sfondo del rettangolo in cui sono avvenute le operazioni di disegno, infatti usando direttamente il wxClientDC il disegno avviene subito sulla finestra, invece usando il wxMemoryDC le operazioni avvengono in memoria e poi viene ricopiato il disegno sulla finestra. Potete evitare questo problema in diversi modi, ad esempio se il wxMemoryDC viene ricopiato su una finestra uniforme potete selezionare nel wxMemoryDC un wxBrush del colore della finestra e poi richiamare il metodo Clear che ricopre il wxDC con il pennello impostato per lo sfondo.


immagine 9.2

torna ad inizio pagina