| /* |
| * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package com.sun.imageio.plugins.bmp; |
| |
| 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.color.ICC_Profile; |
| 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.DataBufferUShort; |
| import java.awt.image.DirectColorModel; |
| import java.awt.image.IndexColorModel; |
| import java.awt.image.MultiPixelPackedSampleModel; |
| import java.awt.image.PixelInterleavedSampleModel; |
| import java.awt.image.Raster; |
| import java.awt.image.SampleModel; |
| import java.awt.image.SinglePixelPackedSampleModel; |
| import java.awt.image.WritableRaster; |
| |
| import javax.imageio.IIOException; |
| import javax.imageio.ImageIO; |
| import javax.imageio.ImageReader; |
| import javax.imageio.ImageReadParam; |
| import javax.imageio.ImageTypeSpecifier; |
| import javax.imageio.metadata.IIOMetadata; |
| import javax.imageio.spi.ImageReaderSpi; |
| import javax.imageio.stream.ImageInputStream; |
| import javax.imageio.event.IIOReadProgressListener; |
| import javax.imageio.event.IIOReadUpdateListener; |
| import javax.imageio.event.IIOReadWarningListener; |
| |
| import java.io.*; |
| import java.nio.*; |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.StringTokenizer; |
| |
| import com.sun.imageio.plugins.common.ImageUtil; |
| import com.sun.imageio.plugins.common.I18N; |
| |
| /** This class is the Java Image IO plugin reader for BMP images. |
| * It may subsample the image, clip the image, select sub-bands, |
| * and shift the decoded image origin if the proper decoding parameter |
| * are set in the provided <code>ImageReadParam</code>. |
| * |
| * This class supports Microsoft Windows Bitmap Version 3-5, |
| * as well as OS/2 Bitmap Version 2.x (for single-image BMP file). |
| */ |
| public class BMPImageReader extends ImageReader implements BMPConstants { |
| // BMP Image types |
| private static final int VERSION_2_1_BIT = 0; |
| private static final int VERSION_2_4_BIT = 1; |
| private static final int VERSION_2_8_BIT = 2; |
| private static final int VERSION_2_24_BIT = 3; |
| |
| private static final int VERSION_3_1_BIT = 4; |
| private static final int VERSION_3_4_BIT = 5; |
| private static final int VERSION_3_8_BIT = 6; |
| private static final int VERSION_3_24_BIT = 7; |
| |
| private static final int VERSION_3_NT_16_BIT = 8; |
| private static final int VERSION_3_NT_32_BIT = 9; |
| |
| private static final int VERSION_4_1_BIT = 10; |
| private static final int VERSION_4_4_BIT = 11; |
| private static final int VERSION_4_8_BIT = 12; |
| private static final int VERSION_4_16_BIT = 13; |
| private static final int VERSION_4_24_BIT = 14; |
| private static final int VERSION_4_32_BIT = 15; |
| |
| private static final int VERSION_3_XP_EMBEDDED = 16; |
| private static final int VERSION_4_XP_EMBEDDED = 17; |
| private static final int VERSION_5_XP_EMBEDDED = 18; |
| |
| // BMP variables |
| private long bitmapFileSize; |
| private long bitmapOffset; |
| private long compression; |
| private long imageSize; |
| private byte palette[]; |
| private int imageType; |
| private int numBands; |
| private boolean isBottomUp; |
| private int bitsPerPixel; |
| private int redMask, greenMask, blueMask, alphaMask; |
| |
| private SampleModel sampleModel, originalSampleModel; |
| private ColorModel colorModel, originalColorModel; |
| |
| /** The input stream where reads from */ |
| private ImageInputStream iis = null; |
| |
| /** Indicates whether the header is read. */ |
| private boolean gotHeader = false; |
| |
| /** The original image width. */ |
| private int width; |
| |
| /** The original image height. */ |
| private int height; |
| |
| /** The destination region. */ |
| private Rectangle destinationRegion; |
| |
| /** The source region. */ |
| private Rectangle sourceRegion; |
| |
| /** The metadata from the stream. */ |
| private BMPMetadata metadata; |
| |
| /** The destination image. */ |
| private BufferedImage bi; |
| |
| /** Indicates whether subsampled, subregion is required, and offset is |
| * defined |
| */ |
| private boolean noTransform = true; |
| |
| /** Indicates whether subband is selected. */ |
| private boolean seleBand = false; |
| |
| /** The scaling factors. */ |
| private int scaleX, scaleY; |
| |
| /** source and destination bands. */ |
| private int[] sourceBands, destBands; |
| |
| /** Constructs <code>BMPImageReader</code> from the provided |
| * <code>ImageReaderSpi</code>. |
| */ |
| public BMPImageReader(ImageReaderSpi originator) { |
| super(originator); |
| } |
| |
| /** Overrides the method defined in the superclass. */ |
| public void setInput(Object input, |
| boolean seekForwardOnly, |
| boolean ignoreMetadata) { |
| super.setInput(input, seekForwardOnly, ignoreMetadata); |
| iis = (ImageInputStream) input; // Always works |
| if(iis != null) |
| iis.setByteOrder(ByteOrder.LITTLE_ENDIAN); |
| resetHeaderInfo(); |
| } |
| |
| /** Overrides the method defined in the superclass. */ |
| public int getNumImages(boolean allowSearch) throws IOException { |
| if (iis == null) { |
| throw new IllegalStateException(I18N.getString("GetNumImages0")); |
| } |
| if (seekForwardOnly && allowSearch) { |
| throw new IllegalStateException(I18N.getString("GetNumImages1")); |
| } |
| return 1; |
| } |
| |
| @Override |
| public int getWidth(int imageIndex) throws IOException { |
| checkIndex(imageIndex); |
| try { |
| readHeader(); |
| } catch (IllegalArgumentException e) { |
| throw new IIOException(I18N.getString("BMPImageReader6"), e); |
| } |
| return width; |
| } |
| |
| public int getHeight(int imageIndex) throws IOException { |
| checkIndex(imageIndex); |
| try { |
| readHeader(); |
| } catch (IllegalArgumentException e) { |
| throw new IIOException(I18N.getString("BMPImageReader6"), e); |
| } |
| return height; |
| } |
| |
| private void checkIndex(int imageIndex) { |
| if (imageIndex != 0) { |
| throw new IndexOutOfBoundsException(I18N.getString("BMPImageReader0")); |
| } |
| } |
| |
| /** |
| * Process the image header. |
| * |
| * @exception IllegalStateException if source stream is not set. |
| * |
| * @exception IOException if image stream is corrupted. |
| * |
| * @exception IllegalArgumentException if the image stream does not contain |
| * a BMP image, or if a sample model instance to describe the |
| * image can not be created. |
| */ |
| protected void readHeader() throws IOException, IllegalArgumentException { |
| if (gotHeader) |
| return; |
| |
| if (iis == null) { |
| throw new IllegalStateException("Input source not set!"); |
| } |
| int profileData = 0, profileSize = 0; |
| |
| this.metadata = new BMPMetadata(); |
| iis.mark(); |
| |
| // read and check the magic marker |
| byte[] marker = new byte[2]; |
| iis.read(marker); |
| if (marker[0] != 0x42 || marker[1] != 0x4d) |
| throw new IllegalArgumentException(I18N.getString("BMPImageReader1")); |
| |
| // Read file size |
| bitmapFileSize = iis.readUnsignedInt(); |
| // skip the two reserved fields |
| iis.skipBytes(4); |
| |
| // Offset to the bitmap from the beginning |
| bitmapOffset = iis.readUnsignedInt(); |
| // End File Header |
| |
| // Start BitmapCoreHeader |
| long size = iis.readUnsignedInt(); |
| |
| if (size == 12) { |
| width = iis.readShort(); |
| height = iis.readShort(); |
| } else { |
| width = iis.readInt(); |
| height = iis.readInt(); |
| } |
| |
| metadata.width = width; |
| metadata.height = height; |
| |
| int planes = iis.readUnsignedShort(); |
| bitsPerPixel = iis.readUnsignedShort(); |
| |
| //metadata.colorPlane = planes; |
| metadata.bitsPerPixel = (short)bitsPerPixel; |
| |
| // As BMP always has 3 rgb bands, except for Version 5, |
| // which is bgra |
| numBands = 3; |
| |
| if (size == 12) { |
| // Windows 2.x and OS/2 1.x |
| metadata.bmpVersion = VERSION_2; |
| |
| // Classify the image type |
| if (bitsPerPixel == 1) { |
| imageType = VERSION_2_1_BIT; |
| } else if (bitsPerPixel == 4) { |
| imageType = VERSION_2_4_BIT; |
| } else if (bitsPerPixel == 8) { |
| imageType = VERSION_2_8_BIT; |
| } else if (bitsPerPixel == 24) { |
| imageType = VERSION_2_24_BIT; |
| } |
| |
| // Read in the palette |
| int numberOfEntries = (int)((bitmapOffset - 14 - size) / 3); |
| int sizeOfPalette = numberOfEntries*3; |
| palette = new byte[sizeOfPalette]; |
| iis.readFully(palette, 0, sizeOfPalette); |
| metadata.palette = palette; |
| metadata.paletteSize = numberOfEntries; |
| } else { |
| compression = iis.readUnsignedInt(); |
| imageSize = iis.readUnsignedInt(); |
| long xPelsPerMeter = iis.readInt(); |
| long yPelsPerMeter = iis.readInt(); |
| long colorsUsed = iis.readUnsignedInt(); |
| long colorsImportant = iis.readUnsignedInt(); |
| |
| metadata.compression = (int)compression; |
| metadata.xPixelsPerMeter = (int)xPelsPerMeter; |
| metadata.yPixelsPerMeter = (int)yPelsPerMeter; |
| metadata.colorsUsed = (int)colorsUsed; |
| metadata.colorsImportant = (int)colorsImportant; |
| |
| if (size == 40) { |
| // Windows 3.x and Windows NT |
| switch((int)compression) { |
| |
| case BI_JPEG: |
| case BI_PNG: |
| metadata.bmpVersion = VERSION_3; |
| imageType = VERSION_3_XP_EMBEDDED; |
| break; |
| |
| case BI_RGB: // No compression |
| case BI_RLE8: // 8-bit RLE compression |
| case BI_RLE4: // 4-bit RLE compression |
| |
| // Read in the palette |
| if (bitmapOffset < (size + 14)) { |
| throw new IIOException(I18N.getString("BMPImageReader7")); |
| } |
| int numberOfEntries = (int)((bitmapOffset-14-size) / 4); |
| int sizeOfPalette = numberOfEntries * 4; |
| palette = new byte[sizeOfPalette]; |
| iis.readFully(palette, 0, sizeOfPalette); |
| |
| metadata.palette = palette; |
| metadata.paletteSize = numberOfEntries; |
| |
| if (bitsPerPixel == 1) { |
| imageType = VERSION_3_1_BIT; |
| } else if (bitsPerPixel == 4) { |
| imageType = VERSION_3_4_BIT; |
| } else if (bitsPerPixel == 8) { |
| imageType = VERSION_3_8_BIT; |
| } else if (bitsPerPixel == 24) { |
| imageType = VERSION_3_24_BIT; |
| } else if (bitsPerPixel == 16) { |
| imageType = VERSION_3_NT_16_BIT; |
| |
| redMask = 0x7C00; |
| greenMask = 0x3E0; |
| blueMask = (1 << 5) - 1;// 0x1F; |
| metadata.redMask = redMask; |
| metadata.greenMask = greenMask; |
| metadata.blueMask = blueMask; |
| } else if (bitsPerPixel == 32) { |
| imageType = VERSION_3_NT_32_BIT; |
| redMask = 0x00FF0000; |
| greenMask = 0x0000FF00; |
| blueMask = 0x000000FF; |
| metadata.redMask = redMask; |
| metadata.greenMask = greenMask; |
| metadata.blueMask = blueMask; |
| } |
| |
| metadata.bmpVersion = VERSION_3; |
| break; |
| |
| case BI_BITFIELDS: |
| |
| if (bitsPerPixel == 16) { |
| imageType = VERSION_3_NT_16_BIT; |
| } else if (bitsPerPixel == 32) { |
| imageType = VERSION_3_NT_32_BIT; |
| } |
| |
| // BitsField encoding |
| redMask = (int)iis.readUnsignedInt(); |
| greenMask = (int)iis.readUnsignedInt(); |
| blueMask = (int)iis.readUnsignedInt(); |
| metadata.redMask = redMask; |
| metadata.greenMask = greenMask; |
| metadata.blueMask = blueMask; |
| |
| if (colorsUsed != 0) { |
| // there is a palette |
| sizeOfPalette = (int)colorsUsed*4; |
| palette = new byte[sizeOfPalette]; |
| iis.readFully(palette, 0, sizeOfPalette); |
| |
| metadata.palette = palette; |
| metadata.paletteSize = (int)colorsUsed; |
| } |
| metadata.bmpVersion = VERSION_3_NT; |
| |
| break; |
| default: |
| throw new |
| IIOException(I18N.getString("BMPImageReader2")); |
| } |
| } else if (size == 108 || size == 124) { |
| // Windows 4.x BMP |
| if (size == 108) |
| metadata.bmpVersion = VERSION_4; |
| else if (size == 124) |
| metadata.bmpVersion = VERSION_5; |
| |
| // rgb masks, valid only if comp is BI_BITFIELDS |
| redMask = (int)iis.readUnsignedInt(); |
| greenMask = (int)iis.readUnsignedInt(); |
| blueMask = (int)iis.readUnsignedInt(); |
| // Only supported for 32bpp BI_RGB argb |
| alphaMask = (int)iis.readUnsignedInt(); |
| long csType = iis.readUnsignedInt(); |
| int redX = iis.readInt(); |
| int redY = iis.readInt(); |
| int redZ = iis.readInt(); |
| int greenX = iis.readInt(); |
| int greenY = iis.readInt(); |
| int greenZ = iis.readInt(); |
| int blueX = iis.readInt(); |
| int blueY = iis.readInt(); |
| int blueZ = iis.readInt(); |
| long gammaRed = iis.readUnsignedInt(); |
| long gammaGreen = iis.readUnsignedInt(); |
| long gammaBlue = iis.readUnsignedInt(); |
| |
| if (size == 124) { |
| metadata.intent = iis.readInt(); |
| profileData = iis.readInt(); |
| profileSize = iis.readInt(); |
| iis.skipBytes(4); |
| } |
| |
| metadata.colorSpace = (int)csType; |
| |
| if (csType == LCS_CALIBRATED_RGB) { |
| // All the new fields are valid only for this case |
| metadata.redX = redX; |
| metadata.redY = redY; |
| metadata.redZ = redZ; |
| metadata.greenX = greenX; |
| metadata.greenY = greenY; |
| metadata.greenZ = greenZ; |
| metadata.blueX = blueX; |
| metadata.blueY = blueY; |
| metadata.blueZ = blueZ; |
| metadata.gammaRed = (int)gammaRed; |
| metadata.gammaGreen = (int)gammaGreen; |
| metadata.gammaBlue = (int)gammaBlue; |
| } |
| |
| // Read in the palette |
| int numberOfEntries = (int)((bitmapOffset-14-size) / 4); |
| int sizeOfPalette = numberOfEntries*4; |
| palette = new byte[sizeOfPalette]; |
| iis.readFully(palette, 0, sizeOfPalette); |
| metadata.palette = palette; |
| metadata.paletteSize = numberOfEntries; |
| |
| switch ((int)compression) { |
| case BI_JPEG: |
| case BI_PNG: |
| if (size == 108) { |
| imageType = VERSION_4_XP_EMBEDDED; |
| } else if (size == 124) { |
| imageType = VERSION_5_XP_EMBEDDED; |
| } |
| break; |
| default: |
| if (bitsPerPixel == 1) { |
| imageType = VERSION_4_1_BIT; |
| } else if (bitsPerPixel == 4) { |
| imageType = VERSION_4_4_BIT; |
| } else if (bitsPerPixel == 8) { |
| imageType = VERSION_4_8_BIT; |
| } else if (bitsPerPixel == 16) { |
| imageType = VERSION_4_16_BIT; |
| if ((int)compression == BI_RGB) { |
| redMask = 0x7C00; |
| greenMask = 0x3E0; |
| blueMask = 0x1F; |
| } |
| } else if (bitsPerPixel == 24) { |
| imageType = VERSION_4_24_BIT; |
| } else if (bitsPerPixel == 32) { |
| imageType = VERSION_4_32_BIT; |
| if ((int)compression == BI_RGB) { |
| redMask = 0x00FF0000; |
| greenMask = 0x0000FF00; |
| blueMask = 0x000000FF; |
| } |
| } |
| |
| metadata.redMask = redMask; |
| metadata.greenMask = greenMask; |
| metadata.blueMask = blueMask; |
| metadata.alphaMask = alphaMask; |
| } |
| } else { |
| throw new |
| IIOException(I18N.getString("BMPImageReader3")); |
| } |
| } |
| |
| if (height > 0) { |
| // bottom up image |
| isBottomUp = true; |
| } else { |
| // top down image |
| isBottomUp = false; |
| height = Math.abs(height); |
| } |
| |
| // Reset Image Layout so there's only one tile. |
| //Define the color space |
| ColorSpace colorSpace = ColorSpace.getInstance(ColorSpace.CS_sRGB); |
| if (metadata.colorSpace == PROFILE_LINKED || |
| metadata.colorSpace == PROFILE_EMBEDDED) { |
| |
| iis.mark(); |
| iis.skipBytes(profileData - size); |
| byte[] profile = new byte[profileSize]; |
| iis.readFully(profile, 0, profileSize); |
| iis.reset(); |
| |
| try { |
| if (metadata.colorSpace == PROFILE_LINKED && |
| isLinkedProfileAllowed() && |
| !isUncOrDevicePath(profile)) |
| { |
| String path = new String(profile, "windows-1252"); |
| |
| colorSpace = |
| new ICC_ColorSpace(ICC_Profile.getInstance(path)); |
| } else { |
| colorSpace = |
| new ICC_ColorSpace(ICC_Profile.getInstance(profile)); |
| } |
| } catch (Exception e) { |
| colorSpace = ColorSpace.getInstance(ColorSpace.CS_sRGB); |
| } |
| } |
| |
| if (bitsPerPixel == 0 || |
| compression == BI_JPEG || compression == BI_PNG ) |
| { |
| // the colorModel and sampleModel will be initialzed |
| // by the reader of embedded image |
| colorModel = null; |
| sampleModel = null; |
| } else if (bitsPerPixel == 1 || bitsPerPixel == 4 || bitsPerPixel == 8) { |
| // When number of bitsPerPixel is <= 8, we use IndexColorModel. |
| numBands = 1; |
| |
| if (bitsPerPixel == 8) { |
| int[] bandOffsets = new int[numBands]; |
| for (int i = 0; i < numBands; i++) { |
| bandOffsets[i] = numBands -1 -i; |
| } |
| sampleModel = |
| new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, |
| width, height, |
| numBands, |
| numBands * width, |
| bandOffsets); |
| } else { |
| // 1 and 4 bit pixels can be stored in a packed format. |
| sampleModel = |
| new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE, |
| width, height, |
| bitsPerPixel); |
| } |
| |
| // Create IndexColorModel from the palette. |
| byte r[], g[], b[]; |
| if (imageType == VERSION_2_1_BIT || |
| imageType == VERSION_2_4_BIT || |
| imageType == VERSION_2_8_BIT) { |
| |
| |
| size = palette.length/3; |
| |
| if (size > 256) { |
| size = 256; |
| } |
| |
| int off; |
| r = new byte[(int)size]; |
| g = new byte[(int)size]; |
| b = new byte[(int)size]; |
| for (int i=0; i<(int)size; i++) { |
| off = 3 * i; |
| b[i] = palette[off]; |
| g[i] = palette[off+1]; |
| r[i] = palette[off+2]; |
| } |
| } else { |
| size = palette.length/4; |
| |
| if (size > 256) { |
| size = 256; |
| } |
| |
| int off; |
| r = new byte[(int)size]; |
| g = new byte[(int)size]; |
| b = new byte[(int)size]; |
| for (int i=0; i<size; i++) { |
| off = 4 * i; |
| b[i] = palette[off]; |
| g[i] = palette[off+1]; |
| r[i] = palette[off+2]; |
| } |
| } |
| |
| if (ImageUtil.isIndicesForGrayscale(r, g, b)) |
| colorModel = |
| ImageUtil.createColorModel(null, sampleModel); |
| else |
| colorModel = new IndexColorModel(bitsPerPixel, (int)size, r, g, b); |
| } else if (bitsPerPixel == 16) { |
| numBands = 3; |
| sampleModel = |
| new SinglePixelPackedSampleModel(DataBuffer.TYPE_USHORT, |
| width, height, |
| new int[] {redMask, greenMask, blueMask}); |
| |
| colorModel = |
| new DirectColorModel(colorSpace, |
| 16, redMask, greenMask, blueMask, 0, |
| false, DataBuffer.TYPE_USHORT); |
| |
| } else if (bitsPerPixel == 32) { |
| numBands = alphaMask == 0 ? 3 : 4; |
| |
| // The number of bands in the SampleModel is determined by |
| // the length of the mask array passed in. |
| int[] bitMasks = numBands == 3 ? |
| new int[] {redMask, greenMask, blueMask} : |
| new int[] {redMask, greenMask, blueMask, alphaMask}; |
| |
| sampleModel = |
| new SinglePixelPackedSampleModel(DataBuffer.TYPE_INT, |
| width, height, |
| bitMasks); |
| |
| colorModel = |
| new DirectColorModel(colorSpace, |
| 32, redMask, greenMask, blueMask, alphaMask, |
| false, DataBuffer.TYPE_INT); |
| } else { |
| numBands = 3; |
| // Create SampleModel |
| int[] bandOffsets = new int[numBands]; |
| for (int i = 0; i < numBands; i++) { |
| bandOffsets[i] = numBands -1 -i; |
| } |
| |
| sampleModel = |
| new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, |
| width, height, |
| numBands, |
| numBands * width, |
| bandOffsets); |
| |
| colorModel = |
| ImageUtil.createColorModel(colorSpace, sampleModel); |
| } |
| |
| originalSampleModel = sampleModel; |
| originalColorModel = colorModel; |
| |
| // Reset to the start of bitmap; then jump to the |
| //start of image data |
| iis.reset(); |
| iis.skipBytes(bitmapOffset); |
| gotHeader = true; |
| } |
| |
| public Iterator getImageTypes(int imageIndex) |
| throws IOException { |
| checkIndex(imageIndex); |
| try { |
| readHeader(); |
| } catch (IllegalArgumentException e) { |
| throw new IIOException(I18N.getString("BMPImageReader6"), e); |
| } |
| ArrayList list = new ArrayList(1); |
| list.add(new ImageTypeSpecifier(originalColorModel, |
| originalSampleModel)); |
| return list.iterator(); |
| } |
| |
| public ImageReadParam getDefaultReadParam() { |
| return new ImageReadParam(); |
| } |
| |
| public IIOMetadata getImageMetadata(int imageIndex) |
| throws IOException { |
| checkIndex(imageIndex); |
| if (metadata == null) { |
| try { |
| readHeader(); |
| } catch (IllegalArgumentException e) { |
| throw new IIOException(I18N.getString("BMPImageReader6"), e); |
| } |
| } |
| return metadata; |
| } |
| |
| public IIOMetadata getStreamMetadata() throws IOException { |
| return null; |
| } |
| |
| public boolean isRandomAccessEasy(int imageIndex) throws IOException { |
| checkIndex(imageIndex); |
| try { |
| readHeader(); |
| } catch (IllegalArgumentException e) { |
| throw new IIOException(I18N.getString("BMPImageReader6"), e); |
| } |
| return metadata.compression == BI_RGB; |
| } |
| |
| public BufferedImage read(int imageIndex, ImageReadParam param) |
| throws IOException { |
| |
| if (iis == null) { |
| throw new IllegalStateException(I18N.getString("BMPImageReader5")); |
| } |
| |
| checkIndex(imageIndex); |
| clearAbortRequest(); |
| processImageStarted(imageIndex); |
| |
| if (param == null) |
| param = getDefaultReadParam(); |
| |
| //read header |
| try { |
| readHeader(); |
| } catch (IllegalArgumentException e) { |
| throw new IIOException(I18N.getString("BMPImageReader6"), e); |
| } |
| |
| sourceRegion = new Rectangle(0, 0, 0, 0); |
| destinationRegion = new Rectangle(0, 0, 0, 0); |
| |
| computeRegions(param, this.width, this.height, |
| param.getDestination(), |
| sourceRegion, |
| destinationRegion); |
| |
| scaleX = param.getSourceXSubsampling(); |
| scaleY = param.getSourceYSubsampling(); |
| |
| // If the destination band is set used it |
| sourceBands = param.getSourceBands(); |
| destBands = param.getDestinationBands(); |
| |
| seleBand = (sourceBands != null) && (destBands != null); |
| noTransform = |
| destinationRegion.equals(new Rectangle(0, 0, width, height)) || |
| seleBand; |
| |
| if (!seleBand) { |
| sourceBands = new int[numBands]; |
| destBands = new int[numBands]; |
| for (int i = 0; i < numBands; i++) |
| destBands[i] = sourceBands[i] = i; |
| } |
| |
| // If the destination is provided, then use it. Otherwise, create new one |
| bi = param.getDestination(); |
| |
| // Get the image data. |
| WritableRaster raster = null; |
| |
| if (bi == null) { |
| if (sampleModel != null && colorModel != null) { |
| sampleModel = |
| sampleModel.createCompatibleSampleModel(destinationRegion.x + |
| destinationRegion.width, |
| destinationRegion.y + |
| destinationRegion.height); |
| if (seleBand) |
| sampleModel = sampleModel.createSubsetSampleModel(sourceBands); |
| raster = Raster.createWritableRaster(sampleModel, new Point()); |
| bi = new BufferedImage(colorModel, raster, false, null); |
| } |
| } else { |
| raster = bi.getWritableTile(0, 0); |
| sampleModel = bi.getSampleModel(); |
| colorModel = bi.getColorModel(); |
| |
| noTransform &= destinationRegion.equals(raster.getBounds()); |
| } |
| |
| byte bdata[] = null; // buffer for byte data |
| short sdata[] = null; // buffer for short data |
| int idata[] = null; // buffer for int data |
| |
| // the sampleModel can be null in case of embedded image |
| if (sampleModel != null) { |
| if (sampleModel.getDataType() == DataBuffer.TYPE_BYTE) |
| bdata = (byte[]) |
| ((DataBufferByte)raster.getDataBuffer()).getData(); |
| else if (sampleModel.getDataType() == DataBuffer.TYPE_USHORT) |
| sdata = (short[]) |
| ((DataBufferUShort)raster.getDataBuffer()).getData(); |
| else if (sampleModel.getDataType() == DataBuffer.TYPE_INT) |
| idata = (int[]) |
| ((DataBufferInt)raster.getDataBuffer()).getData(); |
| } |
| |
| // There should only be one tile. |
| switch(imageType) { |
| |
| case VERSION_2_1_BIT: |
| // no compression |
| read1Bit(bdata); |
| break; |
| |
| case VERSION_2_4_BIT: |
| // no compression |
| read4Bit(bdata); |
| break; |
| |
| case VERSION_2_8_BIT: |
| // no compression |
| read8Bit(bdata); |
| break; |
| |
| case VERSION_2_24_BIT: |
| // no compression |
| read24Bit(bdata); |
| break; |
| |
| case VERSION_3_1_BIT: |
| // 1-bit images cannot be compressed. |
| read1Bit(bdata); |
| break; |
| |
| case VERSION_3_4_BIT: |
| switch((int)compression) { |
| case BI_RGB: |
| read4Bit(bdata); |
| break; |
| |
| case BI_RLE4: |
| readRLE4(bdata); |
| break; |
| |
| default: |
| throw new |
| IIOException(I18N.getString("BMPImageReader1")); |
| } |
| break; |
| |
| case VERSION_3_8_BIT: |
| switch((int)compression) { |
| case BI_RGB: |
| read8Bit(bdata); |
| break; |
| |
| case BI_RLE8: |
| readRLE8(bdata); |
| break; |
| |
| default: |
| throw new |
| IIOException(I18N.getString("BMPImageReader1")); |
| } |
| |
| break; |
| |
| case VERSION_3_24_BIT: |
| // 24-bit images are not compressed |
| read24Bit(bdata); |
| break; |
| |
| case VERSION_3_NT_16_BIT: |
| read16Bit(sdata); |
| break; |
| |
| case VERSION_3_NT_32_BIT: |
| read32Bit(idata); |
| break; |
| |
| case VERSION_3_XP_EMBEDDED: |
| case VERSION_4_XP_EMBEDDED: |
| case VERSION_5_XP_EMBEDDED: |
| bi = readEmbedded((int)compression, bi, param); |
| break; |
| |
| case VERSION_4_1_BIT: |
| read1Bit(bdata); |
| break; |
| |
| case VERSION_4_4_BIT: |
| switch((int)compression) { |
| |
| case BI_RGB: |
| read4Bit(bdata); |
| break; |
| |
| case BI_RLE4: |
| readRLE4(bdata); |
| break; |
| |
| default: |
| throw new |
| IIOException(I18N.getString("BMPImageReader1")); |
| } |
| |
| case VERSION_4_8_BIT: |
| switch((int)compression) { |
| |
| case BI_RGB: |
| read8Bit(bdata); |
| break; |
| |
| case BI_RLE8: |
| readRLE8(bdata); |
| break; |
| |
| default: |
| throw new |
| IIOException(I18N.getString("BMPImageReader1")); |
| } |
| break; |
| |
| case VERSION_4_16_BIT: |
| read16Bit(sdata); |
| break; |
| |
| case VERSION_4_24_BIT: |
| read24Bit(bdata); |
| break; |
| |
| case VERSION_4_32_BIT: |
| read32Bit(idata); |
| break; |
| } |
| |
| if (abortRequested()) |
| processReadAborted(); |
| else |
| processImageComplete(); |
| |
| return bi; |
| } |
| |
| public boolean canReadRaster() { |
| return true; |
| } |
| |
| public Raster readRaster(int imageIndex, |
| ImageReadParam param) throws IOException { |
| BufferedImage bi = read(imageIndex, param); |
| return bi.getData(); |
| } |
| |
| private void resetHeaderInfo() { |
| gotHeader = false; |
| bi = null; |
| sampleModel = originalSampleModel = null; |
| colorModel = originalColorModel = null; |
| } |
| |
| public void reset() { |
| super.reset(); |
| iis = null; |
| resetHeaderInfo(); |
| } |
| |
| // Deal with 1 Bit images using IndexColorModels |
| private void read1Bit(byte[] bdata) throws IOException { |
| int bytesPerScanline = (width + 7) / 8; |
| int padding = bytesPerScanline % 4; |
| if (padding != 0) { |
| padding = 4 - padding; |
| } |
| |
| int lineLength = bytesPerScanline + padding; |
| |
| if (noTransform) { |
| int j = isBottomUp ? (height -1)*bytesPerScanline : 0; |
| |
| for (int i=0; i<height; i++) { |
| if (abortRequested()) { |
| break; |
| } |
| iis.readFully(bdata, j, bytesPerScanline); |
| iis.skipBytes(padding); |
| j += isBottomUp ? -bytesPerScanline : bytesPerScanline; |
| processImageUpdate(bi, 0, i, |
| destinationRegion.width, 1, 1, 1, |
| new int[]{0}); |
| processImageProgress(100.0F * i/destinationRegion.height); |
| } |
| } else { |
| byte[] buf = new byte[lineLength]; |
| int lineStride = |
| ((MultiPixelPackedSampleModel)sampleModel).getScanlineStride(); |
| |
| if (isBottomUp) { |
| int lastLine = |
| sourceRegion.y + (destinationRegion.height - 1) * scaleY; |
| iis.skipBytes(lineLength * (height - 1 - lastLine)); |
| } else |
| iis.skipBytes(lineLength * sourceRegion.y); |
| |
| int skipLength = lineLength * (scaleY - 1); |
| |
| // cache the values to avoid duplicated computation |
| int[] srcOff = new int[destinationRegion.width]; |
| int[] destOff = new int[destinationRegion.width]; |
| int[] srcPos = new int[destinationRegion.width]; |
| int[] destPos = new int[destinationRegion.width]; |
| |
| for (int i = destinationRegion.x, x = sourceRegion.x, j = 0; |
| i < destinationRegion.x + destinationRegion.width; |
| i++, j++, x += scaleX) { |
| srcPos[j] = x >> 3; |
| srcOff[j] = 7 - (x & 7); |
| destPos[j] = i >> 3; |
| destOff[j] = 7 - (i & 7); |
| } |
| |
| int k = destinationRegion.y * lineStride; |
| if (isBottomUp) |
| k += (destinationRegion.height - 1) * lineStride; |
| |
| for (int j = 0, y = sourceRegion.y; |
| j < destinationRegion.height; j++, y+=scaleY) { |
| |
| if (abortRequested()) |
| break; |
| iis.read(buf, 0, lineLength); |
| for (int i = 0; i < destinationRegion.width; i++) { |
| //get the bit and assign to the data buffer of the raster |
| int v = (buf[srcPos[i]] >> srcOff[i]) & 1; |
| bdata[k + destPos[i]] |= v << destOff[i]; |
| } |
| |
| k += isBottomUp ? -lineStride : lineStride; |
| iis.skipBytes(skipLength); |
| processImageUpdate(bi, 0, j, |
| destinationRegion.width, 1, 1, 1, |
| new int[]{0}); |
| processImageProgress(100.0F*j/destinationRegion.height); |
| } |
| } |
| } |
| |
| // Method to read a 4 bit BMP image data |
| private void read4Bit(byte[] bdata) throws IOException { |
| |
| int bytesPerScanline = (width + 1) / 2; |
| |
| // Padding bytes at the end of each scanline |
| int padding = bytesPerScanline % 4; |
| if (padding != 0) |
| padding = 4 - padding; |
| |
| int lineLength = bytesPerScanline + padding; |
| |
| if (noTransform) { |
| int j = isBottomUp ? (height -1) * bytesPerScanline : 0; |
| |
| for (int i=0; i<height; i++) { |
| if (abortRequested()) { |
| break; |
| } |
| iis.readFully(bdata, j, bytesPerScanline); |
| iis.skipBytes(padding); |
| j += isBottomUp ? -bytesPerScanline : bytesPerScanline; |
| processImageUpdate(bi, 0, i, |
| destinationRegion.width, 1, 1, 1, |
| new int[]{0}); |
| processImageProgress(100.0F * i/destinationRegion.height); |
| } |
| } else { |
| byte[] buf = new byte[lineLength]; |
| int lineStride = |
| ((MultiPixelPackedSampleModel)sampleModel).getScanlineStride(); |
| |
| if (isBottomUp) { |
| int lastLine = |
| sourceRegion.y + (destinationRegion.height - 1) * scaleY; |
| iis.skipBytes(lineLength * (height - 1 - lastLine)); |
| } else |
| iis.skipBytes(lineLength * sourceRegion.y); |
| |
| int skipLength = lineLength * (scaleY - 1); |
| |
| // cache the values to avoid duplicated computation |
| int[] srcOff = new int[destinationRegion.width]; |
| int[] destOff = new int[destinationRegion.width]; |
| int[] srcPos = new int[destinationRegion.width]; |
| int[] destPos = new int[destinationRegion.width]; |
| |
| for (int i = destinationRegion.x, x = sourceRegion.x, j = 0; |
| i < destinationRegion.x + destinationRegion.width; |
| i++, j++, x += scaleX) { |
| srcPos[j] = x >> 1; |
| srcOff[j] = (1 - (x & 1)) << 2; |
| destPos[j] = i >> 1; |
| destOff[j] = (1 - (i & 1)) << 2; |
| } |
| |
| int k = destinationRegion.y * lineStride; |
| if (isBottomUp) |
| k += (destinationRegion.height - 1) * lineStride; |
| |
| for (int j = 0, y = sourceRegion.y; |
| j < destinationRegion.height; j++, y+=scaleY) { |
| |
| if (abortRequested()) |
| break; |
| iis.read(buf, 0, lineLength); |
| for (int i = 0; i < destinationRegion.width; i++) { |
| //get the bit and assign to the data buffer of the raster |
| int v = (buf[srcPos[i]] >> srcOff[i]) & 0x0F; |
| bdata[k + destPos[i]] |= v << destOff[i]; |
| } |
| |
| k += isBottomUp ? -lineStride : lineStride; |
| iis.skipBytes(skipLength); |
| processImageUpdate(bi, 0, j, |
| destinationRegion.width, 1, 1, 1, |
| new int[]{0}); |
| processImageProgress(100.0F*j/destinationRegion.height); |
| } |
| } |
| } |
| |
| // Method to read 8 bit BMP image data |
| private void read8Bit(byte[] bdata) throws IOException { |
| |
| // Padding bytes at the end of each scanline |
| int padding = width % 4; |
| if (padding != 0) { |
| padding = 4 - padding; |
| } |
| |
| int lineLength = width + padding; |
| |
| if (noTransform) { |
| int j = isBottomUp ? (height -1) * width : 0; |
| |
| for (int i=0; i<height; i++) { |
| if (abortRequested()) { |
| break; |
| } |
| iis.readFully(bdata, j, width); |
| iis.skipBytes(padding); |
| j += isBottomUp ? -width : width; |
| processImageUpdate(bi, 0, i, |
| destinationRegion.width, 1, 1, 1, |
| new int[]{0}); |
| processImageProgress(100.0F * i/destinationRegion.height); |
| } |
| } else { |
| byte[] buf = new byte[lineLength]; |
| int lineStride = |
| ((ComponentSampleModel)sampleModel).getScanlineStride(); |
| |
| if (isBottomUp) { |
| int lastLine = |
| sourceRegion.y + (destinationRegion.height - 1) * scaleY; |
| iis.skipBytes(lineLength * (height - 1 - lastLine)); |
| } else |
| iis.skipBytes(lineLength * sourceRegion.y); |
| |
| int skipLength = lineLength * (scaleY - 1); |
| |
| int k = destinationRegion.y * lineStride; |
| if (isBottomUp) |
| k += (destinationRegion.height - 1) * lineStride; |
| k += destinationRegion.x; |
| |
| for (int j = 0, y = sourceRegion.y; |
| j < destinationRegion.height; j++, y+=scaleY) { |
| |
| if (abortRequested()) |
| break; |
| iis.read(buf, 0, lineLength); |
| for (int i = 0, m = sourceRegion.x; |
| i < destinationRegion.width; i++, m += scaleX) { |
| //get the bit and assign to the data buffer of the raster |
| bdata[k + i] = buf[m]; |
| } |
| |
| k += isBottomUp ? -lineStride : lineStride; |
| iis.skipBytes(skipLength); |
| processImageUpdate(bi, 0, j, |
| destinationRegion.width, 1, 1, 1, |
| new int[]{0}); |
| processImageProgress(100.0F*j/destinationRegion.height); |
| } |
| } |
| } |
| |
| // Method to read 24 bit BMP image data |
| private void read24Bit(byte[] bdata) throws IOException { |
| // Padding bytes at the end of each scanline |
| // width * bitsPerPixel should be divisible by 32 |
| int padding = width * 3 % 4; |
| if ( padding != 0) |
| padding = 4 - padding; |
| |
| int lineStride = width * 3; |
| int lineLength = lineStride + padding; |
| |
| if (noTransform) { |
| int j = isBottomUp ? (height -1) * width * 3 : 0; |
| |
| for (int i=0; i<height; i++) { |
| if (abortRequested()) { |
| break; |
| } |
| iis.readFully(bdata, j, lineStride); |
| iis.skipBytes(padding); |
| j += isBottomUp ? -lineStride : lineStride; |
| processImageUpdate(bi, 0, i, |
| destinationRegion.width, 1, 1, 1, |
| new int[]{0}); |
| processImageProgress(100.0F * i/destinationRegion.height); |
| } |
| } else { |
| byte[] buf = new byte[lineLength]; |
| lineStride = |
| ((ComponentSampleModel)sampleModel).getScanlineStride(); |
| |
| if (isBottomUp) { |
| int lastLine = |
| sourceRegion.y + (destinationRegion.height - 1) * scaleY; |
| iis.skipBytes(lineLength * (height - 1 - lastLine)); |
| } else |
| iis.skipBytes(lineLength * sourceRegion.y); |
| |
| int skipLength = lineLength * (scaleY - 1); |
| |
| int k = destinationRegion.y * lineStride; |
| if (isBottomUp) |
| k += (destinationRegion.height - 1) * lineStride; |
| k += destinationRegion.x * 3; |
| |
| for (int j = 0, y = sourceRegion.y; |
| j < destinationRegion.height; j++, y+=scaleY) { |
| |
| if (abortRequested()) |
| break; |
| iis.read(buf, 0, lineLength); |
| for (int i = 0, m = 3 * sourceRegion.x; |
| i < destinationRegion.width; i++, m += 3 * scaleX) { |
| //get the bit and assign to the data buffer of the raster |
| int n = 3 * i + k; |
| for (int b = 0; b < destBands.length; b++) |
| bdata[n + destBands[b]] = buf[m + sourceBands[b]]; |
| } |
| |
| k += isBottomUp ? -lineStride : lineStride; |
| iis.skipBytes(skipLength); |
| processImageUpdate(bi, 0, j, |
| destinationRegion.width, 1, 1, 1, |
| new int[]{0}); |
| processImageProgress(100.0F*j/destinationRegion.height); |
| } |
| } |
| } |
| |
| private void read16Bit(short sdata[]) throws IOException { |
| // Padding bytes at the end of each scanline |
| // width * bitsPerPixel should be divisible by 32 |
| int padding = width * 2 % 4; |
| |
| if ( padding != 0) |
| padding = 4 - padding; |
| |
| int lineLength = width + padding / 2; |
| |
| if (noTransform) { |
| int j = isBottomUp ? (height -1) * width : 0; |
| for (int i=0; i<height; i++) { |
| if (abortRequested()) { |
| break; |
| } |
| |
| iis.readFully(sdata, j, width); |
| iis.skipBytes(padding); |
| |
| j += isBottomUp ? -width : width; |
| processImageUpdate(bi, 0, i, |
| destinationRegion.width, 1, 1, 1, |
| new int[]{0}); |
| processImageProgress(100.0F * i/destinationRegion.height); |
| } |
| } else { |
| short[] buf = new short[lineLength]; |
| int lineStride = |
| ((SinglePixelPackedSampleModel)sampleModel).getScanlineStride(); |
| |
| if (isBottomUp) { |
| int lastLine = |
| sourceRegion.y + (destinationRegion.height - 1) * scaleY; |
| iis.skipBytes(lineLength * (height - 1 - lastLine) << 1); |
| } else |
| iis.skipBytes(lineLength * sourceRegion.y << 1); |
| |
| int skipLength = lineLength * (scaleY - 1) << 1; |
| |
| int k = destinationRegion.y * lineStride; |
| if (isBottomUp) |
| k += (destinationRegion.height - 1) * lineStride; |
| k += destinationRegion.x; |
| |
| for (int j = 0, y = sourceRegion.y; |
| j < destinationRegion.height; j++, y+=scaleY) { |
| |
| if (abortRequested()) |
| break; |
| iis.readFully(buf, 0, lineLength); |
| for (int i = 0, m = sourceRegion.x; |
| i < destinationRegion.width; i++, m += scaleX) { |
| //get the bit and assign to the data buffer of the raster |
| sdata[k + i] = buf[m]; |
| } |
| |
| k += isBottomUp ? -lineStride : lineStride; |
| iis.skipBytes(skipLength); |
| processImageUpdate(bi, 0, j, |
| destinationRegion.width, 1, 1, 1, |
| new int[]{0}); |
| processImageProgress(100.0F*j/destinationRegion.height); |
| } |
| } |
| } |
| |
| private void read32Bit(int idata[]) throws IOException { |
| if (noTransform) { |
| int j = isBottomUp ? (height -1) * width : 0; |
| |
| for (int i=0; i<height; i++) { |
| if (abortRequested()) { |
| break; |
| } |
| iis.readFully(idata, j, width); |
| j += isBottomUp ? -width : width; |
| processImageUpdate(bi, 0, i, |
| destinationRegion.width, 1, 1, 1, |
| new int[]{0}); |
| processImageProgress(100.0F * i/destinationRegion.height); |
| } |
| } else { |
| int[] buf = new int[width]; |
| int lineStride = |
| ((SinglePixelPackedSampleModel)sampleModel).getScanlineStride(); |
| |
| if (isBottomUp) { |
| int lastLine = |
| sourceRegion.y + (destinationRegion.height - 1) * scaleY; |
| iis.skipBytes(width * (height - 1 - lastLine) << 2); |
| } else |
| iis.skipBytes(width * sourceRegion.y << 2); |
| |
| int skipLength = width * (scaleY - 1) << 2; |
| |
| int k = destinationRegion.y * lineStride; |
| if (isBottomUp) |
| k += (destinationRegion.height - 1) * lineStride; |
| k += destinationRegion.x; |
| |
| for (int j = 0, y = sourceRegion.y; |
| j < destinationRegion.height; j++, y+=scaleY) { |
| |
| if (abortRequested()) |
| break; |
| iis.readFully(buf, 0, width); |
| for (int i = 0, m = sourceRegion.x; |
| i < destinationRegion.width; i++, m += scaleX) { |
| //get the bit and assign to the data buffer of the raster |
| idata[k + i] = buf[m]; |
| } |
| |
| k += isBottomUp ? -lineStride : lineStride; |
| iis.skipBytes(skipLength); |
| processImageUpdate(bi, 0, j, |
| destinationRegion.width, 1, 1, 1, |
| new int[]{0}); |
| processImageProgress(100.0F*j/destinationRegion.height); |
| } |
| } |
| } |
| |
| private void readRLE8(byte bdata[]) throws IOException { |
| // If imageSize field is not provided, calculate it. |
| int imSize = (int)imageSize; |
| if (imSize == 0) { |
| imSize = (int)(bitmapFileSize - bitmapOffset); |
| } |
| |
| int padding = 0; |
| // If width is not 32 bit aligned, then while uncompressing each |
| // scanline will have padding bytes, calculate the amount of padding |
| int remainder = width % 4; |
| if (remainder != 0) { |
| padding = 4 - remainder; |
| } |
| |
| // Read till we have the whole image |
| byte values[] = new byte[imSize]; |
| int bytesRead = 0; |
| iis.readFully(values, 0, imSize); |
| |
| // Since data is compressed, decompress it |
| decodeRLE8(imSize, padding, values, bdata); |
| } |
| |
| private void decodeRLE8(int imSize, |
| int padding, |
| byte[] values, |
| byte[] bdata) throws IOException { |
| |
| byte val[] = new byte[width * height]; |
| int count = 0, l = 0; |
| int value; |
| boolean flag = false; |
| int lineNo = isBottomUp ? height - 1 : 0; |
| int lineStride = |
| ((ComponentSampleModel)sampleModel).getScanlineStride(); |
| int finished = 0; |
| |
| while (count != imSize) { |
| value = values[count++] & 0xff; |
| if (value == 0) { |
| switch(values[count++] & 0xff) { |
| |
| case 0: |
| // End-of-scanline marker |
| if (lineNo >= sourceRegion.y && |
| lineNo < sourceRegion.y + sourceRegion.height) { |
| if (noTransform) { |
| int pos = lineNo * width; |
| for(int i = 0; i < width; i++) |
| bdata[pos++] = val[i]; |
| processImageUpdate(bi, 0, lineNo, |
| destinationRegion.width, 1, 1, 1, |
| new int[]{0}); |
| finished++; |
| } else if ((lineNo - sourceRegion.y) % scaleY == 0) { |
| int currentLine = (lineNo - sourceRegion.y) / scaleY + |
| destinationRegion.y; |
| int pos = currentLine * lineStride; |
| pos += destinationRegion.x; |
| for (int i = sourceRegion.x; |
| i < sourceRegion.x + sourceRegion.width; |
| i += scaleX) |
| bdata[pos++] = val[i]; |
| processImageUpdate(bi, 0, currentLine, |
| destinationRegion.width, 1, 1, 1, |
| new int[]{0}); |
| finished++; |
| } |
| } |
| processImageProgress(100.0F * finished / destinationRegion.height); |
| lineNo += isBottomUp ? -1 : 1; |
| l = 0; |
| |
| if (abortRequested()) { |
| flag = true; |
| } |
| |
| break; |
| |
| case 1: |
| // End-of-RLE marker |
| flag = true; |
| break; |
| |
| case 2: |
| // delta or vector marker |
| int xoff = values[count++] & 0xff; |
| int yoff = values[count] & 0xff; |
| // Move to the position xoff, yoff down |
| l += xoff + yoff*width; |
| break; |
| |
| default: |
| int end = values[count-1] & 0xff; |
| for (int i=0; i<end; i++) { |
| val[l++] = (byte)(values[count++] & 0xff); |
| } |
| |
| // Whenever end pixels can fit into odd number of bytes, |
| // an extra padding byte will be present, so skip that. |
| if ((end & 1) == 1) { |
| count++; |
| } |
| } |
| } else { |
| for (int i=0; i<value; i++) { |
| val[l++] = (byte)(values[count] & 0xff); |
| } |
| |
| count++; |
| } |
| |
| // If End-of-RLE data, then exit the while loop |
| if (flag) { |
| break; |
| } |
| } |
| } |
| |
| private void readRLE4(byte[] bdata) throws IOException { |
| |
| // If imageSize field is not specified, calculate it. |
| int imSize = (int)imageSize; |
| if (imSize == 0) { |
| imSize = (int)(bitmapFileSize - bitmapOffset); |
| } |
| |
| int padding = 0; |
| // If width is not 32 byte aligned, then while uncompressing each |
| // scanline will have padding bytes, calculate the amount of padding |
| int remainder = width % 4; |
| if (remainder != 0) { |
| padding = 4 - remainder; |
| } |
| |
| // Read till we have the whole image |
| byte[] values = new byte[imSize]; |
| iis.readFully(values, 0, imSize); |
| |
| // Decompress the RLE4 compressed data. |
| decodeRLE4(imSize, padding, values, bdata); |
| } |
| |
| private void decodeRLE4(int imSize, |
| int padding, |
| byte[] values, |
| byte[] bdata) throws IOException { |
| byte[] val = new byte[width]; |
| int count = 0, l = 0; |
| int value; |
| boolean flag = false; |
| int lineNo = isBottomUp ? height - 1 : 0; |
| int lineStride = |
| ((MultiPixelPackedSampleModel)sampleModel).getScanlineStride(); |
| int finished = 0; |
| |
| while (count != imSize) { |
| |
| value = values[count++] & 0xFF; |
| if (value == 0) { |
| |
| |
| // Absolute mode |
| switch(values[count++] & 0xFF) { |
| |
| case 0: |
| // End-of-scanline marker |
| // End-of-scanline marker |
| if (lineNo >= sourceRegion.y && |
| lineNo < sourceRegion.y + sourceRegion.height) { |
| if (noTransform) { |
| int pos = lineNo * (width + 1 >> 1); |
| for(int i = 0, j = 0; i < width >> 1; i++) |
| bdata[pos++] = |
| (byte)((val[j++] << 4) | val[j++]); |
| if ((width & 1) == 1) |
| bdata[pos] |= val[width - 1] << 4; |
| |
| processImageUpdate(bi, 0, lineNo, |
| destinationRegion.width, 1, 1, 1, |
| new int[]{0}); |
| finished++; |
| } else if ((lineNo - sourceRegion.y) % scaleY == 0) { |
| int currentLine = (lineNo - sourceRegion.y) / scaleY + |
| destinationRegion.y; |
| int pos = currentLine * lineStride; |
| pos += destinationRegion.x >> 1; |
| int shift = (1 - (destinationRegion.x & 1)) << 2; |
| for (int i = sourceRegion.x; |
| i < sourceRegion.x + sourceRegion.width; |
| i += scaleX) { |
| bdata[pos] |= val[i] << shift; |
| shift += 4; |
| if (shift == 4) { |
| pos++; |
| } |
| shift &= 7; |
| } |
| processImageUpdate(bi, 0, currentLine, |
| destinationRegion.width, 1, 1, 1, |
| new int[]{0}); |
| finished++; |
| } |
| } |
| processImageProgress(100.0F * finished / destinationRegion.height); |
| lineNo += isBottomUp ? -1 : 1; |
| l = 0; |
| |
| if (abortRequested()) { |
| flag = true; |
| } |
| |
| break; |
| |
| case 1: |
| // End-of-RLE marker |
| flag = true; |
| break; |
| |
| case 2: |
| // delta or vector marker |
| int xoff = values[count++] & 0xFF; |
| int yoff = values[count] & 0xFF; |
| // Move to the position xoff, yoff down |
| l += xoff + yoff*width; |
| break; |
| |
| default: |
| int end = values[count-1] & 0xFF; |
| for (int i=0; i<end; i++) { |
| val[l++] = (byte)(((i & 1) == 0) ? (values[count] & 0xf0) >> 4 |
| : (values[count++] & 0x0f)); |
| } |
| |
| // When end is odd, the above for loop does not |
| // increment count, so do it now. |
| if ((end & 1) == 1) { |
| count++; |
| } |
| |
| // Whenever end pixels can fit into odd number of bytes, |
| // an extra padding byte will be present, so skip that. |
| if ((((int)Math.ceil(end/2)) & 1) ==1 ) { |
| count++; |
| } |
| break; |
| } |
| } else { |
| // Encoded mode |
| int alternate[] = { (values[count] & 0xf0) >> 4, |
| values[count] & 0x0f }; |
| for (int i=0; (i < value) && (l < width); i++) { |
| val[l++] = (byte)alternate[i & 1]; |
| } |
| |
| count++; |
| } |
| |
| // If End-of-RLE data, then exit the while loop |
| if (flag) { |
| break; |
| } |
| } |
| } |
| |
| /** Decodes the jpeg/png image embedded in the bitmap using any jpeg |
| * ImageIO-style plugin. |
| * |
| * @param bi The destination <code>BufferedImage</code>. |
| * @param bmpParam The <code>ImageReadParam</code> for decoding this |
| * BMP image. The parameters for subregion, band selection and |
| * subsampling are used in decoding the jpeg image. |
| */ |
| |
| private BufferedImage readEmbedded(int type, |
| BufferedImage bi, ImageReadParam bmpParam) |
| throws IOException { |
| String format; |
| switch(type) { |
| case BI_JPEG: |
| format = "JPEG"; |
| break; |
| case BI_PNG: |
| format = "PNG"; |
| break; |
| default: |
| throw new |
| IOException("Unexpected compression type: " + type); |
| } |
| ImageReader reader = |
| ImageIO.getImageReadersByFormatName(format).next(); |
| if (reader == null) { |
| throw new RuntimeException(I18N.getString("BMPImageReader4") + |
| " " + format); |
| } |
| // prepare input |
| byte[] buff = new byte[(int)imageSize]; |
| iis.read(buff); |
| reader.setInput(ImageIO.createImageInputStream(new ByteArrayInputStream(buff))); |
| if (bi == null) { |
| ImageTypeSpecifier embType = reader.getImageTypes(0).next(); |
| bi = embType.createBufferedImage(destinationRegion.x + |
| destinationRegion.width, |
| destinationRegion.y + |
| destinationRegion.height); |
| } |
| |
| reader.addIIOReadProgressListener(new EmbeddedProgressAdapter() { |
| public void imageProgress(ImageReader source, |
| float percentageDone) |
| { |
| processImageProgress(percentageDone); |
| } |
| }); |
| |
| reader.addIIOReadUpdateListener(new IIOReadUpdateListener() { |
| public void imageUpdate(ImageReader source, |
| BufferedImage theImage, |
| int minX, int minY, |
| int width, int height, |
| int periodX, int periodY, |
| int[] bands) |
| { |
| processImageUpdate(theImage, minX, minY, |
| width, height, |
| periodX, periodY, bands); |
| } |
| public void passComplete(ImageReader source, |
| BufferedImage theImage) |
| { |
| processPassComplete(theImage); |
| } |
| public void passStarted(ImageReader source, |
| BufferedImage theImage, |
| int pass, |
| int minPass, int maxPass, |
| int minX, int minY, |
| int periodX, int periodY, |
| int[] bands) |
| { |
| processPassStarted(theImage, pass, minPass, maxPass, |
| minX, minY, periodX, periodY, |
| bands); |
| } |
| public void thumbnailPassComplete(ImageReader source, |
| BufferedImage thumb) {} |
| public void thumbnailPassStarted(ImageReader source, |
| BufferedImage thumb, |
| int pass, |
| int minPass, int maxPass, |
| int minX, int minY, |
| int periodX, int periodY, |
| int[] bands) {} |
| public void thumbnailUpdate(ImageReader source, |
| BufferedImage theThumbnail, |
| int minX, int minY, |
| int width, int height, |
| int periodX, int periodY, |
| int[] bands) {} |
| }); |
| |
| reader.addIIOReadWarningListener(new IIOReadWarningListener() { |
| public void warningOccurred(ImageReader source, String warning) |
| { |
| processWarningOccurred(warning); |
| } |
| }); |
| |
| ImageReadParam param = reader.getDefaultReadParam(); |
| param.setDestination(bi); |
| param.setDestinationBands(bmpParam.getDestinationBands()); |
| param.setDestinationOffset(bmpParam.getDestinationOffset()); |
| param.setSourceBands(bmpParam.getSourceBands()); |
| param.setSourceRegion(bmpParam.getSourceRegion()); |
| param.setSourceSubsampling(bmpParam.getSourceXSubsampling(), |
| bmpParam.getSourceYSubsampling(), |
| bmpParam.getSubsamplingXOffset(), |
| bmpParam.getSubsamplingYOffset()); |
| reader.read(0, param); |
| return bi; |
| } |
| |
| private class EmbeddedProgressAdapter implements IIOReadProgressListener { |
| public void imageComplete(ImageReader src) {} |
| public void imageProgress(ImageReader src, float percentageDone) {} |
| public void imageStarted(ImageReader src, int imageIndex) {} |
| public void thumbnailComplete(ImageReader src) {} |
| public void thumbnailProgress(ImageReader src, float percentageDone) {} |
| public void thumbnailStarted(ImageReader src, int iIdx, int tIdx) {} |
| public void sequenceComplete(ImageReader src) {} |
| public void sequenceStarted(ImageReader src, int minIndex) {} |
| public void readAborted(ImageReader src) {} |
| } |
| |
| private static Boolean isLinkedProfileDisabled = null; |
| |
| private static boolean isLinkedProfileAllowed() { |
| if (isLinkedProfileDisabled == null) { |
| PrivilegedAction<Boolean> a = new PrivilegedAction<Boolean>() { |
| public Boolean run() { |
| return Boolean.getBoolean("sun.imageio.plugins.bmp.disableLinkedProfiles"); |
| } |
| }; |
| isLinkedProfileDisabled = AccessController.doPrivileged(a); |
| } |
| return !isLinkedProfileDisabled; |
| } |
| |
| private static Boolean isWindowsPlatform = null; |
| |
| /** |
| * Verifies whether the byte array contans a unc path. |
| * Non-UNC path examples: |
| * c:\path\to\file - simple notation |
| * \\?\c:\path\to\file - long notation |
| * |
| * UNC path examples: |
| * \\server\share - a UNC path in simple notation |
| * \\?\UNC\server\share - a UNC path in long notation |
| * \\.\some\device - a path to device. |
| */ |
| private static boolean isUncOrDevicePath(byte[] p) { |
| if (isWindowsPlatform == null) { |
| PrivilegedAction<Boolean> a = new PrivilegedAction<Boolean>() { |
| public Boolean run() { |
| String osname = System.getProperty("os.name"); |
| return (osname != null && |
| osname.toLowerCase().startsWith("win")); |
| } |
| }; |
| isWindowsPlatform = AccessController.doPrivileged(a); |
| } |
| |
| if (!isWindowsPlatform) { |
| /* no need for the check on platforms except windows */ |
| return false; |
| } |
| |
| /* normalize prefix of the path */ |
| if (p[0] == '/') p[0] = '\\'; |
| if (p[1] == '/') p[1] = '\\'; |
| if (p[3] == '/') p[3] = '\\'; |
| |
| |
| if ((p[0] == '\\') && (p[1] == '\\')) { |
| if ((p[2] == '?') && (p[3] == '\\')) { |
| // long path: whether unc or local |
| return ((p[4] == 'U' || p[4] == 'u') && |
| (p[5] == 'N' || p[5] == 'n') && |
| (p[6] == 'C' || p[6] == 'c')); |
| } else { |
| // device path or short unc notation |
| return true; |
| } |
| } else { |
| return false; |
| } |
| } |
| } |