View Javadoc

1   // Copyright 2003-2007, FreeHEP.
2   package org.freehep.graphicsio;
3   
4   import java.awt.Color;
5   import java.awt.Component;
6   import java.awt.Dimension;
7   import java.awt.Graphics;
8   import java.awt.GraphicsConfiguration;
9   import java.awt.Image;
10  import java.awt.RenderingHints;
11  import java.awt.image.BufferedImage;
12  import java.awt.image.RenderedImage;
13  import java.io.File;
14  import java.io.FileNotFoundException;
15  import java.io.FileOutputStream;
16  import java.io.IOException;
17  import java.io.InputStream;
18  import java.io.OutputStream;
19  import java.io.ByteArrayOutputStream;
20  import java.util.Arrays;
21  import java.util.Comparator;
22  import java.util.Enumeration;
23  import java.util.HashMap;
24  import java.util.Iterator;
25  import java.util.Map;
26  import java.util.Properties;
27  import java.util.SortedSet;
28  import java.util.TreeSet;
29  
30  import javax.imageio.IIOImage;
31  import javax.imageio.ImageIO;
32  import javax.imageio.ImageReader;
33  import javax.imageio.ImageWriteParam;
34  import javax.imageio.ImageWriter;
35  import javax.imageio.stream.ImageInputStream;
36  import javax.imageio.stream.ImageOutputStream;
37  
38  import org.freehep.graphics2d.PixelGraphics2D;
39  import org.freehep.util.UserProperties;
40  import org.freehep.util.io.ASCII85OutputStream;
41  import org.freehep.util.io.FlateOutputStream;
42  import org.freehep.util.images.ImageUtilities;
43  import org.freehep.graphicsio.raw.RawImageWriteParam;
44  
45  /**
46   * Generic class for generating bitmap outputs from an image.
47   *
48   * @author Mark Donszelmann
49   * @version $Id: ImageGraphics2D.java 10273 2007-01-09 19:01:32Z duns $
50   */
51  public class ImageGraphics2D extends PixelGraphics2D {
52  
53      private final static String alwaysCompressedFormats[] = {
54          ImageConstants.JPG.toLowerCase(),
55          ImageConstants.JPEG.toLowerCase(),
56          ImageConstants.GIF.toLowerCase()};
57  
58      private final static String nonTransparentFormats[] = {
59          ImageConstants.JPG.toLowerCase(),
60          ImageConstants.JPEG.toLowerCase(),
61          ImageConstants.PPM.toLowerCase()};
62  
63      public static final String rootKey = "org.freehep.graphicsio";
64  
65      // our general properties
66      public static final String TRANSPARENT = "." + PageConstants.TRANSPARENT;
67  
68      public static final String BACKGROUND = "." + PageConstants.BACKGROUND;
69  
70      public static final String BACKGROUND_COLOR = "."
71              + PageConstants.BACKGROUND_COLOR;
72  
73      // our image properties
74      public static final String ANTIALIAS = ".Antialias";
75  
76      public static final String ANTIALIAS_TEXT = ".AntialiasText";
77  
78      // standard image properties
79      public static final String PROGRESSIVE = ".Progressive";
80  
81      public static final String COMPRESS = ".Compress";
82  
83      public static final String COMPRESS_MODE = ".CompressMode";
84  
85      public static final String COMPRESS_DESCRIPTION = ".CompressDescription";
86  
87      public static final String COMPRESS_QUALITY = ".CompressQuality";
88  
89      private static final Map /* UserProperties */defaultProperties = new HashMap();
90  
91      public static Properties getDefaultProperties(String format) {
92          UserProperties properties = (UserProperties) defaultProperties
93                  .get(format);
94          if (properties == null) {
95              properties = new UserProperties();
96              defaultProperties.put(format, properties);
97  
98              String formatKey = rootKey + "." + format;
99  
100             // set our parameters
101             if (canWriteTransparent(format)) {
102                 properties.setProperty(formatKey + TRANSPARENT, true);
103                 properties.setProperty(formatKey + BACKGROUND, false);
104                 properties
105                         .setProperty(formatKey + BACKGROUND_COLOR, Color.GRAY);
106             } else {
107                 properties.setProperty(formatKey + BACKGROUND, false);
108                 properties
109                         .setProperty(formatKey + BACKGROUND_COLOR, Color.GRAY);
110             }
111 
112             // set our parameters
113             properties.setProperty(formatKey + ANTIALIAS, true);
114             properties.setProperty(formatKey + ANTIALIAS_TEXT, true);
115 
116             // copy parameters from specific format
117             ImageWriter writer = getPreferredImageWriter(format);
118             if (writer != null) {
119                 ImageWriteParam param = writer.getDefaultWriteParam();
120 
121                 // compression
122                 if (param.canWriteCompressed()) {
123                     param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
124                     properties.setProperty(formatKey + COMPRESS, true);
125                     String[] compressionTypes = param.getCompressionTypes();
126                     String compressionType = param.getCompressionType();
127                     properties.setProperty(formatKey + COMPRESS_MODE, compressionType != null ? compressionType : compressionTypes[0]);
128                     properties.setProperty(formatKey + COMPRESS_DESCRIPTION,
129                             "Custom");
130                     float compressionQuality = 0.0f;
131                     try {
132                     	compressionQuality = param.getCompressionQuality();
133                     } catch (IllegalStateException e) {
134                     	// ignored
135                     }
136                     properties.setProperty(formatKey + COMPRESS_QUALITY, compressionQuality);
137                 } else {
138                     properties.setProperty(formatKey + COMPRESS, false);
139                     properties.setProperty(formatKey + COMPRESS_MODE, "");
140                     properties.setProperty(formatKey + COMPRESS_DESCRIPTION,
141                             "Custom");
142                     properties.setProperty(formatKey + COMPRESS_QUALITY, 0.0f);
143                 }
144 
145                 // progressive
146                 if (param.canWriteProgressive()) {
147                     properties
148                             .setProperty(
149                                     formatKey + PROGRESSIVE,
150                                     param.getProgressiveMode() != ImageWriteParam.MODE_DISABLED);
151                 } else {
152                     properties.setProperty(formatKey + PROGRESSIVE, false);
153                 }
154             } else {
155                 System.err.println(ImageGraphics2D.class
156                         + ": No writer for format '" + format + "'.");
157             }
158         }
159         return properties;
160     }
161 
162     public void setProperties(Properties newProperties) {
163         if (newProperties == null)
164             return;
165 
166         String formatKey = rootKey + "." + format;
167         Properties formatProperties = new Properties();
168         for (Enumeration e = newProperties.propertyNames(); e.hasMoreElements();) {
169             String key = (String) e.nextElement();
170             String value = newProperties.getProperty(key);
171             if (key.indexOf("." + format) < 0) {
172                 key = formatKey + key;
173             }
174             formatProperties.setProperty(key, value);
175         }
176         super.setProperties(formatProperties);
177 
178         setPropertiesOnGraphics();
179     }
180 
181     private void setPropertiesOnGraphics() {
182         String formatKey = rootKey + "." + format;
183         if (isProperty(formatKey + ANTIALIAS)) {
184             setRenderingHint(RenderingHints.KEY_ANTIALIASING,
185                     RenderingHints.VALUE_ANTIALIAS_ON);
186         } else {
187             setRenderingHint(RenderingHints.KEY_ANTIALIASING,
188                     RenderingHints.VALUE_ANTIALIAS_OFF);
189         }
190 
191         if (isProperty(formatKey + ANTIALIAS_TEXT)) {
192             setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
193                     RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
194         } else {
195             setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
196                     RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
197         }
198 
199         if (isProperty(formatKey + TRANSPARENT)) {
200             setBackground(null);
201         } else if (isProperty(formatKey + BACKGROUND)) {
202             setBackground(getPropertyColor(formatKey + BACKGROUND_COLOR));
203         } else {
204             setBackground(component != null ? component.getBackground()
205                     : Color.WHITE);
206         }
207     }
208 
209     private void setHintsOnGraphics() {
210         if (format.equalsIgnoreCase(ImageConstants.JPG)) {
211             // since we draw JPG on non-transparent background, we cannot blit
212             // compatible images
213             setRenderingHint(KEY_SYMBOL_BLIT, VALUE_SYMBOL_BLIT_OFF);
214         } else {
215             setRenderingHint(KEY_SYMBOL_BLIT, VALUE_SYMBOL_BLIT_ON);
216         }
217 
218     }
219 
220     protected OutputStream os;
221 
222     protected BufferedImage image;
223 
224     protected String format;
225 
226     protected Component component;
227 
228     public ImageGraphics2D(File file, Dimension size, String format)
229             throws FileNotFoundException {
230         this(new FileOutputStream(file), size, format);
231     }
232 
233     public ImageGraphics2D(File file, Component component, String format)
234             throws FileNotFoundException {
235         this(new FileOutputStream(file), component, format);
236     }
237 
238     public ImageGraphics2D(OutputStream os, Dimension size, String format) {
239         super();
240         init(os, size, format);
241         component = null;
242     }
243 
244     public ImageGraphics2D(OutputStream os, Component component, String format) {
245         super();
246         this.component = component;
247         init(os, component.getSize(), format);
248 
249         setColor(component.getForeground());
250         GraphicsConfiguration gc = component.getGraphicsConfiguration();
251         if (gc != null)
252             setTransform(gc.getDefaultTransform());
253     }
254 
255     private void init(OutputStream os, Dimension size, String format) {
256         this.os = os;
257         this.format = format;
258 
259         initProperties(getDefaultProperties(format));
260 
261         // create actual graphics
262         image = createBufferedImage(format, size.width, size.height);
263         setHostGraphics(image.getGraphics());
264 
265         // set graphics properties
266         setPropertiesOnGraphics();
267 
268         // set graphics hints
269         setHintsOnGraphics();
270 
271         // Ensure that a clipping path is set on this graphics
272         // context. This avoids a null pointer exception inside of
273         // a JLayeredPane when printing.
274         hostGraphics.clipRect(0, 0, size.width, size.height);
275     }
276 
277     protected ImageGraphics2D(ImageGraphics2D graphics) {
278         super(graphics);
279         image = graphics.image;
280         os = graphics.os;
281         format = graphics.format;
282 
283         // make sure all hints are copied.
284         setHintsOnGraphics();
285     }
286 
287     public Graphics create() {
288         return new ImageGraphics2D(this);
289     }
290 
291     public Graphics create(double x, double y, double width, double height) {
292         ImageGraphics2D imageGraphics = new ImageGraphics2D(this);
293         imageGraphics.translate(x, y);
294         imageGraphics.clipRect(0, 0, width, height);
295         return imageGraphics;
296     }
297 
298     public void startExport() {
299         if (getBackground() != null) {
300             clearRect(0.0, 0.0, image.getWidth(), image.getHeight());
301         }
302     }
303 
304     public void endExport() {
305         try {
306             write();
307             closeStream();
308         } catch (IOException e) {
309             handleException(e);
310         }
311     }
312 
313     protected void write() throws IOException {
314         writeImage((RenderedImage) image, format, getProperties(), os);
315     }
316 
317     public void closeStream() throws IOException {
318         os.close();
319     }
320 
321     /**
322      * Handles an exception which has been caught. Dispatches exception to
323      * writeWarning for UnsupportedOperationExceptions and writeError for others
324      *
325      * @param exception to be handled
326      */
327     protected void handleException(Exception exception) {
328         System.err.println(exception);
329     }
330 
331     /**
332      * creates an empty image
333      *
334      * @param format e.g. {@link ImageConstants#BMP} or {ImageConstants#PNG}
335      * @param width image width
336      * @param height image height
337      * @return offscreen buffered image
338      */
339     public static BufferedImage createBufferedImage(
340         String format,
341         int width,
342         int height) {
343 
344         // NOTE: special case for WBMP which only
345         // supports on color band with sample size 1
346         // (which means black / white with no gray scale)
347         if (ImageConstants.WBMP.equalsIgnoreCase(format)) {
348             return new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY);
349         }
350 
351         // NOTE: special case for JPEG which has no Alpha
352         if (ImageConstants.JPG.equalsIgnoreCase(format)) {
353             return new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
354         }
355 
356         // NOTE: special case for BMP which has no Alpha
357         if (ImageConstants.BMP.equalsIgnoreCase(format)) {
358             return new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
359         }
360 
361         return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
362     }
363 
364     public static BufferedImage generateThumbnail(Component component,
365             Dimension size) {
366         int longSide = Math.max(size.width, size.height);
367         if (longSide < 0)
368             return null;
369 
370         int componentWidth = component.getBounds().width;
371         int componentHeight = component.getBounds().height;
372 
373         BufferedImage image = new BufferedImage(componentWidth,
374                 componentHeight, BufferedImage.TYPE_INT_ARGB);
375         Graphics imageGraphics = image.getGraphics();
376         component.print(imageGraphics);
377 
378         int width = longSide;
379         int height = longSide;
380         if (componentWidth < componentHeight) {
381             width = componentWidth * size.height / componentHeight;
382         } else {
383             height = componentHeight * size.width / componentWidth;
384         }
385 
386         BufferedImage scaled = new BufferedImage(width, height,
387                 BufferedImage.TYPE_INT_ARGB);
388         Graphics scaledGraphics = scaled.getGraphics();
389         scaledGraphics.drawImage(image, 0, 0, width, height, null);
390 
391         return scaled;
392     }
393 
394     public static void writeImage(Image image, String format,
395             Properties properties, OutputStream os) throws IOException {
396         // FIXME hardcoded background
397         writeImage(
398                 ImageUtilities.createRenderedImage(image, null, Color.black),
399                 format, properties, os);
400     }
401 
402     public static void writeImage(RenderedImage image, String format,
403             Properties properties, OutputStream os) throws IOException {
404 
405         ImageWriter writer = getPreferredImageWriter(format);
406         if (writer == null)
407             throw new IOException(ImageGraphics2D.class
408                     + ": No writer for format '" + format + "'.");
409 
410         // get the parameters for this format
411         UserProperties user = new UserProperties(properties);
412         ImageWriteParam param = writer.getDefaultWriteParam();
413         if (param instanceof ImageParamConverter) {
414             param = ((ImageParamConverter) param).getWriteParam(user);
415         }
416 
417         // now set the standard write parameters
418         String formatKey = rootKey + "." + format;
419         if (param.canWriteCompressed()) {
420             if (user.isProperty(formatKey + COMPRESS)) {
421                 if (user.getProperty(formatKey + COMPRESS_MODE).equals("")) {
422                     param.setCompressionMode(ImageWriteParam.MODE_DEFAULT);
423                 } else {
424                     param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
425                     param.setCompressionType(user.getProperty(formatKey
426                             + COMPRESS_MODE));
427                     param.setCompressionQuality(user.getPropertyFloat(formatKey
428                             + COMPRESS_QUALITY));
429                 }
430             } else {
431                 if (canWriteUncompressed(format)) {
432                     param.setCompressionMode(ImageWriteParam.MODE_DISABLED);
433                 }
434             }
435         }
436         if (param.canWriteProgressive()) {
437             if (user.isProperty(formatKey + PROGRESSIVE)) {
438                 param.setProgressiveMode(ImageWriteParam.MODE_DEFAULT);
439             } else {
440                 param.setProgressiveMode(ImageWriteParam.MODE_DISABLED);
441             }
442         }
443 
444         // write the image
445         ImageOutputStream ios = ImageIO.createImageOutputStream(os);
446         writer.setOutput(ios);
447         writer.write(null, new IIOImage(image, null, null), param);
448         writer.dispose();
449         ios.close();
450     }
451 
452     public static ImageWriter getPreferredImageWriter(String format) {
453         return (ImageWriter)getImageWriters(ImageIO
454                 .getImageWritersByFormatName(format)).first();
455     }
456 
457     public static ImageWriter getPreferredImageWriterForMIMEType(String mimeType) {
458         return (ImageWriter)getImageWriters(ImageIO
459                 .getImageWritersByMIMEType(mimeType)).first();
460     }
461 
462     public static SortedSet/*<ImageWriter>*/ getImageWriters(Iterator iterator) {
463         // look for a writer that supports the given format,
464         // BUT prefer our own "org.freehep."
465         // over "com.sun.imageio." over "com.sun.media." over others
466         SortedSet imageWriters = new TreeSet(new Comparator() {
467         	private int order(Object o) {
468         		String className = o.getClass().getName();
469         		if (className.startsWith("org.freehep.")) {
470                     return 0;
471                 } else if (className.startsWith("com.sun.imageio.")) {
472                     return 1;
473                 } else if (className.startsWith("com.sun.media.")) {
474                     return 2;
475                 }
476         		return 3;
477         	}
478 
479         	public int compare(Object arg0, Object arg1) {
480         		int order0 = order(arg0);
481         		int order1 = order(arg1);
482         		return order0 < order1 ? -1 : order0 > order1 ? 1 : 0;
483         	}
484         });
485         while (iterator.hasNext()) {
486             imageWriters.add((ImageWriter) iterator.next());
487         }
488         return imageWriters;
489     }
490 
491     public static BufferedImage readImage(String format, InputStream is)
492             throws IOException {
493         Iterator iterator = ImageIO.getImageReadersByFormatName(format.toLowerCase());
494         if (!iterator.hasNext()) {
495             throw new IOException(ImageGraphics2D.class
496                     + ": No reader for format '" + format + "'.");
497         }
498         ImageReader reader = (ImageReader) iterator.next();
499 
500         ImageInputStream iis = ImageIO.createImageInputStream(is);
501         reader.setInput(iis, true);
502         BufferedImage image = reader.read(0);
503         reader.dispose();
504         iis.close();
505         return image;
506     }
507 
508     public static boolean canWriteUncompressed(String format) {
509         // Method forgotten by Sun, BUG# 4856395.
510         // If param.canWriteCompressed() is true, then it may be that
511         // the format always needs to be compressed... GIF and JPG are among of
512         // them.
513         return !Arrays.asList(alwaysCompressedFormats).contains(
514                 format.toLowerCase());
515     }
516 
517     public static boolean canWriteTransparent(String format) {
518         return !Arrays.asList(nonTransparentFormats).contains(
519                 format.toLowerCase());
520     }
521 
522     /**
523      * @param bkg Background color for the image
524      * @return Properties used to create a RAW image
525      * @param code Color encoding, e.g. {@link ImageConstants#COLOR_MODEL_RGB}
526      */
527     public static UserProperties getRAWProperties(Color bkg, String code) {
528         UserProperties result = new UserProperties();
529         result.setProperty(RawImageWriteParam.BACKGROUND, bkg);
530         result.setProperty(RawImageWriteParam.CODE, code);
531         result.setProperty(RawImageWriteParam.PAD, 1);
532         return result;
533     }
534 
535     /**
536      * Converts a given image to byte[]
537      *
538      * @throws IOException thrown by {@link #writeImage(java.awt.image.RenderedImage, String, java.util.Properties, java.io.OutputStream)}
539      * @param image Image to convert
540      * @param format e.g. {@link ImageConstants#JPG}, {@link ImageConstants#PNG, {@link ImageConstants#RAW}
541      * @param props Properties for writing, e.g. {@link org.freehep.graphicsio.raw.RawImageWriteParam#BACKGROUND}
542      * @param encoding {@link ImageConstants#ENCODING_ASCII85}, {@link ImageConstants#ENCODING_FLATE} or null
543      * @return bytes representing the image
544      */
545     public static byte[] toByteArray(
546         RenderedImage image,
547         String format,
548         String encoding,
549         Properties props) throws IOException {
550 
551         ByteArrayOutputStream bos = new ByteArrayOutputStream();
552         OutputStream os = bos;
553 
554         if (ImageConstants.ENCODING_ASCII85.equals(encoding)
555             || ImageConstants.ENCODING_FLATE_ASCII85.equals(encoding)) {
556             os = new ASCII85OutputStream(os);
557         }
558 
559         if (ImageConstants.ENCODING_FLATE.equals(encoding)
560             || ImageConstants.ENCODING_FLATE_ASCII85.equals(encoding)) {
561             os = new FlateOutputStream(os);
562         }
563 
564         // avoid NPE
565         if (props == null) {
566             props = new Properties();
567         }
568 
569         // write image into the stream
570         ImageGraphics2D.writeImage(image, format.toLowerCase(), props, os);
571         os.close();
572 
573         // return reulting bytes from stream
574         return bos.toByteArray();
575     }
576 }