Rewritten threading code to use latches, to avoid shutting down the executor service between steps.

This commit is contained in:
Harald Kuhr 2011-12-20 15:42:16 +01:00
parent 6ba32b657a
commit 280407d9c0
2 changed files with 107 additions and 45 deletions

View File

@ -28,6 +28,9 @@
package com.twelvemonkeys.image; package com.twelvemonkeys.image;
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import com.twelvemonkeys.lang.StringUtil;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam; import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader; import javax.imageio.ImageReader;
@ -42,6 +45,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Iterator; import java.util.Iterator;
import java.util.Random; import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -55,14 +59,19 @@ import java.util.concurrent.TimeUnit;
*/ */
public class MappedBufferImage { public class MappedBufferImage {
private static int threads = Runtime.getRuntime().availableProcessors(); private static int threads = Runtime.getRuntime().availableProcessors();
private static ExecutorService executorService = Executors.newFixedThreadPool(threads);
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
int argIndex = 0;
File file = args.length > 0 ? new File(args[argIndex]) : null;
int w; int w;
int h; int h;
BufferedImage image; BufferedImage image;
File file = args.length > 0 ? new File(args[0]) : null;
if (file != null && file.exists()) { if (file != null && file.exists()) {
argIndex++;
// Load image using ImageIO // Load image using ImageIO
ImageInputStream input = ImageIO.createImageInputStream(file); ImageInputStream input = ImageIO.createImageInputStream(file);
Iterator<ImageReader> readers = ImageIO.getImageReaders(input); Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
@ -74,6 +83,7 @@ public class MappedBufferImage {
} }
ImageReader reader = readers.next(); ImageReader reader = readers.next();
try {
reader.setInput(input); reader.setInput(input);
Iterator<ImageTypeSpecifier> types = reader.getImageTypes(0); Iterator<ImageTypeSpecifier> types = reader.getImageTypes(0);
@ -84,26 +94,36 @@ public class MappedBufferImage {
w = reader.getWidth(0); w = reader.getWidth(0);
h = reader.getHeight(0); h = reader.getHeight(0);
// GraphicsConfiguration configuration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); // GraphicsConfiguration configuration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
// ColorModel cm2 = configuration.getColorModel(cm.getTransparency()); // ColorModel cm2 = configuration.getColorModel(cm.getTransparency());
// image = MappedImageFactory.createCompatibleMappedImage(w, h, cm2); // image = MappedImageFactory.createCompatibleMappedImage(w, h, cm2);
// image = MappedImageFactory.createCompatibleMappedImage(w, h, cm); // image = MappedImageFactory.createCompatibleMappedImage(w, h, cm);
// image = MappedImageFactory.createCompatibleMappedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR); // image = MappedImageFactory.createCompatibleMappedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR);
// image = MappedImageFactory.createCompatibleMappedImage(w, h, BufferedImage.TYPE_INT_BGR); // image = MappedImageFactory.createCompatibleMappedImage(w, h, BufferedImage.TYPE_INT_BGR);
// image = MappedImageFactory.createCompatibleMappedImage(w, h, type);
// if (w > 1024 || h > 1024) {
image = MappedImageFactory.createCompatibleMappedImage(w, h, type); image = MappedImageFactory.createCompatibleMappedImage(w, h, type);
// }
// else {
// image = type.createBufferedImage(w, h); // image = type.createBufferedImage(w, h);
// }
System.out.println("image = " + image); System.out.println("image = " + image);
ImageReadParam param = reader.getDefaultReadParam(); ImageReadParam param = reader.getDefaultReadParam();
param.setDestination(image); param.setDestination(image);
reader.addIIOReadProgressListener(new ConsoleProgressListener());
reader.read(0, param); reader.read(0, param);
} }
finally {
reader.dispose();
}
}
else { else {
w = args.length > 0 ? Integer.parseInt(args[0]) : 6000; w = args.length > argIndex && StringUtil.isNumber(args[argIndex]) ? Integer.parseInt(args[argIndex++]) : 6000;
h = args.length > 1 ? Integer.parseInt(args[1]) : w * 2 / 3; h = args.length > argIndex && StringUtil.isNumber(args[argIndex]) ? Integer.parseInt(args[argIndex++]) : w * 2 / 3;
GraphicsConfiguration configuration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); GraphicsConfiguration configuration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
image = MappedImageFactory.createCompatibleMappedImage(w, h, configuration, Transparency.TRANSLUCENT); image = MappedImageFactory.createCompatibleMappedImage(w, h, configuration, Transparency.TRANSLUCENT);
@ -122,8 +142,8 @@ public class MappedBufferImage {
paintDots(w, h, image); paintDots(w, h, image);
} }
// TODO: Make re-sampling optional // Resample down to some fixed size
if (true) { if (args.length > argIndex && "-scale".equals(args[argIndex++])) {
image = resampleImage(image, 800); image = resampleImage(image, 800);
} }
@ -161,14 +181,14 @@ public class MappedBufferImage {
float aspect = image.getHeight() / (float) image.getWidth(); float aspect = image.getHeight() / (float) image.getWidth();
int height = Math.round(width * aspect); int height = Math.round(width * aspect);
ExecutorService executorService = Executors.newFixedThreadPool(threads);
// NOTE: The createCompatibleDestImage takes the byte order/layout into account, unlike the cm.createCompatibleWritableRaster // NOTE: The createCompatibleDestImage takes the byte order/layout into account, unlike the cm.createCompatibleWritableRaster
final BufferedImage output = new ResampleOp(width, height).createCompatibleDestImage(image, null); final BufferedImage output = new ResampleOp(width, height).createCompatibleDestImage(image, null);
final int inStep = (int) Math.ceil(image.getHeight() / (double) threads); final int inStep = (int) Math.ceil(image.getHeight() / (double) threads);
final int outStep = (int) Math.ceil(height / (double) threads); final int outStep = (int) Math.ceil(height / (double) threads);
final CountDownLatch latch = new CountDownLatch(threads);
// Resample image in slices // Resample image in slices
for (int i = 0; i < threads; i++) { for (int i = 0; i < threads; i++) {
final int inY = i * inStep; final int inY = i * inStep;
@ -181,15 +201,17 @@ public class MappedBufferImage {
BufferedImage in = image.getSubimage(0, inY, image.getWidth(), inHeight); BufferedImage in = image.getSubimage(0, inY, image.getWidth(), inHeight);
BufferedImage out = output.getSubimage(0, outY, width, outHeight); BufferedImage out = output.getSubimage(0, outY, width, outHeight);
new ResampleOp(width, outHeight, ResampleOp.FILTER_LANCZOS).filter(in, out); new ResampleOp(width, outHeight, ResampleOp.FILTER_LANCZOS).filter(in, out);
// new ResampleOp(width, outHeight, ResampleOp.FILTER_LANCZOS).resample(in, out, ResampleOp.createFilter(ResampleOp.FILTER_LANCZOS));
// BufferedImage out = new ResampleOp(width, outHeight, ResampleOp.FILTER_LANCZOS).filter(in, null); // BufferedImage out = new ResampleOp(width, outHeight, ResampleOp.FILTER_LANCZOS).filter(in, null);
// ImageUtil.drawOnto(output.getSubimage(0, outY, width, outHeight), out); // ImageUtil.drawOnto(output.getSubimage(0, outY, width, outHeight), out);
// showIt(width, outHeight, out, "foo");
} }
catch (RuntimeException e) { catch (RuntimeException e) {
e.printStackTrace(); e.printStackTrace();
throw e; throw e;
} }
finally {
latch.countDown();
}
} }
}); });
} }
@ -200,8 +222,7 @@ public class MappedBufferImage {
Boolean done = null; Boolean done = null;
try { try {
executorService.shutdown(); done = latch.await(5L, TimeUnit.MINUTES);
done = executorService.awaitTermination(5L, TimeUnit.MINUTES);
} }
catch (InterruptedException ignore) { catch (InterruptedException ignore) {
} }
@ -223,21 +244,19 @@ public class MappedBufferImage {
Color.GRAY, Color.GREEN, Color.YELLOW, Color.PINK, Color.LIGHT_GRAY, Color.DARK_GRAY Color.GRAY, Color.GREEN, Color.YELLOW, Color.PINK, Color.LIGHT_GRAY, Color.DARK_GRAY
}; };
ExecutorService executorService = Executors.newFixedThreadPool(threads); CountDownLatch latch = new CountDownLatch(threads);
int step = (int) Math.ceil(hs / (double) threads); int step = (int) Math.ceil(hs / (double) threads);
Random r = new Random(); Random r = new Random();
for (int i = 0; i < threads; i++) { for (int i = 0; i < threads; i++) {
executorService.submit(new PaintDotsTask(image, s, ws, colors, r, i * step, i * step + step)); executorService.submit(new PaintDotsTask(image, s, ws, colors, r, i * step, i * step + step, latch));
} }
System.err.printf("Started painting in %d threads, waiting for execution to complete...%n", threads); System.err.printf("Started painting in %d threads, waiting for execution to complete...%n", threads);
Boolean done = null; Boolean done = null;
try { try {
executorService.shutdown(); done = latch.await(3L, TimeUnit.MINUTES);
done = executorService.awaitTermination(3L, TimeUnit.MINUTES);
} }
catch (InterruptedException ignore) { catch (InterruptedException ignore) {
} }
@ -279,16 +298,15 @@ public class MappedBufferImage {
int step = (int) Math.ceil(h / (double) threads); int step = (int) Math.ceil(h / (double) threads);
ExecutorService executorService = Executors.newFixedThreadPool(threads); CountDownLatch latch = new CountDownLatch(threads);
for (int i = 0; i < threads; i++) { for (int i = 0; i < threads; i++) {
executorService.submit(new PaintBackgroundTask(w, h, buffer, alpha, i * step, i * step + step)); executorService.submit(new PaintBackgroundTask(w, h, buffer, alpha, i * step, i * step + step, latch));
} }
System.err.printf("Started painting in %d threads, waiting for execution to complete...%n", threads); System.err.printf("Started painting in %d threads, waiting for execution to complete...%n", threads);
Boolean done = null; Boolean done = null;
try { try {
executorService.shutdown(); done = latch.await(3L, TimeUnit.MINUTES);
done = executorService.awaitTermination(3L, TimeUnit.MINUTES);
} }
catch (InterruptedException ignore) { catch (InterruptedException ignore) {
} }
@ -474,8 +492,9 @@ public class MappedBufferImage {
private final Random random; private final Random random;
private final int last; private final int last;
private final int first; private final int first;
private final CountDownLatch latch;
public PaintDotsTask(BufferedImage image, int s, int wstep, Color[] colors, Random random, int first, int last) { public PaintDotsTask(BufferedImage image, int s, int wstep, Color[] colors, Random random, int first, int last, CountDownLatch latch) {
this.image = image; this.image = image;
this.s = s; this.s = s;
this.wstep = wstep; this.wstep = wstep;
@ -483,11 +502,17 @@ public class MappedBufferImage {
this.random = random; this.random = random;
this.last = last; this.last = last;
this.first = first; this.first = first;
this.latch = latch;
} }
public void run() { public void run() {
try {
paintDots0(image, s, wstep, colors, random, first, last); paintDots0(image, s, wstep, colors, random, first, last);
} }
finally {
latch.countDown();
}
}
} }
private static class PaintBackgroundTask implements Runnable { private static class PaintBackgroundTask implements Runnable {
@ -497,18 +522,53 @@ public class MappedBufferImage {
private final boolean alpha; private final boolean alpha;
private final int first; private final int first;
private final int last; private final int last;
private final CountDownLatch latch;
public PaintBackgroundTask(int w, int h, DataBuffer buffer, boolean alpha, int first, int last) { public PaintBackgroundTask(int w, int h, DataBuffer buffer, boolean alpha, int first, int last, CountDownLatch latch) {
this.w = w; this.w = w;
this.h = h; this.h = h;
this.buffer = buffer; this.buffer = buffer;
this.alpha = alpha; this.alpha = alpha;
this.first = first; this.first = first;
this.last = last; this.last = last;
this.latch = latch;
} }
public void run() { public void run() {
try {
paintBackground0(w, h, buffer, alpha, first, last); paintBackground0(w, h, buffer, alpha, first, last);
} }
finally {
latch.countDown();
}
}
}
private static class ConsoleProgressListener extends ProgressListenerBase {
static final int COLUMNS = System.getenv("COLUMNS") != null ? Integer.parseInt(System.getenv("COLUMNS")) - 2 : 78;
int left = COLUMNS;
@Override
public void imageComplete(ImageReader source) {
for (; left > 0; left--) {
System.out.print(".");
}
System.out.println("]");
}
@Override
public void imageProgress(ImageReader source, float percentageDone) {
int progress = COLUMNS - Math.round(COLUMNS * percentageDone / 100f);
if (progress < left) {
for (; left > progress; left--) {
System.out.print(".");
}
}
}
@Override
public void imageStarted(ImageReader source, int imageIndex) {
System.out.print("[");
}
} }
} }

View File

@ -46,6 +46,10 @@ import java.io.IOException;
* @version $Id: MappedImageFactory.java,v 1.0 May 26, 2010 5:07:01 PM haraldk Exp$ * @version $Id: MappedImageFactory.java,v 1.0 May 26, 2010 5:07:01 PM haraldk Exp$
*/ */
public final class MappedImageFactory { public final class MappedImageFactory {
// TODO: Create a way to do ColorConvertOp (or other color space conversion) on these images.
// - Current implementation of CCOp delegates to internal sun.awt classes that assumes java.awt.DataBufferByte for type byte buffers :-/
private MappedImageFactory() {} private MappedImageFactory() {}
public static BufferedImage createCompatibleMappedImage(int width, int height, int type) throws IOException { public static BufferedImage createCompatibleMappedImage(int width, int height, int type) throws IOException {
@ -69,8 +73,6 @@ public final class MappedImageFactory {
static BufferedImage createCompatibleMappedImage(int width, int height, SampleModel sm, ColorModel cm) throws IOException { static BufferedImage createCompatibleMappedImage(int width, int height, SampleModel sm, ColorModel cm) throws IOException {
DataBuffer buffer = MappedFileBuffer.create(sm.getTransferType(), width * height * sm.getNumDataElements(), 1); DataBuffer buffer = MappedFileBuffer.create(sm.getTransferType(), width * height * sm.getNumDataElements(), 1);
// TODO: Figure out if it's better to use SunWritableRaster when available? -- Haven't seen any improvements yet
// return new BufferedImage(cm, new SunWritableRaster(sm, buffer, new Point()), cm.isAlphaPremultiplied(), null);
return new BufferedImage(cm, new GenericWritableRaster(sm, buffer, new Point()), cm.isAlphaPremultiplied(), null); return new BufferedImage(cm, new GenericWritableRaster(sm, buffer, new Point()), cm.isAlphaPremultiplied(), null);
} }
} }