View Javadoc

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 }