Produrre un disegno su video

Produrre un disegno su video

Ancora un passo, ma molto importante, dedicato alla grafica!
In questo step infatti si imparerà a scrivere programmi in Python che ci consentiranno di produrre semplici disegni e animazioni sul video.
Per comprendere bene come funziona il video di un personal computer e come scrivere poi le istruzioni che ci consentiranno di disegnare sul video, bisogna
capire come è fatto il video di un PC.
Cominciamo con la codifica delle immagini.
Osserviamo attentamente le immagini della televisione o del video del PC e vediamo che sono formate da una serie di puntini uno vicino all’altro: sono in realtà dei quadratini molto piccoli.
Ingrandendo l’immagine i quadratini si vedranno molto bene.
Ecco cosa succede all’immagine di un volto che sorride se la ingrandiamo tantissime volte. I quadratini da cui è formata sono evidentissimi.


E se ingrandiamo un pezzo della scritta Wikipedia?

Nella TV i puntini sono circa 500 su ogni riga e ci sono circa 300 righe sullo schermo, quindi in tutto ci sono 150.000 puntini. I puntini sono di materiale fosforescente. Questi puntini si chiamano “pixel” (picture element), o “punto di un’immagine”, e sia sul PC che sulla TV possono essere in bianco e nero o colorati. Si comincia ad analizzare i puntini in bianco e nero perché quelli colorati sono un poco più complicati. Inoltre, si suppone quasi sempre che un puntino sia bianco oppure nero. Nella grafica più raffinata un puntino può essere non soltanto bianco e nero ma anche grigio più o meno scuro.

Disegnamo una casa e rappresentiamola sul monitor del calcolatore. Sovrapponiamo poi al disegno una griglia di quadratini ognuno dei quali rappresenta un “pixel”. Scriviamo 0 se nel quadratino c’è più bianco che nero, oppure 1 se c’è più nero (o colore).
Ora proviamo a farlo con due griglie diverse: la prima ha i quadratini di 1 cm e la seconda di 0,5 cm.
Proviamo a riempire di 1 e 0 le due griglie e conta quanti puntini ci sono nel primo esempio e quanti nel secondo.

Si può notare che se i quadratini sono più piccoli il disegno verrà meglio; in linguaggio tecnico si dice che è più definito.
La definizione di un’immagine si chiama risoluzione ed è il numero di quadratini che ci sono in una certa area.
Un’immagine ha una risoluzione più alta quando i puntini per area sono tanti, ha una risoluzione bassa se i puntini sono pochi.
La risoluzione di uno schermo si misura moltiplicando il numero di pixel di ogni riga per il numero di righe.
Ad esempio, la risoluzione dello schermo televisivo, che è 500 x 300, corrisponde a 300 righe ciascuna delle quali è composta da 500 pixel.
La risoluzione dello schermo TV è molto bassa, sui video dei PC usati per applicazioni di grafica si arriva a risoluzioni di 4096 x 3300 pixel.
Come si è appena visto, un’immagine viene rappresentata con dei puntini e la perfezione del disegno dipende dal numero dei puntini e quindi dai bit.
Servono tantissimi bit per rappresentare bene un’immagine.
L’enorme quantità di bit necessaria per rappresentare bene un’immagine è il motivo per cui ci vuole tanto tempo per “scaricare” un’immagine da Internet. Poiché le immagini sul video sono composte da tanti puntini uno vicino all’altro, o se preferisci da tanti bit, anche un’immagine può essere rappresentata sul calcolatore come una lunga fila di bit. Si dice che i pixel possono essere accesi (quando il quadratino è nero o colorato) o spenti (quando il quadratino è bianco) e l’immagine si può rappresentare semplicemente dicendo che un pixel e’ acceso e l’altro e’ spento.
Questo si può dire con un codice, ad esempio 0 = spento, 1 = acceso.
Con 150.000 bit si può descrivere un’immagine in bianco e nero su uno schermo televisivo, ma si ha bisogno di 500.000 per descrivere la stessa immagine sul PC. Questo perché il video del PC ha molte più righe e molte più colonne, anzi sul PC puoi decidere tu quante righe e colonne usare.
Partendo da 800 colonne x 600 righe, si può arrivare fino ai più potenti video che hanno 1280 colonne x 1024 righe.
Un’immagine digitale è composta da una serie di bit (0 e 1) che rappresentano il valore dei pixel corrispondenti.
L’immagine nel calcolatore sarà una lunga sequenza di 0 e 1, che saranno usati proprio come comandi per decidere se accendere o spegnere i pixel da una apposita “memoria” del PC; infatti il video del PC ha una memoria che si chiama “memoria video”, dove sono scritte le sequenze di 0 e 1 che comandano la luce sui pixel.
Quindi la memoria video, in cui è descritta l’immagine come una sequenza di 0 e 1, trasmette al video i comandi da eseguire per rappresentare l’immagine.
Quando facciamo un disegno sul PC, questo viene disegnato a puntini e la perfezione del disegno dipende dal numero dei puntini. 
Questa operazione si chiama anche “discretizzazione” di un’immagine: l’immagine può cambiare la sua intensità, cioè può essere semplificata, approssimata o complicata. La discretizzazione di un’immagine è la “numerizzazione” o “digitalizzazione” dell’immagine, cioè la trasformazione dell’immagine in bit (0 e 1). Naturalmente tanto più numerosi sono i pixel tanto migliore sarà la qualità dell’immagine.
“Immagine digitale”, “TV digitale”, “macchina fotografica digitale” ecc., con queste espressioni indichiamo tutti gli apparati le cui immagini sono rappresentate con dei numeri (dei puntini).
In conclusione, si potrebbe produrre un disegno sul video scrivendo un programma fatto da molte istruzioni che precisino i punti dello schermo che si vogliono illuminare uno alla volta. Ma occorrerebbero troppe istruzioni e molto tempo per produrre disegni anche molto semplici.
Fortunatamente altri programmatori hanno scritto delle funzioni che ci consentono di disegnare parti di un disegno con uno sforzo molto piccolo.
Queste funzioni sono state raccolte in una “libreria”, che è appunto un insieme di funzioni che il programma principale potrà richiamare quando ne ha bisogno.
La libreria che si usa per disegnare è:

Pygame

In Python le raccolte di funzioni si chiamano “librerie” e non sono niente altro che programmi già scritti e funzionanti pronti per l’uso.
Il nome di questa libreria è stato generato fondendo le parole “python” e “game”, per ricordarci che essa potrà essere utilizzata per programmare anche videogame.

Il primo disegno!

Scriviamo insieme un primo semplice programma che servirà a disegnare un palloncino.
Il programma inizierà con un’istruzione un po’ strana:

import pygame

che significa:
Caro Python, ho bisogno della libreria Pygame. Per favore mettimi a disposizione (“import) tutte le funzioni di questa libreria.
Bisogna ricordarsi di dire sempre a Python in quale libreria dovrà cercare le funzioni che servono, altrimenti si lamenterà di non conoscerne il nome!
Successivamente dobbiamo scrivere:

import time

che è l’ordine impartito a Python di mettere a disposizione una seconda libreria, chiamata “time” (tempo). Questa libreria contiene funzioni che consentono di far comparire un disegno sul video per un certo numero di secondi, in modo da cambiare disegno a intervalli ben definiti ditempo, come è necessario se vuoi disegnare un oggetto che si sposta sul video.
Quindi scriviamo:

pygame.init ()

che è la richiesta di attivare la funzione “init()” della libreria “pygame”.
Questa funzione non fa altro che pulire il video per poter fare un disegno. Infatti, il video sarà pulito per poter fare il disegno.
A questo punto si è in grado di disegnare, ma come prima operazione bisogna dare un nome all’area del video che conterrà il nuovo disegno e decidere quanto sarà grande questa area. Per questo bisognerà scrivere, ad esempio:

areapalloncino = pygame.display.set_mode ((400,500))

Cosa significano quei numeri tra le parentesi tonde?
Sono le dimensioni dell’area dove disegneremo.
L’istruzione servirà a chiamare la funzione “set_mode” della libreria “display” di Pygame, e ci consentirà di:

  1. dare un nome (“areapalloncino”) all’area del video dove si disegnerà;
  2. decidere le dimensioni di quest’area, che sarà un rettangolo largo 400 pixel (o “quadratini”) e alto 500 pixel.

In pratica è come prendere un foglio da disegno a quadretti e poi tagliarlo con le forbici della dimensione adatta al tuo disegno. Quel foglietto sarà chiamato “area palloncino”.
Adesso siamo in grado di disegnare il tuo palloncino, ma per continuare è importante sapere cosa sono le “coordinate” soprattutto perché, le coordinate del video sono un po’ strane!!!
Abbiamo detto che i pixel del video sono individuati dalle coordinate (riga e colonna) ma attenzione al fatto che sul video il punto 0,0 è in alto a sinistra, e quindi si conta a partire dall’alto verso il basso.
Cosi nella figura che vediamo,

il pixel A ha “ascissa” 4, perché sta nella colonna 4 e “ordinata” 1, perché sta nella riga 1 e la sua posizione si indica con A(4,1).
Invece, il pixel B avrà ascissa uguale a 3 e ordinata uguale a 4, e quindi B(3,4).

…continuiamo a disegnare…

Il nostro programma ha già quattro istruzioni.

import pygame
import time
pygame.init()
areapalloncino = pygame.display.set_mode((400,500))

Ora disegniamo il palloncino. Useremo la funzione: pygame.draw.circle
L’istruzione che si dovrà scrivere non è proprio facilissima:

pygame.draw.circle(areapalloncino, (255,0,0), (300,50), 30, 2)

Ora con un po’ di pazienza cerchiamo di capire come funziona questa istruzione.

  1. areapalloncino: questo è il nome dell’area del disegno che conterrà il palloncino.
  2. (255, 0, 0) specifica il colore del palloncino.
  3. (300, 50) sono le coordinate del centro del cerchio. In pratica, il centro del cerchio starà sulla 300-esima colonna e sulla 50-esima riga (partendo dall’alto!).
  4. 30 è il raggio del cerchio che è 30 quadratini (pixel).
  5. 2 è lo spessore della linea usata per disegnare il cerchio. Lo spessore è indicato in pixel, quindi nel nostro caso la linea che disegna il cerchio sarà spessa come 2 quadratini.

È importante sapere che se invece di 2 si scrivesse 0, oppure, più semplicemente, non si scrivesse nulla dopo la misura del raggio, tutto il cerchio sarebbe colorato e non soltanto la linea di contorno.

Due ultime istruzioni devono essere scritte per chiudere il programma:
pygame.display.update() e time.sleep(10)
La prima di queste due istruzioni: pygame.display.update()
è l’ordine di aggiornare il disegno sulla base delle istruzioni precedenti. Se, ad esempio, nelle due istruzioni precedenti, del tipo:
pygame.draw.circle(areapalloncino, (255,0,0), (300,50),30, 2)

abbiamo definito due cerchi, questi saranno disegnati insieme solo dopo l’esecuzione dell’istruzione update.

L’istruzione time.sleep() è l’ordine di “dormire” per un certo periodo di tempo espresso in secondi e frazioni di secondo prima di riprendere
l’esecuzione del programma. Vedremo che questa istruzione, sarà utile per realizzare disegni animati. Ecco il nostro programma completo:
import pygame
import time
pygame.init()
areapalloncino = pygame.display.set_mode((400,500))
pygame.draw.circle(areapalloncino,(255,0,0),(300,50),30,2)
pygame.display.update()
time.sleep(10)
Esercitati a disegnare, magari giocando un po’ con la dimensione e la
posizione del palloncino. Cambia le coordinate del centro e la dimensione del
raggio. Ad esempio:
(50,300) e 10
(250,250) e 50
e così via…

Adesso giochiamo con i colori

pygame.draw.circle(areapalloncino,(255,0,0),(300,50),30, 2)

Si è visto che nell’istruzione riportata sopra la parte (255, 0, 0) indica il codice del colore ROSSO. In questo modo, con i tre numeri scritti fra le due
parentesi puoi indicare moltissimi colori diversi.
Questi tre numeri indicano, nell’ordine, le componenti dei tre colori fondamentali – rosso, verde, blu – (in inglese: R.G.B., Red, Green, Blue).
La scala dei valori dei colori va da 0 a 255. Quindi (255,0,0) è un palloncino di colore rosso (il rosso più intenso che esiste tra i tuoi colori), mentre se si scrive (0,50,255) il colore del palloncino sarà blu intenso con una leggera sfumatura di verde.
Si provi a disegnare il palloncino cambiando i colori, ad esempio sostituendo:
(255, 0, 0) con:
(200,50,50)
(50,200,50)
(50,50,50)
e ancora
(0,255,0)
(0,0,255)

Si possono disegnare diversi palloncini, di colori diversi, cambiando i valori delle tre variabili R, G, B, cioè cambiando il codice del colore, come nella variante del nostro programmino:
import pygame
import time
R = 100
G = 255
B = 20
pygame.init ()
areapalloncino = pygame.display.set_mode((400,500))
pygame.draw.circle(areapalloncino,(R,G,B),(300,50),30,0)
pygame.display.update()
time.sleep(10)

Tutto avviene come se si avessero tre matite colorate, una rossa, una verde e una blu, e decidessi di mescolare i colori disegnando un palloncino, ad esempio, con poco rosso, poco verde e tanto blu (50,50,200).
N. B. nel
programma si è scritto 0 al fondo dell’istruzione pygame.draw.circle(…) e quindi il palloncino sarà colorato per intero, non solo sul bordo.
Per verificare se si è diventati abbastanza bravi,  si disegni un palloncino giallo di grandi dimensioni e uno viola piccolo piccolo.
Ora esaminiamo una nuova istruzione che ci permetterà di disegnare le linee.

import pygame
import time
pygame init()
areasegmenti = pygame.display.set_mode((400, 500))
pygame.draw.line(areasegmenti,(255,0,0),(50,200), 250,300))
pygame.display.update()
time.sleep(10)

L’istruzione scritta in rosso nel programma:
pygame.draw.line(areasegmenti,(255,0,0),(50,200), 250,300))

  1. draw.line è il nome della nuova istruzione che serve a disegnare un segmento
  2. areasegmenti è il nome dell’area dove disegnare il segmento
  3. (255,0,0) è il codice del colore. In questo caso il segmento sarà rosso, senza alcuna componente verde o blu.
  4. (50,200) sono le due coordinate di un estremo del segmento, che quindi partirà dal quadratino che sta nella colonna 50 e nella riga 200
  5. sono le due coordinate dell’altro estremo del segmento, che quindi finirà nel quadratino che sta nella colonna 250 e nella riga 200.

Con le istruzioni che abbiamo visto siamo già in grado di disegnare una casetta come questa. Non è bellissima ma è un disegno!

Cominciamo con disegnare il segmento AB partendo dall’ipotesi che A stia nella colonna 50 a partire da sinistra e nella riga 500 a partire dall’alto e che B sia nella colonna 150 e nella riga 500.
Con parole più eleganti, anche se più difficili, si dovrebbe dire che:
il punto A ha ascissa = 50 e ordinata = 500;
il punto B ha ascissa = 150 e ordinata = 500.
Sinteticamente possiamo scrivere:
A (50, 500)
B (150,500)Per disegnare il segmento AB scriveremo allora:
pygame.draw.line(areapalloncino,(255,255,0),(50,500),(150,500))

Disegnamo ora il segmento BC supponendo C(150,200). Basterà scrivere:
pygame.draw.line(areapalloncino, (255,255,0),(150, 500), (150,200))

Continuiamo disegnando i segmenti CD e DA con le istruzioni seguenti:
pygame.draw.line(areapalloncino, (255,0,0),(150, 200), (50,200))
pygame.draw.line(areapalloncino, (255,255,0),(50,200), (50,500))

Disegniamo ora il tetto supponendo E(80, 170) e F(370, 170).
Partendo dal punto D, disegniamo DE, EF e FC scrivendo:
pygame.draw.line(areapalloncino, (255,0,0),(50,200),(80,170)))
pygame.draw.line(areapalloncino, (255,0,0),(80,170),(370,170))
pygame.draw.line(areapalloncino, (255,0,0),(370,170),(150,200))
Finiamo il disegno disegnando la porta e le due finestre.
Si può iniziare con la porta, disegnando i tre segmenti GK, KL, LH. Quindi passare alle finestre.

Segmenti e casette

Riprendi ora il programma che disegna il palloncino e aggiungi l’istruzione scritta in rosso nell’esempio esattamente dove indicato:
import pygame
import time
pygame.init()
areapalloncino = pygame.display.set_mode ((400,500))
pygame.draw.circle(areapalloncino, (255,0,0), (300,50), 30, 2)
pygame.draw.line(areapalloncino, (255,255,0),(300,80), (300, 280))
pygame.display.update()
time.sleep(10)
La nuova istruzione serve ad aggiungere il cordino al palloncino!
Più in generale l’istruzione pygame.draw.line serve a disegnare un segmento.
Esaminiamo i parametri dell’istruzione che abbiamo scritto:
pygame.draw.line(areapalloncino, (255,255,0),(300,80), (300, 280))

 

  1. “areapalloncino” ha il significato già noto
  2. (255,255,0) questo è il codice del colore del segmento che vogliamo disegnare (il cordino del palloncino)
  3. (300,80) queste sono le coordinate del punto di partenza del segmento (300-esima colonna e 80-esima riga)
  4. (300, 280) queste sono le coordinate del punto di arrivo del segmento (300-esima colonna e 280-esima riga)

Nota bene:
per disegnare un segmento bisogna indicare le coordinate del punto di partenza e del punto di arrivo. Se il segmento è verticale per entrambi i punti la colonna sarà la stessa mentre la riga del punto di arrivo sarà data dalla coordinata del punto di partenza più la lunghezza del segmento.
Nel nostro esempio il cordino del palloncino è lungo 200 pixel.

…finalmente: Animazione!

Come si fa a costruire un cartone animato sul video?
Un cartone animato è costituito da una successione di disegni, leggermente diversi uno dall’altro, che quando sono proiettati in sequenza danno l’impressione del movimento.
Si riprenda il programma che disegna il palloncino e con poche istruzioni in più … facciamolo volare verso l’alto.
import pygame
import time
pygame.init()
areapalloncino = pygame.display.set_mode((400,500))
pygame.draw.circle(areapalloncino, (255,0,0), (300,350), 30)
pygame.draw.line(areapalloncino, (255,255,0),(300,380), (300, 580))
pygame.display.update()
time.sleep(0.30)
areapalloncino.fill(255,255,255)
pygame.draw.circle(areapalloncino, (255,0,0), (300,250), 30)
pygame.draw.line(areapalloncino, (255,255,0),(300,280), (300, 480))
pygame.display.update()
time.sleep(0.30)
areapalloncino.fill(255,255,255)
pygame.draw.circle(areapalloncino, (255,0,0), (300,150), 30)
pygame.draw.line(areapalloncino, (255,255,0),(300,180), (300, 380))
pygame.display.update()
time.sleep(0.30)
areapalloncino.fill(255,255,255)

Per far volare il palloncino, si è dovuto disegnare il palloncino e il cordino per tre volte, in tre posizioni diverse, spostando il cerchio e il segmento di 100 pixel verso l’alto la seconda e la terza volta (da 350 il centro del cerchio che rappresenta il pallone si sposta a 250 e poi a 150).

Esaminiamo con attenzione il programma. Le prime quattro istruzioni hanno il solito significato.
La quinta istruzione:
pygame.draw.circle(areapalloncino, (255,0,0), (300,350), 30)
significa: preparati a disegnare un palloncino con centro nella posizione (300, 350)

La sesta istruzione:
pygame.draw.line(areapalloncino, (255,255,0),(300,380), (300, 580))
significa: preparati a disegnare il cordino di quel palloncino

La settima istruzione:
pygame.display.update()
equivale al seguente ordine “ proietta sul video i due disegni del palloncino e del cordino che hai preparato”.

L’ottava istruzione:
time.sleep(0.30)
equivale all’ordine di aspettare, senza far nulla, un tempo dell’ordine di 0,30 secondi, ovvero 3 decimi di secondo.
Sfortunatamente questa istruzione potrebbe presentare qualche problema, in quanto è stata pensata per un calcolatore di velocità media. Se il tuo calcolatore è veloce, potrebbe succedere che il programma aspetti meno di 0,30 secondi, per cui probabilmente si dovrà cambiare il tempo di attesa.

La nona istruzione:
areapalloncino.fill((255,255,255))
è un’istruzione nuova e anch’essa molto importante. Ordina di coprire tutta l’area del disegno (“areapalloncino”) con il colore (255,255,255), cioè ordina di riempire tutto l’area di pixel bianchi. Ed è importante per evitare che i palloncini siano disegnati sullo stesso “foglio”, altrimenti noi vedremmo tre palloncini invece di un palloncino che vola.
In sostanza questa istruzione è equivalente all’ordine di cancellare tutto il video e ricominciare a disegnare.
È importante quello che succede nelle istruzioni seguenti, con le quali ridisegno il palloncino e il cordino in una nuova posizione e aspetto nuovamente un po’ prima di procedere al nuovo disegno del palloncino.

….continua l’animazione…

Il programma che abbiamo scritto è un programma di animazione molto rudimentale, perché l’immagine del palloncino si muove a scatti, spostandosi ogni volta di 100 pixel. In effetti, più che volare il palloncino saltella!

Ora proviamo a scrivere il programma seguente che fa le stesse cose del precedente ma … meglio.
import pygame
import time
pygame.init()
areapalloncino = pygame.display.set_mode((400,500))
i = 0
while i < 600:
      pygame.draw.circle(areapalloncino, (255,0,0), (300,350), 30)
      pygame.draw.line(areapalloncino,(255,255,0),(300,380),(300, 580))
      pygame.display.update()
      time.sleep(0.03)
      areapalloncino.fill((255,255,255))
      i = i + 1

Nota bene: La struttura del programma ora è la seguente:

i = 0
while < 600:
      #disegna il palloncino spostato di i pixel verso l’alto
      pygame.display.update()
      time.sleep(0.03)
      areapalloncino.fill((255,255,255))
      i = i + 1

In sostanza, quando:
i = 0 (la prima volta nel ciclo while): il palloncino è in posizione di partenza
i = 1 (la seconda volta ” ” ” ): il palloncino è spostato di 1 pixel verso l’alto
i = 2 (la terza volta ” ” ” ): si ridisegna il palloncino spostato verso l’alto di 2 pixel
….. e cosi via per 600 volte!

È importante sottolineare che il palloncino non si muove ma viene disegnato 600 volte in 600 posizioni diverse, per dare l’illusione del movimento.
L’istruzione time.sleep (0.03) lascia l’immagine sul video solo 3 centesimi di secondo. Siccome il nostro occhio conserva un’immagine che colpisce la retina per un tempo di poco superiore a quei 3 centesimi di secondo, ora non ci accorgiamo più del fatto che il movimento avviene a scatti, ma abbiamo l’impressione che il movimento sia continuo. Una buona animazione deve avere tempi di aggiornamento dell’immagine dell’ordine di centesimi di secondo.

Commento all'articolo