Un filtro trasforma con metodi matematici i singoli pixel di un’immagine generando una nuova versione dell’immagine originale. La libreria GD di PHP mette a disposizione funzioni già pronte per questo compito. Per imparare, vediamo come creare alcuni semplici filtri leggendo e scrivendo i valori dei singoli pixel. Useremo le seguenti funzioni di GD (che di seguito proviamo direttamente nella shell PHP):
- imagecolorat(), che restituisce il colore di un pixel date le sue coordinate x e y;
- imagesetpixel(), che imposta (in inglese to set) il colore del pixel di un’immagine.
php > $image = imagecreatetruecolor(1, 1); // Immagine di un solo pixel php > $color = imagecolorat($image, 0, 0); // Recupera il valore del pixel php > echo $color; // Il pixel è nero di default, tutti i bit sono a 0, 0 php > imagesetpixel($image, 0, 0, 0xff0000); // Colora il pixel (0,0) di rosso
Creare un’immagine speculare
Partiamo da un programma che, data un’immagine, crea la sua immagine speculare (in inglese specchio si dice mirror), che contiene cioè gli stessi pixel, però invertiti orizzontalmente. Crea l’app ImageMirror con dentro il file index.php e la sottodirectory images con le immagini originali (qui usiamo hand.jpg, presa da Pixabay, che puoi vedere qui sotto).
La prima parte di index.php è costituita dalla funzione imageMirror(), che effettua il mirroring passando in rassegna ciascuna riga dell’immagine e scambiando il primo pixel con l’ultimo, il secondo con il penultimo e così via.
< ? php /* Restituisce l'immagine speculare in orizzontale dell'immagine specificata */ function imageMirror($image) { $w = imagesx($image); $h = imagesy($image); for ($y = 0; $y < $h; $y++) { // Per ogni riga for ($x1 = 0; $x1 < $w / 2; $x1++) { // Per la prima metà delle colonne $p1 = imagecolorat($image, $x1, $y); // Recupera il pixel a sx $x2 = $w - $x1 - 1; // Calcola l'ascissa del corrispondente pixel a dx $p2 = imagecolorat($image, $x2, $y); // Recupera il pixel a dx imagesetpixel($image, $x1, $y, $p2); // Scambia i pixel: (x1, y) <- p2 imagesetpixel($image, $x2, $y, $p1); // e (x2, y) <- p1 } } return $image; // Restituisce l'immagine modificata }
La seconda parte recupera il nome dell’immagine (utilizzando la variabile globale $_GET), apre il file JPEG e salva su file l’immagine restituita da imageMirror().
// basename() fa in modo che l'utente possa specificare solo il nome // di un file (senza estensione .jpg) che dev'essere presente in "./images/" if (isset($_REQUEST['file'])) { $basename = basename($_REQUEST['file']); // Non considera le estensioni $filenameIn = "./images/" . $basename . ".jpg"; // Filename di input $image = imageMirror(imagecreatefromjpeg($filenameIn)); $filenameOut = "./images/" . $basename . "_out.jpg"; // Filename di output if (imagejpeg($image, $filenameOut)) { // Crea l'immagine speculare echo "Immagine speculare salvata correttamente!"; } else { echo "Errore nel salvataggio (controlla i permessi di scrittura)!"; } } else { echo "Aggiungi all'indirizzo il nome del file senza estensione."; echo "Es. http://localhost/cap09/ImageMirror/?file=hand"; }
Scrivi nella barra degli indirizzi del browser http://localhost/cap09/ImageMirror/?file=hand. In questo modo esegui lo script di default (cioè index.php) di ImageMirror passandogli con il metodo get il parametro file con valore hand. Se vuoi elaborare altre immagini JPEG copiale in images e aggiorna l’URL dell’applicazione con il nome della nuova immagine senza estensione. L’app visualizza un messaggio di errore se il file non esiste oppure se la directory images non ha i permessi di scrittura per l’utente che esegue il web server Apache.
Creare filtri sui colori
Per filtrare i colori utilizziamo imagecolorsforindex() che restituisce un array con i valori per i canali R, G, B e A (Alpha, che qui non consideriamo perché le immagini sono del tutto opache, cioè senza trasparenza). Vediamo alcuni esempi:
php > $image = imagecreatetruecolor(1, 1); // Immagine di un solo pixel php > imagesetpixel($image, 0, 0, 0xa36d3e); // Pixel (0,0) marrone scuro php > $color = imagecolorat($image, 0, 0); // Recupera il valore del pixel php > echo $color; // Valore del pixel in decimale (indice di colore) 10710334 php > echo dechex($color); // Controlla il valore in esadecimale a36d3e php > $rgba = imagecolorsforindex($image, $color); php > print_r($rgba); // Array dei canali RGBA Array ( [red] => 163 [green] => 109 [blue] => 62 [alpha] => 0 ) php > echo $rgba["red"]; // Valore in decimale della componente rossa
Crea ora l’applicazione ImageFilter. Lo script libs/functions.php riportato di seguito contiene due utili funzioni. La seconda, indexForColors(), serve per creare un valore (o indice) di colore a partire dalle componenti RGB. Per farlo usa l’operatore di shift binario >> che sposta verso destra i singoli bit di un certo numero di posizioni, inserendo degli zeri nelle posizioni che si liberano a sinistra, e l’operatore di OR bitwise | che effettua una somma binaria bit a bit. Non ci addentriamo nel dettaglio della spiegazione, che puoi comunque intuire facendo riferimento alla prossima figura.
< ? php /* Restituisce l'array delle componenti RGB di un pixel di un'immagine */ function getRGB($image, $x, $y) { $color = imagecolorat($image, $x, $y); $rgba = imagecolorsforindex($image, $color); return [$rgba["red"], $rgba["green"], $rgba["blue"]]; } /* Restituisce l'indice di colore corrispondente alle componenti RGB */ function indexForColors($r, $g, $b) { return $r << 16 | $g << 8 | $b; // Shift a sx ("<<") e OR bitwise ("|") }
Il primo filtro di colore restituisce un’immagine in scala di grigi (vedi la seconda immagine dentro la serie seguente). Tenendo conto che un grigio ha lo stesso valore per tutte le componenti, impostiamo la media RGB come valore per tutti e tre i canali.
< ? php require "libs/functions.php"; // Usa: getRGB() e indexForColors() $image = imagecreatefromjpeg("images/lorenzo.jpg"); $image = filterGreys($image); header('Content-type: image/jpeg'); imagejpeg($image); /* Ritorna la versione in scala di grigi di un'immagine */ function filterGreys($image) { $width = imageSX($image); $height = imageSY($image); // Crea un'immagine nuova di lavoro con le stesse dimensioni dell'originale $newImage = imagecreatetruecolor($width, $height); for ($y = 0; $y < $height; $y++) { // Scorri le righe for ($x = 0; $x < $width; $x++) { // Scorri le colonne list($r, $g, $b) = getRGB($image, $x, $y); // Leggi i valori RGB // FILTRO: parte specifica di trasformazione dei valori RGB $r = $g = $b = ($r + $g + $b) / 3; // Stesso valore per R, G e B $color = indexForColors($r, $g, $b); // Crea il valore (indice) di colore imagesetpixel($newImage, $x, $y, $color); // Scrivi i valori RGB } } return $newImage; }
Fai attenzione, poiché nella riga di codice del listato precedente:
list($r, $g, $b) = getRGB($image, $x, $y); // Leggi i valori RGB
usiamo l’assegnazione multipla, un espediente sintattico che permette di assegnare il primo valore di un array alla prima variabile in una lista, il secondo alla seconda variabile eccetera. Ecco un altro esempio:
php > list($a, $b, $c) = [3, 7, 2]; // Equivale a: $a = 3; $b = 7; $c = 2;
Vediamo ora solo la parte specifica di trasformazione dei valori RGB per le due ultime immagini della figura precedente. Per il bianco e nero (black_and_white.php), usiamo l’operatore ternario per confrontare la media dei valori delle componenti (il valore per creare il grigio visto prima) con un valore di soglia (in inglese threshold): se la media è superiore o uguale alla soglia allora il pixel diventa nero, altrimenti diventa bianco.
$threshold = 75; // Soglia per scegliere tra il bianco e il nero $color1 = 0xFFFFFF; // Colore da usare per un valore sopra la soglia $color2 = 0x000000; // Colore da usare per un valore sotto la soglia $average = ($r + $g + $b) / 3; // Media di R, G e B $color = ($average >= $threshold) ? $color1 : $color2;
Puoi creare altri filtri rimpiazzando nel codice del listato precedente la riga contrassegnata con FILTRO:. Per esempio, per ottenere l’ultima immagine della figura appena vista, usiamo la seguente istruzione per “fare passare” solo la componente rossa:
list($r, $g, $b) = [$r, 0, 0];
Per ottenere il negativo di un’immagine (seconda immagine nella prossima figura), calcoliamo i complementari delle componenti RGB (cioè i valori che servono per raggiungere il valore massimo di 255):
$r = 255 - $r; // Valore complementare per il canale rosso $g = 255 - $g; // ...per quello verde $b = 255 - $b; // ...e per quello blu
Terminiamo il capitolo con lo script fourcolors.php, che ha tre soglie e quattro intervalli di valore, ciascuno associato a un colore diverso (terza immagine nella figura che stai per vedere). Per questa associazione, la formula che abbiamo utilizzato è la seguente:
$n = 4; // Numero di colori associati a sottointervalli di [0, 255] $colors = [0xffff00, 0x00ff00, 0xff0000, 0x0000ff]; // $n colori $average = ($r + $g + $b) / 3; // Media di R, G e B $index = floor($average / ceil(255 / $n)); // Calcolo l'indice... $color = $colors[$index]; // ...per scegliere il colore nell'array dei colori
Variazioni sul tema
Partendo dal codice qui presentato puoi espandere la conoscenza della materia, per esempio in questi modi.
- Crea l’app Watermark2, che alterna due immagini come watermark (per esempio la mascotte e il logo di PHP) disponendole a scacchiera.
- Crea l’app ImageMirror2, che crea l’immagine speculare in verticale.
- Crea lo script twocolor.php modificando i due colori di black_and_white.php per ottenere un effetto simile a quello dell’ultima immagine dell’ultima figura, agendo sui valori di colore e sul valore di soglia.