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 06154aef..5c9cdca5 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 @@ -1,543 +1,536 @@ -/* - * Copyright (c) 2008, Harald Kuhr - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name "TwelveMonkeys" nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.twelvemonkeys.image; - -import java.awt.*; -import java.awt.image.*; -import java.util.*; -import java.util.List; -import java.lang.reflect.Array; - -/** - * A faster, lighter and easier way to convert an {@code Image} to a - * {@code BufferedImage} than using a {@code PixelGrabber}. - * Clients may provide progress listeners to monitor conversion progress. - *
- * Supports source image subsampling and source region extraction. - * Supports source images with 16 bit {@link ColorModel} and - * {@link DataBuffer#TYPE_USHORT} transfer type, without converting to - * 32 bit/TYPE_INT. - * - * NOTE: Does not support images with more than one {@code ColorModel} or - * different types of pixel data. This is not very common. - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/BufferedImageFactory.java#1 $ - */ -public final class BufferedImageFactory { - private List{@code - * short value = (short) (intValue & 0x0000ffff); - * }- * - * @param pPixels the pixel data to convert - * @return an array of {@code short}s, same lenght as {@code pPixels} - */ - 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; - } - - /** - * This interface allows clients of a {@code BufferedImageFactory} to - * receive notifications of decoding progress. - * - * @see BufferedImageFactory#addProgressListener - * @see BufferedImageFactory#removeProgressListener - */ - public static interface ProgressListener extends EventListener { - - /** - * Reports progress to this listener. - * Invoked by the {@code BufferedImageFactory} to report progress in - * the image decoding. - * - * @param pFactory the factory reporting the progress - * @param pPercentage the perccentage of progress - */ - void progress(BufferedImageFactory pFactory, float pPercentage); - } - - private class Consumer implements ImageConsumer { - /** - * Implementation of all setPixels methods. - * Note that this implementation assumes that all invocations for one - * image uses the same color model, and that the pixel data has the - * same type. - * - * @param pX x coordinate of pixel data region - * @param pY y coordinate of pixel data region - * @param pWidth width of pixel data region - * @param pHeight height of pixel data region - * @param pModel the color model of the pixel data - * @param pPixels the pixel data array - * @param pOffset the offset into the pixel data array - * @param pScanSize the scan size of the pixel data array - */ - private void setPixelsImpl(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, Object pPixels, int pOffset, int pScanSize) { - setColorModelOnce(pModel); - - if (pPixels == null) { - return; - } - - //System.out.println("Setting " + pPixels.getClass().getComponentType() + " pixels: " + Array.getLength(pPixels)); - - // Allocate array if neccessary - if (mSourcePixels == null) { - /* - System.out.println("ColorModel: " + pModel); - System.out.println("Scansize: " + pScanSize + " TrasferType: " + ImageUtil.getTransferType(pModel)); - System.out.println("Creating " + pPixels.getClass().getComponentType() + " array of length " + (mWidth * mHeight)); - */ - // Allocate a suitable source pixel array - // TODO: Should take pixel "width" into consideration, for byte packed rasters?! - // OR... Is anything but single-pixel models really supported by the API? - mSourcePixels = Array.newInstance(pPixels.getClass().getComponentType(), mWidth * mHeight); - mScanSize = mWidth; - mOffset = 0; - } - else if (mSourcePixels.getClass() != pPixels.getClass()) { - throw new IllegalStateException("Only one pixel type allowed"); - } - - // AOI stuff - if (pY < mY) { - int diff = mY - pY; - if (diff >= pHeight) { - return; - } - pOffset += pScanSize * diff; - pY += diff; - pHeight -= diff; - } - if (pY + pHeight > mY + mHeight) { - pHeight = (mY + mHeight) - pY; - if (pHeight <= 0) { - return; - } - } - - if (pX < mX) { - int diff = mX - pX; - if (diff >= pWidth) { - return; - } - pOffset += diff; - pX += diff; - pWidth -= diff; - } - if (pX + pWidth > mX + mWidth) { - pWidth = (mX + mWidth) - pX; - if (pWidth <= 0) { - return; - } - } - - int dstOffset = mOffset + (pY - mY) * mScanSize + (pX - mX); - - // Do the pixel copying - for (int i = pHeight; i > 0; i--) { - System.arraycopy(pPixels, pOffset, mSourcePixels, dstOffset, pWidth); - pOffset += pScanSize; - dstOffset += mScanSize; - } - - processProgress(pY + pHeight); - } - - /** {@code ImageConsumer} implementation, do not invoke directly */ - public void setPixels(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, short[] pPixels, int pOffset, int pScanSize) { - setPixelsImpl(pX, pY, pWidth, pHeight, pModel, pPixels, pOffset, pScanSize); - } - - private void setColorModelOnce(ColorModel pModel) { - // NOTE: There seems to be a "bug" in AreaAveragingScaleFilter, as it - // first passes the original colormodel through in setColorModel, then - // later replaces it with the default RGB in the first setPixels call - // (this is probably allowed according to the spec, but it's a waste of - // time and space). - if (mSourceColorModel != pModel) { - if (/*mSourceColorModel == null ||*/ mSourcePixels == null) { - mSourceColorModel = pModel; - } - else { - throw new IllegalStateException("Change of ColorModel after pixel delivery not supported"); - } - } - - // If color model is all we ask for, stop now - if (mReadColorModelOnly) { - mConsumer.imageComplete(ImageConsumer.IMAGEABORTED); - } - } - - /** {@code ImageConsumer} implementation, do not invoke */ - public void imageComplete(int pStatus) { - mFetching = false; - - if (mProducer != null) { - mProducer.removeConsumer(this); - } - - switch (pStatus) { - case IMAGEERROR: - new Error().printStackTrace(); - mError = true; - break; - } - - synchronized (BufferedImageFactory.this) { - BufferedImageFactory.this.notifyAll(); - } - } - - /** {@code ImageConsumer} implementation, do not invoke directly */ - public void setColorModel(ColorModel pModel) { - //System.out.println("SetColorModel: " + pModel); - setColorModelOnce(pModel); - } - - /** {@code ImageConsumer} implementation, do not invoke directly */ - public void setDimensions(int pWidth, int pHeight) { - //System.out.println("Setting dimensions: " + pWidth + ", " + pHeight); - if (mWidth < 0) { - mWidth = pWidth - mX; - } - if (mHeight < 0) { - mHeight = pHeight - mY; - } - - // Hmm.. Special case, but is it a good idea? - if (mWidth <= 0 || mHeight <= 0) { - imageComplete(STATICIMAGEDONE); - } - } - - /** {@code ImageConsumer} implementation, do not invoke directly */ - public void setHints(int pHintflags) { - // ignore - } - - /** {@code ImageConsumer} implementation, do not invoke directly */ - 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); - //} - } - - /** {@code ImageConsumer} implementation, do not invoke directly */ - 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) { - // 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); - } - else { - setPixelsImpl(pX, pY, pWeigth, pHeight, pModel, pPixels, pOffset, pScanSize); - } - } - - /** {@code ImageConsumer} implementation, do not invoke directly */ - public void setProperties(Hashtable pProperties) { - mSourceProperties = pProperties; - } - } +/* + * Copyright (c) 2008, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.image; + +import java.awt.*; +import java.awt.image.*; +import java.util.*; +import java.util.List; +import java.lang.reflect.Array; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * A faster, lighter and easier way to convert an {@code Image} to a + * {@code BufferedImage} than using a {@code PixelGrabber}. + * Clients may provide progress listeners to monitor conversion progress. + * + * Supports source image subsampling and source region extraction. + * Supports source images with 16 bit {@link ColorModel} and + * {@link DataBuffer#TYPE_USHORT} transfer type, without converting to + * 32 bit/TYPE_INT. + * + * NOTE: Does not support images with more than one {@code ColorModel} or + * different types of pixel data. This is not very common. + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/BufferedImageFactory.java#1 $ + */ +public final class BufferedImageFactory { + private List
{@code + * short value = (short) (intValue & 0x0000ffff); + * }+ * + * @param pPixels the pixel data to convert + * @return an array of {@code short}s, same lenght as {@code pPixels} + */ + 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; + } + + /** + * This interface allows clients of a {@code BufferedImageFactory} to + * receive notifications of decoding progress. + * + * @see BufferedImageFactory#addProgressListener + * @see BufferedImageFactory#removeProgressListener + */ + public static interface ProgressListener extends EventListener { + + /** + * Reports progress to this listener. + * Invoked by the {@code BufferedImageFactory} to report progress in + * the image decoding. + * + * @param pFactory the factory reporting the progress + * @param pPercentage the percentage of progress + */ + void progress(BufferedImageFactory pFactory, float pPercentage); + } + + private class Consumer implements ImageConsumer { + /** + * Implementation of all setPixels methods. + * Note that this implementation assumes that all invocations for one + * image uses the same color model, and that the pixel data has the + * same type. + * + * @param pX x coordinate of pixel data region + * @param pY y coordinate of pixel data region + * @param pWidth width of pixel data region + * @param pHeight height of pixel data region + * @param pModel the color model of the pixel data + * @param pPixels the pixel data array + * @param pOffset the offset into the pixel data array + * @param pScanSize the scan size of the pixel data array + */ + @SuppressWarnings({"SuspiciousSystemArraycopy"}) + private void setPixelsImpl(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, Object pPixels, int pOffset, int pScanSize) { + setColorModelOnce(pModel); + + if (pPixels == null) { + return; + } + + // Allocate array if necessary + if (mSourcePixels == null) { + // Allocate a suitable source pixel array + // TODO: Should take pixel "width" into consideration, for byte packed rasters?! + // OR... Is anything but single-pixel models really supported by the API? + mSourcePixels = Array.newInstance(pPixels.getClass().getComponentType(), mWidth * mHeight); + mScanSize = mWidth; + mOffset = 0; + } + else if (mSourcePixels.getClass() != pPixels.getClass()) { + throw new IllegalStateException("Only one pixel type allowed"); + } + + // AOI stuff + if (pY < mY) { + int diff = mY - pY; + if (diff >= pHeight) { + return; + } + pOffset += pScanSize * diff; + pY += diff; + pHeight -= diff; + } + if (pY + pHeight > mY + mHeight) { + pHeight = (mY + mHeight) - pY; + if (pHeight <= 0) { + return; + } + } + + if (pX < mX) { + int diff = mX - pX; + if (diff >= pWidth) { + return; + } + pOffset += diff; + pX += diff; + pWidth -= diff; + } + if (pX + pWidth > mX + mWidth) { + pWidth = (mX + mWidth) - pX; + if (pWidth <= 0) { + return; + } + } + + int dstOffset = mOffset + (pY - mY) * mScanSize + (pX - mX); + + // Do the pixel copying + for (int i = pHeight; i > 0; i--) { + System.arraycopy(pPixels, pOffset, mSourcePixels, dstOffset, pWidth); + pOffset += pScanSize; + dstOffset += mScanSize; + } + + processProgress(pY + pHeight); + } + + public void setPixels(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, short[] pPixels, int pOffset, int pScanSize) { + setPixelsImpl(pX, pY, pWidth, pHeight, pModel, pPixels, pOffset, pScanSize); + } + + private void setColorModelOnce(ColorModel pModel) { + // NOTE: There seems to be a "bug" in AreaAveragingScaleFilter, as it + // first passes the original color model through in setColorModel, then + // later replaces it with the default RGB in the first setPixels call + // (this is probably allowed according to the spec, but it's a waste of time and space). + if (mSourceColorModel != pModel) { + if (/*mSourceColorModel == null ||*/ mSourcePixels == null) { + mSourceColorModel = pModel; + } + else { + throw new IllegalStateException("Change of ColorModel after pixel delivery not supported"); + } + } + + // If color model is all we ask for, stop now + if (mReadColorModelOnly) { + mConsumer.imageComplete(ImageConsumer.IMAGEABORTED); + } + } + + @SuppressWarnings({"ThrowableInstanceNeverThrown"}) + public void imageComplete(int pStatus) { + mFetching = false; + + if (mProducer != null) { + mProducer.removeConsumer(this); + } + + switch (pStatus) { + case ImageConsumer.IMAGEERROR: + new Error().printStackTrace(); + mError = true; + break; + } + + synchronized (BufferedImageFactory.this) { + BufferedImageFactory.this.notifyAll(); + } + } + + public void setColorModel(ColorModel pModel) { + setColorModelOnce(pModel); + } + + public void setDimensions(int pWidth, int pHeight) { + if (mWidth < 0) { + mWidth = pWidth - mX; + } + if (mHeight < 0) { + mHeight = pHeight - mY; + } + + // Hmm.. Special case, but is it a good idea? + if (mWidth <= 0 || mHeight <= 0) { + imageComplete(ImageConsumer.STATICIMAGEDONE); + } + } + + public void setHints(int pHintflags) { + // ignore + } + + 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); + //} + } + + 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) { + // 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); + } + else { + setPixelsImpl(pX, pY, pWeigth, pHeight, pModel, pPixels, pOffset, pScanSize); + } + } + + public void setProperties(Hashtable pProperties) { + mSourceProperties = pProperties; + } + } } \ No newline at end of file