/*****************************************************************************
* Copyright (C) The Apache Software Foundation. All rights reserved. *
* ------------------------------------------------------------------------- *
* This software is published under the terms of the Apache Software License *
* version 1.1, a copy of which has been included with this distribution in *
* the LICENSE file. *
*****************************************************************************/
package org.apache.batik.svggen;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.RenderableImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import org.w3c.dom.Element;
/**
* This class is a default implementation of the GenericImageHandler
* for handlers implementing a caching strategy.
*
* @author <a href="mailto:vincent.hardy@eng.sun.com">Vincent Hardy</a>
* @version $Id: DefaultCachedImageHandler.java,v 1.5 2003/07/04 15:25:37 vhardy Exp $
* @see org.apache.batik.svggen.SVGGraphics2D
*/
public abstract class DefaultCachedImageHandler
implements CachedImageHandler,
SVGSyntax,
ErrorConstants {
// duplicate the string here to remove dependencies on
// org.apache.batik.dom.util.XLinkSupport
static final String XLINK_NAMESPACE_URI =
"http://www.w3.org/1999/xlink";
static final AffineTransform IDENTITY = new AffineTransform();
// for createGraphics method.
private static Method createGraphics = null;
private static boolean initDone = false;
private final static Class[] paramc = new Class[] {BufferedImage.class};
private static Object[] paramo = null;
protected ImageCacher imageCacher;
/**
* The image cache can be used by subclasses for efficient image storage
*/
public ImageCacher getImageCacher() {
return imageCacher;
}
void setImageCacher(ImageCacher imageCacher) {
if (imageCacher == null){
throw new IllegalArgumentException();
}
// Save current DOMTreeManager if any
DOMTreeManager dtm = null;
if (this.imageCacher != null){
dtm = this.imageCacher.getDOMTreeManager();
}
this.imageCacher = imageCacher;
if (dtm != null){
this.imageCacher.setDOMTreeManager(dtm);
}
}
/**
* This <tt>GenericImageHandler</tt> implementation does not
* need to interact with the DOMTreeManager.
*/
public void setDOMTreeManager(DOMTreeManager domTreeManager){
imageCacher.setDOMTreeManager(domTreeManager);
}
/**
* This method creates a <code>Graphics2D</code> from a
* <code>BufferedImage</code>. If Batik extensions to AWT are
* in the CLASSPATH it uses them, otherwise, it uses the regular
* AWT method.
*/
private static Graphics2D createGraphics(BufferedImage buf) {
if (!initDone) {
try {
Class clazz = Class.forName("org.apache.batik.ext.awt.image.GraphicsUtil");
createGraphics = clazz.getMethod("createGraphics", paramc);
paramo = new Object[1];
} catch (Throwable t) {
// happen only if Batik extensions are not their
} finally {
initDone = true;
}
}
if (createGraphics == null)
return buf.createGraphics();
else {
paramo[0] = buf;
Graphics2D g2d = null;
try {
g2d = (Graphics2D)createGraphics.invoke(null, paramo);
} catch (Exception e) {
// should not happened
}
return g2d;
}
}
/**
* Creates an Element which can refer to an image.
* Note that no assumptions should be made by the caller about the
* corresponding SVG tag. By default, an <image> tag is
* used, but the {@link CachedImageHandlerBase64Encoder}, for
* example, overrides this method to use a different tag.
*/
public Element createElement(SVGGeneratorContext generatorContext) {
// Create a DOM Element in SVG namespace to refer to an image
Element imageElement =
generatorContext.getDOMFactory().createElementNS
(SVG_NAMESPACE_URI, SVG_IMAGE_TAG);
return imageElement;
}
/**
* The handler sets the xlink:href tag and returns a transform
*/
public AffineTransform handleImage(Image image,
Element imageElement,
int x, int y,
int width, int height,
SVGGeneratorContext generatorContext) {
int imageWidth = image.getWidth(null);
int imageHeight = image.getHeight(null);
AffineTransform af = null;
if(imageWidth == 0 || imageHeight == 0 ||
width == 0 || height == 0) {
// Forget about it
handleEmptyImage(imageElement);
} else {
// First set the href
try {
handleHREF(image, imageElement, generatorContext);
} catch (SVGGraphics2DIOException e) {
try {
generatorContext.errorHandler.handleError(e);
} catch (SVGGraphics2DIOException io) {
// we need a runtime exception because
// java.awt.Graphics2D method doesn't throw exceptions..
throw new SVGGraphics2DRuntimeException(io);
}
}
// Then create the transformation:
// Because we cache image data, the stored image may
// need to be scaled.
af = handleTransform(imageElement, (double) x, (double) y,
(double) imageWidth, (double) imageHeight,
(double) width, (double) height,
generatorContext);
}
return af;
}
/**
* The handler sets the xlink:href tag and returns a transform
*/
public AffineTransform handleImage(RenderedImage image,
Element imageElement,
int x, int y,
int width, int height,
SVGGeneratorContext generatorContext) {
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
AffineTransform af = null;
if(imageWidth == 0 || imageHeight == 0 ||
width == 0 || height == 0) {
// Forget about it
handleEmptyImage(imageElement);
} else {
// First set the href
try {
handleHREF(image, imageElement, generatorContext);
} catch (SVGGraphics2DIOException e) {
try {
generatorContext.errorHandler.handleError(e);
} catch (SVGGraphics2DIOException io) {
// we need a runtime exception because
// java.awt.Graphics2D method doesn't throw exceptions..
throw new SVGGraphics2DRuntimeException(io);
}
}
// Then create the transformation:
// Because we cache image data, the stored image may
// need to be scaled.
af = handleTransform(imageElement, (double) x, (double) y,
(double) imageWidth, (double) imageHeight,
(double) width, (double) height, generatorContext);
}
return af;
}
/**
* The handler sets the xlink:href tag and returns a transform
*/
public AffineTransform handleImage(RenderableImage image,
Element imageElement,
double x, double y,
double width, double height,
SVGGeneratorContext generatorContext) {
double imageWidth = image.getWidth();
double imageHeight = image.getHeight();
AffineTransform af = null;
if(imageWidth == 0 || imageHeight == 0 ||
width == 0 || height == 0) {
// Forget about it
handleEmptyImage(imageElement);
} else {
// First set the href
try {
handleHREF(image, imageElement, generatorContext);
} catch (SVGGraphics2DIOException e) {
try {
generatorContext.errorHandler.handleError(e);
} catch (SVGGraphics2DIOException io) {
// we need a runtime exception because
// java.awt.Graphics2D method doesn't throw exceptions..
throw new SVGGraphics2DRuntimeException(io);
}
}
// Then create the transformation:
// Because we cache image data, the stored image may
// need to be scaled.
af = handleTransform(imageElement, x,y,
imageWidth, imageHeight,
width, height, generatorContext);
}
return af;
}
/**
* Determines the transformation needed to get the cached image to
* scale & position properly. Sets x and y attributes on the element
* accordingly.
*/
protected AffineTransform handleTransform(Element imageElement,
double x, double y,
double srcWidth,
double srcHeight,
double dstWidth,
double dstHeight,
SVGGeneratorContext generatorContext) {
// In this the default case, <image> element, we just
// set x, y, width and height attributes.
// No additional transform is necessary.
imageElement.setAttributeNS(null,
SVG_X_ATTRIBUTE,
generatorContext.doubleString(x));
imageElement.setAttributeNS(null,
SVG_Y_ATTRIBUTE,
generatorContext.doubleString(y));
imageElement.setAttributeNS(null,
SVG_WIDTH_ATTRIBUTE,
generatorContext.doubleString(dstWidth));
imageElement.setAttributeNS(null,
SVG_HEIGHT_ATTRIBUTE,
generatorContext.doubleString(dstHeight));
return null;
}
protected void handleEmptyImage(Element imageElement) {
imageElement.setAttributeNS(XLINK_NAMESPACE_URI,
ATTR_XLINK_HREF, "");
imageElement.setAttributeNS(null, SVG_WIDTH_ATTRIBUTE, "0");
imageElement.setAttributeNS(null, SVG_HEIGHT_ATTRIBUTE, "0");
}
/**
* The handler should set the xlink:href tag and the width and
* height attributes.
*/
public void handleHREF(Image image, Element imageElement,
SVGGeneratorContext generatorContext)
throws SVGGraphics2DIOException {
if (image == null)
throw new SVGGraphics2DRuntimeException(ERR_IMAGE_NULL);
int width = image.getWidth(null);
int height = image.getHeight(null);
if (width==0 || height==0) {
handleEmptyImage(imageElement);
} else {
if (image instanceof RenderedImage) {
handleHREF((RenderedImage)image, imageElement,
generatorContext);
} else {
BufferedImage buf = buildBufferedImage(new Dimension(width, height));
Graphics2D g = createGraphics(buf);
g.drawImage(image, 0, 0, null);
g.dispose();
handleHREF((RenderedImage)buf, imageElement,
generatorContext);
}
}
}
/**
* This method creates a BufferedImage of the right size and type
* for the derived class.
*/
public BufferedImage buildBufferedImage(Dimension size){
return new BufferedImage(size.width, size.height, getBufferedImageType());
}
/**
* This template method should set the xlink:href attribute on the input
* Element parameter
*/
protected void handleHREF(RenderedImage image, Element imageElement,
SVGGeneratorContext generatorContext)
throws SVGGraphics2DIOException {
//
// Create an buffered image if necessary
//
BufferedImage buf = null;
if (image instanceof BufferedImage
&&
((BufferedImage)image).getType() == getBufferedImageType()){
buf = (BufferedImage)image;
} else {
Dimension size = new Dimension(image.getWidth(), image.getHeight());
buf = buildBufferedImage(size);
Graphics2D g = createGraphics(buf);
g.drawRenderedImage(image, IDENTITY);
g.dispose();
}
//
// Cache image and set xlink:href
//
cacheBufferedImage(imageElement, buf, generatorContext);
}
/**
* This method will delegate to the <tt>handleHREF</tt> which
* uses a <tt>RenderedImage</tt>
*/
protected void handleHREF(RenderableImage image, Element imageElement,
SVGGeneratorContext generatorContext)
throws SVGGraphics2DIOException {
// Create an buffered image where the image will be drawn
Dimension size = new Dimension((int)Math.ceil(image.getWidth()),
(int)Math.ceil(image.getHeight()));
BufferedImage buf = buildBufferedImage(size);
Graphics2D g = createGraphics(buf);
g.drawRenderableImage(image, IDENTITY);
g.dispose();
handleHREF((RenderedImage)buf, imageElement, generatorContext);
}
protected void cacheBufferedImage(Element imageElement,
BufferedImage buf,
SVGGeneratorContext generatorContext)
throws SVGGraphics2DIOException {
ByteArrayOutputStream os;
if (generatorContext == null)
throw new SVGGraphics2DRuntimeException(ERR_CONTEXT_NULL);
try {
os = new ByteArrayOutputStream();
// encode the image in memory
encodeImage(buf, os);
os.flush();
os.close();
} catch (IOException e) {
// should not happen since we do in-memory processing
throw new SVGGraphics2DIOException(ERR_UNEXPECTED, e);
}
// ask the cacher for a reference
String ref = imageCacher.lookup(os,
buf.getWidth(),
buf.getHeight(),
generatorContext);
// set the URL
imageElement.setAttributeNS(XLINK_NAMESPACE_URI,
ATTR_XLINK_HREF,
getRefPrefix() + ref);
}
/**
* Should return the prefix with wich the image reference
* should be pre-concatenated.
*/
public abstract String getRefPrefix();
/**
* Derived classes should implement this method and encode the input
* BufferedImage as needed
*/
public abstract void encodeImage(BufferedImage buf, OutputStream os)
throws IOException;
/**
* This template method should be overridden by derived classes to
* declare the image type they need for saving to file.
*/
public abstract int getBufferedImageType();
}