From f419cfecdd15c12f35a1fa0d82b7c2364976154d Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Tue, 12 Oct 2010 10:40:55 +0200 Subject: [PATCH] Cleaned up the NIO buffered image classes. --- sandbox/pom.xml | 104 +++++-- sandbox/sandbox-common/pom.xml | 28 ++ .../image/GenericWritableRaster.java | 13 +- .../image/MappedBufferImage.java | 285 ++++++++++++++---- .../twelvemonkeys/image/MappedFileBuffer.java | 38 ++- .../image/MappedImageFactory.java | 40 ++- 6 files changed, 390 insertions(+), 118 deletions(-) diff --git a/sandbox/pom.xml b/sandbox/pom.xml index 7d8b667b..c9c9a2bc 100644 --- a/sandbox/pom.xml +++ b/sandbox/pom.xml @@ -8,7 +8,7 @@ 3.0-SNAPSHOT TwelveMonkeys :: Sandbox pom - + The TwelveMonkeys Sandbox. Experimental stuff, in progress, not for production use. @@ -25,35 +25,88 @@ - - com.twelvemonkeys.common - common-image - ${project.version} - compile - - - - com.twelvemonkeys.common - common-image - ${project.version} - tests - test - - junit junit - 4.3.1 test jmock jmock-cglib - 1.0.1 test + + + + + com.twelvemonkeys.common + common-lang + ${project.version} + compile + + + com.twelvemonkeys.common + common-io + ${project.version} + compile + + + com.twelvemonkeys.common + common-image + ${project.version} + compile + + + com.twelvemonkeys.swing + swing-core + ${project.version} + compile + + + com.twelvemonkeys.swing + swing-application + ${project.version} + compile + + + com.twelvemonkeys.imageio + imageio-core + ${project.version} + provided + + + + com.twelvemonkeys.common + common-io + ${project.version} + test + tests + + + com.twelvemonkeys.common + common-lang + ${project.version} + test + tests + + + + junit + junit + 4.3.1 + test + + + + jmock + jmock-cglib + 1.0.1 + test + + + @@ -64,7 +117,7 @@ maven-resources-plugin - UTF-8 + UTF-8 @@ -84,5 +137,18 @@ + + + + + org.apache.maven.plugins + maven-surefire-plugin + true + + true + + + + \ No newline at end of file diff --git a/sandbox/sandbox-common/pom.xml b/sandbox/sandbox-common/pom.xml index 28e295ee..e713c7eb 100644 --- a/sandbox/sandbox-common/pom.xml +++ b/sandbox/sandbox-common/pom.xml @@ -15,4 +15,32 @@ The TwelveMonkeys Sandbox Common. Experimental stuff. + + + com.twelvemonkeys.common + common-io + compile + + + + com.twelvemonkeys.common + common-image + compile + + + + com.twelvemonkeys.common + common-io + test + tests + + + + com.twelvemonkeys.common + common-lang + test + tests + + + diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/GenericWritableRaster.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/GenericWritableRaster.java index b80493c5..f6b0a9ce 100644 --- a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/GenericWritableRaster.java +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/GenericWritableRaster.java @@ -6,7 +6,9 @@ import java.awt.image.SampleModel; import java.awt.image.WritableRaster; /** - * GenericWritableRaster + * A generic writable raster. + * For use when factory methods from {@link java.awt.image.Raster} can't be used, + * typically because of custom data buffers. * * @author Harald Kuhr * @author last modified by $Author: haraldk$ @@ -19,6 +21,13 @@ class GenericWritableRaster extends WritableRaster { @Override public String toString() { - return String.format("%s@%x: w = %s h = %s", getClass().getSimpleName(), System.identityHashCode(this), getWidth(), getHeight()); + return String.format( + "%s: %s width = %s height = %s #Bands = %s xOff = %s yOff = %s %s", + getClass().getSimpleName(), + sampleModel, + getWidth(), getHeight(), getNumBands(), + sampleModelTranslateX, sampleModelTranslateY, + dataBuffer + ); } } diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedBufferImage.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedBufferImage.java index 99fd4d0b..0763c6a8 100644 --- a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedBufferImage.java +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedBufferImage.java @@ -1,11 +1,22 @@ package com.twelvemonkeys.image; +import javax.imageio.ImageIO; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageReader; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.stream.ImageInputStream; import javax.swing.*; import java.awt.*; +import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.awt.image.DataBuffer; +import java.io.File; import java.io.IOException; +import java.util.Iterator; import java.util.Random; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; /** * MappedBufferImage @@ -15,53 +26,99 @@ import java.util.Random; * @version $Id: MappedBufferImage.java,v 1.0 Jun 13, 2010 7:33:19 PM haraldk Exp$ */ public class MappedBufferImage { - private static final boolean ALPHA = true; + private static int threads = Runtime.getRuntime().availableProcessors(); public static void main(String[] args) throws IOException { - int w = args.length > 0 ? Integer.parseInt(args[0]) : 6000; - int h = args.length > 1 ? Integer.parseInt(args[1]) : w * 2 / 3; + int w; + int h; + BufferedImage image; + File file = args.length > 0 ? new File(args[0]) : null; + + if (file != null && file.exists()) { + // Load image using ImageIO + ImageInputStream input = ImageIO.createImageInputStream(file); + Iterator readers = ImageIO.getImageReaders(input); - GraphicsConfiguration configuration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); - BufferedImage image = MappedImageFactory.createCompatibleMappedImage(w, h, configuration, MappedBufferImage.ALPHA ? Transparency.TRANSLUCENT : Transparency.OPAQUE); - - System.out.println("image = " + image); - - DataBuffer buffer = image.getRaster().getDataBuffer(); - - // Mix in some nice colors - for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - int r = (int) ((x * y * 255.0) / (h * w)); - int g = (int) (((w - x) * y * 255.0) / (h * w)); - int b = (int) ((x * (h - y) * 255.0) / (h * w)); - int a = ALPHA ? (int) (((w - x) * (h - y) * 255.0) / (h * w)) : 0; - - switch (buffer.getDataType()) { - case DataBuffer.TYPE_BYTE: - int off = (y * w + x) * (ALPHA ? 4 : 3); - if (ALPHA) { - buffer.setElem(off++, 255 - a); - buffer.setElem(off++, b); - buffer.setElem(off++, g); - buffer.setElem(off, r); - } - else { - // TODO: Why the RGB / ABGR byte order inconsistency?? - buffer.setElem(off++, r); - buffer.setElem(off++, g); - buffer.setElem(off, b); - } - break; - case DataBuffer.TYPE_INT: - buffer.setElem(y * w + x, (255 - a) << 24 | r << 16 | g << 8 | b); - break; - default: - System.err.println("Transfer type not supported: " + buffer.getDataType()); - } + if (!readers.hasNext()) { + System.err.println("No image reader found for input: " + file.getAbsolutePath()); + System.exit(0); + return; } + + ImageReader reader = readers.next(); + reader.setInput(input); + + Iterator types = reader.getImageTypes(0); + ImageTypeSpecifier type = types.next(); + + // TODO: Negotiate best layout according to the GraphicsConfiguration. + + w = reader.getWidth(0); + h = reader.getHeight(0); + +// GraphicsConfiguration configuration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); +// ColorModel cm2 = configuration.getColorModel(cm.getTransparency()); + +// image = MappedImageFactory.createCompatibleMappedImage(w, h, cm2); +// image = MappedImageFactory.createCompatibleMappedImage(w, h, cm); +// image = MappedImageFactory.createCompatibleMappedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR); + image = MappedImageFactory.createCompatibleMappedImage(w, h, type); +// image = type.createBufferedImage(w, h); + + System.out.println("image = " + image); + + ImageReadParam param = reader.getDefaultReadParam(); + param.setDestination(image); + + reader.read(0, param); + } + else { + w = args.length > 0 ? Integer.parseInt(args[0]) : 6000; + h = args.length > 1 ? Integer.parseInt(args[1]) : w * 2 / 3; + + GraphicsConfiguration configuration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); + image = MappedImageFactory.createCompatibleMappedImage(w, h, configuration, Transparency.TRANSLUCENT); +// image = MappedImageFactory.createCompatibleMappedImage(w, h, configuration, Transparency.OPAQUE); +// image = MappedImageFactory.createCompatibleMappedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR); + + System.out.println("image = " + image); + + DataBuffer buffer = image.getRaster().getDataBuffer(); + final boolean alpha = image.getColorModel().hasAlpha(); + + // Mix in some nice colors + createBackground(w, h, buffer, alpha); + + // Add some random dots (get out the coffee) + paintDots(w, h, image); } - // Add some random dots (get out the coffee) + int bytesPerPixel = image.getColorModel().getPixelSize() / 8; // Calculate first to avoid overflow + JFrame frame = new JFrame(String.format("Test [%s x %s] (%s)", w, h, toHumanReadableSize(w * h * bytesPerPixel))) { + @Override + public Dimension getPreferredSize() { + // TODO: This looks like a useful util method... + DisplayMode displayMode = getGraphicsConfiguration().getDevice().getDisplayMode(); + Dimension size = super.getPreferredSize(); + + size.width = Math.min(size.width, displayMode.getWidth()); + size.height = Math.min(size.height, displayMode.getHeight()); + + return size; + } + }; + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + JScrollPane scroll = new JScrollPane(new ImageComponent(image)); + scroll.setBorder(BorderFactory.createEmptyBorder()); + frame.add(scroll); + frame.pack(); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + } + + private static void paintDots(int w, int h, BufferedImage image) { + long start = System.currentTimeMillis(); + int s = 300; int ws = w / s; int hs = h / s; @@ -72,9 +129,28 @@ public class MappedBufferImage { }; Random r = new Random(); + ExecutorService executorService = Executors.newFixedThreadPool(threads); - long start = System.currentTimeMillis(); - for (int y = 0; y < hs - 1; y++) { + int step = (int) Math.ceil(hs / (double) threads); + + for (int i = 0; i < threads; i++) { + executorService.submit(new PaintDotsTask(image, s, ws, colors, r, i * step, i * step + step)); + } + System.err.printf("Started painting in %d threads, waiting for execution to complete...%n", threads); + + Boolean done = null; + try { + executorService.shutdown(); + done = executorService.awaitTermination(3L, TimeUnit.MINUTES); + } + catch (InterruptedException ignore) { + } + + System.out.printf("%s painting %d dots in %d ms%n", (done == null ? "Interrupted" : !done ? "Timed out" : "Done"), Math.max(0, hs - 1) * Math.max(0, ws - 1), System.currentTimeMillis() - start); + } + + private static void paintDots0(BufferedImage image, int s, int ws, Color[] colors, Random r, final int first, final int last) { + for (int y = first; y < last; y++) { for (int x = 0; x < ws - 1; x++) { BufferedImage tile = image.getSubimage(x * s, y * s, 2 * s, 2 * s); Graphics2D g; @@ -100,26 +176,75 @@ public class MappedBufferImage { } } } + } - System.out.printf("Done painting %d dots in %d ms%n", hs * ws, System.currentTimeMillis() - start); + private static void createBackground(int w, int h, DataBuffer buffer, boolean alpha) { + long start = System.currentTimeMillis(); - JFrame frame = new JFrame(String.format("Test [%s x %s] (%s)", w, h, toHumanReadableSize(w * h * (ALPHA ? 4 : 3)))); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - JScrollPane scroll = new JScrollPane(new ImageComponent(image)); - scroll.setBorder(BorderFactory.createEmptyBorder()); - frame.add(scroll); - frame.pack(); - frame.setLocationRelativeTo(null); - frame.setVisible(true); + int step = (int) Math.ceil(h / (double) threads); + + ExecutorService executorService = Executors.newFixedThreadPool(threads); + for (int i = 0; i < threads; i++) { + executorService.submit(new PaintBackgroundTask(w, h, buffer, alpha, i * step, i * step + step)); + } + System.err.printf("Started painting in %d threads, waiting for execution to complete...%n", threads); + + Boolean done = null; + try { + executorService.shutdown(); + done = executorService.awaitTermination(3L, TimeUnit.MINUTES); + } + catch (InterruptedException ignore) { + } + + System.out.printf("%s creating background in %d ms%n", (done == null ? "Interrupted" : !done ? "Timed out" : "Done"), System.currentTimeMillis() - start); + } + + private static void paintBackground0(int w, int h, DataBuffer buffer, boolean alpha, final int first, final int last) { + for (int y = first; y < last; y++) { + for (int x = 0; x < w; x++) { + int r = (int) ((x * y * 255.0) / (h * w)); + int g = (int) (((w - x) * y * 255.0) / (h * w)); + int b = (int) ((x * (h - y) * 255.0) / (h * w)); + int a = alpha ? (int) (((w - x) * (h - y) * 255.0) / (h * w)) : 0; + + switch (buffer.getDataType()) { + case DataBuffer.TYPE_BYTE: + int off = (y * w + x) * (alpha ? 4 : 3); + if (alpha) { + buffer.setElem(off++, 255 - a); + buffer.setElem(off++, b); + buffer.setElem(off++, g); + buffer.setElem(off, r); + } + else { + // TODO: Why the RGB / ABGR byte order inconsistency?? + buffer.setElem(off++, r); + buffer.setElem(off++, g); + buffer.setElem(off, b); + } + break; + case DataBuffer.TYPE_INT: + buffer.setElem(y * w + x, (255 - a) << 24 | r << 16 | g << 8 | b); + break; + default: + System.err.println("Transfer type not supported: " + buffer.getDataType()); + } + } + } } private static String toHumanReadableSize(long size) { - return String.format("%,d MB", (int) (size / (double) (1024L << 10))); + return String.format("%,d MB", (long) (size / (double) (1024L << 10))); } + /** + * A fairly optimized component for displaying a BufferedImage + */ private static class ImageComponent extends JComponent implements Scrollable { private final BufferedImage image; private Paint texture; + double zoom = 1; public ImageComponent(final BufferedImage image) { setOpaque(true); // Very important when subclassing JComponent... @@ -166,6 +291,11 @@ public class MappedBufferImage { g2.setPaint(texture); g2.fillRect(rect.x, rect.y, rect.width, rect.height); + if (zoom != 1) { + AffineTransform transform = AffineTransform.getScaleInstance(zoom, zoom); + g2.setTransform(transform); + } + long start = System.currentTimeMillis(); repaintImage(rect, g2); System.err.println("repaint: " + (System.currentTimeMillis() - start) + " ms"); @@ -204,14 +334,13 @@ public class MappedBufferImage { // e.printStackTrace(); // Happens whenever apple.awt.OSXCachingSufraceManager runs out of memory // TODO: Figure out why repaint(x,y,w,h) doesn't work any more..? -// repaint(rect.x, rect.y, rect.width, rect.height); // NOTE: Will cause a brief flash while the component is redrawn repaint(); // NOTE: Might cause a brief flash while the component is redrawn } } @Override public Dimension getPreferredSize() { - return new Dimension(image.getWidth(), image.getHeight()); + return new Dimension((int) (image.getWidth() * zoom), (int) (image.getHeight() * zoom)); } public Dimension getPreferredScrollableViewportSize() { @@ -240,4 +369,50 @@ public class MappedBufferImage { return false; } } + + private static class PaintDotsTask implements Runnable { + private final BufferedImage image; + private final int s; + private final int wstep; + private final Color[] colors; + private final Random random; + private final int last; + private final int first; + + public PaintDotsTask(BufferedImage image, int s, int wstep, Color[] colors, Random random, int first, int last) { + this.image = image; + this.s = s; + this.wstep = wstep; + this.colors = colors; + this.random = random; + this.last = last; + this.first = first; + } + + public void run() { + paintDots0(image, s, wstep, colors, random, first, last); + } + } + + private static class PaintBackgroundTask implements Runnable { + private final int w; + private final int h; + private final DataBuffer buffer; + private final boolean alpha; + private final int first; + private final int last; + + public PaintBackgroundTask(int w, int h, DataBuffer buffer, boolean alpha, int first, int last) { + this.w = w; + this.h = h; + this.buffer = buffer; + this.alpha = alpha; + this.first = first; + this.last = last; + } + + public void run() { + paintBackground0(w, h, buffer, alpha, first, last); + } + } } diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedFileBuffer.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedFileBuffer.java index 784f5d19..1581e53c 100644 --- a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedFileBuffer.java +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedFileBuffer.java @@ -8,14 +8,18 @@ import java.nio.*; import java.nio.channels.FileChannel; /** - * MappedFileBuffer + * A {@code DataBuffer} implementation that is backed by a memory mapped file. + * Memory will be allocated outside the normal JVM heap, allowing more efficient + * memory usage for large buffers. * * @author Harald Kuhr * @author last modified by $Author: haraldk$ * @version $Id: MappedFileBuffer.java,v 1.0 Jun 12, 2010 4:56:51 PM haraldk Exp$ + * + * @see java.nio.channels.FileChannel#map(java.nio.channels.FileChannel.MapMode, long, long) */ public abstract class MappedFileBuffer extends DataBuffer { - final Buffer buffer; + private final Buffer buffer; private MappedFileBuffer(final int type, final int size, final int numBanks) throws IOException { super(type, size, numBanks); @@ -24,23 +28,10 @@ public abstract class MappedFileBuffer extends DataBuffer { throw new IllegalArgumentException("Integer overflow for size: " + size); } - int componentSize; - switch (type) { - case DataBuffer.TYPE_BYTE: - componentSize = 1; - break; - case DataBuffer.TYPE_USHORT: - componentSize = 2; - break; - case DataBuffer.TYPE_INT: - componentSize = 4; - break; - default: - throw new IllegalArgumentException("Unsupported data type: " + type); - } + int componentSize = DataBuffer.getDataTypeSize(type) / 8; - File tempFile = File.createTempFile(String.format("%s-", getClass().getSimpleName()), ".tmp"); - tempFile.deleteOnExit(); + // Create temp file to get a file handle to use for memory mapping + File tempFile = File.createTempFile(String.format("%s-", getClass().getSimpleName().toLowerCase()), ".tmp"); try { RandomAccessFile raf = new RandomAccessFile(tempFile, "rw"); @@ -52,7 +43,6 @@ public abstract class MappedFileBuffer extends DataBuffer { // Map entire file into memory, let OS virtual memory/paging do the heavy lifting MappedByteBuffer byteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, length); -// MappedByteBuffer byteBuffer = channel.map(FileChannel.MapMode.PRIVATE, 0, length); switch (type) { case DataBuffer.TYPE_BYTE: @@ -72,15 +62,21 @@ public abstract class MappedFileBuffer extends DataBuffer { channel.close(); } finally { + // NOTE: File can't be deleted right now on Windows, as the file is open. Let JVM clean up later if (!tempFile.delete()) { - System.err.println("Could not delete temp file: " + tempFile.getAbsolutePath()); + tempFile.deleteOnExit(); } } } + @Override + public String toString() { + return String.format("MappedFileBuffer: %s", buffer); + } + // TODO: Is throws IOException a good idea? - public static MappedFileBuffer create(final int type, final int size, final int numBanks) throws IOException { + public static DataBuffer create(final int type, final int size, final int numBanks) throws IOException { switch (type) { case DataBuffer.TYPE_BYTE: return new DataBufferByte(size, numBanks); diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedImageFactory.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedImageFactory.java index 6a241909..3ed5524c 100644 --- a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedImageFactory.java +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedImageFactory.java @@ -1,5 +1,6 @@ package com.twelvemonkeys.image; +import javax.imageio.ImageTypeSpecifier; import java.awt.*; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; @@ -8,42 +9,39 @@ import java.awt.image.SampleModel; import java.io.IOException; /** - * MappedImageFactory + * A factory for creating {@link BufferedImage}s backed by memory mapped files. + * The data buffers will be allocated outside the normal JVM heap, allowing more efficient + * memory usage for large images. * * @author Harald Kuhr * @author last modified by $Author: haraldk$ * @version $Id: MappedImageFactory.java,v 1.0 May 26, 2010 5:07:01 PM haraldk Exp$ */ public class MappedImageFactory { - public static BufferedImage createCompatibleMappedImage(int width, int height, int type) throws IOException { - return createCompatibleMappedImage(width, height, new BufferedImage(1, 1, type).getColorModel()); + BufferedImage temp = new BufferedImage(1, 1, type); + return createCompatibleMappedImage(width, height, temp.getSampleModel().createCompatibleSampleModel(width, height), temp.getColorModel()); } public static BufferedImage createCompatibleMappedImage(int width, int height, GraphicsConfiguration configuration, int transparency) throws IOException { - ColorModel cm = configuration.getColorModel(transparency); -// ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); -// ColorModel cm = new ComponentColorModel(cs, ALPHA, false, ALPHA ? TRANSLUCENT : OPAQUE, DataBuffer.TYPE_BYTE); - return createCompatibleMappedImage(width, height, cm); + // TODO: Should we also use the sample model? + return createCompatibleMappedImage(width, height, configuration.getColorModel(transparency)); } - private static BufferedImage createCompatibleMappedImage(int width, int height, ColorModel cm) throws IOException { - SampleModel sm = cm.createCompatibleSampleModel(width, height); + public static BufferedImage createCompatibleMappedImage(int width, int height, ImageTypeSpecifier type) throws IOException { + return createCompatibleMappedImage(width, height, type.getSampleModel(width, height), type.getColorModel()); + } -// System.err.println("cm: " + cm); -// System.err.println("cm.getNumComponents(): " + cm.getNumComponents()); -// System.err.println("cm.getPixelSize(): " + cm.getPixelSize()); -// System.err.println("cm.getComponentSize(): " + Arrays.toString(cm.getComponentSize())); -// System.err.println("sm.getNumDataElements(): " + sm.getNumDataElements()); -// System.err.println("sm.getNumBands(): " + sm.getNumBands()); -// System.err.println("sm.getSampleSize(): " + Arrays.toString(sm.getSampleSize())); + static BufferedImage createCompatibleMappedImage(int width, int height, ColorModel cm) throws IOException { + return createCompatibleMappedImage(width, height, cm.createCompatibleSampleModel(width, height), cm); + } + 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 = sm.createDataBuffer(); +// DataBuffer buffer = DirectBuffer.create(sm.getTransferType(), width * height * sm.getNumDataElements(), 1); - BufferedImage image = new BufferedImage(cm, new GenericWritableRaster(sm, buffer, new Point()), cm.isAlphaPremultiplied(), null); -// BufferedImage image = new BufferedImage(cm, new SunWritableRaster(sm, buffer, new Point()), cm.isAlphaPremultiplied(), null); -// BufferedImage image = new BufferedImage(cm, Raster.createWritableRaster(sm, buffer, null), false null); - return image; + // 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); } }