graficos - java 2d ejemplos
¿Es esta la forma correcta de usar la API de gráficos 2D de Java? (3)
Estoy creando un front-end gráfico para una simulación JBox2D. La simulación se ejecuta incrementalmente, y entre las actualizaciones, se supone que los contenidos de la simulación se dibujarán. Similar a un juego, excepto sin entrada.
Solo necesito primitivas geométricas para dibujar una simulación JBox2D. Esta API parecía la opción más simple, pero su diseño es un poco confuso.
Actualmente tengo una clase llamada Window
extiende JFrame
, que contiene como miembro otra clase llamada Renderer
. La clase Window
solo se inicializa a sí misma y proporciona un método updateDisplay()
(llamado por el bucle principal), que llama al updateDisplay(objects)
en el Renderer
. Hice estos dos métodos yo mismo y su único propósito es llamar a repaint()
en el Renderer
.
¿ JPanel
supone que el JPanel
debe usarse de esa manera? ¿O se supone que debo usar algún método más sofisticado para la animación (que involucre eventos y / o intervalos de tiempo en algún hilo de back-end)?
¿Por qué no usar cosas del banco de pruebas? Ya lo hace todo. Simplemente tome JPanel, controlador y drenaje de depuración. Utiliza el dibujo 2D de Java.
Vea aquí para el JPanel que hace el renderizado en búfer: https://github.com/dmurph/jbox2d/blob/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework/j2d/TestPanelJ2D.java
y aquí para el sorteo de depuración: https://github.com/dmurph/jbox2d/blob/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework/j2d/DebugDrawJ2D.java
Mira el archivo TestbedMain.java para ver cómo se lanza el banco de pruebas normal y arranca lo que no necesitas :)
Ediciones: Descargo de responsabilidad: mantengo jbox2d
Aquí está el paquete para el framework testbed: https://github.com/dmurph/jbox2d/tree/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework
TestbedMain.java está en la carpeta j2d, aquí: https://github.com/dmurph/jbox2d/tree/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework/j2d
Para una simulación estrechamente acoplada, javax.swing.Timer
es una buena opción. Deje que el oyente del temporizador invoque su implementación de paintComponent()
, como se muestra aquí y en el ejemplo citado aquí .
Para una simulación ligeramente acoplada, permita que el modelo evolucione en la SwingWorker
fondo de un SwingWorker
, como se muestra aquí . Invoque publish()
cuando corresponda a su simulación.
La elección está dictada en parte por la naturaleza de la simulación y el ciclo de trabajo del modelo.
Si desea programar las actualizaciones en un intervalo establecido, javax.swing.Timer
ofrece un servicio integrado de Swing. Timer
ejecuta su tarea en el EDT periódicamente, sin tener un bucle explícito. (Un bucle explícito bloquearía el EDT del procesamiento de eventos, lo que congelaría la UI. Lo expliqué más en profundidad aquí ).
Al final, haciendo cualquier tipo de pintura en Swing, seguirás haciendo dos cosas:
-
paintComponent
para hacer tu dibujo. - Llamar a
repaint
según sea necesario para solicitar que su dibujo se haga visible. (Swing normalmente solo se vuelve a pintar cuando es necesario, por ejemplo, cuando la ventana de otro programa pasa por encima de un componente Swing).
Si estás haciendo esas dos cosas, probablemente lo estés haciendo bien. Swing realmente no tiene una API de alto nivel para la animación. Está diseñado principalmente teniendo en cuenta los componentes de la GUI. Sin duda puede hacer algunas cosas buenas, pero tendrá que escribir un componente sobre todo desde cero, como lo está haciendo.
Pintar en AWT y Swing cubre algunas de las cosas ''detrás de escena'' si no lo tienes marcado.
Puede mirar hacia JavaFX. No sé mucho sobre eso personalmente, pero se supone que está más orientado a la animación.
Como algo así como una optimización, una cosa que se puede hacer es pintar en una imagen separada y luego pintar la imagen en el panel en paintComponent
. Esto es especialmente útil si la pintura es larga: el sistema puede programar los repintados, de modo que esto se mantenga cuando esté más bajo control.
Si no está dibujando una imagen, entonces necesitaría construir un modelo con objetos y pintarlos todos en paintComponent
.
Aquí hay un ejemplo de dibujo a una imagen:
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
/**
* Holding left-click draws, and
* right-clicking cycles the color.
*/
class PaintAnyTime {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new PaintAnyTime();
}
});
}
Color[] colors = {Color.red, Color.blue, Color.black};
int currentColor = 0;
BufferedImage img = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB);
Graphics2D imgG2 = img.createGraphics();
JFrame frame = new JFrame("Paint Any Time");
JPanel panel = new JPanel() {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// Creating a copy of the Graphics
// so any reconfiguration we do on
// it doesn''t interfere with what
// Swing is doing.
Graphics2D g2 = (Graphics2D) g.create();
// Drawing the image.
int w = img.getWidth();
int h = img.getHeight();
g2.drawImage(img, 0, 0, w, h, null);
// Drawing a swatch.
Color color = colors[currentColor];
g2.setColor(color);
g2.fillRect(0, 0, 16, 16);
g2.setColor(Color.black);
g2.drawRect(-1, -1, 17, 17);
// At the end, we dispose the
// Graphics copy we''ve created
g2.dispose();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(img.getWidth(), img.getHeight());
}
};
MouseAdapter drawer = new MouseAdapter() {
boolean rButtonDown;
Point prev;
@Override
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
prev = e.getPoint();
}
if (SwingUtilities.isRightMouseButton(e) && !rButtonDown) {
// (This just behaves a little better
// than using the mouseClicked event.)
rButtonDown = true;
currentColor = (currentColor + 1) % colors.length;
panel.repaint();
}
}
@Override
public void mouseDragged(MouseEvent e) {
if (prev != null) {
Point next = e.getPoint();
Color color = colors[currentColor];
// We can safely paint to the
// image any time we want to.
imgG2.setColor(color);
imgG2.drawLine(prev.x, prev.y, next.x, next.y);
// We just need to repaint the
// panel to make sure the
// changes are visible
// immediately.
panel.repaint();
prev = next;
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
prev = null;
}
if (SwingUtilities.isRightMouseButton(e)) {
rButtonDown = false;
}
}
};
PaintAnyTime() {
// RenderingHints let you specify
// options such as antialiasing.
imgG2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
imgG2.setStroke(new BasicStroke(3));
//
panel.setBackground(Color.white);
panel.addMouseListener(drawer);
panel.addMouseMotionListener(drawer);
Cursor cursor =
Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
panel.setCursor(cursor);
frame.setContentPane(panel);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Si la rutina es de larga duración y las reparaciones pueden ocurrir al mismo tiempo, también se puede usar el doble almacenamiento en memoria intermedia. El dibujo se realiza a una imagen que está separada de la que se muestra. Luego, cuando la rutina de dibujo finaliza, las referencias de imagen se intercambian para que la actualización sea perfecta.
Por lo general, debería utilizar doble almacenamiento en búfer para un juego, por ejemplo. El doble almacenamiento en memoria intermedia evita que la imagen se muestre en un estado parcial. Esto podría suceder si, por ejemplo, estuvieras usando un hilo de fondo para el bucle del juego (en lugar de un Timer
) y se repintó el juego estaba haciendo la pintura. Sin doble buffering, este tipo de situación daría lugar a parpadeo o desgarro.
Los componentes Swing tienen doble buffer por defecto, por lo que si todo su dibujo ocurre en el EDT, no es necesario que escriba usted mismo la lógica del doble buffer. Swing ya lo hace.
Aquí hay un ejemplo algo más complicado que muestra una tarea de larga duración y un intercambio de memoria intermedia:
import java.awt.*;
import javax.swing.*;
import java.awt.image.*;
import java.awt.event.*;
import java.util.*;
/**
* Left-click to spawn a new background
* painting task.
*/
class DoubleBuffer {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new DoubleBuffer();
}
});
}
final int width = 640;
final int height = 480;
BufferedImage createCompatibleImage() {
GraphicsConfiguration gc =
GraphicsEnvironment
.getLocalGraphicsEnvironment()
.getDefaultScreenDevice()
.getDefaultConfiguration();
// createCompatibleImage creates an image that is
// optimized for the display device.
// See http://docs.oracle.com/javase/8/docs/api/java/awt/GraphicsConfiguration.html#createCompatibleImage-int-int-int-
return gc.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
}
// The front image is the one which is
// displayed in the panel.
BufferedImage front = createCompatibleImage();
// The back image is the one that gets
// painted to.
BufferedImage back = createCompatibleImage();
boolean isPainting = false;
final JFrame frame = new JFrame("Double Buffer");
final JPanel panel = new JPanel() {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// Scaling the image to fit the panel.
Dimension actualSize = getSize();
int w = actualSize.width;
int h = actualSize.height;
g.drawImage(front, 0, 0, w, h, null);
}
};
final MouseAdapter onClick = new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
if (!isPainting) {
isPainting = true;
new PaintTask(e.getPoint()).execute();
}
}
};
DoubleBuffer() {
panel.setPreferredSize(new Dimension(width, height));
panel.setBackground(Color.WHITE);
panel.addMouseListener(onClick);
frame.setContentPane(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
void swap() {
BufferedImage temp = front;
front = back;
back = temp;
}
class PaintTask extends SwingWorker<Void, Void> {
final Point pt;
PaintTask(Point pt) {
this.pt = pt;
}
@Override
public Void doInBackground() {
Random rand = new Random();
synchronized(DoubleBuffer.this) {
Graphics2D g2 = back.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
g2.setBackground(new Color(0, true));
g2.clearRect(0, 0, width, height);
// (This computes pow(2, rand.nextInt(3) + 7).)
int depth = 1 << ( rand.nextInt(3) + 7 );
float hue = rand.nextInt(depth);
int radius = 1;
int c;
// This loop just draws concentric circles,
// starting from the inside and extending
// outwards until it hits the outside of
// the image.
do {
int rgb = Color.HSBtoRGB(hue / depth, 1, 1);
g2.setColor(new Color(rgb));
int x = pt.x - radius;
int y = pt.y - radius;
int d = radius * 2;
g2.drawOval(x, y, d, d);
++radius;
++hue;
c = (int) (radius * Math.cos(Math.PI / 4));
} while (
(0 <= pt.x - c) || (pt.x + c < width)
|| (0 <= pt.y - c) || (pt.y + c < height)
);
g2.dispose();
back.flush();
return (Void) null;
}
}
@Override
public void done() {
// done() is completed on the EDT,
// so for this small program, this
// is the only place where synchronization
// is necessary.
// paintComponent will see the swap
// happen the next time it is called.
synchronized(DoubleBuffer.this) {
swap();
}
isPainting = false;
panel.repaint();
}
}
}
La rutina de pintura solo tiene la intención de dibujar basura, lo que lleva mucho tiempo: