/*
* $Id: BmpImage.java 4242 2010-01-02 23:22:20Z xlv $
*
* This file is part of the iText project.
* Copyright (c) 1998-2009 1T3XT BVBA
* Authors: Bruno Lowagie, Paulo Soares, et al.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3
* as published by the Free Software Foundation with the addition of the
* following permission added to Section 15 as permitted in Section 7(a):
* FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT,
* 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public License
* along with this program; if not, see http://www.gnu.org/licenses or write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA, 02110-1301 USA, or download the license from the following URL:
* http://itextpdf.com/terms-of-use/
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License.
*
* In accordance with Section 7(b) of the GNU Affero General Public License,
* you must retain the producer line in every PDF that is created or manipulated
* using iText.
*
* You can be released from the requirements of the license by purchasing
* a commercial license. Buying such a license is mandatory as soon as you
* develop commercial activities involving the iText software without
* disclosing the source code of your own applications.
* These activities include: offering paid services to customers as an ASP,
* serving PDFs on the fly in a web application, shipping iText with a closed
* source product.
*
* For more information, please contact iText Software Corp. at this
* address: sales@itextpdf.com
*/
/*
* This code was originally released in 2001 by SUN (see class
* com.sun.media.imageioimpl.plugins.bmp.BMPImageReader.java)
* using the BSD license in a specific wording. In a mail dating from
* January 23, 2008, Brian Burkhalter (@sun.com) gave us permission
* to use the code under the following version of the BSD license:
*
* Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistribution of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistribution in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* Neither the name of Sun Microsystems, Inc. or the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any
* kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
* WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
* EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
* NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
* USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
* ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
* CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
* REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
* INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed or intended for
* use in the design, construction, operation or maintenance of any
* nuclear facility.
*/
package com.itextpdf.text.pdf.codec;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import com.itextpdf.text.BadElementException;
import com.itextpdf.text.ExceptionConverter;
import com.itextpdf.text.Image;
import com.itextpdf.text.ImgRaw;
import com.itextpdf.text.Utilities;
import com.itextpdf.text.error_messages.MessageLocalization;
import com.itextpdf.text.pdf.PdfArray;
import com.itextpdf.text.pdf.PdfDictionary;
import com.itextpdf.text.pdf.PdfName;
import com.itextpdf.text.pdf.PdfNumber;
import com.itextpdf.text.pdf.PdfString;
/** Reads a BMP image. All types of BMP can be read.
* <p>
* It is based in the JAI codec.
*
* @author Paulo Soares
*/
public class BmpImage {
// BMP variables
private InputStream 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;
public HashMap<String, Object> properties = new HashMap<String, Object>();
private long xPelsPerMeter;
private long yPelsPerMeter;
// 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;
int width;
int height;
BmpImage(InputStream is, boolean noHeader, int size) throws IOException {
bitmapFileSize = size;
bitmapOffset = 0;
process(is, noHeader);
}
/** Reads a BMP from an url.
* @param url the url
* @throws IOException on error
* @return the image
*/
public static Image getImage(URL url) throws IOException {
InputStream is = null;
try {
is = url.openStream();
Image img = getImage(is);
img.setUrl(url);
return img;
}
finally {
if (is != null) {
is.close();
}
}
}
/** Reads a BMP from a stream. The stream is not closed.
* @param is the stream
* @throws IOException on error
* @return the image
*/
public static Image getImage(InputStream is) throws IOException {
return getImage(is, false, 0);
}
/** Reads a BMP from a stream. The stream is not closed.
* The BMP may not have a header and be considered as a plain DIB.
* @param is the stream
* @param noHeader true to process a plain DIB
* @param size the size of the DIB. Not used for a BMP
* @throws IOException on error
* @return the image
*/
public static Image getImage(InputStream is, boolean noHeader, int size) throws IOException {
BmpImage bmp = new BmpImage(is, noHeader, size);
try {
Image img = bmp.getImage();
img.setDpi((int)(bmp.xPelsPerMeter * 0.0254d + 0.5d), (int)(bmp.yPelsPerMeter * 0.0254d + 0.5d));
img.setOriginalType(Image.ORIGINAL_BMP);
return img;
}
catch (BadElementException be) {
throw new ExceptionConverter(be);
}
}
/** Reads a BMP from a file.
* @param file the file
* @throws IOException on error
* @return the image
*/
public static Image getImage(String file) throws IOException {
return getImage(Utilities.toURL(file));
}
/** Reads a BMP from a byte array.
* @param data the byte array
* @throws IOException on error
* @return the image
*/
public static Image getImage(byte data[]) throws IOException {
ByteArrayInputStream is = new ByteArrayInputStream(data);
Image img = getImage(is);
img.setOriginalData(data);
return img;
}
protected void process(InputStream stream, boolean noHeader) throws IOException {
if (noHeader || stream instanceof BufferedInputStream) {
inputStream = stream;
} else {
inputStream = new BufferedInputStream(stream);
}
if (!noHeader) {
// Start File Header
if (!(readUnsignedByte(inputStream) == 'B' &&
readUnsignedByte(inputStream) == 'M')) {
throw new RuntimeException(MessageLocalization.getComposedMessage("invalid.magic.value.for.bmp.file"));
}
// 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 (bitmapOffset == 0)
bitmapOffset = size;
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;
if (bitmapOffset == size) {
switch (imageType) {
case VERSION_2_1_BIT:
sizeOfPalette = 2 * 3;
break;
case VERSION_2_4_BIT:
sizeOfPalette = 16 * 3;
break;
case VERSION_2_8_BIT:
sizeOfPalette = 256 * 3;
break;
case VERSION_2_24_BIT:
sizeOfPalette = 0;
break;
}
bitmapOffset = size + sizeOfPalette;
}
readPalette(sizeOfPalette);
} else {
compression = readDWord(inputStream);
imageSize = readDWord(inputStream);
xPelsPerMeter = readLong(inputStream);
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
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));
}
// Read in the palette
int numberOfEntries = (int)((bitmapOffset-14-size) / 4);
int sizeOfPalette = numberOfEntries*4;
if (bitmapOffset == size) {
switch (imageType) {
case VERSION_3_1_BIT:
sizeOfPalette = (int)(colorsUsed == 0 ? 2 : colorsUsed) * 4;
break;
case VERSION_3_4_BIT:
sizeOfPalette = (int)(colorsUsed == 0 ? 16 : colorsUsed) * 4;
break;
case VERSION_3_8_BIT:
sizeOfPalette = (int)(colorsUsed == 0 ? 256 : colorsUsed) * 4;
break;
default:
sizeOfPalette = 0;
break;
}
bitmapOffset = size + sizeOfPalette;
}
readPalette(sizeOfPalette);
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;
readPalette(sizeOfPalette);
}
properties.put("bmp_version", "BMP v. 3.x NT");
break;
default:
throw new
RuntimeException("Invalid compression specified in BMP file.");
}
} 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);
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));
// Read in the palette
int numberOfEntries = (int)((bitmapOffset-14-size) / 4);
int sizeOfPalette = numberOfEntries*4;
if (bitmapOffset == size) {
switch (imageType) {
case VERSION_4_1_BIT:
sizeOfPalette = (int)(colorsUsed == 0 ? 2 : colorsUsed) * 4;
break;
case VERSION_4_4_BIT:
sizeOfPalette = (int)(colorsUsed == 0 ? 16 : colorsUsed) * 4;
break;
case VERSION_4_8_BIT:
sizeOfPalette = (int)(colorsUsed == 0 ? 256 : colorsUsed) * 4;
break;
default:
sizeOfPalette = 0;
break;
}
bitmapOffset = size + sizeOfPalette;
}
readPalette(sizeOfPalette);
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("Not implemented yet.");
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("Not implemented yet.");
}
} else {
properties.put("bmp_version", "BMP v. 5.x");
throw new
RuntimeException("BMP version 5 not implemented yet.");
}
}
if (height > 0) {
// bottom up image
isBottomUp = true;
} else {
// top down image
isBottomUp = false;
height = Math.abs(height);
}
// When number of bitsPerPixel is <= 8, we use IndexColorModel.
if (bitsPerPixel == 1 || bitsPerPixel == 4 || bitsPerPixel == 8) {
numBands = 1;
// Create IndexColorModel from the palette.
byte r[], g[], b[];
int sizep;
if (imageType == VERSION_2_1_BIT ||
imageType == VERSION_2_4_BIT ||
imageType == VERSION_2_8_BIT) {
sizep = palette.length/3;
if (sizep > 256) {
sizep = 256;
}
int off;
r = new byte[sizep];
g = new byte[sizep];
b = new byte[sizep];
for (int i=0; i<sizep; i++) {
off = 3 * i;
b[i] = palette[off];
g[i] = palette[off+1];
r[i] = palette[off+2];
}
} else {
sizep = palette.length/4;
if (sizep > 256) {
sizep = 256;
}
int off;
r = new byte[sizep];
g = new byte[sizep];
b = new byte[sizep];
for (int i=0; i<sizep; i++) {
off = 4 * i;
b[i] = palette[off];
g[i] = palette[off+1];
r[i] = palette[off+2];
}
}
} else if (bitsPerPixel == 16) {
numBands = 3;
} 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.
} else {
numBands = 3;
}
}
private byte[] getPalette(int group) {
if (palette == null)
return null;
byte np[] = new byte[palette.length / group * 3];
int e = palette.length / group;
for (int k = 0; k < e; ++k) {
int src = k * group;
int dest = k * 3;
np[dest + 2] = palette[src++];
np[dest + 1] = palette[src++];
np[dest] = palette[src];
}
return np;
}
private Image getImage() throws IOException, BadElementException {
byte bdata[] = null; // buffer for byte 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
return read1Bit(3);
case VERSION_2_4_BIT:
// no compression
return read4Bit(3);
case VERSION_2_8_BIT:
// no compression
return read8Bit(3);
case VERSION_2_24_BIT:
// no compression
bdata = new byte[width * height * 3];
read24Bit(bdata);
return new ImgRaw(width, height, 3, 8, bdata);
case VERSION_3_1_BIT:
// 1-bit images cannot be compressed.
return read1Bit(4);
case VERSION_3_4_BIT:
switch((int)compression) {
case BI_RGB:
return read4Bit(4);
case BI_RLE4:
return readRLE4();
default:
throw new
RuntimeException("Invalid compression specified for BMP file.");
}
case VERSION_3_8_BIT:
switch((int)compression) {
case BI_RGB:
return read8Bit(4);
case BI_RLE8:
return readRLE8();
default:
throw new
RuntimeException("Invalid compression specified for BMP file.");
}
case VERSION_3_24_BIT:
// 24-bit images are not compressed
bdata = new byte[width * height * 3];
read24Bit(bdata);
return new ImgRaw(width, height, 3, 8, bdata);
case VERSION_3_NT_16_BIT:
return read1632Bit(false);
case VERSION_3_NT_32_BIT:
return read1632Bit(true);
case VERSION_4_1_BIT:
return read1Bit(4);
case VERSION_4_4_BIT:
switch((int)compression) {
case BI_RGB:
return read4Bit(4);
case BI_RLE4:
return readRLE4();
default:
throw new
RuntimeException("Invalid compression specified for BMP file.");
}
case VERSION_4_8_BIT:
switch((int)compression) {
case BI_RGB:
return read8Bit(4);
case BI_RLE8:
return readRLE8();
default:
throw new
RuntimeException("Invalid compression specified for BMP file.");
}
case VERSION_4_16_BIT:
return read1632Bit(false);
case VERSION_4_24_BIT:
bdata = new byte[width * height * 3];
read24Bit(bdata);
return new ImgRaw(width, height, 3, 8, bdata);
case VERSION_4_32_BIT:
return read1632Bit(true);
}
return null;
}
private Image indexedModel(byte bdata[], int bpc, int paletteEntries) throws BadElementException {
Image img = new ImgRaw(width, height, 1, bpc, bdata);
PdfArray colorspace = new PdfArray();
colorspace.add(PdfName.INDEXED);
colorspace.add(PdfName.DEVICERGB);
byte np[] = getPalette(paletteEntries);
int len = np.length;
colorspace.add(new PdfNumber(len / 3 - 1));
colorspace.add(new PdfString(np));
PdfDictionary ad = new PdfDictionary();
ad.put(PdfName.COLORSPACE, colorspace);
img.setAdditional(ad);
return img;
}
private void readPalette(int sizeOfPalette) throws IOException {
if (sizeOfPalette == 0) {
return;
}
palette = new byte[sizeOfPalette];
int bytesRead = 0;
while (bytesRead < sizeOfPalette) {
int r = inputStream.read(palette, bytesRead, sizeOfPalette - bytesRead);
if (r < 0) {
throw new RuntimeException(MessageLocalization.getComposedMessage("incomplete.palette"));
}
bytesRead += r;
}
properties.put("palette", palette);
}
// Deal with 1 Bit images using IndexColorModels
private Image read1Bit(int paletteEntries) throws IOException, BadElementException {
byte bdata[] = new byte[(width + 7) / 8 * height];
int padding = 0;
int bytesPerScanline = (int)Math.ceil(width/8.0d);
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];
int bytesRead = 0;
while (bytesRead < imSize) {
bytesRead += inputStream.read(values, bytesRead,
imSize - bytesRead);
}
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);
}
}
return indexedModel(bdata, 1, paletteEntries);
}
// Method to read a 4 bit BMP image data
private Image read4Bit(int paletteEntries) throws IOException, BadElementException {
byte bdata[] = new byte[(width + 1) / 2 * height];
// Padding bytes at the end of each scanline
int padding = 0;
int bytesPerScanline = (int)Math.ceil(width/2.0d);
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];
int bytesRead = 0;
while (bytesRead < imSize) {
bytesRead += inputStream.read(values, bytesRead,
imSize - bytesRead);
}
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);
}
}
return indexedModel(bdata, 4, paletteEntries);
}
// Method to read 8 bit BMP image data
private Image read8Bit(int paletteEntries) throws IOException, BadElementException {
byte bdata[] = new byte[width * height];
// 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];
int bytesRead = 0;
while (bytesRead < imSize) {
bytesRead += inputStream.read(values, bytesRead, imSize - bytesRead);
}
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);
}
}
return indexedModel(bdata, 8, paletteEntries);
}
// 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 = (width * 3 + 3) / 4 * 4 * height;
// Read till we have the whole image
byte values[] = new byte[imSize];
try {
int bytesRead = 0;
while (bytesRead < imSize) {
int r = inputStream.read(values, bytesRead,
imSize - bytesRead);
if (r < 0)
break;
bytesRead += r;
}
} catch (IOException ioe) {
throw new ExceptionConverter(ioe);
}
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 + 2] = values[count++];
bdata[l + 1] = values[count++];
bdata[l] = values[count++];
l += 3;
}
}
} else {
count = -padding;
for (int i=0; i<height; i++) {
count += padding;
for (int j=0; j<width; j++) {
bdata[l + 2] = values[count++];
bdata[l + 1] = values[count++];
bdata[l] = values[count++];
l += 3;
}
}
}
}
private int findMask(int mask) {
int k = 0;
for (; k < 32; ++k) {
if ((mask & 1) == 1)
break;
mask >>>= 1;
}
return mask;
}
private int findShift(int mask) {
int k = 0;
for (; k < 32; ++k) {
if ((mask & 1) == 1)
break;
mask >>>= 1;
}
return k;
}
private Image read1632Bit(boolean is32) throws IOException, BadElementException {
int red_mask = findMask(redMask);
int red_shift = findShift(redMask);
int red_factor = red_mask + 1;
int green_mask = findMask(greenMask);
int green_shift = findShift(greenMask);
int green_factor = green_mask + 1;
int blue_mask = findMask(blueMask);
int blue_shift = findShift(blueMask);
int blue_factor = blue_mask + 1;
byte bdata[] = new byte[width * height * 3];
// Padding bytes at the end of each scanline
int padding = 0;
if (!is32) {
// 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;
int v;
if (isBottomUp) {
for (int i=height - 1; i >= 0; --i) {
l = width * 3 * i;
for (int j=0; j<width; j++) {
if (is32)
v = (int)readDWord(inputStream);
else
v = readWord(inputStream);
bdata[l++] = (byte)((v >>> red_shift & red_mask) * 256 / red_factor);
bdata[l++] = (byte)((v >>> green_shift & green_mask) * 256 / green_factor);
bdata[l++] = (byte)((v >>> blue_shift & blue_mask) * 256 / blue_factor);
}
for (int m=0; m<padding; m++) {
inputStream.read();
}
}
} else {
for (int i=0; i<height; i++) {
for (int j=0; j<width; j++) {
if (is32)
v = (int)readDWord(inputStream);
else
v = readWord(inputStream);
bdata[l++] = (byte)((v >>> red_shift & red_mask) * 256 / red_factor);
bdata[l++] = (byte)((v >>> green_shift & green_mask) * 256 / green_factor);
bdata[l++] = (byte)((v >>> blue_shift & blue_mask) * 256 / blue_factor);
}
for (int m=0; m<padding; m++) {
inputStream.read();
}
}
}
return new ImgRaw(width, height, 3, 8, bdata);
}
private Image readRLE8() throws IOException, BadElementException {
// If imageSize field is not provided, calculate it.
int imSize = (int)imageSize;
if (imSize == 0) {
imSize = (int)(bitmapFileSize - bitmapOffset);
}
// Read till we have the whole image
byte values[] = new byte[imSize];
int bytesRead = 0;
while (bytesRead < imSize) {
bytesRead += inputStream.read(values, bytesRead,
imSize - bytesRead);
}
// Since data is compressed, decompress it
byte val[] = decodeRLE(true, 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);
byte temp[] = new byte[val.length];
int bytesPerScanline = width;
for (int i=0; i<height; i++) {
System.arraycopy(val,
imSize - (i+1)*bytesPerScanline,
temp,
i*bytesPerScanline, bytesPerScanline);
}
val = temp;
}
return indexedModel(val, 8, 4);
}
private Image readRLE4() throws IOException, BadElementException {
// If imageSize field is not specified, calculate it.
int imSize = (int)imageSize;
if (imSize == 0) {
imSize = (int)(bitmapFileSize - bitmapOffset);
}
// Read till we have the whole image
byte values[] = new byte[imSize];
int bytesRead = 0;
while (bytesRead < imSize) {
bytesRead += inputStream.read(values, bytesRead,
imSize - bytesRead);
}
// Decompress the RLE4 compressed data.
byte val[] = decodeRLE(false, values);
// Invert it as it is bottom up format.
if (isBottomUp) {
byte inverted[] = val;
val = new byte[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++];
}
}
}
int stride = (width + 1) / 2;
byte bdata[] = new byte[stride * height];
int ptr = 0;
int sh = 0;
for (int h = 0; h < height; ++h) {
for (int w = 0; w < width; ++w) {
if ((w & 1) == 0)
bdata[sh + w / 2] = (byte)(val[ptr++] << 4);
else
bdata[sh + w / 2] |= (byte)(val[ptr++] & 0x0f);
}
sh += stride;
}
return indexedModel(bdata, 4, 4);
}
private byte[] decodeRLE(boolean is8, byte values[]) {
byte val[] = new byte[width * height];
try {
int ptr = 0;
int x = 0;
int q = 0;
for (int y = 0; y < height && ptr < values.length;) {
int count = values[ptr++] & 0xff;
if (count != 0) {
// encoded mode
int bt = values[ptr++] & 0xff;
if (is8) {
for (int i = count; i != 0; --i) {
val[q++] = (byte)bt;
}
}
else {
for (int i = 0; i < count; ++i) {
val[q++] = (byte)((i & 1) == 1 ? bt & 0x0f : bt >>> 4 & 0x0f);
}
}
x += count;
}
else {
// escape mode
count = values[ptr++] & 0xff;
if (count == 1)
break;
switch (count) {
case 0:
x = 0;
++y;
q = y * width;
break;
case 2:
// delta mode
x += values[ptr++] & 0xff;
y += values[ptr++] & 0xff;
q = y * width + x;
break;
default:
// absolute mode
if (is8) {
for (int i = count; i != 0; --i)
val[q++] = (byte)(values[ptr++] & 0xff);
}
else {
int bt = 0;
for (int i = 0; i < count; ++i) {
if ((i & 1) == 0)
bt = values[ptr++] & 0xff;
val[q++] = (byte)((i & 1) == 1 ? bt & 0x0f : bt >>> 4 & 0x0f);
}
}
x += count;
// read pad byte
if (is8) {
if ((count & 1) == 1)
++ptr;
}
else {
if ((count & 3) == 1 || (count & 3) == 2)
++ptr;
}
break;
}
}
}
}
catch (RuntimeException e) {
//empty on purpose
}
return val;
}
// 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 = 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);
}
}