/*
* @(#)ImageOutputFormat.java
*
* Copyright (c) 1996-2010 The authors and contributors of JHotDraw.
* You may not use, copy or modify this file, except in compliance with the
* accompanying license terms.
*/
package org.jhotdraw.draw.io;
import javax.annotation.Nullable;
import org.jhotdraw.gui.filechooser.ExtensionFileFilter;
import org.jhotdraw.draw.*;
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.*;
import java.net.URI;
import javax.imageio.*;
import javax.swing.*;
import org.jhotdraw.gui.datatransfer.*;
import static org.jhotdraw.draw.AttributeKeys.*;
/**
* An output format for exporting drawings using one of the image formats
* supported by javax.imageio.
*
* @author Werner Randelshofer
* @version $Id$
*/
public class ImageOutputFormat implements OutputFormat {
/**
* Format description used for the file filter.
*/
private String description;
/**
* File name extension used for the file filter.
*/
private String fileExtension;
/**
* Image IO image format name.
*/
private String formatName;
/**
* The image type must match the output format, for example, PNG supports
* BufferedImage.TYPE_INT_ARGB whereas GIF needs BufferedImage.TYPE_
*/
private int imageType;
/** Creates a new image output format for Portable Network Graphics PNG. */
public ImageOutputFormat() {
this("PNG", "Portable Network Graphics (PNG)", "png", BufferedImage.TYPE_INT_ARGB);
}
/** Creates a new image output format for the specified image format.
*
* @param formatName The format name for the javax.imageio.ImageIO object.
* @param description The format description to be used for the file filter.
* @param fileExtension The file extension to be used for file filter.
* @param bufferedImageType The BufferedImage type used to produce the image.
* The value of this parameter must match with the format name.
*/
public ImageOutputFormat(String formatName, String description, String fileExtension,
int bufferedImageType) {
this.formatName = formatName;
this.description = description;
this.fileExtension = fileExtension;
this.imageType = bufferedImageType;
}
@Override
public javax.swing.filechooser.FileFilter getFileFilter() {
return new ExtensionFileFilter(description, fileExtension);
}
@Override
public String getFileExtension() {
return fileExtension;
}
@Override
public JComponent getOutputFormatAccessory() {
return null;
}
@Override
public void write(URI uri, Drawing drawing) throws IOException {
write(new File(uri),drawing);
}
/**
* Writes the drawing to the specified file.
* This method ensures that all figures of the drawing are visible on
* the image.
*/
public void write(File file, Drawing drawing) throws IOException {
try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
write(out, drawing);
}
}
/**
* Writes the drawing to the specified output stream.
* This method ensures that all figures of the drawing are visible on
* the image.
*/
@Override
public void write(OutputStream out, Drawing drawing) throws IOException {
write(out, drawing, drawing.getChildren(), null, null);
}
/**
* Writes the drawing to the specified output stream.
* This method applies the specified transform to the drawing, and draws
* it on an image of the specified size.
*/
public void write(OutputStream out, Drawing drawing,
AffineTransform drawingTransform, Dimension imageSize) throws IOException {
write(out, drawing, drawing.getChildren(), drawingTransform, imageSize);
}
/**
* Writes the drawing to the specified output stream.
* This method ensures that all figures of the drawing are visible on
* the image.
*/
@Override
public Transferable createTransferable(Drawing drawing, java.util.List<Figure> figures, double scaleFactor) throws IOException {
return new ImageTransferable(toImage(drawing, figures, scaleFactor, true));
}
/**
* Writes the figures to the specified output stream.
* This method ensures that all figures of the drawing are visible on
* the image.
*/
public void write(OutputStream out, Drawing drawing, java.util.List<Figure> figures) throws IOException {
write(out, drawing, figures, null, null);
}
/**
* Writes the figures to the specified output stream.
* This method applies the specified transform to the drawing, and draws
* it on an image of the specified size.
*/
public void write(OutputStream out, Drawing drawing, java.util.List<Figure> figures,
@Nullable AffineTransform drawingTransform, @Nullable Dimension imageSize) throws IOException {
BufferedImage img;
if (drawingTransform == null || imageSize == null) {
img = toImage(drawing, figures, 1d, false);
} else {
img = toImage(drawing, figures, drawingTransform, imageSize);
}
ImageIO.write(img, formatName, out);
img.flush();
}
/**
* Creates a BufferedImage from the specified list of figures.
* <p>
* The images are drawn using the specified scale factor. If some figures
* have a drawing area located at negative coordinates, then the drawing
* coordinates are translated, so that all figures are visible on the
* image.
*
* @param drawing The drawing.
* @param figures A list of figures of the drawing.
* @param scaleFactor The scale factor used when drawing the figures.
* @param clipToFigures If this is true, the image is clipped to the figures.
* If this is false, the image includes the drawing area,
*/
public BufferedImage toImage(Drawing drawing,
java.util.List<Figure> figures,
double scaleFactor, boolean clipToFigures) {
// Return a transparent 1-pixel image if the drawing is empty.
if (drawing.getChildCount()==0) {
return new BufferedImage(1,1,BufferedImage.TYPE_INT_ARGB);
}
// Determine the draw bounds of the figures
Rectangle2D.Double drawBounds = null;
for (Figure f : figures) {
if (drawBounds == null) {
drawBounds = f.getDrawingArea();
} else {
drawBounds.add(f.getDrawingArea());
}
}
if (clipToFigures) {
AffineTransform transform = new AffineTransform();
transform.translate(-drawBounds.x * scaleFactor,
-drawBounds.y * scaleFactor);
transform.scale(scaleFactor, scaleFactor);
return toImage(drawing, figures, transform,
new Dimension(
(int) (drawBounds.width * scaleFactor),
(int) (drawBounds.height * scaleFactor)));
} else {
AffineTransform transform = new AffineTransform();
if (drawBounds.x < 0) {
transform.translate(-drawBounds.x * scaleFactor, 0);
}
if (drawBounds.y < 0) {
transform.translate(0, -drawBounds.y * scaleFactor);
}
transform.scale(scaleFactor, scaleFactor);
return toImage(drawing, figures, transform,
new Dimension(
(int) ((Math.max(0, drawBounds.x) + drawBounds.width) * scaleFactor),
(int) ((Math.max(0, drawBounds.y) + drawBounds.height) * scaleFactor)));
}
}
/**
* Creates a BufferedImage from the specified list of figures.
*
* @param drawing The drawing.
* @param figures A list of figures of the drawing.
* @param transform The AffineTransform to be used when drawing
* the figures.
* @param imageSize The width and height of the image.
*/
public BufferedImage toImage(
Drawing drawing,
java.util.List<Figure> figures,
AffineTransform transform,
Dimension imageSize) {
// Create the buffered image and clear it
Color background = drawing.get(CANVAS_FILL_COLOR);
double opacity = drawing.get(CANVAS_FILL_OPACITY);
if (background == null) {
background = new Color(0xff, 0xff, 0xff, 0x0);
} else {
background = new Color(background.getRed(), background.getGreen(), background.getBlue(), (int) (background.getAlpha() * opacity));
}
BufferedImage buf = new BufferedImage(
Math.max(1, imageSize.width), Math.max(1, imageSize.height),
(background.getAlpha() == 255) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB);
Graphics2D g = buf.createGraphics();
// Clear the buffered image with the background color
Composite savedComposite = g.getComposite();
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC));
g.setColor(background);
g.fillRect(0, 0, buf.getWidth(), buf.getHeight());
g.setComposite(savedComposite);
// Draw the figures onto the buffered image
setRenderingHints(g);
g.transform(transform);
for (Figure f : figures) {
f.draw(g);
}
g.dispose();
// Convert the image, if it does not have the specified image type
if (imageType != BufferedImage.TYPE_INT_ARGB) {
BufferedImage buf2 = new BufferedImage(
buf.getWidth(), buf.getHeight(),
imageType);
g = buf2.createGraphics();
setRenderingHints(g);
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC));
g.drawImage(buf, 0, 0, null);
g.dispose();
buf.flush();
buf = buf2;
}
return buf;
}
protected void setRenderingHints(Graphics2D g) {
g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,
RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_NORMALIZE);
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
}
}