Handler para manipular imagenes

Cada día es mas frecuente - imprescindible diria yo - que las aplicaciones Web permitan al usuario subir al servidor sus propias imagenes. El problema es que ese usuario no tiene porque saber los peligros de subir imagenes demasiado grandes y pesadas, o porque no, las imagenes deben ser grandes por requerimientos del sistema - como es mi caso -.

Estoy trabajando es un sistema que permite subir archivos de gran tamaño a la base de datos, fotos que deben visualizarse a través de una página web pero que deben permitir su descarga en alta definicion.

Una solucion fácil sería crear dos imagenes - una con menor calidad para mostrar en web y otra para descargar. Descarté esta solución ya que potencialmente el sistema va a trabajar con millones de imagenes, y duplicar toda esa información es un coste de almacenamiento excesivo.

La solución implementada ha sido con un manejador http, que recibe la petición a la imagen y la procesa para reducir su tamaño "al vuelo".

La idea es que los controles imagen no apunten directamente al archivo, sino que apunte al handler para manipular la imagen antes de servirla de vuelta. Por ejemplo, el código de mis webform es algó similar a esto. ImageUrl apunta a un handler http - BigImgHandler.ashx - pasando por query string la ruta de la imagen que queremos mostrar.

 

<form id="form1" runat="server">

<asp:Image ID="Image1" runat="server"

ImageUrl="BigImgHandler.ashx?img=~/img/bigImage.jpg" />

</form>

El handler recibirá la ruta de la imagen real, la leerá del disco y la modifcará disminuyendo su tamaño antes de devolverla.

El esqueleto del handler se muestra a continuacion - únicamente implementamos los métodos que define la interfaz IHttpHandler, como puede observarse todo el trabajo se delega en el método ProcesarSolicitud que comentaremos más adelante.

 

using System;

using System.Data;

using System.Configuration;

using System.Linq;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.HtmlControls;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Xml.Linq;

using System.Drawing.Imaging;

using System.Drawing;

using System.IO;

 

public class BigImgHandler: IHttpHandler

{

public int Width { get; set; }

public int Height { get; set; }

 

public virtual void Inicialize();

 

public bool IsReusable

{

get{

return true;}

}

 

public void ProcessRequest(HttpContext context)

{

ProcesarSolicitud(context);

}

 

// ... Implementacion de la clase

}

También definimos dos propiedades, Width y Height, que van a determinar el tamaño de salida final de la imagen, y un método Inicialize - pensado precisamente para asignar estas propiedades. El método es virtual lo que nos permitirá sobreescribirlo es clases derivadas - podrémos tener varios handler, que únicamente modifiquen el tamaño de salida.

Bien, vamos ahora con el método ProcesarSolicitud, que es quien realiza todo el trabajo.

 

private void ProcesarSolicitud(HttpContext context)

{

Inicialize();

context.Response.Clear();

string imgurl = context.Request["img"];

context.Response.ContentType = GetContentType(imgurl);

string path = VirtualPathUtility.ToAbsolute(imgurl);

path = context.Server.MapPath(path);

byte[] buffer = GetImagen(path, Width, Height);

context.Response.BinaryWrite(buffer);

}

El método recoge la url de la imagen que vamos a procesar, la convierte a una ruta local haciendo uso de la clase VirtulPathUtility y devuelve una salida binaria con una nueva imagen que genera el método GetImagen.

 
	byte[] GetImagen(String path, int width, int height)

{

using (FileStream fs =

new FileStream(path, FileMode.Open, FileAccess.Read))

{

using (Bitmap imgIn = new Bitmap(fs))

{

double y;

double x;

double ratio;

DeterminarRatio(width, height, imgIn,

out y, out x, out ratio);

using (System.IO.MemoryStream outStream =

new System.IO.MemoryStream())

{

using (Bitmap imgOut =

new Bitmap((int)(x * ratio), (int)(y * ratio)))

{

using (Graphics g = Graphics.FromImage(imgOut))

{

NewImage(path, imgIn, y, x, factor,

outStream, imgOut, g);

}

return outStream.ToArray();

}

}

}

}

}

 

private void NewImage(String path, Bitmap imgIn,

double y, double x, double ratio,

System.IO.MemoryStream outStream,

Bitmap imgOut, Graphics g)

{

g.Clear(Color.White);

g.DrawImage(imgIn,

new Rectangle(0, 0, (int)(ratio* x),

(int)(ratio * y)),

new Rectangle(0, 0, (int)x, (int)y),

GraphicsUnit.Pixel);

 

imgOut.Save(outStream, GetImageFormat(path));

}

Es aqui donde se realiza todo el trabajo ... se hacen varias cosas ...

  1. Lectura de la imagen original - en modo solo lectura (para que no haya errores al tratar archivo protegidor contra escritura)
  2. Calculo del ratio altura-anchura, para que la nueva imagen sea proporcional y no quede "deformada"
  3. Creación de la nueva imagen. Para ello se crea una nueva image (una nueva instancia de la clase Bitmap) con el ancho y alto calculado anteriormente, y se dibuja en ella el contenido de la imagen original.
  4. Finalmente se devuelve el nuevo archivo como un array de bytes.

Es importante destacar que:

  1.  La clase Bitmap no se refiere a un formato de imagen concreto. El formato lo proporcionamos al grabar la imagen.
  2.  La utilizacion de using garantiza que los recursos utilizados son liberados al finalizar la ejecución. 

El código se apoya en varias funciones auxiliares:

  • DeterminarRatio - que establece el ratio de altura-anchura de la imagen original para que la imagen resultante no se deforme
  • GetContentType - que devuelve el literal correspondiente al ContentType de cada tipo de imagen.
  • GetImageFormat - que retorna el tipo de la enumeracion ImageFormat necesario para crear la nueva imagen.

El código de esas funciones se muestra aqui.

 

private static void DeterminarRatio(int width, int height,

Bitmap imgIn, out double y, out double x, out double ratio)

{

y = imgIn.Height;

x = imgIn.Width;

ratio = 1;

 

if (width > 0)

{

ratio = width / x;

}

else if (height > 0)

{

ratio = height / y;

}

}

 

string GetContentType(String path)

{

switch (Path.GetExtension(path))

{

case ".bmp":

return "Image/bmp";

case ".gif":

return "Image/gif";

case ".jpg":

return "Image/jpeg";

case ".png":

return "Image/png";

default:

return "";

}

}

 

ImageFormat GetImageFormat(String path)

{

switch (Path.GetExtension(path))

{

case ".bmp":

return ImageFormat.Bmp;

case ".gif":

return ImageFormat.Gif;

case ".jpg":

return ImageFormat.Jpeg;

case ".png":

return ImageFormat.Png;

case ".ico":

return ImageFormat.Icon;

default:

return ImageFormat.Jpeg;

}

}

Por úlitmo comentar que esta solución es válida solo en determinadas circunstancias - ya que requiere de un uso extra de memoria y procesador. En mi caso las imagenes solo se muestran al hacer click en un enlace por lo que el uso de memoria y procesador compesa sobradamente el de almacenamiento.

El código completo de handler es el siguiente:

using System;

using System.Data;

using System.Configuration;

using System.Linq;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.HtmlControls;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Xml.Linq;

using System.Drawing.Imaging;

using System.Drawing;

using System.IO;

 

public class BigImgHandler: IHttpHandler

{

public bool IsReusable

{

get{return true;}

}

 

public void ProcessRequest(HttpContext context)

{

ProcesarSolicitud(context);

}

 

public int Width { get; set; }

public int Height { get; set; }

 

public void Inicialize()

{

this.Width = 700;

}

 

 

private void ProcesarSolicitud(HttpContext context)

{

Inicialize();

context.Response.Clear();

string imgurl = context.Request["img"];

context.Response.ContentType = GetContentType(imgurl);

string path = VirtualPathUtility.ToAbsolute(imgurl);

path = context.Server.MapPath(path);

byte[] buffer = GetImagen(path, Width, Height);

context.Response.BinaryWrite(buffer);

}

byte[] GetImagen(String path, int width, int height)

{

using (FileStream fs =

new FileStream(path, FileMode.Open, FileAccess.Read))

{

using (Bitmap imgIn = new Bitmap(fs))

{

double y;

double x;

double ratio;

DeterminarRatio(width, height, imgIn,

out y, out x, out ratio);

using (System.IO.MemoryStream outStream =

new System.IO.MemoryStream())

{

using (Bitmap imgOut =

new Bitmap((int)(x * ratio), (int)(y * ratio)))

{

using (Graphics g = Graphics.FromImage(imgOut))

{

NewImage(path, imgIn, y, x, factor,

outStream, imgOut, g);

}

return outStream.ToArray();

}

}

}

}

}

 

private void NewImage(String path, Bitmap imgIn,

double y, double x, double ratio,

System.IO.MemoryStream outStream,

Bitmap imgOut, Graphics g)

{

g.Clear(Color.White);

g.DrawImage(imgIn,

new Rectangle(0, 0, (int)(ratio* x),

(int)(ratio * y)),

new Rectangle(0, 0, (int)x, (int)y),

GraphicsUnit.Pixel);

 

imgOut.Save(outStream, GetImageFormat(path));

}

 

private static void DeterminarRatio

(int width, int height, Bitmap imgIn,

out double y, out double x, out double ratio)

{

y = imgIn.Height;

x = imgIn.Width;

ratio = 1;

if (width > 0)

{

ratio = width / x;

}

else if (height > 0)

{

ratio = height / y;

}

}

 

string GetContentType(String path)

{

switch (Path.GetExtension(path))

{

case ".bmp":

return "Image/bmp";

case ".gif":

return "Image/gif";

case ".jpg":

return "Image/jpeg";

case ".png":

return "Image/png";

default:

return "";

}

}

 

ImageFormat GetImageFormat(String path)

{

switch (Path.GetExtension(path))

{

case ".bmp":

return ImageFormat.Bmp;

case ".gif":

return ImageFormat.Gif;

case ".jpg":

return ImageFormat.Jpeg;

case ".png":

return ImageFormat.Png;

case ".ico":

return ImageFormat.Icon;

default:

return ImageFormat.Jpeg;

}

}

}

Saludos,

DJK

 

Pedro  Herrarte  Sánchez
Handler para manipular imagenes
Pedro Herrarte Sánchez

Pedro Herrarte, es consultor independiente, ofreciendo servicios de consultoría, análisis, desarrollo y formación. Posee mas de diez años de experiencia trabajando para las principales empresas de España. Es especialista en tecnologías .NET, entornos Web (ASP.NET, ASP.NET MVC,jQuery, HTML5), bases de datos (SQL Server y ORACLE) e integración de sistemas. Es experto en desarrollo (C#, VB.Net, T-SQL, PL/SQL, , ASP, CGI , C, Pro*C, Java, Essbase, Vignette, PowerBuilder y Visual Basic ...) y bases de datos (SQL Server y ORACLE). Pedro es MCP y MAP 2012, es fundador, diseñador y programador de www.devjoker.com..
Fecha de alta:26/05/2009
Última actualizacion:26/05/2009
Visitas totales:11090
Valorar el contenido:
Últimas consultas realizadas en los foros
Últimas preguntas sin contestar en los foros de devjoker.com