You are viewing a plain text version of this content. The canonical link for it is here.
Posted to batik-users@xmlgraphics.apache.org by or...@io7m.com on 2016/06/18 16:05:26 UTC

Rendering a specific object from an SVG file?

Hello.

I have a set of SVG files. I know nothing about their contents other
than the fact that somewhere inside each file is an element with an id
called "button". I don't know if that element will be a group, a rectangle,
etc. It could be anything.

I want to load a file, and render only the given element with the
"button" id to a PNG file. The bounds of the rendered image should be
such that they contain the rendered object tightly with no margins.

This is the closest that I've been able to manage:

--8<--

import org.apache.batik.anim.dom.SAXSVGDocumentFactory;
import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.DocumentLoader;
import org.apache.batik.bridge.GVTBuilder;
import org.apache.batik.bridge.UserAgent;
import org.apache.batik.bridge.UserAgentAdapter;
import org.apache.batik.gvt.CompositeGraphicsNode;
import org.apache.batik.gvt.GraphicsNode;
import org.apache.batik.gvt.renderer.DynamicRenderer;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.util.XMLResourceDescriptor;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.imageio.ImageIO;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Objects;

public final class Main
{
  private Main()
  {

  }

  public static void main(final String[] args)
    throws IOException, TranscoderException
  {
    final String parser = XMLResourceDescriptor.getXMLParserClassName();
    final SAXSVGDocumentFactory f = new SAXSVGDocumentFactory(parser);
    final String uri = "/tmp/button.svg";
    final Document doc = f.createDocument(uri);

    final UserAgent agent = new UserAgentAdapter();
    final DocumentLoader loader = new DocumentLoader(agent);
    final BridgeContext ctx = new BridgeContext(agent, loader);
    ctx.setDynamicState(BridgeContext.DYNAMIC);
    final GVTBuilder builder = new GVTBuilder();
    builder.build(ctx, doc);

    final Element elem_node = doc.getElementById("button");
    final GraphicsNode elem_gnode = ctx.getGraphicsNode(elem_node);
    final Rectangle2D elem_bounds = elem_gnode.getBounds();
    System.out.println("Bounds: " + elem_bounds);

    {
      GraphicsNode node = elem_gnode;
      while (true) {
        System.out.println("Transform (g): " + node.getGlobalTransform());
        System.out.println("Transform:     " + node.getTransform());
        final CompositeGraphicsNode parent = elem_gnode.getParent();
        if (Objects.equals(parent, node)) {
          break;
        }
        node = parent;
      }
    }

    final double elem_x = elem_bounds.getX();
    final double elem_y = elem_bounds.getY();
    final double elem_width = elem_bounds.getWidth();
    final double elem_height = elem_bounds.getHeight();
    final int elem_width_i = (int) Math.ceil(elem_width);
    final int elem_height_i = (int) Math.ceil(elem_height);

    final DynamicRenderer renderer = new DynamicRenderer();
    renderer.setTransform(
      AffineTransform.getTranslateInstance(-elem_x, -elem_y));
    renderer.setTree(elem_gnode);
    renderer.updateOffScreen(elem_width_i, elem_height_i);
    final Rectangle r = new Rectangle(
      0,
      0,
      Integer.MAX_VALUE,
      Integer.MAX_VALUE);
    renderer.repaint(r);

    final BufferedImage image = renderer.getOffScreen();
    ImageIO.write(image, "PNG", new File("/tmp/button.png"));
  }
}

-->8--

The example file used here is:

  http://waste.io7m.com/2016/06/18/button.svg

The problem: This feels fragile. It has the feel of code that will
break as soon as it's presented with any SVG file that contains some
unusual combinations of transforms.

I had to do a lot of experimenting with transforms. Setting transforms
for any of the GVT nodes simply resulted in the transforms apparently
being silently ignored.

I eventually stumbled across setting an affine transform for the renderer 
such that it positions the rendered object at the corner of the rendered 
image. This feels like abuse of the API, but it's the only thing I could
get to actually have any effect on the output image at all.

Secondly, I have to pass Integer.MAX_VALUE as the width and height of
the rectangle to be updated. Originally, I tried passing elem_width_i
and elem_height_i, but this simply truncated the rendered image in
ways I don't fully understand. What exactly am I supposed to pass there?

What is the correct, reliable way to achieve what I'm trying to achieve?

M


---------------------------------------------------------------------
To unsubscribe, e-mail: batik-users-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: batik-users-help@xmlgraphics.apache.org