org.freehep.graphicsio.emf.EMFImageLoader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of freehep-graphicsio-emf Show documentation
Show all versions of freehep-graphicsio-emf Show documentation
FreeHEP Enhanced Metafile Format Driver
package org.freehep.graphicsio.emf;
import org.freehep.graphicsio.emf.gdi.BitmapInfoHeader;
import org.freehep.graphicsio.emf.gdi.BlendFunction;
import java.awt.image.BufferedImage;
import java.awt.Color;
import java.io.IOException;
import java.util.Arrays;
/**
* this class creates a BufferedImage from EMF imaga data stored in
* a byte[].
*
* @author Steffen Greiffenberg
* @version $Id: freehep-graphicsio-emf/src/main/java/org/freehep/graphicsio/emf/EMFImageLoader.java 10ec7516e3ce 2007/02/06 18:42:34 duns $
*/
public class EMFImageLoader {
/**
* creates a BufferedImage from an EMFInputStream using
* BitmapInfoHeader data
*
* @param bmi BitmapInfoHeader storing Bitmap informations
* @param width expected image width
* @param height expected image height
* @param emf EMF stream
* @param len length of image data
* @param blendFunction contains values for transparency
* @return BufferedImage or null
* @throws java.io.IOException thrown by EMFInputStream
*/
public static BufferedImage readImage(
BitmapInfoHeader bmi,
int width,
int height,
EMFInputStream emf,
int len,
BlendFunction blendFunction) throws IOException {
// 0 Windows 98/Me, Windows 2000/XP: The number of bits-per-pixel
// is specified or is implied by the JPEG or PNG format.
if (bmi.getBitCount() == 1) {
// 1 The bitmap is monochrome, and the bmiColors
// member of BITMAPINFO contains two entries. Each
// bit in the bitmap array represents a pixel. If
// the bit is clear, the pixel is displayed with
// the color of the first entry in the bmiColors
// table; if the bit is set, the pixel has the color
// of the second entry in the table.
// byte[] bytes = emf.readByte(len);
int blue = emf.readUnsignedByte();
int green = emf.readUnsignedByte();
int red = emf.readUnsignedByte();
/*int unused =*/ emf.readUnsignedByte();
int color1 = new Color(red, green, blue).getRGB();
blue = emf.readUnsignedByte();
green = emf.readUnsignedByte();
red = emf.readUnsignedByte();
/*unused = */ emf.readUnsignedByte();
int color2 = new Color(red, green, blue).getRGB();
BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
int[] data = emf.readUnsignedByte(len - 8);
// TODO: this is highly experimental and does
// not work for the tested examples
int strangeOffset = width % 8;
if (strangeOffset != 0) {
strangeOffset = 8 - strangeOffset;
}
// iterator for pixel data
int pixel = 0;
// mask for getting the bits from a pixel data byte
int[] mask = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
// image data are swapped compared to java standard
for (int y = height - 1; y > -1; y--) {
for (int x = 0; x < width; x++) {
int pixelDataGroup = data[pixel / 8];
int pixelData = pixelDataGroup & mask[pixel % 8];
pixel ++;
if (pixelData > 0) {
result.setRGB(x, y, color2);
} else {
result.setRGB(x, y, color1);
}
}
// add the extra width
pixel = pixel + strangeOffset;
}
/* for debugging: shows every loaded image
javax.swing.JFrame f = new javax.swing.JFrame("test");
f.getContentPane().setBackground(Color.green);
f.getContentPane().setLayout(
new java.awt.BorderLayout(0, 0));
f.getContentPane().add(
java.awt.BorderLayout.CENTER,
new javax.swing.JLabel(
new javax.swing.ImageIcon(result)));
f.setSize(new java.awt.Dimension(width + 20, height + 20));
f.setVisible(true);*/
return result;
} else if ((bmi.getBitCount() == 8) &&
(bmi.getCompression() == EMFConstants.BI_RGB)) {
// 8 The bitmap has a maximum of 256 colors, and the bmiColors member
// of BITMAPINFO contains up to 256 entries. In this case, each byte in
// the array represents a single pixel.
// TODO has to be done in BitMapInfoHeader?
// read the color table
int colorsUsed = bmi.getClrUsed();
// typedef struct tagRGBQUAD {
// BYTE rgbBlue;
// BYTE rgbGreen;
// BYTE rgbRed;
// BYTE rgbReserved;
// } RGBQUAD;
int[] colors = emf.readUnsignedByte(colorsUsed * 4);
// data a indexes to a certain color in the colortable.
// Each byte represents a pixel
int[] data = emf.readUnsignedByte(len - (colorsUsed * 4));
// convert it to a color table
int[] colorTable = new int[256];
// iterator for color data
int color = 0;
for (int i = 0; i < colorsUsed; i++, color = i * 4) {
colorTable[i] = new Color(
colors[color + 2],
colors[color + 1],
colors[color]).getRGB();
}
// fill with black to avoid ArrayIndexOutOfBoundExceptions;
// somme images seem to use more colors than stored in ClrUsed
if (colorsUsed < 256) {
Arrays.fill(colorTable, colorsUsed, 256, 0);
}
// don't know why, but the width has to be adjusted ...
// it took more than an hour to determine the strangeOffset
int strangeOffset = width % 4;
if (strangeOffset != 0) {
strangeOffset = 4 - strangeOffset;
}
// create the image
BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// iterator for pixel data
int pixel = 0;
// image data are swapped compared to java standard
for (int y = height - 1; y > -1; y--) {
for (int x = 0; x < width; x++) {
result.setRGB(x, y, colorTable[data[pixel++]]);
}
// add the extra width
pixel = pixel + strangeOffset;
}
return result;
}
// The bitmap has a maximum of 2^16 colors. If the biCompression member
// of the BITMAPINFOHEADER is BI_RGB, the bmiColors member of BITMAPINFO is
// NULL.
else if ((bmi.getBitCount() == 16) &&
(bmi.getCompression() == EMFConstants.BI_RGB)) {
// Each WORD in the bitmap array represents a single pixel. The
// relative intensities of red, green, and blue are represented with
// five bits for each color component. The value for blue is in the least
// significant five bits, followed by five bits each for green and red.
// The most significant bit is not used. The bmiColors color table is used
// for optimizing colors used on palette-based devices, and must contain
// the number of entries specified by the biClrUsed member of the
// BITMAPINFOHEADER.
int[] data = emf.readDWORD(len / 4);
// don't know why, by the width has to be the half ...
// maybe that has something to do with sie HALFTONE rendering setting.
width = (width + (width % 2)) / 2;
// to avoid ArrayIndexOutOfBoundExcesptions
height = data.length / width / 2;
// create a non transparent image
BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// found no sample and color model to mak this work
// tag.image.setRGB(0, 0, tag.widthSrc, tag.heightSrc, data, 0, 0);
// used in the loop
int off = 0;
int pixel, neighbor;
// image data are swapped compared to java standard
for (int y = height - 1; y > -1; y--, off = off + width) {
for (int x = 0; x < width; x++) {
neighbor = data[off + width];
pixel = data[off++];
// compute the average of the pixel and it's neighbor
// and set the reulting color values
result.setRGB(x, y, new Color(
// 0xF800 = 2 * 0x7C00
(float)((pixel & 0x7C00) + (neighbor & 0x7C00)) / 0xF800,
(float)((pixel & 0x3E0) + (neighbor & 0x3E0)) / 0x7C0,
(float)((pixel & 0x1F) + (neighbor & 0x1F)) / 0x3E).getRGB());
}
}
/* for debugging: shows every loaded image
javax.swing.JFrame f = new javax.swing.JFrame("test");
f.getContentPane().setBackground(Color.green);
f.getContentPane().setLayout(
new java.awt.BorderLayout(0, 0));
f.getContentPane().add(
java.awt.BorderLayout.CENTER,
new javax.swing.JLabel(
new javax.swing.ImageIcon(result)));
f.pack();
f.setVisible(true);*/
return result;
}
// The bitmap has a maximum of 2^32 colors. If the biCompression member of the
// BITMAPINFOHEADER is BI_RGB, the bmiColors member of BITMAPINFO is NULL.
else if ((bmi.getBitCount() == 32) &&
(bmi.getCompression() == EMFConstants.BI_RGB)) {
// 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 bmiColors color table is used for optimizing colors used on
// palette-based devices, and must contain the number of entries specified
// by the biClrUsed member of the BITMAPINFOHEADER.
width = (width + (width % 20)) / 20;
height = (height + (height % 20)) / 20;
// create a transparent image
BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
// read the image data
int[] data = emf.readDWORD(len / 4);
// used to iterate the pixels later
int off = 0;
int pixel;
int alpha;
// The SourceConstantaAlpha member of BLENDFUNCTION specifies an alpha transparency
// value to be used on the entire source bitmap. The SourceConstantAlpha value is
// combined with any per-pixel alpha values. If SourceConstantAlpha is 0, it is
// assumed that the image is transparent. Set the SourceConstantAlpha value to 255
// (which indicates that the image is opaque) when you only want to use per-pixel
// alpha values.
int sourceConstantAlpha = blendFunction.getSourceConstantAlpha();
if (blendFunction.getAlphaFormat() != EMFConstants.AC_SRC_ALPHA) {
// If the source bitmap has no per-pixel alpha value (that is, AC_SRC_ALPHA is not
// set), the SourceConstantAlpha value determines the blend of the source and
// destination bitmaps, as shown in the following table. Note that SCA is used
// for SourceConstantAlpha here. Also, SCA is divided by 255 because it has a
// value that ranges from 0 to 255.
// Dst.Red = Src.Red * (SCA/255.0) + Dst.Red * (1.0 - (SCA/255.0))
// Dst.Green = Src.Green * (SCA/255.0) + Dst.Green * (1.0 - (SCA/255.0))
// Dst.Blue = Src.Blue * (SCA/255.0) + Dst.Blue * (1.0 - (SCA/255.0))
// If the destination bitmap has an alpha channel, then the blend is as follows.
// Dst.Alpha = Src.Alpha * (SCA/255.0) + Dst.Alpha * (1.0 - (SCA/255.0))
for (int y = height - 1; y > -1 && off < data.length; y--) {
for (int x = 0; x < width && off < data.length; x++) {
pixel = data[off++];
result.setRGB(x, y, new Color(
(pixel & 0xFF0000) >> 16,
(pixel & 0xFF00) >> 8,
(pixel & 0xFF),
// TODO not tested
sourceConstantAlpha
).getRGB());
}
}
}
// When the BlendOp parameter is AC_SRC_OVER , the source bitmap is placed over
// the destination bitmap based on the alpha values of the source pixels.
else {
// If the source bitmap does not use SourceConstantAlpha (that is, it equals
// 0xFF), the per-pixel alpha determines the blend of the source and destination
// bitmaps, as shown in the following table.
if (sourceConstantAlpha == 0xFF) {
// Dst.Red = Src.Red + (1 - Src.Alpha) * Dst.Red
// Dst.Green = Src.Green + (1 - Src.Alpha) * Dst.Green
// Dst.Blue = Src.Blue + (1 - Src.Alpha) * Dst.Blue
// If the destination bitmap has an alpha channel, then the blend is as follows.
// Dest.alpha = Src.Alpha + (1 - SrcAlpha) * Dst.Alpha
// image data are swapped compared to java standard
for (int y = height - 1; y > -1 && off < data.length; y--) {
for (int x = 0; x < width && off < data.length; x++) {
pixel = data[off++];
alpha = (pixel & 0xFF000000) >> 24;
if (alpha == -1) {
alpha = 0xFF;
}
result.setRGB(x, y, new Color(
(pixel & 0xFF0000) >> 16,
(pixel & 0xFF00) >> 8,
(pixel & 0xFF),
alpha
).getRGB());
}
}
}
// If the source has both the SourceConstantAlpha (that is, it is not 0xFF)
// and per-pixel alpha, the source is pre-multiplied by the SourceConstantAlpha
// and then the blend is based on the per-pixel alpha. The following tables show
// this. Note that SourceConstantAlpha is divided by 255 because it has a value
// that ranges from 0 to 255.
else {
// Src.Red = Src.Red * SourceConstantAlpha / 255.0;
// Src.Green = Src.Green * SourceConstantAlpha / 255.0;
// Src.Blue = Src.Blue * SourceConstantAlpha / 255.0;
// Src.Alpha = Src.Alpha * SourceConstantAlpha / 255.0;
// Dst.Red = Src.Red + (1 - Src.Alpha) * Dst.Red
// Dst.Green = Src.Green + (1 - Src.Alpha) * Dst.Green
// Dst.Blue = Src.Blue + (1 - Src.Alpha) * Dst.Blue
// Dst.Alpha = Src.Alpha + (1 - Src.Alpha) * Dst.Alpha
for (int y = height - 1; y > -1 && off < data.length; y--) {
for (int x = 0; x < width && off < data.length; x++) {
pixel = data[off++];
alpha = (pixel & 0xFF000000) >> 24;
if (alpha == -1) {
alpha = 0xFF;
}
// TODO not tested
alpha = alpha * sourceConstantAlpha / 0xFF;
result.setRGB(x, y, new Color(
(pixel & 0xFF0000) >> 16,
(pixel & 0xFF00) >> 8,
(pixel & 0xFF),
alpha
).getRGB());
}
}
}
}
/* for debugging: shows every loaded image
javax.swing.JFrame f = new javax.swing.JFrame("test");
f.getContentPane().setBackground(Color.green);
f.getContentPane().setLayout(
new java.awt.BorderLayout(0, 0));
f.getContentPane().add(
java.awt.BorderLayout.CENTER,
new javax.swing.JLabel(
new javax.swing.ImageIcon(result)));
f.setSize(new java.awt.Dimension(width + 20, height + 20));
f.setVisible(true);*/
return result;
}
// If the biCompression member of the BITMAPINFOHEADER is BI_BITFIELDS,
// the bmiColors member 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.
// Windows NT/ 2000: When the biCompression member is 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.
// Windows 95/98/Me: When the biCompression member is BI_BITFIELDS, the system
// supports only the following 32-bpp color mask: The blue mask is 0x000000FF,
// the green mask is 0x0000FF00, and the red mask is 0x00FF0000.
else if ((bmi.getBitCount() == 32) &&
(bmi.getCompression() == EMFConstants.BI_BITFIELDS)) {
/* byte[] bytes =*/ emf.readByte(len);
return null;
} else {
/* byte[] bytes =*/ emf.readByte(len);
return null;
}
}
}