0001: /*
0002: * Copyright 2003 by Paulo Soares.
0003: *
0004: * The contents of this file are subject to the Mozilla Public License Version 1.1
0005: * (the "License"); you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at http://www.mozilla.org/MPL/
0007: *
0008: * Software distributed under the License is distributed on an "AS IS" basis,
0009: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
0010: * for the specific language governing rights and limitations under the License.
0011: *
0012: * The Original Code is 'iText, a free JAVA-PDF library'.
0013: *
0014: * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
0015: * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
0016: * All Rights Reserved.
0017: * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
0018: * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
0019: *
0020: * Contributor(s): all the names of the contributors are added in the source code
0021: * where applicable.
0022: *
0023: * Alternatively, the contents of this file may be used under the terms of the
0024: * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
0025: * provisions of LGPL are applicable instead of those above. If you wish to
0026: * allow use of your version of this file only under the terms of the LGPL
0027: * License and not to allow others to use your version of this file under
0028: * the MPL, indicate your decision by deleting the provisions above and
0029: * replace them with the notice and other provisions required by the LGPL.
0030: * If you do not delete the provisions above, a recipient may use your version
0031: * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
0032: *
0033: * This library is free software; you can redistribute it and/or modify it
0034: * under the terms of the MPL as stated above or under the terms of the GNU
0035: * Library General Public License as published by the Free Software Foundation;
0036: * either version 2 of the License, or any later version.
0037: *
0038: * This library is distributed in the hope that it will be useful, but WITHOUT
0039: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
0040: * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
0041: * details.
0042: *
0043: * If you didn't download this code from the following link, you should check if
0044: * you aren't using an obsolete version:
0045: * http://www.lowagie.com/iText/
0046: *
0047: *
0048: * The original JAI codecs have the following license
0049: *
0050: * Copyright (c) 2001 Sun Microsystems, Inc. All Rights Reserved.
0051: *
0052: * Redistribution and use in source and binary forms, with or without
0053: * modification, are permitted provided that the following conditions are met:
0054: *
0055: * -Redistributions of source code must retain the above copyright notice, this
0056: * list of conditions and the following disclaimer.
0057: *
0058: * -Redistribution in binary form must reproduct the above copyright notice,
0059: * this list of conditions and the following disclaimer in the documentation
0060: * and/or other materials provided with the distribution.
0061: *
0062: * Neither the name of Sun Microsystems, Inc. or the names of contributors may
0063: * be used to endorse or promote products derived from this software without
0064: * specific prior written permission.
0065: *
0066: * This software is provided "AS IS," without a warranty of any kind. ALL
0067: * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
0068: * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
0069: * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
0070: * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
0071: * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
0072: * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
0073: * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
0074: * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
0075: * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
0076: * POSSIBILITY OF SUCH DAMAGES.
0077: *
0078: * You acknowledge that Software is not designed,licensed or intended for use in
0079: * the design, construction, operation or maintenance of any nuclear facility.
0080: */
0081: package com.lowagie.text.pdf.codec;
0082:
0083: import java.io.BufferedInputStream;
0084: import java.io.ByteArrayInputStream;
0085: import java.io.IOException;
0086: import java.io.InputStream;
0087: import java.net.URL;
0088: import java.util.HashMap;
0089:
0090: import com.lowagie.text.BadElementException;
0091: import com.lowagie.text.ExceptionConverter;
0092: import com.lowagie.text.Image;
0093: import com.lowagie.text.ImgRaw;
0094: import com.lowagie.text.Utilities;
0095: import com.lowagie.text.pdf.PdfArray;
0096: import com.lowagie.text.pdf.PdfDictionary;
0097: import com.lowagie.text.pdf.PdfName;
0098: import com.lowagie.text.pdf.PdfNumber;
0099: import com.lowagie.text.pdf.PdfString;
0100:
0101: /** Reads a BMP image. All types of BMP can be read.
0102: * <p>
0103: * It is based in the JAI codec.
0104: *
0105: * @author Paulo Soares (psoares@consiste.pt)
0106: */
0107: public class BmpImage {
0108:
0109: // BMP variables
0110: private InputStream inputStream;
0111: private long bitmapFileSize;
0112: private long bitmapOffset;
0113: private long compression;
0114: private long imageSize;
0115: private byte palette[];
0116: private int imageType;
0117: private int numBands;
0118: private boolean isBottomUp;
0119: private int bitsPerPixel;
0120: private int redMask, greenMask, blueMask, alphaMask;
0121: public HashMap properties = new HashMap();
0122: private long xPelsPerMeter;
0123: private long yPelsPerMeter;
0124: // BMP Image types
0125: private static final int VERSION_2_1_BIT = 0;
0126: private static final int VERSION_2_4_BIT = 1;
0127: private static final int VERSION_2_8_BIT = 2;
0128: private static final int VERSION_2_24_BIT = 3;
0129:
0130: private static final int VERSION_3_1_BIT = 4;
0131: private static final int VERSION_3_4_BIT = 5;
0132: private static final int VERSION_3_8_BIT = 6;
0133: private static final int VERSION_3_24_BIT = 7;
0134:
0135: private static final int VERSION_3_NT_16_BIT = 8;
0136: private static final int VERSION_3_NT_32_BIT = 9;
0137:
0138: private static final int VERSION_4_1_BIT = 10;
0139: private static final int VERSION_4_4_BIT = 11;
0140: private static final int VERSION_4_8_BIT = 12;
0141: private static final int VERSION_4_16_BIT = 13;
0142: private static final int VERSION_4_24_BIT = 14;
0143: private static final int VERSION_4_32_BIT = 15;
0144:
0145: // Color space types
0146: private static final int LCS_CALIBRATED_RGB = 0;
0147: private static final int LCS_sRGB = 1;
0148: private static final int LCS_CMYK = 2;
0149:
0150: // Compression Types
0151: private static final int BI_RGB = 0;
0152: private static final int BI_RLE8 = 1;
0153: private static final int BI_RLE4 = 2;
0154: private static final int BI_BITFIELDS = 3;
0155:
0156: int width;
0157: int height;
0158:
0159: BmpImage(InputStream is, boolean noHeader, int size)
0160: throws IOException {
0161: bitmapFileSize = size;
0162: bitmapOffset = 0;
0163: process(is, noHeader);
0164: }
0165:
0166: /** Reads a BMP from an url.
0167: * @param url the url
0168: * @throws IOException on error
0169: * @return the image
0170: */
0171: public static Image getImage(URL url) throws IOException {
0172: InputStream is = null;
0173: try {
0174: is = url.openStream();
0175: Image img = getImage(is);
0176: img.setUrl(url);
0177: return img;
0178: } finally {
0179: if (is != null) {
0180: is.close();
0181: }
0182: }
0183: }
0184:
0185: /** Reads a BMP from a stream. The stream is not closed.
0186: * @param is the stream
0187: * @throws IOException on error
0188: * @return the image
0189: */
0190: public static Image getImage(InputStream is) throws IOException {
0191: return getImage(is, false, 0);
0192: }
0193:
0194: /** Reads a BMP from a stream. The stream is not closed.
0195: * The BMP may not have a header and be considered as a plain DIB.
0196: * @param is the stream
0197: * @param noHeader true to process a plain DIB
0198: * @param size the size of the DIB. Not used for a BMP
0199: * @throws IOException on error
0200: * @return the image
0201: */
0202: public static Image getImage(InputStream is, boolean noHeader,
0203: int size) throws IOException {
0204: BmpImage bmp = new BmpImage(is, noHeader, size);
0205: try {
0206: Image img = bmp.getImage();
0207: img.setDpi(
0208: (int) ((double) bmp.xPelsPerMeter * 0.0254 + 0.5),
0209: (int) ((double) bmp.yPelsPerMeter * 0.0254 + 0.5));
0210: img.setOriginalType(Image.ORIGINAL_BMP);
0211: return img;
0212: } catch (BadElementException be) {
0213: throw new ExceptionConverter(be);
0214: }
0215: }
0216:
0217: /** Reads a BMP from a file.
0218: * @param file the file
0219: * @throws IOException on error
0220: * @return the image
0221: */
0222: public static Image getImage(String file) throws IOException {
0223: return getImage(Utilities.toURL(file));
0224: }
0225:
0226: /** Reads a BMP from a byte array.
0227: * @param data the byte array
0228: * @throws IOException on error
0229: * @return the image
0230: */
0231: public static Image getImage(byte data[]) throws IOException {
0232: ByteArrayInputStream is = new ByteArrayInputStream(data);
0233: Image img = getImage(is);
0234: img.setOriginalData(data);
0235: return img;
0236: }
0237:
0238: protected void process(InputStream stream, boolean noHeader)
0239: throws IOException {
0240: if (noHeader || stream instanceof BufferedInputStream) {
0241: inputStream = stream;
0242: } else {
0243: inputStream = new BufferedInputStream(stream);
0244: }
0245: if (!noHeader) {
0246: // Start File Header
0247: if (!(readUnsignedByte(inputStream) == 'B' && readUnsignedByte(inputStream) == 'M')) {
0248: throw new RuntimeException(
0249: "Invalid magic value for BMP file.");
0250: }
0251:
0252: // Read file size
0253: bitmapFileSize = readDWord(inputStream);
0254:
0255: // Read the two reserved fields
0256: readWord(inputStream);
0257: readWord(inputStream);
0258:
0259: // Offset to the bitmap from the beginning
0260: bitmapOffset = readDWord(inputStream);
0261:
0262: // End File Header
0263: }
0264: // Start BitmapCoreHeader
0265: long size = readDWord(inputStream);
0266:
0267: if (size == 12) {
0268: width = readWord(inputStream);
0269: height = readWord(inputStream);
0270: } else {
0271: width = readLong(inputStream);
0272: height = readLong(inputStream);
0273: }
0274:
0275: int planes = readWord(inputStream);
0276: bitsPerPixel = readWord(inputStream);
0277:
0278: properties.put("color_planes", new Integer(planes));
0279: properties.put("bits_per_pixel", new Integer(bitsPerPixel));
0280:
0281: // As BMP always has 3 rgb bands, except for Version 5,
0282: // which is bgra
0283: numBands = 3;
0284: if (bitmapOffset == 0)
0285: bitmapOffset = size;
0286: if (size == 12) {
0287: // Windows 2.x and OS/2 1.x
0288: properties.put("bmp_version", "BMP v. 2.x");
0289:
0290: // Classify the image type
0291: if (bitsPerPixel == 1) {
0292: imageType = VERSION_2_1_BIT;
0293: } else if (bitsPerPixel == 4) {
0294: imageType = VERSION_2_4_BIT;
0295: } else if (bitsPerPixel == 8) {
0296: imageType = VERSION_2_8_BIT;
0297: } else if (bitsPerPixel == 24) {
0298: imageType = VERSION_2_24_BIT;
0299: }
0300:
0301: // Read in the palette
0302: int numberOfEntries = (int) ((bitmapOffset - 14 - size) / 3);
0303: int sizeOfPalette = numberOfEntries * 3;
0304: if (bitmapOffset == size) {
0305: switch (imageType) {
0306: case VERSION_2_1_BIT:
0307: sizeOfPalette = 2 * 3;
0308: break;
0309: case VERSION_2_4_BIT:
0310: sizeOfPalette = 16 * 3;
0311: break;
0312: case VERSION_2_8_BIT:
0313: sizeOfPalette = 256 * 3;
0314: break;
0315: case VERSION_2_24_BIT:
0316: sizeOfPalette = 0;
0317: break;
0318: }
0319: bitmapOffset = size + sizeOfPalette;
0320: }
0321: readPalette(sizeOfPalette);
0322: } else {
0323:
0324: compression = readDWord(inputStream);
0325: imageSize = readDWord(inputStream);
0326: xPelsPerMeter = readLong(inputStream);
0327: yPelsPerMeter = readLong(inputStream);
0328: long colorsUsed = readDWord(inputStream);
0329: long colorsImportant = readDWord(inputStream);
0330:
0331: switch ((int) compression) {
0332: case BI_RGB:
0333: properties.put("compression", "BI_RGB");
0334: break;
0335:
0336: case BI_RLE8:
0337: properties.put("compression", "BI_RLE8");
0338: break;
0339:
0340: case BI_RLE4:
0341: properties.put("compression", "BI_RLE4");
0342: break;
0343:
0344: case BI_BITFIELDS:
0345: properties.put("compression", "BI_BITFIELDS");
0346: break;
0347: }
0348:
0349: properties.put("x_pixels_per_meter",
0350: new Long(xPelsPerMeter));
0351: properties.put("y_pixels_per_meter",
0352: new Long(yPelsPerMeter));
0353: properties.put("colors_used", new Long(colorsUsed));
0354: properties.put("colors_important",
0355: new Long(colorsImportant));
0356:
0357: if (size == 40) {
0358: // Windows 3.x and Windows NT
0359: switch ((int) compression) {
0360:
0361: case BI_RGB: // No compression
0362: case BI_RLE8: // 8-bit RLE compression
0363: case BI_RLE4: // 4-bit RLE compression
0364:
0365: if (bitsPerPixel == 1) {
0366: imageType = VERSION_3_1_BIT;
0367: } else if (bitsPerPixel == 4) {
0368: imageType = VERSION_3_4_BIT;
0369: } else if (bitsPerPixel == 8) {
0370: imageType = VERSION_3_8_BIT;
0371: } else if (bitsPerPixel == 24) {
0372: imageType = VERSION_3_24_BIT;
0373: } else if (bitsPerPixel == 16) {
0374: imageType = VERSION_3_NT_16_BIT;
0375: redMask = 0x7C00;
0376: greenMask = 0x3E0;
0377: blueMask = 0x1F;
0378: properties
0379: .put("red_mask", new Integer(redMask));
0380: properties.put("green_mask", new Integer(
0381: greenMask));
0382: properties.put("blue_mask", new Integer(
0383: blueMask));
0384: } else if (bitsPerPixel == 32) {
0385: imageType = VERSION_3_NT_32_BIT;
0386: redMask = 0x00FF0000;
0387: greenMask = 0x0000FF00;
0388: blueMask = 0x000000FF;
0389: properties
0390: .put("red_mask", new Integer(redMask));
0391: properties.put("green_mask", new Integer(
0392: greenMask));
0393: properties.put("blue_mask", new Integer(
0394: blueMask));
0395: }
0396:
0397: // Read in the palette
0398: int numberOfEntries = (int) ((bitmapOffset - 14 - size) / 4);
0399: int sizeOfPalette = numberOfEntries * 4;
0400: if (bitmapOffset == size) {
0401: switch (imageType) {
0402: case VERSION_3_1_BIT:
0403: sizeOfPalette = (int) (colorsUsed == 0 ? 2
0404: : colorsUsed) * 4;
0405: break;
0406: case VERSION_3_4_BIT:
0407: sizeOfPalette = (int) (colorsUsed == 0 ? 16
0408: : colorsUsed) * 4;
0409: break;
0410: case VERSION_3_8_BIT:
0411: sizeOfPalette = (int) (colorsUsed == 0 ? 256
0412: : colorsUsed) * 4;
0413: break;
0414: default:
0415: sizeOfPalette = 0;
0416: break;
0417: }
0418: bitmapOffset = size + sizeOfPalette;
0419: }
0420: readPalette(sizeOfPalette);
0421:
0422: properties.put("bmp_version", "BMP v. 3.x");
0423: break;
0424:
0425: case BI_BITFIELDS:
0426:
0427: if (bitsPerPixel == 16) {
0428: imageType = VERSION_3_NT_16_BIT;
0429: } else if (bitsPerPixel == 32) {
0430: imageType = VERSION_3_NT_32_BIT;
0431: }
0432:
0433: // BitsField encoding
0434: redMask = (int) readDWord(inputStream);
0435: greenMask = (int) readDWord(inputStream);
0436: blueMask = (int) readDWord(inputStream);
0437:
0438: properties.put("red_mask", new Integer(redMask));
0439: properties
0440: .put("green_mask", new Integer(greenMask));
0441: properties.put("blue_mask", new Integer(blueMask));
0442:
0443: if (colorsUsed != 0) {
0444: // there is a palette
0445: sizeOfPalette = (int) colorsUsed * 4;
0446: readPalette(sizeOfPalette);
0447: }
0448:
0449: properties.put("bmp_version", "BMP v. 3.x NT");
0450: break;
0451:
0452: default:
0453: throw new RuntimeException(
0454: "Invalid compression specified in BMP file.");
0455: }
0456: } else if (size == 108) {
0457: // Windows 4.x BMP
0458:
0459: properties.put("bmp_version", "BMP v. 4.x");
0460:
0461: // rgb masks, valid only if comp is BI_BITFIELDS
0462: redMask = (int) readDWord(inputStream);
0463: greenMask = (int) readDWord(inputStream);
0464: blueMask = (int) readDWord(inputStream);
0465: // Only supported for 32bpp BI_RGB argb
0466: alphaMask = (int) readDWord(inputStream);
0467: long csType = readDWord(inputStream);
0468: int redX = readLong(inputStream);
0469: int redY = readLong(inputStream);
0470: int redZ = readLong(inputStream);
0471: int greenX = readLong(inputStream);
0472: int greenY = readLong(inputStream);
0473: int greenZ = readLong(inputStream);
0474: int blueX = readLong(inputStream);
0475: int blueY = readLong(inputStream);
0476: int blueZ = readLong(inputStream);
0477: long gammaRed = readDWord(inputStream);
0478: long gammaGreen = readDWord(inputStream);
0479: long gammaBlue = readDWord(inputStream);
0480:
0481: if (bitsPerPixel == 1) {
0482: imageType = VERSION_4_1_BIT;
0483: } else if (bitsPerPixel == 4) {
0484: imageType = VERSION_4_4_BIT;
0485: } else if (bitsPerPixel == 8) {
0486: imageType = VERSION_4_8_BIT;
0487: } else if (bitsPerPixel == 16) {
0488: imageType = VERSION_4_16_BIT;
0489: if ((int) compression == BI_RGB) {
0490: redMask = 0x7C00;
0491: greenMask = 0x3E0;
0492: blueMask = 0x1F;
0493: }
0494: } else if (bitsPerPixel == 24) {
0495: imageType = VERSION_4_24_BIT;
0496: } else if (bitsPerPixel == 32) {
0497: imageType = VERSION_4_32_BIT;
0498: if ((int) compression == BI_RGB) {
0499: redMask = 0x00FF0000;
0500: greenMask = 0x0000FF00;
0501: blueMask = 0x000000FF;
0502: }
0503: }
0504:
0505: properties.put("red_mask", new Integer(redMask));
0506: properties.put("green_mask", new Integer(greenMask));
0507: properties.put("blue_mask", new Integer(blueMask));
0508: properties.put("alpha_mask", new Integer(alphaMask));
0509:
0510: // Read in the palette
0511: int numberOfEntries = (int) ((bitmapOffset - 14 - size) / 4);
0512: int sizeOfPalette = numberOfEntries * 4;
0513: if (bitmapOffset == size) {
0514: switch (imageType) {
0515: case VERSION_4_1_BIT:
0516: sizeOfPalette = (int) (colorsUsed == 0 ? 2
0517: : colorsUsed) * 4;
0518: break;
0519: case VERSION_4_4_BIT:
0520: sizeOfPalette = (int) (colorsUsed == 0 ? 16
0521: : colorsUsed) * 4;
0522: break;
0523: case VERSION_4_8_BIT:
0524: sizeOfPalette = (int) (colorsUsed == 0 ? 256
0525: : colorsUsed) * 4;
0526: break;
0527: default:
0528: sizeOfPalette = 0;
0529: break;
0530: }
0531: bitmapOffset = size + sizeOfPalette;
0532: }
0533: readPalette(sizeOfPalette);
0534:
0535: switch ((int) csType) {
0536: case LCS_CALIBRATED_RGB:
0537: // All the new fields are valid only for this case
0538: properties.put("color_space", "LCS_CALIBRATED_RGB");
0539: properties.put("redX", new Integer(redX));
0540: properties.put("redY", new Integer(redY));
0541: properties.put("redZ", new Integer(redZ));
0542: properties.put("greenX", new Integer(greenX));
0543: properties.put("greenY", new Integer(greenY));
0544: properties.put("greenZ", new Integer(greenZ));
0545: properties.put("blueX", new Integer(blueX));
0546: properties.put("blueY", new Integer(blueY));
0547: properties.put("blueZ", new Integer(blueZ));
0548: properties.put("gamma_red", new Long(gammaRed));
0549: properties.put("gamma_green", new Long(gammaGreen));
0550: properties.put("gamma_blue", new Long(gammaBlue));
0551:
0552: // break;
0553: throw new RuntimeException("Not implemented yet.");
0554:
0555: case LCS_sRGB:
0556: // Default Windows color space
0557: properties.put("color_space", "LCS_sRGB");
0558: break;
0559:
0560: case LCS_CMYK:
0561: properties.put("color_space", "LCS_CMYK");
0562: // break;
0563: throw new RuntimeException("Not implemented yet.");
0564: }
0565:
0566: } else {
0567: properties.put("bmp_version", "BMP v. 5.x");
0568: throw new RuntimeException(
0569: "BMP version 5 not implemented yet.");
0570: }
0571: }
0572:
0573: if (height > 0) {
0574: // bottom up image
0575: isBottomUp = true;
0576: } else {
0577: // top down image
0578: isBottomUp = false;
0579: height = Math.abs(height);
0580: }
0581: // When number of bitsPerPixel is <= 8, we use IndexColorModel.
0582: if (bitsPerPixel == 1 || bitsPerPixel == 4 || bitsPerPixel == 8) {
0583:
0584: numBands = 1;
0585:
0586: // Create IndexColorModel from the palette.
0587: byte r[], g[], b[];
0588: int sizep;
0589: if (imageType == VERSION_2_1_BIT
0590: || imageType == VERSION_2_4_BIT
0591: || imageType == VERSION_2_8_BIT) {
0592:
0593: sizep = palette.length / 3;
0594:
0595: if (sizep > 256) {
0596: sizep = 256;
0597: }
0598:
0599: int off;
0600: r = new byte[sizep];
0601: g = new byte[sizep];
0602: b = new byte[sizep];
0603: for (int i = 0; i < sizep; i++) {
0604: off = 3 * i;
0605: b[i] = palette[off];
0606: g[i] = palette[off + 1];
0607: r[i] = palette[off + 2];
0608: }
0609: } else {
0610: sizep = palette.length / 4;
0611:
0612: if (sizep > 256) {
0613: sizep = 256;
0614: }
0615:
0616: int off;
0617: r = new byte[sizep];
0618: g = new byte[sizep];
0619: b = new byte[sizep];
0620: for (int i = 0; i < sizep; i++) {
0621: off = 4 * i;
0622: b[i] = palette[off];
0623: g[i] = palette[off + 1];
0624: r[i] = palette[off + 2];
0625: }
0626: }
0627:
0628: } else if (bitsPerPixel == 16) {
0629: numBands = 3;
0630: } else if (bitsPerPixel == 32) {
0631: numBands = alphaMask == 0 ? 3 : 4;
0632:
0633: // The number of bands in the SampleModel is determined by
0634: // the length of the mask array passed in.
0635: } else {
0636: numBands = 3;
0637: }
0638: }
0639:
0640: private byte[] getPalette(int group) {
0641: if (palette == null)
0642: return null;
0643: byte np[] = new byte[palette.length / group * 3];
0644: int e = palette.length / group;
0645: for (int k = 0; k < e; ++k) {
0646: int src = k * group;
0647: int dest = k * 3;
0648: np[dest + 2] = palette[src++];
0649: np[dest + 1] = palette[src++];
0650: np[dest] = palette[src];
0651: }
0652: return np;
0653: }
0654:
0655: private Image getImage() throws IOException, BadElementException {
0656: byte bdata[] = null; // buffer for byte data
0657:
0658: // if (sampleModel.getDataType() == DataBuffer.TYPE_BYTE)
0659: // bdata = (byte[])((DataBufferByte)tile.getDataBuffer()).getData();
0660: // else if (sampleModel.getDataType() == DataBuffer.TYPE_USHORT)
0661: // sdata = (short[])((DataBufferUShort)tile.getDataBuffer()).getData();
0662: // else if (sampleModel.getDataType() == DataBuffer.TYPE_INT)
0663: // idata = (int[])((DataBufferInt)tile.getDataBuffer()).getData();
0664:
0665: // There should only be one tile.
0666: switch (imageType) {
0667:
0668: case VERSION_2_1_BIT:
0669: // no compression
0670: return read1Bit(3);
0671:
0672: case VERSION_2_4_BIT:
0673: // no compression
0674: return read4Bit(3);
0675:
0676: case VERSION_2_8_BIT:
0677: // no compression
0678: return read8Bit(3);
0679:
0680: case VERSION_2_24_BIT:
0681: // no compression
0682: bdata = new byte[width * height * 3];
0683: read24Bit(bdata);
0684: return new ImgRaw(width, height, 3, 8, bdata);
0685:
0686: case VERSION_3_1_BIT:
0687: // 1-bit images cannot be compressed.
0688: return read1Bit(4);
0689:
0690: case VERSION_3_4_BIT:
0691: switch ((int) compression) {
0692: case BI_RGB:
0693: return read4Bit(4);
0694:
0695: case BI_RLE4:
0696: return readRLE4();
0697:
0698: default:
0699: throw new RuntimeException(
0700: "Invalid compression specified for BMP file.");
0701: }
0702:
0703: case VERSION_3_8_BIT:
0704: switch ((int) compression) {
0705: case BI_RGB:
0706: return read8Bit(4);
0707:
0708: case BI_RLE8:
0709: return readRLE8();
0710:
0711: default:
0712: throw new RuntimeException(
0713: "Invalid compression specified for BMP file.");
0714: }
0715:
0716: case VERSION_3_24_BIT:
0717: // 24-bit images are not compressed
0718: bdata = new byte[width * height * 3];
0719: read24Bit(bdata);
0720: return new ImgRaw(width, height, 3, 8, bdata);
0721:
0722: case VERSION_3_NT_16_BIT:
0723: return read1632Bit(false);
0724:
0725: case VERSION_3_NT_32_BIT:
0726: return read1632Bit(true);
0727:
0728: case VERSION_4_1_BIT:
0729: return read1Bit(4);
0730:
0731: case VERSION_4_4_BIT:
0732: switch ((int) compression) {
0733:
0734: case BI_RGB:
0735: return read4Bit(4);
0736:
0737: case BI_RLE4:
0738: return readRLE4();
0739:
0740: default:
0741: throw new RuntimeException(
0742: "Invalid compression specified for BMP file.");
0743: }
0744:
0745: case VERSION_4_8_BIT:
0746: switch ((int) compression) {
0747:
0748: case BI_RGB:
0749: return read8Bit(4);
0750:
0751: case BI_RLE8:
0752: return readRLE8();
0753:
0754: default:
0755: throw new RuntimeException(
0756: "Invalid compression specified for BMP file.");
0757: }
0758:
0759: case VERSION_4_16_BIT:
0760: return read1632Bit(false);
0761:
0762: case VERSION_4_24_BIT:
0763: bdata = new byte[width * height * 3];
0764: read24Bit(bdata);
0765: return new ImgRaw(width, height, 3, 8, bdata);
0766:
0767: case VERSION_4_32_BIT:
0768: return read1632Bit(true);
0769: }
0770: return null;
0771: }
0772:
0773: private Image indexedModel(byte bdata[], int bpc, int paletteEntries)
0774: throws BadElementException {
0775: Image img = new ImgRaw(width, height, 1, bpc, bdata);
0776: PdfArray colorspace = new PdfArray();
0777: colorspace.add(PdfName.INDEXED);
0778: colorspace.add(PdfName.DEVICERGB);
0779: byte np[] = getPalette(paletteEntries);
0780: int len = np.length;
0781: colorspace.add(new PdfNumber(len / 3 - 1));
0782: colorspace.add(new PdfString(np));
0783: PdfDictionary ad = new PdfDictionary();
0784: ad.put(PdfName.COLORSPACE, colorspace);
0785: img.setAdditional(ad);
0786: return img;
0787: }
0788:
0789: private void readPalette(int sizeOfPalette) throws IOException {
0790: if (sizeOfPalette == 0) {
0791: return;
0792: }
0793:
0794: palette = new byte[sizeOfPalette];
0795: int bytesRead = 0;
0796: while (bytesRead < sizeOfPalette) {
0797: int r = inputStream.read(palette, bytesRead, sizeOfPalette
0798: - bytesRead);
0799: if (r < 0) {
0800: throw new RuntimeException("incomplete palette");
0801: }
0802: bytesRead += r;
0803: }
0804: properties.put("palette", palette);
0805: }
0806:
0807: // Deal with 1 Bit images using IndexColorModels
0808: private Image read1Bit(int paletteEntries) throws IOException,
0809: BadElementException {
0810: byte bdata[] = new byte[((width + 7) / 8) * height];
0811: int padding = 0;
0812: int bytesPerScanline = (int) Math.ceil((double) width / 8.0);
0813:
0814: int remainder = bytesPerScanline % 4;
0815: if (remainder != 0) {
0816: padding = 4 - remainder;
0817: }
0818:
0819: int imSize = (bytesPerScanline + padding) * height;
0820:
0821: // Read till we have the whole image
0822: byte values[] = new byte[imSize];
0823: int bytesRead = 0;
0824: while (bytesRead < imSize) {
0825: bytesRead += inputStream.read(values, bytesRead, imSize
0826: - bytesRead);
0827: }
0828:
0829: if (isBottomUp) {
0830:
0831: // Convert the bottom up image to a top down format by copying
0832: // one scanline from the bottom to the top at a time.
0833:
0834: for (int i = 0; i < height; i++) {
0835: System.arraycopy(values, imSize - (i + 1)
0836: * (bytesPerScanline + padding), bdata, i
0837: * bytesPerScanline, bytesPerScanline);
0838: }
0839: } else {
0840:
0841: for (int i = 0; i < height; i++) {
0842: System.arraycopy(values, i
0843: * (bytesPerScanline + padding), bdata, i
0844: * bytesPerScanline, bytesPerScanline);
0845: }
0846: }
0847: return indexedModel(bdata, 1, paletteEntries);
0848: }
0849:
0850: // Method to read a 4 bit BMP image data
0851: private Image read4Bit(int paletteEntries) throws IOException,
0852: BadElementException {
0853: byte bdata[] = new byte[((width + 1) / 2) * height];
0854:
0855: // Padding bytes at the end of each scanline
0856: int padding = 0;
0857:
0858: int bytesPerScanline = (int) Math.ceil((double) width / 2.0);
0859: int remainder = bytesPerScanline % 4;
0860: if (remainder != 0) {
0861: padding = 4 - remainder;
0862: }
0863:
0864: int imSize = (bytesPerScanline + padding) * height;
0865:
0866: // Read till we have the whole image
0867: byte values[] = new byte[imSize];
0868: int bytesRead = 0;
0869: while (bytesRead < imSize) {
0870: bytesRead += inputStream.read(values, bytesRead, imSize
0871: - bytesRead);
0872: }
0873:
0874: if (isBottomUp) {
0875:
0876: // Convert the bottom up image to a top down format by copying
0877: // one scanline from the bottom to the top at a time.
0878: for (int i = 0; i < height; i++) {
0879: System.arraycopy(values, imSize - (i + 1)
0880: * (bytesPerScanline + padding), bdata, i
0881: * bytesPerScanline, bytesPerScanline);
0882: }
0883: } else {
0884: for (int i = 0; i < height; i++) {
0885: System.arraycopy(values, i
0886: * (bytesPerScanline + padding), bdata, i
0887: * bytesPerScanline, bytesPerScanline);
0888: }
0889: }
0890: return indexedModel(bdata, 4, paletteEntries);
0891: }
0892:
0893: // Method to read 8 bit BMP image data
0894: private Image read8Bit(int paletteEntries) throws IOException,
0895: BadElementException {
0896: byte bdata[] = new byte[width * height];
0897: // Padding bytes at the end of each scanline
0898: int padding = 0;
0899:
0900: // width * bitsPerPixel should be divisible by 32
0901: int bitsPerScanline = width * 8;
0902: if (bitsPerScanline % 32 != 0) {
0903: padding = (bitsPerScanline / 32 + 1) * 32 - bitsPerScanline;
0904: padding = (int) Math.ceil(padding / 8.0);
0905: }
0906:
0907: int imSize = (width + padding) * height;
0908:
0909: // Read till we have the whole image
0910: byte values[] = new byte[imSize];
0911: int bytesRead = 0;
0912: while (bytesRead < imSize) {
0913: bytesRead += inputStream.read(values, bytesRead, imSize
0914: - bytesRead);
0915: }
0916:
0917: if (isBottomUp) {
0918:
0919: // Convert the bottom up image to a top down format by copying
0920: // one scanline from the bottom to the top at a time.
0921: for (int i = 0; i < height; i++) {
0922: System.arraycopy(values, imSize - (i + 1)
0923: * (width + padding), bdata, i * width, width);
0924: }
0925: } else {
0926: for (int i = 0; i < height; i++) {
0927: System.arraycopy(values, i * (width + padding), bdata,
0928: i * width, width);
0929: }
0930: }
0931: return indexedModel(bdata, 8, paletteEntries);
0932: }
0933:
0934: // Method to read 24 bit BMP image data
0935: private void read24Bit(byte[] bdata) {
0936: // Padding bytes at the end of each scanline
0937: int padding = 0;
0938:
0939: // width * bitsPerPixel should be divisible by 32
0940: int bitsPerScanline = width * 24;
0941: if (bitsPerScanline % 32 != 0) {
0942: padding = (bitsPerScanline / 32 + 1) * 32 - bitsPerScanline;
0943: padding = (int) Math.ceil(padding / 8.0);
0944: }
0945:
0946: int imSize = ((width * 3 + 3) / 4 * 4) * height;
0947: // Read till we have the whole image
0948: byte values[] = new byte[imSize];
0949: try {
0950: int bytesRead = 0;
0951: while (bytesRead < imSize) {
0952: int r = inputStream.read(values, bytesRead, imSize
0953: - bytesRead);
0954: if (r < 0)
0955: break;
0956: bytesRead += r;
0957: }
0958: } catch (IOException ioe) {
0959: throw new ExceptionConverter(ioe);
0960: }
0961:
0962: int l = 0, count;
0963:
0964: if (isBottomUp) {
0965: int max = width * height * 3 - 1;
0966:
0967: count = -padding;
0968: for (int i = 0; i < height; i++) {
0969: l = max - (i + 1) * width * 3 + 1;
0970: count += padding;
0971: for (int j = 0; j < width; j++) {
0972: bdata[l + 2] = values[count++];
0973: bdata[l + 1] = values[count++];
0974: bdata[l] = values[count++];
0975: l += 3;
0976: }
0977: }
0978: } else {
0979: count = -padding;
0980: for (int i = 0; i < height; i++) {
0981: count += padding;
0982: for (int j = 0; j < width; j++) {
0983: bdata[l + 2] = values[count++];
0984: bdata[l + 1] = values[count++];
0985: bdata[l] = values[count++];
0986: l += 3;
0987: }
0988: }
0989: }
0990: }
0991:
0992: private int findMask(int mask) {
0993: int k = 0;
0994: for (; k < 32; ++k) {
0995: if ((mask & 1) == 1)
0996: break;
0997: mask >>>= 1;
0998: }
0999: return mask;
1000: }
1001:
1002: private int findShift(int mask) {
1003: int k = 0;
1004: for (; k < 32; ++k) {
1005: if ((mask & 1) == 1)
1006: break;
1007: mask >>>= 1;
1008: }
1009: return k;
1010: }
1011:
1012: private Image read1632Bit(boolean is32) throws IOException,
1013: BadElementException {
1014:
1015: int red_mask = findMask(redMask);
1016: int red_shift = findShift(redMask);
1017: int red_factor = red_mask + 1;
1018: int green_mask = findMask(greenMask);
1019: int green_shift = findShift(greenMask);
1020: int green_factor = green_mask + 1;
1021: int blue_mask = findMask(blueMask);
1022: int blue_shift = findShift(blueMask);
1023: int blue_factor = blue_mask + 1;
1024: byte bdata[] = new byte[width * height * 3];
1025: // Padding bytes at the end of each scanline
1026: int padding = 0;
1027:
1028: if (!is32) {
1029: // width * bitsPerPixel should be divisible by 32
1030: int bitsPerScanline = width * 16;
1031: if (bitsPerScanline % 32 != 0) {
1032: padding = (bitsPerScanline / 32 + 1) * 32
1033: - bitsPerScanline;
1034: padding = (int) Math.ceil(padding / 8.0);
1035: }
1036: }
1037:
1038: int imSize = (int) imageSize;
1039: if (imSize == 0) {
1040: imSize = (int) (bitmapFileSize - bitmapOffset);
1041: }
1042:
1043: int l = 0;
1044: int v;
1045: if (isBottomUp) {
1046: for (int i = height - 1; i >= 0; --i) {
1047: l = width * 3 * i;
1048: for (int j = 0; j < width; j++) {
1049: if (is32)
1050: v = (int) readDWord(inputStream);
1051: else
1052: v = readWord(inputStream);
1053: bdata[l++] = (byte) (((v >>> red_shift) & red_mask) * 256 / red_factor);
1054: bdata[l++] = (byte) (((v >>> green_shift) & green_mask) * 256 / green_factor);
1055: bdata[l++] = (byte) (((v >>> blue_shift) & blue_mask) * 256 / blue_factor);
1056: }
1057: for (int m = 0; m < padding; m++) {
1058: inputStream.read();
1059: }
1060: }
1061: } else {
1062: for (int i = 0; i < height; i++) {
1063: for (int j = 0; j < width; j++) {
1064: if (is32)
1065: v = (int) readDWord(inputStream);
1066: else
1067: v = readWord(inputStream);
1068: bdata[l++] = (byte) (((v >>> red_shift) & red_mask) * 256 / red_factor);
1069: bdata[l++] = (byte) (((v >>> green_shift) & green_mask) * 256 / green_factor);
1070: bdata[l++] = (byte) (((v >>> blue_shift) & blue_mask) * 256 / blue_factor);
1071: }
1072: for (int m = 0; m < padding; m++) {
1073: inputStream.read();
1074: }
1075: }
1076: }
1077: return new ImgRaw(width, height, 3, 8, bdata);
1078: }
1079:
1080: private Image readRLE8() throws IOException, BadElementException {
1081:
1082: // If imageSize field is not provided, calculate it.
1083: int imSize = (int) imageSize;
1084: if (imSize == 0) {
1085: imSize = (int) (bitmapFileSize - bitmapOffset);
1086: }
1087:
1088: // Read till we have the whole image
1089: byte values[] = new byte[imSize];
1090: int bytesRead = 0;
1091: while (bytesRead < imSize) {
1092: bytesRead += inputStream.read(values, bytesRead, imSize
1093: - bytesRead);
1094: }
1095:
1096: // Since data is compressed, decompress it
1097: byte val[] = decodeRLE(true, values);
1098:
1099: // Uncompressed data does not have any padding
1100: imSize = width * height;
1101:
1102: if (isBottomUp) {
1103:
1104: // Convert the bottom up image to a top down format by copying
1105: // one scanline from the bottom to the top at a time.
1106: // int bytesPerScanline = (int)Math.ceil((double)width/8.0);
1107: byte temp[] = new byte[val.length];
1108: int bytesPerScanline = width;
1109: for (int i = 0; i < height; i++) {
1110: System.arraycopy(val, imSize - (i + 1)
1111: * (bytesPerScanline), temp, i
1112: * bytesPerScanline, bytesPerScanline);
1113: }
1114: val = temp;
1115: }
1116: return indexedModel(val, 8, 4);
1117: }
1118:
1119: private Image readRLE4() throws IOException, BadElementException {
1120:
1121: // If imageSize field is not specified, calculate it.
1122: int imSize = (int) imageSize;
1123: if (imSize == 0) {
1124: imSize = (int) (bitmapFileSize - bitmapOffset);
1125: }
1126:
1127: // Read till we have the whole image
1128: byte values[] = new byte[imSize];
1129: int bytesRead = 0;
1130: while (bytesRead < imSize) {
1131: bytesRead += inputStream.read(values, bytesRead, imSize
1132: - bytesRead);
1133: }
1134:
1135: // Decompress the RLE4 compressed data.
1136: byte val[] = decodeRLE(false, values);
1137:
1138: // Invert it as it is bottom up format.
1139: if (isBottomUp) {
1140:
1141: byte inverted[] = val;
1142: val = new byte[width * height];
1143: int l = 0, index, lineEnd;
1144:
1145: for (int i = height - 1; i >= 0; i--) {
1146: index = i * width;
1147: lineEnd = l + width;
1148: while (l != lineEnd) {
1149: val[l++] = inverted[index++];
1150: }
1151: }
1152: }
1153: int stride = ((width + 1) / 2);
1154: byte bdata[] = new byte[stride * height];
1155: int ptr = 0;
1156: int sh = 0;
1157: for (int h = 0; h < height; ++h) {
1158: for (int w = 0; w < width; ++w) {
1159: if ((w & 1) == 0)
1160: bdata[sh + w / 2] = (byte) (val[ptr++] << 4);
1161: else
1162: bdata[sh + w / 2] |= (byte) (val[ptr++] & 0x0f);
1163: }
1164: sh += stride;
1165: }
1166: return indexedModel(bdata, 4, 4);
1167: }
1168:
1169: private byte[] decodeRLE(boolean is8, byte values[]) {
1170: byte val[] = new byte[width * height];
1171: try {
1172: int ptr = 0;
1173: int x = 0;
1174: int q = 0;
1175: for (int y = 0; y < height && ptr < values.length;) {
1176: int count = values[ptr++] & 0xff;
1177: if (count != 0) {
1178: // encoded mode
1179: int bt = values[ptr++] & 0xff;
1180: if (is8) {
1181: for (int i = count; i != 0; --i) {
1182: val[q++] = (byte) bt;
1183: }
1184: } else {
1185: for (int i = 0; i < count; ++i) {
1186: val[q++] = (byte) ((i & 1) == 1 ? (bt & 0x0f)
1187: : ((bt >>> 4) & 0x0f));
1188: }
1189: }
1190: x += count;
1191: } else {
1192: // escape mode
1193: count = values[ptr++] & 0xff;
1194: if (count == 1)
1195: break;
1196: switch (count) {
1197: case 0:
1198: x = 0;
1199: ++y;
1200: q = y * width;
1201: break;
1202: case 2:
1203: // delta mode
1204: x += values[ptr++] & 0xff;
1205: y += values[ptr++] & 0xff;
1206: q = y * width + x;
1207: break;
1208: default:
1209: // absolute mode
1210: if (is8) {
1211: for (int i = count; i != 0; --i)
1212: val[q++] = (byte) (values[ptr++] & 0xff);
1213: } else {
1214: int bt = 0;
1215: for (int i = 0; i < count; ++i) {
1216: if ((i & 1) == 0)
1217: bt = values[ptr++] & 0xff;
1218: val[q++] = (byte) ((i & 1) == 1 ? (bt & 0x0f)
1219: : ((bt >>> 4) & 0x0f));
1220: }
1221: }
1222: x += count;
1223: // read pad byte
1224: if (is8) {
1225: if ((count & 1) == 1)
1226: ++ptr;
1227: } else {
1228: if ((count & 3) == 1 || (count & 3) == 2)
1229: ++ptr;
1230: }
1231: break;
1232: }
1233: }
1234: }
1235: } catch (RuntimeException e) {
1236: //empty on purpose
1237: }
1238:
1239: return val;
1240: }
1241:
1242: // Windows defined data type reading methods - everything is little endian
1243:
1244: // Unsigned 8 bits
1245: private int readUnsignedByte(InputStream stream) throws IOException {
1246: return (stream.read() & 0xff);
1247: }
1248:
1249: // Unsigned 2 bytes
1250: private int readUnsignedShort(InputStream stream)
1251: throws IOException {
1252: int b1 = readUnsignedByte(stream);
1253: int b2 = readUnsignedByte(stream);
1254: return ((b2 << 8) | b1) & 0xffff;
1255: }
1256:
1257: // Signed 16 bits
1258: private int readShort(InputStream stream) throws IOException {
1259: int b1 = readUnsignedByte(stream);
1260: int b2 = readUnsignedByte(stream);
1261: return (b2 << 8) | b1;
1262: }
1263:
1264: // Unsigned 16 bits
1265: private int readWord(InputStream stream) throws IOException {
1266: return readUnsignedShort(stream);
1267: }
1268:
1269: // Unsigned 4 bytes
1270: private long readUnsignedInt(InputStream stream) throws IOException {
1271: int b1 = readUnsignedByte(stream);
1272: int b2 = readUnsignedByte(stream);
1273: int b3 = readUnsignedByte(stream);
1274: int b4 = readUnsignedByte(stream);
1275: long l = (long) ((b4 << 24) | (b3 << 16) | (b2 << 8) | b1);
1276: return l & 0xffffffff;
1277: }
1278:
1279: // Signed 4 bytes
1280: private int readInt(InputStream stream) throws IOException {
1281: int b1 = readUnsignedByte(stream);
1282: int b2 = readUnsignedByte(stream);
1283: int b3 = readUnsignedByte(stream);
1284: int b4 = readUnsignedByte(stream);
1285: return (b4 << 24) | (b3 << 16) | (b2 << 8) | b1;
1286: }
1287:
1288: // Unsigned 4 bytes
1289: private long readDWord(InputStream stream) throws IOException {
1290: return readUnsignedInt(stream);
1291: }
1292:
1293: // 32 bit signed value
1294: private int readLong(InputStream stream) throws IOException {
1295: return readInt(stream);
1296: }
1297: }
|