Thumbnail “al volo” con .Net

Scarica il file thumbnail.aspx

 

In tanti anni di programmazione ho messo da parte tanti tips e tanti tricks che potrebbero venire utili a chi si avvicina per la prima volta al mondo della programmazione, specialmente in c#.
Non so se riuscirò ad avere il tempo di mettere on line il materiale che in questi anni ho sviluppato, ma piano piano, spero di riuscire a pubblicare abbastanza articoli da poter essere utile a qualcuno.

Spesso capita, per chi lavora con asp.net di dover fare i conti con immagini caricate dall’utente tramite un backoffice sviluppato “ad hoc”. Immaginiamo ad esempio un sito di e-commerce, l’utente avrà a disposizione un’area protetta dove amministrare il sito, inserire nuovi prodotti, informazioni e immagini;
oppure un sito di un’agenzia immobiliare, l’agenzia dovrà poter inserire un immobile, le relative informazioni e anche un set di immagini per rendere l’inserzione più appetibile per chi cerca casa.
Generalmente l’utente che usa il backoffice non ha specifiche competenze informatiche e quasi sempre non capisce la differenza tra caricare un’immagine da 3Mb rispetto ad una di 300Kb. In più, le attuali macchine fotografiche digitali scattano fotografia con risoluzioni ormai altissime, pesanti ed inutilmente grandi in contesti web, che porteranno sicuramente l’utente amministratore del sito, a caricare immagini inutilmente grandi e che rischiano di rallentare in maniera notevole il caricamente delle pagine anche con connessioni a banda larga.

Il programmatore che sviluppa il backoffice del sito, al fine di evitare suicidi di massa nell’attesa del caricamento delle pagine, dovrà cercare di limitare il più possibile questo problema scegliendo tra almeno 2 soluzioni architetturali: 

  1. Ridurre le immagini “on the fly” quando queste vengono visualizzate nel parte di presentation
  2. Ridimesionare le immagini all’atto del caricamento sul server

Inutile dire che la seconda ipotesi permette di risparmiare spazio su disco ma è un’operazione irreversibile. Una volta che l’immagine è stata ridimensionata non sarà più possibile visualizzarla con risoluzioni superiori se non con una grave perdita di qualità.
Oggi spiegherò la soluzione 1, come riuscire a ridurre le immagini “al volo” senza perdere la qualità e credetemi, nulla di più semplice.
Non cito nemmeno la possibilità di ridimensionare l’immagine con gli attributi del tag img html poichè, come ben sapete, quest’operazione non riduce la quantità di dati che il client/browser deve scaricare ma ridimensiona l’immagine solo dopo averla scaricata e con scarsissimi risultati.
 

Innanzitutto un concetto fondamentale:
quando in il browser interpreta il tag html <img> sa che riceverà dal webserver uno stream di byte che lui stesso dovrà interpretare e decodificare in base al “content type” che sarà inviato sempre dal webserver. Il file dunque che noi specifichiamo all’interno del tag <img>, verrà inviato dal webserver come uno stream di bytes al browser. Se si tratta di un file jpg, il webserver lo leggerà ed invierà il contenuto al browser con il content type “image/jpeg”, se si trattasse invece di un file gif, il content type inviato dal webserver sarebbe “image/gif”.
Detto questo, nulla ci vieta dunque di mettere al posto del nome del file da visualizzare un link di altra natura, che punti ad una pagina aspx, che poi ritorni a sua volta uno stream di bytes con gli opportuni content types.
Voglio poter inserire un tag simile:

<img src=”thumbnail.aspx?FN=/public/images/test.jpg&amp;MW=800&amp;MH=600“/>


Dunque, vediamo di capire come è composta la pagina aspx che dobbiamo creare e i concetti che ci stanno dietro… 

Esempio : 

Gundam ridimensionato a 150px Gundam ridimensionato a 150px in  altezza (guarda le proprietà dell’immagine per vederil link completo) 

Gundam ridimesionato a 300px Gundam ridimensionato a 300px in  altezza (guarda le proprietà dell’immagine per vederil link completo) 

Gundam immagine originale. 

  

La nostra pagina aspx che andremo a creare accetterà 3 parametri in ingresso: 

  1. FN = Path del file immagine da ridimensionare
  2. MW = Larghezza in pixel dell’immagine desiderata
  3. MH = Altezza in pixel dell’immagine desiderata 

Il codice completo ed utilizzabile è allegato all’articolo. Questo codice che ho scritto implementa in più anche la possibilità di applicare sopra l’immagine un logo precedentemente creato per “etichettare” per così dire, l’immagine. Esempi del risultato li potete trovare in tutte le immagine del seguente sito: http://www.ilcercacasa.org 

Passiamo a commentare il codice: 


 

private Image getThumbnailImage(Image src, Int32 width, Int32 height){
    return src.GetThumbnailImage(width, height, null, IntPtr.Zero);	//IntPtr.Zero
}

Ecco, la funzione descritta sopra GetThumbnailImage è una funzione parallela a quella scritta da me. Infatti non tutti sanno che windows memorizza una piccola miniatura di tutte le immagine che elabora all’interno di un database contenente appunto tutte le miniature che man mano vengono create. Questa funzione non fa altro che estrapolare dal database la miniatura dell’immagine richiesta (se non esiste la crea sul momento). Questa funzione però ha due grossi limiti. Il primo riguarda le dimensioni dell’immagine desiderata: la thumbnail (miniatura) che il sistema genera con questo metodo può essere al massimo di 120 px w/h, questo significa che se chiamo questa funzione passandole come parametri di ridimensionamento dei valori che superano i 120px, il sistema prima ridimensionerà l’immagine al valore massimo 120px e poi la ridimensionerà nuovamente al valore richiesto (es 400px). Il risultato è pessimo con una notevole perdita di qualità, è come se allargassimo digitalmente a 400px un’immagine di 120px. Il secondo limite è un bug della funzione: talvolta e non ho ancora capito perchè, la funzione all’interno delle GDI che viene chiamata da GetThumbnailImage ritorna un OutOfMemory facendo esplodere la chiamata. 

  


 

//Controllo se l'iimagine deve essere ridimensionata...
if(oldImage.Width > lWidth || oldImage.Height > lHeight) {
    xfactor = (1.0 * oldImage.Width / lWidth);
    yfactor = (1.0 * oldImage.Height / lHeight);

    if(xfactor >= yfactor) {
        factor = xfactor;
    } else {
        factor = yfactor;
    }

    //Adesso calcolo le dimensioni in base al fattore...
    lWidth = Convert.ToInt32(oldImage.Width / factor);
    lHeight = Convert.ToInt32(oldImage.Height / factor);

  

In questa parte del codice recuperiamo il fattore di riduzione che dobbiamo applicare alle dimensioni dell’immagine per poterla ridimesnionare proporzionalmente. Inoltre controlliamo anche se l’immagine deve essere ridimensionata, infatti l’utente potrebbe aver inserito un’immagine talmente piccola da non richiedere il ridimensionamento. 

  


 

// Bug di GetThumbnailImage, se ritorno un OutOfMemory, è necessario rieseguirla.
try {
    newImage = getThumbnailImage(oldImage, lWidth, lHeight);
} catch (OutOfMemoryException){
    newImage = getThumbnailImage(oldImage, lWidth, lHeight);
}

🙁
Queste sono le parti di codice che un programmatore non vorrebbe mai scrivere !  E’ la classica “sporca”… che però… in questo caso risolve il problema. Prima menzionavo un bug di questa funzione… ecco, richiamando la funzione subito dopo l’OutOfMemory tutto funziona correttamente !!! Incredibile… ma vero !!! 😎  

  


 

//Creo la bitmap nuova che conterrà l'immagine
Bitmap bmp = new Bitmap(lWidth, lHeight);
bmp.SetResolution(oldImage.HorizontalResolution, oldImage.VerticalResolution);

//Mi serve l'oggetto Graphics
Graphics grFx = Graphics.FromImage(bmp);

//Impostazioni globali...
grFx.CompositingQuality = CompositingQuality.HighQuality;
grFx.CompositingMode = CompositingMode.SourceOver;
grFx.InterpolationMode = InterpolationMode.HighQualityBicubic;
grFx.PageUnit = GraphicsUnit.Pixel;
grFx.SmoothingMode = SmoothingMode.HighQuality;

//Ridimensionamento
grFx.DrawImage(oldImage, 0, 0, lWidth, lHeight);
grFx.Dispose();
newImage = bmp;


In questa parte il codice si spiega da solo, creo un oggetto bitmap delle dimensioni desiderate, da questo genero l’oggetto Graphics che mi permette di manipolare diversi attributi relativamente alla qualità dell’immagine, dopodichè ridimensiono l’immagine originale “appiccicandola” sopra il mio oggetto bitmap.
 

  


 

switch(Path.GetExtension(Server.MapPath(decodedFileName))) {
    case ".jpg":
        Response.ContentType = "image/jpeg";	//jpeg
        newImage.Save(Response.OutputStream, ImageFormat.Jpeg);	//Jpeg
        break;
    case ".jpeg":
        Response.ContentType = "image/jpeg";	//jpeg
        newImage.Save(Response.OutputStream, ImageFormat.Jpeg);	//Jpeg
        break;
    case ".gif":
        Response.ContentType = "image/gif";		//gif
        newImage.Save(Response.OutputStream, ImageFormat.Gif);	//Gif
        break;
    case ".png":
        Response.ContentType = "image/png";		//Png
        newImage.Save(Response.OutputStream, ImageFormat.Png);	//Png
        break;
    case ".bmp":
        Response.ContentType = "image/bmp";		//Bmp
        newImage.Save(Response.OutputStream, ImageFormat.Bmp);	//Bmp
        break;
    default:
        Response.ContentType = "image/*";	//else
        newImage.Save(Response.OutputStream, ImageFormat.MemoryBmp);	//else / jpeg
        break;
}

Questa parte invece consente di ordinare al browser come deve interpretare lo stream di bytes che gli verrà passato. Se si tratta di una jpeg, il content type sarà come già detto in precedenza: “image/jpeg”, se è una gif sarà “image/gif” e così via.
  


 

switch(Path.GetExtension(Server.MapPath(decodedFileName))) {
    case ".jpg":
        Response.ContentType = "image/jpeg";	//jpeg
        newImage.Save(Response.OutputStream, ImageFormat.Jpeg);	//Jpeg
        break;
    case ".jpeg":
        Response.ContentType = "image/jpeg";	//jpeg
        newImage.Save(Response.OutputStream, ImageFormat.Jpeg);	//Jpeg
        break;
    case ".gif":
        Response.ContentType = "image/gif";		//gif
        newImage.Save(Response.OutputStream, ImageFormat.Gif);	//Gif
        break;
    case ".png":
        Response.ContentType = "image/png";		//Png
        newImage.Save(Response.OutputStream, ImageFormat.Png);	//Png
        break;
    case ".bmp":
        Response.ContentType = "image/bmp";		//Bmp
        newImage.Save(Response.OutputStream, ImageFormat.Bmp);	//Bmp
        break;
    default:
        Response.ContentType = "image/*";	//else
        newImage.Save(Response.OutputStream, ImageFormat.MemoryBmp);	//else / jpeg
        break;
}

Questa parte invece consente di ordinare al browser come deve interpretare lo stream di bytes che gli verrà passato. Se si tratta di una jpeg, il content type sarà come già detto in precedenza: “image/jpeg”, se è una gif sarà “image/gif” e così via.
Il metodo newImage.Save() permette di inviare l’immagine sullo stream.

 

Bene, direi che potrebbe essere tutto. per testare la thumbnail.aspx copiatela nel vs sito (ovviamente IIS/Microsoft), e chiamatela !!! Spero di esser stato utile a qualcuno :-). Buon divertimento.  

Questa voce è stata pubblicata in .Net e contrassegnata con , , , , , , , , , . Contrassegna il permalink.

1 risposta a Thumbnail “al volo” con .Net

  1. gabriele scrive:

    Molto ben fatto. Ho usato la tua idea (e il tuo codice) in una mia applicazione. Grazie.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.