diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/BufferedImageFactory.java b/common/common-image/src/main/java/com/twelvemonkeys/image/BufferedImageFactory.java index 69de39b3..d6b097ff 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/BufferedImageFactory.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/BufferedImageFactory.java @@ -259,11 +259,9 @@ public final class BufferedImageFactory { sourceProperties = null; } - private void processProgress(int mScanline) { + private void processProgress(int scanline) { if (listeners != null) { - int percent = 100 * mScanline / height; - - //System.out.println("Progress: " + percent + "%"); + int percent = 100 * scanline / height; if (percent > percentageDone) { percentageDone = percent; @@ -323,7 +321,7 @@ public final class BufferedImageFactory { * pixels. The conversion is done, by masking out the * higher 16 bits of the {@code int}. * - * For eny given {@code int}, the {@code short} value is computed as + * For any given {@code int}, the {@code short} value is computed as * follows: *
{@code * short value = (short) (intValue & 0x0000ffff); @@ -334,9 +332,11 @@ public final class BufferedImageFactory { */ private static short[] toShortPixels(int[] pPixels) { short[] pixels = new short[pPixels.length]; + for (int i = 0; i < pixels.length; i++) { pixels[i] = (short) (pPixels[i] & 0xffff); } + return pixels; } @@ -507,24 +507,11 @@ public final class BufferedImageFactory { } public void setPixels(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, byte[] pPixels, int pOffset, int pScanSize) { - /*if (pModel.getPixelSize() < 8) { - // Byte packed - setPixelsImpl(pX, pY, pWidth, pHeight, pModel, toBytePackedPixels(pPixels, pModel.getPixelSize()), pOffset, pScanSize); - } - /* - else if (pModel.getPixelSize() > 8) { - // Byte interleaved - setPixelsImpl(pX, pY, pWidth, pHeight, pModel, toByteInterleavedPixels(pPixels), pOffset, pScanSize); - } - */ - //else { - // Default, pixelSize == 8, one byte pr pixel - setPixelsImpl(pX, pY, pWidth, pHeight, pModel, pPixels, pOffset, pScanSize); - //} + setPixelsImpl(pX, pY, pWidth, pHeight, pModel, pPixels, pOffset, pScanSize); } public void setPixels(int pX, int pY, int pWeigth, int pHeight, ColorModel pModel, int[] pPixels, int pOffset, int pScanSize) { - if (ImageUtil.getTransferType(pModel) == DataBuffer.TYPE_USHORT) { + if (pModel.getTransferType() == DataBuffer.TYPE_USHORT) { // NOTE: Workaround for limitation in ImageConsumer API // Convert int[] to short[], to be compatible with the ColorModel setPixelsImpl(pX, pY, pWeigth, pHeight, pModel, toShortPixels(pPixels), pOffset, pScanSize); @@ -538,4 +525,86 @@ public final class BufferedImageFactory { sourceProperties = pProperties; } } + + /* + public static void main(String[] args) throws InterruptedException { + Image image = Toolkit.getDefaultToolkit().createImage(args[0]); + System.err.printf("image: %s (which is %sa buffered image)\n", image, image instanceof BufferedImage ? "" : "not "); + + int warmUpLoops = 500; + int testLoops = 100; + + for (int i = 0; i < warmUpLoops; i++) { + // Warm up... + convertUsingFactory(image); + convertUsingPixelGrabber(image); + convertUsingPixelGrabberNaive(image); + } + + BufferedImage bufferedImage = null; + long start = System.currentTimeMillis(); + for (int i = 0; i < testLoops; i++) { + bufferedImage = convertUsingFactory(image); + } + System.err.printf("Conversion time (factory): %f ms (image: %s)\n", (System.currentTimeMillis() - start) / (double) testLoops, bufferedImage); + + start = System.currentTimeMillis(); + for (int i = 0; i < testLoops; i++) { + bufferedImage = convertUsingPixelGrabber(image); + } + System.err.printf("Conversion time (grabber): %f ms (image: %s)\n", (System.currentTimeMillis() - start) / (double) testLoops, bufferedImage); + + start = System.currentTimeMillis(); + for (int i = 0; i < testLoops; i++) { + bufferedImage = convertUsingPixelGrabberNaive(image); + } + System.err.printf("Conversion time (naive g): %f ms (image: %s)\n", (System.currentTimeMillis() - start) / (double) testLoops, bufferedImage); + } + + private static BufferedImage convertUsingPixelGrabberNaive(Image image) throws InterruptedException { + // NOTE: It does not matter if we wait for the image or not, the time is about the same as it will only happen once + if ((image.getWidth(null) < 0 || image.getHeight(null) < 0) && !ImageUtil.waitForImage(image)) { + System.err.printf("Could not get image dimensions for image %s\n", image.getSource()); + } + + int w = image.getWidth(null); + int h = image.getHeight(null); + PixelGrabber grabber = new PixelGrabber(image, 0, 0, w, h, true); // force RGB + grabber.grabPixels(); + + // Following casts are safe, as we force RGB in the pixel grabber + int[] pixels = (int[]) grabber.getPixels(); + + BufferedImage bufferedImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); +// bufferedImage.setRGB(0, 0, w, h, pixels, 0, w); + bufferedImage.getRaster().setDataElements(0, 0, w, h, pixels); + + return bufferedImage; + } + + private static BufferedImage convertUsingPixelGrabber(Image image) throws InterruptedException { + // NOTE: It does not matter if we wait for the image or not, the time is about the same as it will only happen once + if ((image.getWidth(null) < 0 || image.getHeight(null) < 0) && !ImageUtil.waitForImage(image)) { + System.err.printf("Could not get image dimensions for image %s\n", image.getSource()); + } + + int w = image.getWidth(null); + int h = image.getHeight(null); + PixelGrabber grabber = new PixelGrabber(image, 0, 0, w, h, true); // force RGB + grabber.grabPixels(); + + // Following casts are safe, as we force RGB in the pixel grabber +// DirectColorModel cm = (DirectColorModel) grabber.getColorModel(); + DirectColorModel cm = (DirectColorModel) ColorModel.getRGBdefault(); + int[] pixels = (int[]) grabber.getPixels(); + + WritableRaster raster = Raster.createPackedRaster(new DataBufferInt(pixels, pixels.length), w, h, w, cm.getMasks(), null); + + return new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null); + } + + private static BufferedImage convertUsingFactory(Image image) { + return new BufferedImageFactory(image).getBufferedImage(); + } + */ } \ No newline at end of file diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java b/common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java index 721051b9..c3d2f87d 100644 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java @@ -181,9 +181,6 @@ public final class ImageUtil { /** */ protected static final Point LOCATION_UPPER_LEFT = new Point(0, 0); - /** */ - private static final boolean COLORMODEL_TRANSFERTYPE_SUPPORTED = isColorModelTransferTypeSupported(); - /** */ private static final GraphicsConfiguration DEFAULT_CONFIGURATION = getDefaultGraphicsConfiguration(); @@ -205,22 +202,6 @@ public final class ImageUtil { private ImageUtil() { } - /** - * Tests if {@code ColorModel} has a {@code getTransferType} method. - * - * @return {@code true} if {@code ColorModel} has a - * {@code getTransferType} method - */ - private static boolean isColorModelTransferTypeSupported() { - try { - ColorModel.getRGBdefault().getTransferType(); - return true; - } - catch (Throwable t) { - return false; - } - } - /** * Converts the {@code RenderedImage} to a {@code BufferedImage}. * The new image will have the same {@code ColorModel}, @@ -378,7 +359,7 @@ public final class ImageUtil { /** * Creates a copy of the given image. The image will have the same - * colormodel and raster type, but will not share image (pixel) data. + * color model and raster type, but will not share image (pixel) data. * * @param pImage the image to clone. * @@ -408,11 +389,11 @@ public final class ImageUtil { *

* This method is optimized for the most common cases of {@code ColorModel} * and pixel data combinations. The raster's backing {@code DataBuffer} is - * created directly from the pixel data, as this is faster and with more + * created directly from the pixel data, as this is faster and more * resource-friendly than using * {@code ColorModel.createCompatibleWritableRaster(w, h)}. *

- * For unknown combinations, the method will fallback to using + * For uncommon combinations, the method will fallback to using * {@code ColorModel.createCompatibleWritableRaster(w, h)} and * {@code WritableRaster.setDataElements(w, h, pixels)} *

@@ -438,8 +419,8 @@ public final class ImageUtil { */ static WritableRaster createRaster(int pWidth, int pHeight, Object pPixels, ColorModel pColorModel) { // NOTE: This is optimized code for most common cases. - // We create a DataBuffer with the array from grabber.getPixels() - // directly, and creating a raster based on the ColorModel. + // We create a DataBuffer from the pixel array directly, + // and creating a raster based on the DataBuffer and ColorModel. // Creating rasters this way is faster and more resource-friendly, as // cm.createCompatibleWritableRaster allocates an // "empty" DataBuffer with a storage array of w*h. This array is @@ -453,14 +434,12 @@ public final class ImageUtil { if (pPixels instanceof int[]) { int[] data = (int[]) pPixels; buffer = new DataBufferInt(data, data.length); - //bands = data.length / (w * h); bands = pColorModel.getNumComponents(); } else if (pPixels instanceof short[]) { short[] data = (short[]) pPixels; buffer = new DataBufferUShort(data, data.length); bands = data.length / (pWidth * pHeight); - //bands = cm.getNumComponents(); } else if (pPixels instanceof byte[]) { byte[] data = (byte[]) pPixels; @@ -473,47 +452,30 @@ public final class ImageUtil { else { bands = data.length / (pWidth * pHeight); } - - //bands = pColorModel.getNumComponents(); - //System.out.println("Pixels: " + data.length + " (" + buffer.getSize() + ")"); - //System.out.println("w*h*bands: " + (pWidth * pHeight * bands)); - //System.out.println("Bands: " + bands); - //System.out.println("Numcomponents: " + pColorModel.getNumComponents()); } else { - //System.out.println("Fallback!"); // Fallback mode, slower & requires more memory, but compatible bands = -1; - // Create raster from colormodel, w and h + // Create raster from color model, w and h raster = pColorModel.createCompatibleWritableRaster(pWidth, pHeight); raster.setDataElements(0, 0, pWidth, pHeight, pPixels); // Note: This is known to throw ClassCastExceptions.. } - //System.out.println("Bands: " + bands); - //System.out.println("Pixels: " + pixels.getClass() + " length: " + buffer.getSize()); - //System.out.println("Needed Raster: " + cm.createCompatibleWritableRaster(1, 1)); - if (raster == null) { - //int bits = cm.getPixelSize(); - //if (bits > 4) { if (pColorModel instanceof IndexColorModel && isIndexedPacked((IndexColorModel) pColorModel)) { - //System.out.println("Creating packed indexed model"); raster = Raster.createPackedRaster(buffer, pWidth, pHeight, pColorModel.getPixelSize(), LOCATION_UPPER_LEFT); } else if (pColorModel instanceof PackedColorModel) { - //System.out.println("Creating packed model"); PackedColorModel pcm = (PackedColorModel) pColorModel; raster = Raster.createPackedRaster(buffer, pWidth, pHeight, pWidth, pcm.getMasks(), LOCATION_UPPER_LEFT); } else { - //System.out.println("Creating interleaved model"); // (A)BGR order... For TYPE_3BYTE_BGR/TYPE_4BYTE_ABGR/TYPE_4BYTE_ABGR_PRE. int[] bandsOffsets = new int[bands]; for (int i = 0; i < bands;) { bandsOffsets[i] = bands - (++i); } - //System.out.println("zzz Data array: " + buffer.getSize()); raster = Raster.createInterleavedRaster(buffer, pWidth, pHeight, pWidth * bands, bands, bandsOffsets, LOCATION_UPPER_LEFT); } @@ -1782,7 +1744,7 @@ public final class ImageUtil { * @param pTimeOut the time to wait, in milliseconds. * * @return true if the image was loaded successfully, false if an error - * occured, or the wait was interrupted. + * occurred, or the wait was interrupted. * * @see #waitForImages(Image[],long) */ @@ -1797,7 +1759,7 @@ public final class ImageUtil { * @param pImages an array of Image objects to wait for. * * @return true if the images was loaded successfully, false if an error - * occured, or the wait was interrupted. + * occurred, or the wait was interrupted. * * @see #waitForImages(Image[],long) */ @@ -1813,7 +1775,7 @@ public final class ImageUtil { * @param pTimeOut the time to wait, in milliseconds * * @return true if the images was loaded successfully, false if an error - * occured, or the wait was interrupted. + * occurred, or the wait was interrupted. */ public static boolean waitForImages(Image[] pImages, long pTimeOut) { // TODO: Need to make sure that we don't wait for the same image many times @@ -1868,7 +1830,7 @@ public final class ImageUtil { } /** - * Tests wether the image has any transparent or semi-transparent pixels. + * Tests whether the image has any transparent or semi-transparent pixels. * * @param pImage the image * @param pFast if {@code true}, the method tests maximum 10 x 10 pixels, @@ -1936,7 +1898,7 @@ public final class ImageUtil { } /** - * Blends two ARGB values half and half, to create a tone inbetween. + * Blends two ARGB values half and half, to create a tone in between. * * @param pRGB1 color 1 * @param pRGB2 color 2 @@ -1949,7 +1911,7 @@ public final class ImageUtil { } /** - * Blends two colors half and half, to create a tone inbetween. + * Blends two colors half and half, to create a tone in between. * * @param pColor color 1 * @param pOther color 2 @@ -1967,7 +1929,7 @@ public final class ImageUtil { } /** - * Blends two colors, controlled by the blendfactor. + * Blends two colors, controlled by the blending factor. * A factor of {@code 0.0} will return the first color, * a factor of {@code 1.0} will return the second. * @@ -1989,50 +1951,4 @@ public final class ImageUtil { private static int clamp(float f) { return (int) f; } - /** - * PixelGrabber subclass that stores any potential properties from an image. - */ - /* - private static class MyPixelGrabber extends PixelGrabber { - private Hashtable mProps = null; - - public MyPixelGrabber(Image pImage) { - // Simply grab all pixels, do not convert to default RGB space - super(pImage, 0, 0, -1, -1, false); - } - - // Default implementation does not store the properties... - public void setProperties(Hashtable pProps) { - super.setProperties(pProps); - mProps = pProps; - } - - public Hashtable getProperties() { - return mProps; - } - } - */ - - /** - * Gets the transfer type from the given {@code ColorModel}. - *

- * NOTE: This is a workaround for missing functionality in JDK 1.2. - * - * @param pModel the color model - * @return the transfer type - * - * @throws NullPointerException if {@code pModel} is {@code null}. - * - * @see java.awt.image.ColorModel#getTransferType() - */ - public static int getTransferType(ColorModel pModel) { - if (COLORMODEL_TRANSFERTYPE_SUPPORTED) { - return pModel.getTransferType(); - } - else { - // Stupid workaround - // TODO: Create something that performs better - return pModel.createCompatibleSampleModel(1, 1).getDataType(); - } - } } \ No newline at end of file diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/InverseColorMapIndexColorModel.java b/common/common-image/src/main/java/com/twelvemonkeys/image/InverseColorMapIndexColorModel.java index 8bba6e5e..640fa002 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/InverseColorMapIndexColorModel.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/InverseColorMapIndexColorModel.java @@ -30,6 +30,7 @@ package com.twelvemonkeys.image; import com.twelvemonkeys.lang.StringUtil; +import com.twelvemonkeys.lang.Validate; import java.awt.*; import java.awt.image.DataBuffer; @@ -37,7 +38,7 @@ import java.awt.image.IndexColorModel; /** * A faster implementation of {@code IndexColorModel}, that is backed by an - * inverse color-map, for fast lookups. + * inverse color-map, for fast look-ups. * * @author Harald Kuhr * @author $Author: haku $ @@ -60,19 +61,17 @@ public class InverseColorMapIndexColorModel extends IndexColorModel { * Creates an {@code InverseColorMapIndexColorModel} from an existing * {@code IndexColorModel}. * - * @param pColorModel the colormodel to create from + * @param pColorModel the color model to create from. + * @throws IllegalArgumentException if {@code pColorModel} is {@code null} */ - public InverseColorMapIndexColorModel(IndexColorModel pColorModel) { - this(pColorModel, getRGBs(pColorModel)); + public InverseColorMapIndexColorModel(final IndexColorModel pColorModel) { + this(Validate.notNull(pColorModel, "color model"), getRGBs(pColorModel)); } // NOTE: The pRGBs parameter is used to get around invoking getRGBs two // times. What is wrong with protected?! private InverseColorMapIndexColorModel(IndexColorModel pColorModel, int[] pRGBs) { - super(pColorModel.getComponentSize()[0], pColorModel.getMapSize(), - pRGBs, 0, - ImageUtil.getTransferType(pColorModel), - pColorModel.getValidPixels()); + super(pColorModel.getComponentSize()[0], pColorModel.getMapSize(), pRGBs, 0, pColorModel.getTransferType(), pColorModel.getValidPixels()); rgbs = pRGBs; mapSize = rgbs.length; @@ -82,11 +81,11 @@ public class InverseColorMapIndexColorModel extends IndexColorModel { } /** - * Creates a defensive copy of the RGB colormap in the given + * Creates a defensive copy of the RGB color map in the given * {@code IndexColorModel}. * - * @param pColorModel the indec colormodel to get RGB values from - * @return the RGB colormap + * @param pColorModel the indexed color model to get RGB values from + * @return the RGB color map */ private static int[] getRGBs(IndexColorModel pColorModel) { int[] rgb = new int[pColorModel.getMapSize()];