/*****************************************************************************
* 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.gvt.filter;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Composite;
import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Rectangle2D;
import java.awt.image.RenderedImage;
import java.awt.image.BufferedImage;
import java.awt.image.renderable.RenderContext;
import java.awt.image.renderable.RenderableImage;
import java.util.Map;
import org.apache.batik.gvt.GraphicsNode;
import org.apache.batik.gvt.GraphicsNodeRenderContext;
import org.apache.batik.gvt.filter.GraphicsNodeRable;
import org.apache.batik.ext.awt.RenderingHintsKeyExt;
import org.apache.batik.ext.awt.image.GraphicsUtil;
import org.apache.batik.ext.awt.image.renderable.Filter;
import org.apache.batik.ext.awt.image.renderable.AbstractRable;
import org.apache.batik.ext.awt.image.renderable.PaintRable;
import org.apache.batik.ext.awt.image.rendered.CachableRed;
import org.apache.batik.ext.awt.image.rendered.TranslateRed;
import org.apache.batik.ext.awt.image.SVGComposite;
/**
* This implementation of RenderableImage will render its input
* GraphicsNode into a BufferedImage upon invokation of one of its
* createRendering methods.
*
* @author <a href="mailto:vincent.hardy@eng.sun.com">Vincent Hardy</a>
* @version $Id: GraphicsNodeRable8Bit.java,v 1.12 2001/05/15 13:55:39 deweese Exp $
*/
public class GraphicsNodeRable8Bit
extends AbstractRable
implements GraphicsNodeRable, PaintRable {
private AffineTransform cachedGn2dev = null;
private AffineTransform cachedUsr2dev = null;
private CachableRed cachedRed = null;
/**
* Should GraphicsNodeRable call primitivePaint or Paint.
*/
private boolean usePrimitivePaint = true;
/**
* The current render context for the filtered GraphicsNode.
*/
private GraphicsNodeRenderContext gnrc = null;
/**
* Returns true if this Rable get's it's contents by calling
* primitivePaint on the associated <tt>GraphicsNode</tt> or
* false if it uses paint.
*/
public boolean getUsePrimitivePaint() {
return usePrimitivePaint;
}
/**
* Set to true if this Rable should get it's contents by calling
* primitivePaint on the associated <tt>GraphicsNode</tt> or false
* if it should use paint.
*/
public void setUsePrimitivePaint(boolean usePrimitivePaint) {
this.usePrimitivePaint = usePrimitivePaint;
}
/**
* GraphicsNode this image can render
*/
private GraphicsNode node;
/**
* Returns the <tt>GraphicsNode</tt> rendered by this image
*/
public GraphicsNode getGraphicsNode(){
return node;
}
/**
* Sets the <tt>GraphicsNode</tt> this image should render
*/
public void setGraphicsNode(GraphicsNode node){
if(node == null){
throw new IllegalArgumentException();
}
this.node = node;
}
/**
* @param node The GraphicsNode this image should represent
*/
public GraphicsNodeRable8Bit(GraphicsNode node,
GraphicsNodeRenderContext gnrc){
if(node == null)
throw new IllegalArgumentException();
this.node = node;
this.gnrc = gnrc;
this.usePrimitivePaint = true;
}
/**
* @param node The GraphicsNode this image should represent
* @param props The Properties for this image.
*/
public GraphicsNodeRable8Bit(GraphicsNode node,
GraphicsNodeRenderContext gnrc,
Map props){
super((Filter)null, props);
if(node == null)
throw new IllegalArgumentException();
this.node = node;
this.gnrc = gnrc;
this.usePrimitivePaint = true;
}
/**
* @param node the GraphicsNode this image should represent
* @param usePrimitivePaint indicates if the image should
* include any filters or mask operations on <tt>node</tt>
*/
public GraphicsNodeRable8Bit(GraphicsNode node,
boolean usePrimitivePaint){
if(node == null)
throw new IllegalArgumentException();
this.node = node;
this.usePrimitivePaint = usePrimitivePaint;
}
/**
* Returns the bounds of this Rable in the user coordinate system.
*/
public Rectangle2D getBounds2D(){
if (usePrimitivePaint){
Rectangle2D primitiveBounds
= node.getPrimitiveBounds(getGraphicsNodeRenderContext());
if(primitiveBounds != null){
return (Rectangle2D)(primitiveBounds.clone());
}
else{
return new Rectangle2D.Double(0, 0, 0, 0);
}
}
// When not using Primitive paint we return out bounds in our
// parent's user space. This makes sense since this is the
// space that we will draw our selves into (since paint unlike
// primitivePaint incorporates the transform from our user
// space to our parents user space).
Rectangle2D bounds = node.getBounds(getGraphicsNodeRenderContext());
if(bounds == null){
return new Rectangle2D.Double(0, 0, 0, 0);
}
AffineTransform at = node.getTransform();
if (at != null){
bounds = at.createTransformedShape(bounds).getBounds2D();
}
return bounds;
}
/**
* Returns true if successive renderings (that is, calls to
* createRendering() or createScaledRendering()) with the same arguments
* may produce different results. This method may be used to
* determine whether an existing rendering may be cached and
* reused. It is always safe to return true.
*/
public boolean isDynamic(){
return false;
}
/**
* Should perform the equivilent action as
* createRendering followed by drawing the RenderedImage to
* Graphics2D, or return false.
*
* @param g2d The Graphics2D to draw to.
* @return true if the paint call succeeded, false if
* for some reason the paint failed (in which
* case a createRendering should be used).
*/
public boolean paintRable(Graphics2D g2d) {
// This optimization only apply if we are using
// SrcOver. Otherwise things break...
Composite c = g2d.getComposite();
if (!SVGComposite.OVER.equals(c))
return false;
ColorSpace g2dCS = GraphicsUtil.getDestinationColorSpace(g2d);
if ((g2dCS == null) ||
(g2dCS != ColorSpace.getInstance(ColorSpace.CS_sRGB))){
// Only draw directly into sRGB destinations...
return false;
}
// System.out.println("drawImage GNR: " + g2dCS);
GraphicsNode gn = getGraphicsNode();
GraphicsNodeRenderContext gnrc
= GraphicsNodeRenderContext.getGraphicsNodeRenderContext(g2d);
if (getUsePrimitivePaint()){
gn.primitivePaint(g2d, gnrc);
}
else{
gn.paint(g2d, gnrc);
}
// Paint did the work...
return true;
}
/**
* Creates a RenderedImage that represented a rendering of this image
* using a given RenderContext. This is the most general way to obtain a
* rendering of a RenderableImage.
*
* <p> The created RenderedImage may have a property identified
* by the String HINTS_OBSERVED to indicate which RenderingHints
* (from the RenderContext) were used to create the image.
* In addition any RenderedImages
* that are obtained via the getSources() method on the created
* RenderedImage may have such a property.
*
* @param renderContext the RenderContext to use to produce the rendering.
* @return a RenderedImage containing the rendered data.
*/
public RenderedImage createRendering(RenderContext renderContext){
// Get user space to device space transform
AffineTransform usr2dev = renderContext.getTransform();
AffineTransform gn2dev;
if (usr2dev == null) {
usr2dev = new AffineTransform();
gn2dev = usr2dev;
} else {
gn2dev = (AffineTransform)usr2dev.clone();
}
// Get the nodes transform (so we can pick up changes in this.
AffineTransform gn2usr = node.getTransform();
if (gn2usr != null) {
gn2dev.concatenate(gn2usr);
}
if ((cachedGn2dev != null) &&
(gn2dev.getScaleX() == cachedGn2dev.getScaleX()) &&
(gn2dev.getScaleY() == cachedGn2dev.getScaleY()) &&
(gn2dev.getShearX() == cachedGn2dev.getShearX()) &&
(gn2dev.getShearY() == cachedGn2dev.getShearY()))
{
// Just some form of Translation
double deltaX = (usr2dev.getTranslateX() -
cachedUsr2dev.getTranslateX());
double deltaY = (usr2dev.getTranslateY() -
cachedUsr2dev.getTranslateY());
// System.out.println("Using Cached Red!!! " +
// deltaX + "x" + deltaY);
if ((deltaX ==0) && (deltaY == 0))
// Actually no translation
return cachedRed;
// System.out.println("Delta: [" + deltaX + ", " + deltaY + "]");
// Integer translation in device space..
if ((deltaX == (int)deltaX) &&
(deltaY == (int)deltaY)) {
return new TranslateRed
(cachedRed,
(int)Math.round(cachedRed.getMinX()+deltaX),
(int)Math.round(cachedRed.getMinY()+deltaY));
}
}
// Fell through let's do a new rendering...
if (false) {
System.out.println("Not using Cached Red: " + usr2dev);
System.out.println("Old: " + cachedUsr2dev);
}
Rectangle2D bounds2D = getBounds2D();
if((bounds2D.getWidth() > 0) &&
(bounds2D.getHeight() > 0)) {
cachedUsr2dev = (AffineTransform)usr2dev.clone();
cachedGn2dev = gn2dev;
cachedRed = new GraphicsNodeRed8Bit
(node, usr2dev, getGraphicsNodeRenderContext(),
usePrimitivePaint, renderContext.getRenderingHints());
return cachedRed;
}
return null;
}
protected void setGraphicsNodeRenderContext(GraphicsNodeRenderContext rc) {
this.gnrc = rc;
}
protected GraphicsNodeRenderContext getGraphicsNodeRenderContext() {
return gnrc;
}
}