diff options
Diffstat (limited to 'src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPlot.java')
-rw-r--r-- | src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPlot.java | 179 |
1 files changed, 102 insertions, 77 deletions
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPlot.java b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPlot.java index 14889fb0..81299b10 100644 --- a/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPlot.java +++ b/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPlot.java @@ -4,7 +4,7 @@ package de.lmu.ifi.dbs.elki.visualization.svg; This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures - Copyright (C) 2011 + Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -23,15 +23,12 @@ package de.lmu.ifi.dbs.elki.visualization.svg; along with this program. If not, see <http://www.gnu.org/licenses/>. */ -import java.io.ByteArrayOutputStream; +import java.awt.image.BufferedImage; import java.io.File; -import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.lang.ref.WeakReference; -import java.net.URI; -import java.net.URISyntaxException; import java.util.Collection; import java.util.HashMap; @@ -44,31 +41,25 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.apache.batik.dom.svg.SVGDOMImplementation; -import org.apache.batik.dom.util.DOMUtilities; -import org.apache.batik.svggen.SVGSyntax; import org.apache.batik.transcoder.Transcoder; import org.apache.batik.transcoder.TranscoderException; import org.apache.batik.transcoder.TranscoderInput; import org.apache.batik.transcoder.TranscoderOutput; +import org.apache.batik.transcoder.XMLAbstractTranscoder; import org.apache.batik.transcoder.image.JPEGTranscoder; import org.apache.batik.transcoder.image.PNGTranscoder; -import org.apache.batik.util.Base64EncoderStream; import org.apache.batik.util.SVGConstants; -import org.apache.fop.render.ps.EPSTranscoder; -import org.apache.fop.render.ps.PSTranscoder; -import org.apache.fop.svg.PDFTranscoder; import org.w3c.dom.DOMImplementation; import org.w3c.dom.DocumentType; import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; import org.w3c.dom.events.Event; import org.w3c.dom.svg.SVGDocument; import org.w3c.dom.svg.SVGPoint; import de.lmu.ifi.dbs.elki.logging.LoggingUtil; import de.lmu.ifi.dbs.elki.utilities.FileUtil; -import de.lmu.ifi.dbs.elki.utilities.xml.XMLNodeListIterator; +import de.lmu.ifi.dbs.elki.visualization.batikutil.CloneInlineImages; +import de.lmu.ifi.dbs.elki.visualization.batikutil.ThumbnailTranscoder; import de.lmu.ifi.dbs.elki.visualization.css.CSSClass; import de.lmu.ifi.dbs.elki.visualization.css.CSSClassManager; import de.lmu.ifi.dbs.elki.visualization.css.CSSClassManager.CSSNamingConflict; @@ -165,12 +156,12 @@ public class SVGPlot { // create a CSS class manager. cssman = new CSSClassManager(); } - + /** * Clean up the plot. */ public void dispose() { - getUpdateRunner().clear(); + runner.clear(); } /** @@ -296,6 +287,7 @@ public class SVGPlot { /** * Convenience method to add a CSS class or log an error. + * * @param cls CSS class to add. */ public void addCSSClassOrLogError(CSSClass cls) { @@ -303,7 +295,7 @@ public class SVGPlot { cssman.addClass(cls); } catch(CSSNamingConflict e) { - de.lmu.ifi.dbs.elki.logging.LoggingUtil.exception(e); + //de.lmu.ifi.dbs.elki.logging.LoggingUtil.exception(e); } } @@ -313,8 +305,7 @@ public class SVGPlot { */ public void updateStyleElement() { // TODO: this should be sufficient - why does Batik occasionally not pick up - // the - // changes unless we actually replace the style element itself? + // the changes unless we actually replace the style element itself? // cssman.updateStyleElement(document, style); Element newstyle = cssman.makeStyleElement(document); style.getParentNode().replaceChild(newstyle, style); @@ -336,40 +327,7 @@ public class SVGPlot { OutputStream out = new FileOutputStream(file); // TODO embed linked images. javax.xml.transform.Result result = new StreamResult(out); - // deep clone document - SVGDocument doc = (SVGDocument) DOMUtilities.deepCloneDocument(getDocument(), getDocument().getImplementation()); - NodeList imgs = doc.getElementsByTagNameNS(SVGConstants.SVG_NAMESPACE_URI, SVGConstants.SVG_IMAGE_TAG); - final String tmpurl = new File(System.getProperty("java.io.tmpdir") + File.separator).toURI().toString(); - for(Node img : new XMLNodeListIterator(imgs)) { - if(img instanceof Element) { - try { - Element i = (Element) img; - String href = i.getAttributeNS(SVGConstants.XLINK_NAMESPACE_URI, SVGConstants.XLINK_HREF_ATTRIBUTE); - if(href.startsWith(tmpurl) && href.endsWith(".png")) { - // need to convert the image into an inline image. - ByteArrayOutputStream os = new ByteArrayOutputStream(); - Base64EncoderStream encoder = new Base64EncoderStream(os); - File in = new File(new URI(href)); - FileInputStream instream = new FileInputStream(in); - byte[] buf = new byte[4096]; - while(true) { - int read = instream.read(buf, 0, buf.length); - if(read <= 0) { - break; - } - encoder.write(buf, 0, read); - } - instream.close(); - encoder.close(); - // replace HREF with inlined image data. - i.setAttributeNS(SVGConstants.XLINK_NAMESPACE_URI, SVGConstants.XLINK_HREF_ATTRIBUTE, SVGSyntax.DATA_PROTOCOL_PNG_PREFIX + os.toString()); - } - } - catch(URISyntaxException e) { - LoggingUtil.warning("Error in embedding PNG image."); - } - } - } + SVGDocument doc = cloneDocument(); // Use a transformer for pretty printing Transformer xformer = TransformerFactory.newInstance().newTransformer(); xformer.setOutputProperty(OutputKeys.INDENT, "yes"); @@ -387,11 +345,9 @@ public class SVGPlot { * @throws TranscoderException On input/parsing errors */ protected void transcode(File file, Transcoder transcoder) throws IOException, TranscoderException { - // Since the Transcoder is Batik-based, it will replace the rendering tree, - // which would then break display. Thus we need to deep clone the document - // first. - // -- found by Simon. - SVGDocument doc = (SVGDocument) DOMUtilities.deepCloneDocument(getDocument(), getDocument().getImplementation()); + // Disable validation, performance is more important here (thumbnails!) + transcoder.addTranscodingHint(XMLAbstractTranscoder.KEY_XML_PARSER_VALIDATING, Boolean.FALSE); + SVGDocument doc = cloneDocument(); TranscoderInput input = new TranscoderInput(doc); OutputStream out = new FileOutputStream(file); TranscoderOutput output = new TranscoderOutput(out); @@ -401,14 +357,37 @@ public class SVGPlot { } /** + * Clone the SVGPlot document for transcoding. + * + * This will usually be necessary for exporting the SVG document if it is + * currently being displayed: otherwise, we break the Batik rendering trees. + * (Discovered by Simon). + * + * @return cloned document + */ + protected SVGDocument cloneDocument() { + return (SVGDocument) new CloneInlineImages().cloneDocument(SVGDOMImplementation.getDOMImplementation(), document); + } + + /** * Transcode file to PDF. * * @param file Output filename * @throws IOException On write errors * @throws TranscoderException On input/parsing errors. + * @throws ClassNotFoundException PDF transcoder not installed */ - public void saveAsPDF(File file) throws IOException, TranscoderException { - transcode(file, new PDFTranscoder()); + public void saveAsPDF(File file) throws IOException, TranscoderException, ClassNotFoundException { + try { + Object t = Class.forName("org.apache.fop.svg.PDFTranscoder").newInstance(); + transcode(file, (Transcoder) t); + } + catch(InstantiationException e) { + throw new ClassNotFoundException("Could not instantiate PDF transcoder - is Apache FOP installed?", e); + } + catch(IllegalAccessException e) { + throw new ClassNotFoundException("Could not instantiate PDF transcoder - is Apache FOP installed?", e); + } } /** @@ -417,9 +396,19 @@ public class SVGPlot { * @param file Output filename * @throws IOException On write errors * @throws TranscoderException On input/parsing errors. + * @throws ClassNotFoundException PS transcoder not installed */ - public void saveAsPS(File file) throws IOException, TranscoderException { - transcode(file, new PSTranscoder()); + public void saveAsPS(File file) throws IOException, TranscoderException, ClassNotFoundException { + try { + Object t = Class.forName("org.apache.fop.render.ps.PSTranscoder").newInstance(); + transcode(file, (Transcoder) t); + } + catch(InstantiationException e) { + throw new ClassNotFoundException("Could not instantiate PS transcoder - is Apache FOP installed?", e); + } + catch(IllegalAccessException e) { + throw new ClassNotFoundException("Could not instantiate PS transcoder - is Apache FOP installed?", e); + } } /** @@ -428,9 +417,36 @@ public class SVGPlot { * @param file Output filename * @throws IOException On write errors * @throws TranscoderException On input/parsing errors. + * @throws ClassNotFoundException EPS transcoder not installed */ - public void saveAsEPS(File file) throws IOException, TranscoderException { - transcode(file, new EPSTranscoder()); + public void saveAsEPS(File file) throws IOException, TranscoderException, ClassNotFoundException { + try { + Object t = Class.forName("org.apache.fop.render.ps.EPSTranscoder").newInstance(); + transcode(file, (Transcoder) t); + } + catch(InstantiationException e) { + throw new ClassNotFoundException("Could not instantiate EPS transcoder - is Apache FOP installed?", e); + } + catch(IllegalAccessException e) { + throw new ClassNotFoundException("Could not instantiate EPS transcoder - is Apache FOP installed?", e); + } + } + + /** + * Test whether FOP were installed (for PDF, PS and EPS output support). + * + * @return true when FOP is available. + */ + public static boolean hasFOPInstalled() { + try { + Class<?> c1 = Class.forName("org.apache.fop.svg.PDFTranscoder"); + Class<?> c2 = Class.forName("org.apache.fop.render.ps.PSTranscoder"); + Class<?> c3 = Class.forName("org.apache.fop.render.ps.EPSTranscoder"); + return (c1 != null) && (c2 != null) && (c3 != null); + } + catch(ClassNotFoundException e) { + return false; + } } /** @@ -491,8 +507,9 @@ public class SVGPlot { * @throws TranscoderException on transcoding errors * @throws TransformerFactoryConfigurationError on transcoding errors * @throws TransformerException on transcoding errors + * @throws ClassNotFoundException when the transcoder was not installed */ - public void saveAsANY(File file, int width, int height, double quality) throws IOException, TranscoderException, TransformerFactoryConfigurationError, TransformerException { + public void saveAsANY(File file, int width, int height, double quality) throws IOException, TranscoderException, TransformerFactoryConfigurationError, TransformerException, ClassNotFoundException { String extension = FileUtil.getFilenameExtension(file); if(extension.equals("svg")) { saveAsSVG(file); @@ -518,6 +535,23 @@ public class SVGPlot { } /** + * Convert the SVG to a thumbnail image. + * + * @param width Width of thumbnail + * @param height Height of thumbnail + * @return Buffered image + */ + public BufferedImage makeAWTImage(int width, int height) throws TranscoderException { + ThumbnailTranscoder t = new ThumbnailTranscoder(); + t.addTranscodingHint(PNGTranscoder.KEY_WIDTH, new Float(width)); + t.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, new Float(height)); + // Don't clone. Assume this is used safely. + TranscoderInput input = new TranscoderInput(document); + t.transcode(input, null); + return t.getLastImage(); + } + + /** * Dump the SVG plot to a debug file. */ public void dumpDebugFile() { @@ -563,21 +597,12 @@ public class SVGPlot { } /** - * Get the plots update runner. - * - * @return update runner - */ - private UpdateRunner getUpdateRunner() { - return runner; - } - - /** * Schedule an update. * * @param runnable Runnable to schedule */ public void scheduleUpdate(Runnable runnable) { - getUpdateRunner().invokeLater(runnable); + runner.invokeLater(runnable); } /** @@ -586,7 +611,7 @@ public class SVGPlot { * @param sync Update synchronizer */ public void synchronizeWith(UpdateSynchronizer sync) { - getUpdateRunner().synchronizeWith(sync); + runner.synchronizeWith(sync); } /** @@ -595,7 +620,7 @@ public class SVGPlot { * @param sync Update synchronizer to detach from. */ public void unsynchronizeWith(UpdateSynchronizer sync) { - getUpdateRunner().unsynchronizeWith(sync); + runner.unsynchronizeWith(sync); } /** |