giovedì 18 novembre 2010

DataURI e le immagini dinamiche in ASP.NET

In questo post vorrei proporvi un modo per creare immagini dinamiche  (in maniera analoga a quanto già proposto in questo post) all’interno delle nostre pagine ASPX utilizzando il DataURI.

Partiamo dal definire cosa è il DataURI.

Il DataURI (o più esattamente lo schema data URI), la cui definizione esatta è rintracciabile seguendo il link,  rappresenta un modo per includere all’interno delle nostre pagine web delle risorse (ad esempio grafiche, ma non solo).

Lo schema data URI compare già nelle specifiche dell’HTML 4.01.

I vantaggi di utilizzare lo schema data URI sono i seguenti:

  • le risorse incorporate nella pagina non richiedono differenti chiamate al server come accadrebbe se le risorse fossero indicate con un normale URL. Si ha quindi un risparmi nel numero di richieste e si elimina il traffico dell’header delle stesse;
  • per files di piccole dimensioni si ha un guadagno di tempo, poichè, generalmente, l’avvio della comunicazione TCP è lento;
  • nelle comunicazioni HTTPS, il browser, di solito, richiede che tutti gli elementi della pagina siano scaricati in connessione sicura (pena la comunicazione all’utente che ci sono elementi non “sicuri”) e l’HTTPS aggiunge un ulteriore overhead alla trasmissione;
  • i messaggi email possono, in questo modo contanere immagini senza che queste appaiano come degli allegati;
  • se il browser ha un numero basso di connessioni simultanee, si ha un vantaggio di tempo.

Lo schema data URI, però, ha anche degli svantaggi:

  • le risorse incorporate come data URI non sono cachate come le comuni risorse (ad esempio immagini) referenziate con un comune URL;
  • alcuni browser ancora in circolazione (ad esempio IE7 con circa il 27% della distribuzione sul mercato a luglio 2010) non lo supportano;
  • alcuni browser hanno una limitazione sulla lunghezza massima del data URI (ad esempio IE8 ha una limitazione a 32KB);
  • l’encoding del data URI (in base 64) è mediamente 1/3 più largo dell’equivalente encoding binario;
  • i software che si occupano di controllare la sicurezza dei siti lavorano male sui dati contanuti nello schema data URI.

Il formato di una risorsa espressa secondo lo schema data URI è il seguente:

data:[<MIME-type>][;charset=<encoding>][;base64],<data>

Se non si indica il base64, si intende che i dati sono espressi in ASCII.

Ad esempio, un’immagine in HTML è espressa nel seguente modo:

  1. <img src="data:image/png;base64,
  2.         iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGP
  3.         C/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IA
  4.         AAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1J
  5.         REFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jq
  6.         ch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0
  7.         vr4MkhoXe0rZigAAAABJRU5ErkJggg==" alt="Red dot" />

Lo schema data URI può essere utilizzato anche all’interno di un CSS (per i browser che lo supportano) o per includere del JavaScript.

Capito cosa è lo schema data URI vediamo come possiamo utilizzarlo  nelle nostre pagine ASPX.

Innanzitutto vediamo cosa si ottiene in termini di richieste e dati trasferiti quando includiamo delle immagini tramite data URI e in maniera classica.

Per fare questo realizziamo due pagine all’interno delle quali visualizzeremo le immagini contenute in una cartella nelle due modalità.

Il code behind della pagina che utilizza la tecnica standard è la seguente:

  1. Imports System.IO
  2.  
  3. Public Class MultiImageStandard
  4.     Inherits System.Web.UI.Page
  5.  
  6.     Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
  7.         If Not IsPostBack Then
  8.             CreateImages(10)
  9.         End If
  10.     End Sub
  11.  
  12.     Private Sub CreateImages(ByVal numImages As Integer)
  13.         Dim imagePath = Server.MapPath("/images")
  14.         Dim imageRep = New ImageRepository()
  15.         Dim imageFiles = imageRep.GetAllImages(imagePath)
  16.         For Each imageFile In imageFiles
  17.             Dim image = New Image()
  18.             Dim fi = New FileInfo(imageFile)
  19.             image.ImageUrl = String.Format("/images/{0}", fi.Name)
  20.             Me.Controls.Add(image)
  21.         Next
  22.     End Sub
  23. End Class

La classe ImageRepository enumera le immagini presenti nella cartella.

In sostanza creiamo un oggetto Image (web control) per ogni immagine ed impostiamo il sui ImageUrl. Il risultato ottenuto è il seguente:

image

Utilizzando IE9 e la developer toolbar (tasto F12) possiamo osservare il numero di chiamate eseguite verso il server per recuperare la pagina e le 12 immagini visualizzate.

Il code behind della pagina che utilizza lo schema data URI è il seguente:

  1. Public Class MultiImageDataUri
  2.     Inherits System.Web.UI.Page
  3.  
  4.     Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
  5.         If Not IsPostBack Then
  6.             CreateImages(10)
  7.         End If
  8.  
  9.     End Sub
  10.  
  11.     Private Sub CreateImages(ByVal numImages As Integer)
  12.         Dim imagePath = Server.MapPath("/images")
  13.         Dim imageRep = New ImageRepository()
  14.         Dim imageFiles = imageRep.GetAllImages(imagePath)
  15.         For Each imageFile In imageFiles
  16.             Dim image = New Image()
  17.             image.ImageUrl = CreateDataUri(imageFile)
  18.             Me.Controls.Add(Image)
  19.         Next
  20.     End Sub
  21.  
  22.     Private Function CreateDataUri(ByVal imagePath As String) As String
  23.         Dim image = New Drawing.Bitmap(imagePath)
  24.         Dim base64String As String
  25.         Using ms As New IO.MemoryStream
  26.             image.Save(ms, image.RawFormat)
  27.             base64String = Convert.ToBase64String(ms.ToArray())
  28.         End Using
  29.         Return String.Format("data:image/png;base64,{0}", base64String)
  30.     End Function
  31.  
  32. End Class

In questo caso, per ogni immagine da visualizzare, craiamo un oggetto Bitmap, ne ricaviamo la sua rappresentazione come array di Byte e, quindi, lo encodiamo in base64.

Il risultato, in questo caso, è il seguente:

image

Osserviamo una sola connessione per recuperare l’intera pagina.

Un altro utilizzo dello schema data URI può essere quello di visualizzare delle immagini dinamiche.

Supponiamo di voler fare in modo che data un’immagine venga applicata su di essa una scritta di watermark (esattamente come già avevamo fatto nel post).

Per realizzare tutto ciò è sufficiente creare un immagine bitmap a partire dall’immagine su cui applicare il watermark, recuperare il suo contesto grafico, disegnare il watermark su di essa e, infine encodarla in base64.

In sostanza possiamo scrivere:

  1. Imports System.Drawing
  2.  
  3. Public Class DynamicImage
  4.     Inherits System.Web.UI.Page
  5.  
  6.     Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
  7.         If Not IsPostBack Then
  8.             CreateImage("/images/BigImage/DominicanRepublic_DE-DE1561561743.jpg", "CodeTailor", Image1)
  9.         End If
  10.     End Sub
  11.  
  12.     Private Sub CreateImage(ByVal imagePath As String,
  13.                             ByVal watermark As String,
  14.                             ByVal imageControl As System.Web.UI.WebControls.Image)
  15.  
  16.         Dim imageFullPath = HttpContext.Current.Server.MapPath(imagePath)
  17.         Dim image As New Bitmap(imageFullPath)
  18.         Dim gr = Graphics.FromImage(image)
  19.         Dim watermarkFont = New Font(FontFamily.GenericMonospace, _
  20.             10, FontStyle.Regular, GraphicsUnit.Pixel)
  21.         Dim textSize = gr.MeasureString(watermark, watermarkFont)
  22.         Dim x = 0
  23.         While x < image.Width
  24.             Dim y = 0
  25.             While y < image.Height
  26.                 gr.DrawString(watermark, watermarkFont, Brushes.White, x, y)
  27.                 y = CInt(y + (3 / 2 * textSize.Height))
  28.             End While
  29.             x = CInt(x + (3 / 2 * textSize.Width))
  30.         End While
  31.         Dim base64String As String
  32.         Using ms As New IO.MemoryStream
  33.             image.Save(ms, image.RawFormat)
  34.             base64String = Convert.ToBase64String(ms.ToArray())
  35.         End Using
  36.         imageControl.ImageUrl = String.Format("data:image/png;base64,{0}", base64String)
  37.     End Sub
  38. End Class

Il risultato che otteniamo è il seguente:

image



Nessun commento: