Link to home
Start Free TrialLog in
Avatar of DavyBoyHayes
DavyBoyHayes

asked on

Using Batik to obtain a BufferedImage of an SVG

We have a project which requires us to us SVG files as icons. We want to render the SVG to BufferedImages to allow the one time render of the Icons (the icons will not animate during their lifetime on the screen). We have investigated ways of trying this, and have kinda hit our heads against a brick wall. We have tried using JSVGCanvas, and using the get offscreen method provided, which does work, but that means for each icon, we have the bloat of a JSVGCanvas to contend with, which is not a good thing. We then looked at a way of using just one JSVGCanvas as a render platform for all icons, which was a better solution, but not ideal, as we don't need the full bloat of swing, just the SVG image rendered into a BufferedImage. Next we looked at ways of using the Rasterizer App, which rasterizes SVG to Jpeg etc... which is almost what we after, and sure enough, inside the code of JPEGTranscoder, it actually uses a BufferedImage to render, then saves this to JPEG file. We tried creating our own BufferedImageTranscoder, but fell over quite early.
Has anybody got any solutions to offer. Big money (ok points) is on offer!
Avatar of CEHJ
CEHJ
Flag of United Kingdom of Great Britain and Northern Ireland image

>>We then looked at a way of using just one JSVGCanvas as a render platform for all icons

Can't you just use that to get a BufferedImage and then clone each one?
Avatar of DavyBoyHayes
DavyBoyHayes

ASKER

Each icon is different. What I meant here is that we had 1 JSVGCanvas, and we loaded the first SVG Document, then captured the BufferedImage, then loaded the next SVG Documetnt, and captured the next BufferedImage. It is a solution, but I am not happy with using Swing in the solution, as Swing can be quite bloated (although we are using Swing for the gui). I am trying to reduce the speed of loading and rendering.
It seems to me you can just do the following:

a. add a listener to the JSVGCanvas to tell when a source is fully loaded
b. get the graphics context
c. create a new BufferedImage
d. get *its* graphics context
e. use the source context to draw the image to the destination (the BufferedImage)

Obviously that would be done in a loop
Well, JSVGCanvas is inherently Swing isn't it? I'm not sure what other means are available. A lot of the overhead is probably xml parsing, which is a hit you'll take Swing or not.
Yes, that is the method we have at the minute. Unfortunately, we don't want to use a JSVGCanvas. We want to mimick the way that JSVGCanvas works, without having the Swing extra memory taken up.

Yes we want a single class whose responsibility is to take SVG documents and to return a BufferedImage. Ideally, the class would have a method

public BufferedImage renderSVGDocument(String strURI)

Who would return a BufferedImage on successful completion of a render, or null on non successful completion.
I don't know if you saw my comment at 03:51 before posting your last, but i don't know of any non-Swing stuff.
One thought we have is to have all the SVG files pre-read and parsed, so we already SVGOMDocuments loaded, and all we have to do is maybe

public BufferedImage renderSVGDocument(SVGOMDocument doc)

So the process at run-time would be:

Application Start-up
~~~~~~~~~~~~~~~~~~~~
Load SVG Files
Convert each into SVGOMDocument

Icons Required
~~~~~~~~~~~~~~
Has BufferedImage been rendered for current resolution?
N: buffImage = RenderSVGDocument(doc);
Y: draw buffImage onto desktop

The only time the Icons will be required to get re-rendered is when the output resolution is changed (ie zoom-in/out or printing).
Seems OK, but you don't make explicit whether *SVG* is to be re-rendered. That shouldn't be necessary, as you should be able to carry out transformations on the BufferedImages themselves i would have thought. Therefore i think SVG rendering would be a one time hit only.
The point behind the need for SVG icons rather than just standard raster ones, is that when the user zooms into the viewspace, the icon should not appear blocky, it should be smooth. Similarly, when printing out, if the print DPI is 720, then you have 10x's as much quality depth you can use. But if you are just dumping the BufferedImage at a transformed size, you are not using the full resolution of the printer, and this is bad for us. The point though for rendering the SVG to a BufferedImage is that while the user is moving round the screen in the current zoom level, they haven't got to wait for each icon to get re-rendered.
Yes i see. You could *try* the approach i mentioned, using anti-aliasing and setting appropriate hints though.
We have tried this method, and we are looking for a more low level method. We haven't used the anti-aliasing and hints though in our method, but that will come when we finalise the method we will use.
ASKER CERTIFIED SOLUTION
Avatar of CEHJ
CEHJ
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I have found the solution, and I am offering it to the global community. I thank you CEHJ for your suggestions, and whilst I don't feel that you've earned 500 points worth (though your answers are good), I cannot reduce the points now, so you will get them. Anyhoos, here goes...

Note the following code has been modified from org.apache.batik.apps.transcoder.* which was written by the Apache Software Foundation.

BufferedImageTranscoder.java
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/*
 * BufferedImageTranscoder.java
 *
 * Created on 13 March 2003, 14:09
 */

/**
 *
 * @author  hayesd
 */
/*****************************************************************************
 * 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.transcoder.image;

import java.awt.Color;
import java.awt.image.BufferedImage;

import org.apache.batik.transcoder.image.ImageTranscoder;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.TranscodingHints;
import org.apache.batik.transcoder.image.resources.Messages;

/**
 * This class is an <tt>ImageTranscoder</tt> that produces a BufferedImage.
 *
 * @author <a href="mailto:d.hayes@abm-uk.com">David Hayes</a>
 *
 */
public class BufferedImageTranscoder extends ImageTranscoder {

    // Contains the last rendered BufferedImage. A solution found to obtain the image after the Transcode() step in ImageTranscoder

    private BufferedImage biLast = null;

    /**
     * Constructs a new transcoder that produces BufferedImage images.
     */

    public BufferedImageTranscoder() {
        hints.put(ImageTranscoder.KEY_BACKGROUND_COLOR, Color.white);
    }

    /**
     * Creates a new ARGB image with the specified dimension.
     * @param width the image width in pixels
     * @param height the image height in pixels
     */
    public BufferedImage createImage(int width, int height) {
        return new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    }

    // Note this method does not need the Transcoder Output. It allows you to loosly assume that TranscoderOutput is of type BufferedImageTranscoderOutput, purely because it doesn't really care.

    /**
     * Writes the specified image to the specified output.
     * @param img the image to write
     * @param output the output where to store the image
     * @param TranscoderException if an error occured while storing the image
     */
    public void writeImage(BufferedImage img, TranscoderOutput output)
    throws TranscoderException {
        biLast = img;
    }

    public BufferedImage getLastRendered(){
        return biLast;
    }
}

BufferedImageTranscoderOutput.java
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/*
 * BufferedImageTranscoderOutput.java
 *
 * Created on 13 March 2003, 15:59
 */

/**
 *
 * @author  hayesd
 */

//package org.apache.batik.transcoder;

import java.awt.image.*;

import org.apache.batik.transcoder.*;

public class BufferedImageTranscoderOutput extends TranscoderOutput {

    /** Creates a new instance of BufferedImageTranscoderOutput */
    public BufferedImageTranscoderOutput() {
    }

}

SVGtoBufferedImageConverter.java
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/*
 * SVGtoBufferedImageConverter.java
 *
 * Created on 14 March 2003, 10:58
 */

/**
 *
 * @author  hayesd
 */

import org.apache.batik.transcoder.Transcoder;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.FileOutputStream;

import java.awt.image.BufferedImage;

public class SVGtoBufferedImageConverter{
   
    /** Creates a new instance of SVGtoBufferedImageConverter */
    public SVGtoBufferedImageConverter() {
    }
   
    // Takes the inputfile, strFileName, and renders the file to a BufferedImage;
    public BufferedImage renderSVG(String strFileName) throws Exception {
       
        BufferedImageTranscoder transcoder = new BufferedImageTranscoder();
        BufferedImage outputImage = null;
        transcode(strFileName, transcoder);
        outputImage = ((BufferedImageTranscoder) transcoder).getLastRendered();
        return outputImage;
    }
   
    // The method which calls the transcode method in BufferedImageTranscoder
    protected void transcode(String inputFile,
    BufferedImageTranscoder transcoder)
    throws Exception {
        TranscoderInput input = null;
        TranscoderOutput output = null;
       
        InputStream in = new FileInputStream(inputFile);
        input = new TranscoderInput(in);
        output = new BufferedImageTranscoderOutput();
        transcoder.transcode(input, output);    
    }
 
}


And finally, your code which calls it...

myprogram.java (snippet)
~~~~~~~~~~~~~~~~~~~~~~~~

SVGtoBufferedImageConverter converter = new SVGtoBufferedImageConverter();
BufferedImage biSVG = null;
try{
   biSVG = converter.renderSVG("c:\myfilename.svg");
}
catch(Exception e)
{
}

Et voila! Jobs a good un!
I feel I must point out that this is far from a complete solution, but is infact a good starting block for most people to work from. If I find a more complete solution, I will try and let the community know!
Not the final answer I was after unfortunately, though not due to CEHJ's fault. I finally figured out the answer, and posted. I couldn't reduce the points to reflect the level of help CEHJ provided (would have happily given 50), so 500 it is. Ah well, I guess there are some games in life you win, and there are some you don't...

Thanks for the help CEHJ!
 A bit late but it could be of help to anyone with similar problems. There is a package that deals with SVG graphics and it is compatible with Batik 1.1.1. Not sure if it will be of any help but you might be able to consult the source code (it's opne source anyway) and come up with a solution:

http://openmap.bbn.com/
and for the API: http://openmap.bbn.com/doc/api/ (you might want to take a look at the SVGFormatter).
Interesting. Thanks for the points.