Rewritten to use SunWritableRaster if available, with graceful fallback.

Better tiling in test app.
This commit is contained in:
Harald Kuhr 2013-09-08 14:41:06 +02:00
parent 2116feb49f
commit 47425e2ca0
2 changed files with 123 additions and 22 deletions

View File

@ -61,6 +61,7 @@ import java.util.concurrent.*;
public class MappedBufferImage {
private static int threads = Runtime.getRuntime().availableProcessors();
private static ExecutorService executorService = Executors.newFixedThreadPool(threads * 4);
private static ExecutorService executorService2 = Executors.newFixedThreadPool(2);
public static void main(String[] args) throws IOException {
int argIndex = 0;
@ -553,15 +554,15 @@ public class MappedBufferImage {
}
}
public void drawTo(Graphics2D g) {
public boolean drawTo(Graphics2D g) {
BufferedImage img = data.get();
if (img != null) {
g.drawImage(img, x, y, null);
return true;
}
// g.setPaint(Color.GREEN);
// g.drawString(String.format("[%d, %d]", x, y), x + 20, y + 20);
return false;
}
public int getX() {
@ -622,6 +623,7 @@ public class MappedBufferImage {
}
// TODO: Consider a fixed size (mem) LRUCache instead
// TODO: Better yet, re-use tiles
Map<Point, Tile> tiles = createTileCache();
private void repaintImage(final Rectangle rect, final Graphics2D g2) {
@ -634,6 +636,15 @@ public class MappedBufferImage {
// Paint tiles of the image, to preserve memory
final int tileSize = 200;
// Calculate relative to image(0,0), rather than rect(x, y)
int xOff = rect.x % tileSize;
int yOff = rect.y % tileSize;
rect.x -= xOff;
rect.y -= yOff;
rect.width += xOff;
rect.height += yOff;
int tilesW = 1 + rect.width / tileSize;
int tilesH = 1 + rect.height / tileSize;
@ -658,10 +669,10 @@ public class MappedBufferImage {
// TODO: Could we use ImageProducer/ImageConsumer/ImageObserver interface??
// Destination (display) coordinates
int dstX = (int) Math.round(x * zoom);
int dstY = (int) Math.round(y * zoom);
int dstW = (int) Math.round(w * zoom);
int dstH = (int) Math.round(h * zoom);
int dstX = (int) Math.floor(x * zoom);
int dstY = (int) Math.floor(y * zoom);
int dstW = (int) Math.ceil(w * zoom);
int dstH = (int) Math.ceil(h * zoom);
if (dstW == 0 || dstH == 0) {
continue;
@ -678,8 +689,8 @@ public class MappedBufferImage {
// final int tileSrcH = Math.min(tileSize, image.getHeight() - tileSrcY);
// Destination (display) coordinates
int tileDstX = (int) Math.round(tileSrcX * zoom);
int tileDstY = (int) Math.round(tileSrcY * zoom);
int tileDstX = (int) Math.floor(tileSrcX * zoom);
int tileDstY = (int) Math.floor(tileSrcY * zoom);
// final int tileDstW = (int) Math.round(tileSrcW * zoom);
// final int tileDstH = (int) Math.round(tileSrcH * zoom);
@ -699,9 +710,7 @@ public class MappedBufferImage {
Tile tile = tiles.get(point);
if (tile != null) {
Reference<BufferedImage> img = tile.data;
if (img != null) {
tile.drawTo(g2);
if (tile.drawTo(g2)) {
continue;
}
else {
@ -713,9 +722,8 @@ public class MappedBufferImage {
// Dispatch to off-thread worker
final Map<Point, Tile> localTiles = tiles;
executorService.submit(new Runnable() {
executorService2.submit(new Runnable() {
public void run() {
// TODO: Fix rounding issues... Problem is that sometimes the srcW/srcH is 1 pixel off filling the tile...
int tileSrcX = (int) Math.round(point.x / zoom);
int tileSrcY = (int) Math.round(point.y / zoom);
int tileSrcW = Math.min(tileSize, image.getWidth() - tileSrcX);
@ -735,8 +743,14 @@ public class MappedBufferImage {
}
// Test against current view rect, to avoid computing tiles that will be thrown away immediately
// TODO: EDT safe?
if (!getVisibleRect().intersects(new Rectangle(point.x, point.y, tileDstW, tileDstH))) {
final Rectangle visibleRect = new Rectangle();
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
visibleRect.setBounds(getVisibleRect());
}
});
if (!visibleRect.intersects(new Rectangle(point.x, point.y, tileDstW, tileDstH))) {
return;
}

View File

@ -30,11 +30,12 @@ package com.twelvemonkeys.image;
import javax.imageio.ImageTypeSpecifier;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.SampleModel;
import java.awt.image.*;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.lang.reflect.UndeclaredThrowableException;
/**
* A factory for creating {@link BufferedImage}s backed by memory mapped files.
@ -50,6 +51,9 @@ 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 static final boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.image.mapped.debug"));
static final RasterFactory RASTER_FACTORY = createRasterFactory();
private MappedImageFactory() {}
public static BufferedImage createCompatibleMappedImage(int width, int height, int type) throws IOException {
@ -58,7 +62,8 @@ public final class MappedImageFactory {
}
public static BufferedImage createCompatibleMappedImage(int width, int height, GraphicsConfiguration configuration, int transparency) throws IOException {
// TODO: Should we also use the sample model?
// BufferedImage temp = configuration.createCompatibleImage(1, 1, transparency);
// return createCompatibleMappedImage(width, height, temp.getSampleModel().createCompatibleSampleModel(width, height), temp.getColorModel());
return createCompatibleMappedImage(width, height, configuration.getColorModel(transparency));
}
@ -73,6 +78,88 @@ public final class MappedImageFactory {
static BufferedImage createCompatibleMappedImage(int width, int height, SampleModel sm, ColorModel cm) throws IOException {
DataBuffer buffer = MappedFileBuffer.create(sm.getTransferType(), width * height * sm.getNumDataElements(), 1);
return new BufferedImage(cm, new GenericWritableRaster(sm, buffer, new Point()), cm.isAlphaPremultiplied(), null);
return new BufferedImage(cm, RASTER_FACTORY.createRaster(sm, buffer, new Point()), cm.isAlphaPremultiplied(), null);
}
private static RasterFactory createRasterFactory() {
try {
// Try to instantiate, will throw LinkageError if it fails
return new SunRasterFactory();
}
catch (LinkageError e) {
if (DEBUG) {
e.printStackTrace();
}
System.err.println("Could not instantiate SunWritableRaster, falling back to GenericWritableRaster.");
}
// Fall back
return new GenericRasterFactory();
}
static interface RasterFactory {
WritableRaster createRaster(SampleModel model, DataBuffer buffer, Point origin);
}
/**
* Generic implementation that should work for any JRE, and creates a custom subclass of {@link WritableRaster}.
*/
static final class GenericRasterFactory implements RasterFactory {
public WritableRaster createRaster(final SampleModel model, final DataBuffer buffer, final Point origin) {
return new GenericWritableRaster(model, buffer, origin);
}
}
/**
* Sun/Oracle JRE-specific implementation that creates {@code sun.awt.image.SunWritableRaster}.
* Callers must catch {@link LinkageError}.
*/
static final class SunRasterFactory implements RasterFactory {
final private Constructor<WritableRaster> factoryMethod = getFactoryMethod();
@SuppressWarnings("unchecked")
private static Constructor<WritableRaster> getFactoryMethod() {
try {
Class<?> cls = Class.forName("sun.awt.image.SunWritableRaster");
if (Modifier.isAbstract(cls.getModifiers())) {
throw new IncompatibleClassChangeError("sun.awt.image.SunWritableRaster has become abstract and can't be instantiated");
}
return (Constructor<WritableRaster>) cls.getConstructor(SampleModel.class, DataBuffer.class, Point.class);
}
catch (ClassNotFoundException e) {
throw new NoClassDefFoundError(e.getMessage());
}
catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
public WritableRaster createRaster(final SampleModel model, final DataBuffer buffer, final Point origin) {
try {
return factoryMethod.newInstance(model, buffer, origin);
}
catch (InstantiationException e) {
throw new Error("Could not create SunWritableRaster: ", e); // Should never happen, as we test for abstract class
}
catch (IllegalAccessException e) {
throw new Error("Could not create SunWritableRaster: ", e); // Should never happen, only public constructors are reflected
}
catch (InvocationTargetException e) {
// Unwrap to allow normal exception flow
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
}
else if (cause instanceof Error) {
throw (Error) cause;
}
throw new UndeclaredThrowableException(cause);
}
}
}
}