Thursday, October 2, 2008

Manejo dinámico de imágenes

En todos los proyectos web hay que lidiar con imágenes, iconos, thumbnails, etc. y siempre es tedioso y poco productivo tener que armar versiones de la misma imagen para mostrarlas en diferentes tamaños, agregarles marcas de agua o transformarlas a escala de grises.

En este post voy mostrare una implementación de un System.Web.IHttpHandler que realiza modificaciones “on the fly” de imágenes.

Este Handler funciona recibiendo parametros en la url y estos parámetros le indican el archivo a procesar y que transformaciones deben ejecutarse. Las transformaciones implementadas son las siguientes:

  • Convertir a escala de grises.
  • Resizado.
  • Reemplazo de un color por otro.
  • Agregado de una marca de agua.

Antes de ver el codigo vamos a ver la referencia de estos parametros de url:

  • url: (obligatorio) System.Uri con la url del archivo, este url puede ser relativa o absoluta. Si se especifica absoluta la obtención del archivo se realizara via un request http.
  • outputformat: (opcional) mediante este parámetro se puede especificar cual va a ser el formato de salida de la imagen. Las opciones posibles son jpg, png y gif. Si se omite este parámetro la opción por defecto es png.
  • bw: (opcional) convierte la imagen a escala de grises. Las opciones válidas son true y false. Por defecto es false.
  • replacecolor: (opcional) reemplaza un color por otro. El formato del valor de este parámetro es colororigen_colordestino, los colores expresados en rgb. Ej: replacecolor=30,30,30_70,70,70.
  • resize: (opcional) genera una imagen al tamaño especificado. El formato del valor de este parámetro es ancho_alto. Ej: resize=200_100 para 200px de ancho por 100px de alto.
  • watermark: (opcional) permite agregar una marca de agua a la imagen especificada en el parámetro url. Los valores posibles de este parámetro son true y false, y cuando es true necesita de los siguientes parámetros:
      • watermarkurl: (obligatorio) url relativa o absoluta del archivo que actuará como marca de agua.
      • watermarkopacity: (opcional) define la opacidad de la marca de agua. Los valores posibles de este parámetro es cualquier entero entre 0 y 100. El valor por defecto es 50.
      • watermarkposition: (opcional) especifica la posición de la marca de agua en relacion a la imagen principal. Los valores posibles son TopLeft, TopRight, BottomRight, BottomLeft, Center. El valor por defecto es Center.
      • watermarksize: (opcional) especifica el tamaño de la marca de agua. El formato es el mismo que en el parámetro resize y si no se especifica se utiliza el tamaño original de la marca de agua.

Ahora sí, veamos un poco de codigo.

Como mencioné anteriormente, esta implementación esta hecha sobre un IHttpHandler. Aqui esta la declaración:

public class Handler : IHttpHandler

La interfaz IHttpHandler define la propiedad IsReusable y el método ProcessRequest que es el que realiza el proceso de los requests.

El metodo ProcessRequest empieza por validar el parámetro url que es obligatorio, sino lo encuentra o el no se puede obtener una imagen de esa url devuelve un error HTTP 400 Bad Request.

// declaro variables
Uri url = null;
// obtener parametro url
if (string.IsNullOrEmpty(context.Request.QueryString["url"]))
{
// request invalido.
context.Response.StatusCode = 400; //400 = bad request
context.Response.StatusDescription = "Request invalido. Falta parametro 'url'";
}
else
{
// parsear url
url = new Uri(context.Request.QueryString["url"], UriKind.RelativeOrAbsolute);
}

Para no procesar una y otra vez la misma imagen con los mismos parámetros, utilicé el sistema de cache de ASP.NET para almacenar el output del proceso de transformación, con lo cual al iniciar cada request hay que consultar si ya no se proceso un request igual:

string cacheKey = context.Request.QueryString.ToString().ToLowerInvariant();

byte[] cacheValue = (byte[])context.Cache.Get(cacheKey);
if (cacheValue != null)
{
// la image ya ha sido procesada anteriormente, escribir al buffer.
WriteToBuffer(context, cacheValue, url);

// sale
return;
}

Si no se ha procesado un request de iguales características, se prosigue con el proceso. El codigo de las transformaciones es bastante claro y directo, pero quiero resaltar el uso del método DrawImage de la clase System.Drawing.Graphics en vez del método System.Drawing.Image.GetThumbnailImage porque este último produce imagenes de poca calidad si el tamaño supera los 120 píxeles.