You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1257 lines
33 KiB
1257 lines
33 KiB
/* |
|
* Copyright (c) 2001 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: |
|
* |
|
* -Redistributions of source code must retain the above copyright notice, this |
|
* list of conditions and the following disclaimer. |
|
* |
|
* -Redistribution in binary form must reproduct 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 AND ITS LICENSORS SHALL NOT BE |
|
* LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING |
|
* OR DISTRIBUTING THE 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 SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE |
|
* POSSIBILITY OF SUCH DAMAGES. |
|
* |
|
* You acknowledge that Software is not designed,licensed or intended for use in |
|
* the design, construction, operation or maintenance of any nuclear facility. |
|
*/ |
|
|
|
package com.fr.third.JAI; |
|
import java.awt.Point; |
|
import java.awt.color.ColorSpace; |
|
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.Raster; |
|
import java.awt.image.RenderedImage; |
|
import java.awt.image.WritableRaster; |
|
import java.awt.image.IndexColorModel; |
|
import java.awt.image.MultiPixelPackedSampleModel; |
|
import java.awt.image.SinglePixelPackedSampleModel; |
|
import java.io.IOException; |
|
import java.io.BufferedInputStream; |
|
import java.io.InputStream; |
|
|
|
/** |
|
*/ |
|
public class BMPImageDecoder extends ImageDecoderImpl { |
|
|
|
public BMPImageDecoder(InputStream input, ImageDecodeParam param) { |
|
super(input, param); |
|
} |
|
|
|
public RenderedImage decodeAsRenderedImage(int page) throws IOException { |
|
if (page != 0) { |
|
throw new IOException(JaiI18N.getString("BMPImageDecoder8")); |
|
} |
|
return new BMPImage(input); |
|
} |
|
} |
|
|
|
class BMPImage extends SimpleRenderedImage { |
|
|
|
// BMP variables |
|
private BufferedInputStream inputStream; |
|
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; |
|
|
|
// 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; |
|
|
|
// Color space types |
|
private static final int LCS_CALIBRATED_RGB = 0; |
|
private static final int LCS_sRGB = 1; |
|
private static final int LCS_CMYK = 2; |
|
|
|
// Compression Types |
|
private static final int BI_RGB = 0; |
|
private static final int BI_RLE8 = 1; |
|
private static final int BI_RLE4 = 2; |
|
private static final int BI_BITFIELDS = 3; |
|
|
|
private WritableRaster theTile = null; |
|
|
|
/** |
|
* Constructor for BMPImage |
|
* |
|
* @param stream |
|
*/ |
|
public BMPImage(InputStream stream) { |
|
if (stream instanceof BufferedInputStream) { |
|
inputStream = (BufferedInputStream)stream; |
|
} else { |
|
inputStream = new BufferedInputStream(stream); |
|
} |
|
try { |
|
|
|
// Start File Header |
|
if (!(readUnsignedByte(inputStream) == 'B' && |
|
readUnsignedByte(inputStream) == 'M')) { |
|
throw new |
|
RuntimeException(JaiI18N.getString("BMPImageDecoder0")); |
|
} |
|
|
|
// Read file size |
|
bitmapFileSize = readDWord(inputStream); |
|
|
|
// Read the two reserved fields |
|
readWord(inputStream); |
|
readWord(inputStream); |
|
|
|
// Offset to the bitmap from the beginning |
|
bitmapOffset = readDWord(inputStream); |
|
|
|
// End File Header |
|
|
|
// Start BitmapCoreHeader |
|
long size = readDWord(inputStream); |
|
|
|
if (size == 12) { |
|
width = readWord(inputStream); |
|
height = readWord(inputStream); |
|
} else { |
|
width = readLong(inputStream); |
|
height = readLong(inputStream); |
|
} |
|
|
|
int planes = readWord(inputStream); |
|
bitsPerPixel = readWord(inputStream); |
|
|
|
properties.put("color_planes", new Integer(planes)); |
|
properties.put("bits_per_pixel", new Integer(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 |
|
properties.put("bmp_version", "BMP v. 2.x"); |
|
|
|
// 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]; |
|
inputStream.read(palette, 0, sizeOfPalette); |
|
properties.put("palette", palette); |
|
} else { |
|
|
|
compression = readDWord(inputStream); |
|
imageSize = readDWord(inputStream); |
|
long xPelsPerMeter = readLong(inputStream); |
|
long yPelsPerMeter = readLong(inputStream); |
|
long colorsUsed = readDWord(inputStream); |
|
long colorsImportant = readDWord(inputStream); |
|
|
|
switch((int)compression) { |
|
case BI_RGB: |
|
properties.put("compression", "BI_RGB"); |
|
break; |
|
|
|
case BI_RLE8: |
|
properties.put("compression", "BI_RLE8"); |
|
break; |
|
|
|
case BI_RLE4: |
|
properties.put("compression", "BI_RLE4"); |
|
break; |
|
|
|
case BI_BITFIELDS: |
|
properties.put("compression", "BI_BITFIELDS"); |
|
break; |
|
} |
|
|
|
properties.put("x_pixels_per_meter", new Long(xPelsPerMeter)); |
|
properties.put("y_pixels_per_meter", new Long(yPelsPerMeter)); |
|
properties.put("colors_used", new Long(colorsUsed)); |
|
properties.put("colors_important", new Long(colorsImportant)); |
|
|
|
if (size == 40) { |
|
// Windows 3.x and Windows NT |
|
switch((int)compression) { |
|
|
|
case BI_RGB: // No compression |
|
case BI_RLE8: // 8-bit RLE compression |
|
case BI_RLE4: // 4-bit RLE compression |
|
|
|
// Read in the palette |
|
int numberOfEntries = (int)((bitmapOffset-14-size) / 4); |
|
int sizeOfPalette = numberOfEntries*4; |
|
palette = new byte[sizeOfPalette]; |
|
inputStream.read(palette, 0, sizeOfPalette); |
|
properties.put("palette", palette); |
|
|
|
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 = 0x1F; |
|
properties.put("red_mask", new Integer(redMask)); |
|
properties.put("green_mask", new Integer(greenMask)); |
|
properties.put("blue_mask", new Integer(blueMask)); |
|
} else if (bitsPerPixel == 32) { |
|
imageType = VERSION_3_NT_32_BIT; |
|
redMask = 0x00FF0000; |
|
greenMask = 0x0000FF00; |
|
blueMask = 0x000000FF; |
|
properties.put("red_mask", new Integer(redMask)); |
|
properties.put("green_mask", new Integer(greenMask)); |
|
properties.put("blue_mask", new Integer(blueMask)); |
|
} |
|
|
|
properties.put("bmp_version", "BMP v. 3.x"); |
|
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)readDWord(inputStream); |
|
greenMask = (int)readDWord(inputStream); |
|
blueMask = (int)readDWord(inputStream); |
|
|
|
properties.put("red_mask", new Integer(redMask)); |
|
properties.put("green_mask", new Integer(greenMask)); |
|
properties.put("blue_mask", new Integer(blueMask)); |
|
|
|
if (colorsUsed != 0) { |
|
// there is a palette |
|
sizeOfPalette = (int)colorsUsed*4; |
|
palette = new byte[sizeOfPalette]; |
|
inputStream.read(palette, 0, sizeOfPalette); |
|
properties.put("palette", palette); |
|
} |
|
|
|
properties.put("bmp_version", "BMP v. 3.x NT"); |
|
break; |
|
|
|
default: |
|
throw new |
|
RuntimeException(JaiI18N.getString("BMPImageDecoder1")); |
|
} |
|
} else if (size == 108) { |
|
// Windows 4.x BMP |
|
|
|
properties.put("bmp_version", "BMP v. 4.x"); |
|
|
|
// rgb masks, valid only if comp is BI_BITFIELDS |
|
redMask = (int)readDWord(inputStream); |
|
greenMask = (int)readDWord(inputStream); |
|
blueMask = (int)readDWord(inputStream); |
|
// Only supported for 32bpp BI_RGB argb |
|
alphaMask = (int)readDWord(inputStream); |
|
long csType = readDWord(inputStream); |
|
int redX = readLong(inputStream); |
|
int redY = readLong(inputStream); |
|
int redZ = readLong(inputStream); |
|
int greenX = readLong(inputStream); |
|
int greenY = readLong(inputStream); |
|
int greenZ = readLong(inputStream); |
|
int blueX = readLong(inputStream); |
|
int blueY = readLong(inputStream); |
|
int blueZ = readLong(inputStream); |
|
long gammaRed = readDWord(inputStream); |
|
long gammaGreen = readDWord(inputStream); |
|
long gammaBlue = readDWord(inputStream); |
|
|
|
// Read in the palette |
|
int numberOfEntries = (int)((bitmapOffset-14-size) / 4); |
|
int sizeOfPalette = numberOfEntries*4; |
|
palette = new byte[sizeOfPalette]; |
|
inputStream.read(palette, 0, sizeOfPalette); |
|
|
|
if (palette != null || palette.length != 0) { |
|
properties.put("palette", palette); |
|
} |
|
|
|
switch((int)csType) { |
|
case LCS_CALIBRATED_RGB: |
|
// All the new fields are valid only for this case |
|
properties.put("color_space", "LCS_CALIBRATED_RGB"); |
|
properties.put("redX", new Integer(redX)); |
|
properties.put("redY", new Integer(redY)); |
|
properties.put("redZ", new Integer(redZ)); |
|
properties.put("greenX", new Integer(greenX)); |
|
properties.put("greenY", new Integer(greenY)); |
|
properties.put("greenZ", new Integer(greenZ)); |
|
properties.put("blueX", new Integer(blueX)); |
|
properties.put("blueY", new Integer(blueY)); |
|
properties.put("blueZ", new Integer(blueZ)); |
|
properties.put("gamma_red", new Long(gammaRed)); |
|
properties.put("gamma_green", new Long(gammaGreen)); |
|
properties.put("gamma_blue", new Long(gammaBlue)); |
|
|
|
// break; |
|
throw new |
|
RuntimeException(JaiI18N.getString("BMPImageDecoder2")); |
|
|
|
case LCS_sRGB: |
|
// Default Windows color space |
|
properties.put("color_space", "LCS_sRGB"); |
|
break; |
|
|
|
case LCS_CMYK: |
|
properties.put("color_space", "LCS_CMYK"); |
|
// break; |
|
throw new |
|
RuntimeException(JaiI18N.getString("BMPImageDecoder2")); |
|
} |
|
|
|
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; |
|
} |
|
} |
|
|
|
properties.put("red_mask", new Integer(redMask)); |
|
properties.put("green_mask", new Integer(greenMask)); |
|
properties.put("blue_mask", new Integer(blueMask)); |
|
properties.put("alpha_mask", new Integer(alphaMask)); |
|
} else { |
|
properties.put("bmp_version", "BMP v. 5.x"); |
|
throw new |
|
RuntimeException(JaiI18N.getString("BMPImageDecoder4")); |
|
} |
|
} |
|
} catch (IOException ioe) { |
|
throw new RuntimeException(JaiI18N.getString("BMPImageDecoder5")); |
|
} |
|
|
|
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. |
|
tileWidth = width; |
|
tileHeight = height; |
|
|
|
// When number of bitsPerPixel is <= 8, we use IndexColorModel. |
|
if (bitsPerPixel == 1 || bitsPerPixel == 4 || bitsPerPixel == 8) { |
|
|
|
numBands = 1; |
|
|
|
if (bitsPerPixel == 8) { |
|
sampleModel = |
|
RasterFactory.createPixelInterleavedSampleModel( |
|
DataBuffer.TYPE_BYTE, |
|
width, height, |
|
numBands); |
|
} 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[]; |
|
int size; |
|
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[size]; |
|
g = new byte[size]; |
|
b = new byte[size]; |
|
for (int i=0; i<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[size]; |
|
g = new byte[size]; |
|
b = new byte[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]; |
|
} |
|
} |
|
|
|
colorModel = new IndexColorModel(bitsPerPixel, 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.getInstance(ColorSpace.CS_sRGB), |
|
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.getInstance(ColorSpace.CS_sRGB), |
|
32, redMask, greenMask, blueMask, alphaMask, |
|
false, DataBuffer.TYPE_INT); |
|
} else { |
|
numBands = 3; |
|
// Create SampleModel |
|
sampleModel = |
|
RasterFactory.createPixelInterleavedSampleModel( |
|
DataBuffer.TYPE_BYTE, width, height, numBands); |
|
|
|
colorModel = |
|
ImageCodec.createComponentColorModel(sampleModel); |
|
} |
|
} |
|
|
|
// Deal with 1 Bit images using IndexColorModels |
|
private void read1Bit(byte[] bdata, int paletteEntries) { |
|
|
|
int padding = 0; |
|
int bytesPerScanline = (int)Math.ceil((double)width/8.0); |
|
|
|
int remainder = bytesPerScanline % 4; |
|
if (remainder != 0) { |
|
padding = 4 - remainder; |
|
} |
|
|
|
int imSize = (bytesPerScanline + padding) * height; |
|
|
|
// Read till we have the whole image |
|
byte values[] = new byte[imSize]; |
|
try { |
|
int bytesRead = 0; |
|
while (bytesRead < imSize) { |
|
bytesRead += inputStream.read(values, bytesRead, |
|
imSize - bytesRead); |
|
} |
|
} catch (IOException ioe) { |
|
throw new |
|
RuntimeException(JaiI18N.getString("BMPImageDecoder6")); |
|
} |
|
|
|
if (isBottomUp) { |
|
|
|
// Convert the bottom up image to a top down format by copying |
|
// one scanline from the bottom to the top at a time. |
|
|
|
for (int i=0; i<height; i++) { |
|
System.arraycopy(values, |
|
imSize - (i+1)*(bytesPerScanline + padding), |
|
bdata, |
|
i*bytesPerScanline, bytesPerScanline); |
|
} |
|
} else { |
|
|
|
for (int i=0; i<height; i++) { |
|
System.arraycopy(values, |
|
i * (bytesPerScanline + padding), |
|
bdata, |
|
i * bytesPerScanline, |
|
bytesPerScanline); |
|
} |
|
} |
|
} |
|
|
|
// Method to read a 4 bit BMP image data |
|
private void read4Bit(byte[] bdata, int paletteEntries) { |
|
|
|
// Padding bytes at the end of each scanline |
|
int padding = 0; |
|
|
|
int bytesPerScanline = (int)Math.ceil((double)width/2.0); |
|
int remainder = bytesPerScanline % 4; |
|
if (remainder != 0) { |
|
padding = 4 - remainder; |
|
} |
|
|
|
int imSize = (bytesPerScanline + padding) * height; |
|
|
|
// Read till we have the whole image |
|
byte values[] = new byte[imSize]; |
|
try { |
|
int bytesRead = 0; |
|
while (bytesRead < imSize) { |
|
bytesRead += inputStream.read(values, bytesRead, |
|
imSize - bytesRead); |
|
} |
|
} catch (IOException ioe) { |
|
throw new |
|
RuntimeException(JaiI18N.getString("BMPImageDecoder6")); |
|
} |
|
|
|
if (isBottomUp) { |
|
|
|
// Convert the bottom up image to a top down format by copying |
|
// one scanline from the bottom to the top at a time. |
|
for (int i=0; i<height; i++) { |
|
System.arraycopy(values, |
|
imSize - (i+1)*(bytesPerScanline + padding), |
|
bdata, |
|
i*bytesPerScanline, |
|
bytesPerScanline); |
|
} |
|
} else { |
|
for (int i=0; i<height; i++) { |
|
System.arraycopy(values, |
|
i * (bytesPerScanline + padding), |
|
bdata, |
|
i * bytesPerScanline, |
|
bytesPerScanline); |
|
} |
|
} |
|
} |
|
|
|
// Method to read 8 bit BMP image data |
|
private void read8Bit(byte[] bdata, int paletteEntries) { |
|
|
|
// Padding bytes at the end of each scanline |
|
int padding = 0; |
|
|
|
// width * bitsPerPixel should be divisible by 32 |
|
int bitsPerScanline = width * 8; |
|
if ( bitsPerScanline%32 != 0) { |
|
padding = (bitsPerScanline/32 + 1)*32 - bitsPerScanline; |
|
padding = (int)Math.ceil(padding/8.0); |
|
} |
|
|
|
int imSize = (width + padding) * height; |
|
|
|
// Read till we have the whole image |
|
byte values[] = new byte[imSize]; |
|
try { |
|
int bytesRead = 0; |
|
while (bytesRead < imSize) { |
|
bytesRead += inputStream.read(values, bytesRead, |
|
imSize - bytesRead); |
|
} |
|
} catch (IOException ioe) { |
|
throw new |
|
RuntimeException(JaiI18N.getString("BMPImageDecoder6")); |
|
} |
|
|
|
if (isBottomUp) { |
|
|
|
// Convert the bottom up image to a top down format by copying |
|
// one scanline from the bottom to the top at a time. |
|
for (int i=0; i<height; i++) { |
|
System.arraycopy(values, |
|
imSize - (i+1) * (width + padding), |
|
bdata, |
|
i * width, |
|
width); |
|
} |
|
} else { |
|
for (int i=0; i<height; i++) { |
|
System.arraycopy(values, |
|
i * (width + padding), |
|
bdata, |
|
i * width, |
|
width); |
|
} |
|
} |
|
} |
|
|
|
// Method to read 24 bit BMP image data |
|
private void read24Bit(byte[] bdata) { |
|
// Padding bytes at the end of each scanline |
|
int padding = 0; |
|
|
|
// width * bitsPerPixel should be divisible by 32 |
|
int bitsPerScanline = width * 24; |
|
if ( bitsPerScanline%32 != 0) { |
|
padding = (bitsPerScanline/32 + 1)*32 - bitsPerScanline; |
|
padding = (int)Math.ceil(padding/8.0); |
|
} |
|
|
|
int imSize = (int)imageSize; |
|
if (imSize == 0) { |
|
imSize = (int)(bitmapFileSize - bitmapOffset); |
|
} |
|
|
|
// Read till we have the whole image |
|
byte values[] = new byte[imSize]; |
|
try { |
|
int bytesRead = 0; |
|
while (bytesRead < imSize) { |
|
bytesRead += inputStream.read(values, bytesRead, |
|
imSize - bytesRead); |
|
} |
|
} catch (IOException ioe) { |
|
// throw new RuntimeException(JaiI18N.getString("BMPImageDecoder6")); |
|
throw new RuntimeException(ioe.getMessage()); |
|
} |
|
|
|
int l=0, count; |
|
|
|
if (isBottomUp) { |
|
int max = width*height*3-1; |
|
|
|
count = -padding; |
|
for (int i=0; i<height; i++) { |
|
l = max - (i+1)*width*3 + 1; |
|
count += padding; |
|
for (int j=0; j<width; j++) { |
|
bdata[l++] = values[count++]; |
|
bdata[l++] = values[count++]; |
|
bdata[l++] = values[count++]; |
|
} |
|
} |
|
} else { |
|
count = -padding; |
|
for (int i=0; i<height; i++) { |
|
count += padding; |
|
for (int j=0; j<width; j++) { |
|
bdata[l++] = values[count++]; |
|
bdata[l++] = values[count++]; |
|
bdata[l++] = values[count++]; |
|
} |
|
} |
|
} |
|
} |
|
|
|
private void read16Bit(short sdata[]) { |
|
// Padding bytes at the end of each scanline |
|
int padding = 0; |
|
|
|
// width * bitsPerPixel should be divisible by 32 |
|
int bitsPerScanline = width * 16; |
|
if ( bitsPerScanline%32 != 0) { |
|
padding = (bitsPerScanline/32 + 1)*32 - bitsPerScanline; |
|
padding = (int)Math.ceil(padding/8.0); |
|
} |
|
|
|
int imSize = (int)imageSize; |
|
if (imSize == 0) { |
|
imSize = (int)(bitmapFileSize - bitmapOffset); |
|
} |
|
|
|
int l=0; |
|
|
|
try { |
|
if (isBottomUp) { |
|
int max = width*height-1; |
|
|
|
for (int i=0; i<height; i++) { |
|
l = max - (i+1)*width + 1; |
|
for (int j=0; j<width; j++) { |
|
sdata[l++] = (short)(readWord(inputStream) & 0xffff); |
|
} |
|
for (int m=0; m<padding; m++) { |
|
inputStream.read(); |
|
} |
|
} |
|
} else { |
|
for (int i=0; i<height; i++) { |
|
for (int j=0; j<width; j++) { |
|
sdata[l++] = (short)(readWord(inputStream) & 0xffff); |
|
} |
|
for (int m=0; m<padding; m++) { |
|
inputStream.read(); |
|
} |
|
} |
|
} |
|
} catch (IOException ioe) { |
|
throw new RuntimeException(JaiI18N.getString("BMPImageDecoder6")); |
|
} |
|
} |
|
|
|
private void read32Bit(int idata[]) { |
|
int imSize = (int)imageSize; |
|
if (imSize == 0) { |
|
imSize = (int)(bitmapFileSize - bitmapOffset); |
|
} |
|
|
|
int l=0; |
|
|
|
try { |
|
if (isBottomUp) { |
|
int max = width*height-1; |
|
|
|
for (int i=0; i<height; i++) { |
|
l = max - (i+1)*width + 1; |
|
for (int j=0; j<width; j++) { |
|
idata[l++] = (int)readDWord(inputStream); |
|
} |
|
} |
|
} else { |
|
for (int i=0; i<height; i++) { |
|
for (int j=0; j<width; j++) { |
|
idata[l++] = (int)readDWord(inputStream); |
|
} |
|
} |
|
} |
|
} catch (IOException ioe) { |
|
throw new RuntimeException(JaiI18N.getString("BMPImageDecoder6")); |
|
} |
|
} |
|
|
|
private void readRLE8(byte bdata[]) { |
|
|
|
// 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]; |
|
try { |
|
int bytesRead = 0; |
|
while (bytesRead < imSize) { |
|
bytesRead += inputStream.read(values, bytesRead, |
|
imSize - bytesRead); |
|
} |
|
} catch (IOException ioe) { |
|
throw new RuntimeException(JaiI18N.getString("BMPImageDecoder6")); |
|
} |
|
|
|
// Since data is compressed, decompress it |
|
byte val[] = decodeRLE8(imSize, padding, values); |
|
|
|
// Uncompressed data does not have any padding |
|
imSize = width * height; |
|
|
|
if (isBottomUp) { |
|
|
|
// Convert the bottom up image to a top down format by copying |
|
// one scanline from the bottom to the top at a time. |
|
// int bytesPerScanline = (int)Math.ceil((double)width/8.0); |
|
int bytesPerScanline = width; |
|
for (int i=0; i<height; i++) { |
|
System.arraycopy(val, |
|
imSize - (i+1)*(bytesPerScanline), |
|
bdata, |
|
i*bytesPerScanline, bytesPerScanline); |
|
} |
|
|
|
} else { |
|
|
|
bdata = val; |
|
} |
|
} |
|
|
|
private byte[] decodeRLE8(int imSize, int padding, byte values[]) { |
|
|
|
byte val[] = new byte[width * height]; |
|
int count = 0, l = 0; |
|
int value; |
|
boolean flag = false; |
|
|
|
while (count != imSize) { |
|
|
|
value = values[count++] & 0xff; |
|
|
|
if (value == 0) { |
|
switch(values[count++] & 0xff) { |
|
|
|
case 0: |
|
// End-of-scanline marker |
|
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 (!isEven(end)) { |
|
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; |
|
} |
|
} |
|
|
|
return val; |
|
} |
|
|
|
private int[] readRLE4() { |
|
|
|
// 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 |
|
int values[] = new int[imSize]; |
|
try { |
|
for (int i=0; i<imSize; i++) { |
|
values[i] = inputStream.read(); |
|
} |
|
} catch(IOException ioe) { |
|
throw new RuntimeException(JaiI18N.getString("BMPImageDecoder6")); |
|
} |
|
|
|
// Decompress the RLE4 compressed data. |
|
int val[] = decodeRLE4(imSize, padding, values); |
|
|
|
// Invert it as it is bottom up format. |
|
if (isBottomUp) { |
|
|
|
int inverted[] = val; |
|
val = new int[width * height]; |
|
int l = 0, index, lineEnd; |
|
|
|
for (int i = height-1; i >= 0; i--) { |
|
index = i * width; |
|
lineEnd = l + width; |
|
while(l != lineEnd) { |
|
val[l++] = inverted[index++]; |
|
} |
|
} |
|
} |
|
|
|
// This array will be used to call setPixels as the decompression |
|
// had unpacked the 4bit pixels each into an int. |
|
return val; |
|
} |
|
|
|
private int[] decodeRLE4(int imSize, int padding, int values[]) { |
|
|
|
int val[] = new int[width * height]; |
|
int count = 0, l = 0; |
|
int value; |
|
boolean flag = false; |
|
|
|
while (count != imSize) { |
|
|
|
value = values[count++]; |
|
|
|
if (value == 0) { |
|
|
|
// Absolute mode |
|
switch(values[count++]) { |
|
|
|
case 0: |
|
// End-of-scanline marker |
|
break; |
|
|
|
case 1: |
|
// End-of-RLE marker |
|
flag = true; |
|
break; |
|
|
|
case 2: |
|
// delta or vector marker |
|
int xoff = values[count++]; |
|
int yoff = values[count]; |
|
// Move to the position xoff, yoff down |
|
l += xoff + yoff*width; |
|
break; |
|
|
|
default: |
|
int end = values[count-1]; |
|
for (int i=0; i<end; i++) { |
|
val[l++] = isEven(i) ? (values[count] & 0xf0) >> 4 |
|
: (values[count++] & 0x0f); |
|
} |
|
|
|
// When end is odd, the above for loop does not |
|
// increment count, so do it now. |
|
if (!isEven(end)) { |
|
count++; |
|
} |
|
|
|
// Whenever end pixels can fit into odd number of bytes, |
|
// an extra padding byte will be present, so skip that. |
|
if ( !isEven((int)Math.ceil(end/2)) ) { |
|
count++; |
|
} |
|
break; |
|
} |
|
} else { |
|
// Encoded mode |
|
int alternate[] = { (values[count] & 0xf0) >> 4, |
|
values[count] & 0x0f }; |
|
for (int i=0; i<value; i++) { |
|
val[l++] = alternate[i%2]; |
|
} |
|
|
|
count++; |
|
} |
|
|
|
// If End-of-RLE data, then exit the while loop |
|
if (flag) { |
|
break; |
|
} |
|
|
|
} |
|
|
|
return val; |
|
} |
|
|
|
private boolean isEven(int number) { |
|
return (number%2 == 0 ? true : false); |
|
} |
|
|
|
// Windows defined data type reading methods - everything is little endian |
|
|
|
// Unsigned 8 bits |
|
private int readUnsignedByte(InputStream stream) throws IOException { |
|
return (stream.read() & 0xff); |
|
} |
|
|
|
// Unsigned 2 bytes |
|
private int readUnsignedShort(InputStream stream) throws IOException { |
|
int b1 = readUnsignedByte(stream); |
|
int b2 = readUnsignedByte(stream); |
|
return ((b2 << 8) | b1) & 0xffff; |
|
} |
|
|
|
// Signed 16 bits |
|
private int readShort(InputStream stream) throws IOException { |
|
int b1 = readUnsignedByte(stream); |
|
int b2 = readUnsignedByte(stream); |
|
return (b2 << 8) | b1; |
|
} |
|
|
|
// Unsigned 16 bits |
|
private int readWord(InputStream stream) throws IOException { |
|
return readUnsignedShort(stream); |
|
} |
|
|
|
// Unsigned 4 bytes |
|
private long readUnsignedInt(InputStream stream) throws IOException { |
|
int b1 = readUnsignedByte(stream); |
|
int b2 = readUnsignedByte(stream); |
|
int b3 = readUnsignedByte(stream); |
|
int b4 = readUnsignedByte(stream); |
|
long l = (long)((b4 << 24) | (b3 << 16) | (b2 << 8) | b1); |
|
return l & 0xffffffff; |
|
} |
|
|
|
// Signed 4 bytes |
|
private int readInt(InputStream stream) throws IOException { |
|
int b1 = readUnsignedByte(stream); |
|
int b2 = readUnsignedByte(stream); |
|
int b3 = readUnsignedByte(stream); |
|
int b4 = readUnsignedByte(stream); |
|
return (b4 << 24) | (b3 << 16) | (b2 << 8) | b1; |
|
} |
|
|
|
// Unsigned 4 bytes |
|
private long readDWord(InputStream stream) throws IOException { |
|
return readUnsignedInt(stream); |
|
} |
|
|
|
// 32 bit signed value |
|
private int readLong(InputStream stream) throws IOException { |
|
return readInt(stream); |
|
} |
|
|
|
private synchronized Raster computeTile(int tileX, int tileY) { |
|
if (theTile != null) { |
|
return theTile; |
|
} |
|
|
|
// Create a new tile |
|
Point org = new Point(tileXToX(tileX), tileYToY(tileY)); |
|
WritableRaster tile = |
|
RasterFactory.createWritableRaster(sampleModel, org); |
|
byte bdata[] = null; // buffer for byte data |
|
short sdata[] = null; // buffer for short data |
|
int idata[] = null; // buffer for int data |
|
|
|
if (sampleModel.getDataType() == DataBuffer.TYPE_BYTE) |
|
bdata = (byte[])((DataBufferByte)tile.getDataBuffer()).getData(); |
|
else if (sampleModel.getDataType() == DataBuffer.TYPE_USHORT) |
|
sdata = (short[])((DataBufferUShort)tile.getDataBuffer()).getData(); |
|
else if (sampleModel.getDataType() == DataBuffer.TYPE_INT) |
|
idata = (int[])((DataBufferInt)tile.getDataBuffer()).getData(); |
|
|
|
// There should only be one tile. |
|
switch(imageType) { |
|
|
|
case VERSION_2_1_BIT: |
|
// no compression |
|
read1Bit(bdata, 3); |
|
break; |
|
|
|
case VERSION_2_4_BIT: |
|
// no compression |
|
read4Bit(bdata, 3); |
|
break; |
|
|
|
case VERSION_2_8_BIT: |
|
// no compression |
|
read8Bit(bdata, 3); |
|
break; |
|
|
|
case VERSION_2_24_BIT: |
|
// no compression |
|
read24Bit(bdata); |
|
break; |
|
|
|
case VERSION_3_1_BIT: |
|
// 1-bit images cannot be compressed. |
|
read1Bit(bdata, 4); |
|
break; |
|
|
|
case VERSION_3_4_BIT: |
|
switch((int)compression) { |
|
case BI_RGB: |
|
read4Bit(bdata, 4); |
|
break; |
|
|
|
case BI_RLE4: |
|
int pixels[] = readRLE4(); |
|
tile.setPixels(0, 0, width, height, pixels); |
|
break; |
|
|
|
default: |
|
throw new |
|
RuntimeException(JaiI18N.getString("BMPImageDecoder3")); |
|
} |
|
break; |
|
|
|
case VERSION_3_8_BIT: |
|
switch((int)compression) { |
|
case BI_RGB: |
|
read8Bit(bdata, 4); |
|
break; |
|
|
|
case BI_RLE8: |
|
readRLE8(bdata); |
|
break; |
|
|
|
default: |
|
throw new |
|
RuntimeException(JaiI18N.getString("BMPImageDecoder3")); |
|
} |
|
|
|
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_4_1_BIT: |
|
read1Bit(bdata, 4); |
|
break; |
|
|
|
case VERSION_4_4_BIT: |
|
switch((int)compression) { |
|
|
|
case BI_RGB: |
|
read4Bit(bdata, 4); |
|
break; |
|
|
|
case BI_RLE4: |
|
int pixels[] = readRLE4(); |
|
tile.setPixels(0, 0, width, height, pixels); |
|
break; |
|
|
|
default: |
|
throw new |
|
RuntimeException(JaiI18N.getString("BMPImageDecoder3")); |
|
} |
|
|
|
case VERSION_4_8_BIT: |
|
switch((int)compression) { |
|
|
|
case BI_RGB: |
|
read8Bit(bdata, 4); |
|
break; |
|
|
|
case BI_RLE8: |
|
readRLE8(bdata); |
|
break; |
|
|
|
default: |
|
throw new |
|
RuntimeException(JaiI18N.getString("BMPImageDecoder3")); |
|
} |
|
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; |
|
} |
|
|
|
theTile = tile; |
|
|
|
return tile; |
|
} |
|
|
|
public synchronized Raster getTile(int tileX, int tileY) { |
|
if ((tileX != 0) || (tileY != 0)) { |
|
throw new |
|
IllegalArgumentException(JaiI18N.getString("BMPImageDecoder7")); |
|
} |
|
return computeTile(tileX, tileY); |
|
} |
|
}
|
|
|