com.lowagie.text.pdf.codec.BmpImage Maven / Gradle / Ivy
/*
* Copyright 2003-2008 by Paulo Soares.
*
* The contents of this file are subject to the Mozilla Public License Version 1.1
* (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the License.
*
* The Original Code is 'iText, a free JAVA-PDF library'.
*
* The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
* the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
* All Rights Reserved.
* Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
* are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
*
* Contributor(s): all the names of the contributors are added in the source code
* where applicable.
*
* Alternatively, the contents of this file may be used under the terms of the
* LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
* provisions of LGPL are applicable instead of those above. If you wish to
* allow use of your version of this file only under the terms of the LGPL
* License and not to allow others to use your version of this file under
* the MPL, indicate your decision by deleting the provisions above and
* replace them with the notice and other provisions required by the LGPL.
* If you do not delete the provisions above, a recipient may use your version
* of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the MPL as stated above or under the terms of the GNU
* Library General Public License as published by the Free Software Foundation;
* either version 2 of the License, or any later version.
*
* This library 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 Library general Public License for more
* details.
*
* If you didn't download this code from the following link, you should check if
* you aren't using an obsolete version:
* http://www.lowagie.com/iText/
*
* 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.lowagie.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.lowagie.text.BadElementException;
import com.lowagie.text.ExceptionConverter;
import com.lowagie.text.Image;
import com.lowagie.text.ImgRaw;
import com.lowagie.text.Utilities;
import com.lowagie.text.pdf.PdfArray;
import com.lowagie.text.pdf.PdfDictionary;
import com.lowagie.text.pdf.PdfName;
import com.lowagie.text.pdf.PdfNumber;
import com.lowagie.text.pdf.PdfString;
/**
* Reads a BMP image. All types of BMP can be read.
*
* It is based in the JAI codec.
*
* @author Paulo Soares ([email protected])
*/
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;
@SuppressWarnings("unused")
private int numBands;
private boolean isBottomUp;
private int bitsPerPixel;
private int redMask, greenMask, blueMask, alphaMask;
public HashMap properties = new HashMap();
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("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", Integer.valueOf(planes));
properties.put("bits_per_pixel", Integer.valueOf(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", Long.valueOf(xPelsPerMeter));
properties.put("y_pixels_per_meter", Long.valueOf(yPelsPerMeter));
properties.put("colors_used", Long.valueOf(colorsUsed));
properties.put("colors_important", Long.valueOf(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", Integer.valueOf(redMask));
properties.put("green_mask", Integer.valueOf(greenMask));
properties.put("blue_mask", Integer.valueOf(blueMask));
} else if (bitsPerPixel == 32) {
imageType = VERSION_3_NT_32_BIT;
redMask = 0x00FF0000;
greenMask = 0x0000FF00;
blueMask = 0x000000FF;
properties.put("red_mask", Integer.valueOf(redMask));
properties.put("green_mask", Integer.valueOf(greenMask));
properties.put("blue_mask", Integer.valueOf(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", Integer.valueOf(redMask));
properties.put("green_mask", Integer.valueOf(greenMask));
properties.put("blue_mask", Integer.valueOf(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", Integer.valueOf(redMask));
properties.put("green_mask", Integer.valueOf(greenMask));
properties.put("blue_mask", Integer.valueOf(blueMask));
properties.put("alpha_mask", Integer.valueOf(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", Integer.valueOf(redX));
properties.put("redY", Integer.valueOf(redY));
properties.put("redZ", Integer.valueOf(redZ));
properties.put("greenX", Integer.valueOf(greenX));
properties.put("greenY", Integer.valueOf(greenY));
properties.put("greenZ", Integer.valueOf(greenZ));
properties.put("blueX", Integer.valueOf(blueX));
properties.put("blueY", Integer.valueOf(blueY));
properties.put("blueZ", Integer.valueOf(blueZ));
properties.put("gamma_red", Long.valueOf(gammaRed));
properties.put("gamma_green", Long.valueOf(gammaGreen));
properties.put("gamma_blue", Long.valueOf(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("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
@SuppressWarnings("unused")
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);
}
}