AWT Custom Rendering - Captures smooth resizing and eliminates flicker

I have been looking at this for several months, and so far this is the best I have come up with.

The structure (rendering outside the EDT) is not discussed, as our application works this way and will not be overwritten. The application has a layout model and a scenario model that are integrated and render discs, so rendering must be done outside of the AWT drawing model.

What I'm trying to find is the best and most reliable way to do custom rendering.

The next SSCCE works quite well for us. However, when changing the frame size, it has 2 disadvantages:

  • There is occasional flickering, especially with fast resizing.
  • The "smooth resizing" hack, which should cause a resizing (via checkSize here) from the paint () call, works well for extensions. When reducing the frame, it usually does not appear until the mouse button is released.
  • In addition, but it is not so obvious, it causes random IllegalStateExceptions errors - is it normal to catch / ignore them?

It is also useful to introduce whether this is the best approach for a custom visualization path that takes place outside of the EDT. I have tried the most and have done quite extensive research. This combination (buffer image, double buffer strategy) seems to work best.

import java.awt.Color; import java.awt.Dimension; import java.awt.Frame; import java.awt.Graphics; import java.awt.Insets; import java.awt.Toolkit; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import java.awt.image.BufferStrategy; public class SmoothResize extends Frame implements ComponentListener, MouseMotionListener { public SmoothResize() { addComponentListener(this); addMouseMotionListener(this); } private boolean sizeChanged = false; private Dimension old = new Dimension(0, 0); private synchronized void checkSize(String source) { int width = getWidth(); int height = getHeight(); if (old.width == width && old.height == height) return; sizeChanged = true; String type = (old.width > width && old.height > height) ? "shrink" : (old.width < width && old.height < height) ? "expand" : "resize"; System.out.println(source + " reports " + type + ": "+getWidth()+", "+getHeight()); old.setSize(width, height); } public void componentResized(ComponentEvent arg0) { checkSize("componentResized"); } public void mouseMoved(MouseEvent e) { checkSize("mouseMoved"); } public void paint(Graphics g) { checkSize("paint"); } public void update(Graphics g) { paint(g); } public void addNotify() { super.addNotify(); createBufferStrategy(2); } private synchronized void render() { BufferStrategy strategy = getBufferStrategy(); if (strategy==null || !sizeChanged) return; sizeChanged = false; // Render single frame do { // The following loop ensures that the contents of the drawing buffer // are consistent in case the underlying surface was recreated do { System.out.println("render"); Graphics draw = strategy.getDrawGraphics(); Insets i = getInsets(); int w = getWidth()-i.left-i.right; int h = getHeight()-i.top-i.bottom; draw.setColor(Color.YELLOW); draw.fillRect(i.left, i.top+(h/2), w/2, h/2); draw.fillRect(i.left+(w/2), i.top, w/2, h/2); draw.setColor(Color.BLACK); draw.fillRect(i.left, i.top, w/2, h/2); draw.fillRect(i.left+(w/2), i.top+(h/2), w/2, h/2); draw.dispose(); // Repeat the rendering if the drawing buffer contents // were restored } while (strategy.contentsRestored()); // Display the buffer strategy.show(); // Repeat the rendering if the drawing buffer was lost } while (strategy.contentsLost()); } public static void main(String[] args) { Toolkit.getDefaultToolkit().setDynamicLayout(true); System.setProperty("sun.awt.noerasebackground", "true"); SmoothResize srtest = new SmoothResize(); //srtest.setIgnoreRepaint(true); srtest.setSize(100, 100); srtest.setVisible(true); while (true) { srtest.render(); } } public void componentHidden(ComponentEvent arg0) { } public void componentMoved(ComponentEvent arg0) { } public void componentShown(ComponentEvent arg0) { } public void mouseDragged(MouseEvent e) { } } 
+6
source share
2 answers

Here is the code that renders an external thread doing all the work. It does this by providing everything that implements the Renderable interface. I tested this with both Swing and AWT ( JFrame and Frame ), and it works without flickering. Note that it flickers if you are implementing on JRootPane and setting this panel as the root panel of the JFrame . This is due to how the component is buffered and can be fixed if you want to use this.

If this is not what you were looking for, just say it and I will give it one more step. This is really funny as it has been a while since I did any work with the Java GUI.

Anyway, here you go:

 import java.awt.Color; import java.awt.Dimension; import java.awt.Frame; import java.awt.Graphics; import java.awt.Toolkit; import javax.swing.JFrame; public class SmoothResize extends Frame implements Renderable { public static void main(String[] args) { Toolkit.getDefaultToolkit().setDynamicLayout(true); System.setProperty("sun.awt.noerasebackground", "true"); SmoothResize srtest = new SmoothResize(); RenderThread renderThread = new RenderThread(srtest); renderThread.start(); srtest.setSize(100, 100); srtest.setVisible(true); } public SmoothResize() { } public void addNotify() { super.addNotify(); createBufferStrategy(2); } @Override public Dimension getSize() { return new Dimension(getWidth(), getHeight()); } @Override public Graphics acquireGraphics() { return this.getGraphics(); } } class RenderThread extends Thread { Renderable target; Dimension last_size = new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); public RenderThread(Renderable d) { if (d == null) { throw new NullPointerException("Drawable target cannot be null."); } target = d; } @Override public void run() { while (true) { render(false); } } private synchronized void render(boolean force) { Dimension size; do { size = target.getSize(); if (size == null) { return; } Graphics draw = target.acquireGraphics(); if (draw == null) { return; } draw.setPaintMode(); int w = (int) (((double) (size.width)) / 2 + 0.5); int h = (int) (((double) (size.height)) / 2 + 0.5); draw.setColor(Color.YELLOW); draw.fillRect(0, h, w, h); draw.fillRect(w, 0, w, h); draw.setColor(Color.BLACK); draw.fillRect(0, 0, w, h); draw.fillRect(w, h, w, h); draw.dispose(); // Repeat the rendering if the target changed size } while (!size.equals(target.getSize())); } } interface Renderable { public Graphics acquireGraphics(); public Dimension getSize(); } 
+3
source

This answer is left here for reference, but this is not the correct answer because it displays inside the EDT stream.

Here's a working fix !: D The main problem is that ComponentResized is not assigned properly until the mouse is released after compression. In addition, since the paint and checkSize methods are synchronized, they can be mutually exclusive in rare cases. The fix is ​​to override the validation method in the Frame class. This method is always called if the frame changes state, including contractions and growth. Therefore, we just need to check the size in the validate, and we really can completely forget about using the ComponentResized method in general.

So, here is a working code that compiles as is. I changed some variable names to improve my personal readability.

 import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Insets; import java.awt.Toolkit; import java.awt.image.BufferStrategy; import java.awt.Frame; public class SmoothResize extends Frame { public static void main(String[] args) { Toolkit.getDefaultToolkit().setDynamicLayout(true); System.setProperty("sun.awt.noerasebackground", "true"); SmoothResize srtest = new SmoothResize(); //srtest.setIgnoreRepaint(true); srtest.setSize(100, 100); srtest.setVisible(true); } public SmoothResize() { render(); } private Dimension old_size = new Dimension(0, 0); private Dimension new_size = new Dimension(0, 0); public void validate() { super.validate(); new_size.width = getWidth(); new_size.height = getHeight(); if (old_size.equals(new_size)) { return; } else { render(); } } public void paint(Graphics g) { validate(); } public void update(Graphics g) { paint(g); } public void addNotify() { super.addNotify(); createBufferStrategy(2); } protected synchronized void render() { BufferStrategy strategy = getBufferStrategy(); if (strategy == null) { return; } // Render single frame do { // The following loop ensures that the contents of the drawing buffer // are consistent in case the underlying surface was recreated do { Graphics draw = strategy.getDrawGraphics(); Insets i = getInsets(); int w = (int)(((double)(getWidth() - i.left - i.right))/2+0.5); int h = (int)(((double)(getHeight() - i.top - i.bottom))/2+0.5); draw.setColor(Color.YELLOW); draw.fillRect(i.left, i.top + h, w,h); draw.fillRect(i.left + w, i.top, w,h); draw.setColor(Color.BLACK); draw.fillRect(i.left, i.top, w, h); draw.fillRect(i.left + w, i.top + h, w,h); draw.dispose(); // Repeat the rendering if the drawing buffer contents // were restored } while (strategy.contentsRestored()); // Display the buffer strategy.show(); // Repeat the rendering if the drawing buffer was lost } while (strategy.contentsLost()); } } 

I hope this works for you!

Besides the last second edit, I changed the logical triple operation to select your shrink or String extension. The last comparison was unnecessary, because for the comparison in question, the values ​​may be greater, less, or equal to each other. There is no other possibility that would not NullPointerException .

Killed text is no longer relevant since I completely deleted this entire method. I indicate other changes that I made to the original post as comments.

+2
source

Source: https://habr.com/ru/post/893546/


All Articles