miércoles, 13 de julio de 2011
Java Tips
Hola a todos, buscando información sobre como inciarse en el desarrollo de aplicaciones web en Java (JSP y Servlets), me encontré esta página que tiene bastante contenido para los que quieran incursionar a fondo en la plataforma Java, el sitio es llamado Java Tips. En este sitio encontraremos abundantes tutoriales que nos llevarán paso a paso para conocer cada una de las diferentes tecnologías de programación que esta plataforma ofrece (como JSP y los Servlets, tecnicas para el lenguaje Java, manipulación de imágenes, Java EE, Swing, Applets y diferentes utilerias, entre otros).
Más adelante, espero poder desarrollar algunas entradas en este blog, con las aplicaciones de estos tutoriales que haya encotrado más interesantes.
Hasta pronto.
martes, 21 de junio de 2011
Tutorial de PHP de Zend
Etiquetas:
Introducción a PHP,
PHP,
tutorial,
zend
Hola, después de mucho tiempo sin publicar, ahora traigo una entrada algo corta pero sustancial. Se trata de uno de los tutoriales del lenguaje PHP más completos que he encontrado, desarrollado por la propia compañía Zend, y que nos lleva de la mano dando los primeros pasos hasta el desarrollo de aplicaciones avanzadas. Sin más preámbulos, aquí está:
PHP 101: PHP For the Absolute Beginner
This area is intended for everyone new to PHP. It opens with a series of informal, entertaining tutorials written by Vikram Vaswani, founder and CEO of Melonfire. These tutorials build on a previously-published 5-part series which has now been updated and extended to embrace PHP 5, making parts of it suitable for those of you who already have worked with PHP 4 in the past.
If you came here to learn about elementary PHP 4 or basic PHP 5, this is for you. Enjoy!
El tutorial se divide en 15 partes, cada una con su respectiva introducción teórica, acompañadas de interesantes ejercicios para poner el práctica lo aprendido.
Saludos.
Saludos.
lunes, 14 de junio de 2010
Como devolver arreglos y estructuras de datos en SOAP transparentemente, utilizando PHP y NuSOAP
Etiquetas:
arreglos asociativos,
NuSOAP,
PHP,
Programación web,
Servicio Web,
SOAP,
Web Service,
XML
¡Saludos nuevamente blogueros!
En este artículo veremos como resolver una duda que apareció hace un tiempo, acerca del envío de datos por servicios web utilizando la biblioteca NuSOAP de PHP. En un artículo pasado, hablamos de como implementar un servicio web desde PHP utilizando esta biblioteca; este servicio contaba con dos métodos, uno de ellos devolvía una cadena y el otro un arreglo asociativo de PHP, se pudo consumir el servicio desde un cliente AJAX sin problema procesando el código XML directamente, sin embargo, al trabajar de esta manera se pierde uno de los puntos importantes para utilizar protocolos de comunicación entre aplicaciones: la transparencia. Es decir, al consumir el servicio el cliente no pudo extraer la estructura de datos recibida, sin necesidad de darle un tratamiento al XML de la respuesta y teniendo que saber de antemano cuál era la estructura XML que recibiría.
Ahora, la idea es corregir este problema de transparencia, al mismo tiempo que mostramos como mandar diferentes tipos de datos complejos, como son: estructuras, arreglos y arreglos de estructuras.
Manos a la obra
Para poder llegar a este nivel de transparencia entre aplicaciones es que el protocolo SOAP utiliza el WSDL, el cual además de contener la definición de cada uno de los métodos que posee un servicio web, puede tener también la definición de tipos de datos complejos. Un tipo de dato complejo consiste una colección de valores agrupados dentro de una estructura, y dentro de este tipo de datos es que encontramos los objetos y arreglos.
Podemos definir estos datos complejos utilizando la clase soap_server de la biblioteca NuSOAP. Como recordarán -y si no es así lo menciono- esta clase nos permite crear servicios web en nuestro servidor para que lo consuman los clientes. Dentro de esta clase se encuentra un objeto llamado wsdl, definido como una propiedad de la misma clase, el cual posee una serie de métodos para definir las características del WSDL del servicio web. El método que emplearemos concretamente para definir los nuevos tipos de datos será el addComplexType. El primer paso consistirá en instanciar la clase soap_server de la siguiente forma:
Una vez hecho esto contamos con un objeto de esta clase, para el cual podemos definir sus características:
Ahora viene lo bueno, definir tipos de datos complejos para que sean devueltos por los métodos de nuestro servicio. Para este ejemplo definiremos 3 estructuras diferentes para probar las posibilidades que nos ofrece esta clase:
Como vemos, se incluye una referencia(http://schemas.xmlsoap.org/soap/encoding/:Array) a la definición del formato que debe tener un dato de tipo complejo Array.
Finalmente -y lo más interesante-, un arreglo de estructuras de datos:
Digo que el último ejemplo me parece el más interesante porque con este podemos definir datos de entrada y de salida para los métodos que consistirán en listas de estructuras de datos, similares a las que encontramos en muchos lenguajes de programación de alto nivel y sobre todo muy útiles para representar registros de bases de datos y archivos estructurados.
Para incluir estos datos de entrada y de salida de los métodos, basta con declararlo en el método registrer donde se declaran los métodos, y asegurarnos por supuesto de que la estructura de los parámetros de estos métodos que hagan referencia los tipos de datos complejos, corresponda con la estructura declarada, es decir:
Si por ejemplo creamos un método que devuelva el tipo de dato complejo ArregloDeEstructuras, en el código de nuestro método deberemos incluir una estructura de datos equivalente, en este caso un arreglo asociativo, donde cada llave del arreglo corresponde al identificador de los datos de nuestra estructura previamente definida -llamada Estructura en este ejemplo-; así en el método, cada entrada del arreglo contendrá a la vez un arreglo asociativo, que corresponderá a esta estructura de datos.
De esta forma es posible crear servicios web que devuelvan a nuestras aplicaciones arreglos de estructuras de datos, e incluso arreglos de arreglos, lo cual ayudará a que nuestros servicios puedan generar mayor variedad de datos de salida, y de esta forma aumentaremos el poder las capacidades nuestras aplicaciones. Además de esto, se logra mucha mayor transparencia, ya que cualquier aplicación de terceros o que nosotros mismos desarrollemos, solo tendrá que seguir el formato definido en el WSDL de nuestros servicios web, para poder desentrañar estos tipos de datos complejos. Algunos entornos de desarrollo -como el de .Net- incluso permiten implementar el consumo de estos servicios web interpretando los tipos de datos complejos como objetos nativos del lenguaje, lo que nos permite manipularlos con mucha mayor facilidad, así que las posibilidades son muchas.
Hasta la próxima.
Ahora, la idea es corregir este problema de transparencia, al mismo tiempo que mostramos como mandar diferentes tipos de datos complejos, como son: estructuras, arreglos y arreglos de estructuras.
Manos a la obra
Para poder llegar a este nivel de transparencia entre aplicaciones es que el protocolo SOAP utiliza el WSDL, el cual además de contener la definición de cada uno de los métodos que posee un servicio web, puede tener también la definición de tipos de datos complejos. Un tipo de dato complejo consiste una colección de valores agrupados dentro de una estructura, y dentro de este tipo de datos es que encontramos los objetos y arreglos.
Podemos definir estos datos complejos utilizando la clase soap_server de la biblioteca NuSOAP. Como recordarán -y si no es así lo menciono- esta clase nos permite crear servicios web en nuestro servidor para que lo consuman los clientes. Dentro de esta clase se encuentra un objeto llamado wsdl, definido como una propiedad de la misma clase, el cual posee una serie de métodos para definir las características del WSDL del servicio web. El método que emplearemos concretamente para definir los nuevos tipos de datos será el addComplexType. El primer paso consistirá en instanciar la clase soap_server de la siguiente forma:
require_once('nusoap.php');
$servidor = new soap_server;
Una vez hecho esto contamos con un objeto de esta clase, para el cual podemos definir sus características:
$ns = "http://nuestroespaciodenombres";
$servidor->configureWSDL('NombreDelServicio',$ns);
$servidor->wsdl->schematargetnamespace = $ns;
Ahora viene lo bueno, definir tipos de datos complejos para que sean devueltos por los métodos de nuestro servicio. Para este ejemplo definiremos 3 estructuras diferentes para probar las posibilidades que nos ofrece esta clase:
- Primero un arreglo de cadenas sencillo:
$servidor->wsdl->addComplexType(
'ArregloDeCadenas',
'complexType',
'array',
'sequence',
'http://schemas.xmlsoap.org/soap/encoding/:Array',
array(),
array(array('ref' => 'http://schemas.xmlsoap.org/soap/encoding/:arrayType',
'wsdl:arrayType' => 'xsd:string[]')
),
'xsd:string'
);
Como vemos, se incluye una referencia(http://schemas.xmlsoap.org/soap/encoding/:Array) a la definición del formato que debe tener un dato de tipo complejo Array.
- Después una estructura de datos, similar a los arreglos asociativos, que contendrá una serie de valores distinguidos por un nombre:
$servidor->wsdl->addComplexType(
'Estructura',
'complexType',
'struct',
'all',
'',
array(
'Nombre' => array('name' => 'Nombre', 'type' => 'xsd:string'),
'Apellidos'=>array('name' => 'Apellidos', 'type' => 'xsd:string'),
'Edad'=>array('name' => 'Edad', 'type' => 'xsd:integer')
)
);
$servidor->wsdl->addComplexType(
'ArregloDeEstructuras',
'complexType',
'array',
'sequence',
'http://schemas.xmlsoap.org/soap/encoding/:Array',
array(),
array(array('ref' => 'http://schemas.xmlsoap.org/soap/encoding/:arrayType',
'wsdl:arrayType' => 'tns:Estructura[]')
),
'tns:Estructura'
);
Digo que el último ejemplo me parece el más interesante porque con este podemos definir datos de entrada y de salida para los métodos que consistirán en listas de estructuras de datos, similares a las que encontramos en muchos lenguajes de programación de alto nivel y sobre todo muy útiles para representar registros de bases de datos y archivos estructurados.
Para incluir estos datos de entrada y de salida de los métodos, basta con declararlo en el método registrer donde se declaran los métodos, y asegurarnos por supuesto de que la estructura de los parámetros de estos métodos que hagan referencia los tipos de datos complejos, corresponda con la estructura declarada, es decir:
function consultaPersonas($param) {
$arreglo = array();
$arreglo[] = array('Nombre'=>"Juan", 'Apellidos'=>"Torres", 'Edad'=>18);
$arreglo[] = array('Nombre'=>"Teresa", 'Apellidos'=>"Jiménez Sánchez", 'Edad'=>19);
$arreglo[] = array('Nombre'=>"Efraín", 'Apellidos'=>"Ovalles López", 'Edad'=>22);
return $arreglo;
}
$servidor->register('consultaPersonas',
array('param'=>'xsd:string'),
array('return'=>'tns:ArregloDeEstructuras'),
$ns
);
Si por ejemplo creamos un método que devuelva el tipo de dato complejo ArregloDeEstructuras, en el código de nuestro método deberemos incluir una estructura de datos equivalente, en este caso un arreglo asociativo, donde cada llave del arreglo corresponde al identificador de los datos de nuestra estructura previamente definida -llamada Estructura en este ejemplo-; así en el método, cada entrada del arreglo contendrá a la vez un arreglo asociativo, que corresponderá a esta estructura de datos.
De esta forma es posible crear servicios web que devuelvan a nuestras aplicaciones arreglos de estructuras de datos, e incluso arreglos de arreglos, lo cual ayudará a que nuestros servicios puedan generar mayor variedad de datos de salida, y de esta forma aumentaremos el poder las capacidades nuestras aplicaciones. Además de esto, se logra mucha mayor transparencia, ya que cualquier aplicación de terceros o que nosotros mismos desarrollemos, solo tendrá que seguir el formato definido en el WSDL de nuestros servicios web, para poder desentrañar estos tipos de datos complejos. Algunos entornos de desarrollo -como el de .Net- incluso permiten implementar el consumo de estos servicios web interpretando los tipos de datos complejos como objetos nativos del lenguaje, lo que nos permite manipularlos con mucha mayor facilidad, así que las posibilidades son muchas.
Hasta la próxima.
viernes, 11 de junio de 2010
Devolver objetos en JSON desde PHP
Etiquetas:
ajax,
javascript,
json,
json_encoded,
PHP,
PHP5,
Programación web
¡Saludos blogueros!
Ese pequeño artículo va para los que se preguntaban en el ejemplo anterior, ¿qué era ese formato llamado JSON que utiliza flickr para enviarnos la información? Se trata más que nada de una forma para intercambiar datos estructurados entre diferentes sistemas sobre protocolos como HTTP. JSON, define cómo se deberán codificar los objetos de un determinado lenguaje para convertirlos en cadenas de texto, con el propósito de enviarlos hacia otras aplicaciones, y que estas puedan reconstruirlos como objetos nativos, utilizando la decodificación definida por el mismo formato JSON.
Por ejemplo, digamos que mi aplicación desea mandar un objeto que contiene determinada información hacia un cliente Javascript, además de las ya conocidas técnicas basadas en XML o protocolos como SOAP, tenemos la posibilidad de mandarlo codificado en JSON, de modo que desde nuestra aplicación convertiremos ese objeto en una cadena de texto que lo representará, y lo enviaremos a nuestro cliente Javascript como una respuesta en texto simple, el cliente -con previo conocimiento del formato de la respuesta-, solo tendrá que aplicar las reglas de la notación para -partir de la cadena- reconstruir el objeto y utilizarlo dentro de su propia ejecución de instrucciones. Tal y como hicimos en el ejemplo anterior con los servicios de flickr.
La gran ventaja de JSON, es que el cliente Javascript puede utilizar la instrucción eval, para reconstruir el objeto partir de la cadena recibida, sin necesidad de ningún interprete especia, tal y como explica el artículo de la Wikipedia sobre JSON. Del lado de nuestra aplicación en el servidor por otro lado sí se necesitará de una funcionalidad que haga el trabajo de codificar los objetos u arreglos en la notación de JSON, pero para eso la gran mayoría de los lenguajes cuentan ya con bibliotecas de funciones o clases para hacer la tarea.
En este artículo veremos cómo hacer esa tarea del lado del servidor, utilizando la biblioteca de funciones para JSON de PHP, disponible a partir de la versión 5.2.0. Creando la siguiente aplicación web en este lenguaje:
respuestajson.php
El programa en PHP codificara en notación JSON el objeto $articulo, dando como resultado una cadena que devolverá al cliente, la cual podemos visualizar si accedemos a la aplicación desde el navegador (http://localhost/respuestajson.php):
Ahora, el cliente AJAX para procesar estos datos sería el siguiente:
clientejson.html
Este código implementa una sencilla aplicación AJAX, similar a las vistas en los primero ejemplos sobre esta tecnología vistos en este blog. Básicamente accede a la aplicación en PHP que creamos previamente, y procesa el texto plano que recibe como respuesta, almacenándolo en la cadena, debido a que este texto consiste en la codificación en JSON del objeto original, lo decodifica utilizando la instrucción eval("(" + cadCodificadaJSON + ")"), creando el objeto objDatos como resultado.
El resultado obtenido se vería en el navegador como sigue:
De esta forma, podemos transmitir conjuntos de datos estructurados sin recurrir a formatos como XML o protocolos como SOAP, que requieren un mayor trabajo de procesamiento de la información en el cliente, pero que en contra parte, exige que el servidor pueda llevar a cabo la codificación de objetos en JSON. La elección final de la forma en que vayamos a transmitir los datos sobre HTTP, dependerá de los requerimientos que nos exijan la arquitectura y la plataforma que habremos de utilizar.
Saludos.
Ese pequeño artículo va para los que se preguntaban en el ejemplo anterior, ¿qué era ese formato llamado JSON que utiliza flickr para enviarnos la información? Se trata más que nada de una forma para intercambiar datos estructurados entre diferentes sistemas sobre protocolos como HTTP. JSON, define cómo se deberán codificar los objetos de un determinado lenguaje para convertirlos en cadenas de texto, con el propósito de enviarlos hacia otras aplicaciones, y que estas puedan reconstruirlos como objetos nativos, utilizando la decodificación definida por el mismo formato JSON.
Por ejemplo, digamos que mi aplicación desea mandar un objeto que contiene determinada información hacia un cliente Javascript, además de las ya conocidas técnicas basadas en XML o protocolos como SOAP, tenemos la posibilidad de mandarlo codificado en JSON, de modo que desde nuestra aplicación convertiremos ese objeto en una cadena de texto que lo representará, y lo enviaremos a nuestro cliente Javascript como una respuesta en texto simple, el cliente -con previo conocimiento del formato de la respuesta-, solo tendrá que aplicar las reglas de la notación para -partir de la cadena- reconstruir el objeto y utilizarlo dentro de su propia ejecución de instrucciones. Tal y como hicimos en el ejemplo anterior con los servicios de flickr.
La gran ventaja de JSON, es que el cliente Javascript puede utilizar la instrucción eval, para reconstruir el objeto partir de la cadena recibida, sin necesidad de ningún interprete especia, tal y como explica el artículo de la Wikipedia sobre JSON. Del lado de nuestra aplicación en el servidor por otro lado sí se necesitará de una funcionalidad que haga el trabajo de codificar los objetos u arreglos en la notación de JSON, pero para eso la gran mayoría de los lenguajes cuentan ya con bibliotecas de funciones o clases para hacer la tarea.
En este artículo veremos cómo hacer esa tarea del lado del servidor, utilizando la biblioteca de funciones para JSON de PHP, disponible a partir de la versión 5.2.0. Creando la siguiente aplicación web en este lenguaje:
respuestajson.php
<?php
//Clase para representar una serie de diferentes artículos para tiendas
class Articulo {
public $id;
public $nombre;
public $categoria;
public $precioDeSalida;
public $detalles;
}
$articulo = new Articulo();
$articulo->id = 1;
$articulo->nombre = "Nintendo DS";
$articulo->categoria = "Videojuego";
$articulo->precioDeSalida = "$3 500";
$articulo->detalles = array('Soporta juegos para Gameboy advance','Pantalla tactil','Reproducción de MP3');
echo json_encode($articulo);?>
El programa en PHP codificara en notación JSON el objeto $articulo, dando como resultado una cadena que devolverá al cliente, la cual podemos visualizar si accedemos a la aplicación desde el navegador (http://localhost/respuestajson.php):
{"id":1,"nombre":"Nintendo DS","categoria":"Videojuego","precioDeSalida":"$3 500","detalles":["Soporta juegos para Gameboy advance","Pantalla tactil","Reproducci\u00f3n de MP3"]}
Ahora, el cliente AJAX para procesar estos datos sería el siguiente:
clientejson.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Decodifica un objeto en JSON</title>
<script type="application/javascript">
var http_request = new XMLHttpRequest();
var url = "http://localhost/respuestajson.php"; // Esta URL debería devolver datos JSON
// Descarga los datos JSON del servidor.
http_request.onreadystatechange = manejaJSON;
http_request.open("GET", url, true);
http_request.send(null);
function manejaJSON() {
if (http_request.readyState == 4) {
if (http_request.status == 200) {
var cadCodificadaJSON = http_request.responseText;
var objDatos = eval("(" + cadCodificadaJSON + ")"); //Creamos el objeto utilizando la cadena codificada
//Ahora con el objeto desplegamos los datos mandados desde el servidor
document.getElementById("divId").innerHTML = objDatos.id;
document.getElementById("divNombre").innerHTML = objDatos.nombre;
document.getElementById("divCategoria").innerHTML = objDatos.categoria;
document.getElementById("divPrecio").innerHTML = objDatos.precioDeSalida;
document.getElementById("divDetalles").innerHTML = objDatos.detalles[0] + ", " + objDatos.detalles[1] + ", " + objDatos.detalles[2];
} else {
alert("Ocurrio un problema con la URL.");
}
http_request = null;
}
}
</script>
</head>
<body>
<h2>Producto</h2>
<div id="divId"></div>
<div id="divNombre"></div>
<div id="divCategoria"></div>
<div id="divPrecio"></div>
<div id="divDetalles"></div>
</body>
</html>
Este código implementa una sencilla aplicación AJAX, similar a las vistas en los primero ejemplos sobre esta tecnología vistos en este blog. Básicamente accede a la aplicación en PHP que creamos previamente, y procesa el texto plano que recibe como respuesta, almacenándolo en la cadena, debido a que este texto consiste en la codificación en JSON del objeto original, lo decodifica utilizando la instrucción eval("(" + cadCodificadaJSON + ")"), creando el objeto objDatos como resultado.
El resultado obtenido se vería en el navegador como sigue:
Producto
1
Nintendo DS
Videojuego
$3 500
Soporta juegos para Gameboy advance, Pantalla tactil, Reproducción de MP3
De esta forma, podemos transmitir conjuntos de datos estructurados sin recurrir a formatos como XML o protocolos como SOAP, que requieren un mayor trabajo de procesamiento de la información en el cliente, pero que en contra parte, exige que el servidor pueda llevar a cabo la codificación de objetos en JSON. La elección final de la forma en que vayamos a transmitir los datos sobre HTTP, dependerá de los requerimientos que nos exijan la arquitectura y la plataforma que habremos de utilizar.
Saludos.
miércoles, 28 de abril de 2010
Una forma rápida de acceder a una galería de Flickr utilizando jQuery
Etiquetas:
ajax,
cycle,
flickr,
galería de imágenes,
javascript,
jquery
¡Saludos de nuevo blogueros!
Siguiendo con las pruebas de la biblioteca jQuery, ahora me puse a revisar su potencial explotando algún servicio de imágenes de Internet, entre los que encontré los servicios para acceder a las galerías de imágenes de Flickr. Este manejador de imágenes posee varias formas para que nuestras aplicaciones puedan descargarse una lista con la información de las imágenes que tienen sus álbumes, ya sea a consumiendo alguna de sus APIs para la búsqueda de fotografías o utilizando su servicio de suscripción al feed RSS de un usuario.
Si consumimos alguna de las APIs de Flickr, tenemos la posibilidad de buscar sobre toda la base de datos del sistema, cualquier fotografía que coincida con las palabras clave o el identificador del usuario que estemos buscando. Esta forma de utlizar jQuery se explora en este artículo de CocinaMental, donde se utiliza la API flickr.photos.search.
Por otro lado, a través de un feed RSS, podemos acceder a la información de las últimas fotografías que han sido agregadas a la galería de un usuario específico.
El método que exploraremos en este artículo consistirá en consumir el feed RSS de una galería de Flickr desde nuestra aplicación AJAX, dejando a los usuarios que deseen saber como explotar las APIs de Flickr la referencia a al artículo en CocinaMental, donde se expone bastante claro como lograrlo. Como no es mi intención crear una artículo redundante me decante por este método sobre el de las APIs, además de que es sumamente rápido ponerlo a trabajar.
Antes de empezar
Adicionalmente a la biblioteca jQuery, en el programa utilizaremos un plug in llamado cycle, el cual nos permitirá agregar el efecto de cambio automático de imágenes con desvanecimiento a la galería que mostraremos.
Ambas bibliotecas las incluiremos en nuestro código con referencias a sus archivos fuente, pero en esta ocasión no será necesario descargar ningún archivo, ya que referenciaremos directamente al último archivo fuente, que se encuentra en los servidores de cada uno de los proveedores de sus códigos fuente.
Primero elegimos las imágenes
El primer paso consiste en darnos un paseo por Flickr y encontrar alguna galería que nos interese utilizar para la prueba, toda galería posee un autor, es decir el usuario que subió las imágenes, así que la galería que elijamos está directamente vinculada con este usuario. Por ejemplo en una consulta a una imagen de Flickr acabé accediendo a la galería del usuario Plateada, que podemos ver en la imagen siguiente:

Dentro de la información que aparece, lo que nos interesa para nuestra aplicación está al final de la página, en esta parte podemos ver el enlace al feed RSS de la galería, llamado Latest:

Pero no haremos clic sobre el enlace, sino que nos fijaremos en la URL que aparece en éste, ya que dentro de esta URL está la información que utilizaremos para acceder a la galería. Podemos copiar esta dirección URL para verla con calma y obtendremos un texto como el siguiente:
http://api.flickr.com/services/feeds/photos_public.gne?id=59492828@N00&lang=en-us&format=rss_200El campo id corresponde al identificador del usuario autor de la galería Plateada, y este es precisamente el dato que utilizaremos en nuestra aplicación de ejemplo.
Vamos al código
El código para la aplicación Web que implementará esta galería es el siguiente:
galeriaJQuery.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Galería de imágenes de Flickr con jQuery</title>
<style type="text/css">
.slideshow { margin: 20px auto; padding: 0; }
.slide { margin: 0; padding: 0}
.slide img { padding: 15px; border: 1px solid #ccc; background-color: #eee; margin: auto; display: block }
</style>
<script src="http://code.jquery.com/jquery-latest.js" type="text/javascript"></script>
<script src="http://cloud.github.com/downloads/malsup/cycle/jquery.cycle.all.latest.min.js" type="text/javascript"></script>
<script type="text/javascript">
$.getJSON("http://api.flickr.com/services/feeds/photos_public.gne?id=59492828@N00&lang=en-us&format=json&jsoncallback=?",
function(data){
$.each(data.items, function(i,item){
$("<img/>").attr("src", item.media.m).appendTo("#imagenes").wrap("<div class=\"slide\"><h3>"+ item.title +"</h3><a href='" + item.link + "'></a></div>");
});
$('#imagenes').cycle({
fx: 'fade',
timeout: 5000
});
});
</script>
</head>
<body>
<div id="imagenes" class="slideshow"></div>
</body>
</html>
Como vemos, se referencia a los archivos fuente de las blibliotecas jQuery y cycle desde su ubicación dentro de Internet en sus servidores Web.
Además se incluye un pequeño syle CSS para darle una apariencia más agradable a las imágenes, incluyendo en cada una un pequeño marco gris.
En lo que corresponde a la funcionalidad, el acceso a la lista de imágenes se lleva a cabo por mediante la funcionalidad jQuery.getJSON, que lleva a cabo una petición de datos en formato JSON al servidor de Flickr, utilizando una solicitud HTTP-GET (es decir, con los datos incluidos un la dirección URL del recurso al que estamos accediendo).
$.getJSON("http://api.flickr.com/services/feeds/photos_public.gne?id=59492828@N00〈=en-us&format=json&jsoncallback=?",Dentro de la URL que utiliza la función es donde se incluirá el id del usuario que obtuvimos previamente, en este caso 59492828@N00.
Recibir la información de respuesta en formato JSON facilita bastante el trabajo, ya que es interpretada por javascript como una estructura nativa del lenguaje, y gracias a esto podemos utilizarla directamente dentro de la aplicación. Como sucede en la función que se define en seguida:
function(data){
$.each(data.items, function(i,item){
$("<img/>").attr("src", item.media.m).appendTo("#imagenes").wrap("<div class=\"slide\"><h3>"+ item.title +"</h3><a href='" + item.link + "'></a></div>");
});
$('#imagenes').cycle({
fx: 'fade',
timeout: 5000
});
});En donde, utilizando la funcionalidad de jQuery each, podemos rellenar el elemento div "imagenes" con la información de cada una de las imágenes que recibimos como respuesta a la petición, incluyendo la ruta URL al archivo de la imagen, con lo que utilizando etiquetas HTML podemos visualizar todas las imagenes un nuestra aplicación.
Finalmente, utilizando el plug in cycle, es que podemos agregar la función para cambiar automáticamente una imagen por la siguiente en la lista, incluyendo un atractivo efecto de desvanecimiento entre una imagen y la otra. En este caso el efecto para hacer el cambio es el 'fade' (se desvanecen) que nos proporciona la biblioteca, con un tiempo entre los cambios de 5000 milisegundos.
El resultado final deberá desplegar todas las imágenes de la galería Plateada, incluyendo el título de cada una, haciendo automáticamente el cambio ante nuestros ojos:
Para visualizar cualquier otra galería de Flickr que deseemos, bastará modificar el argumento id dentro de la URL que aparece en el código fuente, con el identficador del autor de la galería que queramos. De esta forma, utilizando recursos 100% libres, podemos agregar un atractivo elemento para nuestras aplicaciones Web.
Hasta la próxima.
martes, 27 de abril de 2010
El cuerpo humano en HTML y PHP
Me encontré esta imagen en Chistes Geek y no puede dejar de traerla, en el cuerpo humano descrito en HTML con código PHP embebido para hacerlo dinámico:
miércoles, 14 de abril de 2010
AJAX utilizando jQuery
Etiquetas:
ajax,
Frameworks,
javascript,
jquery,
PHP
¡Saludos nuevamente blogueros!
De regreso con un breve artículo sobre como utilizar el framework para trabajar con AJAX llamado jQuery. Esta biblioteca de funciones tiene como objetivo general facilitar nuestro trabajo no solo con AJAX sino con todos nuestros scripts de Javascript, ofreciendo -como podemos ver en su documentación anterior- APIs como selectores para el acceso directo a los elementos de la página, manipulación del contenido CSS, efectos de animación en los elementos HTML, manejo de eventos, entre otras muchas utilidades. Debido a la gran popularidad de que ha gozado en las noticias que han llegado a un servidor, decidí explorar un poco este paquete para ver que cosas se pueden hacer con sus herramientas.
Una de las diferencias principales que aparecen en esta framework comparado con Prototype, es que en jQuery existe un mayor encapsulamiento de las funciones, lo que se aprecia en la cantidad de código que resulta al comparar ambos métodos, y queda todavía más claro cuando revisamos que Prototype nos permite crear nuestras propias clases para utilizarlas en las aplicaciones, cosa que no existe en jQuery, donde la filosofía es reducir el trabajo del programador y alejarlo de los detalles tras el código. En este artículo del blog de anieto2k podemos ver una pequeña comparación entre el rendimiento de ambas soluciones. Por otro lado, la conclusión a la que llegan la web intenta sobre el por qué ha ganado tanto terreno el framework jQuery, es que realmente hace más sencillo el desarrollo, cosa que siempre es bienvenida. :-)
Pero bueno, vayamos a la práctica y veamos cómo trabajar con AJAX utilizando este framework. El archivo de esta biblioteca lo encontraremos en su página oficial, dentro de la sección download. Este archivo lo utilizaremos en el código HTML haciendo referencia a él por medio del ya conocido acceso a archivos javascript:
Antes de empezar
Dentro de la documentación que la página oficial contiene, podemos revisar el apartado sobre la funcionalidad de jQuery para AJAX, gracias al conjunto de métodos que posee es posible llevar a cabo todo el proceso de petición al servidor Web, y procesamiento de la respuesta en unas pocas líneas de código. Concretamente nos centraremos en el método jQuery.ajax(), ya que en un solo llamado a esta función podemos configurar todo el proceso de la petición AJAX.
Esta función recibe un único parámetro que consisten en un objeto, que posee una serie de propiedades que nos permiten definir todo el proceso de la petición AJAX. Para este ejemplo utilizaremos las siguientes:
url
Dirección URL del recurso Web al que accederemos.
type
Define el tipo de envío de información que se hará, ya sea GET o POST.
data
Corresponde a la información que se mandará al servidor, en forma de parejas llave-valor, éste representa los mismos datos que pasamos en la cadena de consulta (query) cuando solicitamos una URL remota en los ejemplos sobre AJAX anteriores. Puede ser una cadena de valores codificada en formato de URL (p.e. '&dato1=valor1&dato2=valor2') o un objeto cuyas propiedades correspondan a los datos (p.e. {dato1:"valor1",dato2:"valor2"}).
dataType
Especifica el tipo de datos que esperamos que nos devuelva el servidor después de nuestra petición, estos pueden ser xml, json, script o html.
error
Guarda la referencia a la función que se ejecutará en caso de que haya algún error durante la petición al servidor Web.
complete
Guarda la referencia a la función que se ejecutará cuando la petición al servidor haya tenido una respuesta de parte de éste. Esta función puede recibir como parámetro la información devuelta por el servidor como respuesta a nuestra petición.
beforeSend
Guarda la referencia a la función que se ejecutará antes de iniciar el proceso de petición a nuestro sevidor Web.
Manos a la obra
Para la implementación de la petición asíncrona al servidor utilizaremos la funcionalidad jQuery.ajax que aparece en la documentación, con el código que aparece a continuación:
ajaxJQuery.html
Dentro del código javascript existen 2 funciones, la primera, peticionAjax() lleva a cabo la petición al servidor Web y procesamiento de la respuesta. La segunda, creaObjParametros() se encarga de recuperar los datos capturados por el usuario en el formulario, y almacenarlos dentro de las propiedades de un objeto que regresa como resultado. En la función para enviar la petición se utiliza la función $.ajax (jQuery.ajax) para realizar todo el proceso, ya que el único parámetro que recibe consiste en un objeto, cuyas propiedades contienen todos los datos para que jQuery, lleve a cabo las acciones de petición al servidor Web y el manejo de la respuesta que éste devuelva.
Cómo ya habíamos señalado la propiedad url contiene la dirección URL del recurso Web al que estamos accediendo, en este caso el archivo validaDatos.php, que consiste en una aplicación Web escrita en PHP para validar datos de entrada. El código de este programa aparece en el tercer ejemplo que vimos en este blog sobre Ajax, que se titula Combinando Ajax con PHP. Pueden consultar este artículo para mayor referencia sobre el comportamiento de este programa.
El código aparece a continuación:
validaDatos.php
Básicamente es un programa que recibe una serie de datos enviados por POST desde nuestro navegador, los valida utilizando expresiones regulares, y dependiendo de si cumplieron o no con estás, imprime un mensaje correspondiente al resultado del proceso.
El resultado final de la implementación se puede probar localmente, almacenado en nuestro servidor Web estos dos archivos, y accediendo con nuestro navegador ajaxJQuery.html:
Y así, con unas pocas líneas podemos adaptar nuestros programas anteriores al framework jQuery, y comprobar como es bastante sencillo de utilizar permite además tener un código mucho más limpio.
Al momento de decidir entre cual framework para Ajax utilizar, deberiamos revisar cada una de las funcionalidades que aparecen en sus respectivas documentaciones, y en base a nuestras necesidades y conocimientos previos elegir el que mejor se nos adapte. Al final, la decisión recaerá unicamente en el desarrollador.
Saludos y hasta la próxima.
Una de las diferencias principales que aparecen en esta framework comparado con Prototype, es que en jQuery existe un mayor encapsulamiento de las funciones, lo que se aprecia en la cantidad de código que resulta al comparar ambos métodos, y queda todavía más claro cuando revisamos que Prototype nos permite crear nuestras propias clases para utilizarlas en las aplicaciones, cosa que no existe en jQuery, donde la filosofía es reducir el trabajo del programador y alejarlo de los detalles tras el código. En este artículo del blog de anieto2k podemos ver una pequeña comparación entre el rendimiento de ambas soluciones. Por otro lado, la conclusión a la que llegan la web intenta sobre el por qué ha ganado tanto terreno el framework jQuery, es que realmente hace más sencillo el desarrollo, cosa que siempre es bienvenida. :-)
Pero bueno, vayamos a la práctica y veamos cómo trabajar con AJAX utilizando este framework. El archivo de esta biblioteca lo encontraremos en su página oficial, dentro de la sección download. Este archivo lo utilizaremos en el código HTML haciendo referencia a él por medio del ya conocido acceso a archivos javascript:
<script type="text/javascript" src="jquery-1.4.2.min.js"></script>
Antes de empezar
Dentro de la documentación que la página oficial contiene, podemos revisar el apartado sobre la funcionalidad de jQuery para AJAX, gracias al conjunto de métodos que posee es posible llevar a cabo todo el proceso de petición al servidor Web, y procesamiento de la respuesta en unas pocas líneas de código. Concretamente nos centraremos en el método jQuery.ajax(), ya que en un solo llamado a esta función podemos configurar todo el proceso de la petición AJAX.
Esta función recibe un único parámetro que consisten en un objeto, que posee una serie de propiedades que nos permiten definir todo el proceso de la petición AJAX. Para este ejemplo utilizaremos las siguientes:
url
Dirección URL del recurso Web al que accederemos.
type
Define el tipo de envío de información que se hará, ya sea GET o POST.
data
Corresponde a la información que se mandará al servidor, en forma de parejas llave-valor, éste representa los mismos datos que pasamos en la cadena de consulta (query) cuando solicitamos una URL remota en los ejemplos sobre AJAX anteriores. Puede ser una cadena de valores codificada en formato de URL (p.e. '&dato1=valor1&dato2=valor2') o un objeto cuyas propiedades correspondan a los datos (p.e. {dato1:"valor1",dato2:"valor2"}).
dataType
Especifica el tipo de datos que esperamos que nos devuelva el servidor después de nuestra petición, estos pueden ser xml, json, script o html.
error
Guarda la referencia a la función que se ejecutará en caso de que haya algún error durante la petición al servidor Web.
complete
Guarda la referencia a la función que se ejecutará cuando la petición al servidor haya tenido una respuesta de parte de éste. Esta función puede recibir como parámetro la información devuelta por el servidor como respuesta a nuestra petición.
beforeSend
Guarda la referencia a la función que se ejecutará antes de iniciar el proceso de petición a nuestro sevidor Web.
Manos a la obra
Para la implementación de la petición asíncrona al servidor utilizaremos la funcionalidad jQuery.ajax que aparece en la documentación, con el código que aparece a continuación:
ajaxJQuery.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Prueba de AJAX con JQuery</title>
<script type="text/javascript" src="jquery-1.4.2.min.js"></script>
<script type="text/javascript">
function peticionAjax() {
var objParametros = creaObjParametros();
$.ajax({url: "http://localhost/validaDatos.php",
type: 'POST',
data: objParametros,
dataType: "html",
beforeSend: function() {
document.getElementById("divRespuesta").innerHTML = "<p>Iniciando nueva petición Ajax...</p>";
},
complete: function(respuesta) {
document.getElementById("divRespuesta").innerHTML += respuesta.responseText;
},
error : function(respuesta) {
alert("Ocurrió un error al acceder al servidor Web");
}
});
}
function creaObjParametros() {
var obj = new Object();
obj.txtAutor = document.getElementById("txtAutor").value;
obj.txtAnio = document.getElementById("txtAnio").value;
obj.txtTitulo = document.getElementById("txtTitulo").value;
return obj;
}
</script>
</head>
<body>
<form style="background-color:#EFEDF1">
<label for="txtAutor">Autor:</label>
<input type="text" id="txtAutor" name="txtAutor" size="40" /><br/>
<label for="txtAnio">Año:</label>
<input type="text" id="txtAnio" name="txtAnio" size="10" /><br/>
<label for="txtTitulo">Títiulo:</label>
<input type="text" id="txtTitulo" name="txtTitulo" size="50" /><br/>
<input type="button" value="Validar datos" onclick="peticionAjax()" />
</form>
<div id="divRespuesta"></div>
</body>
</html>
Dentro del código javascript existen 2 funciones, la primera, peticionAjax() lleva a cabo la petición al servidor Web y procesamiento de la respuesta. La segunda, creaObjParametros() se encarga de recuperar los datos capturados por el usuario en el formulario, y almacenarlos dentro de las propiedades de un objeto que regresa como resultado. En la función para enviar la petición se utiliza la función $.ajax (jQuery.ajax) para realizar todo el proceso, ya que el único parámetro que recibe consiste en un objeto, cuyas propiedades contienen todos los datos para que jQuery, lleve a cabo las acciones de petición al servidor Web y el manejo de la respuesta que éste devuelva.
Cómo ya habíamos señalado la propiedad url contiene la dirección URL del recurso Web al que estamos accediendo, en este caso el archivo validaDatos.php, que consiste en una aplicación Web escrita en PHP para validar datos de entrada. El código de este programa aparece en el tercer ejemplo que vimos en este blog sobre Ajax, que se titula Combinando Ajax con PHP. Pueden consultar este artículo para mayor referencia sobre el comportamiento de este programa.
El código aparece a continuación:
validaDatos.php
<?php
//Expresiones regulares para la validación de los datos
$er_titulo = "/^([a-z]|[A-Z]|[ÁÉÍÓÚáéíóúÑñÜü]|[0-9]|[\s\.\-,:'\"])+$/";
$er_autor = "/^([a-z]|[A-Z]|[ÁÉÍÓÚáéíóúÑñÜü]|[\s,\.'\-])+$/";
$er_anio = "/^([1-2][0-9][0-9][0-9])$/";
//Validamos que se hayan enviado todos los datos por post
if(isset($_POST['txtAutor']) && isset($_POST['txtAnio']) && isset($_POST['txtTitulo'])){
//Validamos el contenido de los campos
$autor = stripslashes(trim($_POST['txtAutor']));
$anio = trim($_POST['txtAnio']);
$titulo = stripslashes(trim($_POST['txtTitulo']));
if(!preg_match($er_autor,$autor)){
$campo = "autor";
}
else if(!preg_match($er_anio,$anio)){
$campo = "año";
}
else if(!preg_match($er_titulo,$titulo)){
$campo = "título";
}
else{
$mensaje = "Todos los campos capturados son correctos :)";
}
if(isset($campo))
$mensaje = "Error: El texto capturado en el campo $campo es incorrecto";
}
else{
$mensaje = "No se recibieron los valores";
}
?>
<p><?=$mensaje?></p>
<p><u>Valores recibidos</u>:<br />
Autor: <?=$_POST['txtAutor']?><br />
Año: <?=$_POST['txtAnio']?><br />
Título: <?=$_POST['txtTitulo']?><br />
</p>
Básicamente es un programa que recibe una serie de datos enviados por POST desde nuestro navegador, los valida utilizando expresiones regulares, y dependiendo de si cumplieron o no con estás, imprime un mensaje correspondiente al resultado del proceso.
El resultado final de la implementación se puede probar localmente, almacenado en nuestro servidor Web estos dos archivos, y accediendo con nuestro navegador ajaxJQuery.html:
Y así, con unas pocas líneas podemos adaptar nuestros programas anteriores al framework jQuery, y comprobar como es bastante sencillo de utilizar permite además tener un código mucho más limpio.
Al momento de decidir entre cual framework para Ajax utilizar, deberiamos revisar cada una de las funcionalidades que aparecen en sus respectivas documentaciones, y en base a nuestras necesidades y conocimientos previos elegir el que mejor se nos adapte. Al final, la decisión recaerá unicamente en el desarrollador.
Saludos y hasta la próxima.
viernes, 18 de diciembre de 2009
Tratamiento de imágenes en Java, Parte 2: Controlador y Modelo
Etiquetas:
Controlador,
Java,
Modelo,
Procesamiento de imágenes,
Swing
¡Saludos nuevamente blogueros!
Ahora terminaremos con el código que quedó pendiente en el artículo anterior, que consiste en la programación del Controlador y el Modelo de nuestra aplicación.
El Controlador tendrá la obligación de manejar los eventos que ocurran en la interfaz de usuario, de modo que recibirá las acciones del usuario en el menú de opciones y las paletas de edición, los cuales ya creamos en el artículo anterior.
Por otro lado, el Modelo tendrá responsabilidades a más bajo nivel, encargándose de implementar la carga, la edición y el guardado de las imágenes. Separando las diferentes funcionalidades en dos capas de nivel distinto, como si de las capas de Negocio y Acceso a datos se tratara.
El Controlador
El Controlador de la aplicación consistirá en una única clase que implementará las interfaces ActionListener y ChangeListener, para implementar en control de eventos del menú y de las barras JSlider respectivamente.
El código es el siguiente:
Controlador.java
Como podemos ver, en la implementación del método actionPerformed() lleva a cabo las acciones relacionadas con el menú de opciones, tanto para las acciones de la opción "Archivo" (Abrir, Guardar y Salir) como para la "Edición" (Ajustar Brillo, Ajustar Colores, Escala de Grises). Las opciones de edición solo se activarán una vez que un archivo de imagen haya sido cargado por el editor, para lo cual se apoyará en un objeto de la clase ManejadorDeImagenes, la cual veremos más adelante.
En la implementación del método stateChanged(), se reciben las acciones ocurridas en los controles de tipo JSlider, de forma que podremos capturar el valor del brillo o los colores que el usuario capture en la interface, y llamará a alguno de los métodos del objeto de la clase ManejadorDeImagenes para aplicar estos cambios a las imagenes cargadas.
El Modelo
Como ya explicamos antes, el modelo consistirá estará separado en dos capas, que en la programación veremos expresado como dos clases diferentes. La primera ManejadorDeImagenes, que equivaldrá a la capa de negocio en las aplicaciones de manejo de datos. Esta clase será un intermediario entre el Controlador y la Vista de la aplicación con el nivel de Acceso a datos, que en este caso consiste en la apertura, modificación y salvado de las imágenes. Estas operaciones de bajo nivel serán llevadas a cabo por la clase ProcesadorDeImagenes, la cual estará completamente abstraída del resto de la aplicación, ya que en ella ocurrirán las operaciones a nivel de pixeles sobre las imágenes, y además poseerá métodos para cargar una imagen desde el archivo y salvar las imágenes resultantes del procesamiento hacia un archivo nuevo.
El código de la primera de estas clases aparece a continuación:
ManejadorDeImagenes.java
Como vemos en los métodos cargaArchivoDeImagen() y guardaArchivoDeImagen(), la funcionalidad para elegir los archivos que serán cargados y guardados se facilita con el uso de la clase JFileChooser, la cual permite llamar a una ventana de selección de archivos, con la cual resulta más agradable esta tarea.
También podemos apreciar que se utiliza un objeto de la clase FiltroDeArchivo, para establecer el filtro que tendrá el objeto selector de la clase JFileChooser. De esta forma nos aseguramos que solo puedan ser seleccionadas imágenes de tipo JPEG y GIF. El código de esta clase auxiliar aparece a continuación:
FiltroDeArchivo.java
Tanto estos dos métodos como el resto serán llamados desde el Controlador de la aplicación para cada acción que el usuario vaya realizando en la interfaz gráfica. De modo que el método muestraEscalaDeGrises() será llamado cuando el usuario seleccione la opcion "Escala de Grises" en el menú principal. Mientas que el método muestraBrillo() es llamado cuando el usuario realice un cambio en el brillo de la imagen, utilizando la barra JSlider jslBrillo para ajustar el brillo. Algo similar ocurrirá con el método muestraColores(), que se llamará cuando alguna de las barras JSlider de colores (jslRojo, jslVerde, jslAzul) sea utilizada. Estos métodos además llevan a cabo la actualización en la pantalla de las imágenes una vez que son modificadas. En el caso de que el usuario aumente el brillo o modifique los colores, la acción ocurrirá tan rápido que no podrá darse cuenta de todo el trabajo que la PC llevó a cabo para cambiar la apariencia de la imagen capturada.
El método restableceImagen() se utiliza para volver a colocar en la pantalla la imagen original que fue cargada, lo cual es simplemente una forma de asegurarnos que las tres acciones de edición (aumentar brillo, modificar colores y convertir a escala de grises) siempre ocurran sobre la misma imagen que fue cargada.
El código de la clase ProcesadorDeImagenes a la que nos hemos referido aparece a continuación:
ProcesadorDeImagenes.java
Como ya mencionamos esta clase trabajo al nivel más bajo de la aplicación directamente con los pixeles de las imagenes. Para lograr esto utiliza dos objetos de las clase Image (imagenBase e imagenModificada), para almacenar tanto las imágenes que son leídas tanto como la imagen que resulta del procesamiento (respectivamente). Los métodos estableceImagen() y cargarImagen() le permiten tener una imagen sobre la cual llevar a cabo el procesamiento que ocurre en los otros métodos.
Los métodos modificaBrillo(), escalaDeGrises() y modificaColores() llevan a cabo los cambios en los pixeles de la imagen, aplicando una serie de operaciones a cada uno de sus valores de intensidad de color, almacenando los valores de los pixeles dentro de un arreglo de número enteros y recorriéndolo dentro de un ciclo for. De esta forma, cada uno de los pixeles es tratado como un número entero, para el cual los valores de la intensidad del color estarán contenidos en sus primeros tres grupos de 8 bits. Es decir, los primero 8 bits menos significativos del entero representarán la intensidad del azul, los siguientes 8 bits los del verde y los 8 bits que le siguen los del rojo. Es por esto que en el código vemos que se lleva a cabo un desplazamiento a nivel de bits del valor de un pixel, para junto con la operación AND obtener los 8 bits del valor de cada uno de los tres colores del pixel:
El código anterior es el que encontramos en el método modificaBrillo(), y lo que vemos que se hace es justamente sumarle una misma cantidad a cada uno de los valores de color del pixel para de esta forma obtener un pixel más iluminado, para después almacenar este pixel obtenido dentro del arreglo de pixeles.
En los otros dos métodos se sigue una lógica parecida, con la única diferencia de que para modificaColores, se aumentará la intensidad de los colores de los pixeles con un valor distinto para cada color, mientras que en escalaDeGrises se promediará el valor de los tres colores de cada pixel y se almacenará el mismo resultado dentro de los tres valores de color del pixel, para de esta forma obtener el equivalente en escala de grises del color original.
Implementación final
Ahora bien, visto el código del Controlador y el Modelo de la aplicación, solo nos queda juntarlo todo con la Vista para echar a correr el programa final. Para hacer eso, tendremos que modificar el código de la clase EditorImg que realizamos en el artículo anterior, agregando las líneas que se remarcan en el siguiente código:
EditorImg.java
Y como vemos, lo que se hace es simplemente agregar el Controlador a la aplicación, instanciando un objeto de la clase Controlador, y asignando la gestión de eventos del menú principal y de los componentes JSlider a este objeto.
Una vez listo el paso anterior, podemos probar correr el programa y deberemos ver la misma pantalla que en el artículo anterior, con la diferencia de que ahora ya podremos cargar, editar y modificar las imágenes.
Por ejemplo, así es como se visualiza el cargado de las imágenes:
Aquí vemos una imagen ya cargada:
Si modificamos los colores:
O el brillo:
Y aquí como se ve en escala de grises:
Al final solo tenemos que salvar la imagen para concluir con el proceso de edición.
Bien, el código está totalmente a su disposición y es obviamente propenso a mejoras :D, ya que las funciones de procesamiento que aquí implementamos son algunas de las que se consideran básicas, pero con estos principios es posible incursionar en conceptos más avanzados de procesamiento de imágenes, en particular le veo mucho potencial a la clase ProcesadorDeImagenes para seguirla desarrollando e incluir un amplio abanico de operaciones de procesamiento de imágenes digitales, y que gracias a la abstracción de su diseño, pueda ser llevada a otras aplicaciones de forma transparente.
Y por hoy es todo, posiblemente este sea el último post de este año 2009, ya que la próxima semana ya estaré de vacaciones y tendré mi agenda un poco ocupada, pero espero que para el año que entra tendré mayor tiempo y decisión XD para seguir explorando las posibilidades de las herramientas de software libre.
Saludos desde la cabaña y hasta la próxima.
Ahora terminaremos con el código que quedó pendiente en el artículo anterior, que consiste en la programación del Controlador y el Modelo de nuestra aplicación.
El Controlador tendrá la obligación de manejar los eventos que ocurran en la interfaz de usuario, de modo que recibirá las acciones del usuario en el menú de opciones y las paletas de edición, los cuales ya creamos en el artículo anterior.
Por otro lado, el Modelo tendrá responsabilidades a más bajo nivel, encargándose de implementar la carga, la edición y el guardado de las imágenes. Separando las diferentes funcionalidades en dos capas de nivel distinto, como si de las capas de Negocio y Acceso a datos se tratara.
El Controlador
El Controlador de la aplicación consistirá en una única clase que implementará las interfaces ActionListener y ChangeListener, para implementar en control de eventos del menú y de las barras JSlider respectivamente.
El código es el siguiente:
Controlador.java
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JMenuItem;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.JPanel;
/**
* @Desc Clase que implementa la gestión de evento en la interfaz de usuario
* @author Beto González
*
*/
public class Controlador implements ActionListener, ChangeListener{
ManejadorDeImagenes manejador;
PanelSwing panel;
public Controlador(PanelSwing panel) {
this.panel = panel;
manejador = new ManejadorDeImagenes();
}
/**
* @Desc Método que capturará los eventos ocurridos en el menú principal del sistema
*/
public void actionPerformed(ActionEvent ie) {
JMenuItem i = (JMenuItem)ie.getSource();
if(i.getText() == "Abrir"){
boolean estado = manejador.cargaArchivoDeImagen(panel, panel.lienzo);
if(estado) {
panel.guardar.setEnabled(true);
panel.brillo.setEnabled(true);
panel.color.setEnabled(true);
panel.escala.setEnabled(true);
panel.esqueInf.show(panel.panelBajo, "carta1");
}
}
else if(i.getText() == "Guardar")
manejador.guardaArchivoDeImagen(panel);
else if(i.getText() == "Salir")
System.exit(0);
else if(i.getText() == "Ajustar Brillo")
{
manejador.restableceImagen(panel.lienzo);
panel.jslBrillo.setValue(0);
panel.esqueInf.show(panel.panelBajo, "carta2");
}
else if(i.getText() == "Ajustar Colores")
{
manejador.restableceImagen(panel.lienzo);
panel.esqueInf.show(panel.panelBajo, "carta3");
}
else if(i.getText() == "Escala de Grises")
{
panel.esqueInf.show(panel.panelBajo, "carta1");
manejador.muestraEscalaDeGrises(panel.lienzo);
}
}
/**
* @Desc Método que captarará los eventos ocurridos en los componentes JSlider de la interfaz de usuario
*/
public void stateChanged(ChangeEvent e)
{
JSlider slider = (JSlider) e.getSource();
if(slider == panel.jslBrillo)
manejador.muestraBrillo(panel.lienzo, slider.getValue());
else if(slider == panel.jslRojo) {
manejador.muestraColores(panel.lienzo, slider.getValue(), panel.jslVerde.getValue(), panel.jslAzul.getValue());
}
else if(slider == panel.jslVerde)
manejador.muestraColores(panel.lienzo, panel.jslRojo.getValue(), slider.getValue(), panel.jslAzul.getValue());
else if(slider == panel.jslAzul)
manejador.muestraColores(panel.lienzo, panel.jslRojo.getValue(), panel.jslVerde.getValue(), slider.getValue());
}
}
Como podemos ver, en la implementación del método actionPerformed() lleva a cabo las acciones relacionadas con el menú de opciones, tanto para las acciones de la opción "Archivo" (Abrir, Guardar y Salir) como para la "Edición" (Ajustar Brillo, Ajustar Colores, Escala de Grises). Las opciones de edición solo se activarán una vez que un archivo de imagen haya sido cargado por el editor, para lo cual se apoyará en un objeto de la clase ManejadorDeImagenes, la cual veremos más adelante.
En la implementación del método stateChanged(), se reciben las acciones ocurridas en los controles de tipo JSlider, de forma que podremos capturar el valor del brillo o los colores que el usuario capture en la interface, y llamará a alguno de los métodos del objeto de la clase ManejadorDeImagenes para aplicar estos cambios a las imagenes cargadas.
El Modelo
Como ya explicamos antes, el modelo consistirá estará separado en dos capas, que en la programación veremos expresado como dos clases diferentes. La primera ManejadorDeImagenes, que equivaldrá a la capa de negocio en las aplicaciones de manejo de datos. Esta clase será un intermediario entre el Controlador y la Vista de la aplicación con el nivel de Acceso a datos, que en este caso consiste en la apertura, modificación y salvado de las imágenes. Estas operaciones de bajo nivel serán llevadas a cabo por la clase ProcesadorDeImagenes, la cual estará completamente abstraída del resto de la aplicación, ya que en ella ocurrirán las operaciones a nivel de pixeles sobre las imágenes, y además poseerá métodos para cargar una imagen desde el archivo y salvar las imágenes resultantes del procesamiento hacia un archivo nuevo.
El código de la primera de estas clases aparece a continuación:
ManejadorDeImagenes.java
import java.awt.*;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
/**
* @Desc Clase del nivel de la capa de negocios, que implementa las operaciones que son llamadas desde el Controlador de la aplicación
* para poder cargar las imagenes, alamacenarlas y modificaralas, apoyandose en un objeto la clase de más bajo nivel, es decir ProcesadorDeImagenes
* @author Beto González
*
*/
public class ManejadorDeImagenes {
ProcesadorDeImagenes procesador;
boolean editado = false;
// Constructor de la clase
public ManejadorDeImagenes() {
procesador = new ProcesadorDeImagenes();
}
/**
* @Desc Método que lleva a cabo la carga de un archivo de imagen
* @param contenedor
* @param lienzo
* @return
*/
public boolean cargaArchivoDeImagen(JPanel contenedor, PanelDeImagen lienzo)
{
String nombreArchivo = "";
boolean estado = true;
if(editado)
{
//new MsgError("Confirmacion","Aqui debemos pedir confirmación",200,180);
int resultado = JOptionPane.showConfirmDialog((Component)null, "¿Deseas guardar los cambios en este documento?","Confirmación",JOptionPane.YES_NO_OPTION);
if(resultado==JOptionPane.YES_OPTION)
guardaArchivoDeImagen(contenedor);
}
JFileChooser selector = new JFileChooser();
selector.addChoosableFileFilter(new FiltrodeArchivo("gif","Archivos Gif"));
String lista[] = {"jpeg","jpg"};
selector.addChoosableFileFilter(new FiltrodeArchivo(lista,"Archivos JPEG"));
selector.setDialogTitle("Abrir archivo de imagen");
selector.setDialogType(JFileChooser.OPEN_DIALOG);
int resultado = selector.showOpenDialog(null);
if(resultado == JFileChooser.APPROVE_OPTION)
{
nombreArchivo = selector.getSelectedFile().getName();
String ruta = selector.getSelectedFile().getPath();
Image imagen = procesador.cargaImagen(ruta, nombreArchivo);
lienzo.estableceImagen(imagen);
lienzo.repaint();
editado = false;
}
else
estado = false;
return estado;
}
/**
* @Desc Método que lleva a cabo la operación de salvar el archivo de imagen cargado
* @param contenedor
* @return
*/
public boolean guardaArchivoDeImagen(JPanel contenedor)
{
boolean estado = true;
JFileChooser selector = new JFileChooser();
selector.addChoosableFileFilter(new FiltrodeArchivo("gif","Archivos Gif"));
String lista[] = {"jpeg","jpg"};
selector.addChoosableFileFilter(new FiltrodeArchivo(lista,"Archivos JPEG"));
selector.setDialogTitle("Guardar archivo de imagen");
selector.setDialogType(JFileChooser.SAVE_DIALOG);
int resultado = selector.showSaveDialog(contenedor);
if(resultado == JFileChooser.APPROVE_OPTION)
{
//guardar archivo en la ruta especificada
String nombreArchivo = selector.getSelectedFile().getName();
String ruta = selector.getSelectedFile().getPath();
estado = procesador.guardaImagen(ruta, nombreArchivo);
if(!estado)
JOptionPane.showMessageDialog((Component)null,"Error del sistema : "+procesador.devuelveMensajeDeError(),"Error de Imagen",JOptionPane.OK_OPTION);
editado = false;
}
else
estado = false;
return estado;
}
/**
* @Desc Método que lleva a cabo la transformación de la imagen cargada a una imagen de escala de grises y la despliega en pantalla
* @param lienzo
*/
public void muestraEscalaDeGrises(PanelDeImagen lienzo)
{
procesador.escalaDeGrises();
lienzo.estableceImagen(procesador.devuelveImagenModificada());
lienzo.repaint();
}
/**
* @Desc Método que lleva a cabo la modificación del brillo de la imagen cargada y despliega la imagen resultante en pantalla
* @param lienzo
* @param valor
*/
public void muestraBrillo(PanelDeImagen lienzo, int valor)
{
procesador.modificaBrillo(valor);
lienzo.estableceImagen(procesador.devuelveImagenModificada());
lienzo.repaint();
editado = true;
}
/**
* @Desc @Desc Método que lleva a cabo la modificación de los colores de la imagen cargada y despliega la imagen resultante en pantalla
* @param lienzo
* @param rojo
* @param verde
* @param azul
*/
public void muestraColores(PanelDeImagen lienzo, int rojo, int verde, int azul)
{
procesador.modificaColor(rojo,verde,azul);
lienzo.estableceImagen(procesador.devuelveImagenModificada());
lienzo.repaint();
editado = true;
}
/**
* @Desc Método que coloca en la pantalla la imagen original que se cargó con el método cargarArchivoDeImagen
* @param lienzo
*/
public void restableceImagen(PanelDeImagen lienzo)
{
lienzo.estableceImagen(procesador.devuelveImagenBase());
lienzo.repaint();
editado = false;
}
}
Como vemos en los métodos cargaArchivoDeImagen() y guardaArchivoDeImagen(), la funcionalidad para elegir los archivos que serán cargados y guardados se facilita con el uso de la clase JFileChooser, la cual permite llamar a una ventana de selección de archivos, con la cual resulta más agradable esta tarea.
También podemos apreciar que se utiliza un objeto de la clase FiltroDeArchivo, para establecer el filtro que tendrá el objeto selector de la clase JFileChooser. De esta forma nos aseguramos que solo puedan ser seleccionadas imágenes de tipo JPEG y GIF. El código de esta clase auxiliar aparece a continuación:
FiltroDeArchivo.java
import java.io.*;
/**
* @Desc Clase que permite crear un filtro de archivos para utilizarlo con un selector de archivos
* @author Beto González
*
*/
public class FiltrodeArchivo extends javax.swing.filechooser.FileFilter{
String descrip = "";
String listTipos[];
FiltrodeArchivo(String tipo, String descripcion)
{
listTipos = new String[1];
listTipos[0] = tipo;
descrip = descripcion;
}
FiltrodeArchivo(String listTipos[], String descripcion)
{
this.listTipos = listTipos;
descrip = descripcion;
}
public boolean accept(File fileobj){
boolean extIgual = false;
String extension = "";
int ind=0;
if(fileobj.getPath().lastIndexOf(".") > 0)
{
extension = fileobj.getPath().substring(fileobj.getPath().lastIndexOf(".")+1).toLowerCase();
}
if(extension!="")
{
while(ind<listTipos.length && !extIgual)
{
extIgual = extension.equals(listTipos[ind].toLowerCase());
ind++;
}
return extIgual;
}
else
return fileobj.isDirectory();
}
public String getDescription()
{
if (descrip != "")
return descrip.concat(concatTipos());
else
return "";
}
private String concatTipos()
{
StringBuffer tipos = new StringBuffer(" (*."+listTipos[0]);
for(int i=1;i<listTipos.length;i++)
tipos.append(",*."+listTipos[i]);
tipos.append(")");
return tipos.toString();
}
}
Tanto estos dos métodos como el resto serán llamados desde el Controlador de la aplicación para cada acción que el usuario vaya realizando en la interfaz gráfica. De modo que el método muestraEscalaDeGrises() será llamado cuando el usuario seleccione la opcion "Escala de Grises" en el menú principal. Mientas que el método muestraBrillo() es llamado cuando el usuario realice un cambio en el brillo de la imagen, utilizando la barra JSlider jslBrillo para ajustar el brillo. Algo similar ocurrirá con el método muestraColores(), que se llamará cuando alguna de las barras JSlider de colores (jslRojo, jslVerde, jslAzul) sea utilizada. Estos métodos además llevan a cabo la actualización en la pantalla de las imágenes una vez que son modificadas. En el caso de que el usuario aumente el brillo o modifique los colores, la acción ocurrirá tan rápido que no podrá darse cuenta de todo el trabajo que la PC llevó a cabo para cambiar la apariencia de la imagen capturada.
El método restableceImagen() se utiliza para volver a colocar en la pantalla la imagen original que fue cargada, lo cual es simplemente una forma de asegurarnos que las tres acciones de edición (aumentar brillo, modificar colores y convertir a escala de grises) siempre ocurran sobre la misma imagen que fue cargada.
El código de la clase ProcesadorDeImagenes a la que nos hemos referido aparece a continuación:
ProcesadorDeImagenes.java
import java.awt.Canvas;
import java.awt.image.*;
import java.awt.Image;
import java.awt.Toolkit;
import java.io.IOException;
import java.io.File;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
/**
* @Desc Clase que implementa el procesamiento básico de imágenes digitales
* @author Beto González
*
*/
public class ProcesadorDeImagenes extends Canvas {
Image imagenBase;
Image imagenModificada;
String mensajeDeError = "";
String tipoDeImagen = "";
/**
* @Desc Método que permite agregar una imagen al procesador que es recibida como parámetro
* @param imagen
*/
public void estableceImagen(Image imagen)
{
imagenBase = imagen;
imagenModificada = null;
tipoDeImagen = (String)imagenBase.getProperty("type", this);
}
/**
* @Desc Método que permite agregar una imagen al procesador directamente desde un archivo de imagen
* @param imagen
*/
public Image cargaImagen(String ruta, String nombreDeArchivo)
{
imagenBase = Toolkit.getDefaultToolkit().getImage(ruta);
imagenModificada = null;
String[] partes = null;
partes = nombreDeArchivo.split("\\.");
int tope = partes.length;
if(tope > 1)
tipoDeImagen = partes[tope - 1];
return imagenBase;
}
/**
* @Desc Método que modifica el brillo de la imagen base contenida a partir del valor de intesidad recibido
* @param intensidad
* @return Verdadero si todo salió bien, falso en caso de error
*/
public boolean modificaBrillo(int intensidad)
{
boolean estado = true;
int p, rojo, verde, azul;
int a = imagenBase.getWidth(this); //Ancho
int h = imagenBase.getHeight(this); //Alto
int totalDePixeles = a * h;
int pixeles[] = new int[totalDePixeles]; //Arreglo de pixeles
PixelGrabber pg = new PixelGrabber(imagenBase,0,0,a,h,pixeles,0,a);
try
{
pg.grabPixels();
for(int i = 0; i < totalDePixeles; i++)
{
p = pixeles[i]; //Valor de un pixel
rojo = (0xff & (p>>16)) + intensidad; //Desplaza el entero p 16 bits a la derecha y aplica la operacion AND a los primeros 8 bits
verde = (0xff & (p>>8)) + intensidad; //Desplaza el entero p 8 bits a la derecha y aplica la operacion AND a los siguientes 8 bits
azul = (0xff & p) + intensidad; //Aplica la operacion AND a los siguientes 8 bits
if(rojo>255) rojo=255;
if(verde>255) verde=255;
if(azul>255) azul=255;
if(rojo<0) rojo=0;
if(verde<0) verde=0;
if(azul<0) azul=0;
pixeles[i]=(0xff000000|rojo<<16|verde<<8|azul);
}
imagenModificada = createImage(new MemoryImageSource(a,h,pixeles,0,a));
}catch(InterruptedException e)
{
//JOptionPane.showMessageDialog((Component)null,"Error del sistema : "+e.getMessage(),"Error de Imagen",JOptionPane.OK_OPTION);
estado = false;
this.mensajeDeError = e.getMessage();
}
return estado;
}
/**
* @Desc Método que convierte la imagen base contenida en una imagen a escala de grises
* @return Verdadero si todo salió bien, falso en caso de error
*/
public boolean escalaDeGrises()
{
boolean estado = true;
int p, promedio, rojo, verde, azul;
int a = imagenBase.getWidth(this); //Ancho
int h = imagenBase.getHeight(this); //Alto
int totalDePixeles = a * h;
int pixeles[] = new int[totalDePixeles]; //Arreglo de pixeles
PixelGrabber pg = new PixelGrabber(imagenBase,0,0,a,h,pixeles,0,a);
try
{
pg.grabPixels();
for(int i = 0; i < totalDePixeles; i++)
{
p = pixeles[i]; //Valor de un pixel
rojo = (0xff & (p>>16)); //Desplaza el entero p 16 bits a la derecha y aplica la operacion AND a los primeros 8 bits
verde = (0xff & (p>>8)); //Desplaza el entero p 8 bits a la derecha y aplica la operacion AND a los siguientes 8 bits
azul = (0xff & p) ; //Aplica la operacion AND a los siguientes 8 bits
promedio = (int) ((rojo+verde+azul)/3);
pixeles[i]=(0xff000000|promedio<<16|promedio<<8|promedio);
}
imagenModificada = createImage(new MemoryImageSource(a,h,pixeles,0,a));
}catch(InterruptedException e)
{
//JOptionPane.showMessageDialog((Component)null,"Error del sistema : "+e.getMessage(),"Error de Imagen",JOptionPane.OK_OPTION);
estado = false;
this.mensajeDeError = e.getMessage();
}
return estado;
}
/**
* @Desc Método que modifica los colores de la imagen base contenida a partir de los valores de intensidad de los colores rojo, verde y amarillo recibidos
* @param iRojo
* @param iVerde
* @param iAzul
* @return Verdadero si todo salió bien, falso en caso de error
*/
public boolean modificaColor(int iRojo, int iVerde, int iAzul)
{
boolean estado = true;
int p, rojo=0, verde=0, azul=0;
int a = imagenBase.getWidth(this); //Ancho
int h = imagenBase.getHeight(this); //Alto
int totalDePixeles = a * h;
int pixeles[] = new int[totalDePixeles]; //Arreglo de pixeles
PixelGrabber pg = new PixelGrabber(imagenBase,0,0,a,h,pixeles,0,a);
try
{
pg.grabPixels();
for(int i = 0;i<(a*h);i++)
{
p = pixeles[i]; //Valor de un pixel
rojo = (0xff & (p>>16)) + iRojo;
verde = (0xff & (p>>8)) + iVerde;
azul = (0xff & p) + iAzul;
if(rojo>255) rojo=255;
if(verde>255) verde=255;
if(azul>255) azul=255;
if(rojo<0) rojo=0;
if(verde<0) verde=0;
if(azul<0) azul=0;
pixeles[i]=(0xff000000|rojo<<16|verde<<8|azul);
}
imagenModificada = createImage(new MemoryImageSource(a,h,pixeles,0,a));
}catch(InterruptedException e)
{
estado = false;
this.mensajeDeError = e.getMessage();
}
return estado;
}
/**
* @Desc Método que almacena la imagen contenida en una archivo de imagen, de acuerdo a la información del archivo que recibe como parámetro
* @param ruta
* @param nombreDeArchivo
* @param tipoDeImagen
* @return
*/
public boolean guardaImagen(String ruta, String nombreDeArchivo, String tipoDeImagen)
{
boolean estado = true;
BufferedImage imagen = creaBufferedImage((imagenModificada != null) ? imagenModificada : imagenBase);
try {
ImageIO.write(imagen, tipoDeImagen, new File(ruta));
} catch (IOException e) {
estado = false;
this.mensajeDeError = e.getMessage();
}
return estado;
}
/**
* @Desc Versión por defecto del método guardaImagen
* @param ruta
* @param nombreDeArchivo
* @return
*/
public boolean guardaImagen(String ruta, String nombreDeArchivo)
{
String[] partes = null;
partes = nombreDeArchivo.split("\\.");
int tope = partes.length;
if(tope > 1)
tipoDeImagen = partes[tope - 1];
return guardaImagen(ruta, nombreDeArchivo, tipoDeImagen);
}
/**
* @Desc Método que devuelve la imagen modificada por el procesador en un objeto de la clase Image
* @return
*/
public Image devuelveImagenModificada()
{
return imagenModificada;
}
/**
* @Desc Método que devuelve la imagen base dentro de un objeto de la clase Image
* @return
*/
public Image devuelveImagenBase()
{
return imagenBase;
}
/**
* @Desc Método que retora el último mensaje de error producido por los métodos de la clase
* @return
*/
public String devuelveMensajeDeError()
{
return mensajeDeError;
}
/**
* @Desc Versión por defecto del método creaBufferedImage
* @param imageIn
* @return El objeto BufferedImage
*/
public BufferedImage creaBufferedImage(Image imagenDeEntrada) {
return creaBufferedImage(imagenDeEntrada, BufferedImage.TYPE_INT_RGB);
}
/**
* @Desc Método para convertir un objeto Image a un objeto BufferedImage
* @param imageIn
* @param imageType
* @return El objeto BufferedImage
*/
public BufferedImage creaBufferedImage(Image imagenDeEntrada, int imageType) {
BufferedImage bufferedImageDeSalida = new BufferedImage(imagenDeEntrada.getWidth(this), imagenDeEntrada.getHeight(this), imageType);
Graphics g = bufferedImageDeSalida.getGraphics();
g.drawImage(imagenDeEntrada, 0, 0, null);
return bufferedImageDeSalida;
}
}
Como ya mencionamos esta clase trabajo al nivel más bajo de la aplicación directamente con los pixeles de las imagenes. Para lograr esto utiliza dos objetos de las clase Image (imagenBase e imagenModificada), para almacenar tanto las imágenes que son leídas tanto como la imagen que resulta del procesamiento (respectivamente). Los métodos estableceImagen() y cargarImagen() le permiten tener una imagen sobre la cual llevar a cabo el procesamiento que ocurre en los otros métodos.
Los métodos modificaBrillo(), escalaDeGrises() y modificaColores() llevan a cabo los cambios en los pixeles de la imagen, aplicando una serie de operaciones a cada uno de sus valores de intensidad de color, almacenando los valores de los pixeles dentro de un arreglo de número enteros y recorriéndolo dentro de un ciclo for. De esta forma, cada uno de los pixeles es tratado como un número entero, para el cual los valores de la intensidad del color estarán contenidos en sus primeros tres grupos de 8 bits. Es decir, los primero 8 bits menos significativos del entero representarán la intensidad del azul, los siguientes 8 bits los del verde y los 8 bits que le siguen los del rojo. Es por esto que en el código vemos que se lleva a cabo un desplazamiento a nivel de bits del valor de un pixel, para junto con la operación AND obtener los 8 bits del valor de cada uno de los tres colores del pixel:
p = pixeles[i]; //Valor de un pixel
rojo = (0xff & (p>>16)) + intensidad; //Desplaza el entero p 16 bits a la derecha y aplica la operacion AND a los primeros 8 bits
verde = (0xff & (p>>8)) + intensidad; //Desplaza el entero p 8 bits a la derecha y aplica la operacion AND a los siguientes 8 bits
azul = (0xff & p) + intensidad; //Aplica la operacion AND a los siguientes 8 bits
El código anterior es el que encontramos en el método modificaBrillo(), y lo que vemos que se hace es justamente sumarle una misma cantidad a cada uno de los valores de color del pixel para de esta forma obtener un pixel más iluminado, para después almacenar este pixel obtenido dentro del arreglo de pixeles.
En los otros dos métodos se sigue una lógica parecida, con la única diferencia de que para modificaColores, se aumentará la intensidad de los colores de los pixeles con un valor distinto para cada color, mientras que en escalaDeGrises se promediará el valor de los tres colores de cada pixel y se almacenará el mismo resultado dentro de los tres valores de color del pixel, para de esta forma obtener el equivalente en escala de grises del color original.
Implementación final
Ahora bien, visto el código del Controlador y el Modelo de la aplicación, solo nos queda juntarlo todo con la Vista para echar a correr el programa final. Para hacer eso, tendremos que modificar el código de la clase EditorImg que realizamos en el artículo anterior, agregando las líneas que se remarcan en el siguiente código:
EditorImg.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/**
* Descripción: Esta clase implementa un editor básico de imagenes .jpg y .gif,
* utilizando componentes Swing y clases de la libreria image.
* @author Alberto González Espinoza
* @version 1.0
* @category Multimedia
*/
public class EditorImg extends JFrame{
static final long serialVersionUID=10000;
PanelSwing panel;
Controlador controlador;
public static void main(String[] args) {
// TODO Auto-generated method stub
EditorImg editor = new EditorImg();
editor.setBounds(120, 120, 800, 600);
editor.setVisible(true);
editor.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
editor.addWindowListener(new WindowAdapter()
{
public void WindowCloser(WindowEvent e)
{
System.exit(0);
}
});
}
/**
* @Desc Constructor de la clase
*/
EditorImg() {
super("Editor básico de imagenes");
Container contentPane = getContentPane();
panel = new PanelSwing(this);
controlador = new Controlador(panel);
panel.abrir.addActionListener(controlador);
panel.guardar.addActionListener(controlador);
panel.salir.addActionListener(controlador);
panel.brillo.addActionListener(controlador);
panel.escala.addActionListener(controlador);
panel.color.addActionListener(controlador);
panel.jslBrillo.addChangeListener(controlador);
panel.jslRojo.addChangeListener(controlador);
panel.jslVerde.addChangeListener(controlador);
panel.jslAzul.addChangeListener(controlador);
contentPane.add(panel);
}
}
Y como vemos, lo que se hace es simplemente agregar el Controlador a la aplicación, instanciando un objeto de la clase Controlador, y asignando la gestión de eventos del menú principal y de los componentes JSlider a este objeto.
Una vez listo el paso anterior, podemos probar correr el programa y deberemos ver la misma pantalla que en el artículo anterior, con la diferencia de que ahora ya podremos cargar, editar y modificar las imágenes.
Por ejemplo, así es como se visualiza el cargado de las imágenes:
Aquí vemos una imagen ya cargada:
Si modificamos los colores:
O el brillo:
Y aquí como se ve en escala de grises:
Al final solo tenemos que salvar la imagen para concluir con el proceso de edición.
Bien, el código está totalmente a su disposición y es obviamente propenso a mejoras :D, ya que las funciones de procesamiento que aquí implementamos son algunas de las que se consideran básicas, pero con estos principios es posible incursionar en conceptos más avanzados de procesamiento de imágenes, en particular le veo mucho potencial a la clase ProcesadorDeImagenes para seguirla desarrollando e incluir un amplio abanico de operaciones de procesamiento de imágenes digitales, y que gracias a la abstracción de su diseño, pueda ser llevada a otras aplicaciones de forma transparente.
Y por hoy es todo, posiblemente este sea el último post de este año 2009, ya que la próxima semana ya estaré de vacaciones y tendré mi agenda un poco ocupada, pero espero que para el año que entra tendré mayor tiempo y decisión XD para seguir explorando las posibilidades de las herramientas de software libre.
Saludos desde la cabaña y hasta la próxima.
martes, 15 de septiembre de 2009
Tratamiento de imágenes en Java, Parte 1: Introducción y Vista
Etiquetas:
Java,
Procesamiento de imágenes,
Swing,
Vista
¡Vamos a ponerle color a Java!
Hola de nuevo blogueros, después de una larga sequía de varios meses ahora traigo el código fuente para la creación de un sencillo editor de imágenes en Java, el cual servirá para dos propósitos, como son el ilustrar el potencial del manejo de archivos e imágenes con Java y conocer algunos componentes adicionales de las interfaces de usuario de Swing.
Sobre la edición de imágenes
Las imágenes digitales consisten en un conjunto de valores numéricos ordenados en forma de arreglos o matrices, cada uno de estos valores representa el contenido del color de un pixel.
Sobre la edición de imágenes
Las imágenes digitales consisten en un conjunto de valores numéricos ordenados en forma de arreglos o matrices, cada uno de estos valores representa el contenido del color de un pixel.
Los pixeles son los puntos de color que se despliegan en la pantalla al reproducir una imagen en nuestra PC. Al observar estos puntos en conjunto podemos apreciar la imagen en su totalidad, la cual no es otra cosa que una representación digital de una imagen del mundo real, pero que nuestro cerebro interpreta como si se tratara de la imagen original.
Podemos distinguir tres tipos de imágenes por las características de sus pixeles, estás son las las imágenes en escala de grises, las imágenes en blanco y negro y las imágenes a color.
Podemos distinguir tres tipos de imágenes por las características de sus pixeles, estás son las las imágenes en escala de grises, las imágenes en blanco y negro y las imágenes a color.
A nivel de programación encontramos que lo que las diferencia es la forma en la que varían los valores de los pixeles y cómo son interpretados por nuestra PC.
En las imágenes a color tenemos un conjunto de ternas de valores para representar el color de cada pixel que será visualizado. Así, en una imagen a color un pixel está formado en realidad por tres valores que representan la intensidad de los colores rojo, verde y azul. Al combinar estos tres valores se puede obtener un color diferente a los tres originales, y haciéndolos variar en su intensidad podemos obtener todo un gran conjunto de colores para dibujar. La forma en la que estos colores son combinados obedece al modelo de color RGB, que es el que se utiliza más frecuentemente con las imágenes digitales.
En las imágenes a color tenemos un conjunto de ternas de valores para representar el color de cada pixel que será visualizado. Así, en una imagen a color un pixel está formado en realidad por tres valores que representan la intensidad de los colores rojo, verde y azul. Al combinar estos tres valores se puede obtener un color diferente a los tres originales, y haciéndolos variar en su intensidad podemos obtener todo un gran conjunto de colores para dibujar. La forma en la que estos colores son combinados obedece al modelo de color RGB, que es el que se utiliza más frecuentemente con las imágenes digitales.
En cambio, con las imágenes en escala de grises cada pixel representa un valor equivalente a una graduación de gris. Tal y como dice el artículo enlazado, "las imágenes representadas de este tipo están compuestas de sombras de grises, que van desde el negro más profundo variando gradualmente en intensidad de grises hasta llegar al blanco."
Una de las características interesantes del procesamiento de imagen es que el posible convertir una imagen a color a una imagen equivalente en escala de grises, aplicando un sencillo algoritmo de procesamiento a sus pixeles. Esto se logra calculando el promedio de las ternas de valores de cada uno de los pixeles de la imagen, y reemplazando los tres valores de cada pixel por el promedio obtenido, de forma que los tres colores tengan un mismo valor de intensidad.
Los pixeles de las imágenes en blanco y negro, son similares a las de escala de grises, con la diferencia de que éstos solo pueden contener uno de dos valores de color posibles, el 255 (blanco) y el 0 (negro).
Otra característica importante, es que si modificamos los valores de intensidad de los pixeles de una imagen, podemos hacer que varíen tanto su brillo como su color. Por ejemplo, para aumentar el brillo de una imagen, podemos sumarle un valor escalar a cada una de las ternas de colores de sus pixeles. Mientras que si deseáramos hacer que variara su color, podemos cambiar la intensidad de una de las tres ternas de color de cada uno de sus pixeles, por ejemplo para hacer más azul una imagen podemos sumarle un escalar a todos los valores de azul de sus pixeles.
Teniendo clara esta diferencia entre los tipos de imagen y lo que es un pixel, ahora podemos pasar a la programación de nuestra aplicación, la cual consistirá en una ventana con un menú de opciones, que permitirá cargar archivos de imágenes de nuestros sistema y gracias a una serie de componentes para interfaces gráficas, darles un pequeño tratamiento a estas imágenes. El programa además tendrá la capacidad para salvar las imágenes una vez que hayan sido modificadas por el usuario.
Una de las características interesantes del procesamiento de imagen es que el posible convertir una imagen a color a una imagen equivalente en escala de grises, aplicando un sencillo algoritmo de procesamiento a sus pixeles. Esto se logra calculando el promedio de las ternas de valores de cada uno de los pixeles de la imagen, y reemplazando los tres valores de cada pixel por el promedio obtenido, de forma que los tres colores tengan un mismo valor de intensidad.
Los pixeles de las imágenes en blanco y negro, son similares a las de escala de grises, con la diferencia de que éstos solo pueden contener uno de dos valores de color posibles, el 255 (blanco) y el 0 (negro).
Otra característica importante, es que si modificamos los valores de intensidad de los pixeles de una imagen, podemos hacer que varíen tanto su brillo como su color. Por ejemplo, para aumentar el brillo de una imagen, podemos sumarle un valor escalar a cada una de las ternas de colores de sus pixeles. Mientras que si deseáramos hacer que variara su color, podemos cambiar la intensidad de una de las tres ternas de color de cada uno de sus pixeles, por ejemplo para hacer más azul una imagen podemos sumarle un escalar a todos los valores de azul de sus pixeles.
Teniendo clara esta diferencia entre los tipos de imagen y lo que es un pixel, ahora podemos pasar a la programación de nuestra aplicación, la cual consistirá en una ventana con un menú de opciones, que permitirá cargar archivos de imágenes de nuestros sistema y gracias a una serie de componentes para interfaces gráficas, darles un pequeño tratamiento a estas imágenes. El programa además tendrá la capacidad para salvar las imágenes una vez que hayan sido modificadas por el usuario.
Manos a la obra
Nuevamente nos basaremos en el esquema MVC (Modelo, Vista, Controlador) para la construcción del software, comenzando con la parte de la vista para nuestra ventana y nuestros paneles de opciones.
Componentes de la interfaz de usuario:
JSlider. Utilizaremos este componente para crear una pequeña paleta de intensidad de colores y otra de intensidad del brillo, de forma que podamos modificar con los tonos de rojo, verde y azul de las imágenes, así como el brillo que estas proyectan.
JPanel. Utilizaremos este componente primero, para crear todos los paneles en los que se cargarán los componentes, y además crearemos una clase llamada PanelDeImagen que extenderá a la clase JPanel, para que funcione como un lienzo sobre el cual dibujaremos las imágenes que carguemos y editemos en la aplicación.
JScrollPane. Ya que el tamaño de las imágenes puede varias respecto del tamaño de la ventana de nuestra aplicación, utilizaremos un panel de tipo JScrollPane, el cual tiene la característica de hacer aparecer barras de desplazamiento en sus bordes cuando su contenido excede a su tamaño en pantalla, de forma que podemos cargar imágenes más grandes que nuestra ventana sin problemas.
JMenuBar, JMenu y JMenuItem. Estos componentes nos permitirán crear una barra de menú en el panel superior de nuestra aplicación, el cual contendrá las opciones relacionadas con los archivos, como son abrir un archivo de imagen, guardar la imagen editada, salir de la aplicación. También permitirán activar los paneles que contendrán las opciones para la edición de nuestras imágenes.
JFileChooser. Este componente adicional, nos auxiliará para hacer más sencilla la selección y el guardado de archivos de imágenes, ya que con los objetos de esta clase podemos llamar una ventana de selección de archivos, que nos permitirá elegir los archivos que editaremos en nuestra aplicación de forma visual.
La Vista
El código para implementar la vista de la aplicación aparecerá a continuación, separando cada una de las clases en un apartado.
Código para la implementación de la ventana de la aplicación:
EditorImg.java
Código para implementar el lienzo sobre el que se dibujará la imagen:
PanelDeImagen.java
La funcionalidad de los objetos de la clase PanelDeImagen consistirá en que recibirán una imagen por medio del método estableceImagen() y la dibujarán en su panel al llamar al método repaint() que heredaron de la clase JPanel. Ya que en esta implementación el panel de las imagenes estará contenida dentro de un objeto JScrollPane, es que además contamos con el método estableceBase(), con el cual podemos guardar una referencia a este panel. Además, debido a que las dimensiones de las imagenes cargada por la aplicación pueden, es que extendimos al método paintComponent(), el cuál además de encargarse de dibujar la imagen dentro del panel, modificará las dimensiones de este y del panel JScrollPane que lo contenga, de forma que se adapten al tamaño de las imágenes y estas puedan ser visualizadas en su totalidad en pantalla.
La siguiente clase implementa la totalidad de los componentes de la interfaz gráfica de la aplicación:
PanelSwing.java
Hasta este punto hemos completado la parte de la Vista de la aplicación, y nos quedan pendientes el Controlador y el Modelo. Sin embargo, para poder apreciar realmente la funcionalidad de las clases anteriores es necesario probarlas, echando a correr el programa, de forma que podamos visualizarlas y obtener un resultado como el siguiente:

En esta captura, podemos apreciar los tres paneles principales de la aplicación, el menú de opciones en la parte superior, el panel central que contiene los paneles de los componentes de las clases JScrollPane y PanelDeImagen, y finalmente el panel inferior, en el cual se desplegarán los componentes visuales para la edición de las imagenes, una vez que el Controlador y el Modelo estén trabajando.
En el siguiente artículo concluiremos con esta implementación.
Hasta pronto.
Componentes de la interfaz de usuario:
JSlider. Utilizaremos este componente para crear una pequeña paleta de intensidad de colores y otra de intensidad del brillo, de forma que podamos modificar con los tonos de rojo, verde y azul de las imágenes, así como el brillo que estas proyectan.
JPanel. Utilizaremos este componente primero, para crear todos los paneles en los que se cargarán los componentes, y además crearemos una clase llamada PanelDeImagen que extenderá a la clase JPanel, para que funcione como un lienzo sobre el cual dibujaremos las imágenes que carguemos y editemos en la aplicación.
JScrollPane. Ya que el tamaño de las imágenes puede varias respecto del tamaño de la ventana de nuestra aplicación, utilizaremos un panel de tipo JScrollPane, el cual tiene la característica de hacer aparecer barras de desplazamiento en sus bordes cuando su contenido excede a su tamaño en pantalla, de forma que podemos cargar imágenes más grandes que nuestra ventana sin problemas.
JMenuBar, JMenu y JMenuItem. Estos componentes nos permitirán crear una barra de menú en el panel superior de nuestra aplicación, el cual contendrá las opciones relacionadas con los archivos, como son abrir un archivo de imagen, guardar la imagen editada, salir de la aplicación. También permitirán activar los paneles que contendrán las opciones para la edición de nuestras imágenes.
JFileChooser. Este componente adicional, nos auxiliará para hacer más sencilla la selección y el guardado de archivos de imágenes, ya que con los objetos de esta clase podemos llamar una ventana de selección de archivos, que nos permitirá elegir los archivos que editaremos en nuestra aplicación de forma visual.
La Vista
El código para implementar la vista de la aplicación aparecerá a continuación, separando cada una de las clases en un apartado.
Código para la implementación de la ventana de la aplicación:
EditorImg.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/**
* Descripción: Esta clase implementa un editor básico de imágenes .jpg y .gif,
* utilizando componentes Swing y una serie de clases para el manejo y procesamiento de imagenes digitales.
* @author Beto González
* @version 1.0
* @category Multimedia
*/
public class EditorImg extends JFrame{
static final long serialVersionUID=10000;
PanelSwing panel;
public static void main(String[] args) {
// TODO Auto-generated method stub
EditorImg editor = new EditorImg();
editor.setBounds(120, 120, 800, 600);
editor.setVisible(true);
editor.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
editor.addWindowListener(new WindowAdapter()
{
public void WindowCloser(WindowEvent e)
{
System.exit(0);
}
});
}
/**
* @Desc Constructor de la clase
*/
EditorImg() {
super("Editor básico de imagenes");
Container contentPane = getContentPane();
panel = new PanelSwing(this);
contentPane.add(panel);
}
}
Código para implementar el lienzo sobre el que se dibujará la imagen:
PanelDeImagen.java
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
/**
* @Desc Clase que extiende a la clase JPanel que se utiliza para crear un panel que permita visualizar imágenes en su interior
* @author Beto González
*
*/
public class PanelDeImagen extends JPanel{
static final long serialVersionUID=10000;
Image img;
Dimension tamaño;
JScrollPane base;
/**
* @Desc Constructor de la clase
*/
PanelDeImagen()
{
setBackground(Color.white);
}
/**
* @Desc Método a través del cual la clase recibe el objeto de la imagen que será visualizada en su interior
* @param i
*/
public void estableceImagen(Image i)
{
img = i;
}
/**
* @Desc Método a través del cual la clase obtiene la referencia hacia el panel en el cual se encuentra contenido.
* @param i
*/
public void estableceBase(JScrollPane contenedor)
{
base = contenedor;
}
/**
* @Desc Método extendido que es llamado cada ves que un objeto de esta clase llama al método repaint(). A este le agregamos
* una funcionalidad adicional que le permite redimencionar el panel que contiene la imagen de acuerdo a las dimensiones de
* ésta
*/
public void paintComponent(Graphics g)
{
super.paintComponent(g);
if (img != null)
{
if(base != null)
{
setSize(new Dimension(base.getWidth()-10,base.getHeight()-10));
setPreferredSize(new Dimension(base.getWidth()-10,base.getHeight()-10));
}
tamaño = new Dimension(getWidth(),getHeight());
int x = tamaño.width - img.getWidth(this);
while (x < 0)
{
tamaño.setSize(tamaño.width+1, tamaño.height);
x = tamaño.width - img.getWidth(this);
}
if(x > 0)
x = (int) x/2;
int y = tamaño.height - img.getHeight(this);
while (y < 0)
{
tamaño.setSize(tamaño.width, tamaño.height+1);
y = tamaño.height - img.getHeight(this);
}
if(y > 0)
y = (int) y/2;
if(!getSize().equals(tamaño))
{
setSize(tamaño);
setPreferredSize(tamaño);
}
g.drawImage(img, x, y, this);
}
}
}
La funcionalidad de los objetos de la clase PanelDeImagen consistirá en que recibirán una imagen por medio del método estableceImagen() y la dibujarán en su panel al llamar al método repaint() que heredaron de la clase JPanel. Ya que en esta implementación el panel de las imagenes estará contenida dentro de un objeto JScrollPane, es que además contamos con el método estableceBase(), con el cual podemos guardar una referencia a este panel. Además, debido a que las dimensiones de las imagenes cargada por la aplicación pueden, es que extendimos al método paintComponent(), el cuál además de encargarse de dibujar la imagen dentro del panel, modificará las dimensiones de este y del panel JScrollPane que lo contenga, de forma que se adapten al tamaño de las imágenes y estas puedan ser visualizadas en su totalidad en pantalla.
La siguiente clase implementa la totalidad de los componentes de la interfaz gráfica de la aplicación:
PanelSwing.java
import java.awt.*;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
* @Desc Clase utilizada para crear los componentes de la interfaz gráfica de la aplicación
* @author Beto González
*
*/
public class PanelSwing extends JPanel {
static final long serialVersionUID = 10000;
String nombreArchivo, ruta;
JMenuBar barraMenu;
JMenu menuArchivo, menuEdicion;
JMenuItem abrir, guardar, salir, brillo, color, escala;
JScrollPane panelDespl;
JPanel panelBajo, panelBrillo, panelColor, panelVacio;
int altura = 80;
Image imagen;
Image imgAux;
EditorImg editor;
PanelDeImagen lienzo;
JSlider jslBrillo, jslRojo, jslVerde, jslAzul;
JLabel lblRojo, lblVerde, lblAzul;
CardLayout esqueInf;
/**
* @Desc Constructor de la clase
* @param editor
*/
PanelSwing(EditorImg editor)
{
this.editor = editor;
this.setLayout(new BorderLayout());
barraMenu = new JMenuBar();
FlowLayout f = new FlowLayout();
f.setAlignment(FlowLayout.LEFT);
barraMenu.setLayout(f);
menuArchivo = new JMenu("Archivo");
menuEdicion = new JMenu("Edición");
abrir = menuArchivo.add("Abrir");
guardar = menuArchivo.add("Guardar");
guardar.setEnabled(false);
menuArchivo.addSeparator();
salir = menuArchivo.add("Salir");
brillo = menuEdicion.add("Ajustar Brillo");
color = menuEdicion.add("Ajustar Colores");
escala = menuEdicion.add("Escala de Grises");
brillo.setEnabled(false);
color.setEnabled(false);
escala.setEnabled(false);
barraMenu.add(menuArchivo);
barraMenu.add(menuEdicion);
this.add("North",barraMenu); //Agregamos la barra de menu
creapanelCentral(); //Creamos el panel en el que se mostrara la imagen seleccionada
creapanelBajo(); //Creamos el panel en el que se mostraran los controles para manipular la imagen
}
/**
* @Desc Método que crea el contenido del panel central de la ventana
*/
private void creapanelCentral()
{
lienzo = new PanelDeImagen();
panelDespl = new JScrollPane(lienzo);
lienzo.estableceBase(panelDespl);
add("Center",panelDespl);
}
/**
* @Desc Método que crea el contenido del panel inferior de la ventana
*/
private void creapanelBajo()
{
panelBajo = new JPanel();
esqueInf = new CardLayout();
panelBajo.setLayout(esqueInf);
panelBajo.setPreferredSize(new Dimension(this.getWidth(),altura));
jslBrillo = new JSlider(SwingConstants.HORIZONTAL,0,100,0);
jslBrillo.setPaintTicks(true);
jslBrillo.setPaintLabels(true);
jslBrillo.setMajorTickSpacing(10);
jslBrillo.setMinorTickSpacing(5);
panelColor = new JPanel();
panelVacio = new JPanel();
panelBrillo = new JPanel(new BorderLayout());
panelBrillo.add("Center", new JLabel("Puedes ajustar el brillo de la imagen",JLabel.CENTER));
panelBrillo.add("South",jslBrillo);
panelBajo.add("carta1", panelVacio);
panelBajo.add("carta2", panelBrillo);
creaPaletas();
esqueInf.show(panelBajo, "carta1");
this.add("South",panelBajo);
}
/**
* @Desc Método que crea el contenido del panel inferior de la ventana
*/
private void creaPaletas()
{
GridBagLayout gridbag = new GridBagLayout();
GridBagConstraints constrain = new GridBagConstraints();
panelColor.setLayout(gridbag);
lblRojo = new JLabel("Rojo");
lblVerde = new JLabel("Verde");
lblAzul = new JLabel("Azul");
constrain.gridx = 0; constrain.gridy = 0;
constrain.gridheight = 1; constrain.gridwidth = 2;
gridbag.setConstraints(lblRojo, constrain);
panelColor.add(lblRojo);
constrain.gridx = 2; constrain.gridy = 0;
gridbag.setConstraints(lblVerde, constrain);
panelColor.add(lblVerde);
constrain.gridx = 4; constrain.gridy = 0;
gridbag.setConstraints(lblAzul, constrain);
panelColor.add(lblAzul);
jslRojo = new JSlider(SwingConstants.HORIZONTAL,0,50,0);
jslVerde = new JSlider(SwingConstants.HORIZONTAL,0,50,0);
jslAzul = new JSlider(SwingConstants.HORIZONTAL,0,50,0);
constrain.gridx = 0; constrain.gridy = 1;
constrain.gridheight = 1; constrain.gridwidth = 2;
gridbag.setConstraints(jslRojo, constrain);
panelColor.add(jslRojo);
constrain.gridx = 2; constrain.gridy = 1;
gridbag.setConstraints(jslVerde, constrain);
panelColor.add(jslVerde);
constrain.gridx = 4; constrain.gridy = 1;
gridbag.setConstraints(jslAzul, constrain);
panelColor.add(jslAzul);
panelBajo.add("carta3", panelColor);
}
}
Hasta este punto hemos completado la parte de la Vista de la aplicación, y nos quedan pendientes el Controlador y el Modelo. Sin embargo, para poder apreciar realmente la funcionalidad de las clases anteriores es necesario probarlas, echando a correr el programa, de forma que podamos visualizarlas y obtener un resultado como el siguiente:

En esta captura, podemos apreciar los tres paneles principales de la aplicación, el menú de opciones en la parte superior, el panel central que contiene los paneles de los componentes de las clases JScrollPane y PanelDeImagen, y finalmente el panel inferior, en el cual se desplegarán los componentes visuales para la edición de las imagenes, una vez que el Controlador y el Modelo estén trabajando.
En el siguiente artículo concluiremos con esta implementación.
Hasta pronto.
Suscribirse a:
Entradas (Atom)

















