Empezando con Java 2D

La API Java 2D es una API central de la plataforma Java 1.2 (consulte Recursos para obtener una variedad de información sobre la API y sus implementaciones). Las implementaciones de la API están disponibles como parte de Java Foundation Classes (JFC) en las versiones beta actuales de Sun JDK para Windows NT / 95 y Solaris. A medida que se finalice Java 1.2, Java 2D debería estar disponible en más plataformas.

Tenga en cuenta que, aunque Java 2D se ha desarrollado de forma algo independiente de las otras partes del JFC, no obstante es una parte central del 1.2 AWT. Haremos la distinción y señalaremos características específicas de 2D para su discusión, pero debe recordar que esta funcionalidad es tan fundamental para los gráficos 1.2 como el antiguo soporte de AWT 1.0 y 1.1.

Java 2D amplía los mecanismos AWT anteriores para dibujar gráficos 2D, manipular texto y fuentes, cargar y usar imágenes y definir y tratar colores y espacios de color. Exploraremos estos nuevos mecanismos en esta columna y en futuras.

Una nota sobre nomenclatura y convenciones

Para esta columna, mi plataforma de desarrollo principal será una PC con Windows 95 o Windows NT. Espero ofrecer otros consejos y trucos específicos de la plataforma siempre que sea posible, pero me centraré en Windows, ya que ahí es donde pasaré la mayor parte del tiempo.

Cuando escribo el nombre de un método, siempre debe tener el formato methodname(). Los paréntesis finales están destinados a diferenciar esto como método. El método puede tomar parámetros o no. En la práctica, el contexto siempre debe dejar esto claro.

Se proporcionarán listados de código fuente con números de línea incluidos. Planeo usar los números de línea para hacer referencias cruzadas entre el texto del artículo y las listas de códigos según corresponda. Esto también debería facilitarle mucho las anotaciones en la columna, en caso de que opte por imprimir una copia. Sin embargo, tenga en cuenta que los archivos fuente vinculados desde la columna serán archivos * .java normales (sin números de línea) para que pueda descargarlos y desarrollarlos con ellos.

Debido a que escribiré sobre muchas de las API de medios y comunicaciones en los próximos meses, quiero asegurarme de que todo el código de muestra tenga sentido en su conjunto, así como en sus partes individuales. Intentaré nombrar constantemente mis ejemplos y colocarlos en paquetes sensatos.

La parte superior de la jerarquía de mi paquete será:

com.javaworld.media 

Cada API o tema sobre el que escribo tendrá al menos un subpaquete en este nivel superior. Por ejemplo, todo el código de este artículo de Java 2D estará en:

com.javaworld.media.j2d 

Entonces, para invocar la primera aplicación de ejemplo en Java 2D, debe descargar el código, ponerlo en su classpath y luego usar:

java com.javaworld.media.j2d.Example01 

(Si el espacio de nombres es demasiado largo para su gusto o por alguna otra razón desea usar el código de ejemplo sin tener que usar el nombre completo, simplemente comente la línea del paquete al comienzo de cada archivo de código fuente).

Generaré un archivo Java Archive (jar) para el código de ejemplo y los archivos de clase de cada artículo. Este archivo estará disponible en los Recursos de cada columna, en caso de que desee descargarlo y ejecutar los ejemplos desde el archivo.

También mantendré un archivo jar actualizado que contiene todo el código y las clases de mis columnas de Programación de medios actuales y anteriores . Este archivo jar que lo abarca todo estará disponible en mi sitio web personal.

Un último punto sobre los ejemplos: he optado por hacer cada ejemplo, a menos que indique específicamente lo contrario, una aplicación o subprograma independiente. Esto dará lugar a alguna repetición de código de vez en cuando, pero creo que es mejor preservar la integridad de cada ejemplo individual.

Ya basta de convenciones. ¡Comencemos a programar con Java 2D!

Graphics2D: una mejor clase de gráficos

La clase central dentro de la API Java 2D es la java.awt.Graphics2Dclase abstracta, que subclases java.awt.Graphicspara extender la funcionalidad de renderizado 2D. Graphics2Dagrega un soporte más uniforme para manipulaciones de una variedad de formas, haciendo que el texto, las líneas y todo tipo de otras formas bidimensionales sean comparables en sus capacidades y utilidad.

Comencemos con un ejemplo simple que muestra cómo obtiene y usa una Graphics2dreferencia.

001 paquete com.javaworld.media.j2d; 002003 importar java.awt. *; 004 import java.awt.event. *; 005 006 clase pública Example01 extiende el marco {007 / ** 008 * Crea una instancia de un objeto Example01. 009 ** / 010 public static void main (String args []) {011 new Example01 (); 012} 013 014 / ** 015 * Nuestro constructor Example01 establece el tamaño del marco, agrega los componentes visuales 016 * y luego los hace visibles para el usuario. 017 * Utiliza una clase adaptadora para lidiar con el 018 * usuario que cierra el marco. 019 ** / 020 public Example01 () {021 // Titula nuestro marco. 022 super ("Java 2D Example01"); 023024 // Establece el tamaño del marco. 025 setSize (400,300); 026 027 // Necesitamos activar la visibilidad de nuestro marco 028 // estableciendo el parámetro Visible en verdadero. 029 setVisible (verdadero); 030 031 // Ahora,queremos estar seguros de que disponemos correctamente de los recursos 032 // que este marco está usando cuando la ventana está cerrada. Usamos 033 // un adaptador de clase interno anónimo para esto. 034 addWindowListener (new WindowAdapter () 035 {public void windowClosing (WindowEvent e) 036 {dispose (); System.exit (0);} 037} 038); 039} 040 041 / ** 042 * El método de pintura proporciona la verdadera magia. Aquí 043 * convertimos el objeto Graphics a Graphics2D para ilustrar 044 * que podemos usar las mismas capacidades gráficas antiguas con 045 * Graphics2D que estamos acostumbrados a usar con Graphics. 046 ** / 047 public void paint (Gráficos g) {048 // Así es como solíamos dibujar un cuadrado con un ancho 049 // de 200, una altura de 200 y comenzando en x = 50, y = 50. 050 g.setColor (Color.red); 051 g.drawRect (50,50,200,200); 052053 // Vamos 's establece el Color en azul y luego usa el objeto Graphics2D 054 // para dibujar un rectángulo, desplazado del cuadrado. 055 // Hasta ahora, no hemos hecho nada con Graphics2D que 056 // no pudiéramos hacer también con Graphics. (En realidad, estamos 057 // usando métodos Graphics2D heredados de Graphics.) 058 Graphics2D g2d = (Graphics2D) g; 059 g2d.setColor (Color.blue); 060 g2d.drawRect (75,75,300,200); 061} 062}

Cuando ejecute Example01, debería ver un cuadrado rojo y un rectángulo azul, como se muestra en la siguiente figura. Tenga en cuenta que existe un problema de rendimiento conocido con la versión de Windows NT / 95 de JDK 1.2 Beta 3 (la versión 1.2 más actual a partir de esta columna). Si este ejemplo es terriblemente lento en su sistema, es posible que deba solucionar el error como se documenta en JavaWorld Java Tip 55 (consulte Recursos a continuación para obtener este consejo).

Tenga en cuenta que así como no crea una instancia de un Graphicsobjeto directamente , tampoco crea una instancia de un Graphics2Dobjeto. Más bien, el tiempo de ejecución de Java construye un objeto de representación y lo pasa a paint()(línea 047 en la lista de código Example01), y en las plataformas Java 1.2 y posteriores, este objeto también implementa la Graphics2Dclase abstracta.

Hasta ahora no hemos hecho nada especial con nuestras capacidades de gráficos 2D. Agreguemos algo de código al final del paint()método de nuestro ejemplo anterior y agreguemos varias características nuevas en Java 2D (Example02):

001 / ** 002 * Aquí usamos nuevas características de la API de Java 2D, como transformaciones afines 003 * y objetos Shape (en este caso, un 004 * genérico, GeneralPath). 005 ** / 006 pintura vacía pública (Gráficos g) {007 g.setColor (Color.red); 008 g.drawRect (50,50,200,200); 009010 Graphics2D g2d = (Graphics2D) g; 011 g2d.setColor (Color.blue); 012 g2d.drawRect (75,75,300,200); 013 014 // Ahora, dibujemos otro rectángulo, pero esta vez, 015 // usemos un GeneralPath para especificarlo segmento por segmento. 016 // Además, vamos a trasladar y rotar este 017 // rectángulo en relación con el espacio del dispositivo (y por lo tanto, a 018 // los dos primeros cuadriláteros) usando AffineTransform. 019 // También cambiaremos su color. 020 Ruta de acceso general = nueva Ruta general (Ruta general.EVEN_ODD); 021 path.moveTo (0.0f, 0.0f); 022 path.lineTo (0.0f, 125.0f); 023 path.lineTo (225.0f, 125.0f);024 path.lineTo (225.0f, 0.0f); 025 path.closePath (); 026 027 AffineTransform en = nuevo AffineTransform (); 028 en.setToRotation (-Math.PI / 8.0); 029 g2d.transform (en); 030 at.setToTranslation (50.0f, 200.0f); 031 g2d.transform (en); 032033 g2d.setColor (Color.green); 034 g2d.fill (ruta); 035}

Tenga en cuenta que, dado que GeneralPathse encuentra en el java.awt.geompaquete, debemos asegurarnos de agregar una línea de importación también:

importar java.awt.geom. *; 

La salida de Example02 se muestra en la siguiente figura.

Java 2D permite la especificación de formas arbitrarias utilizando la java.awt.Shapeinterfaz. Una variedad de formas predeterminadas, como rectángulos, polígonos, líneas 2D, etc., implementan esta interfaz. Uno de los más interesantes en términos de flexibilidad es java.awt.geom.GeneralPath.

GeneralPaths le permiten describir una ruta con un número arbitrario de aristas y una forma potencialmente extremadamente compleja. En Example02, hemos creado un rectángulo (líneas 020-025), pero con la misma facilidad podríamos haber agregado otro lado o lados para hacer un pentágono, o heptágono, o algún otro polígono de múltiples lados. También tenga en cuenta que, a diferencia del Graphicscódigo estándar , Java 2D nos permite especificar coordenadas utilizando números de punto flotante en lugar de enteros. Vendedores de CAD del mundo, ¡regocíjense! De hecho, Java 2D soporta integer, doubley floatingla aritmética en muchos lugares.

Probablemente también hayas notado que cuando creamos la ruta pasamos un parámetro GeneralPath.EVEN_ODD, al constructor (línea 020). Este parámetro representa una regla sinuosa que le dice al renderizador cómo determinar el interior de la forma especificada por nuestra ruta. Consulte la documentación javadoc de Java 2D a la que se hace referencia en Recursos para obtener más información sobre las reglas de bobinado de Java 2D.

La otra gran innovación en Example02 gira en torno al uso de a java.awt.geom.AffineTransforms (líneas 027-031). Dejaré los detalles de tales transformaciones al lector (vea Recursos para artículos que discuten esto con mayor detalle), pero es suficiente decir que le AffineTransformpermiten operar en cualquier gráfico Java 2D para traducirlo (moverlo), rotarlo , escalarlo, cortarlo o realizar combinaciones de estas manipulaciones.

The key to AffineTransform lies in the concept of Device Space and User Space. Device Space is that area into which the graphics will be rendered on the screen. This is analogous to the coordinates that are used when one creates regular AWT-style Graphics-based 2D graphics. User Space, however, is a translatable, rotatable coordinate system that may be operated on by one or more AffineTransforms.

Device Space and User Space coordinate systems initially overlap, with the origin at the upper left of the rendering surface (here, a Frame). The positive x axis moves right from the origin, while the positive y axis moves down.

After the first transformation in Example02 (lines 028 and 029), the User Space coordinate system has been rotated 22.5 degrees counterclockwise relative to the Device Space. Both still share the same origin. (Note that rotations are specified in radians, with -PI/8 radians equaling -22.5 degrees, or 22.5 degrees CCW.) If we were to stop here and draw the rectangle, it would be rotated mostly out of our field of view in the application Frame.

We next apply a second transformation (lines 030 and 031), this one a translation, after the rotation is complete. This moves the User Space coordinate system relative to the Device Space, shifting it down 200.0 (float) units and right 50.0 (float) units.

When we fill in the green rectangle, it is translated and rotated relative to the Device Space.

Of Bezier and higher-ordered curves

Now that we have examined how transforms can be used to manipulate graphical objects, let's reexamine how we build complex and interesting arbitrary shapes.

Curves are used throughout mathematics and computer graphics to approximate complex shapes using a finite, well-defined (and ideally small) number of mathematical points. Whereas the standard AWT did not directly support drawing with arbitrary curves in the past (Java 1.0 or 1.1 platforms), Java 2D adds built-in support for first-, second-, and third-order curves. You can draw curves with two end points and zero, one, or two control points. Java 2D computes first- and second-order curves using linear and quadratic formulas and cubic, or third-order, curves using Bezier curves.

(Las curvas de Bezier son un tipo de curva polinomial paramétrica que tiene algunas propiedades muy deseables relacionadas con el cálculo de superficies y curvas cerradas. Se utilizan en numerosas aplicaciones gráficas. Consulte los Recursos para obtener más información sobre el uso de polinomios paramétricos y curvas de Bezier. en gráficos por computadora.) Los GeneralPathmétodos que dibujan cada una de estas curvas son:

  • lineTo() para segmentos rectos (especificar solo puntos finales)
  • quadTo() para curvas cuadráticas (especificar un punto de control)
  • curveTo() para curvas de tercer orden (especifique dos puntos de control, dibujados usando una curva de Bézier cúbica)