icon java swing doublebuffered

java - icon - jlabel



Java: ¿cómo hacer doble búfer en Swing? (4)

Editar dos

Para evitar que los comentarios sarcásticos y las respuestas de una línea pierdan el punto: IFF es tan simple como llamar a setDoubleBuffered (verdadero) , ¿cómo obtengo acceso al búfer sin conexión actual para poder comenzar a jugar con el dato de datos de píxeles subyacente de BufferedImage?

Me tomé el tiempo para escribir un trozo de código (lo que también parece divertido), así que realmente agradecería las respuestas que respondan (qué sorpresa) y mi pregunta y cómo explica qué / cómo funciona esto en lugar de frases sencillas y snarky. comentarios;)

Aquí hay una pieza de código de trabajo que rebota un cuadrado a través de un JFrame. Me gustaría saber acerca de las diversas formas en que se puede usar para transformar este fragmento de código para que use el doble búfer.

Tenga en cuenta que la forma en que borro la pantalla y vuelvo a dibujar el recuadro no es la más eficiente, pero en realidad no es de lo que se trata esta pregunta (en cierto modo, es mejor por este ejemplo que sea un poco lento).

Básicamente, necesito modificar constantemente muchos píxeles en un BufferedImage (para tener algún tipo de animación) y no quiero ver los artefactos visuales debido al almacenamiento en búfer único en la pantalla.

Tengo un JLabel cuyo icono es un ImageIcon que envuelve un BufferedImage. Quiero modificar ese BufferedImage.

¿Qué hay que hacer para que esto se doble buffer?

Entiendo que de alguna manera se mostrará "imagen 1" mientras dibujaré en "imagen 2" . Pero luego, una vez que termine de dibujar en la "imagen 2" , ¿cómo puedo "reemplazar rápidamente " la imagen 1 " por " la imagen 2 " ?

¿Es esto algo que debería estar haciendo manualmente, como, por ejemplo, intercambiando el ImageIcon de JLabel?

¿Debería estar siempre dibujando en la misma imagen almacenada y luego hacer una "mezcla" rápida de los píxeles de la imagen almacenada en la imagen almacenada de JLabel''s ImageIcon? (Supongo que no y no veo cómo podría "sincronizar" esto con la "línea en blanco vertical" del monitor [o equivalente en pantalla plana: me refiero a ''sincronizar'' sin interferir con el momento en que el propio monitor actualiza su Pixeles, para evitar el cizallamiento]).

¿Qué pasa con las órdenes de "repintado"? ¿Se supone que debo disparar estos mismos? ¿A qué / cuándo debo llamar exactamente repaint () u otra cosa?

El requisito más importante es que debo modificar los píxeles directamente en el búfer de datos de píxeles de las imágenes.

import javax.swing.*; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; public class DemosDoubleBuffering extends JFrame { private static final int WIDTH = 600; private static final int HEIGHT = 400; int xs = 3; int ys = xs; int x = 0; int y = 0; final int r = 80; final BufferedImage bi1; public static void main( final String[] args ) { final DemosDoubleBuffering frame = new DemosDoubleBuffering(); frame.addWindowListener(new WindowAdapter() { public void windowClosing( WindowEvent e) { System.exit(0); } }); frame.setSize( WIDTH, HEIGHT ); frame.pack(); frame.setVisible( true ); } public DemosDoubleBuffering() { super( "Trying to do double buffering" ); final JLabel jl = new JLabel(); bi1 = new BufferedImage( WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB ); final Thread t = new Thread( new Runnable() { public void run() { while ( true ) { move(); drawSquare( bi1 ); jl.repaint(); try {Thread.sleep(10);} catch (InterruptedException e) {} } } }); t.start(); jl.setIcon( new ImageIcon( bi1 ) ); getContentPane().add( jl ); } private void drawSquare( final BufferedImage bi ) { final int[] buf = ((DataBufferInt) bi.getRaster().getDataBuffer()).getData(); for (int i = 0; i < buf.length; i++) { buf[i] = 0xFFFFFFFF; // clearing all white } for (int xx = 0; xx < r; xx++) { for (int yy = 0; yy < r; yy++) { buf[WIDTH*(yy+y)+xx+x] = 0xFF000000; } } } private void move() { if ( !(x + xs >= 0 && x + xs + r < bi1.getWidth()) ) { xs = -xs; } if ( !(y + ys >= 0 && y + ys + r < bi1.getHeight()) ) { ys = -ys; } x += xs; y += ys; } }

EDITAR

Esto no es para una aplicación Java de pantalla completa, sino para una aplicación Java normal, que se ejecuta en su propia ventana (algo pequeña).


---- Editado a la dirección por configuración de píxel ----

El soplo del elemento aborda el almacenamiento en búfer doble, pero también hay un problema sobre cómo obtener píxeles en un BufferedImage .

Si llamas

WriteableRaster raster = bi.getRaster()

en el BufferedImage devolverá un WriteableRaster . Desde allí puedes usar

int[] pixels = new int[WIDTH*HEIGHT]; // code to set array elements here raster.setPixel(0, 0, pixels);

Tenga en cuenta que probablemente desee optimizar el código para no crear realmente una nueva matriz para cada representación. Además, es probable que desee optimizar el código de borrado de la matriz para no utilizar un bucle for.

Arrays.fill(pixels, 0xFFFFFFFF);

probablemente superaría su bucle estableciendo el fondo a blanco.

---- Editado después de la respuesta ----

La clave está en su configuración original de JFrame y dentro del bucle de procesamiento de ejecución.

Primero debe decirle a SWING que deje de Rasterizar cuando quiera; porque, cuando termine de dibujar en la imagen del búfer que desea intercambiar por completo, lo dirá. Haz esto con JFrame.

setIgnoreRepaint(true);

Entonces querrás crear una estrategia de buffer. Básicamente especifica cuántos buffers quieres usar

createBufferStrategy(2);

Ahora que ha intentado crear la estrategia de búfer, debe agarrar el objeto BufferStrategy ya que lo necesitará más adelante para cambiar los búferes.

final BufferStrategy bufferStrategy = getBufferStrategy();

Dentro de tu Thread modifica el bucle run() para que contenga:

... move(); drawSqure(bi1); Graphics g = bufferStrategy.getDrawGraphics(); g.drawImage(bi1, 0, 0, null); g.dispose(); bufferStrategy.show(); ...

Los gráficos capturados de bufferStrategy serán el objeto Graphics fuera de la pantalla, cuando se crea un búfer triple, será el "siguiente" objeto Graphics fuera de la pantalla en una forma de round-robin.

La imagen y el contexto de Gráficos no están relacionados en un escenario de contención, y le dijiste a Swing que harías el dibujo tú mismo, así que debes dibujar la imagen manualmente. Esto no siempre es algo malo, ya que puede especificar el desplazamiento del búfer cuando la imagen está completamente dibujada (y no antes).

Desechar el objeto gráfico es solo una buena idea ya que ayuda en la recolección de basura. Mostrando el bufferStrategy buffers.

Si bien podría haber un paso en falso en algún lugar del código anterior, esto debería hacer que llegues al 90% del camino. ¡Buena suerte!

---- Sigue la publicación original ----

Puede parecer una tontería referir una pregunta de este tipo a un tutorial de javase, pero ¿has visto BufferStrategy y BufferCapatbilites ?

El principal problema con el que creo que te encuentras es que eres engañado por el nombre de la Imagen. Un BufferedImage no tiene nada que ver con el búfer doble, tiene que ver con el "almacenamiento en búfer de los datos (normalmente del disco) en la memoria". Como tal, necesitará dos BufferedImages si desea tener una "imagen de doble búfer"; ya que no es aconsejable alterar los píxeles en la imagen que se muestra (puede causar problemas de repintado).

En su código de renderizado, toma el objeto gráfico. Si configura el almacenamiento en búfer doble de acuerdo con el tutorial anterior, esto significa que agarrará (de manera predeterminada) el objeto de Graphics fuera de la pantalla, y todo el dibujo estará fuera de la pantalla. Luego, dibuja su imagen (la correcta, por supuesto) en el objeto fuera de la pantalla. Finalmente, le dices a la estrategia que show() el búfer y reemplazará el contexto de Gráficos por ti.


Aquí hay una variación en la que todo el dibujo tiene lugar en el hilo de despacho de eventos .

Apéndice:

Básicamente, necesito modificar constantemente muchos píxeles en un BufferedImage ...

Este modelo cinético ilustra varios enfoques para la animación de píxeles.

import java.awt.Color; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Graphics2D; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.*; import java.awt.image.BufferedImage; /** @see http://.com/questions/4430356 */ public class DemosDoubleBuffering extends JPanel implements ActionListener { private static final int W = 600; private static final int H = 400; private static final int r = 80; private int xs = 3; private int ys = xs; private int x = 0; private int y = 0; private final BufferedImage bi; private final JLabel jl = new JLabel(); private final Timer t = new Timer(10, this); public static void main(final String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(new DemosDoubleBuffering()); frame.pack(); frame.setVisible(true); } }); } public DemosDoubleBuffering() { super(true); this.setLayout(new GridLayout()); this.setPreferredSize(new Dimension(W, H)); bi = new BufferedImage(W, H, BufferedImage.TYPE_INT_ARGB); jl.setIcon(new ImageIcon(bi)); this.add(jl); t.start(); } @Override public void actionPerformed(ActionEvent e) { move(); drawSquare(bi); jl.repaint(); } private void drawSquare(final BufferedImage bi) { Graphics2D g = bi.createGraphics(); g.setColor(Color.white); g.fillRect(0, 0, W, H); g.setColor(Color.blue); g.fillRect(x, y, r, r); g.dispose(); } private void move() { if (!(x + xs >= 0 && x + xs + r < bi.getWidth())) { xs = -xs; } if (!(y + ys >= 0 && y + ys + r < bi.getHeight())) { ys = -ys; } x += xs; y += ys; } }


Generalmente usamos la clase Canvas que es adecuada para la animación en Java. Anyhoo, a continuación se muestra cómo lograr doble búfer:

class CustomCanvas extends Canvas { private Image dbImage; private Graphics dbg; int x_pos, y_pos; public CustomCanvas () { } public void update (Graphics g) { // initialize buffer if (dbImage == null) { dbImage = createImage (this.getSize().width, this.getSize().height); dbg = dbImage.getGraphics (); } // clear screen in background dbg.setColor (getBackground ()); dbg.fillRect (0, 0, this.getSize().width, this.getSize().height); // draw elements in background dbg.setColor (getForeground()); paint (dbg); // draw image on the screen g.drawImage (dbImage, 0, 0, this); } public void paint (Graphics g) { g.setColor (Color.red); g.fillOval (x_pos - radius, y_pos - radius, 2 * radius, 2 * radius); } }

Ahora puede actualizar x_pos y y_pos desde un hilo, seguido de la llamada ''repaint'' en el objeto de lienzo. La misma técnica debería funcionar también en un JPanel.


Lo que quieres es básicamente imposible en modo ventana con Swing. No hay compatibilidad con la sincronización de ráster para los repintados de ventanas, esto solo está disponible en modo de pantalla completa (y aún así es posible que no sea compatible con todas las plataformas).

Los componentes de Swing tienen doble búfer de forma predeterminada, es decir, harán todo el renderizado a un búfer intermedio y ese búfer finalmente se copiará en la pantalla, evitando el parpadeo de la limpieza del fondo y luego pintando sobre ella. Y esa es la única estrategia que está razonablemente bien soportada en todas las plataformas subyacentes. Evita solo repintar parpadeo, pero no rasgaduras visuales de elementos gráficos en movimiento.

Una forma razonablemente sencilla de tener acceso a los píxeles sin procesar de un área totalmente bajo su control sería extender un componente personalizado desde JComponent y sobrescribir su método paintComponent () - para pintar el área desde un BufferedImage (desde la memoria):

public class PixelBufferComponent extends JComponent { private BufferedImage bufferImage; public PixelBufferComponent(int width, int height) { bufferImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); setPreferredSize(new Dimension(width, height)); } public void paintComponent(Graphics g) { g.drawImage(bufferImage, 0, 0, null); } }

A continuación, puede manipular su imagen almacenada en búfer de la forma que desee. Para que sus cambios se hagan visibles en la pantalla, simplemente llame a repaint () en ella. Si realiza la manipulación de píxeles desde un subproceso distinto al EDT, necesita DOS imágenes con búfer para hacer frente a las condiciones de carrera entre el repintado real y el subproceso de manipulación.

Tenga en cuenta que este esqueleto no pintará toda el área del componente cuando se usa con un administrador de diseño que extienda el componente más allá de su tamaño preferido.

Tenga en cuenta también que el enfoque de la imagen con búfer solo tiene sentido si realiza una manipulación real de píxeles de bajo nivel a través de setRGB (...) en la imagen o si accede directamente al DataBuffer subyacente. Si puede realizar todas las manipulaciones utilizando los métodos de Graphics2D, puede hacer todas las cosas en el método paintComponent utilizando los gráficos proporcionados (que en realidad es una Graphics2D y puede ser simplemente fundido).