All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.twelvemonkeys.image.BufferedImageFactory Maven / Gradle / Ivy

The newest version!
/*
 * 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 mListeners; private int mPercentageDone; private ImageProducer mProducer; private boolean mError; private boolean mFetching; private boolean mReadColorModelOnly; private int mX = 0; private int mY = 0; private int mWidth = -1; private int mHeight = -1; private int mXSub = 1; private int mYSub = 1; private int mOffset; private int mScanSize; private ColorModel mSourceColorModel; private Hashtable mSourceProperties; // ImageConsumer API dictates Hashtable private Object mSourcePixels; private BufferedImage mBuffered; private ColorModel mColorModel; // NOTE: Just to not expose the inheritance private final Consumer mConsumer = new Consumer(); /** * Creates a {@code BufferedImageFactory}. * @param pSource the source image */ public BufferedImageFactory(Image pSource) { this(pSource.getSource()); } /** * Creates a {@code BufferedImageFactory}. * @param pSource the source image producer */ public BufferedImageFactory(ImageProducer pSource) { mProducer = pSource; } /** * Returns the {@code BufferedImage} extracted from the given * {@code ImageSource}. Multiple requests will return the same image. * * @return the {@code BufferedImage} * * @throws ImageConversionException if the given {@code ImageSource} cannot * be converted for some reason. */ public BufferedImage getBufferedImage() throws ImageConversionException { doFetch(false); return mBuffered; } /** * Returns the {@code ColorModel} extracted from the * given {@code ImageSource}. Multiple requests will return the same model. * * @return the {@code ColorModel} * * @throws ImageConversionException if the given {@code ImageSource} cannot * be converted for some reason. */ public ColorModel getColorModel() throws ImageConversionException { doFetch(true); return mBuffered != null ? mBuffered.getColorModel() : mColorModel; } /** * Frees resources used by this {@code BufferedImageFactory}. */ public void dispose() { freeResources(); mBuffered = null; mColorModel = null; } /** * Aborts the image prodcution. */ public void abort() { mConsumer.imageComplete(ImageConsumer.IMAGEABORTED); } /** * Sets the source region (AOI) for the new image. * * @param pRect the source region */ public void setSourceRegion(Rectangle pRect) { // Refetch everything, if region changed if (mX != pRect.x || mY != pRect.y || mWidth != pRect.width || mHeight != pRect.height) { dispose(); } mX = pRect.x; mY = pRect.y; mWidth = pRect.width; mHeight = pRect.height; } /** * Sets the source subsampling for the new image. * * @param pXSub horisontal subsampling factor * @param pYSub vertical subsampling factor */ public void setSourceSubsampling(int pXSub, int pYSub) { // Refetch everything, if subsampling changed if (mXSub != pXSub || mYSub != pYSub) { dispose(); } if (pXSub > 1) { mXSub = pXSub; } if (pYSub > 1) { mYSub = pYSub; } } private synchronized void doFetch(boolean pColorModelOnly) throws ImageConversionException { if (!mFetching && (!pColorModelOnly && mBuffered == null || mBuffered == null && mSourceColorModel == null)) { // NOTE: Subsampling is only applied if extracting full image if (!pColorModelOnly && (mXSub > 1 || mYSub > 1)) { // If only sampling a region, the region must be scaled too if (mWidth > 0 && mHeight > 0) { mWidth = (mWidth + mXSub - 1) / mXSub; mHeight = (mHeight + mYSub - 1) / mYSub; mX = (mX + mXSub - 1) / mXSub; mY = (mY + mYSub - 1) / mYSub; } mProducer = new FilteredImageSource(mProducer, new SubsamplingFilter(mXSub, mYSub)); } // Start fetching mFetching = true; mReadColorModelOnly = pColorModelOnly; mProducer.startProduction(mConsumer); // Note: If single-thread (synchronous), this call will block // Wait until the producer wakes us up, by calling imageComplete while (mFetching) { try { wait(); } catch (InterruptedException e) { throw new ImageConversionException("Image conversion aborted: " + e.getMessage(), e); } } if (mError) { throw new ImageConversionException("Image conversion failed: ImageConsumer.IMAGEERROR."); } if (pColorModelOnly) { createColorModel(); } else { createBuffered(); } } } private void createColorModel() { mColorModel = mSourceColorModel; // Clean up, in case any objects are copied/cloned, so we can free resources freeResources(); } private void createBuffered() { if (mWidth > 0 && mHeight > 0) { if (mSourceColorModel != null && mSourcePixels != null) { // TODO: Fix pixel size / color model problem WritableRaster raster = ImageUtil.createRaster(mWidth, mHeight, mSourcePixels, mSourceColorModel); mBuffered = new BufferedImage(mSourceColorModel, raster, mSourceColorModel.isAlphaPremultiplied(), mSourceProperties); } else { mBuffered = ImageUtil.createClear(mWidth, mHeight, null); } } // Clean up, in case any objects are copied/cloned, so we can free resources freeResources(); } private void freeResources() { mSourceColorModel = null; mSourcePixels = null; mSourceProperties = null; } private void processProgress(int mScanline) { if (mListeners != null) { int percent = 100 * mScanline / mHeight; //System.out.println("Progress: " + percent + "%"); if (percent > mPercentageDone) { mPercentageDone = percent; // TODO: Fix concurrent modification if a listener removes itself... for (ProgressListener listener : mListeners) { listener.progress(this, percent); } } } } /** * Adds a progress listener to this factory. * * @param pListener the progress listener */ public void addProgressListener(ProgressListener pListener) { if (mListeners == null) { mListeners = new ArrayList(); } mListeners.add(pListener); } /** * Removes a progress listener from this factory. * * @param pListener the progress listener */ public void removeProgressListener(ProgressListener pListener) { if (mListeners == null) { return; } mListeners.remove(pListener); } /** * Removes all progress listeners from this factory. */ public void removeAllProgressListeners() { if (mListeners != null) { mListeners.clear(); } } /** * Converts an array of {@code int} pixles to an array of {@code short} * 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 * follows: *

{@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; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy