1 package org.freehep.graphicsio.emf;
2
3 import org.freehep.graphicsio.emf.gdi.BitmapInfoHeader;
4 import org.freehep.graphicsio.emf.gdi.BlendFunction;
5
6 import java.awt.image.BufferedImage;
7 import java.awt.Color;
8 import java.io.IOException;
9 import java.util.Arrays;
10
11 /**
12 * this class creates a BufferedImage from EMF imaga data stored in
13 * a byte[].
14 *
15 * @author Steffen Greiffenberg
16 * @version $Id$
17 */
18 public class EMFImageLoader {
19
20 /**
21 * creates a BufferedImage from an EMFInputStream using
22 * BitmapInfoHeader data
23 *
24 * @param bmi BitmapInfoHeader storing Bitmap informations
25 * @param width expected image width
26 * @param height expected image height
27 * @param emf EMF stream
28 * @param len length of image data
29 * @param blendFunction contains values for transparency
30 * @return BufferedImage or null
31 * @throws java.io.IOException thrown by EMFInputStream
32 */
33 public static BufferedImage readImage(
34 BitmapInfoHeader bmi,
35 int width,
36 int height,
37 EMFInputStream emf,
38 int len,
39 BlendFunction blendFunction) throws IOException {
40
41 // 0 Windows 98/Me, Windows 2000/XP: The number of bits-per-pixel
42 // is specified or is implied by the JPEG or PNG format.
43
44 if (bmi.getBitCount() == 1) {
45 // 1 The bitmap is monochrome, and the bmiColors
46 // member of BITMAPINFO contains two entries. Each
47 // bit in the bitmap array represents a pixel. If
48 // the bit is clear, the pixel is displayed with
49 // the color of the first entry in the bmiColors
50 // table; if the bit is set, the pixel has the color
51 // of the second entry in the table.
52 // byte[] bytes = emf.readByte(len);
53
54 int blue = emf.readUnsignedByte();
55 int green = emf.readUnsignedByte();
56 int red = emf.readUnsignedByte();
57 /*int unused =*/ emf.readUnsignedByte();
58
59 int color1 = new Color(red, green, blue).getRGB();
60
61 blue = emf.readUnsignedByte();
62 green = emf.readUnsignedByte();
63 red = emf.readUnsignedByte();
64 /*unused = */ emf.readUnsignedByte();
65
66 int color2 = new Color(red, green, blue).getRGB();
67
68 BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
69
70 int[] data = emf.readUnsignedByte(len - 8);
71
72 // TODO: this is highly experimental and does
73 // not work for the tested examples
74 int strangeOffset = width % 8;
75 if (strangeOffset != 0) {
76 strangeOffset = 8 - strangeOffset;
77 }
78
79 // iterator for pixel data
80 int pixel = 0;
81
82 // mask for getting the bits from a pixel data byte
83 int[] mask = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
84
85 // image data are swapped compared to java standard
86 for (int y = height - 1; y > -1; y--) {
87 for (int x = 0; x < width; x++) {
88 int pixelDataGroup = data[pixel / 8];
89 int pixelData = pixelDataGroup & mask[pixel % 8];
90 pixel ++;
91
92 if (pixelData > 0) {
93 result.setRGB(x, y, color2);
94 } else {
95 result.setRGB(x, y, color1);
96 }
97 }
98 // add the extra width
99 pixel = pixel + strangeOffset;
100 }
101
102 /* for debugging: shows every loaded image
103 javax.swing.JFrame f = new javax.swing.JFrame("test");
104 f.getContentPane().setBackground(Color.green);
105 f.getContentPane().setLayout(
106 new java.awt.BorderLayout(0, 0));
107 f.getContentPane().add(
108 java.awt.BorderLayout.CENTER,
109 new javax.swing.JLabel(
110 new javax.swing.ImageIcon(result)));
111 f.setSize(new java.awt.Dimension(width + 20, height + 20));
112 f.setVisible(true);*/
113
114 return result;
115
116 } else if ((bmi.getBitCount() == 8) &&
117 (bmi.getCompression() == EMFConstants.BI_RGB)) {
118 // 8 The bitmap has a maximum of 256 colors, and the bmiColors member
119 // of BITMAPINFO contains up to 256 entries. In this case, each byte in
120 // the array represents a single pixel.
121
122 // TODO has to be done in BitMapInfoHeader?
123 // read the color table
124 int colorsUsed = bmi.getClrUsed();
125
126 // typedef struct tagRGBQUAD {
127 // BYTE rgbBlue;
128 // BYTE rgbGreen;
129 // BYTE rgbRed;
130 // BYTE rgbReserved;
131 // } RGBQUAD;
132 int[] colors = emf.readUnsignedByte(colorsUsed * 4);
133
134 // data a indexes to a certain color in the colortable.
135 // Each byte represents a pixel
136 int[] data = emf.readUnsignedByte(len - (colorsUsed * 4));
137
138 // convert it to a color table
139 int[] colorTable = new int[256];
140 // iterator for color data
141 int color = 0;
142 for (int i = 0; i < colorsUsed; i++, color = i * 4) {
143 colorTable[i] = new Color(
144 colors[color + 2],
145 colors[color + 1],
146 colors[color]).getRGB();
147 }
148
149 // fill with black to avoid ArrayIndexOutOfBoundExceptions;
150 // somme images seem to use more colors than stored in ClrUsed
151 if (colorsUsed < 256) {
152 Arrays.fill(colorTable, colorsUsed, 256, 0);
153 }
154
155 // don't know why, but the width has to be adjusted ...
156 // it took more than an hour to determine the strangeOffset
157 int strangeOffset = width % 4;
158 if (strangeOffset != 0) {
159 strangeOffset = 4 - strangeOffset;
160 }
161
162 // create the image
163 BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
164
165 // iterator for pixel data
166 int pixel = 0;
167
168 // image data are swapped compared to java standard
169 for (int y = height - 1; y > -1; y--) {
170 for (int x = 0; x < width; x++) {
171 result.setRGB(x, y, colorTable[data[pixel++]]);
172 }
173 // add the extra width
174 pixel = pixel + strangeOffset;
175 }
176
177 return result;
178 }
179
180 // The bitmap has a maximum of 2^16 colors. If the biCompression member
181 // of the BITMAPINFOHEADER is BI_RGB, the bmiColors member of BITMAPINFO is
182 // NULL.
183 else if ((bmi.getBitCount() == 16) &&
184 (bmi.getCompression() == EMFConstants.BI_RGB)) {
185
186 // Each WORD in the bitmap array represents a single pixel. The
187 // relative intensities of red, green, and blue are represented with
188 // five bits for each color component. The value for blue is in the least
189 // significant five bits, followed by five bits each for green and red.
190 // The most significant bit is not used. The bmiColors color table is used
191 // for optimizing colors used on palette-based devices, and must contain
192 // the number of entries specified by the biClrUsed member of the
193 // BITMAPINFOHEADER.
194 int[] data = emf.readDWORD(len / 4);
195
196 // don't know why, by the width has to be the half ...
197 // maybe that has something to do with sie HALFTONE rendering setting.
198 width = (width + (width % 2)) / 2;
199 // to avoid ArrayIndexOutOfBoundExcesptions
200 height = data.length / width / 2;
201
202 // create a non transparent image
203 BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
204
205 // found no sample and color model to mak this work
206 // tag.image.setRGB(0, 0, tag.widthSrc, tag.heightSrc, data, 0, 0);
207
208 // used in the loop
209 int off = 0;
210 int pixel, neighbor;
211
212 // image data are swapped compared to java standard
213 for (int y = height - 1; y > -1; y--, off = off + width) {
214 for (int x = 0; x < width; x++) {
215 neighbor = data[off + width];
216 pixel = data[off++];
217
218 // compute the average of the pixel and it's neighbor
219 // and set the reulting color values
220 result.setRGB(x, y, new Color(
221 // 0xF800 = 2 * 0x7C00
222 (float)((pixel & 0x7C00) + (neighbor & 0x7C00)) / 0xF800,
223 (float)((pixel & 0x3E0) + (neighbor & 0x3E0)) / 0x7C0,
224 (float)((pixel & 0x1F) + (neighbor & 0x1F)) / 0x3E).getRGB());
225 }
226 }
227
228 /* for debugging: shows every loaded image
229 javax.swing.JFrame f = new javax.swing.JFrame("test");
230 f.getContentPane().setBackground(Color.green);
231 f.getContentPane().setLayout(
232 new java.awt.BorderLayout(0, 0));
233 f.getContentPane().add(
234 java.awt.BorderLayout.CENTER,
235 new javax.swing.JLabel(
236 new javax.swing.ImageIcon(result)));
237 f.pack();
238 f.setVisible(true);*/
239
240 return result;
241 }
242 // The bitmap has a maximum of 2^32 colors. If the biCompression member of the
243 // BITMAPINFOHEADER is BI_RGB, the bmiColors member of BITMAPINFO is NULL.
244 else if ((bmi.getBitCount() == 32) &&
245 (bmi.getCompression() == EMFConstants.BI_RGB)) {
246 // Each DWORD in the bitmap array represents the relative intensities of blue,
247 // green, and red, respectively, for a pixel. The high byte in each DWORD is not
248 // used. The bmiColors color table is used for optimizing colors used on
249 // palette-based devices, and must contain the number of entries specified
250 // by the biClrUsed member of the BITMAPINFOHEADER.
251
252 width = (width + (width % 20)) / 20;
253 height = (height + (height % 20)) / 20;
254
255 // create a transparent image
256 BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
257 // read the image data
258 int[] data = emf.readDWORD(len / 4);
259
260 // used to iterate the pixels later
261 int off = 0;
262 int pixel;
263 int alpha;
264
265 // The SourceConstantaAlpha member of BLENDFUNCTION specifies an alpha transparency
266 // value to be used on the entire source bitmap. The SourceConstantAlpha value is
267 // combined with any per-pixel alpha values. If SourceConstantAlpha is 0, it is
268 // assumed that the image is transparent. Set the SourceConstantAlpha value to 255
269 // (which indicates that the image is opaque) when you only want to use per-pixel
270 // alpha values.
271 int sourceConstantAlpha = blendFunction.getSourceConstantAlpha();
272
273 if (blendFunction.getAlphaFormat() != EMFConstants.AC_SRC_ALPHA) {
274 // If the source bitmap has no per-pixel alpha value (that is, AC_SRC_ALPHA is not
275 // set), the SourceConstantAlpha value determines the blend of the source and
276 // destination bitmaps, as shown in the following table. Note that SCA is used
277 // for SourceConstantAlpha here. Also, SCA is divided by 255 because it has a
278 // value that ranges from 0 to 255.
279
280 // Dst.Red = Src.Red * (SCA/255.0) + Dst.Red * (1.0 - (SCA/255.0))
281 // Dst.Green = Src.Green * (SCA/255.0) + Dst.Green * (1.0 - (SCA/255.0))
282 // Dst.Blue = Src.Blue * (SCA/255.0) + Dst.Blue * (1.0 - (SCA/255.0))
283
284 // If the destination bitmap has an alpha channel, then the blend is as follows.
285 // Dst.Alpha = Src.Alpha * (SCA/255.0) + Dst.Alpha * (1.0 - (SCA/255.0))
286
287 for (int y = height - 1; y > -1 && off < data.length; y--) {
288 for (int x = 0; x < width && off < data.length; x++) {
289 pixel = data[off++];
290
291 result.setRGB(x, y, new Color(
292 (pixel & 0xFF0000) >> 16,
293 (pixel & 0xFF00) >> 8,
294 (pixel & 0xFF),
295 // TODO not tested
296 sourceConstantAlpha
297 ).getRGB());
298 }
299 }
300 }
301 // When the BlendOp parameter is AC_SRC_OVER , the source bitmap is placed over
302 // the destination bitmap based on the alpha values of the source pixels.
303 else {
304 // If the source bitmap does not use SourceConstantAlpha (that is, it equals
305 // 0xFF), the per-pixel alpha determines the blend of the source and destination
306 // bitmaps, as shown in the following table.
307 if (sourceConstantAlpha == 0xFF) {
308 // Dst.Red = Src.Red + (1 - Src.Alpha) * Dst.Red
309 // Dst.Green = Src.Green + (1 - Src.Alpha) * Dst.Green
310 // Dst.Blue = Src.Blue + (1 - Src.Alpha) * Dst.Blue
311
312 // If the destination bitmap has an alpha channel, then the blend is as follows.
313 // Dest.alpha = Src.Alpha + (1 - SrcAlpha) * Dst.Alpha
314
315 // image data are swapped compared to java standard
316 for (int y = height - 1; y > -1 && off < data.length; y--) {
317 for (int x = 0; x < width && off < data.length; x++) {
318 pixel = data[off++];
319 alpha = (pixel & 0xFF000000) >> 24;
320 if (alpha == -1) {
321 alpha = 0xFF;
322 }
323
324 result.setRGB(x, y, new Color(
325 (pixel & 0xFF0000) >> 16,
326 (pixel & 0xFF00) >> 8,
327 (pixel & 0xFF),
328 alpha
329 ).getRGB());
330 }
331 }
332 }
333
334 // If the source has both the SourceConstantAlpha (that is, it is not 0xFF)
335 // and per-pixel alpha, the source is pre-multiplied by the SourceConstantAlpha
336 // and then the blend is based on the per-pixel alpha. The following tables show
337 // this. Note that SourceConstantAlpha is divided by 255 because it has a value
338 // that ranges from 0 to 255.
339 else {
340 // Src.Red = Src.Red * SourceConstantAlpha / 255.0;
341 // Src.Green = Src.Green * SourceConstantAlpha / 255.0;
342 // Src.Blue = Src.Blue * SourceConstantAlpha / 255.0;
343 // Src.Alpha = Src.Alpha * SourceConstantAlpha / 255.0;
344
345 // Dst.Red = Src.Red + (1 - Src.Alpha) * Dst.Red
346 // Dst.Green = Src.Green + (1 - Src.Alpha) * Dst.Green
347 // Dst.Blue = Src.Blue + (1 - Src.Alpha) * Dst.Blue
348 // Dst.Alpha = Src.Alpha + (1 - Src.Alpha) * Dst.Alpha
349
350 for (int y = height - 1; y > -1 && off < data.length; y--) {
351 for (int x = 0; x < width && off < data.length; x++) {
352 pixel = data[off++];
353
354 alpha = (pixel & 0xFF000000) >> 24;
355 if (alpha == -1) {
356 alpha = 0xFF;
357 }
358
359 // TODO not tested
360 alpha = alpha * sourceConstantAlpha / 0xFF;
361
362 result.setRGB(x, y, new Color(
363 (pixel & 0xFF0000) >> 16,
364 (pixel & 0xFF00) >> 8,
365 (pixel & 0xFF),
366 alpha
367 ).getRGB());
368 }
369 }
370 }
371 }
372
373 /* for debugging: shows every loaded image
374 javax.swing.JFrame f = new javax.swing.JFrame("test");
375 f.getContentPane().setBackground(Color.green);
376 f.getContentPane().setLayout(
377 new java.awt.BorderLayout(0, 0));
378 f.getContentPane().add(
379 java.awt.BorderLayout.CENTER,
380 new javax.swing.JLabel(
381 new javax.swing.ImageIcon(result)));
382 f.setSize(new java.awt.Dimension(width + 20, height + 20));
383 f.setVisible(true);*/
384
385 return result;
386 }
387 // If the biCompression member of the BITMAPINFOHEADER is BI_BITFIELDS,
388 // the bmiColors member contains three DWORD color masks that specify the
389 // red, green, and blue components, respectively, of each pixel. Each DWORD
390 // in the bitmap array represents a single pixel.
391
392 // Windows NT/ 2000: When the biCompression member is BI_BITFIELDS, bits set in
393 // each DWORD mask must be contiguous and should not overlap the bits of
394 // another mask. All the bits in the pixel do not need to be used.
395
396 // Windows 95/98/Me: When the biCompression member is BI_BITFIELDS, the system
397 // supports only the following 32-bpp color mask: The blue mask is 0x000000FF,
398 // the green mask is 0x0000FF00, and the red mask is 0x00FF0000.
399 else if ((bmi.getBitCount() == 32) &&
400 (bmi.getCompression() == EMFConstants.BI_BITFIELDS)) {
401 /* byte[] bytes =*/ emf.readByte(len);
402 return null;
403 } else {
404 /* byte[] bytes =*/ emf.readByte(len);
405 return null;
406 }
407 }
408 }