// Copyright 2003-2007, FreeHEP. package org.freehep.graphicsio; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.SortedSet; import java.util.TreeSet; import org.freehep.graphicsio.raw.RawImageWriteParam; import org.freehep.util.UserProperties; import org.freehep.util.images.ImageUtilities; import org.freehep.util.io.ASCII85OutputStream; import org.freehep.util.io.FlateOutputStream; import com.google.code.appengine.awt.Color; import com.google.code.appengine.awt.Image; import com.google.code.appengine.awt.image.BufferedImage; import com.google.code.appengine.awt.image.RenderedImage; import com.google.code.appengine.imageio.IIOImage; import com.google.code.appengine.imageio.ImageIO; import com.google.code.appengine.imageio.ImageReader; import com.google.code.appengine.imageio.ImageWriteParam; import com.google.code.appengine.imageio.ImageWriter; import com.google.code.appengine.imageio.stream.ImageInputStream; import com.google.code.appengine.imageio.stream.ImageOutputStream; /** * Generic class for generating bitmap outputs from an image. * * @author Mark Donszelmann * @version $Id: ImageGraphics2D.java 10273 2007-01-09 19:01:32Z duns $ */ public class ImageGraphics2D { private final static String alwaysCompressedFormats[] = { ImageConstants.JPG.toLowerCase(), ImageConstants.JPEG.toLowerCase(), ImageConstants.GIF.toLowerCase()}; private final static String nonTransparentFormats[] = { ImageConstants.JPG.toLowerCase(), ImageConstants.JPEG.toLowerCase(), ImageConstants.PPM.toLowerCase()}; public static final String rootKey = "org.freehep.graphicsio"; // our general properties public static final String TRANSPARENT = "." + PageConstants.TRANSPARENT; public static final String BACKGROUND = "." + PageConstants.BACKGROUND; public static final String BACKGROUND_COLOR = "." + PageConstants.BACKGROUND_COLOR; // our image properties public static final String ANTIALIAS = ".Antialias"; public static final String ANTIALIAS_TEXT = ".AntialiasText"; // standard image properties public static final String PROGRESSIVE = ".Progressive"; public static final String COMPRESS = ".Compress"; public static final String COMPRESS_MODE = ".CompressMode"; public static final String COMPRESS_DESCRIPTION = ".CompressDescription"; public static final String COMPRESS_QUALITY = ".CompressQuality"; private static final Map /* UserProperties */defaultProperties = new HashMap(); public static Properties getDefaultProperties(String format) { UserProperties properties = (UserProperties) defaultProperties .get(format); if (properties == null) { properties = new UserProperties(); defaultProperties.put(format, properties); String formatKey = rootKey + "." + format; // set our parameters if (canWriteTransparent(format)) { properties.setProperty(formatKey + TRANSPARENT, true); properties.setProperty(formatKey + BACKGROUND, false); properties .setProperty(formatKey + BACKGROUND_COLOR, Color.GRAY); } else { properties.setProperty(formatKey + BACKGROUND, false); properties .setProperty(formatKey + BACKGROUND_COLOR, Color.GRAY); } // set our parameters properties.setProperty(formatKey + ANTIALIAS, true); properties.setProperty(formatKey + ANTIALIAS_TEXT, true); // copy parameters from specific format ImageWriter writer = getPreferredImageWriter(format); if (writer != null) { ImageWriteParam param = writer.getDefaultWriteParam(); // compression if (param.canWriteCompressed()) { param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); properties.setProperty(formatKey + COMPRESS, true); String[] compressionTypes = param.getCompressionTypes(); String compressionType = param.getCompressionType(); properties.setProperty(formatKey + COMPRESS_MODE, compressionType != null ? compressionType : compressionTypes[0]); properties.setProperty(formatKey + COMPRESS_DESCRIPTION, "Custom"); float compressionQuality = 0.0f; try { compressionQuality = param.getCompressionQuality(); } catch (IllegalStateException e) { // ignored } properties.setProperty(formatKey + COMPRESS_QUALITY, compressionQuality); } else { properties.setProperty(formatKey + COMPRESS, false); properties.setProperty(formatKey + COMPRESS_MODE, ""); properties.setProperty(formatKey + COMPRESS_DESCRIPTION, "Custom"); properties.setProperty(formatKey + COMPRESS_QUALITY, 0.0f); } // progressive if (param.canWriteProgressive()) { properties .setProperty( formatKey + PROGRESSIVE, param.getProgressiveMode() != ImageWriteParam.MODE_DISABLED); } else { properties.setProperty(formatKey + PROGRESSIVE, false); } } else { System.err.println(ImageGraphics2D.class + ": No writer for format '" + format + "'."); } } return properties; } /** * Handles an exception which has been caught. Dispatches exception to * writeWarning for UnsupportedOperationExceptions and writeError for others * * @param exception to be handled */ protected void handleException(Exception exception) { System.err.println(exception); } /** * creates an empty image * * @param format e.g. {@link ImageConstants#BMP} or {ImageConstants#PNG} * @param width image width * @param height image height * @return offscreen buffered image */ public static BufferedImage createBufferedImage( String format, int width, int height) { // NOTE: special case for WBMP which only // supports on color band with sample size 1 // (which means black / white with no gray scale) if (ImageConstants.WBMP.equalsIgnoreCase(format)) { return new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY); } // NOTE: special case for JPEG which has no Alpha if (ImageConstants.JPG.equalsIgnoreCase(format)) { return new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); } // NOTE: special case for BMP which has no Alpha if (ImageConstants.BMP.equalsIgnoreCase(format)) { return new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); } return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); } public static void writeImage(Image image, String format, Properties properties, OutputStream os) throws IOException { // FIXME hardcoded background writeImage( ImageUtilities.createRenderedImage(image, null, Color.black), format, properties, os); } public static void writeImage(RenderedImage image, String format, Properties properties, OutputStream os) throws IOException { ImageWriter writer = getPreferredImageWriter(format); if (writer == null) throw new IOException(ImageGraphics2D.class + ": No writer for format '" + format + "'."); // get the parameters for this format UserProperties user = new UserProperties(properties); ImageWriteParam param = writer.getDefaultWriteParam(); if (param instanceof ImageParamConverter) { param = ((ImageParamConverter) param).getWriteParam(user); } // now set the standard write parameters String formatKey = rootKey + "." + format; if (param.canWriteCompressed()) { if (user.isProperty(formatKey + COMPRESS)) { if (user.getProperty(formatKey + COMPRESS_MODE).equals("")) { param.setCompressionMode(ImageWriteParam.MODE_DEFAULT); } else { param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); param.setCompressionType(user.getProperty(formatKey + COMPRESS_MODE)); param.setCompressionQuality(user.getPropertyFloat(formatKey + COMPRESS_QUALITY)); } } else { if (canWriteUncompressed(format)) { param.setCompressionMode(ImageWriteParam.MODE_DISABLED); } } } if (param.canWriteProgressive()) { if (user.isProperty(formatKey + PROGRESSIVE)) { param.setProgressiveMode(ImageWriteParam.MODE_DEFAULT); } else { param.setProgressiveMode(ImageWriteParam.MODE_DISABLED); } } // write the image ImageOutputStream ios = ImageIO.createImageOutputStream(os); writer.setOutput(ios); writer.write(null, new IIOImage(image, null, null), param); writer.dispose(); ios.close(); } public static ImageWriter getPreferredImageWriter(String format) { return (ImageWriter)getImageWriters(ImageIO .getImageWritersByFormatName(format)).first(); } public static ImageWriter getPreferredImageWriterForMIMEType(String mimeType) { return (ImageWriter)getImageWriters(ImageIO .getImageWritersByMIMEType(mimeType)).first(); } public static SortedSet/*<ImageWriter>*/ getImageWriters(Iterator iterator) { // look for a writer that supports the given format, // BUT prefer our own "org.freehep." // over "com.sun.imageio." over "com.sun.media." over others SortedSet imageWriters = new TreeSet(new Comparator() { private int order(Object o) { String className = o.getClass().getName(); if (className.startsWith("org.freehep.")) { return 0; } else if (className.startsWith("com.sun.imageio.")) { return 1; } else if (className.startsWith("com.sun.media.")) { return 2; } return 3; } public int compare(Object arg0, Object arg1) { int order0 = order(arg0); int order1 = order(arg1); return order0 < order1 ? -1 : order0 > order1 ? 1 : 0; } }); while (iterator.hasNext()) { imageWriters.add((ImageWriter) iterator.next()); } return imageWriters; } public static BufferedImage readImage(String format, InputStream is) throws IOException { Iterator iterator = ImageIO.getImageReadersByFormatName(format.toLowerCase()); if (!iterator.hasNext()) { throw new IOException(ImageGraphics2D.class + ": No reader for format '" + format + "'."); } ImageReader reader = (ImageReader) iterator.next(); ImageInputStream iis = ImageIO.createImageInputStream(is); reader.setInput(iis, true); BufferedImage image = reader.read(0); reader.dispose(); iis.close(); return image; } public static boolean canWriteUncompressed(String format) { // Method forgotten by Sun, BUG# 4856395. // If param.canWriteCompressed() is true, then it may be that // the format always needs to be compressed... GIF and JPG are among of // them. return !Arrays.asList(alwaysCompressedFormats).contains( format.toLowerCase()); } public static boolean canWriteTransparent(String format) { return !Arrays.asList(nonTransparentFormats).contains( format.toLowerCase()); } /** * @param bkg Background color for the image * @return Properties used to create a RAW image * @param code Color encoding, e.g. {@link ImageConstants#COLOR_MODEL_RGB} */ public static UserProperties getRAWProperties(Color bkg, String code) { UserProperties result = new UserProperties(); result.setProperty(RawImageWriteParam.BACKGROUND, bkg); result.setProperty(RawImageWriteParam.CODE, code); result.setProperty(RawImageWriteParam.PAD, 1); return result; } /** * Converts a given image to byte[] * * @throws IOException thrown by {@link #writeImage(com.google.code.appengine.awt.image.RenderedImage, String, java.util.Properties, java.io.OutputStream)} * @param image Image to convert * @param format e.g. {@link ImageConstants#JPG}, {@link ImageConstants#PNG, {@link ImageConstants#RAW} * @param props Properties for writing, e.g. {@link org.freehep.graphicsio.raw.RawImageWriteParam#BACKGROUND} * @param encoding {@link ImageConstants#ENCODING_ASCII85}, {@link ImageConstants#ENCODING_FLATE} or null * @return bytes representing the image */ public static byte[] toByteArray( RenderedImage image, String format, String encoding, Properties props) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); OutputStream os = bos; if (ImageConstants.ENCODING_ASCII85.equals(encoding) || ImageConstants.ENCODING_FLATE_ASCII85.equals(encoding)) { os = new ASCII85OutputStream(os); } if (ImageConstants.ENCODING_FLATE.equals(encoding) || ImageConstants.ENCODING_FLATE_ASCII85.equals(encoding)) { os = new FlateOutputStream(os); } // avoid NPE if (props == null) { props = new Properties(); } // write image into the stream ImageGraphics2D.writeImage(image, format.toLowerCase(), props, os); os.close(); // return reulting bytes from stream return bos.toByteArray(); } }