Le prime versioni del web non prevedevano altro che uno scambio di informazioni puramente testuali e neppure si sarebbe potuto immaginare cosa questo mezzo avrebbe potuto garantire in futuro. Oggi le pagine web sono dense di contenuti multimediali quali immagini, filmati, file sonori, applicazioni flash, documenti Pdf, doc, xls e abbiamo già visto come il PHP possa essere adoperato per la creazione o la modifica in tempo reale di questi contenuti (vedere ad esempio le estensioni Filmati Flash e PHP con Ming e PDF con Php (1)).
Attraverso le estensioni GD è possibile creare, modificare, ridimensionare anche file di tipo immagine. Con questo articolo cercherò di entrare più nel dettaglio di quello che è possibile fare, dato che una prima infarinatura è già stata Disegnare con le librerie GD e PHP.
Una breve storia
La libreria scritta in C è stata sviluppata da una società privata e rilasciata come prodotto open source a dimostrazione della bontà di questo tipo di software. Ad oggi rappresenta praticamente l'unica soluzione utilizzabile quando si tratta di script PHP per la creazione di immagini al volo o per una loro veloce modifica.
La versione 1.x prevedeva inizialmente il supporto per i formati Gif e Jpeg, a cui si sono poi aggiunti i formati Png e Wbmp. Per problemi legali relativi alla licenza per il formato Gif il supporto per quest'ultimo venne tolto a partire dalla versione 1.6 e reintrodotto successivamente alla scadenza del brevetto (per questo motivo venne migliorato il supporto per il formato PNG in modo che sostituisse le immagini Gif).
Con la versione 2 le GD hanno fatto passi da giganti introducendo il supporto per il truecolor, per il canale alfa, per una più vasta gamma di caratteri (con il supporto per Freetype 2) e successivamente reintroducendo il supporto per il formato GIF (versione 2.0.28).
Lo sviluppo continua costantemente e vengono rilasciate nuove funzionalità ad ogni versione; non è detto però che le funzionalità siano accessibili da PHP nel momento stesso in cui vengono implementate.
Da qualche versione di PHP questa libreria viene distribuita direttamente con il codice sorgente dell'interprete, per cui è possibile considerarla come una presenza praticamente obbligata su server con supporto per questo linguaggio.
Formati supportati
I formati principali supportati dalla libreria GD sono GIF, JPEG e PNG, vediamo come vengono gestiti.
Gif
Mime-Type: image/gif
Principali funzioni: imagecreatefromgif(), imagegif(), imagecolortransparent()
Questo tipo di immagini presuppone una limitazione nel numero di colori utilizzato attraverso la creazione di palette con un massimo di 256. Esistono due varianti: GIF87a, la prima versione e GIF89a una successiva rivista e corretta con aggiunte interessanti quali l'interlacciamento, la trasparenza e l'animazione. Con le GD implementate da PHP è possibile soltanto utilizzare le prime due caratteristiche, cioè la memorizzazione delle immagini in modo che prima ne venga mostrata una versione in scarsa qualità e la possibilità di impostare un colore come trasparente. Non è invece possibile creare immagini animate per il momento, anche se le GD lo permettono da poche versioni ed è quindi probabile che il supporto venga esteso anche al PHP.
Per i problemi di licenza accennati in precedenza è possibile trovarsi di fronte ad una versione che non permetta di maneggiare questo formato. È consigliabile quindi evitare di utilizzarlo nella scrittura di applicazioni portabili, e, quando possibile, preferirgli PNG.
Jpeg
Mime-Type: image/jpeg, image/jpe
Principali funzioni: imagecreatefromjpeg(), imagejpeg(), imageinterlace()
Questo formato compresso consente di salvare immagini di tipo fotografico, quindi con milioni di colori, in uno spazio ben più ridotto di quello occupato dall'immagine non compressa. Come per le GIF è stato introdotto il supporto per l'interlacciamento dell'immagine, cioè è possibile che l'immagine venga caricata a poco a poco e vengano mostrate versioni sempre meno approssimative della stessa. Questo avviene con browser basati sul motore Gecko (Mozilla, Firefox, etc.) mentre Internet Explorer non è in grado di visualizzarle correttamente e si limita a mostrare l'immagine soltanto quando è stata caricata del tutto. Paradossalmente quindi risulta che il metodo migliore per dare all'utente impaziente prova dell'effettiva esistenza dell'immagine sia quello di lasciarla non interlacciata e fare quindi in modo che il browser la mostri riga per riga mano a mano che viene caricata.
Png
Mime-Type: image/png
Principali funzioni: imagecreatefrompng(), imagepng(), imagesavealpha(), imagealphablending()
Questo formato risulta di comodo utilizzo dato che è in grado di lavorare in due modalità di cui una molto vicina al funzionamento delle GIF e l'altra molto vicina al funzionamento delle JPEG. Per questo motivo è il tipo di immagine consigliato in fase di utilizzo. Alcuni browser datati potrebbero non essere però in grado di visualizzarle, mentre alcuni browser odierni tipo Internet Explorer potrebbero avere avere alcuni problemi con determinate caratteristiche (salvo ricorrere ad alcuni workaround), per esempio con l'Alpha channel che consentirebbe una gestione più flessibile della trasparenza.
Applicazioni pratiche
Dopo questa rapida carrellata sui principali formati supportati da questa libreria vediamo qualche esempio pratico di cosa è possibile fare.
Riconoscimento versione, formati supportati
Il principale problema quando ci si trova di fronte alla necessità di distribuire uno script che faccia uso di questa libreria è la mancanza di omogeneità tra una versione e l'altra. Ci troviamo infatti di fronte alla possibilità di trovare un sistema completamente differente, senza le funzioni che avevamo previsto di utilizzare. Innanzitutto ci è utile scoprire se le librerie che andremo ad utilizzare appartengono alla versione 1.x o alla versione 2.x.
Purtroppo come evidenziato in questa discussione sorgono dei problemi passando attraverso differenti versioni di PHP compilate con diverse versioni delle GD. Quello che si deve fare quindi è creare un workaround per determinare con precisione se sia possibile lavorare sulle immagini in truecolor.
function GDVersion(){
if( !in_array('gd', get_loaded_extensions()) ) return 0;
elseif( isGD2supported() ) return 2;
else return 1;
}
function isGD2supported(){
global $GD2;
if( isset($GD2) AND $GD2 ) return $GD2;
else{
$php_ver_arr = explode('.', phpversion());
$php_ver = intval($php_ver_arr[0])*100+intval($php_ver_arr[1]);
if( $php_ver < 402 ){ // PHP <= 4.1.x
$GD2 = in_array('imagegd2',get_extension_funcs("gd"));
}
elseif( $php_ver < 403 ){ // PHP = 4.2.x
$im = @imagecreatetruecolor(10, 10);
if( $im ){
$GD2 = 1;
@imagedestroy($im);
}
else $GD2 = 0;
}
else{ // PHP = 4.3.x
$GD2 = function_exists('imagecreatetruecolor');
}
}
return $GD2;
}
echo 'Su questo sistema è presente la versione ' . GDVersion() . ' delle librerie GD.';
A questo punto siamo in grado di lavorare sulle immagini sapendo di preciso quali funzioni sono utilizzabili. Può essere utile anche sapere quali tipi di immagini è possibile manipolare. Per farlo è possibile utilizzare il seguente codice:
function IsFormatSupported($format){
if( ($format == 'gif') AND (imagetypes() & IMG_GIF) )return true;
elseif( ($format == 'jpeg') AND (imagetypes() & IMG_JPG) )return true;
elseif( ($format == 'png') AND (imagetypes() & IMG_PNG) )return true;
else return false;
}
Un'immagine di prova
Creiamo un'immagine di prova per testare il funzionamento visivo della libreria.
if( GDVersion() ){
header("Content-type: image/png");
if( GDVersion() == 1 ){
$im = @imagecreate(300, 255) or die("Cannot Initialize new GD image stream");
$black = imagecolorallocate($im, 0, 0, 0);
$white = imagecolorallocate($im, 255, 255, 255);
$red = imagecolorallocate($im, 255, 0, 0);
$green = imagecolorallocate($im, 0, 255, 0);
$blue = imagecolorallocate($im, 0, 0, 255);
imagefilledrectangle($im, 0, 0, 51, 300, $white);
imagefilledrectangle($im, 51, 0, 102, 300, $red);
imagefilledrectangle($im, 102, 0, 153, 300, $green);
imagefilledrectangle($im, 153, 0, 204, 300, $blue);
imagefilledrectangle($im, 204, 0, 255, 300, $black);
}
else{
$im = @imagecreatetruecolor(300, 255) or die("Cannot Initialize new GD image stream");
for( $i = 0; $i < 256; $i++ ){
$col = imagecolorallocate($im, 255, $i, $i);
imagefilledrectangle($im, 0, $i, 100, $i+1, $col);
}
for( $i = 255; $i > -1; $i-- ){
$col = imagecolorallocate($im, $i, 255, $i);
imagefilledrectangle($im, 100, 255-$i, 200, 256-$i, $col);
}
for( $i = 0; $i < 256; $i++ ){
$col = imagecolorallocate($im, $i, $i, 255);
imagefilledrectangle($im, 200, $i, 300, $i+1, $col);
}
}
$grey = imagecolorallocate($im, 100, 100, 100);
imageString($im, 5, 120, 100, 'GD ' . GDVersion(), $grey);
imagepng($im);
imagedestroy($im);
}
else{
echo 'Errore, libreria GD non disponibile su questo sistema!';
}
In questo modo abbiamo testato il funzionamento della libreria nell'utilizzare una vasta gamma di colori e abbiamo anche realizzato un esempio pratico per l'utilizzo delle funzioni create precedentemente.
Thumbnail
Vediamo ora un uso comune delle librerie GD: la creazione di gallerie di thumbnail come anteprime di dimensioni ridotte mantenendo intatte le proporzioni dell'immagine.
function OutThumb($src, $w, $h){
$size = getimagesize($src);
if( $size[2] == 2 ){$im = @imagecreatefromjpeg($src);}
elseif( $size[2] == 1 ){$im = @imagecreatefromgif($src);}
elseif( $size[2] == 3 ){$im = @imagecreatefrompng($src);}
$newwidth = $size[0];
$newheight = $size[1];
if( $newwidth > $w ){
$newheight = ($w / $newwidth) * $newheight;
$newwidth = $w;
}
if( $newheight > $h ){
$newwidth = ($h / $newheight) * $newwidth;
$newheight = $h;
}
if( (GDVersion() == 2) AND ($size[2] != 1) ){
$new = imagecreatetruecolor($newwidth, $newheight);
imagecopyresampled($new, $im, 0, 0, 0, 0, $newwidth, $newheight, $size[0], $size[1]);
}
else{
$new = imagecreate($newwidth, $newheight);
imagecopyresized($new, $im, 0, 0, 0, 0, $newwidth, $newheight, $size[0], $size[1]);
}
header('Content-Type: ' . $size['mime']);
if( $size[2] == 2 ){@imagejpeg($new, '', 100);}
elseif( $size[2] == 1 ){@imagegif($new);}
elseif( $size[2] == 3 ){@imagepng($new);}
@imagedestroy($im);
@imagedestroy($new);
}
A volte può risultare utile creare delle thumbnail di forma quadrata (ad esempio per averle tutte nelle stesse proporzioni). Ecco un esempio di creazione di thumbnail quadrate, viene ritagliata la parte centrale più grande possibile e ridimensionata alla dimensione $l fornita.
function OutThumb($src, $l){
$size = getimagesize($src);
if( $size[2] == 2 ){$im = @imagecreatefromjpeg($src);}
elseif( $size[2] == 1 ){$im = @imagecreatefromgif($src);}
elseif( $size[2] == 3 ){$im = @imagecreatefrompng($src);}
if( $size[0]>$l or $size[1]>$l ){
$centerX = $size[0]/2;
$centerY = $size[1]/2;
if( $size[0] > $size[1] ){
$luy = 0;
$lux = $centerX-$centerY;
$rdy = $size[1];
$rdx = $size[1];
}
else{
$lux = 0;
$luy = $centerY-$centerX;
$rdx = $size[0];
$rdy = $size[0];
}
if( (GDVersion() == 2) AND ($size[2] != 1) ){
$new = imagecreatetruecolor($l, $l);
imagecopyresampled($new, $im, 0, 0, $lux, $luy, $l, $l, $rdx, $rdy);
}
else{
$new = imagecreate($l, $l);
imagecopyresized($new, $im, 0, 0, $lux, $luy, $l, $l, $rdx, $rdy);
}
}
else{
$new &= $im;
}
header('Content-Type: ' . $size['mime']);
if( $size[2] == 2 ){@imagejpeg($new, $fileOut, 100);}
elseif( $size[2] == 1 ){@imagegif($new, $fileOut);}
elseif( $size[2] == 3 ){@imagepng($new, $fileOut);}
@imagedestroy($im);
@imagedestroy($new);
}
Bisogna sottolineare il fatto che questo processo occupi pesantemente le risorse del sistema e quindi è caldamente consigliabile di utilizzarlo insieme ad un processo di caching.
Bordo
Un'altro effetto interessante da applicare ad un'immagine è l'inserimento di un bordo esterno. Questo effetto è realizzabile facilmente con la funzione imagerectangle.
function ApplicaBordo(&$imres){
$h = imagesy($imres)-1;
$w = imagesx($imres)-1;
$white = imagecolorallocate($imres, 255, 255, 255);
$black = imagecolorallocate($imres, 0, 0, 0);
imagerectangle($imres, 0, 0, $w, $h, $black); // Bordo composto da una linea esterna nera
imagerectangle($imres, 1, 1, $w-1, $h-1, $white); // e da una linea interna bianca
}
Questo codice ha però un problema quando si lavora con immagini PNG con il canale alpha attivato, in quanto gli angoli sarebbero sottoposti ad una sovrapposizione di linee. Andiamo quindi a creare una nostra funzione imagemyrectangle() che richiameremo al posto di imagerectangle() per evitare questo comportamento.
function imagemyrectangle(&$imgres, $x1, $y1, $x2, $y2, $col){
imageline($imgres, $x1, $y1, $x2-2, $y1, $col ); //top
imageline($imgres, $x1+1, $y2-1, $x2, $y2-1, $col ); //bottom
imageline($imgres, $x2-1, $y1, $x2-1, $y2-2, $col ); //right
imageline($imgres, $x1, $y1+1, $x1, $y2, $col ); //left
}
Watermark
Anche l'applicazione di un watermark, cioè di un'immagine salvata sopra all'originale per impedirne un utilizzo al di fuori dei termini di licenza è un'operazione abbastanza semplice con le librerie GD.
function AddWaterMark(&$imgres, &$imgWM){
imageCopyMerge($imgres, $imgWM, 5, 5, 0, 0, imageSX($imgWM), imageSY($imgWM), 50);
}
È sufficiente richiamare la funzione passando come primo argomento la risorsa d'immagine (quella restituita da una delle funzioni imagecreatefrom*() per esempio) su cui vogliamo applicare il watermark e come secondo la risorsa del watermark stesso. In questo caso abbiamo inserito l'immagine a 5 pixel dal margine superiore e da quello laterale sinistro.
Caching
Concludiamo questo articolo dando uno spunto di riflessione: le librerie GD occupano molte risorse del sistema in cui vanno ad operare ed è quindi bene pensare ad un sistema di caching già in fase di progettazione.
Un semplice sistema di caching dovrebbe includere un controllo all'inizio dello script per verificare l'esistenza della copia cache precedentemente creata, fallito il quale verrebbe avviato il processo vero e proprio di creazione e modifica (quello che occupa maggiormente le risorse). Si possono anche ipotizzare sistemi più avanzati che includano nella loro attuazione un accesso al file di configurazione del server web, ad esempio facendo partire lo script di creazione immagine alla richiesta di una non esistente.