// Copyright 2003, FreeHEP. package org.freehep.graphicsio; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; import java.awt.GraphicsConfiguration; import java.awt.Image; import java.awt.RenderingHints; import java.awt.font.FontRenderContext; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; import java.util.Comparator; import java.util.Enumeration; 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 javax.imageio.IIOImage; import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriter; import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageOutputStream; import org.freehep.graphics2d.PixelGraphics2D; import org.freehep.util.UserProperties; import org.freehep.util.images.ImageUtilities; /** * Generic class for generating bitmap outputs from an image. * * @author Mark Donszelmann * @version $Id: ImageGraphics2D.java,v 1.9 2009-08-17 21:44:45 murkle Exp $ */ public class ImageGraphics2D extends PixelGraphics2D { private final static String alwaysCompressedFormats[] = { "jpg", "jpeg", "gif" }; private final static String nonTransparentFormats[] = { "jpg", "jpeg", "ppm" }; 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(); @Override public FontRenderContext getFontRenderContext() { // TODO Auto-generated method stub FontRenderContext f = super.getFontRenderContext(); System.err.println("FRC: " + f.getTransform()); return f; } 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; } @Override public void setProperties(Properties newProperties) { if (newProperties == null) { return; } String formatKey = rootKey + "." + format; Properties formatProperties = new Properties(); for (Enumeration e = newProperties.propertyNames(); e .hasMoreElements();) { String key = (String) e.nextElement(); String value = newProperties.getProperty(key); if (key.indexOf("." + format) < 0) { key = formatKey + key; } formatProperties.setProperty(key, value); } super.setProperties(formatProperties); setPropertiesOnGraphics(); } private void setPropertiesOnGraphics() { String formatKey = rootKey + "." + format; if (isProperty(formatKey + ANTIALIAS)) { setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); } else { setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); } if (isProperty(formatKey + ANTIALIAS_TEXT)) { setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); } else { setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); } if (isProperty(formatKey + TRANSPARENT)) { setBackground(null); } else if (isProperty(formatKey + BACKGROUND)) { setBackground(getPropertyColor(formatKey + BACKGROUND_COLOR)); } else { setBackground(component != null ? component.getBackground() : Color.WHITE); } } private void setHintsOnGraphics() { if (format.equalsIgnoreCase(ImageConstants.JPG)) { // since we draw JPG on non-transparent background, we cannot blit // compatible images setRenderingHint(KEY_SYMBOL_BLIT, VALUE_SYMBOL_BLIT_OFF); } else { setRenderingHint(KEY_SYMBOL_BLIT, VALUE_SYMBOL_BLIT_ON); } } protected OutputStream os; protected BufferedImage image; protected String format; protected Component component; public ImageGraphics2D(File file, Dimension size, String format) throws FileNotFoundException { this(new FileOutputStream(file), size, format); } public ImageGraphics2D(File file, Component component, String format) throws FileNotFoundException { this(new FileOutputStream(file), component, format); } public ImageGraphics2D(OutputStream os, Dimension size, String format) { super(); init(os, size, format); component = null; } public ImageGraphics2D(OutputStream os, Component component, String format) { super(); this.component = component; init(os, component.getSize(), format); setColor(component.getForeground()); GraphicsConfiguration gc = component.getGraphicsConfiguration(); if (gc != null) { setTransform(gc.getDefaultTransform()); } } private void init(OutputStream os, Dimension size, String format) { this.os = os; this.format = format; initProperties(getDefaultProperties(format)); // create actual graphics image = createBufferedImage(format, size.width, size.height); setHostGraphics(image.getGraphics()); // set graphics properties setPropertiesOnGraphics(); // set graphics hints setHintsOnGraphics(); // Ensure that a clipping path is set on this graphics // context. This avoids a null pointer exception inside of // a JLayeredPane when printing. hostGraphics.clipRect(0, 0, size.width, size.height); } protected ImageGraphics2D(ImageGraphics2D graphics) { super(graphics); image = graphics.image; os = graphics.os; format = graphics.format; // make sure all hints are copied. setHintsOnGraphics(); } @Override public Graphics create() { return new ImageGraphics2D(this); } @Override public Graphics create(double x, double y, double width, double height) { ImageGraphics2D imageGraphics = new ImageGraphics2D(this); imageGraphics.translate(x, y); imageGraphics.clipRect(0, 0, width, height); return imageGraphics; } @Override public void startExport() { if (getBackground() != null) { clearRect(0.0, 0.0, image.getWidth(), image.getHeight()); } } @Override public void endExport() { try { write(); closeStream(); } catch (IOException e) { handleException(e); } } protected void write() throws IOException { writeImage((RenderedImage) image, format, getProperties(), os); } public void closeStream() throws IOException { os.close(); } /** * 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); } public static BufferedImage createBufferedImage(String format, int width, int height) { // NOTE: special case for JPEG which has no Alpha int imageType = (format.equalsIgnoreCase("jpg") || format.equalsIgnoreCase("jpeg")) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; BufferedImage image = new BufferedImage(width, height, imageType); return image; } public static BufferedImage generateThumbnail(Component component, Dimension size) { int longSide = Math.max(size.width, size.height); if (longSide < 0) { return null; } int componentWidth = component.getBounds().width; int componentHeight = component.getBounds().height; BufferedImage image = new BufferedImage(componentWidth, componentHeight, BufferedImage.TYPE_INT_ARGB); Graphics imageGraphics = image.getGraphics(); component.print(imageGraphics); int width = longSide; int height = longSide; if (componentWidth < componentHeight) { width = componentWidth * size.height / componentHeight; } else { height = componentHeight * size.width / componentWidth; } BufferedImage scaled = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics scaledGraphics = scaled.getGraphics(); scaledGraphics.drawImage(image, 0, 0, width, height, null); return scaled; } 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; } @Override 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(iterator.next()); } return imageWriters; } public static BufferedImage readImage(String format, InputStream is) throws IOException { Iterator iterator = ImageIO.getImageReadersByFormatName(format); 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()); } }