/******************************************************************************* * Mission Control Technologies, Copyright (c) 2009-2012, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * * The MCT platform is licensed under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0. * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * * MCT includes source code licensed under additional open source licenses. See * the MCT Open Source Licenses file included with this distribution or the About * MCT Licenses dialog available at runtime from the MCT Help menu for additional * information. *******************************************************************************/ package gov.nasa.arc.mct.graphics.view; import java.awt.Dimension; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import org.apache.batik.bridge.BridgeContext; import org.apache.batik.bridge.DocumentLoader; import org.apache.batik.bridge.UserAgent; import org.apache.batik.bridge.UserAgentAdapter; import org.apache.batik.gvt.GraphicsNode; import org.apache.batik.gvt.renderer.ConcreteImageRendererFactory; import org.apache.batik.gvt.renderer.ImageRenderer; import org.apache.batik.swing.gvt.GVTTreeRenderer; import org.apache.batik.swing.gvt.GVTTreeRendererEvent; import org.apache.batik.swing.gvt.GVTTreeRendererListener; import org.apache.batik.swing.svg.GVTTreeBuilder; import org.apache.batik.swing.svg.GVTTreeBuilderEvent; import org.apache.batik.swing.svg.GVTTreeBuilderListener; import org.apache.batik.swing.svg.SVGDocumentLoader; import org.apache.batik.swing.svg.SVGDocumentLoaderEvent; import org.apache.batik.swing.svg.SVGDocumentLoaderListener; import org.w3c.dom.svg.SVGDocument; /** * An SVGRasterizer renders SVG graphics to BufferedImages at * requested resolutions (does not preserve aspect ratio) * * @author vwoeltje * */ public class SVGRasterizer { private Dimension nextRenderingSize = null; private Dimension lastRenderingSize = null; private SVGDocument svgDocument = null; private BufferedImage latestImage = null; private GraphicsNode graphicsNode = null; private Runnable callback = null; private boolean isLoading = true; /** * Create a new rasterizer to convert an SVG document * to BufferedImages, for display on a specific component * Note that loading/parsing and rendering are both done * asynchronously. * @param documentURL the URL of the document to rasterize */ public SVGRasterizer(String documentURL) { UserAgent userAgent = new UserAgentAdapter(); SVGDocumentLoader loader = new SVGDocumentLoader(documentURL, new DocumentLoader(userAgent)); loader.addSVGDocumentLoaderListener(new SVGRasterizerListener()); /* Note that Batik uses the context class loader for loading SAX Parser, etc. * Context class loader is inappropriate under the Felix implementation of OSGi, * so replace it with the regular class loader. */ loader.setContextClassLoader(loader.getClass().getClassLoader()); loader.start(); } /** * Inject behavior to be called when rendering is complete * @param callback the behavior to run after rendering */ public void setCallback(Runnable callback) { this.callback = callback; } /** * Request a raster image of the loaded SVG document, * at a specified width and height. * Note that rendering will be done asynchronously; * subsequent calls to getLatestImage() will return * the rendered image once complete (and the previously * rendered image until then) * @param width the width in pixels at which to render * @param height the height in pixels at which to render */ public void requestRender(int width, int height) { width = (width < 1) ? 1 : width; height = (height < 1) ? 1 : height; if (latestImage != null && width == latestImage.getWidth() && height == latestImage.getHeight()) return; nextRenderingSize = new Dimension (width, height); renderIfReady(); } /** * Get the most recently rendered raster image of this * SVG document. This may be null if no renders have * completed, and may not match the size of the last * rendering request if rendering is incomplete. * @return */ public BufferedImage getLatestImage() { return latestImage; } /** * Check to see if document loading failed * @return true if the document could not be loaded/parsed; * false if it was successful, or is still loading */ public boolean hasFailed() { return graphicsNode == null && !isLoading; } /** * Check to see if the document has been loaded & parsed * @return true if loaded; otherwise false */ public boolean isLoaded() { return graphicsNode != null; } /** * Check to see if any rendering operation has completed * (not necessarily the most recently requested one) * @return true if an image is available; false if not */ public boolean isRendered() { return latestImage != null; } /** * Check to see if all rendering options have completed * @return true if an up-to-date image is available */ public boolean isCurrent() { return latestImage != null && nextRenderingSize == null && lastRenderingSize == null; } /** * Check to see if the document is in the process of loading * & parsing. * @return true if loading & parsing; otherwise false */ public boolean isLoading() { return isLoading; } private synchronized void renderIfReady() { if (graphicsNode == null) return; if (nextRenderingSize == null) return; if (lastRenderingSize != null) return; // Still waiting Dimension size = nextRenderingSize; Rectangle2D bounds = graphicsNode.getPrimitiveBounds(); lastRenderingSize = nextRenderingSize; nextRenderingSize = null; // start renderer... double widthScale = size.getWidth() / bounds.getWidth(); double heightScale = size.getHeight() / bounds.getHeight(); AffineTransform renderTransform = new AffineTransform(); renderTransform.scale(widthScale, heightScale); renderTransform.translate(-bounds.getMinX(), -bounds.getMinY()); ImageRenderer renderer = new ConcreteImageRendererFactory().createStaticImageRenderer(); renderer.setTree(graphicsNode); GVTTreeRenderer gvtRenderer = new GVTTreeRenderer( renderer, renderTransform, true, bounds, size.width, size.height); gvtRenderer.addGVTTreeRendererListener(new SVGRasterizerListener()); gvtRenderer.setContextClassLoader(gvtRenderer.getClass().getClassLoader()); gvtRenderer.start(); } private class SVGRasterizerListener implements SVGDocumentLoaderListener, GVTTreeBuilderListener, GVTTreeRendererListener { /* SVGDocumentLoaderListener */ @Override public void documentLoadingCancelled(SVGDocumentLoaderEvent arg0) { isLoading = false; if (callback != null) callback.run(); } @Override public void documentLoadingCompleted(SVGDocumentLoaderEvent arg0) { svgDocument = arg0.getSVGDocument(); GVTTreeBuilder gvtBuilder; gvtBuilder = new GVTTreeBuilder(svgDocument, new BridgeContext(new UserAgentAdapter())); gvtBuilder.addGVTTreeBuilderListener(this); gvtBuilder.setContextClassLoader(gvtBuilder.getClass().getClassLoader()); gvtBuilder.start(); } @Override public void documentLoadingFailed(SVGDocumentLoaderEvent arg0) { isLoading = false; if (callback != null) callback.run(); } @Override public void documentLoadingStarted(SVGDocumentLoaderEvent arg0) { } /* GVTTreeBuilderListener */ @Override public void gvtBuildCancelled(GVTTreeBuilderEvent arg0) { isLoading = false; if (callback != null) callback.run(); } @Override public void gvtBuildCompleted(GVTTreeBuilderEvent arg0) { graphicsNode = arg0.getGVTRoot(); renderIfReady(); isLoading = false; } @Override public void gvtBuildFailed(GVTTreeBuilderEvent arg0) { isLoading = false; if (callback != null) callback.run(); } @Override public void gvtBuildStarted(GVTTreeBuilderEvent arg0) { } /* GVTTreeRendererListener */ @Override public void gvtRenderingCancelled(GVTTreeRendererEvent arg0) { } @Override public synchronized void gvtRenderingCompleted(GVTTreeRendererEvent arg0) { BufferedImage img = new BufferedImage(lastRenderingSize.width, lastRenderingSize.height, BufferedImage.TYPE_INT_ARGB); img.getGraphics().drawImage(arg0.getImage(), 0, 0, lastRenderingSize.width, lastRenderingSize.height, 0, 0, lastRenderingSize.width, lastRenderingSize.height, null); latestImage = img; lastRenderingSize = null; if (callback != null) callback.run(); } @Override public void gvtRenderingFailed(GVTTreeRendererEvent arg0) { if (callback != null) callback.run(); } @Override public void gvtRenderingPrepare(GVTTreeRendererEvent arg0) { } @Override public void gvtRenderingStarted(GVTTreeRendererEvent arg0) { } } }