summaryrefslogtreecommitdiff
path: root/src/de/lmu/ifi/dbs/elki/visualization/svg/SVGPlot.java
diff options
context:
space:
mode:
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.java179
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);
}
/**