001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.image.renderer; 031 032import java.awt.Color; 033import java.awt.Dimension; 034import java.awt.Graphics; 035import java.awt.geom.Area; 036import java.awt.geom.Path2D; 037import java.awt.geom.Path2D.Double; 038import java.awt.image.BufferedImage; 039import java.awt.image.ImageObserver; 040import java.io.Writer; 041import java.util.ArrayList; 042import java.util.List; 043 044import org.apache.batik.dom.GenericDOMImplementation; 045import org.apache.batik.ext.awt.image.spi.ImageWriter; 046import org.apache.batik.ext.awt.image.spi.ImageWriterRegistry; 047import org.apache.batik.svggen.CachedImageHandlerBase64Encoder; 048import org.apache.batik.svggen.DefaultStyleHandler; 049import org.apache.batik.svggen.GenericImageHandler; 050import org.apache.batik.svggen.SVGCSSStyler; 051import org.apache.batik.svggen.SVGGeneratorContext; 052import org.apache.batik.svggen.SVGGraphics2D; 053import org.apache.batik.svggen.SVGGraphics2DIOException; 054import org.openimaj.image.FImage; 055import org.openimaj.image.Image; 056import org.openimaj.image.ImageUtilities; 057import org.openimaj.image.MBFImage; 058import org.openimaj.image.SVGImage; 059import org.openimaj.image.colour.RGBColour; 060import org.openimaj.math.geometry.point.Point2d; 061import org.openimaj.math.geometry.point.Point2dImpl; 062import org.openimaj.math.geometry.shape.Polygon; 063import org.openimaj.math.geometry.shape.Shape; 064import org.w3c.dom.CDATASection; 065import org.w3c.dom.DOMImplementation; 066import org.w3c.dom.Document; 067import org.w3c.dom.DocumentFragment; 068import org.w3c.dom.Element; 069import org.w3c.dom.Node; 070import org.w3c.dom.svg.SVGSVGElement; 071import org.xml.sax.XMLReader; 072 073/** 074 * {@link ImageRenderer} for {@link FImage} images. Supports both anti-aliased 075 * and fast rendering. 076 * 077 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 078 * 079 */ 080public class SVGRenderer extends ImageRenderer<Float[], SVGImage> { 081 082 private SVGGraphics2D svgGen; 083 084 /** 085 * Construct with given target image. 086 * 087 * @param targetImage 088 * the target image. 089 */ 090 public SVGRenderer(final SVGImage targetImage) { 091 super(targetImage); 092 prepareSVG(); 093 } 094 095 /** 096 * Construct with given target image and rendering hints. 097 * 098 * @param targetImage 099 * the target image. 100 * @param hints 101 * the render hints 102 */ 103 public SVGRenderer(final SVGImage targetImage, final SVGRenderHints hints) { 104 super(targetImage, hints); 105 prepareSVG(); 106 } 107 108 /** 109 * Construct with given target image and rendering hints. 110 * 111 * @param targetImage 112 * the target image. 113 * @param hints 114 * the render hints 115 */ 116 public SVGRenderer(final SVGRenderHints hints) { 117 super(null, hints); 118 prepareSVG(); 119 } 120 121 /** 122 * @param create 123 */ 124 public SVGRenderer(SVGImage img, Graphics create) { 125 super(img); 126 this.svgGen = (SVGGraphics2D) create; 127 } 128 129 public SVGRenderer(SVGImage img, RenderHints renderHints, Graphics create) { 130 super(img,renderHints); 131 this.svgGen = (SVGGraphics2D) create; 132 } 133 134 private void prepareSVG() { 135 DOMImplementation impl = GenericDOMImplementation.getDOMImplementation(); 136 String svgNS = "http://www.w3.org/2000/svg"; 137 Document doc = impl.createDocument(svgNS, "svg", null); 138 // Create an instance of the SVG Generator 139 SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(doc); 140 141 142 // Reuse our embedded base64-encoded image data. 143// GenericImageHandler ihandler = new CachedImageHandlerBase64Encoder(); 144// ctx.setGenericImageHandler(ihandler); 145 this.svgGen = new SVGGraphics2D(ctx,false); 146 147 if(this.targetImage != null){ 148 int w = targetImage.getWidth(); 149 int h = targetImage.getHeight(); 150 this.svgGen.setSVGCanvasSize(new Dimension(w, h)); 151 } else if(this.hints!=null && hints instanceof SVGRenderHints){ 152 int w = ((SVGRenderHints)hints).width; 153 int h = ((SVGRenderHints)hints).height; 154 this.svgGen.setSVGCanvasSize(new Dimension(w, h)); 155 } 156 157 } 158 159 @Override 160 public void drawLine(int x1, int y1, double theta, int length,int thickness, Float[] col) { 161 final int x2 = x1 + (int) Math.round(Math.cos(theta) * length); 162 final int y2 = y1 + (int) Math.round(Math.sin(theta) * length); 163 164 this.drawLine(x1, y1, x2, y2, thickness, col); 165 } 166 167 @Override 168 public void drawLine(int x0, int y0, int x1, int y1, int thickness,Float[] col) { 169 if(thickness <= 1){ 170 this.svgGen.setColor(colorFromFloats(col)); 171 this.svgGen.drawLine(x0, y0, x1, y1); 172 } else { 173 final double theta = Math.atan2(y1 - y0, x1 - x0); 174 final double t = thickness / 2; 175 final double sin = t * Math.sin(theta); 176 final double cos = t * Math.cos(theta); 177 178 final Polygon p = new Polygon(); 179 p.addVertex(new Point2dImpl((float) (x0 - sin), (float) (y0 + cos))); 180 p.addVertex(new Point2dImpl((float) (x0 + sin), (float) (y0 - cos))); 181 p.addVertex(new Point2dImpl((float) (x1 + sin), (float) (y1 - cos))); 182 p.addVertex(new Point2dImpl((float) (x1 - sin), (float) (y1 + cos))); 183 184 this.drawPolygonFilled(p, col); 185 } 186 } 187 188 private Color colorFromFloats(Float[] col) { 189 Color ret = new Color(col[0], col[1], col[2]); 190 return ret; 191 } 192 193 @Override 194 public void drawLine(float x0, float y0, float x1, float y1, int thickness,Float[] col) { 195 this.drawLine((int)x0, (int)y0, (int)x1, (int)y1, thickness, col); 196 } 197 198 @Override 199 public void drawPoint(Point2d p, Float[] col, int size) { 200 this.svgGen.setColor(colorFromFloats(col)); 201 Path2D.Double path = new Path2D.Double(); 202 path.moveTo(p.getX(), p.getY()); 203 path.lineTo(p.getX(), p.getY()); 204 this.svgGen.draw(path ); 205 } 206 207 @Override 208 public void drawPolygon(Polygon p, int thickness, Float[] col) { 209 this.svgGen.setColor(colorFromFloats(col)); 210 List<Area> a = jPolyFromPolygon(p); 211 for (Area polygon : a) { 212 this.svgGen.draw(polygon); 213 } 214 215 } 216 217 @Override 218 public void drawPolygonFilled(Polygon p, Float[] col) { 219 this.svgGen.setColor(colorFromFloats(col)); 220 List<Area> a = jPolyFromPolygon(p); 221 for (Area polygon : a) { 222 this.svgGen.fill(polygon); 223 } 224 } 225 226 @Override 227 public void drawShapeFilled(Shape s, Float[] col) { 228 super.drawPolygonFilled(s.asPolygon(), col); 229 } 230 231 private List<java.awt.geom.Area> jPolyFromPolygon(Polygon p) { 232 List<java.awt.geom.Area> ret = new ArrayList<java.awt.geom.Area>(); 233 Path2D path = new Path2D.Double(); 234 boolean start = true; 235 for (Point2d p2d : p.getVertices()) { 236 if(start){ 237 path.moveTo(p2d.getX(), p2d.getY()); 238 start = false; 239 } 240 else{ 241 path.lineTo(p2d.getX(), p2d.getY()); 242 } 243 244 } 245 java.awt.geom.Area pp = new java.awt.geom.Area(); 246 ret.add(new java.awt.geom.Area(path)); 247 return ret; 248 } 249 250 @Override 251 protected void drawHorizLine(int x1, int x2, int y, Float[] col) { 252 this.drawLine(x1, y, x2, y, 1, col); 253 } 254 255 @Override 256 public Float[] defaultForegroundColour() { 257 return RGBColour.BLACK; 258 } 259 260 @Override 261 public Float[] defaultBackgroundColour() { 262 return RGBColour.WHITE; 263 } 264 265 @Override 266 protected Float[] sanitise(Float[] colour) { 267 return colour; 268 } 269 270 /** 271 * @param out 272 * @throws SVGGraphics2DIOException 273 */ 274 public void write(Writer out) throws SVGGraphics2DIOException{ 275 this.svgGen.stream(out, true); 276 } 277 278 public void drawOIImage(Image<?,?> im) { 279 BufferedImage createBufferedImage = ImageUtilities.createBufferedImage(im); 280 this.svgGen.drawImage(createBufferedImage, 0, 0, this.colorFromFloats(this.defaultBackgroundColour()), new ImageObserver() { 281 282 @Override 283 public boolean imageUpdate(java.awt.Image img, int infoflags, int x, int y,int width, int height) { 284 return true; 285 } 286 }); 287 } 288 289 @Override 290 public SVGRenderer clone() { 291 SVGRenderer ret = new SVGRenderer((SVGRenderHints)this.getRenderHints()); 292 return ret; 293 } 294 295 @Override 296 public void drawImage(SVGImage image, int x, int y) { 297 // TODO: fix this... 298// throw new UnsupportedOperationException(); 299// Element root = this.svgGen.getRoot(); 300// System.out.println(root); 301// Node node = this.svgGen.getDOMFactory().importNode(image.createRenderer().svgGen.getRoot(), true); 302// image.createRenderer().svgGen.getRoot((Element) node); 303// root.appendChild(node); 304 } 305 306 public SVGGraphics2D getGraphics2D() { 307 return this.svgGen; 308 } 309 310 public Document getDocument() { 311 Element root = svgGen.getRoot(); 312 // 313 // Enforce that the default and xlink namespace 314 // declarations appear on the root element 315 // 316 317 Document doc = null; 318 try { 319 // 320 // Enforce that the default and xlink namespace 321 // declarations appear on the root element 322 // 323 root.setAttributeNS(SVGGraphics2D.XMLNS_NAMESPACE_URI, 324 SVGGraphics2D.XMLNS_PREFIX, 325 SVGGraphics2D.SVG_NAMESPACE_URI); 326 327 root.setAttributeNS(SVGGraphics2D.XMLNS_NAMESPACE_URI, 328 SVGGraphics2D.XMLNS_PREFIX + ":" + SVGGraphics2D.XLINK_PREFIX, 329 SVGGraphics2D.XLINK_NAMESPACE_URI); 330// DOMImplementation impl = GenericDOMImplementation.getDOMImplementation(); 331// String svgNS = "http://www.w3.org/2000/svg"; 332// doc = impl.createDocument(svgNS, "svg", null); 333// doc.appendChild(root); 334 } 335 catch (Exception e){ 336 337 } 338 return root.getOwnerDocument(); 339 340 } 341}