0001: /*
0002: * $RCSfile: BMPImageDecoder.java,v $
0003: *
0004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * Use is subject to license terms.
0007: *
0008: * $Revision: 1.4 $
0009: * $Date: 2006/08/22 00:12:03 $
0010: * $State: Exp $
0011: */
0012: package com.sun.media.jai.codecimpl;
0013:
0014: import java.awt.Point;
0015: import java.awt.RenderingHints;
0016: import java.awt.Transparency;
0017: import java.awt.color.ColorSpace;
0018: import java.awt.image.DataBuffer;
0019: import java.awt.image.DataBufferByte;
0020: import java.awt.image.DataBufferInt;
0021: import java.awt.image.DataBufferUShort;
0022: import java.awt.image.DirectColorModel;
0023: import java.awt.image.Raster;
0024: import java.awt.image.RenderedImage;
0025: import java.awt.image.WritableRaster;
0026: import java.awt.image.IndexColorModel;
0027: import java.awt.image.MultiPixelPackedSampleModel;
0028: import java.awt.image.SinglePixelPackedSampleModel;
0029: import java.io.IOException;
0030: import java.io.BufferedInputStream;
0031: import java.io.InputStream;
0032: import java.util.Hashtable;
0033: import java.util.Enumeration;
0034: import com.sun.media.jai.codec.BMPEncodeParam;
0035: import com.sun.media.jai.codec.ImageCodec;
0036: import com.sun.media.jai.codec.ImageDecoder;
0037: import com.sun.media.jai.codec.ImageDecoderImpl;
0038: import com.sun.media.jai.codec.ImageDecodeParam;
0039: import com.sun.media.jai.codecimpl.ImagingListenerProxy;
0040: import com.sun.media.jai.codecimpl.util.ImagingException;
0041: import com.sun.media.jai.codecimpl.util.RasterFactory;
0042:
0043: /**
0044: * @since EA2
0045: */
0046: public class BMPImageDecoder extends ImageDecoderImpl {
0047:
0048: public BMPImageDecoder(InputStream input, ImageDecodeParam param) {
0049: super (input, param);
0050: }
0051:
0052: public RenderedImage decodeAsRenderedImage(int page)
0053: throws IOException {
0054: if (page != 0) {
0055: throw new IOException(JaiI18N.getString("BMPImageDecoder8"));
0056: }
0057: try {
0058: return new BMPImage(input);
0059: } catch (Exception e) {
0060: throw CodecUtils.toIOException(e);
0061: }
0062: }
0063: }
0064:
0065: class BMPImage extends SimpleRenderedImage {
0066:
0067: // BMP variables
0068: private BufferedInputStream inputStream;
0069: private long bitmapFileSize;
0070: private long bitmapOffset;
0071: private long compression;
0072: private long imageSize;
0073: private byte palette[];
0074: private int imageType;
0075: private int numBands;
0076: private boolean isBottomUp;
0077: private int bitsPerPixel;
0078: private int redMask, greenMask, blueMask, alphaMask;
0079:
0080: // BMP Image types
0081: private static final int VERSION_2_1_BIT = 0;
0082: private static final int VERSION_2_4_BIT = 1;
0083: private static final int VERSION_2_8_BIT = 2;
0084: private static final int VERSION_2_24_BIT = 3;
0085:
0086: private static final int VERSION_3_1_BIT = 4;
0087: private static final int VERSION_3_4_BIT = 5;
0088: private static final int VERSION_3_8_BIT = 6;
0089: private static final int VERSION_3_24_BIT = 7;
0090:
0091: private static final int VERSION_3_NT_16_BIT = 8;
0092: private static final int VERSION_3_NT_32_BIT = 9;
0093:
0094: private static final int VERSION_4_1_BIT = 10;
0095: private static final int VERSION_4_4_BIT = 11;
0096: private static final int VERSION_4_8_BIT = 12;
0097: private static final int VERSION_4_16_BIT = 13;
0098: private static final int VERSION_4_24_BIT = 14;
0099: private static final int VERSION_4_32_BIT = 15;
0100:
0101: // Color space types
0102: private static final int LCS_CALIBRATED_RGB = 0;
0103: private static final int LCS_sRGB = 1;
0104: private static final int LCS_CMYK = 2;
0105:
0106: // Compression Types
0107: private static final int BI_RGB = 0;
0108: private static final int BI_RLE8 = 1;
0109: private static final int BI_RLE4 = 2;
0110: private static final int BI_BITFIELDS = 3;
0111:
0112: private WritableRaster theTile = null;
0113:
0114: /**
0115: * Constructor for BMPImage
0116: *
0117: * @param stream
0118: */
0119: public BMPImage(InputStream stream) {
0120: if (stream instanceof BufferedInputStream) {
0121: inputStream = (BufferedInputStream) stream;
0122: } else {
0123: inputStream = new BufferedInputStream(stream);
0124: }
0125: try {
0126:
0127: inputStream.mark(Integer.MAX_VALUE);
0128:
0129: // Start File Header
0130: if (!(readUnsignedByte(inputStream) == 'B' && readUnsignedByte(inputStream) == 'M')) {
0131: throw new RuntimeException(JaiI18N
0132: .getString("BMPImageDecoder0"));
0133: }
0134:
0135: // Read file size
0136: bitmapFileSize = readDWord(inputStream);
0137:
0138: // Read the two reserved fields
0139: readWord(inputStream);
0140: readWord(inputStream);
0141:
0142: // Offset to the bitmap from the beginning
0143: bitmapOffset = readDWord(inputStream);
0144:
0145: // End File Header
0146:
0147: // Start BitmapCoreHeader
0148: long size = readDWord(inputStream);
0149:
0150: if (size == 12) {
0151: width = readWord(inputStream);
0152: height = readWord(inputStream);
0153: } else {
0154: width = readLong(inputStream);
0155: height = readLong(inputStream);
0156: }
0157:
0158: int planes = readWord(inputStream);
0159: bitsPerPixel = readWord(inputStream);
0160:
0161: properties.put("color_planes", new Integer(planes));
0162: properties.put("bits_per_pixel", new Integer(bitsPerPixel));
0163:
0164: // As BMP always has 3 rgb bands, except for Version 5,
0165: // which is bgra
0166: numBands = 3;
0167:
0168: if (size == 12) {
0169: // Windows 2.x and OS/2 1.x
0170: properties.put("bmp_version", "BMP v. 2.x");
0171:
0172: // Classify the image type
0173: if (bitsPerPixel == 1) {
0174: imageType = VERSION_2_1_BIT;
0175: } else if (bitsPerPixel == 4) {
0176: imageType = VERSION_2_4_BIT;
0177: } else if (bitsPerPixel == 8) {
0178: imageType = VERSION_2_8_BIT;
0179: } else if (bitsPerPixel == 24) {
0180: imageType = VERSION_2_24_BIT;
0181: }
0182:
0183: // Read in the palette
0184: int numberOfEntries = (int) ((bitmapOffset - 14 - size) / 3);
0185: int sizeOfPalette = numberOfEntries * 3;
0186: palette = new byte[sizeOfPalette];
0187: inputStream.read(palette, 0, sizeOfPalette);
0188: properties.put("palette", palette);
0189: } else {
0190:
0191: compression = readDWord(inputStream);
0192: imageSize = readDWord(inputStream);
0193: long xPelsPerMeter = readLong(inputStream);
0194: long yPelsPerMeter = readLong(inputStream);
0195: long colorsUsed = readDWord(inputStream);
0196: long colorsImportant = readDWord(inputStream);
0197:
0198: switch ((int) compression) {
0199: case BI_RGB:
0200: properties.put("compression", "BI_RGB");
0201: break;
0202:
0203: case BI_RLE8:
0204: properties.put("compression", "BI_RLE8");
0205: break;
0206:
0207: case BI_RLE4:
0208: properties.put("compression", "BI_RLE4");
0209: break;
0210:
0211: case BI_BITFIELDS:
0212: properties.put("compression", "BI_BITFIELDS");
0213: break;
0214: }
0215:
0216: properties.put("x_pixels_per_meter", new Long(
0217: xPelsPerMeter));
0218: properties.put("y_pixels_per_meter", new Long(
0219: yPelsPerMeter));
0220: properties.put("colors_used", new Long(colorsUsed));
0221: properties.put("colors_important", new Long(
0222: colorsImportant));
0223:
0224: if (size == 40) {
0225: // Windows 3.x and Windows NT
0226: switch ((int) compression) {
0227:
0228: case BI_RGB: // No compression
0229: case BI_RLE8: // 8-bit RLE compression
0230: case BI_RLE4: // 4-bit RLE compression
0231:
0232: // Read in the palette
0233: int numberOfEntries = (int) ((bitmapOffset - 14 - size) / 4);
0234: int sizeOfPalette = numberOfEntries * 4;
0235: palette = new byte[sizeOfPalette];
0236: inputStream.read(palette, 0, sizeOfPalette);
0237: properties.put("palette", palette);
0238:
0239: if (bitsPerPixel == 1) {
0240: imageType = VERSION_3_1_BIT;
0241: } else if (bitsPerPixel == 4) {
0242: imageType = VERSION_3_4_BIT;
0243: } else if (bitsPerPixel == 8) {
0244: imageType = VERSION_3_8_BIT;
0245: } else if (bitsPerPixel == 24) {
0246: imageType = VERSION_3_24_BIT;
0247: } else if (bitsPerPixel == 16) {
0248: imageType = VERSION_3_NT_16_BIT;
0249: redMask = 0x7C00;
0250: greenMask = 0x3E0;
0251: blueMask = 0x1F;
0252: properties.put("red_mask", new Integer(
0253: redMask));
0254: properties.put("green_mask", new Integer(
0255: greenMask));
0256: properties.put("blue_mask", new Integer(
0257: blueMask));
0258: } else if (bitsPerPixel == 32) {
0259: imageType = VERSION_3_NT_32_BIT;
0260: redMask = 0x00FF0000;
0261: greenMask = 0x0000FF00;
0262: blueMask = 0x000000FF;
0263: properties.put("red_mask", new Integer(
0264: redMask));
0265: properties.put("green_mask", new Integer(
0266: greenMask));
0267: properties.put("blue_mask", new Integer(
0268: blueMask));
0269: }
0270:
0271: properties.put("bmp_version", "BMP v. 3.x");
0272: break;
0273:
0274: case BI_BITFIELDS:
0275:
0276: if (bitsPerPixel == 16) {
0277: imageType = VERSION_3_NT_16_BIT;
0278: } else if (bitsPerPixel == 32) {
0279: imageType = VERSION_3_NT_32_BIT;
0280: }
0281:
0282: // BitsField encoding
0283: redMask = (int) readDWord(inputStream);
0284: greenMask = (int) readDWord(inputStream);
0285: blueMask = (int) readDWord(inputStream);
0286:
0287: properties
0288: .put("red_mask", new Integer(redMask));
0289: properties.put("green_mask", new Integer(
0290: greenMask));
0291: properties.put("blue_mask", new Integer(
0292: blueMask));
0293:
0294: if (colorsUsed != 0) {
0295: // there is a palette
0296: sizeOfPalette = (int) colorsUsed * 4;
0297: palette = new byte[sizeOfPalette];
0298: inputStream.read(palette, 0, sizeOfPalette);
0299: properties.put("palette", palette);
0300: }
0301:
0302: properties.put("bmp_version", "BMP v. 3.x NT");
0303: break;
0304:
0305: default:
0306: throw new RuntimeException(JaiI18N
0307: .getString("BMPImageDecoder1"));
0308: }
0309: } else if (size == 108) {
0310: // Windows 4.x BMP
0311:
0312: properties.put("bmp_version", "BMP v. 4.x");
0313:
0314: // rgb masks, valid only if comp is BI_BITFIELDS
0315: redMask = (int) readDWord(inputStream);
0316: greenMask = (int) readDWord(inputStream);
0317: blueMask = (int) readDWord(inputStream);
0318: // Only supported for 32bpp BI_RGB argb
0319: alphaMask = (int) readDWord(inputStream);
0320: long csType = readDWord(inputStream);
0321: int redX = readLong(inputStream);
0322: int redY = readLong(inputStream);
0323: int redZ = readLong(inputStream);
0324: int greenX = readLong(inputStream);
0325: int greenY = readLong(inputStream);
0326: int greenZ = readLong(inputStream);
0327: int blueX = readLong(inputStream);
0328: int blueY = readLong(inputStream);
0329: int blueZ = readLong(inputStream);
0330: long gammaRed = readDWord(inputStream);
0331: long gammaGreen = readDWord(inputStream);
0332: long gammaBlue = readDWord(inputStream);
0333:
0334: // Read in the palette
0335: int numberOfEntries = (int) ((bitmapOffset - 14 - size) / 4);
0336: int sizeOfPalette = numberOfEntries * 4;
0337: palette = new byte[sizeOfPalette];
0338: inputStream.read(palette, 0, sizeOfPalette);
0339:
0340: if (palette != null || palette.length != 0) {
0341: properties.put("palette", palette);
0342: }
0343:
0344: switch ((int) csType) {
0345: case LCS_CALIBRATED_RGB:
0346: // All the new fields are valid only for this case
0347: properties.put("color_space",
0348: "LCS_CALIBRATED_RGB");
0349: properties.put("redX", new Integer(redX));
0350: properties.put("redY", new Integer(redY));
0351: properties.put("redZ", new Integer(redZ));
0352: properties.put("greenX", new Integer(greenX));
0353: properties.put("greenY", new Integer(greenY));
0354: properties.put("greenZ", new Integer(greenZ));
0355: properties.put("blueX", new Integer(blueX));
0356: properties.put("blueY", new Integer(blueY));
0357: properties.put("blueZ", new Integer(blueZ));
0358: properties.put("gamma_red", new Long(gammaRed));
0359: properties.put("gamma_green", new Long(
0360: gammaGreen));
0361: properties.put("gamma_blue",
0362: new Long(gammaBlue));
0363:
0364: // break;
0365: throw new RuntimeException(JaiI18N
0366: .getString("BMPImageDecoder2"));
0367:
0368: case LCS_sRGB:
0369: // Default Windows color space
0370: properties.put("color_space", "LCS_sRGB");
0371: break;
0372:
0373: case LCS_CMYK:
0374: properties.put("color_space", "LCS_CMYK");
0375: // break;
0376: throw new RuntimeException(JaiI18N
0377: .getString("BMPImageDecoder2"));
0378: }
0379:
0380: if (bitsPerPixel == 1) {
0381: imageType = VERSION_4_1_BIT;
0382: } else if (bitsPerPixel == 4) {
0383: imageType = VERSION_4_4_BIT;
0384: } else if (bitsPerPixel == 8) {
0385: imageType = VERSION_4_8_BIT;
0386: } else if (bitsPerPixel == 16) {
0387: imageType = VERSION_4_16_BIT;
0388: if ((int) compression == BI_RGB) {
0389: redMask = 0x7C00;
0390: greenMask = 0x3E0;
0391: blueMask = 0x1F;
0392: }
0393: } else if (bitsPerPixel == 24) {
0394: imageType = VERSION_4_24_BIT;
0395: } else if (bitsPerPixel == 32) {
0396: imageType = VERSION_4_32_BIT;
0397: if ((int) compression == BI_RGB) {
0398: redMask = 0x00FF0000;
0399: greenMask = 0x0000FF00;
0400: blueMask = 0x000000FF;
0401: }
0402: }
0403:
0404: properties.put("red_mask", new Integer(redMask));
0405: properties
0406: .put("green_mask", new Integer(greenMask));
0407: properties.put("blue_mask", new Integer(blueMask));
0408: properties
0409: .put("alpha_mask", new Integer(alphaMask));
0410: } else {
0411: properties.put("bmp_version", "BMP v. 5.x");
0412: throw new RuntimeException(JaiI18N
0413: .getString("BMPImageDecoder4"));
0414: }
0415: }
0416: } catch (IOException ioe) {
0417: String message = JaiI18N.getString("BMPImageDecoder5");
0418: ImagingListenerProxy.errorOccurred(message,
0419: new ImagingException(message, ioe), this , false);
0420: // throw new RuntimeException(JaiI18N.getString("BMPImageDecoder5"));
0421: }
0422:
0423: if (height > 0) {
0424: // bottom up image
0425: isBottomUp = true;
0426: } else {
0427: // top down image
0428: isBottomUp = false;
0429: height = Math.abs(height);
0430: }
0431:
0432: // Reset Image Layout so there's only one tile.
0433: tileWidth = width;
0434: tileHeight = height;
0435:
0436: // When number of bitsPerPixel is <= 8, we use IndexColorModel.
0437: if (bitsPerPixel == 1 || bitsPerPixel == 4 || bitsPerPixel == 8) {
0438:
0439: numBands = 1;
0440:
0441: if (bitsPerPixel == 8) {
0442: sampleModel = RasterFactory
0443: .createPixelInterleavedSampleModel(
0444: DataBuffer.TYPE_BYTE, width, height,
0445: numBands);
0446: } else {
0447: // 1 and 4 bit pixels can be stored in a packed format.
0448: sampleModel = new MultiPixelPackedSampleModel(
0449: DataBuffer.TYPE_BYTE, width, height,
0450: bitsPerPixel);
0451: }
0452:
0453: // Create IndexColorModel from the palette.
0454: byte r[], g[], b[];
0455: int size;
0456: if (imageType == VERSION_2_1_BIT
0457: || imageType == VERSION_2_4_BIT
0458: || imageType == VERSION_2_8_BIT) {
0459:
0460: size = palette.length / 3;
0461:
0462: if (size > 256) {
0463: size = 256;
0464: }
0465:
0466: int off;
0467: r = new byte[size];
0468: g = new byte[size];
0469: b = new byte[size];
0470: for (int i = 0; i < size; i++) {
0471: off = 3 * i;
0472: b[i] = palette[off];
0473: g[i] = palette[off + 1];
0474: r[i] = palette[off + 2];
0475: }
0476: } else {
0477: size = palette.length / 4;
0478:
0479: if (size > 256) {
0480: size = 256;
0481: }
0482:
0483: int off;
0484: r = new byte[size];
0485: g = new byte[size];
0486: b = new byte[size];
0487: for (int i = 0; i < size; i++) {
0488: off = 4 * i;
0489: b[i] = palette[off];
0490: g[i] = palette[off + 1];
0491: r[i] = palette[off + 2];
0492: }
0493: }
0494:
0495: if (ImageCodec.isIndicesForGrayscale(r, g, b))
0496: colorModel = ImageCodec
0497: .createComponentColorModel(sampleModel);
0498: else
0499: colorModel = new IndexColorModel(bitsPerPixel, size, r,
0500: g, b);
0501: } else if (bitsPerPixel == 16) {
0502: numBands = 3;
0503: sampleModel = new SinglePixelPackedSampleModel(
0504: DataBuffer.TYPE_USHORT, width, height, new int[] {
0505: redMask, greenMask, blueMask });
0506:
0507: colorModel = new DirectColorModel(ColorSpace
0508: .getInstance(ColorSpace.CS_sRGB), 16, redMask,
0509: greenMask, blueMask, 0, false,
0510: DataBuffer.TYPE_USHORT);
0511: } else if (bitsPerPixel == 32) {
0512: numBands = alphaMask == 0 ? 3 : 4;
0513:
0514: // The number of bands in the SampleModel is determined by
0515: // the length of the mask array passed in.
0516: int[] bitMasks = numBands == 3 ? new int[] { redMask,
0517: greenMask, blueMask } : new int[] { redMask,
0518: greenMask, blueMask, alphaMask };
0519:
0520: sampleModel = new SinglePixelPackedSampleModel(
0521: DataBuffer.TYPE_INT, width, height, bitMasks);
0522:
0523: colorModel = new DirectColorModel(ColorSpace
0524: .getInstance(ColorSpace.CS_sRGB), 32, redMask,
0525: greenMask, blueMask, alphaMask, false,
0526: DataBuffer.TYPE_INT);
0527: } else {
0528: numBands = 3;
0529: // Create SampleModel
0530: sampleModel = RasterFactory
0531: .createPixelInterleavedSampleModel(
0532: DataBuffer.TYPE_BYTE, width, height,
0533: numBands);
0534:
0535: colorModel = ImageCodec
0536: .createComponentColorModel(sampleModel);
0537: }
0538:
0539: try {
0540: inputStream.reset();
0541: inputStream.skip(bitmapOffset);
0542: } catch (IOException ioe) {
0543: String message = JaiI18N.getString("BMPImageDecoder9");
0544: ImagingListenerProxy.errorOccurred(message,
0545: new ImagingException(message, ioe), this , false);
0546: }
0547: }
0548:
0549: // Deal with 1 Bit images using IndexColorModels
0550: private void read1Bit(byte[] bdata, int paletteEntries) {
0551:
0552: int padding = 0;
0553: int bytesPerScanline = (int) Math.ceil((double) width / 8.0);
0554:
0555: int remainder = bytesPerScanline % 4;
0556: if (remainder != 0) {
0557: padding = 4 - remainder;
0558: }
0559:
0560: int imSize = (bytesPerScanline + padding) * height;
0561:
0562: // Read till we have the whole image
0563: byte values[] = new byte[imSize];
0564: try {
0565: int bytesRead = 0;
0566: while (bytesRead < imSize) {
0567: bytesRead += inputStream.read(values, bytesRead, imSize
0568: - bytesRead);
0569: }
0570: } catch (IOException ioe) {
0571: String message = JaiI18N.getString("BMPImageDecoder6");
0572: ImagingListenerProxy.errorOccurred(message,
0573: new ImagingException(message, ioe), this , false);
0574: // throw new
0575: // RuntimeException(JaiI18N.getString("BMPImageDecoder6"));
0576: }
0577:
0578: if (isBottomUp) {
0579:
0580: // Convert the bottom up image to a top down format by copying
0581: // one scanline from the bottom to the top at a time.
0582:
0583: for (int i = 0; i < height; i++) {
0584: System.arraycopy(values, imSize - (i + 1)
0585: * (bytesPerScanline + padding), bdata, i
0586: * bytesPerScanline, bytesPerScanline);
0587: }
0588: } else {
0589:
0590: for (int i = 0; i < height; i++) {
0591: System.arraycopy(values, i
0592: * (bytesPerScanline + padding), bdata, i
0593: * bytesPerScanline, bytesPerScanline);
0594: }
0595: }
0596: }
0597:
0598: // Method to read a 4 bit BMP image data
0599: private void read4Bit(byte[] bdata, int paletteEntries) {
0600:
0601: // Padding bytes at the end of each scanline
0602: int padding = 0;
0603:
0604: int bytesPerScanline = (int) Math.ceil((double) width / 2.0);
0605: int remainder = bytesPerScanline % 4;
0606: if (remainder != 0) {
0607: padding = 4 - remainder;
0608: }
0609:
0610: int imSize = (bytesPerScanline + padding) * height;
0611:
0612: // Read till we have the whole image
0613: byte values[] = new byte[imSize];
0614: try {
0615: int bytesRead = 0;
0616: while (bytesRead < imSize) {
0617: bytesRead += inputStream.read(values, bytesRead, imSize
0618: - bytesRead);
0619: }
0620: } catch (IOException ioe) {
0621: String message = JaiI18N.getString("BMPImageDecoder6");
0622: ImagingListenerProxy.errorOccurred(message,
0623: new ImagingException(message, ioe), this , false);
0624: // throw new
0625: // RuntimeException(JaiI18N.getString("BMPImageDecoder6"));
0626: }
0627:
0628: if (isBottomUp) {
0629:
0630: // Convert the bottom up image to a top down format by copying
0631: // one scanline from the bottom to the top at a time.
0632: for (int i = 0; i < height; i++) {
0633: System.arraycopy(values, imSize - (i + 1)
0634: * (bytesPerScanline + padding), bdata, i
0635: * bytesPerScanline, bytesPerScanline);
0636: }
0637: } else {
0638: for (int i = 0; i < height; i++) {
0639: System.arraycopy(values, i
0640: * (bytesPerScanline + padding), bdata, i
0641: * bytesPerScanline, bytesPerScanline);
0642: }
0643: }
0644: }
0645:
0646: // Method to read 8 bit BMP image data
0647: private void read8Bit(byte[] bdata, int paletteEntries) {
0648:
0649: // Padding bytes at the end of each scanline
0650: int padding = 0;
0651:
0652: // width * bitsPerPixel should be divisible by 32
0653: int bitsPerScanline = width * 8;
0654: if (bitsPerScanline % 32 != 0) {
0655: padding = (bitsPerScanline / 32 + 1) * 32 - bitsPerScanline;
0656: padding = (int) Math.ceil(padding / 8.0);
0657: }
0658:
0659: int imSize = (width + padding) * height;
0660:
0661: // Read till we have the whole image
0662: byte values[] = new byte[imSize];
0663: try {
0664: int bytesRead = 0;
0665: while (bytesRead < imSize) {
0666: bytesRead += inputStream.read(values, bytesRead, imSize
0667: - bytesRead);
0668: }
0669: } catch (IOException ioe) {
0670: String message = JaiI18N.getString("BMPImageDecoder6");
0671: ImagingListenerProxy.errorOccurred(message,
0672: new ImagingException(message, ioe), this , false);
0673: // throw new
0674: // RuntimeException(JaiI18N.getString("BMPImageDecoder6"));
0675: }
0676:
0677: if (isBottomUp) {
0678:
0679: // Convert the bottom up image to a top down format by copying
0680: // one scanline from the bottom to the top at a time.
0681: for (int i = 0; i < height; i++) {
0682: System.arraycopy(values, imSize - (i + 1)
0683: * (width + padding), bdata, i * width, width);
0684: }
0685: } else {
0686: for (int i = 0; i < height; i++) {
0687: System.arraycopy(values, i * (width + padding), bdata,
0688: i * width, width);
0689: }
0690: }
0691: }
0692:
0693: // Method to read 24 bit BMP image data
0694: private void read24Bit(byte[] bdata) {
0695: // Padding bytes at the end of each scanline
0696: int padding = 0;
0697:
0698: // width * bitsPerPixel should be divisible by 32
0699: int bitsPerScanline = width * 24;
0700: if (bitsPerScanline % 32 != 0) {
0701: padding = (bitsPerScanline / 32 + 1) * 32 - bitsPerScanline;
0702: padding = (int) Math.ceil(padding / 8.0);
0703: }
0704:
0705: int imSize = (int) imageSize;
0706: if (imSize == 0) {
0707: imSize = (int) (bitmapFileSize - bitmapOffset);
0708: }
0709:
0710: // Read till we have the whole image
0711: byte values[] = new byte[imSize];
0712: try {
0713: int bytesRead = 0;
0714: while (bytesRead < imSize) {
0715: bytesRead += inputStream.read(values, bytesRead, imSize
0716: - bytesRead);
0717: }
0718: } catch (IOException ioe) {
0719: // throw new RuntimeException(JaiI18N.getString("BMPImageDecoder6"));
0720: String message = JaiI18N.getString("BMPImageDecoder4");
0721: ImagingListenerProxy.errorOccurred(message,
0722: new ImagingException(message, ioe), this , false);
0723: // throw new RuntimeException(ioe.getMessage());
0724: }
0725:
0726: int l = 0, count;
0727:
0728: if (isBottomUp) {
0729: int max = width * height * 3 - 1;
0730:
0731: count = -padding;
0732: for (int i = 0; i < height; i++) {
0733: l = max - (i + 1) * width * 3 + 1;
0734: count += padding;
0735: for (int j = 0; j < width; j++) {
0736: bdata[l++] = values[count++];
0737: bdata[l++] = values[count++];
0738: bdata[l++] = values[count++];
0739: }
0740: }
0741: } else {
0742: count = -padding;
0743: for (int i = 0; i < height; i++) {
0744: count += padding;
0745: for (int j = 0; j < width; j++) {
0746: bdata[l++] = values[count++];
0747: bdata[l++] = values[count++];
0748: bdata[l++] = values[count++];
0749: }
0750: }
0751: }
0752: }
0753:
0754: private void read16Bit(short sdata[]) {
0755: // Padding bytes at the end of each scanline
0756: int padding = 0;
0757:
0758: // width * bitsPerPixel should be divisible by 32
0759: int bitsPerScanline = width * 16;
0760: if (bitsPerScanline % 32 != 0) {
0761: padding = (bitsPerScanline / 32 + 1) * 32 - bitsPerScanline;
0762: padding = (int) Math.ceil(padding / 8.0);
0763: }
0764:
0765: int imSize = (int) imageSize;
0766: if (imSize == 0) {
0767: imSize = (int) (bitmapFileSize - bitmapOffset);
0768: }
0769:
0770: int l = 0;
0771:
0772: try {
0773: if (isBottomUp) {
0774: int max = width * height - 1;
0775:
0776: for (int i = 0; i < height; i++) {
0777: l = max - (i + 1) * width + 1;
0778: for (int j = 0; j < width; j++) {
0779: sdata[l++] = (short) (readWord(inputStream) & 0xffff);
0780: }
0781: for (int m = 0; m < padding; m++) {
0782: inputStream.read();
0783: }
0784: }
0785: } else {
0786: for (int i = 0; i < height; i++) {
0787: for (int j = 0; j < width; j++) {
0788: sdata[l++] = (short) (readWord(inputStream) & 0xffff);
0789: }
0790: for (int m = 0; m < padding; m++) {
0791: inputStream.read();
0792: }
0793: }
0794: }
0795: } catch (IOException ioe) {
0796: String message = JaiI18N.getString("BMPImageDecoder6");
0797: ImagingListenerProxy.errorOccurred(message,
0798: new ImagingException(message, ioe), this , false);
0799: // throw new RuntimeException(JaiI18N.getString("BMPImageDecoder6"));
0800: }
0801: }
0802:
0803: private void read32Bit(int idata[]) {
0804: int imSize = (int) imageSize;
0805: if (imSize == 0) {
0806: imSize = (int) (bitmapFileSize - bitmapOffset);
0807: }
0808:
0809: int l = 0;
0810:
0811: try {
0812: if (isBottomUp) {
0813: int max = width * height - 1;
0814:
0815: for (int i = 0; i < height; i++) {
0816: l = max - (i + 1) * width + 1;
0817: for (int j = 0; j < width; j++) {
0818: idata[l++] = (int) readDWord(inputStream);
0819: }
0820: }
0821: } else {
0822: for (int i = 0; i < height; i++) {
0823: for (int j = 0; j < width; j++) {
0824: idata[l++] = (int) readDWord(inputStream);
0825: }
0826: }
0827: }
0828: } catch (IOException ioe) {
0829: String message = JaiI18N.getString("BMPImageDecoder6");
0830: ImagingListenerProxy.errorOccurred(message,
0831: new ImagingException(message, ioe), this , false);
0832: // throw new RuntimeException(JaiI18N.getString("BMPImageDecoder6"));
0833: }
0834: }
0835:
0836: private void readRLE8(byte bdata[]) {
0837:
0838: // If imageSize field is not provided, calculate it.
0839: int imSize = (int) imageSize;
0840: if (imSize == 0) {
0841: imSize = (int) (bitmapFileSize - bitmapOffset);
0842: }
0843:
0844: int padding = 0;
0845: // If width is not 32 bit aligned, then while uncompressing each
0846: // scanline will have padding bytes, calculate the amount of padding
0847: int remainder = width % 4;
0848: if (remainder != 0) {
0849: padding = 4 - remainder;
0850: }
0851:
0852: // Read till we have the whole image
0853: byte values[] = new byte[imSize];
0854: try {
0855: int bytesRead = 0;
0856: while (bytesRead < imSize) {
0857: bytesRead += inputStream.read(values, bytesRead, imSize
0858: - bytesRead);
0859: }
0860: } catch (IOException ioe) {
0861: String message = JaiI18N.getString("BMPImageDecoder6");
0862: ImagingListenerProxy.errorOccurred(message,
0863: new ImagingException(message, ioe), this , false);
0864: // throw new RuntimeException(JaiI18N.getString("BMPImageDecoder6"));
0865: }
0866:
0867: // Since data is compressed, decompress it
0868: byte val[] = decodeRLE8(imSize, padding, values);
0869:
0870: // Uncompressed data does not have any padding
0871: imSize = width * height;
0872:
0873: if (isBottomUp) {
0874:
0875: // Convert the bottom up image to a top down format by copying
0876: // one scanline from the bottom to the top at a time.
0877: // int bytesPerScanline = (int)Math.ceil((double)width/8.0);
0878: int bytesPerScanline = width;
0879: for (int i = 0; i < height; i++) {
0880: System.arraycopy(val, imSize - (i + 1)
0881: * (bytesPerScanline), bdata, i
0882: * bytesPerScanline, bytesPerScanline);
0883: }
0884:
0885: } else {
0886:
0887: bdata = val;
0888: }
0889: }
0890:
0891: private byte[] decodeRLE8(int imSize, int padding, byte values[]) {
0892:
0893: byte val[] = new byte[width * height];
0894: int count = 0, l = 0;
0895: int value;
0896: boolean flag = false;
0897:
0898: while (count != imSize) {
0899:
0900: value = values[count++] & 0xff;
0901:
0902: if (value == 0) {
0903: switch (values[count++] & 0xff) {
0904:
0905: case 0:
0906: // End-of-scanline marker
0907: break;
0908:
0909: case 1:
0910: // End-of-RLE marker
0911: flag = true;
0912: break;
0913:
0914: case 2:
0915: // delta or vector marker
0916: int xoff = values[count++] & 0xff;
0917: int yoff = values[count] & 0xff;
0918: // Move to the position xoff, yoff down
0919: l += xoff + yoff * width;
0920: break;
0921:
0922: default:
0923: int end = values[count - 1] & 0xff;
0924: for (int i = 0; i < end; i++) {
0925: val[l++] = (byte) (values[count++] & 0xff);
0926: }
0927:
0928: // Whenever end pixels can fit into odd number of bytes,
0929: // an extra padding byte will be present, so skip that.
0930: if (!isEven(end)) {
0931: count++;
0932: }
0933: }
0934: } else {
0935: for (int i = 0; i < value; i++) {
0936: val[l++] = (byte) (values[count] & 0xff);
0937: }
0938: count++;
0939: }
0940:
0941: // If End-of-RLE data, then exit the while loop
0942: if (flag) {
0943: break;
0944: }
0945: }
0946:
0947: return val;
0948: }
0949:
0950: private int[] readRLE4() {
0951:
0952: // If imageSize field is not specified, calculate it.
0953: int imSize = (int) imageSize;
0954: if (imSize == 0) {
0955: imSize = (int) (bitmapFileSize - bitmapOffset);
0956: }
0957:
0958: int padding = 0;
0959: // If width is not 32 byte aligned, then while uncompressing each
0960: // scanline will have padding bytes, calculate the amount of padding
0961: int remainder = width % 4;
0962: if (remainder != 0) {
0963: padding = 4 - remainder;
0964: }
0965:
0966: // Read till we have the whole image
0967: int values[] = new int[imSize];
0968: try {
0969: for (int i = 0; i < imSize; i++) {
0970: values[i] = inputStream.read();
0971: }
0972: } catch (IOException ioe) {
0973: String message = JaiI18N.getString("BMPImageDecoder6");
0974: ImagingListenerProxy.errorOccurred(message,
0975: new ImagingException(message, ioe), this , false);
0976: // throw new RuntimeException(JaiI18N.getString("BMPImageDecoder6"));
0977: }
0978:
0979: // Decompress the RLE4 compressed data.
0980: int val[] = decodeRLE4(imSize, padding, values);
0981:
0982: // Invert it as it is bottom up format.
0983: if (isBottomUp) {
0984:
0985: int inverted[] = val;
0986: val = new int[width * height];
0987: int l = 0, index, lineEnd;
0988:
0989: for (int i = height - 1; i >= 0; i--) {
0990: index = i * width;
0991: lineEnd = l + width;
0992: while (l != lineEnd) {
0993: val[l++] = inverted[index++];
0994: }
0995: }
0996: }
0997:
0998: // This array will be used to call setPixels as the decompression
0999: // had unpacked the 4bit pixels each into an int.
1000: return val;
1001: }
1002:
1003: private int[] decodeRLE4(int imSize, int padding, int values[]) {
1004:
1005: int val[] = new int[width * height];
1006: int count = 0, l = 0;
1007: int value;
1008: boolean flag = false;
1009:
1010: while (count != imSize) {
1011:
1012: value = values[count++];
1013:
1014: if (value == 0) {
1015:
1016: // Absolute mode
1017: switch (values[count++]) {
1018:
1019: case 0:
1020: // End-of-scanline marker
1021: break;
1022:
1023: case 1:
1024: // End-of-RLE marker
1025: flag = true;
1026: break;
1027:
1028: case 2:
1029: // delta or vector marker
1030: int xoff = values[count++];
1031: int yoff = values[count];
1032: // Move to the position xoff, yoff down
1033: l += xoff + yoff * width;
1034: break;
1035:
1036: default:
1037: int end = values[count - 1];
1038: for (int i = 0; i < end; i++) {
1039: val[l++] = isEven(i) ? (values[count] & 0xf0) >> 4
1040: : (values[count++] & 0x0f);
1041: }
1042:
1043: // When end is odd, the above for loop does not
1044: // increment count, so do it now.
1045: if (!isEven(end)) {
1046: count++;
1047: }
1048:
1049: // Whenever end pixels can fit into odd number of bytes,
1050: // an extra padding byte will be present, so skip that.
1051: if (!isEven((int) Math.ceil(end / 2))) {
1052: count++;
1053: }
1054: break;
1055: }
1056: } else {
1057: // Encoded mode
1058: int alternate[] = { (values[count] & 0xf0) >> 4,
1059: values[count] & 0x0f };
1060: for (int i = 0; i < value; i++) {
1061: val[l++] = alternate[i % 2];
1062: }
1063:
1064: count++;
1065: }
1066:
1067: // If End-of-RLE data, then exit the while loop
1068: if (flag) {
1069: break;
1070: }
1071:
1072: }
1073:
1074: return val;
1075: }
1076:
1077: private boolean isEven(int number) {
1078: return (number % 2 == 0 ? true : false);
1079: }
1080:
1081: // Windows defined data type reading methods - everything is little endian
1082:
1083: // Unsigned 8 bits
1084: private int readUnsignedByte(InputStream stream) throws IOException {
1085: return (stream.read() & 0xff);
1086: }
1087:
1088: // Unsigned 2 bytes
1089: private int readUnsignedShort(InputStream stream)
1090: throws IOException {
1091: int b1 = readUnsignedByte(stream);
1092: int b2 = readUnsignedByte(stream);
1093: return ((b2 << 8) | b1) & 0xffff;
1094: }
1095:
1096: // Signed 16 bits
1097: private int readShort(InputStream stream) throws IOException {
1098: int b1 = readUnsignedByte(stream);
1099: int b2 = readUnsignedByte(stream);
1100: return (b2 << 8) | b1;
1101: }
1102:
1103: // Unsigned 16 bits
1104: private int readWord(InputStream stream) throws IOException {
1105: return readUnsignedShort(stream);
1106: }
1107:
1108: // Unsigned 4 bytes
1109: private long readUnsignedInt(InputStream stream) throws IOException {
1110: int b1 = readUnsignedByte(stream);
1111: int b2 = readUnsignedByte(stream);
1112: int b3 = readUnsignedByte(stream);
1113: int b4 = readUnsignedByte(stream);
1114: long l = (long) ((b4 << 24) | (b3 << 16) | (b2 << 8) | b1);
1115: return l & 0xffffffff;
1116: }
1117:
1118: // Signed 4 bytes
1119: private int readInt(InputStream stream) throws IOException {
1120: int b1 = readUnsignedByte(stream);
1121: int b2 = readUnsignedByte(stream);
1122: int b3 = readUnsignedByte(stream);
1123: int b4 = readUnsignedByte(stream);
1124: return (b4 << 24) | (b3 << 16) | (b2 << 8) | b1;
1125: }
1126:
1127: // Unsigned 4 bytes
1128: private long readDWord(InputStream stream) throws IOException {
1129: return readUnsignedInt(stream);
1130: }
1131:
1132: // 32 bit signed value
1133: private int readLong(InputStream stream) throws IOException {
1134: return readInt(stream);
1135: }
1136:
1137: private synchronized Raster computeTile(int tileX, int tileY) {
1138: if (theTile != null) {
1139: return theTile;
1140: }
1141:
1142: // Create a new tile
1143: Point org = new Point(tileXToX(tileX), tileYToY(tileY));
1144: WritableRaster tile = RasterFactory.createWritableRaster(
1145: sampleModel, org);
1146: byte bdata[] = null; // buffer for byte data
1147: short sdata[] = null; // buffer for short data
1148: int idata[] = null; // buffer for int data
1149:
1150: if (sampleModel.getDataType() == DataBuffer.TYPE_BYTE)
1151: bdata = (byte[]) ((DataBufferByte) tile.getDataBuffer())
1152: .getData();
1153: else if (sampleModel.getDataType() == DataBuffer.TYPE_USHORT)
1154: sdata = (short[]) ((DataBufferUShort) tile.getDataBuffer())
1155: .getData();
1156: else if (sampleModel.getDataType() == DataBuffer.TYPE_INT)
1157: idata = (int[]) ((DataBufferInt) tile.getDataBuffer())
1158: .getData();
1159:
1160: // There should only be one tile.
1161: switch (imageType) {
1162:
1163: case VERSION_2_1_BIT:
1164: // no compression
1165: read1Bit(bdata, 3);
1166: break;
1167:
1168: case VERSION_2_4_BIT:
1169: // no compression
1170: read4Bit(bdata, 3);
1171: break;
1172:
1173: case VERSION_2_8_BIT:
1174: // no compression
1175: read8Bit(bdata, 3);
1176: break;
1177:
1178: case VERSION_2_24_BIT:
1179: // no compression
1180: read24Bit(bdata);
1181: break;
1182:
1183: case VERSION_3_1_BIT:
1184: // 1-bit images cannot be compressed.
1185: read1Bit(bdata, 4);
1186: break;
1187:
1188: case VERSION_3_4_BIT:
1189: switch ((int) compression) {
1190: case BI_RGB:
1191: read4Bit(bdata, 4);
1192: break;
1193:
1194: case BI_RLE4:
1195: int pixels[] = readRLE4();
1196: tile.setPixels(0, 0, width, height, pixels);
1197: break;
1198:
1199: default:
1200: throw new RuntimeException(JaiI18N
1201: .getString("BMPImageDecoder3"));
1202: }
1203: break;
1204:
1205: case VERSION_3_8_BIT:
1206: switch ((int) compression) {
1207: case BI_RGB:
1208: read8Bit(bdata, 4);
1209: break;
1210:
1211: case BI_RLE8:
1212: readRLE8(bdata);
1213: break;
1214:
1215: default:
1216: throw new RuntimeException(JaiI18N
1217: .getString("BMPImageDecoder3"));
1218: }
1219:
1220: break;
1221:
1222: case VERSION_3_24_BIT:
1223: // 24-bit images are not compressed
1224: read24Bit(bdata);
1225: break;
1226:
1227: case VERSION_3_NT_16_BIT:
1228: read16Bit(sdata);
1229: break;
1230:
1231: case VERSION_3_NT_32_BIT:
1232: read32Bit(idata);
1233: break;
1234:
1235: case VERSION_4_1_BIT:
1236: read1Bit(bdata, 4);
1237: break;
1238:
1239: case VERSION_4_4_BIT:
1240: switch ((int) compression) {
1241:
1242: case BI_RGB:
1243: read4Bit(bdata, 4);
1244: break;
1245:
1246: case BI_RLE4:
1247: int pixels[] = readRLE4();
1248: tile.setPixels(0, 0, width, height, pixels);
1249: break;
1250:
1251: default:
1252: throw new RuntimeException(JaiI18N
1253: .getString("BMPImageDecoder3"));
1254: }
1255:
1256: case VERSION_4_8_BIT:
1257: switch ((int) compression) {
1258:
1259: case BI_RGB:
1260: read8Bit(bdata, 4);
1261: break;
1262:
1263: case BI_RLE8:
1264: readRLE8(bdata);
1265: break;
1266:
1267: default:
1268: throw new RuntimeException(JaiI18N
1269: .getString("BMPImageDecoder3"));
1270: }
1271: break;
1272:
1273: case VERSION_4_16_BIT:
1274: read16Bit(sdata);
1275: break;
1276:
1277: case VERSION_4_24_BIT:
1278: read24Bit(bdata);
1279: break;
1280:
1281: case VERSION_4_32_BIT:
1282: read32Bit(idata);
1283: break;
1284: }
1285:
1286: theTile = tile;
1287:
1288: return tile;
1289: }
1290:
1291: public synchronized Raster getTile(int tileX, int tileY) {
1292: if ((tileX != 0) || (tileY != 0)) {
1293: throw new IllegalArgumentException(JaiI18N
1294: .getString("BMPImageDecoder7"));
1295: }
1296: return computeTile(tileX, tileY);
1297: }
1298:
1299: public void dispose() {
1300: theTile = null;
1301: }
1302: }
|