Descargar ejemplo JPG_Compresor.zip
La iniciativa surgió en base a la necesidad de agregar imágenes de determinados artículos a una base de datos. Asimismo, estas imágenes se deberían poder visualizar en una aplicación. Las imágenes estaban adquiridas mediante una cámara de fotos digital en un tamaño original de 2048 x 1536 Pixels (3.15 MPixels), arrojando valores de almacenamiento en disco de aproximadamente 1.5 Mb, cada una.
Investigando el Espacio de Nombres System.Drawing.Imaging encontré que existe una variada cantidad de Clases que brindan funcionalidad avanzada GDI+ (Graphics Device Interface / Interfase de Dispositivo Gráfico). La solución que comparto con Uds. es sencilla y generé una pequeña aplicación (fácilmente ampliable) que muestra como implementar métodos de compresión de datos (en este caso, imágenes JPG).
El resultado fue más que satisfactorio: por un lado se redujo la visualización «física» de las imágenes a los requerimientos de la aplicación (un espacio de 200 x 150 pixels). Por el otro, en muchos casos, se redujo el peso de las imágenes a menos del 1 % (uno porciento) de su peso original, ajustando el porcentaje de compresión al 70%, sin perder calidad visual apreciable.
Comencemos viendo la sentencia de llamada:
Los parámetros que recibe esta sentencia son:
- El nombre de un Bitmap que contiene la imagen a comprimir (DestTmpImage), ya ajustado según un factor de escala
- Una ubicación de almacenamiento en disco junto con un nombre de archivo
- Un valor numérico que indica el porcentaje de compresión
La subrutina que recibe los datos es la siguiente:
Public Shared Sub SaveJPGWithCompressionSetting(ByVal image As Image, ByVal szFileName As String, ByVal lCompression As Long) Dim eps As EncoderParameters = New EncoderParameters(1) eps.Param(0) = New EncoderParameter(Encoder.Quality, lCompression) Dim ici As ImageCodecInfo = GetEncoderInfo("image/jpeg") Try image.Save(szFileName, ici, eps) Catch exc As Exception MessageBox.Show(exc, " Atención !", MessageBoxButtons.OK, MessageBoxIcon.Error) End Try End Sub
Se comienza dimensionando un objeto de tipo EncoderParameters, llamado «eps», que encapsulará una matriz de objetos tipo EncoderParameter. Los objetos EncoderParameter se utilizan para pasar una matriz de valores a un codificador de imágenes. También se pueden utilizar para recibir una lista de posibles valores que estén soportados por una parámetro particular de un codificador de imágenes particular. En este caso inicializamos un nueva instancia de la clase EncoderParameters para que contenga 1 (un) objeto EncoderParameter, mediante la llamada al constructor adecuado.
A continuación se asigna a la propiedad Param, del objeto declarado anteriormente, una nueva instancia de un objeto EncoderParameter, especificando un objeto Encoder y un valor de tipo Long (32-bit). El objeto tipo Encoder encapsula un identificador global único (GUID) que identifica la categoría de un parámetro de codificación de una imagen. En este caso, la categoría refiere al campo Quality (Calidad). El valor de tipo Long refiere al nivel de compresión deseado (lCompression).
Paso siguiente se dimensiona un objeto de tipo ImageCodecInfo, llamado «ici». Esta Clase proporciona los miembros y métodos de almacenamiento necesarios para recuperar toda información pertinente acerca de los codecs de imagen que estén instalados. Recordemos que codec hace referencia al término Coder-decoder, es decir, un filtro que comprime o descomprime un flujo de datos. En este momento se llama a la función GetEncoderInfo, pasando como parámetro una cadena que contiene el tipo mimeType asignado como image/jpeg. (MIME = Multipurpose Internet Mail Extensions)
Veamos como se compone la función:
Public Shared Function GetEncoderInfo(ByVal mimeType As String) As ImageCodecInfo Dim j As Integer Dim encoders As ImageCodecInfo() encoders = ImageCodecInfo.GetImageEncoders() For j = 0 To encoders.Length If encoders(j).MimeType = mimeType Then Return encoders(j) End If Next j Return Nothing End Function
Dimensionamos una variable de tipo entero llamada «j» para iterar en el loop que figura más abajo.
Dimensionamos una variable de tipo ImageCodecInfo llamada «encoders». Asignamos a esta variable el resultado de llamar al método GetImageEncoders que en este caso tendrá una longitud de 5 ya que refiere a los codecs de imagen que están instalados por defecto. Si revisamos los elementos de esta matriz se puede constatar que los codecs de imagen son:
CodecName | FilenameExtension | FormatDescription | MimeType |
Built-in BMP Codec | .BMP;*.DIB;*.RLE | BMP | image/bmp |
Built-in JPEG Codec | .JPG;*.JPEG;*.JPE;*.JFIF | JPEG | image/jpeg |
Built-in GIF Codec | *.GIF | GIF | image/gif |
Built-in TIFF Codec | *.TIF;*.TIFF | TIFF | image/tiff |
Built-in PNG Codec | *.PNG | PNG | image/png |
Iteramos en el loop buscando un encoder que sea similar al tipo de imagen que se está procesando (en este caso image/jpeg) y obtenemos la inforción referente a ese encoder específico, retornando a la subrutina anterior.
A fines de monitorear el resultado del paso que se ejecutará acontinuación (almacenar la imagen comprimida en disco) y manejar en forma robusta el manejo de excepciones en caso que se produzca un error, utilizaremos una sentencia Try…Catch. En el bloque Try llamaremos el método Save, pasando 3 parámetros: el nombre del archivo («szFileName»), la información del encoder («ici») y los parámetros del encoder («eps»). Si se produce algún error lo podremos manejar a través de un MessageBox para alertar al usuario, que mostrará una descripción de la excepción ocurrida.
Acerca de la aplicación de ejemplo
- Un botón llamado «Cargar Imagen» que permitirá, mediante un Caja de Diálogo, seleccionar y cargar la imagen de tipo JPG a comprimir, que aparecerá en el cuadro llamado «Origen»
- Un objeto tipo TrackBar para poder setear el Porcentaje de Compresión
- Un botón llamado «Obtener Resultado» que dimensiona, genera, y muestra la imagen comprimida en el cuadro llamado»Resultado»
- Un botón llamado «Guardar Imagen» que permitirá, mediante una Caja de Diálogo, almacenar el archivo comprimido resultante
Debajo de la imagen original y de la imagen resultante aparecen sus dimensiones reales expresadas en pixels (Ancho-Alto), como así también el espacio en bytes que ocupa y ocupará en disco luego de la compresión. Es decir, con estos datos, se puede experimentar la relación calidad visual / peso en bytes, para así poder seleccionar lo que nos parezca el mejor resultado previo al momento de almacenar.
El cuadro «Ajustes», que no está habilitado, se muestra como posibilidad de ampliación de la aplicación propuesta a fines de dimensionar la imagen resultante a otros valores que no sean los sugeridos en este ejemplo (200 px de ancho por 150 px de alto).
Conclusión
Esta Nota expone algunos aspectos del Espacio de Nombres System.Drawing.Imaging y detalla aspectos de algunas de sus Clases que trabajan sobre GDI+. Describe como poder comprimir imágenes en formato JPG, pero resulta sencillo ahora extender el manejo a otros tipos de formatos, como GIF o PNG. Inclusive se exponen las herramientas básicas para poder convertir entre formatos. La aplicación ejemplo muestra como trabajar con estas herramientas de forma clara e intuitiva. Espero que les sea de utilidad.
Una pregunta, has probado esta rutina de form masiva, es decir, pones 100 fotos en una carpeta y generas, en otra carpeta el resultado de estas 100 fotos, pero ya ajustado el peso de las mismas. Te lo comento porque yo tengo realice una aplciacion como la que te describi, y el problema que tengo es que el equipo donde corre, se queda sin memoria y el programa se detine ya que se queda corto de memoria. Te ha pasado, saludos.
Claro no debe ser nada complicado, solo le debes de indicar donde copia el resultado. Modificas el FileName y ahí le puedes indicar donde copiar.