poi/src/scratchpad/src/org/apache/poi/hwmf/record/WmfBitmapDib.java

425 lines
18 KiB
Java

package org.apache.poi.hwmf.record;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;
/**
* The DeviceIndependentBitmap Object defines an image in device-independent bitmap (DIB) format.
*/
public class WmfBitmapDib {
public static enum BitCount {
/**
* The image SHOULD be in either JPEG or PNG format. <6> Neither of these formats includes
* a color table, so this value specifies that no color table is present. See [JFIF] and [RFC2083]
* for more information concerning JPEG and PNG compression formats.
*/
BI_BITCOUNT_0(0x0000),
/**
* Each pixel in the bitmap is represented by a single bit. If the bit is clear, the pixel is displayed
* with the color of the first entry in the color table; if the bit is set, the pixel has the color of the
* second entry in the table.
*/
BI_BITCOUNT_1(0x0001),
/**
* Each pixel in the bitmap is represented by a 4-bit index into the color table, and each byte
* contains 2 pixels.
*/
BI_BITCOUNT_2(0x0004),
/**
* Each pixel in the bitmap is represented by an 8-bit index into the color table, and each byte
* contains 1 pixel.
*/
BI_BITCOUNT_3(0x0008),
/**
* Each pixel in the bitmap is represented by a 16-bit value.
* <br/>
* If the Compression field of the BitmapInfoHeader Object is BI_RGB, the Colors field of DIB
* is NULL. Each WORD in the bitmap array represents a single pixel. The relative intensities of
* red, green, and blue are represented with 5 bits for each color component. The value for blue
* is in the least significant 5 bits, followed by 5 bits each for green and red. The most significant
* bit is not used. The color table is used for optimizing colors on palette-based devices, and
* contains the number of entries specified by the ColorUsed field of the BitmapInfoHeader
* Object.
* <br/>
* If the Compression field of the BitmapInfoHeader Object is BI_BITFIELDS, the Colors field
* contains three DWORD color masks that specify the red, green, and blue components,
* respectively, of each pixel. Each WORD in the bitmap array represents a single pixel.
* <br/>
* When the Compression field is set to BI_BITFIELDS, bits set in each DWORD mask MUST be
* contiguous and SHOULD NOT overlap the bits of another mask.
*/
BI_BITCOUNT_4(0x0010),
/**
* The bitmap has a maximum of 2^24 colors, and the Colors field of DIB is
* NULL. Each 3-byte triplet in the bitmap array represents the relative intensities of blue, green,
* and red, respectively, for a pixel. The Colors color table is used for optimizing colors used on
* palette-based devices, and MUST contain the number of entries specified by the ColorUsed
* field of the BitmapInfoHeader Object.
*/
BI_BITCOUNT_5(0x0018),
/**
* The bitmap has a maximum of 2^24 colors.
* <br/>
* If the Compression field of the BitmapInfoHeader Object is set to BI_RGB, the Colors field
* of DIB is set to NULL. Each DWORD in the bitmap array represents the relative intensities of
* blue, green, and red, respectively, for a pixel. The high byte in each DWORD is not used. The
* Colors color table is used for optimizing colors used on palette-based devices, and MUST
* contain the number of entries specified by the ColorUsed field of the BitmapInfoHeader
* Object.
* <br/>
* If the Compression field of the BitmapInfoHeader Object is set to BI_BITFIELDS, the Colors
* field contains three DWORD color masks that specify the red, green, and blue components,
* respectively, of each pixel. Each DWORD in the bitmap array represents a single pixel.
* <br/>
* When the Compression field is set to BI_BITFIELDS, bits set in each DWORD mask must be
* contiguous and should not overlap the bits of another mask. All the bits in the pixel do not
* need to be used.
*/
BI_BITCOUNT_6(0x0020);
int flag;
BitCount(int flag) {
this.flag = flag;
}
static BitCount valueOf(int flag) {
for (BitCount bc : values()) {
if (bc.flag == flag) return bc;
}
return null;
}
}
public static enum Compression {
/**
* The bitmap is in uncompressed red green blue (RGB) format that is not compressed
* and does not use color masks.
*/
BI_RGB(0x0000),
/**
* An RGB format that uses run-length encoding (RLE) compression for bitmaps
* with 8 bits per pixel. The compression uses a 2-byte format consisting of a count byte
* followed by a byte containing a color index.
*/
BI_RLE8(0x0001),
/**
* An RGB format that uses RLE compression for bitmaps with 4 bits per pixel. The
* compression uses a 2-byte format consisting of a count byte followed by two word-length
* color indexes.
*/
BI_RLE4(0x0002),
/**
* The bitmap is not compressed and the color table consists of three DWORD
* color masks that specify the red, green, and blue components, respectively, of each pixel.
* This is valid when used with 16 and 32-bits per pixel bitmaps.
*/
BI_BITFIELDS(0x0003),
/**
* The image is a JPEG image, as specified in [JFIF]. This value SHOULD only be used in
* certain bitmap operations, such as JPEG pass-through. The application MUST query for the
* pass-through support, since not all devices support JPEG pass-through. Using non-RGB
* bitmaps MAY limit the portability of the metafile to other devices. For instance, display device
* contexts generally do not support this pass-through.
*/
BI_JPEG(0x0004),
/**
* The image is a PNG image, as specified in [RFC2083]. This value SHOULD only be
* used certain bitmap operations, such as JPEG/PNG pass-through. The application MUST query
* for the pass-through support, because not all devices support JPEG/PNG pass-through. Using
* non-RGB bitmaps MAY limit the portability of the metafile to other devices. For instance,
* display device contexts generally do not support this pass-through.
*/
BI_PNG(0x0005),
/**
* The image is an uncompressed CMYK format.
*/
BI_CMYK(0x000B),
/**
* A CMYK format that uses RLE compression for bitmaps with 8 bits per pixel.
* The compression uses a 2-byte format consisting of a count byte followed by a byte containing
* a color index.
*/
BI_CMYKRLE8(0x000C),
/**
* A CMYK format that uses RLE compression for bitmaps with 4 bits per pixel.
* The compression uses a 2-byte format consisting of a count byte followed by two word-length
* color indexes.
*/
BI_CMYKRLE4(0x000D);
int flag;
Compression(int flag) {
this.flag = flag;
}
static Compression valueOf(int flag) {
for (Compression c : values()) {
if (c.flag == flag) return c;
}
return null;
}
}
int headerSize;
int headerWidth;
int headerHeight;
int headerPlanes;
BitCount headerBitCount;
Compression headerCompression;
long headerImageSize = -1;
int headerXPelsPerMeter = -1;
int headerYPelsPerMeter = -1;
long headerColorUsed = -1;
long headerColorImportant = -1;
Color colorTable[];
int colorMaskRed=0,colorMaskGreen=0,colorMaskBlue=0;
public int init(LittleEndianInputStream leis) throws IOException {
int size = 0;
size += readHeader(leis);
size += readColors(leis);
int size2;
switch (headerBitCount) {
default:
case BI_BITCOUNT_0:
throw new RuntimeException("JPG and PNG formats aren't supported yet.");
case BI_BITCOUNT_1:
case BI_BITCOUNT_2:
case BI_BITCOUNT_3:
size2 = readBitmapIndexed(leis);
break;
case BI_BITCOUNT_4:
case BI_BITCOUNT_5:
case BI_BITCOUNT_6:
size2 = readBitmapDirect(leis);
break;
}
assert( headerSize != 0x0C || ((((headerWidth * headerPlanes * headerBitCount.flag + 31) & ~31) / 8) * Math.abs(headerHeight)) == size2);
assert ( headerSize == 0x0C || headerImageSize == size2 );
size += size2;
return size;
}
protected int readHeader(LittleEndianInputStream leis) throws IOException {
int size = 0;
/**
* DIBHeaderInfo (variable): Either a BitmapCoreHeader Object or a
* BitmapInfoHeader Object that specifies information about the image.
*
* The first 32 bits of this field is the HeaderSize value.
* If it is 0x0000000C, then this is a BitmapCoreHeader; otherwise, this is a BitmapInfoHeader.
*/
headerSize = leis.readInt();
size += LittleEndianConsts.INT_SIZE;
// BitmapCoreHeader
// A 16-bit unsigned integer that defines the width of the DIB, in pixels.
headerWidth = leis.readUShort();
// A 16-bit unsigned integer that defines the height of the DIB, in pixels.
headerHeight = leis.readUShort();
// A 16-bit unsigned integer that defines the number of planes for the target
// device. This value MUST be 0x0001.
headerPlanes = leis.readUShort();
// A 16-bit unsigned integer that defines the format of each pixel, and the
// maximum number of colors in the DIB.
headerBitCount = BitCount.valueOf(leis.readUShort());
size += 4*LittleEndianConsts.SHORT_SIZE;
if (headerSize > 0x0C) {
// BitmapInfoHeader
// A 32-bit unsigned integer that defines the compression mode of the
// DIB.
// This value MUST NOT specify a compressed format if the DIB is a top-down bitmap,
// as indicated by the Height value.
headerCompression = Compression.valueOf((int)leis.readUInt());
// A 32-bit unsigned integer that defines the size, in bytes, of the image.
// If the Compression value is BI_RGB, this value SHOULD be zero and MUST be ignored.
// If the Compression value is BI_JPEG or BI_PNG, this value MUST specify the size of the JPEG
// or PNG image buffer, respectively.
headerImageSize = leis.readUInt();
// A 32-bit signed integer that defines the horizontal resolution,
// in pixels-per-meter, of the target device for the DIB.
headerXPelsPerMeter = leis.readInt();
// A 32-bit signed integer that defines the vertical resolution,
headerYPelsPerMeter = leis.readInt();
// A 32-bit unsigned integer that specifies the number of indexes in the
// color table used by the DIB
// in pixelsper-meter, of the target device for the DIB.
headerColorUsed = leis.readUInt();
// A 32-bit unsigned integer that defines the number of color indexes that are
// required for displaying the DIB. If this value is zero, all color indexes are required.
headerColorImportant = leis.readUInt();
size += 6*LittleEndianConsts.INT_SIZE;
}
return size;
}
protected int readColors(LittleEndianInputStream leis) throws IOException {
switch (headerBitCount) {
default:
case BI_BITCOUNT_0:
// no table
return 0;
case BI_BITCOUNT_1:
// 2 colors
return readRGBQuad(leis, 2);
case BI_BITCOUNT_2:
// 16 colors
return readRGBQuad(leis, 16);
case BI_BITCOUNT_3:
// 256 colors
return readRGBQuad(leis, 256);
case BI_BITCOUNT_5:
colorMaskRed=0xFF;
colorMaskGreen=0xFF;
colorMaskBlue=0xFF;
return 0;
case BI_BITCOUNT_4:
if (headerCompression == Compression.BI_RGB) {
colorMaskBlue = 0x1F;
colorMaskGreen = 0x1F<<5;
colorMaskRed = 0x1F<<10;
return 0;
} else {
assert(headerCompression == Compression.BI_BITFIELDS);
colorMaskBlue = leis.readInt();
colorMaskGreen = leis.readInt();
colorMaskRed = leis.readInt();
return 3*LittleEndianConsts.INT_SIZE;
}
case BI_BITCOUNT_6:
if (headerCompression == Compression.BI_RGB) {
colorMaskBlue = colorMaskGreen = colorMaskRed = 0xFF;
return 0;
} else {
assert(headerCompression == Compression.BI_BITFIELDS);
colorMaskBlue = leis.readInt();
colorMaskGreen = leis.readInt();
colorMaskRed = leis.readInt();
return 3*LittleEndianConsts.INT_SIZE;
}
}
}
protected int readRGBQuad(LittleEndianInputStream leis, int count) throws IOException {
int size = 0;
List<Color> colorList = new ArrayList<Color>();
for (int i=0; i<count; i++) {
int blue = leis.readUByte();
int green = leis.readUByte();
int red = leis.readUByte();
@SuppressWarnings("unused")
int reserved = leis.readUByte();
Color c = new Color(red, green, blue);
colorList.add(c);
size += 4 * LittleEndianConsts.BYTE_SIZE;
}
colorTable = colorList.toArray(new Color[colorList.size()]);
return size;
}
protected int readBitmapIndexed(LittleEndianInputStream leis) throws IOException {
assert(colorTable != null);
byte r[] = new byte[colorTable.length];
byte g[] = new byte[colorTable.length];
byte b[] = new byte[colorTable.length];
for (int i=0; i<colorTable.length; i++) {
r[i] = (byte)colorTable[i].getRed();
g[i] = (byte)colorTable[i].getGreen();
b[i] = (byte)colorTable[i].getBlue();
}
int bits = 32-Integer.numberOfLeadingZeros(colorTable.length);
IndexColorModel cm = new IndexColorModel(bits,colorTable.length,r,g,b);
BufferedImage bi = new BufferedImage(headerWidth, headerHeight, BufferedImage.TYPE_BYTE_INDEXED, cm);
WritableRaster wr = bi.getRaster();
int pixelCount = headerWidth*headerHeight;
int size = 0;
for (int pixel=0; pixel<pixelCount; size++) {
int v = leis.readUByte();
switch (headerBitCount) {
default:
throw new RuntimeException("invalid bitcount for indexed image");
case BI_BITCOUNT_1:
for (int j=0; j<8 && pixel<pixelCount; j++,pixel++) {
wr.setSample(pixel/headerWidth,pixel%headerWidth,0,(v>>(7-j))&1);
}
break;
case BI_BITCOUNT_2:
wr.setSample(pixel/headerWidth, pixel%headerWidth, 0, (v>>4)&15);
pixel++;
if (pixel<pixelCount) {
wr.setSample(pixel/headerWidth, pixel%headerWidth, 0, v&15);
pixel++;
}
break;
case BI_BITCOUNT_3:
wr.setSample(pixel/headerWidth, pixel%headerWidth, 0, v);
pixel++;
break;
}
}
return size;
}
protected int readBitmapDirect(LittleEndianInputStream leis) throws IOException {
assert(colorTable == null);
BufferedImage bi = new BufferedImage(headerWidth, headerHeight, BufferedImage.TYPE_INT_RGB);
WritableRaster wr = bi.getRaster();
int bitShiftRed=0,bitShiftGreen=0,bitShiftBlue=0;
if (headerCompression == Compression.BI_BITFIELDS) {
bitShiftGreen = 32-Integer.numberOfLeadingZeros(this.colorMaskBlue);
bitShiftRed = 32-Integer.numberOfLeadingZeros(this.colorMaskGreen);
}
int pixelCount = headerWidth*headerHeight;
int size = 0;
int rgb[] = new int[3];
for (int pixel=0; pixel<pixelCount; pixel++) {
int v;
switch (headerBitCount) {
default:
throw new RuntimeException("invalid bitcount for indexed image");
case BI_BITCOUNT_4:
v = leis.readUShort();
rgb[0] = (v & colorMaskRed) >> bitShiftRed;
rgb[1] = (v & colorMaskGreen) >> bitShiftGreen;
rgb[2] = (v & colorMaskBlue) >> bitShiftBlue;
size += LittleEndianConsts.SHORT_SIZE;
break;
case BI_BITCOUNT_5:
rgb[2] = leis.readUByte();
rgb[1] = leis.readUByte();
rgb[0] = leis.readUByte();
size += 3*LittleEndianConsts.BYTE_SIZE;
break;
case BI_BITCOUNT_6:
v = leis.readInt();
rgb[0] = (v & colorMaskRed) >> bitShiftRed;
rgb[1] = (v & colorMaskGreen) >> bitShiftGreen;
rgb[2] = (v & colorMaskBlue) >> bitShiftBlue;
size += LittleEndianConsts.INT_SIZE;
break;
}
wr.setPixel(pixel/headerWidth,pixel%headerWidth,rgb);
}
return size;
}
}