Re: Graphics - how to show partial progress.
rossum wrote:
On Wed, 19 Dec 2007 09:31:01 -0800, Daniel Pitts
<newsgroup.spamfilter@virtualinfinity.net> wrote:
Whoops, be careful.
You will freeze your UI completely if you use this approach!
Your loop should be done outside of the EDT. The synchronization will
be a bit tricky, but I bet you could figure it out. My suggestion is to
batch a number of results from "pickColour" in a separate worker thread,
and then use EventQueue.invokeLater() to add them to your buffered image
and call repaint().
Using that approach, the hard work will be done on the worker thread,
and the easy work on the EDT. This will keep your GUI responsive AND
your user informed.
Thanks for the advice.
My test code now looks as below.
rossum
[snip]
Here is a class that I just wrote which may help you.
I have an example of using it with a random image generator.
package net.virtualinfinity;
import javax.swing.*;
import java.awt.image.BufferedImage;
import java.awt.*;
import java.util.List;
/**
* This class generates an image progessively. Subclasses should
* override getARGB(int, int) to provide pixel values. This class is
* designed to calculate rectangular chunks in a batch, and then write
* those to a BufferedImage. This allows time-consuming image creation
* to be displayed progressively.
* Copyright (C) 2007, Daniel Pitts, All Rights Reserved.
*
* @author Daniel Pitts
*/
public abstract class ProgressiveImageGenerator extends
SwingWorker<Object, ProgressiveImageGenerator.ProgressiveChunk>{
private final BufferedImage image;
private final Dimension chunkSize;
private final int height;
private final int width;
private final int totalChunks;
private int chunksCompleted;
/**
* Result object for a single chunk of the image.
*/
final public static class ProgressiveChunk {
private final Rectangle block;
private final int[] rgbArray;
public ProgressiveChunk(Point origin,
Dimension size,
int[] rgbArray) {
block = new Rectangle(origin, size);
this.rgbArray = rgbArray;
}
private void draw(BufferedImage image) {
image.setRGB(block.x, block.y,
block.width, block.height,
rgbArray, 0, block.width);
}
}
/**
* Creates a new ProgressiveImageGenerator that works on a specific
* BufferedImage.
* @param image destination of progressive image data.
* @param chunkSize the rectable size. A copy is stored, not a
* reference.
*/
public ProgressiveImageGenerator(BufferedImage image,
Dimension chunkSize) {
this.image = image;
this.chunkSize = new Dimension(chunkSize);
height = image.getHeight();
width = image.getWidth();
totalChunks = ceilDivide(width, chunkSize.width) *
ceilDivide(height, chunkSize.height);
}
/**
* Utility method to "round up" a division
* @param dividend number to by divide
* @param divisor number to devide by
* @return
*/
private int ceilDivide(int dividend, int divisor) {
return (dividend + divisor - 1) / divisor;
}
/**
* This method will calculate all of the chunks and publish the
* results one chunk at a time.
* @return always null
*/
@Override
final protected Object doInBackground() {
for (int y = 0; y < height; y += chunkSize.height) {
for (int x = 0; x < width; x += chunkSize.width) {
final int w = Math.min(chunkSize.width, width - x);
final int h = Math.min(chunkSize.height, height - y);
publish(getChunkAt(x, y, new Dimension(w, h)));
}
}
return null;
}
/**
* Creates one chunk result.
* @param x origin x
* @param y origin y
* @param chunkSize size of chunk
* @return chunk result.
*/
private ProgressiveChunk getChunkAt(int x, int y,
Dimension chunkSize) {
int[] rgbArray = new int[chunkSize.width * chunkSize.height];
int offset = 0;
for (int oy =0; oy < chunkSize.height; ++oy) {
for (int ox =0; ox < chunkSize.width; ++ox) {
rgbArray[offset++] = getARGB(ox + x, oy + y);
}
}
return new ProgressiveChunk(new Point(x,y),chunkSize,rgbArray);
}
/**
* Sub-classes should override this method. Returns the pixel
* information for the given pixel location.
* @param x x coordinate
* @param y y coordinate
* @return pixel value suitable for
* {@link java.awt.image.BufferedImage#setRGB(int, int, int)}
*/
protected abstract int getARGB(int x, int y);
/**
* Called after some number of chunks have been processed.
* @param chunksCompleted number of chunks that have been drawn to
* image.
* @param totalChunks total number of chunks that will be processed.
*/
protected void updateProgress(int chunksCompleted,
int totalChunks) {}
/**
* Processes the chunk results, drawing them to the image.
* @param chunks list of chunks to process.
*/
@Override
final protected void process(List<ProgressiveChunk> chunks) {
for (final ProgressiveChunk chunk: chunks) {
chunk.draw(image);
}
chunksCompleted += chunks.size();
updateProgress(chunksCompleted, totalChunks);
}
}
Here is the example usage:
package net.virtualinfinity;
import javax.swing.*;
import java.util.Random;
import java.awt.image.BufferedImage;
import java.awt.*;
/**
* @author Daniel Pitts
*/
public class RandomChunk extends ProgressiveImageGenerator {
private final Random random = new Random();
private Component toRepaint;
public RandomChunk(BufferedImage image, Dimension chunkSize) {
super(image, chunkSize);
}
protected int getARGB(int x, int y) {
if (y % 10 == 0 && x % 40 == 0) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
}
}
return random.nextInt();
}
public Component getToRepaint() {
return toRepaint;
}
public void setToRepaint(Component toRepaint) {
this.toRepaint = toRepaint;
}
protected void updateProgress(int chunksCompleted,
int totalChunks) {
toRepaint.repaint();
}
}
class TestRandomChunk implements Runnable {
public static void main(String[] args) {
EventQueue.invokeLater(new TestRandomChunk());
}
public void run() {
JFrame frame = new JFrame();
frame.setSize(500, 500);
final BufferedImage image = new BufferedImage(500, 500,
BufferedImage.TYPE_INT_ARGB);
final RandomChunk randomChunk = new RandomChunk(image,
new Dimension(50, 50));
final JComponent comp = new JComponent() {
public void paintComponent(Graphics g) {
g.drawImage(image, 0, 0, null);
}
};
randomChunk.setToRepaint(comp);
randomChunk.execute();
frame.getContentPane().add(comp);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
--
Daniel Pitts' Tech Blog: <http://virtualinfinity.net/wordpress/>