6. Extensiones

A partir de JDK 1.2 se incorpora un mecanismo de extensiones que permite añadir nuevas funcionalidades al núcleo de la plataforma Java. Existen extensiones desarrolladas por Sun, como Java 3D y Javamail, pero además cualquier usuario puede desarrollar sus propias extensiones.

6.1. Mecanismo de extensiones de Java

El mecanismo de extensiones proporciona escalabilidad a Java, permitiendo hacer que nuestra propia API esté disponible para cualquier aplicación.

Las extensiones son grupos de paquetes y clases que añaden funcionalidades a la plataforma Java, y que serán incluidas mediante el mecanismo de extensiones de Java. Este mecanismo permite que las extensiones definidas sean accesibles por cualquier aplicación sin necesidad de establecer el CLASSPATH, como si se tratase de las clases del núcleo de Java. De hecho, estas extensiones lo que hacen es extender la API del núcleo.

Gracias a este mecanismo podremos obtener e instalar extensiones que amplíen la API del núcleo de la plataforma Java, bien sean extensiones desarrolladas por Sun o por terceros, o bien crear nuestras propias extensiones. Esto nos permitirá ampliar la API según nuestras necesidades, y además facilitará la distribución de nuestras propias librerías de clases, que cualquier usuario podrá instalar y utilizar fácilmente sin preocuparse del lugar donde ubicar las clases y de establecer las variables de entorno necesarias.

Para la creación de nuestras propias extensiones necesitaremos empaquetar todas las clases de nuestra extensión en un fichero JAR, por lo que a continuación veremos como trabajar con este tipo de ficheros.

6.2. Ficheros JAR

A parte de permitirnos crear extensiones de Java, los ficheros JAR tienen varias ventajas:

Los ficheros JAR utilizan el formato ZIP, por lo que podremos abrirlos con cualquier aplicación que trabaje con ficheros ZIP. De todas formas Java incorpora una herramienta para trabajar con estos ficheros JAR, llamada jar. Con ella podremos empaquetar o extraer el contenido de un fichero JAR, y realizar otras operaciones propias de este formato. Para empaquetar una serie de ficheros de entrada en un JAR haremos lo siguiente:

jar cvf fichero_jar ficheros_de_entrada
Los parámetros utilizados significan los siguiente:
 
c       Indica que la acción a realizar es comprimir (empaquetar) una serie de ficheros en un JAR.
v Verbose. Provoca que muestre por pantalla las operaciones que vaya realizando.
f Indica que la salida debe ir a un fichero en lugar de a la salida estándar. Este fichero es el que especificamos como parámetro fichero_jar.

Ahora podemos visualizar el contenido de nuestro fichero JAR cambiando el parámetro de la acción a realizar por t.

jar tf fichero_jar
t       Indica que la acción a realizar es comprobar (test) el contenido del JAR, mostrando todos los ficheros que contiene.

Si hacemos esto veremos que además de los ficheros especificados dentro del JAR de ha incluido el fichero META-INF/MANIFEST.MF. Este fichero de manifiesto está incluido en todos los JAR, y contiene información sobre su contenido.

Podemos ahora extraer todo el contenido del JAR cambiando el comando de acción por x.

jar xvf fichero_jar
Indica que la acción a realizar es extraer el contenido del JAR.

Con esto se extraerán todos los ficheros, incluido el manifiesto, pudiendo así editar dicho fichero con cualquier editor ASCII. En este caso veremos el manifiesto por defecto que se incluye en los ficheros JAR. Podemos añadir líneas al manifiesto , para ello deberemos crear un fichero con la información que queramos añadir, e incluirlo al crear el JAR para que a la líneas por defecto, añada los datos que nosotros hayamos incluido. Esto lo haremos con:

jar cmf fichero_manifiesto fichero_jar ficheros_de_entrada
Para trabajar con manifiestos tenemos los siguientes parámetros:
 
M  Indica que no se debe incluir el manifiesto por defecto.
m Indica que se deben añadir las líneas del fichero manifiesto que especifique el usuario.

Una vez tengamos nuestras clases empaquetadas en un fichero JAR, para utilizarlas bastará con tener incluido el fichero JAR en el CLASSPATH. De esta forma las clases serás accesibles de la misma forma que si estuviesen directamente en el CLASSPATH sin empaquetar. Para añadir un fichero JAR al CLASSPATH tendremos que incluir el fichero JAR con su ruta absoluta en esta variable de entorno:

SET CLASSPATH=%CLASSPATH%;c:\ruta\fichero.jar

También podemos indicar el CLASSPATH del fichero JAR directamente en la línea de comandos al ejecutar nuestra aplicación ClasePrincipal:

java -cp c:\ruta\fichero.jar ClasePrincipal

En el caso de que empaquetemos una aplicación, podremos definir una clase principal de forma que el fichero JAR sea autoejecutable simplemente escribiendo:

java -jar fichero_jar

Además, en Windows al hacer doble click sobre este fichero JAR también se ejecutará, ya que este tipo de ficheros estarán asociados con el intérprete Java. Para hacer este JAR ejecutable deberemos definir cuál es la clase principal que se va a ejecutar, cosa que haremos en el fichero manifiesto. Para ello deberemos incluir un fichero manifiesto que añada la línea:

Main-Class: MiClasePrincipal

En el caso de los Applets, para cargar un Applet desde un JAR deberemos especificar el fichero JAR en el atributo ARCHIVE del tag APPLET, como se muestra a continuación:

<applet code=MiApplet.class
		archive="AppletJAR.jar">
</applet>

6.3. Creación de nuestras propias extensiones

Lo primero que deberemos hacer cuando queramos crear un extensión es seleccionar todos los paquetes y clases que formarán parte de la API de nuestra extensión y empaquetarlos en un único fichero JAR.

Una vez tengamos el fichero JAR, deberemos añadirlo como extensión, teniendo dos alternativas para ello:
 

En el caso de las extensiones instaladas, deberemos copiar el JAR dentro del directorio del JRE (Java Runtime Environment), en el subdirectorio lib/ext. JDK incluye JRE como un subdirectorio suyo, por lo que en este caso la ruta completa donde habrá que incluir los JAR será:

{java.home}/jre/lib/ext/

Con esto la extensión se habrá incluido en la plataforma Java instalada en nuestra máquina, permitiendo que cualquier programa Java que se ejecute en ella pueda utilizarla directamente.

Sin embargo, pensemos en el caso de un Applet que necesita utilizar una determinada extensión. En este caso no podemos suponer que cualquiera que visite dicho Applet va a tener instalada la extensión necesaria. En este caso será más apropiado utilizar extensiones descargadas. Para utilizar una extensión descargada, deberemos incluir en el manifiesto del JAR que contiene el Applet la siguiente línea:

Class-Path: fichero_jar

De esta forma sabrá que para ejecutar el Applet deberá descargar la extensión especificada en esta línea.

Para la carga de clases, Java buscará en el siguiente orden:

  1. Clases principales de Java (rt.jar, i18n.jar).
  2. Extensiones instaladas
  3. Classpath

6.4. Extensiones existentes

En la página de Sun podemos encontrar una larga lista de extensiones de Java desarrolladas por ellos, que podremos descargar e instalar ampliando de esta manera la API de Java. A continuación veremos las principales extensiones disponibles y una descripción de cada una de ellas:

6.5. Java 3D y JAI

6.5.1. Java 3D

Introducción

Java 3D es una extensión de Java utilizada para desarrollar aplicaciones que incorporen gráficos tridimensionales . Se construyen con ella objetos geométricos y se colocan en un universo virtual, que luego es renderizado.

Los objetos de un programa Java 3D se colocan en una estructura de tipo árbol, llamada grafo de escena, que define un universo virtual:

Figura 1. Grafo de escena en Java 3D

El grafo tiene como raíz un universo virtual (VirtualUniverse o SimpleUniverse) . El Locale define un sistema de coordenadas. De este objeto parten nodos de grupo ( BranchGroup ), que nos definen un subgrafo. Los nodos de transformación (TransformGroup) permiten aplicar transformaciones ( Transform3D ) a sus hijos. Los nodos hojas serán los objetos 3D. Podrán ser formas geométricas (Shape3D ), puntos de vista de la cámara, etc. Podemos definir tanto la geometría como la apariencia de los objetos. Los nodos de comportamiento (Behavior ) pueden o no aparecer, y permiten la interacción y el movimiento de los objetos. La plataforma de vista ( ViewPlatform ) permite tratar la posición del observador.

 

Pasos para construir un mundo 3D

A la hora de generar nuestro mundo virtual 3D, el resultado será un conjunto de elementos  interconectados formando el mundo. El proceso de creación del mundo consta de varios pasos. Si el universo no es demasiado complejo podemos utilizar un objeto SimpleUniverse, que simplifica bastante la tarea de definir el mundo virtual, aunque no permite tener varias vistas simultáneas.  Vemos ejemplos de construcción de cada universo:

Universo Complejo

Código       Applet

Universo Simple

Código       Applet

 

Objetos primitivos

Java 3D incorpora una serie de objetos primitivos, descendientes de la clase objeto geométrico genérica (Shape3D). Dichos elementos son:


 
La caja (Box): define un cubo indicando las longitudes de las aristas X, Y y Z: 
Box(float dimX, float dimY, float dimZ)

 
El cono (Cone): define un cono dados un radio (radio de la base) y una altura 
Cone(float radio, float altura)

 
El cilindro (Cylinder): define un cilindro, dados un radio y una altura 
Cylinder(float radio, float altura)

 
La esfera (Sphere): define una esfera, dado un radio 
Sphere(float radio)

 
El cubo de color (ColorCube): define un cubo con un color en cada cara. Se pasa como parámetro una escala con respecto a un tamaño de arista de 2 metros. 
ColorCube(double escala)

Se pueden construir objetos complejos combinando estos objetos simples. Además, Java 3D permite añadir texto 2D, texto 3D, polígonos y otras figuras.
 

Transformaciones en objetos

Un sistema de coordenadas en Java 3D es como sigue: el eje X es positivo hacia la derecha, el eje Y es positivo hacia arriba y el eje Z es positivo hacia el observador. Las distancias se miden en metros y los ángulos en radianes .

Los objetos Transform3D representan transformaciones geométricas 3D, que pueden ser traslaciones, rotaciones, escalados o una combinación de estos. Internamente, es una matriz de 4x4 doubles.  Se tienen, entre otros, los métodos:
void rotX(double angulo)
void rotY(double angulo)
void rotZ(double angulo)
Especifican una rotación en sentido antihorario sobre el eje que se indica (ángulo en radianes entre 0 y 2 PI)
void setTranslation(
        Vector3f traslacion)
Indica una traslación , indicando directamente el vector de traslación (componentes X, Y y Z)
void setScale(double escala)
void setScale(Vector3d escala)
Se emplean para escalados (bien uniforme, bien definiendo una escala para cada dimensión)

Mediante el método mul(...) de Transform3D podremos formar transformaciones complejas multiplicando las simples.

Los objetos TransformGroup son contenedores de transformaciones geométricas, de modo que a todos los nodos contenidos en un nodo TransformGroup se les aplica dicha transformación. Podemos indicar qué transformación aplicar con el método:

void setTransform(Transform3D trans)
El siguiente ejemplo muestra un cubo rotado en X e Y, y desplazado en X:

Código       Applet

Otras características

Java 3D permite añadir a nuestros mundos virtuales otras posibilidades, que comentamos aquí brevemente:

Figura 2. Ejemplo de aplicación de Sun


En este ejemplo podéis ver algunas de las características de Java 3D. Es una aplicación desarrollada por Sun, que permite navegar por mundos 3D: 

Podéis descargar aquí la aplicación y aquí el código fuente. 
 
 

6.5.2. JAI (Java Advanced Imaging)

Introducción

JAI (Java Advanced Imaging) es una librería estándar de Java para la manipulación y el tratamiento de imágenes. Extiende la plataforma Java permitiendo añadir procesamiento de imágenes sofisticado y de alto nivel. Puede encontrarse información actualizada sobre JAI en:

http://www.java.sun.com/products/java-media/jai/


JAI pretende ser una librería que soporte casi cualquier tipo de procesamiento de imágenes. Ofrece algunas ventajas con respecto a otras librerías de procesamiento de imágenes:

En JAI se unifican los conceptos de imagen y operador, haciéndolos subclases de una misma clase padre. Un objeto de tipo operador tomará una o más imágenes fuente, y otros parámetros, y generará un resultado. Este mismo operador puede convertirse en una fuente para otro operador, formando así un grafo del procesamiento de imágenes.

Ejemplo: El siguiente ejemplo lee una imagen, la escala al doble de su tamaño y muestra el resultado.

 

Tratamiento de imágenes

JAI soporta 3 modelos de imágenes:

 

Programar con JAI

Una operación de procesado de imagen en JAI puede resumirse en los pasos:

Obtención de imágenes

Veremos cómo leer imágenes de ficheros. Tenemos 3 alternativas:

 

El grafo de procesamiento

Veamos ahora cómo componer grafos de procesamiento. Los nodos son las imágenes / operadores que intervienen en el proceso. Se tienen 2 tipos de grafos:

Los grafos renderizados son la forma más simple de renderizado. Son necesarios cuando se requiere trabajar directamente con los píxeles. Se componen de objetos Rendered , que normalmente son instancias de la clase RenderedOp . Las imágenes fuente son objetos RenderedImage .

Ejemplo: El siguiente ejemplo crea dos imágenes constantes y las suma:

ParameterBlock param1 = new ParameterBlock();
ParameterBlock param2 = new ParameterBlock();
...
RenderedOp im0 = JAI.create("constant", param1);
RenderedOp im1 = JAI.create("constant", param2);
RenderedOp im2 = JAI.create("add", im0, im1);

Los grafos renderizables se evalúan más tarde, cuando exista alguna petición de renderizado. Se construyen con nodos RenderableImage, que normalmente son instancias de la clase RenderableOp .

Ejemplo: El siguiente ejemplo lee una imagen, invierte los valores de los píxeles y añade un valor constante a los mismos.

RenderableImage ren = JAI.createRenderable("renderable", pb);
// Operación de inversión
ParameterBlock pb1 = new ParameterBlock();
pb1.addSource(ren);
RenderableOp Op1 = JAI.createRenderable("invert", pb1);
// Operación de suma de constante
ParameterBlock pb2 = new ParameterBlock();
pb2.addSource(Op1);        
pb2.add(2.0f);           
RenderableOp Op2 = JAI.createRenderable("addconst", pb2);

 

Operaciones

Operadores JAI

A continuación se muestran algunos de los operadores de JAI para procesamiento de imágenes, separados en categorías:

1. Operadores de punto

Estos operadores transforman una imagen de entrada en una de salida de forma que cada píxel de salida depende únicamente del correspondiente píxel de entrada. Se tienen, entre muchas otras, las operaciones:
Add, Substract, Multiply, Divide
Suman / restan / multiplican / dividen dos imágenes 
And, Or, Not, Xor
Realizan operaciones lógicas con imágenes
Constant
Forma imágenes con valores constantes

2. Operadores de área

Estos operadores transforman una imagen de entrada en una de salida de forma que cada píxel de salida depende del correspondiente píxel de entrada y una región de píxeles a su alrededor. Se tienen, entre otras, las operaciones:
BoxFilter
Aplica un filtro de media con los puntos de una vecindad
Convolve
Aplica una convolución con un núcleo determinado 
MedianFilter
Aplica un filtro de mediana con los puntos de una vecindad

3. Operadores geométricos

Estos operadores permiten modificar la orientación, tamaño y forma de una imagen. Se tienen, entre otras, las operaciones:
Rotate, Scale, Translate
Aplican rotaciones / escalados / traslaciones a las imágenes

4. Operadores de ficheros

Estos operadores se usan para leer o escribir ficheros de imágenes. Se tienen, entre muchas otras, las operaciones:
Stream
Lee una imagen a partir de un  SeekableStream .
FileLoad, FileStore
Para leer / guardar una imagen de un fichero
GIF, JPEG, PNG, BMP
Para leer imágenes GIF / JPEG / PNG / BMP

5. Otros operadores

Se tienen otros muchos operadores además de los vistos: operadores para convertir entre dominio espacial y frecuencial (mediante transformadas de Fourier), operadores estadísticos (para histogramas, medias), etc.

 

Invocar operaciones

Para poder llamar a los operadores:

createRenderable (String name, 
                      ParameterBlock pBlock)
createRenderableCollection (String name, 
                      ParameterBlock pBlock)
create (String name, ParameterBlock pBlock, 
             RenderingHints hints)
create (String name, RenderedImage renderedImage, 
             Object param, Object param2 ...)
Podremos invocar así a las operaciones anteriores, por ejemplo:
RenderableOp im = JAI.createRenderable("nombre",paramBlock);
RenderedOp im2 = JAI.create ("nombre",imagen param1,param2)

 

Bloques de parámetros

Los parámetros que necesitan los operadores se pasan mediante objetos ParameterBlock o ParameterBlockJAI . El bloque de parámetros contiene la imagen o imágenes fuente de la operación, y un conjunto de parámetros utilizados por dicha operación, que son objetos.

Para añadir una imagen fuente a un bloque de parámetros se emplea el método:
 

addSource()
Para añadir el resto de parámetros, debemos distinguir entre las clases ParameterBlock y ParameterBlockJAI:
add()
set(char c,
    String param)
set(double f,
    String param)
set(int i,
    String param)
set(Object obj,
    String param)
Un ejemplo: aplicamos una rotación que requiere 4 parámetros: la X origen, Y origen, ángulo de rotación e interpolación. Los dos primeros valores tienen por defecto 0.0F, y no los vamos a especificar.
interp = Interpolation.create(Interpolation.INTERP_NEAREST);
ParameterBlockJAI pb = new ParameterBlockJAI();
pb.addSource(im);
pb.set(1.2F, “angle”);
pb.set(interp, “interpolation”);