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.image.BufferedImage;
038import java.awt.image.ImageObserver;
039import java.io.Writer;
040import java.util.ArrayList;
041import java.util.List;
042
043import org.apache.batik.dom.GenericDOMImplementation;
044import org.apache.batik.svggen.SVGGeneratorContext;
045import org.apache.batik.svggen.SVGGraphics2D;
046import org.apache.batik.svggen.SVGGraphics2DIOException;
047import org.openimaj.image.FImage;
048import org.openimaj.image.Image;
049import org.openimaj.image.ImageUtilities;
050import org.openimaj.image.SVGImage;
051import org.openimaj.image.colour.RGBColour;
052import org.openimaj.math.geometry.point.Point2d;
053import org.openimaj.math.geometry.point.Point2dImpl;
054import org.openimaj.math.geometry.shape.Polygon;
055import org.openimaj.math.geometry.shape.Shape;
056import org.w3c.dom.DOMImplementation;
057import org.w3c.dom.Document;
058import org.w3c.dom.Element;
059
060/**
061 * {@link ImageRenderer} for {@link FImage} images. Supports both anti-aliased
062 * and fast rendering.
063 *
064 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
065 *
066 */
067public class SVGRenderer extends ImageRenderer<Float[], SVGImage> {
068
069        private SVGGraphics2D svgGen;
070
071        /**
072         * Construct with given target image.
073         *
074         * @param targetImage
075         *            the target image.
076         */
077        public SVGRenderer(final SVGImage targetImage) {
078                super(targetImage);
079                prepareSVG();
080        }
081
082        /**
083         * Construct with given target image and rendering hints.
084         *
085         * @param targetImage
086         *            the target image.
087         * @param hints
088         *            the render hints
089         */
090        public SVGRenderer(final SVGImage targetImage, final SVGRenderHints hints) {
091                super(targetImage, hints);
092                prepareSVG();
093        }
094
095        /**
096         * Construct with given target image and rendering hints.
097         *
098         * @param hints
099         *            the render hints
100         */
101        public SVGRenderer(final SVGRenderHints hints) {
102                super(null, hints);
103                prepareSVG();
104        }
105
106        /**
107         * @param img
108         * @param create
109         */
110        public SVGRenderer(SVGImage img, Graphics create) {
111                super(img);
112                this.svgGen = (SVGGraphics2D) create;
113        }
114
115        public SVGRenderer(SVGImage img, RenderHints renderHints, Graphics create) {
116                super(img, renderHints);
117                this.svgGen = (SVGGraphics2D) create;
118        }
119
120        private void prepareSVG() {
121                final DOMImplementation impl = GenericDOMImplementation.getDOMImplementation();
122                final String svgNS = "http://www.w3.org/2000/svg";
123                final Document doc = impl.createDocument(svgNS, "svg", null);
124                // Create an instance of the SVG Generator
125                final SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(doc);
126
127                // Reuse our embedded base64-encoded image data.
128                // GenericImageHandler ihandler = new CachedImageHandlerBase64Encoder();
129                // ctx.setGenericImageHandler(ihandler);
130                this.svgGen = new SVGGraphics2D(ctx, false);
131
132                if (this.targetImage != null) {
133                        final int w = targetImage.getWidth();
134                        final int h = targetImage.getHeight();
135                        this.svgGen.setSVGCanvasSize(new Dimension(w, h));
136                } else if (this.hints != null && hints instanceof SVGRenderHints) {
137                        final int w = ((SVGRenderHints) hints).width;
138                        final int h = ((SVGRenderHints) hints).height;
139                        this.svgGen.setSVGCanvasSize(new Dimension(w, h));
140                }
141
142        }
143
144        @Override
145        public void drawLine(int x1, int y1, double theta, int length, int thickness, Float[] col) {
146                final int x2 = x1 + (int) Math.round(Math.cos(theta) * length);
147                final int y2 = y1 + (int) Math.round(Math.sin(theta) * length);
148
149                this.drawLine(x1, y1, x2, y2, thickness, col);
150        }
151
152        @Override
153        public void drawLine(int x0, int y0, int x1, int y1, int thickness, Float[] col) {
154                if (thickness <= 1) {
155                        this.svgGen.setColor(colorFromFloats(col));
156                        this.svgGen.drawLine(x0, y0, x1, y1);
157                } else {
158                        final double theta = Math.atan2(y1 - y0, x1 - x0);
159                        final double t = thickness / 2;
160                        final double sin = t * Math.sin(theta);
161                        final double cos = t * Math.cos(theta);
162
163                        final Polygon p = new Polygon();
164                        p.addVertex(new Point2dImpl((float) (x0 - sin), (float) (y0 + cos)));
165                        p.addVertex(new Point2dImpl((float) (x0 + sin), (float) (y0 - cos)));
166                        p.addVertex(new Point2dImpl((float) (x1 + sin), (float) (y1 - cos)));
167                        p.addVertex(new Point2dImpl((float) (x1 - sin), (float) (y1 + cos)));
168
169                        this.drawPolygonFilled(p, col);
170                }
171        }
172
173        private Color colorFromFloats(Float[] col) {
174                final Color ret = new Color(col[0], col[1], col[2]);
175                return ret;
176        }
177
178        @Override
179        public void drawLine(float x0, float y0, float x1, float y1, int thickness, Float[] col) {
180                this.drawLine((int) x0, (int) y0, (int) x1, (int) y1, thickness, col);
181        }
182
183        @Override
184        public void drawPoint(Point2d p, Float[] col, int size) {
185                this.svgGen.setColor(colorFromFloats(col));
186                final Path2D.Double path = new Path2D.Double();
187                path.moveTo(p.getX(), p.getY());
188                path.lineTo(p.getX(), p.getY());
189                this.svgGen.draw(path);
190        }
191
192        @Override
193        public void drawPolygon(Polygon p, int thickness, Float[] col) {
194                this.svgGen.setColor(colorFromFloats(col));
195                final List<Area> a = jPolyFromPolygon(p);
196                for (final Area polygon : a) {
197                        this.svgGen.draw(polygon);
198                }
199
200        }
201
202        @Override
203        public void drawPolygonFilled(Polygon p, Float[] col) {
204                this.svgGen.setColor(colorFromFloats(col));
205                final List<Area> a = jPolyFromPolygon(p);
206                for (final Area polygon : a) {
207                        this.svgGen.fill(polygon);
208                }
209        }
210
211        @Override
212        public void drawShapeFilled(Shape s, Float[] col) {
213                super.drawPolygonFilled(s.asPolygon(), col);
214        }
215
216        private List<java.awt.geom.Area> jPolyFromPolygon(Polygon p) {
217                final List<java.awt.geom.Area> ret = new ArrayList<java.awt.geom.Area>();
218                final Path2D path = new Path2D.Double();
219                boolean start = true;
220                for (final Point2d p2d : p.getVertices()) {
221                        if (start) {
222                                path.moveTo(p2d.getX(), p2d.getY());
223                                start = false;
224                        }
225                        else {
226                                path.lineTo(p2d.getX(), p2d.getY());
227                        }
228
229                }
230                // final java.awt.geom.Area pp = new java.awt.geom.Area();
231                ret.add(new java.awt.geom.Area(path));
232                return ret;
233        }
234
235        @Override
236        protected void drawHorizLine(int x1, int x2, int y, Float[] col) {
237                this.drawLine(x1, y, x2, y, 1, col);
238        }
239
240        @Override
241        public Float[] defaultForegroundColour() {
242                return RGBColour.BLACK;
243        }
244
245        @Override
246        public Float[] defaultBackgroundColour() {
247                return RGBColour.WHITE;
248        }
249
250        @Override
251        protected Float[] sanitise(Float[] colour) {
252                return colour;
253        }
254
255        /**
256         * @param out
257         * @throws SVGGraphics2DIOException
258         */
259        public void write(Writer out) throws SVGGraphics2DIOException {
260                this.svgGen.stream(out, true);
261        }
262
263        public void drawOIImage(Image<?, ?> im) {
264                final BufferedImage createBufferedImage = ImageUtilities.createBufferedImage(im);
265                this.svgGen.drawImage(createBufferedImage, 0, 0, this.colorFromFloats(this.defaultBackgroundColour()),
266                                new ImageObserver() {
267
268                                        @Override
269                                        public boolean imageUpdate(java.awt.Image img, int infoflags, int x, int y, int width, int height) {
270                                                return true;
271                                        }
272                                });
273        }
274
275        @Override
276        public SVGRenderer clone() {
277                final SVGRenderer ret = new SVGRenderer((SVGRenderHints) this.getRenderHints());
278                return ret;
279        }
280
281        @Override
282        public void drawImage(SVGImage image, int x, int y) {
283                // TODO: fix this...
284                // throw new UnsupportedOperationException();
285                // Element root = this.svgGen.getRoot();
286                // System.out.println(root);
287                // Node node =
288                // this.svgGen.getDOMFactory().importNode(image.createRenderer().svgGen.getRoot(),
289                // true);
290                // image.createRenderer().svgGen.getRoot((Element) node);
291                // root.appendChild(node);
292        }
293
294        public SVGGraphics2D getGraphics2D() {
295                return this.svgGen;
296        }
297
298        public Document getDocument() {
299                final Element root = svgGen.getRoot();
300                //
301                // Enforce that the default and xlink namespace
302                // declarations appear on the root element
303                //
304
305                // final Document doc = null;
306                try {
307                        //
308                        // Enforce that the default and xlink namespace
309                        // declarations appear on the root element
310                        //
311                        root.setAttributeNS(SVGGraphics2D.XMLNS_NAMESPACE_URI,
312                                        SVGGraphics2D.XMLNS_PREFIX,
313                                        SVGGraphics2D.SVG_NAMESPACE_URI);
314
315                        root.setAttributeNS(SVGGraphics2D.XMLNS_NAMESPACE_URI,
316                                        SVGGraphics2D.XMLNS_PREFIX + ":" + SVGGraphics2D.XLINK_PREFIX,
317                                        SVGGraphics2D.XLINK_NAMESPACE_URI);
318                        // DOMImplementation impl =
319                        // GenericDOMImplementation.getDOMImplementation();
320                        // String svgNS = "http://www.w3.org/2000/svg";
321                        // doc = impl.createDocument(svgNS, "svg", null);
322                        // doc.appendChild(root);
323                } catch (final Exception e) {
324
325                }
326                return root.getOwnerDocument();
327
328        }
329}