mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-02 19:15:29 -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 {
|
public class MappedBufferImage {
|
||||||
private static int threads = Runtime.getRuntime().availableProcessors();
|
private static int threads = Runtime.getRuntime().availableProcessors();
|
||||||
private static ExecutorService executorService = Executors.newFixedThreadPool(threads * 4);
|
private static ExecutorService executorService = Executors.newFixedThreadPool(threads * 4);
|
||||||
|
private static ExecutorService executorService2 = Executors.newFixedThreadPool(2);
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
int argIndex = 0;
|
int argIndex = 0;
|
||||||
@ -553,15 +554,15 @@ public class MappedBufferImage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drawTo(Graphics2D g) {
|
public boolean drawTo(Graphics2D g) {
|
||||||
BufferedImage img = data.get();
|
BufferedImage img = data.get();
|
||||||
|
|
||||||
if (img != null) {
|
if (img != null) {
|
||||||
g.drawImage(img, x, y, null);
|
g.drawImage(img, x, y, null);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// g.setPaint(Color.GREEN);
|
return false;
|
||||||
// g.drawString(String.format("[%d, %d]", x, y), x + 20, y + 20);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getX() {
|
public int getX() {
|
||||||
@ -622,6 +623,7 @@ public class MappedBufferImage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Consider a fixed size (mem) LRUCache instead
|
// TODO: Consider a fixed size (mem) LRUCache instead
|
||||||
|
// TODO: Better yet, re-use tiles
|
||||||
Map<Point, Tile> tiles = createTileCache();
|
Map<Point, Tile> tiles = createTileCache();
|
||||||
|
|
||||||
private void repaintImage(final Rectangle rect, final Graphics2D g2) {
|
private void repaintImage(final Rectangle rect, final Graphics2D g2) {
|
||||||
@ -634,6 +636,15 @@ public class MappedBufferImage {
|
|||||||
// Paint tiles of the image, to preserve memory
|
// Paint tiles of the image, to preserve memory
|
||||||
final int tileSize = 200;
|
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 tilesW = 1 + rect.width / tileSize;
|
||||||
int tilesH = 1 + rect.height / tileSize;
|
int tilesH = 1 + rect.height / tileSize;
|
||||||
|
|
||||||
@ -658,10 +669,10 @@ public class MappedBufferImage {
|
|||||||
// TODO: Could we use ImageProducer/ImageConsumer/ImageObserver interface??
|
// TODO: Could we use ImageProducer/ImageConsumer/ImageObserver interface??
|
||||||
|
|
||||||
// Destination (display) coordinates
|
// Destination (display) coordinates
|
||||||
int dstX = (int) Math.round(x * zoom);
|
int dstX = (int) Math.floor(x * zoom);
|
||||||
int dstY = (int) Math.round(y * zoom);
|
int dstY = (int) Math.floor(y * zoom);
|
||||||
int dstW = (int) Math.round(w * zoom);
|
int dstW = (int) Math.ceil(w * zoom);
|
||||||
int dstH = (int) Math.round(h * zoom);
|
int dstH = (int) Math.ceil(h * zoom);
|
||||||
|
|
||||||
if (dstW == 0 || dstH == 0) {
|
if (dstW == 0 || dstH == 0) {
|
||||||
continue;
|
continue;
|
||||||
@ -678,8 +689,8 @@ public class MappedBufferImage {
|
|||||||
// final int tileSrcH = Math.min(tileSize, image.getHeight() - tileSrcY);
|
// final int tileSrcH = Math.min(tileSize, image.getHeight() - tileSrcY);
|
||||||
|
|
||||||
// Destination (display) coordinates
|
// Destination (display) coordinates
|
||||||
int tileDstX = (int) Math.round(tileSrcX * zoom);
|
int tileDstX = (int) Math.floor(tileSrcX * zoom);
|
||||||
int tileDstY = (int) Math.round(tileSrcY * zoom);
|
int tileDstY = (int) Math.floor(tileSrcY * zoom);
|
||||||
// final int tileDstW = (int) Math.round(tileSrcW * zoom);
|
// final int tileDstW = (int) Math.round(tileSrcW * zoom);
|
||||||
// final int tileDstH = (int) Math.round(tileSrcH * zoom);
|
// final int tileDstH = (int) Math.round(tileSrcH * zoom);
|
||||||
|
|
||||||
@ -699,9 +710,7 @@ public class MappedBufferImage {
|
|||||||
Tile tile = tiles.get(point);
|
Tile tile = tiles.get(point);
|
||||||
|
|
||||||
if (tile != null) {
|
if (tile != null) {
|
||||||
Reference<BufferedImage> img = tile.data;
|
if (tile.drawTo(g2)) {
|
||||||
if (img != null) {
|
|
||||||
tile.drawTo(g2);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -713,9 +722,8 @@ public class MappedBufferImage {
|
|||||||
|
|
||||||
// Dispatch to off-thread worker
|
// Dispatch to off-thread worker
|
||||||
final Map<Point, Tile> localTiles = tiles;
|
final Map<Point, Tile> localTiles = tiles;
|
||||||
executorService.submit(new Runnable() {
|
executorService2.submit(new Runnable() {
|
||||||
public void run() {
|
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 tileSrcX = (int) Math.round(point.x / zoom);
|
||||||
int tileSrcY = (int) Math.round(point.y / zoom);
|
int tileSrcY = (int) Math.round(point.y / zoom);
|
||||||
int tileSrcW = Math.min(tileSize, image.getWidth() - tileSrcX);
|
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
|
// Test against current view rect, to avoid computing tiles that will be thrown away immediately
|
||||||
// TODO: EDT safe?
|
final Rectangle visibleRect = new Rectangle();
|
||||||
if (!getVisibleRect().intersects(new Rectangle(point.x, point.y, tileDstW, tileDstH))) {
|
SwingUtilities.invokeAndWait(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
visibleRect.setBounds(getVisibleRect());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!visibleRect.intersects(new Rectangle(point.x, point.y, tileDstW, tileDstH))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,11 +30,12 @@ package com.twelvemonkeys.image;
|
|||||||
|
|
||||||
import javax.imageio.ImageTypeSpecifier;
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.*;
|
||||||
import java.awt.image.ColorModel;
|
|
||||||
import java.awt.image.DataBuffer;
|
|
||||||
import java.awt.image.SampleModel;
|
|
||||||
import java.io.IOException;
|
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.
|
* 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.
|
// 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 :-/
|
// - 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() {}
|
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 {
|
||||||
@ -58,7 +62,8 @@ public final class MappedImageFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static BufferedImage createCompatibleMappedImage(int width, int height, GraphicsConfiguration configuration, int transparency) throws IOException {
|
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));
|
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 {
|
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);
|
||||||
|
|
||||||
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