Articolo: Immagini dinamiche con ASP.NET
| Titolo | Immagini dinamiche con ASP.NET |
| Prerequisiti | Concetti di base di ASP.NET |
| Categoria | Server-side/ASP.NET |
| Descrizione |
In questo articolo useremo .NET e GDI+ per restituire un'immagine
PNG dinamica al client; infatti, così come possibile utilizzare ASP.NET per
restituire HTML personalizzato, la stessa cosa possibile per le immagini.
|
Introduzione
Il 99,99% degli script server-side presenti su Internet è rivolto alla generazione di
codice HTML cosiddetto "dinamico", perchè è funzione dell'istante di esecuzione, del tipo
di utente e di altri input più o meno evidenti.
In effetti qualsiasi tipo di file può essere generato dinamicamente lato server e restituito
all'utente; evidentemente con i formati di testo questo è particolarmente facile (e probabilmente
è uno dei motivi per cui SVG avrà sempre più successo); con i formati binari invece ci
sono maggiori difficoltà perchè non sappiamo esattamente quali byte restituire per ottenere
un certo effetto, ad esempio visualizzare un coniglio rosa con un nome proprio variabile
scritto sulla pancia.
Ma per fortuna .NET può darci una mano. Invece di un coniglio rosa cercheremo di visualizzare
qualcosa di simile alla barra del voto delle recensioni di damix.it, quella graziosa
barra arancione che certamente avrete visto nel mio sito (perchè voi l'avete vista, veeero??? grr...).
In gergo tecnico un componente rettangolare che rappresenta una percentuale è detto "gauge", cioè
"misura".
Top Down
Pensiamo dapprima a "cosa" vorremmo poter scrivere in una pagina ASP.NET per generare un'immagine
dinamica; vorremmo poter chiamare una funzione che prende in ingresso una percentuale, due colori,
qualche altro parametro, così tanto per gradire; poi potremmo volere che questa funzione ci restituisse
un oggetto Bitmap; e poi magari vorremmo poter salvare questa Bitmap come file PNG "direttamente" nello
stream di risposta HTTP dell'utente, con una funzione simile a Response.Write(); quest'ultima funzione
c'è, si chiama Response.BinaryWrite() e prende come argomento un byte []; resta il problema di come ottenere
dei byte che effettivamente rappresentano l'immagine PNG che noi desideriamo. Per farla breve, vogliamo
scrivere una DLL che poi potrà consentirci di fare questo:
gauge.aspx
<%@ Page Language="C#" Debug="true" %>
<%@ Import Namespace="System" %>
<%@ Import Namespace="System.Drawing" %>
<%@ Import Namespace="System.Globalization" %>
<%@ Import Namespace="ImgGauge" %>
<%
Response.ContentType = "image/png";
int level = Int32.Parse(Request.QueryString["level"]);
if (level >= 0 && level <= 100)
{
Bitmap img = GaugeRenderer.GetImage(level, Server.MapPath("gauge-base.png"), new Point(1, 1), new Size(112, 12), Color.Blue, Color.Yellow, Color.Black);
byte [] imgData = GaugeRenderer.GetImageBytes(img);
img.Dispose();
Response.BinaryWrite(imgData);
}
%>
L'idea in effetti è quella di prendere un elemento grafico di base, ovvero nel nostro caso
gauge-base.png, e disegnarci sopra un retangolo sfumato di dimensione variabile con GDI+.
Da questa:

otteniamo questa:
La trasformazione è volutamente semplice; in dipendenza della vostra conoscenza di GDI+
e del tempo che avete a disposizione, potete ottenere cose molto più articolate.
Una volta realizzata questa pagina ASP.NET, potremmo creare pagine che la utilizzano, in
questo modo:
try-gauge.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<title>Prova Gauge</title>
</head>
<body>
<img src="gauge.aspx?level=85" alt="Pompa: 85%"/>
</body>
</html>
Comodo eh? Starete ovviamente già pensando che anche questa pagina potrebbe essere dinamica e
quindi il livello della gauge potrebbe essere letto da un database o da un file XML, come nel
caso di damix.it; oppure starete pensando a un grafico "a torta" da visualizzare con questo
metodo. Vediamo il codice della DLL che consente di renderizzare una gauge piena fino a un certo
livello:
GaugeRenderer.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Drawing.Imaging;
namespace ImgGauge
{
public class GaugeRenderer
{
public static Bitmap GetImage(int level, string imgBasePath, Point p, Size sz, Color start, Color end, Color bck)
{
Bitmap imgBase = new Bitmap(imgBasePath);
Bitmap img = new Bitmap(imgBase.Size.Width, imgBase.Size.Height);
Graphics gfx = Graphics.FromImage(img);
gfx.DrawImage(imgBase, 0, 0);
imgBase.Dispose();
SolidBrush b = new SolidBrush(bck);
gfx.FillRectangle(b, new Rectangle(p, sz));
LinearGradientBrush lg = new LinearGradientBrush(p, p + sz, start, end);
gfx.FillRectangle(lg, new Rectangle(p, new Size((int)((level / 100.0) * sz.Width), sz.Height)));
gfx.Dispose();
return img;
}
public static byte[] GetImageBytes(Bitmap img)
{
byte[] imgData = new byte[8 * img.Width * img.Height];
MemoryStream ms = new MemoryStream(imgData);
img.Save(ms, ImageFormat.Png);
long pos = ms.Position;
ms.Close();
Array.Resize<byte>(ref imgData, (int)pos);
return imgData;
}
}
}
Per semplicità ho strutturato il codice in due sole funzioni statiche. La prima, GetImage(), prende
come argomenti:
- Il livello della gauge, da 0 a 100
- Il percorso dell'immagine di partenza, sulla quale disegnare il rettangolo
- L'angolo superiore sinistro del rettangolo da tracciare
- La dimensione di tale rettangolo
- Il colore iniziale del rettangolo, ovvero il colore del rettangolo a sinistra
- Il colore finale del rettangolo, ovvero il colore del rettangolo a destra; ovviamente questo
colore è effettivamente visualizzato solo se il livello è a 100
- Lo sfondo della gauge, da utilizzare per riempire i punti non occupati dal rettangolo
colorato
La seconda, GetImageBytes() invece prende un'immagine e ne restituisce i byte. Cominciamo dal
commentare la seconda, che è effettivamente la più noiosa.
Si comincia allocando un vettore di byte sufficientemente grande; nel mio caso ho voluto abbondare
riservando 8 byte per ogni pixel dell'immagine. Poi si crea un oggetto MemoryStream; questo
oggetto permetterà di utilizzare il vettore di byte come un file. A questo punto salviamo l'immagine
in PNG sul MemoryStream creato; i pixel dell'immagine finiranno codificati nei byte del vettore
di byte. La proprietà Position di MemoryStream ci da la posizione del cursore; in pratica utilizziamo
questa informazione per capire quanti byte sono stati effettivamente scritti. Poi si chiude il
MemoryStream e si ridimensiona il vettore di byte in modo da tralasciare in eccesso che
avevamo riservato. Infine si restituisce i byte contenenti i pixel dell'immagine.
La prima funzione è molto più semplice. Praticamente crea un immagine Bitmap caricando il
file di base, contenente la gauge vuota, poi crea una seconda immagine vuota della stessa
dimensione; poi disegna l'immagine di base sull'immagine vuota e chiude il riferimento
all'immagine di base.
A questo punto si crea un SolidBrush con il colore di sfondo desiderato e si usa per disegnare
un rettangolo pieno della dimensione specificata.
Il LinearGradientBrush ci consentirà di ottenere l'effetto "sfumato" sulla gauge; il costruttore
prende quattro parametri; il punto dell'immagine in cui è idealmente applicato il colore iniziale,
il punto dell'immagine in cui e idealmente applicato quello finale, e i due colori.
Il rettangolo sfumato viene disegnato con altezza fissa e con larghezza proporzionale al livello.
Infine si fanno le necessarie operazioni di pulizia e si restituisce la Bitmap personalizzata
appena creata.
Conclusione
Spero che questo articolo vi sia stato utile; potete scaricare il
codice sorgente allegato. Con queste tecniche
potete realizzare un qualsiasi tipo di immagine personalizzata! Potete addirittura offrire
un servizio di e-card super-graficose! In cui il testo appare nell'immagine, e non all'esterno!
Wow!