mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-02 02:55:28 -04:00
Rewritten to use SunWritableRaster if available, with graceful fallback.
Better tiling in test app.
This commit is contained in:
parent
2116feb49f
commit
47425e2ca0
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user