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}