/*****************************************************************************
* 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.bridge;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Rectangle2D;
import java.net.URL;
import java.util.Map;
import org.apache.batik.dom.svg.SVGOMDocument;
import org.apache.batik.dom.util.XLinkSupport;
import org.apache.batik.util.XMLConstants;
import org.apache.batik.ext.awt.image.PadMode;
import org.apache.batik.ext.awt.image.renderable.AffineRable8Bit;
import org.apache.batik.ext.awt.image.renderable.Filter;
import org.apache.batik.ext.awt.image.renderable.PadRable8Bit;
import org.apache.batik.ext.awt.image.spi.ImageTagRegistry;
import org.apache.batik.gvt.GraphicsNode;
import org.apache.batik.util.ParsedURL;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.svg.SVGDocument;
/**
* Bridge class for the <feImage> element.
*
* @author <a href="mailto:tkormann@apache.org">Thierry Kormann</a>
* @version $Id: SVGFeImageElementBridge.java,v 1.19 2003/07/09 17:37:16 vhardy Exp $
*/
public class SVGFeImageElementBridge
extends AbstractSVGFilterPrimitiveElementBridge {
/**
* Constructs a new bridge for the <feImage> element.
*/
public SVGFeImageElementBridge() {}
/**
* Returns 'feImage'.
*/
public String getLocalName() {
return SVG_FE_IMAGE_TAG;
}
/**
* Creates a <tt>Filter</tt> primitive according to the specified
* parameters.
*
* @param ctx the bridge context to use
* @param filterElement the element that defines a filter
* @param filteredElement the element that references the filter
* @param filteredNode the graphics node to filter
*
* @param inputFilter the <tt>Filter</tt> that represents the current
* filter input if the filter chain.
* @param filterRegion the filter area defined for the filter chain
* the new node will be part of.
* @param filterMap a map where the mediator can map a name to the
* <tt>Filter</tt> it creates. Other <tt>FilterBridge</tt>s
* can then access a filter node from the filterMap if they
* know its name.
*/
public Filter createFilter(BridgeContext ctx,
Element filterElement,
Element filteredElement,
GraphicsNode filteredNode,
Filter inputFilter,
Rectangle2D filterRegion,
Map filterMap) {
// 'xlink:href' attribute
String uriStr = XLinkSupport.getXLinkHref(filterElement);
if (uriStr.length() == 0) {
throw new BridgeException(filterElement, ERR_ATTRIBUTE_MISSING,
new Object[] {"xlink:href"});
}
//
// According the the SVG specification, feImage behaves like
// <image> if it references an SVG document or a raster image
// and it behaves like a <use> if it references a document
// fragment.
//
// To provide this behavior, depending on whether the uri
// contains a fragment identifier, we create either an
// <image> or a <use> element and request the corresponding
// bridges to build the corresponding GraphicsNode for us.
//
// Then, we take care of the possible transformation needed
// from objectBoundingBox space to user space.
//
GraphicsNode gn = null;
Document document = filterElement.getOwnerDocument();
boolean isUse = (uriStr.indexOf("#") != -1);
Element contentElement = null;
if (isUse) {
contentElement = document.createElementNS(SVG_NAMESPACE_URI,
SVG_USE_TAG);
} else {
contentElement = document.createElementNS(SVG_NAMESPACE_URI,
SVG_IMAGE_TAG);
}
contentElement.setAttributeNS(XLinkSupport.XLINK_NAMESPACE_URI, XMLConstants.XLINK_PREFIX +
":" + SVG_HREF_ATTRIBUTE,
uriStr);
Element proxyElement = document.createElementNS(SVG_NAMESPACE_URI,
SVG_G_TAG);
proxyElement.appendChild(contentElement);
// feImage's default region is that of the filter chain.
Rectangle2D defaultRegion = filterRegion;
// Compute the transform from object bounding box to user
// space if needed.
AffineTransform at = new AffineTransform();
// 'primitiveUnits' attribute - default is userSpaceOnUse
short coordSystemType;
Element filterDefElement = (Element)(filterElement.getParentNode());
boolean isBBox = false;
String s = SVGUtilities.getChainableAttributeNS
(filterDefElement, null, SVG_PRIMITIVE_UNITS_ATTRIBUTE, ctx);
if (s.length() == 0) {
coordSystemType = SVGUtilities.USER_SPACE_ON_USE;
} else {
coordSystemType = SVGUtilities.parseCoordinateSystem
(filterDefElement, SVG_PRIMITIVE_UNITS_ATTRIBUTE, s);
}
if (coordSystemType == SVGUtilities.OBJECT_BOUNDING_BOX) {
isBBox = true;
at = SVGUtilities.toObjectBBox(at, filteredNode);
}
// get filter primitive chain region
Rectangle2D primitiveRegionUserSpace
= SVGUtilities.convertFilterPrimitiveRegion(filterElement,
filteredElement,
filteredNode,
defaultRegion,
filterRegion,
ctx);
Rectangle2D primitiveRegion = primitiveRegionUserSpace;
if (isBBox) {
try {
AffineTransform ati = at.createInverse();
primitiveRegion = ati.createTransformedShape(primitiveRegion).getBounds2D();
} catch (NoninvertibleTransformException nite) {
// Should never happen, seem above
throw new Error();
}
}
contentElement.setAttributeNS(null, SVG_X_ATTRIBUTE, "" + primitiveRegion.getX());
contentElement.setAttributeNS(null, SVG_Y_ATTRIBUTE, "" + primitiveRegion.getY());
contentElement.setAttributeNS(null, SVG_WIDTH_ATTRIBUTE, "" + primitiveRegion.getWidth());
contentElement.setAttributeNS(null, SVG_HEIGHT_ATTRIBUTE, "" + primitiveRegion.getHeight());
// System.err.println(">>>>>>>>>>>> primitiveRegion : " + primitiveRegion);
// System.err.println(">>>>>>>>>>>> at : " + at);
GraphicsNode node = ctx.getGVTBuilder().build(ctx, proxyElement);
Filter filter = node.getGraphicsNodeRable(true);
filter = new AffineRable8Bit(filter, at);
// handle the 'color-interpolation-filters' property
handleColorInterpolationFilters(filter, filterElement);
filter = new PadRable8Bit(filter, primitiveRegionUserSpace, PadMode.ZERO_PAD);
// update the filter Map
updateFilterMap(filterElement, filter, filterMap);
return filter;
}
/**
* Returns a Filter that represents a svg document or element as an image.
*
* @param ctx the bridge context
* @param primitiveRegion the primitive region
* @param Element the referenced element
* @param toBBoxNeeded true if there is a need to transform to ObjectBoundingBox
* space
* @param filterElement parent filter element
* @param filteredNode node to which the filter applies
*/
protected static Filter createSVGFeImage(BridgeContext ctx,
Rectangle2D primitiveRegion,
Element refElement,
boolean toBBoxNeeded,
Element filterElement,
GraphicsNode filteredNode) {
//
// <!> FIX ME
// Unresolved issue on the feImage behavior when referencing an
// image (PNG, JPEG or SVG image).
// VH & TK, 03/08/2002
// Furthermore, for feImage referencing doc fragment, should act
// like a <use>, i.e., CSS cascading and the whole zing bang.
//
GraphicsNode node = ctx.getGVTBuilder().build(ctx, refElement);
Filter filter = node.getGraphicsNodeRable(true);
AffineTransform at = new AffineTransform();
if (toBBoxNeeded){
// 'primitiveUnits' attribute - default is userSpaceOnUse
short coordSystemType;
Element filterDefElement = (Element)(filterElement.getParentNode());
String s = SVGUtilities.getChainableAttributeNS
(filterDefElement, null, SVG_PRIMITIVE_UNITS_ATTRIBUTE, ctx);
if (s.length() == 0) {
coordSystemType = SVGUtilities.USER_SPACE_ON_USE;
} else {
coordSystemType = SVGUtilities.parseCoordinateSystem
(filterDefElement, SVG_PRIMITIVE_UNITS_ATTRIBUTE, s);
}
if (coordSystemType == SVGUtilities.OBJECT_BOUNDING_BOX) {
at = SVGUtilities.toObjectBBox(at, filteredNode);
}
Rectangle2D bounds = filteredNode.getGeometryBounds();
at.preConcatenate(AffineTransform.getTranslateInstance
(primitiveRegion.getX() - bounds.getX(),
primitiveRegion.getY() - bounds.getY()));
} else {
// Need to translate the image to the x, y coordinate to
// have the same behavior as the <use> element
at.translate(primitiveRegion.getX(), primitiveRegion.getY());
}
return new AffineRable8Bit(filter, at);
}
/**
* Returns a Filter that represents an raster image (JPG or PNG).
*
* @param ctx the bridge context
* @param primitiveRegion the primitive region
* @param url the url of the image
*/
protected static Filter createRasterFeImage(BridgeContext ctx,
Rectangle2D primitiveRegion,
ParsedURL purl) {
// Need to fit the raster image to the filter region so that
// we have the same behavior as raster images in the <image> element.
Filter filter = ImageTagRegistry.getRegistry().readURL(purl);
Rectangle2D bounds = filter.getBounds2D();
AffineTransform scale = new AffineTransform();
scale.translate(primitiveRegion.getX(), primitiveRegion.getY());
scale.scale(primitiveRegion.getWidth()/(bounds.getWidth()-1),
primitiveRegion.getHeight()/(bounds.getHeight()-1));
scale.translate(-bounds.getX(), -bounds.getY());
return new AffineRable8Bit(filter, scale);
}
}