/* * #%L * Fork of JAI Image I/O Tools. * %% * Copyright (C) 2008 - 2014 Open Microscopy Environment: * - Board of Regents of the University of Wisconsin-Madison * - Glencoe Software, Inc. * - University of Dundee * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. 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. * * 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 HOLDERS 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. * * The views and conclusions contained in the software and documentation are * those of the authors and should not be interpreted as representing official * policies, either expressed or implied, of any organization. * #L% */ /* * $RCSfile: ImageUtil.java,v $ * * * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistribution of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistribution 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 of Sun Microsystems, Inc. or the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * This software is provided "AS IS," without a warranty of any * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE * POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed or intended for * use in the design, construction, operation or maintenance of any * nuclear facility. * * $Revision: 1.7 $ * $Date: 2007/08/28 18:45:06 $ * $State: Exp $ */ package com.sun.media.imageioimpl.common; import java.awt.Point; import java.awt.Rectangle; import java.awt.Transparency; import java.awt.color.ColorSpace; import java.awt.color.ICC_ColorSpace; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.ComponentColorModel; import java.awt.image.ComponentSampleModel; import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; import java.awt.image.DataBufferInt; import java.awt.image.DataBufferShort; import java.awt.image.DataBufferUShort; import java.awt.image.DirectColorModel; import java.awt.image.IndexColorModel; import java.awt.image.MultiPixelPackedSampleModel; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.awt.image.SampleModel; import java.awt.image.SinglePixelPackedSampleModel; import java.awt.image.WritableRaster; import java.io.IOException; import java.util.Arrays; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Locale; //import javax.imageio.ImageTypeSpecifier; import javax.imageio.IIOException; import javax.imageio.IIOImage; import javax.imageio.ImageReadParam; import javax.imageio.ImageTypeSpecifier; import javax.imageio.ImageWriter; import javax.imageio.spi.IIORegistry; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.spi.ImageReaderWriterSpi; import javax.imageio.spi.ImageWriterSpi; import javax.imageio.spi.ServiceRegistry; import javax.imageio.stream.ImageInputStream; public class ImageUtil { /* XXX testing only public static void main(String[] args) { ImageTypeSpecifier bilevel = ImageTypeSpecifier.createIndexed(new byte[] {(byte)0, (byte)255}, new byte[] {(byte)0, (byte)255}, new byte[] {(byte)0, (byte)255}, null, 1, DataBuffer.TYPE_BYTE); ImageTypeSpecifier gray = ImageTypeSpecifier.createGrayscale(8, DataBuffer.TYPE_BYTE, false); ImageTypeSpecifier grayAlpha = ImageTypeSpecifier.createGrayscale(8, DataBuffer.TYPE_BYTE, false, false); ImageTypeSpecifier rgb = ImageTypeSpecifier.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {0, 1, 2}, DataBuffer.TYPE_BYTE, false, false); ImageTypeSpecifier rgba = ImageTypeSpecifier.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {0, 1, 2, 3}, DataBuffer.TYPE_BYTE, true, false); ImageTypeSpecifier packed = ImageTypeSpecifier.createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB), 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff, DataBuffer.TYPE_BYTE, false); SampleModel bandedSM = new java.awt.image.BandedSampleModel(DataBuffer.TYPE_BYTE, 1, 1, 15); System.out.println(createColorModel(bilevel.getSampleModel())); System.out.println(createColorModel(gray.getSampleModel())); System.out.println(createColorModel(grayAlpha.getSampleModel())); System.out.println(createColorModel(rgb.getSampleModel())); System.out.println(createColorModel(rgba.getSampleModel())); System.out.println(createColorModel(packed.getSampleModel())); System.out.println(createColorModel(bandedSM)); } */ /** * Creates a <code>ColorModel</code> that may be used with the * specified <code>SampleModel</code>. If a suitable * <code>ColorModel</code> cannot be found, this method returns * <code>null</code>. * * <p> Suitable <code>ColorModel</code>s are guaranteed to exist * for all instances of <code>ComponentSampleModel</code>. * For 1- and 3- banded <code>SampleModel</code>s, the returned * <code>ColorModel</code> will be opaque. For 2- and 4-banded * <code>SampleModel</code>s, the output will use alpha transparency * which is not premultiplied. 1- and 2-banded data will use a * grayscale <code>ColorSpace</code>, and 3- and 4-banded data a sRGB * <code>ColorSpace</code>. Data with 5 or more bands will have a * <code>BogusColorSpace</code>.</p> * * <p>An instance of <code>DirectColorModel</code> will be created for * instances of <code>SinglePixelPackedSampleModel</code> with no more * than 4 bands.</p> * * <p>An instance of <code>IndexColorModel</code> will be created for * instances of <code>MultiPixelPackedSampleModel</code>. The colormap * will be a grayscale ramp with <code>1 << numberOfBits</code> * entries ranging from zero to at most 255.</p> * * @return An instance of <code>ColorModel</code> that is suitable for * the supplied <code>SampleModel</code>, or <code>null</code>. * * @throws IllegalArgumentException If <code>sampleModel</code> is * <code>null</code>. */ public static final ColorModel createColorModel(SampleModel sampleModel) { // Check the parameter. if(sampleModel == null) { throw new IllegalArgumentException("sampleModel == null!"); } // Get the data type. int dataType = sampleModel.getDataType(); // Check the data type switch(dataType) { case DataBuffer.TYPE_BYTE: case DataBuffer.TYPE_USHORT: case DataBuffer.TYPE_SHORT: case DataBuffer.TYPE_INT: case DataBuffer.TYPE_FLOAT: case DataBuffer.TYPE_DOUBLE: break; default: // Return null for other types. return null; } // The return variable. ColorModel colorModel = null; // Get the sample size. int[] sampleSize = sampleModel.getSampleSize(); // Create a Component ColorModel. if(sampleModel instanceof ComponentSampleModel) { // Get the number of bands. int numBands = sampleModel.getNumBands(); // Determine the color space. ColorSpace colorSpace = null; if(numBands <= 2) { colorSpace = ColorSpace.getInstance(ColorSpace.CS_GRAY); } else if(numBands <= 4) { colorSpace = ColorSpace.getInstance(ColorSpace.CS_sRGB); } else { colorSpace = new BogusColorSpace(numBands); } boolean hasAlpha = (numBands == 2) || (numBands == 4); boolean isAlphaPremultiplied = false; int transparency = hasAlpha ? Transparency.TRANSLUCENT : Transparency.OPAQUE; colorModel = new ComponentColorModel(colorSpace, sampleSize, hasAlpha, isAlphaPremultiplied, transparency, dataType); } else if (sampleModel.getNumBands() <= 4 && sampleModel instanceof SinglePixelPackedSampleModel) { SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel)sampleModel; int[] bitMasks = sppsm.getBitMasks(); int rmask = 0; int gmask = 0; int bmask = 0; int amask = 0; int numBands = bitMasks.length; if (numBands <= 2) { rmask = gmask = bmask = bitMasks[0]; if (numBands == 2) { amask = bitMasks[1]; } } else { rmask = bitMasks[0]; gmask = bitMasks[1]; bmask = bitMasks[2]; if (numBands == 4) { amask = bitMasks[3]; } } int bits = 0; for (int i = 0; i < sampleSize.length; i++) { bits += sampleSize[i]; } return new DirectColorModel(bits, rmask, gmask, bmask, amask); } else if(sampleModel instanceof MultiPixelPackedSampleModel) { // Load the colormap with a ramp. int bitsPerSample = sampleSize[0]; int numEntries = 1 << bitsPerSample; byte[] map = new byte[numEntries]; for (int i = 0; i < numEntries; i++) { map[i] = (byte)(i*255/(numEntries - 1)); } colorModel = new IndexColorModel(bitsPerSample, numEntries, map, map, map); } return colorModel; } /** * For the case of binary data (<code>isBinary()</code> returns * <code>true</code>), return the binary data as a packed byte array. * The data will be packed as eight bits per byte with no bit offset, * i.e., the first bit in each image line will be the left-most of the * first byte of the line. The line stride in bytes will be * <code>(int)((getWidth()+7)/8)</code>. The length of the returned * array will be the line stride multiplied by <code>getHeight()</code> * * @return the binary data as a packed array of bytes with zero offset * of <code>null</code> if the data are not binary. * @throws IllegalArgumentException if <code>isBinary()</code> returns * <code>false</code> with the <code>SampleModel</code> of the * supplied <code>Raster</code> as argument. */ public static byte[] getPackedBinaryData(Raster raster, Rectangle rect) { SampleModel sm = raster.getSampleModel(); if(!isBinary(sm)) { throw new IllegalArgumentException(I18N.getString("ImageUtil0")); } int rectX = rect.x; int rectY = rect.y; int rectWidth = rect.width; int rectHeight = rect.height; DataBuffer dataBuffer = raster.getDataBuffer(); int dx = rectX - raster.getSampleModelTranslateX(); int dy = rectY - raster.getSampleModelTranslateY(); MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel)sm; int lineStride = mpp.getScanlineStride(); int eltOffset = dataBuffer.getOffset() + mpp.getOffset(dx, dy); int bitOffset = mpp.getBitOffset(dx); int numBytesPerRow = (rectWidth + 7)/8; if(dataBuffer instanceof DataBufferByte && eltOffset == 0 && bitOffset == 0 && numBytesPerRow == lineStride && ((DataBufferByte)dataBuffer).getData().length == numBytesPerRow*rectHeight) { return ((DataBufferByte)dataBuffer).getData(); } byte[] binaryDataArray = new byte[numBytesPerRow*rectHeight]; int b = 0; if(bitOffset == 0) { if(dataBuffer instanceof DataBufferByte) { byte[] data = ((DataBufferByte)dataBuffer).getData(); int stride = numBytesPerRow; int offset = 0; for(int y = 0; y < rectHeight; y++) { System.arraycopy(data, eltOffset, binaryDataArray, offset, stride); offset += stride; eltOffset += lineStride; } } else if(dataBuffer instanceof DataBufferShort || dataBuffer instanceof DataBufferUShort) { short[] data = dataBuffer instanceof DataBufferShort ? ((DataBufferShort)dataBuffer).getData() : ((DataBufferUShort)dataBuffer).getData(); for(int y = 0; y < rectHeight; y++) { int xRemaining = rectWidth; int i = eltOffset; while(xRemaining > 8) { short datum = data[i++]; binaryDataArray[b++] = (byte)((datum >>> 8) & 0xFF); binaryDataArray[b++] = (byte)(datum & 0xFF); xRemaining -= 16; } if(xRemaining > 0) { binaryDataArray[b++] = (byte)((data[i] >>> 8) & 0XFF); } eltOffset += lineStride; } } else if(dataBuffer instanceof DataBufferInt) { int[] data = ((DataBufferInt)dataBuffer).getData(); for(int y = 0; y < rectHeight; y++) { int xRemaining = rectWidth; int i = eltOffset; while(xRemaining > 24) { int datum = data[i++]; binaryDataArray[b++] = (byte)((datum >>> 24) & 0xFF); binaryDataArray[b++] = (byte)((datum >>> 16) & 0xFF); binaryDataArray[b++] = (byte)((datum >>> 8) & 0xFF); binaryDataArray[b++] = (byte)(datum & 0xFF); xRemaining -= 32; } int shift = 24; while(xRemaining > 0) { binaryDataArray[b++] = (byte)((data[i] >>> shift) & 0xFF); shift -= 8; xRemaining -= 8; } eltOffset += lineStride; } } } else { // bitOffset != 0 if(dataBuffer instanceof DataBufferByte) { byte[] data = ((DataBufferByte)dataBuffer).getData(); if((bitOffset & 7) == 0) { int stride = numBytesPerRow; int offset = 0; for(int y = 0; y < rectHeight; y++) { System.arraycopy(data, eltOffset, binaryDataArray, offset, stride); offset += stride; eltOffset += lineStride; } } else { // bitOffset % 8 != 0 int leftShift = bitOffset & 7; int rightShift = 8 - leftShift; for(int y = 0; y < rectHeight; y++) { int i = eltOffset; int xRemaining = rectWidth; while(xRemaining > 0) { if(xRemaining > rightShift) { binaryDataArray[b++] = (byte)(((data[i++]&0xFF) << leftShift) | ((data[i]&0xFF) >>> rightShift)); } else { binaryDataArray[b++] = (byte)((data[i]&0xFF) << leftShift); } xRemaining -= 8; } eltOffset += lineStride; } } } else if(dataBuffer instanceof DataBufferShort || dataBuffer instanceof DataBufferUShort) { short[] data = dataBuffer instanceof DataBufferShort ? ((DataBufferShort)dataBuffer).getData() : ((DataBufferUShort)dataBuffer).getData(); for(int y = 0; y < rectHeight; y++) { int bOffset = bitOffset; for(int x = 0; x < rectWidth; x += 8, bOffset += 8) { int i = eltOffset + bOffset/16; int mod = bOffset % 16; int left = data[i] & 0xFFFF; if(mod <= 8) { binaryDataArray[b++] = (byte)(left >>> (8 - mod)); } else { int delta = mod - 8; int right = data[i+1] & 0xFFFF; binaryDataArray[b++] = (byte)((left << delta) | (right >>> (16 - delta))); } } eltOffset += lineStride; } } else if(dataBuffer instanceof DataBufferInt) { int[] data = ((DataBufferInt)dataBuffer).getData(); for(int y = 0; y < rectHeight; y++) { int bOffset = bitOffset; for(int x = 0; x < rectWidth; x += 8, bOffset += 8) { int i = eltOffset + bOffset/32; int mod = bOffset % 32; int left = data[i]; if(mod <= 24) { binaryDataArray[b++] = (byte)(left >>> (24 - mod)); } else { int delta = mod - 24; int right = data[i+1]; binaryDataArray[b++] = (byte)((left << delta) | (right >>> (32 - delta))); } } eltOffset += lineStride; } } } return binaryDataArray; } /** * Returns the binary data unpacked into an array of bytes. * The line stride will be the width of the <code>Raster</code>. * * @throws IllegalArgumentException if <code>isBinary()</code> returns * <code>false</code> with the <code>SampleModel</code> of the * supplied <code>Raster</code> as argument. */ public static byte[] getUnpackedBinaryData(Raster raster, Rectangle rect) { SampleModel sm = raster.getSampleModel(); if(!isBinary(sm)) { throw new IllegalArgumentException(I18N.getString("ImageUtil0")); } int rectX = rect.x; int rectY = rect.y; int rectWidth = rect.width; int rectHeight = rect.height; DataBuffer dataBuffer = raster.getDataBuffer(); int dx = rectX - raster.getSampleModelTranslateX(); int dy = rectY - raster.getSampleModelTranslateY(); MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel)sm; int lineStride = mpp.getScanlineStride(); int eltOffset = dataBuffer.getOffset() + mpp.getOffset(dx, dy); int bitOffset = mpp.getBitOffset(dx); byte[] bdata = new byte[rectWidth*rectHeight]; int maxY = rectY + rectHeight; int maxX = rectX + rectWidth; int k = 0; if(dataBuffer instanceof DataBufferByte) { byte[] data = ((DataBufferByte)dataBuffer).getData(); for(int y = rectY; y < maxY; y++) { int bOffset = eltOffset*8 + bitOffset; for(int x = rectX; x < maxX; x++) { byte b = data[bOffset/8]; bdata[k++] = (byte)((b >>> (7 - bOffset & 7)) & 0x0000001); bOffset++; } eltOffset += lineStride; } } else if(dataBuffer instanceof DataBufferShort || dataBuffer instanceof DataBufferUShort) { short[] data = dataBuffer instanceof DataBufferShort ? ((DataBufferShort)dataBuffer).getData() : ((DataBufferUShort)dataBuffer).getData(); for(int y = rectY; y < maxY; y++) { int bOffset = eltOffset*16 + bitOffset; for(int x = rectX; x < maxX; x++) { short s = data[bOffset/16]; bdata[k++] = (byte)((s >>> (15 - bOffset % 16)) & 0x0000001); bOffset++; } eltOffset += lineStride; } } else if(dataBuffer instanceof DataBufferInt) { int[] data = ((DataBufferInt)dataBuffer).getData(); for(int y = rectY; y < maxY; y++) { int bOffset = eltOffset*32 + bitOffset; for(int x = rectX; x < maxX; x++) { int i = data[bOffset/32]; bdata[k++] = (byte)((i >>> (31 - bOffset % 32)) & 0x0000001); bOffset++; } eltOffset += lineStride; } } return bdata; } /** * Sets the supplied <code>Raster</code>'s data from an array * of packed binary data of the form returned by * <code>getPackedBinaryData()</code>. * * @throws IllegalArgumentException if <code>isBinary()</code> returns * <code>false</code> with the <code>SampleModel</code> of the * supplied <code>Raster</code> as argument. */ public static void setPackedBinaryData(byte[] binaryDataArray, WritableRaster raster, Rectangle rect) { SampleModel sm = raster.getSampleModel(); if(!isBinary(sm)) { throw new IllegalArgumentException(I18N.getString("ImageUtil0")); } int rectX = rect.x; int rectY = rect.y; int rectWidth = rect.width; int rectHeight = rect.height; DataBuffer dataBuffer = raster.getDataBuffer(); int dx = rectX - raster.getSampleModelTranslateX(); int dy = rectY - raster.getSampleModelTranslateY(); MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel)sm; int lineStride = mpp.getScanlineStride(); int eltOffset = dataBuffer.getOffset() + mpp.getOffset(dx, dy); int bitOffset = mpp.getBitOffset(dx); int b = 0; if(bitOffset == 0) { if(dataBuffer instanceof DataBufferByte) { byte[] data = ((DataBufferByte)dataBuffer).getData(); if(data == binaryDataArray) { // Optimal case: simply return. return; } int stride = (rectWidth + 7)/8; int offset = 0; for(int y = 0; y < rectHeight; y++) { System.arraycopy(binaryDataArray, offset, data, eltOffset, stride); offset += stride; eltOffset += lineStride; } } else if(dataBuffer instanceof DataBufferShort || dataBuffer instanceof DataBufferUShort) { short[] data = dataBuffer instanceof DataBufferShort ? ((DataBufferShort)dataBuffer).getData() : ((DataBufferUShort)dataBuffer).getData(); for(int y = 0; y < rectHeight; y++) { int xRemaining = rectWidth; int i = eltOffset; while(xRemaining > 8) { data[i++] = (short)(((binaryDataArray[b++] & 0xFF) << 8) | (binaryDataArray[b++] & 0xFF)); xRemaining -= 16; } if(xRemaining > 0) { data[i++] = (short)((binaryDataArray[b++] & 0xFF) << 8); } eltOffset += lineStride; } } else if(dataBuffer instanceof DataBufferInt) { int[] data = ((DataBufferInt)dataBuffer).getData(); for(int y = 0; y < rectHeight; y++) { int xRemaining = rectWidth; int i = eltOffset; while(xRemaining > 24) { data[i++] = (int)(((binaryDataArray[b++] & 0xFF) << 24) | ((binaryDataArray[b++] & 0xFF) << 16) | ((binaryDataArray[b++] & 0xFF) << 8) | (binaryDataArray[b++] & 0xFF)); xRemaining -= 32; } int shift = 24; while(xRemaining > 0) { data[i] |= (int)((binaryDataArray[b++] & 0xFF) << shift); shift -= 8; xRemaining -= 8; } eltOffset += lineStride; } } } else { // bitOffset != 0 int stride = (rectWidth + 7)/8; int offset = 0; if(dataBuffer instanceof DataBufferByte) { byte[] data = ((DataBufferByte)dataBuffer).getData(); if((bitOffset & 7) == 0) { for(int y = 0; y < rectHeight; y++) { System.arraycopy(binaryDataArray, offset, data, eltOffset, stride); offset += stride; eltOffset += lineStride; } } else { // bitOffset % 8 != 0 int rightShift = bitOffset & 7; int leftShift = 8 - rightShift; int leftShift8 = 8 + leftShift; int mask = (byte)(255<<leftShift); int mask1 = (byte)~mask; for(int y = 0; y < rectHeight; y++) { int i = eltOffset; int xRemaining = rectWidth; while(xRemaining > 0) { byte datum = binaryDataArray[b++]; if (xRemaining > leftShift8) { // when all the bits in this BYTE will be set // into the data buffer. data[i] = (byte)((data[i] & mask ) | ((datum&0xFF) >>> rightShift)); data[++i] = (byte)((datum & 0xFF) << leftShift); } else if (xRemaining > leftShift) { // All the "leftShift" high bits will be set // into the data buffer. But not all the // "rightShift" low bits will be set. data[i] = (byte)((data[i] & mask ) | ((datum&0xFF) >>> rightShift)); i++; data[i] = (byte)((data[i] & mask1) | ((datum & 0xFF) << leftShift)); } else { // Less than "leftShift" high bits will be set. int remainMask = (1 << leftShift - xRemaining) - 1; data[i] = (byte)((data[i] & (mask | remainMask)) | (datum&0xFF) >>> rightShift & ~remainMask); } xRemaining -= 8; } eltOffset += lineStride; } } } else if(dataBuffer instanceof DataBufferShort || dataBuffer instanceof DataBufferUShort) { short[] data = dataBuffer instanceof DataBufferShort ? ((DataBufferShort)dataBuffer).getData() : ((DataBufferUShort)dataBuffer).getData(); int rightShift = bitOffset & 7; int leftShift = 8 - rightShift; int leftShift16 = 16 + leftShift; int mask = (short)(~(255 << leftShift)); int mask1 = (short)(65535 << leftShift); int mask2 = (short)~mask1; for(int y = 0; y < rectHeight; y++) { int bOffset = bitOffset; int xRemaining = rectWidth; for(int x = 0; x < rectWidth; x += 8, bOffset += 8, xRemaining -= 8) { int i = eltOffset + (bOffset >> 4); int mod = bOffset & 15; int datum = binaryDataArray[b++] & 0xFF; if(mod <= 8) { // This BYTE is set into one SHORT if (xRemaining < 8) { // Mask the bits to be set. datum &= 255 << 8 - xRemaining; } data[i] = (short)((data[i] & mask) | (datum << leftShift)); } else if (xRemaining > leftShift16) { // This BYTE will be set into two SHORTs data[i] = (short)((data[i] & mask1) | ((datum >>> rightShift)&0xFFFF)); data[++i] = (short)((datum << leftShift)&0xFFFF); } else if (xRemaining > leftShift) { // This BYTE will be set into two SHORTs; // But not all the low bits will be set into SHORT data[i] = (short)((data[i] & mask1) | ((datum >>> rightShift)&0xFFFF)); i++; data[i] = (short)((data[i] & mask2) | ((datum << leftShift)&0xFFFF)); } else { // Only some of the high bits will be set into // SHORTs int remainMask = (1 << leftShift - xRemaining) - 1; data[i] = (short)((data[i] & (mask1 | remainMask)) | ((datum >>> rightShift)&0xFFFF & ~remainMask)); } } eltOffset += lineStride; } } else if(dataBuffer instanceof DataBufferInt) { int[] data = ((DataBufferInt)dataBuffer).getData(); int rightShift = bitOffset & 7; int leftShift = 8 - rightShift; int leftShift32 = 32 + leftShift; int mask = 0xFFFFFFFF << leftShift; int mask1 = ~mask; for(int y = 0; y < rectHeight; y++) { int bOffset = bitOffset; int xRemaining = rectWidth; for(int x = 0; x < rectWidth; x += 8, bOffset += 8, xRemaining -= 8) { int i = eltOffset + (bOffset >> 5); int mod = bOffset & 31; int datum = binaryDataArray[b++] & 0xFF; if(mod <= 24) { // This BYTE is set into one INT int shift = 24 - mod; if (xRemaining < 8) { // Mask the bits to be set. datum &= 255 << 8 - xRemaining; } data[i] = (data[i] & (~(255 << shift))) | (datum << shift); } else if (xRemaining > leftShift32) { // All the bits of this BYTE will be set into two INTs data[i] = (data[i] & mask) | (datum >>> rightShift); data[++i] = datum << leftShift; } else if (xRemaining > leftShift) { // This BYTE will be set into two INTs; // But not all the low bits will be set into INT data[i] = (data[i] & mask) | (datum >>> rightShift); i++; data[i] = (data[i] & mask1) | (datum << leftShift); } else { // Only some of the high bits will be set into INT int remainMask = (1 << leftShift - xRemaining) - 1; data[i] = (data[i] & (mask | remainMask)) | (datum >>> rightShift & ~remainMask); } } eltOffset += lineStride; } } } } /** * Copies data into the packed array of the <code>Raster</code> * from an array of unpacked data of the form returned by * <code>getUnpackedBinaryData()</code>. * * <p> If the data are binary, then the target bit will be set if * and only if the corresponding byte is non-zero. * * @throws IllegalArgumentException if <code>isBinary()</code> returns * <code>false</code> with the <code>SampleModel</code> of the * supplied <code>Raster</code> as argument. */ public static void setUnpackedBinaryData(byte[] bdata, WritableRaster raster, Rectangle rect) { SampleModel sm = raster.getSampleModel(); if(!isBinary(sm)) { throw new IllegalArgumentException(I18N.getString("ImageUtil0")); } int rectX = rect.x; int rectY = rect.y; int rectWidth = rect.width; int rectHeight = rect.height; DataBuffer dataBuffer = raster.getDataBuffer(); int dx = rectX - raster.getSampleModelTranslateX(); int dy = rectY - raster.getSampleModelTranslateY(); MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel)sm; int lineStride = mpp.getScanlineStride(); int eltOffset = dataBuffer.getOffset() + mpp.getOffset(dx, dy); int bitOffset = mpp.getBitOffset(dx); int k = 0; if(dataBuffer instanceof DataBufferByte) { byte[] data = ((DataBufferByte)dataBuffer).getData(); for(int y = 0; y < rectHeight; y++) { int bOffset = eltOffset*8 + bitOffset; for(int x = 0; x < rectWidth; x++) { if(bdata[k++] != (byte)0) { data[bOffset/8] |= (byte)(0x00000001 << (7 - bOffset & 7)); } bOffset++; } eltOffset += lineStride; } } else if(dataBuffer instanceof DataBufferShort || dataBuffer instanceof DataBufferUShort) { short[] data = dataBuffer instanceof DataBufferShort ? ((DataBufferShort)dataBuffer).getData() : ((DataBufferUShort)dataBuffer).getData(); for(int y = 0; y < rectHeight; y++) { int bOffset = eltOffset*16 + bitOffset; for(int x = 0; x < rectWidth; x++) { if(bdata[k++] != (byte)0) { data[bOffset/16] |= (short)(0x00000001 << (15 - bOffset % 16)); } bOffset++; } eltOffset += lineStride; } } else if(dataBuffer instanceof DataBufferInt) { int[] data = ((DataBufferInt)dataBuffer).getData(); for(int y = 0; y < rectHeight; y++) { int bOffset = eltOffset*32 + bitOffset; for(int x = 0; x < rectWidth; x++) { if(bdata[k++] != (byte)0) { data[bOffset/32] |= (int)(0x00000001 << (31 - bOffset % 32)); } bOffset++; } eltOffset += lineStride; } } } public static boolean isBinary(SampleModel sm) { return sm instanceof MultiPixelPackedSampleModel && ((MultiPixelPackedSampleModel)sm).getPixelBitStride() == 1 && sm.getNumBands() == 1; } public static ColorModel createColorModel(ColorSpace colorSpace, SampleModel sampleModel) { ColorModel colorModel = null; if(sampleModel == null) { throw new IllegalArgumentException(I18N.getString("ImageUtil1")); } int numBands = sampleModel.getNumBands(); if (numBands < 1 || numBands > 4) { return null; } int dataType = sampleModel.getDataType(); if (sampleModel instanceof ComponentSampleModel) { if (dataType < DataBuffer.TYPE_BYTE || //dataType == DataBuffer.TYPE_SHORT || dataType > DataBuffer.TYPE_DOUBLE) { return null; } if (colorSpace == null) colorSpace = numBands <= 2 ? ColorSpace.getInstance(ColorSpace.CS_GRAY) : ColorSpace.getInstance(ColorSpace.CS_sRGB); boolean useAlpha = (numBands == 2) || (numBands == 4); int transparency = useAlpha ? Transparency.TRANSLUCENT : Transparency.OPAQUE; boolean premultiplied = false; int dataTypeSize = DataBuffer.getDataTypeSize(dataType); int[] bits = new int[numBands]; for (int i = 0; i < numBands; i++) { bits[i] = dataTypeSize; } colorModel = new ComponentColorModel(colorSpace, bits, useAlpha, premultiplied, transparency, dataType); } else if (sampleModel instanceof SinglePixelPackedSampleModel) { SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel)sampleModel; int[] bitMasks = sppsm.getBitMasks(); int rmask = 0; int gmask = 0; int bmask = 0; int amask = 0; numBands = bitMasks.length; if (numBands <= 2) { rmask = gmask = bmask = bitMasks[0]; if (numBands == 2) { amask = bitMasks[1]; } } else { rmask = bitMasks[0]; gmask = bitMasks[1]; bmask = bitMasks[2]; if (numBands == 4) { amask = bitMasks[3]; } } int[] sampleSize = sppsm.getSampleSize(); int bits = 0; for (int i = 0; i < sampleSize.length; i++) { bits += sampleSize[i]; } if (colorSpace == null) colorSpace = ColorSpace.getInstance(ColorSpace.CS_sRGB); colorModel = new DirectColorModel(colorSpace, bits, rmask, gmask, bmask, amask, false, sampleModel.getDataType()); } else if (sampleModel instanceof MultiPixelPackedSampleModel) { int bits = ((MultiPixelPackedSampleModel)sampleModel).getPixelBitStride(); int size = 1 << bits; byte[] comp = new byte[size]; for (int i = 0; i < size; i++) comp[i] = (byte)(255 * i / (size - 1)); colorModel = new IndexColorModel(bits, size, comp, comp, comp); } return colorModel; } public static int getElementSize(SampleModel sm) { int elementSize = DataBuffer.getDataTypeSize(sm.getDataType()); if (sm instanceof MultiPixelPackedSampleModel) { MultiPixelPackedSampleModel mppsm = (MultiPixelPackedSampleModel)sm; return mppsm.getSampleSize(0) * mppsm.getNumBands(); } else if (sm instanceof ComponentSampleModel) { return sm.getNumBands() * elementSize; } else if (sm instanceof SinglePixelPackedSampleModel) { return elementSize; } return elementSize * sm.getNumBands(); } public static long getTileSize(SampleModel sm) { int elementSize = DataBuffer.getDataTypeSize(sm.getDataType()); if (sm instanceof MultiPixelPackedSampleModel) { MultiPixelPackedSampleModel mppsm = (MultiPixelPackedSampleModel)sm; return (mppsm.getScanlineStride() * mppsm.getHeight() + (mppsm.getDataBitOffset() + elementSize -1) / elementSize) * ((elementSize + 7) / 8); } else if (sm instanceof ComponentSampleModel) { ComponentSampleModel csm = (ComponentSampleModel)sm; int[] bandOffsets = csm.getBandOffsets(); int maxBandOff = bandOffsets[0]; for (int i=1; i<bandOffsets.length; i++) maxBandOff = Math.max(maxBandOff, bandOffsets[i]); long size = 0; int pixelStride = csm.getPixelStride(); int scanlineStride = csm.getScanlineStride(); if (maxBandOff >= 0) size += maxBandOff + 1; if (pixelStride > 0) size += pixelStride * (sm.getWidth() - 1); if (scanlineStride > 0) size += scanlineStride * (sm.getHeight() - 1); int[] bankIndices = csm.getBankIndices(); maxBandOff = bankIndices[0]; for (int i=1; i<bankIndices.length; i++) maxBandOff = Math.max(maxBandOff, bankIndices[i]); return size * (maxBandOff + 1) * ((elementSize + 7) / 8); } else if (sm instanceof SinglePixelPackedSampleModel) { SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel)sm; long size = sppsm.getScanlineStride() * (sppsm.getHeight() - 1) + sppsm.getWidth(); return size * ((elementSize + 7) / 8); } return 0; } public static long getBandSize(SampleModel sm) { int elementSize = DataBuffer.getDataTypeSize(sm.getDataType()); if (sm instanceof ComponentSampleModel) { ComponentSampleModel csm = (ComponentSampleModel)sm; int pixelStride = csm.getPixelStride(); int scanlineStride = csm.getScanlineStride(); long size = Math.min(pixelStride, scanlineStride); if (pixelStride > 0) size += pixelStride * (sm.getWidth() - 1); if (scanlineStride > 0) size += scanlineStride * (sm.getHeight() - 1); return size * ((elementSize + 7) / 8); } else return getTileSize(sm); } /** * Tests whether the color indices represent a gray-scale image with * the indicated number of bits over the color component range [0,255]. * The grayscale mapping may be inverted, i.e., 0 -> 255 and * mapSize -> 0. * * @param icm The gray-to-color mapping. * @return Whether the <code>IndexColorModel</code> maps index * <code>i</code> to <code>((255*i)/icm.getMapSize()-1)</code>. * @throws IllegalArgumentException if <code>icm</code> is * <code>null</code>. */ public static boolean isGrayscaleMapping(IndexColorModel icm) { if(icm == null) { throw new IllegalArgumentException("icm == null!"); } // Get colormap size and contents. int mapSize = icm.getMapSize(); byte[] r = new byte[mapSize]; byte[] g = new byte[mapSize]; byte[] b = new byte[mapSize]; icm.getReds(r); icm.getGreens(g); icm.getBlues(b); boolean isGrayToColor = true; // Check ascending ramp. for (int i = 0; i < mapSize; i++) { byte temp = (byte)(i*255/(mapSize - 1)); if (r[i] != temp || g[i] != temp || b[i] != temp) { isGrayToColor = false; break; } } if(!isGrayToColor) { isGrayToColor = true; // Check descending ramp. for (int i = 0, j = mapSize - 1; i < mapSize; i++, j--) { byte temp = (byte)(j*255/(mapSize - 1)); if (r[i] != temp || g[i] != temp || b[i] != temp) { isGrayToColor = false; break; } } } return isGrayToColor; } /** * Tests whether the color indices represent a gray-scale image. * * @param r The red channel color indices. * @param g The green channel color indices. * @param b The blue channel color indices. * @return If all the indices have 256 entries, and are identical mappings, * return <code>true</code>; otherwise, return <code>false</code>. */ public static boolean isIndicesForGrayscale(byte[] r, byte[] g, byte[] b) { if (r.length != g.length || r.length != b.length) return false; int size = r.length; if (size != 256) return false; for (int i = 0; i < size; i++) { byte temp = (byte) i; if (r[i] != temp || g[i] != temp || b[i] != temp) return false; } return true; } /** Converts the provided object to <code>String</code> */ public static String convertObjectToString(Object obj) { if (obj == null) return ""; String s = ""; if (obj instanceof byte[]) { byte[] bArray = (byte[])obj; for (int i = 0; i < bArray.length; i++) s += bArray[i] + " "; return s; } if (obj instanceof int[]) { int[] iArray = (int[])obj; for (int i = 0; i < iArray.length; i++) s += iArray[i] + " " ; return s; } if (obj instanceof short[]) { short[] sArray = (short[])obj; for (int i = 0; i < sArray.length; i++) s += sArray[i] + " " ; return s; } return obj.toString(); } /** Checks that the provided <code>ImageWriter</code> can encode * the provided <code>ImageTypeSpecifier</code> or not. If not, an * <code>IIOException</code> will be thrown. * @param writer The provided <code>ImageWriter</code>. * @param type The image to be tested. * @throws IIOException If the writer cannot encoded the provided image. */ public static final void canEncodeImage(ImageWriter writer, ImageTypeSpecifier type) throws IIOException { ImageWriterSpi spi = writer.getOriginatingProvider(); if(type != null && spi != null && !spi.canEncodeImage(type)) { throw new IIOException(I18N.getString("ImageUtil2")+" "+ writer.getClass().getName()); } } /** Checks that the provided <code>ImageWriter</code> can encode * the provided <code>ColorModel</code> and <code>SampleModel</code>. * If not, an <code>IIOException</code> will be thrown. * @param writer The provided <code>ImageWriter</code>. * @param colorModel The provided <code>ColorModel</code>. * @param sampleModel The provided <code>SampleModel</code>. * @throws IIOException If the writer cannot encoded the provided image. */ public static final void canEncodeImage(ImageWriter writer, ColorModel colorModel, SampleModel sampleModel) throws IIOException { ImageTypeSpecifier type = null; if (colorModel != null && sampleModel != null) type = new ImageTypeSpecifier(colorModel, sampleModel); canEncodeImage(writer, type); } /** * Returns whether the image has contiguous data across rows. */ public static final boolean imageIsContiguous(RenderedImage image) { SampleModel sm; if(image instanceof BufferedImage) { WritableRaster ras = ((BufferedImage)image).getRaster(); sm = ras.getSampleModel(); } else { sm = image.getSampleModel(); } if (sm instanceof ComponentSampleModel) { // Ensure image rows samples are stored contiguously // in a single bank. ComponentSampleModel csm = (ComponentSampleModel)sm; if (csm.getPixelStride() != csm.getNumBands()) { return false; } int[] bandOffsets = csm.getBandOffsets(); for (int i = 0; i < bandOffsets.length; i++) { if (bandOffsets[i] != i) { return false; } } int[] bankIndices = csm.getBankIndices(); for (int i = 0; i < bandOffsets.length; i++) { if (bankIndices[i] != 0) { return false; } } return true; } // Otherwise true if and only if it's a bilevel image with // a MultiPixelPackedSampleModel, 1 bit per pixel, and 1 bit // pixel stride. return ImageUtil.isBinary(sm); } /** * Gets the destination image type. */ // NOTE: Code shamelessly copied from ImageReader.getDestination(). public static final ImageTypeSpecifier getDestinationType(ImageReadParam param, Iterator imageTypes) throws IIOException { if (imageTypes == null || !imageTypes.hasNext()) { throw new IllegalArgumentException("imageTypes null or empty!"); } ImageTypeSpecifier imageType = null; // If param is non-null, use it if (param != null) { imageType = param.getDestinationType(); } // No info from param, use fallback image type if (imageType == null) { Object o = imageTypes.next(); if (!(o instanceof ImageTypeSpecifier)) { throw new IllegalArgumentException ("Non-ImageTypeSpecifier retrieved from imageTypes!"); } imageType = (ImageTypeSpecifier)o; } else { boolean foundIt = false; while (imageTypes.hasNext()) { ImageTypeSpecifier type = (ImageTypeSpecifier)imageTypes.next(); if (type.equals(imageType)) { foundIt = true; break; } } if (!foundIt) { throw new IIOException ("Destination type from ImageReadParam does not match!"); } } return imageType; } /** * Returns <code>true</code> if the given <code>ColorSpace</code> object * is an instance of <code>ICC_ColorSpace</code> but is not one of the * standard <code>ColorSpace</code>s returned by * <code>ColorSpace.getInstance()</code>. * * @param cs The <code>ColorSpace</code> to test. */ public static boolean isNonStandardICCColorSpace(ColorSpace cs) { boolean retval = false; try { // Check the standard ColorSpaces in decreasing order of // likelihood except check CS_PYCC last as in some JREs // PYCC.pf used not to be installed. retval = (cs instanceof ICC_ColorSpace) && !(cs.isCS_sRGB() || cs.equals(ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB)) || cs.equals(ColorSpace.getInstance(ColorSpace.CS_GRAY)) || cs.equals(ColorSpace.getInstance(ColorSpace.CS_CIEXYZ)) || cs.equals(ColorSpace.getInstance(ColorSpace.CS_PYCC))); } catch(IllegalArgumentException e) { // PYCC.pf not installed: ignore it - 'retval' is still 'false'. } return retval; } // Method to return JDK core ImageReaderSPI/ImageWriterSPI for a // given formatName. public static List getJDKImageReaderWriterSPI(ServiceRegistry registry, String formatName, boolean isReader) { IIORegistry iioRegistry = (IIORegistry)registry; Class spiClass; String descPart; if (isReader) { spiClass = ImageReaderSpi.class; descPart = " image reader"; } else { spiClass = ImageWriterSpi.class; descPart = " image writer"; } Iterator iter = iioRegistry.getServiceProviders(spiClass, true); // useOrdering String formatNames[]; ImageReaderWriterSpi provider; String desc = "standard " + formatName + descPart; String jiioPath = "com.sun.media.imageioimpl"; Locale locale = Locale.getDefault(); ArrayList list = new ArrayList(); while (iter.hasNext()) { provider = (ImageReaderWriterSpi)iter.next(); // Look for JDK core ImageWriterSpi's if (provider.getVendorName().startsWith("Sun Microsystems") && desc.equalsIgnoreCase(provider.getDescription(locale)) && // not JAI Image I/O plugins !provider.getPluginClassName().startsWith(jiioPath)) { // Get the formatNames supported by this Spi formatNames = provider.getFormatNames(); for (int i=0; i<formatNames.length; i++) { if (formatNames[i].equalsIgnoreCase(formatName)) { // Must be a JDK provided ImageReader/ImageWriter list.add(provider); break; } } } } return list; } public static void processOnRegistration(ServiceRegistry registry, Class category, String formatName, ImageReaderWriterSpi spi, int deregisterJvmVersion, int priorityJvmVersion) { // Check which JVM we are running on String jvmVendor = System.getProperty("java.vendor"); String jvmVersionString = System.getProperty("java.specification.version"); int verIndex = jvmVersionString.indexOf("1."); // Skip the "1." part to get to the part of the version number that // actually changes from version to version // The assumption here is that "java.specification.version" is // always of the format "x.y" and not "x.y.z" since that is what has // been practically observed in all JDKs to-date, an examination of // the code returning this property bears this out. However this does // not guarantee that the format won't change in the future, // though that seems unlikely. jvmVersionString = jvmVersionString.substring(verIndex+2); int jvmVersion = Integer.parseInt(jvmVersionString); if (jvmVendor.equals("Sun Microsystems Inc.")) { List list; if (spi instanceof ImageReaderSpi) list = getJDKImageReaderWriterSPI(registry, formatName, true); else list = getJDKImageReaderWriterSPI(registry, formatName, false); if (jvmVersion >= deregisterJvmVersion && list.size() != 0) { // De-register JIIO's plug-in registry.deregisterServiceProvider(spi, category); } else { for (int i=0; i<list.size(); i++) { if (jvmVersion >= priorityJvmVersion) { // Set JIIO plug-in to lower priority registry.setOrdering(category, list.get(i), spi); } else { // Set JIIO plug-in to higher priority registry.setOrdering(category, spi, list.get(i)); } } } } } public static int readMultiByteInteger(ImageInputStream iis) throws IOException { int value = iis.readByte(); int result = value & 0x7f; while((value & 0x80) == 0x80) { result <<= 7; value = iis.readByte(); result |= (value & 0x7f); } return result; } }