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.geom.CubicCurve2D;
033import java.awt.geom.QuadCurve2D;
034import java.text.AttributedString;
035import java.util.ArrayList;
036import java.util.Arrays;
037import java.util.Iterator;
038import java.util.List;
039
040import org.openimaj.image.Image;
041import org.openimaj.image.pixel.Pixel;
042import org.openimaj.image.renderer.ScanRasteriser.ScanLineListener;
043import org.openimaj.image.typography.Font;
044import org.openimaj.image.typography.FontRenderer;
045import org.openimaj.image.typography.FontStyle;
046import org.openimaj.math.geometry.line.Line2d;
047import org.openimaj.math.geometry.path.Path2d;
048import org.openimaj.math.geometry.point.Point2d;
049import org.openimaj.math.geometry.point.Point2dImpl;
050import org.openimaj.math.geometry.shape.Polygon;
051import org.openimaj.math.geometry.shape.Shape;
052
053import com.caffeineowl.graphics.bezier.BezierUtils;
054import com.caffeineowl.graphics.bezier.CubicSegmentConsumer;
055import com.caffeineowl.graphics.bezier.QuadSegmentConsumer;
056import com.caffeineowl.graphics.bezier.flatnessalgos.SimpleConvexHullSubdivCriterion;
057
058/**
059 * ImageRenderer is the abstract base class for all renderers capable of drawing
060 * to images.
061 *
062 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
063 *
064 * @param <Q>
065 *            Pixel type
066 * @param <I>
067 *            Image type
068 */
069public abstract class ImageRenderer<Q, I extends Image<Q, I>> {
070        protected RenderHints hints;
071        protected I targetImage;
072
073        /**
074         * Construct with given target image.
075         *
076         * @param targetImage
077         *            the target image.
078         */
079        public ImageRenderer(final I targetImage) {
080                this(targetImage, new RenderHints());
081        }
082
083        /**
084         * Construct with given target image and rendering hints.
085         *
086         * @param targetImage
087         *            the target image.
088         * @param hints
089         *            the render hints
090         */
091        public ImageRenderer(final I targetImage, final RenderHints hints) {
092                this.targetImage = targetImage;
093                this.hints = hints;
094        }
095
096        /**
097         * Draw onto this image lines drawn with the given colour between the points
098         * given. No points are drawn.
099         *
100         * @param pts
101         *            The point list to draw onto this image.
102         * @param col
103         *            The colour to draw the lines
104         */
105        public void drawConnectedPoints(final List<? extends Point2d> pts, final Q col) {
106                Point2d p0 = pts.get(0);
107                for (int i = 1; i < pts.size(); i++) {
108                        final Point2d p1 = pts.get(i);
109
110                        final int x0 = Math.round(p0.getX());
111                        final int y0 = Math.round(p0.getY());
112                        final int x1 = Math.round(p1.getX());
113                        final int y1 = Math.round(p1.getY());
114
115                        this.drawLine(x0, y0, x1, y1, col);
116
117                        p0 = p1;
118                }
119        }
120
121        /**
122         * Draw into this image the provided image at the given coordinates. Parts
123         * of the image outside the bounds of this image will be ignored.
124         *
125         * @param image
126         *            The image to draw.
127         * @param x
128         *            The x-coordinate of the top-left of the image
129         * @param y
130         *            The y-coordinate of the top-left of the image
131         */
132        public void drawImage(final I image, final int x, final int y) {
133                final int stopx = Math.min(this.targetImage.getWidth(), x + image.getWidth());
134                final int stopy = Math.min(this.targetImage.getHeight(), y + image.getHeight());
135                final int startx = Math.max(0, x);
136                final int starty = Math.max(0, y);
137
138                for (int yy = starty; yy < stopy; yy++)
139                        for (int xx = startx; xx < stopx; xx++)
140                                this.targetImage.setPixel(xx, yy, image.getPixel(xx - x, yy - y));
141        }
142
143        /**
144         * Draw into this image the provided image at the given coordinates ignoring
145         * certain pixels. Parts of the image outside the bounds of this image will
146         * be ignored. Pixels in the ignore list will be stripped from the image to
147         * draw.
148         *
149         * @param image
150         *            The image to draw.
151         * @param x
152         *            The x-coordinate of the top-left of the image
153         * @param y
154         *            The y-coordinate of the top-left of the image
155         * @param ignoreList
156         *            The list of pixels to ignore when copying the image
157         */
158        public void drawImage(final I image, final int x, final int y, @SuppressWarnings("unchecked") final Q... ignoreList) {
159                final int stopx = Math.min(this.targetImage.getWidth(), x + image.getWidth());
160                final int stopy = Math.min(this.targetImage.getHeight(), y + image.getHeight());
161                final int startx = Math.max(0, x);
162                final int starty = Math.max(0, y);
163
164                for (int yy = starty; yy < stopy; yy++)
165                        for (int xx = startx; xx < stopx; xx++) {
166                                final Q val = image.getPixel(xx - x, yy - y);
167                                if (Arrays.binarySearch(ignoreList, val, this.targetImage.getPixelComparator()) < 0)
168                                        this.targetImage.setPixel(xx, yy, val);
169                        }
170
171        }
172
173        /**
174         * Draw a line from the coordinates specified by <code>(x1,y1)</code> at an
175         * angle of <code>theta</code> with the given length, thickness and colour.
176         *
177         * @param x1
178         *            The x-coordinate to start the line.
179         * @param y1
180         *            The y-coordinate to start the line.
181         * @param theta
182         *            The angle at which to draw the line.
183         * @param length
184         *            The length to draw the line.
185         * @param thickness
186         *            The thickness to draw the line.
187         * @param col
188         *            The colour to draw the line.
189         */
190        public abstract void drawLine(int x1, int y1, double theta, int length, int thickness, Q col);
191
192        /**
193         * Draw a line from the coordinates specified by <code>(x1,y1)</code> at an
194         * angle of <code>theta</code> with the given length and colour.
195         * Line-thickness will be 1.
196         *
197         * @param x1
198         *            The x-coordinate to start the line.
199         * @param y1
200         *            The y-coordinate to start the line.
201         * @param theta
202         *            The angle at which to draw the line.
203         * @param length
204         *            The length to draw the line.
205         * @param col
206         *            The colour to draw the line.
207         */
208        public void drawLine(final int x1, final int y1, final double theta, final int length, final Q col) {
209                this.drawLine(x1, y1, theta, length, 1, col);
210        }
211
212        /**
213         * Draw a line from the coordinates specified by <code>(x0,y0)</code> to the
214         * coordinates specified by <code>(x1,y1)</code> using the given color and
215         * thickness.
216         *
217         * @param x0
218         *            The x-coordinate at the start of the line.
219         * @param y0
220         *            The y-coordinate at the start of the line.
221         * @param x1
222         *            The x-coordinate at the end of the line.
223         * @param y1
224         *            The y-coordinate at the end of the line.
225         * @param thickness
226         *            The thickness which to draw the line.
227         * @param col
228         *            The colour in which to draw the line.
229         */
230        public abstract void drawLine(int x0, int y0, int x1, int y1, int thickness, Q col);
231
232        /**
233         * Draw a line from the coordinates specified by <code>(x0,y0)</code> to the
234         * coordinates specified by <code>(x1,y1)</code> using the given color and
235         * thickness.
236         *
237         * @param x0
238         *            The x-coordinate at the start of the line.
239         * @param y0
240         *            The y-coordinate at the start of the line.
241         * @param x1
242         *            The x-coordinate at the end of the line.
243         * @param y1
244         *            The y-coordinate at the end of the line.
245         * @param thickness
246         *            The thickness which to draw the line.
247         * @param col
248         *            The colour in which to draw the line.
249         */
250        public abstract void drawLine(final float x0, final float y0, final float x1, final float y1, final int thickness,
251                        final Q col);
252
253        /**
254         * Draw a line from the coordinates specified by <code>(x0,y0)</code> to
255         * <code>(x1,y1)</code> using the given colour. The line thickness will be 1
256         * pixel.
257         *
258         * @param x0
259         *            The x-coordinate at the start of the line.
260         * @param y0
261         *            The y-coordinate at the start of the line.
262         * @param x1
263         *            The x-coordinate at the end of the line.
264         * @param y1
265         *            The y-coordinate at the end of the line.
266         * @param col
267         *            The colour in which to draw the line.
268         */
269        public void drawLine(final int x0, final int y0, final int x1, final int y1, final Q col) {
270                this.drawLine(x0, y0, x1, y1, 1, col);
271        }
272
273        /**
274         * Draw a line from the coordinates specified by <code>(x0,y0)</code> to
275         * <code>(x1,y1)</code> using the given colour. The line thickness will be 1
276         * pixel.
277         *
278         * @param p1
279         *            The coordinate of the start of the line.
280         * @param p2
281         *            The coordinate of the end of the line.
282         * @param col
283         *            The colour in which to draw the line.
284         */
285        public void drawLine(final Point2d p1, final Point2d p2, final Q col) {
286                this.drawLine(Math.round(p1.getX()), Math.round(p1.getY()),
287                                Math.round(p2.getX()), Math.round(p2.getY()),
288                                1, col);
289        }
290
291        /**
292         * Draw a line from the coordinates specified by <code>(x0,y0)</code> to
293         * <code>(x1,y1)</code> using the given colour and thickness.
294         *
295         * @param p1
296         *            The coordinate of the start of the line.
297         * @param p2
298         *            The coordinate of the end of the line.
299         * @param thickness
300         *            the stroke width
301         * @param col
302         *            The colour in which to draw the line.
303         */
304        public void drawLine(final Point2d p1, final Point2d p2, final int thickness, final Q col) {
305                this.drawLine(Math.round(p1.getX()), Math.round(p1.getY()),
306                                Math.round(p2.getX()), Math.round(p2.getY()),
307                                thickness, col);
308        }
309
310        /**
311         * Draw a line from the specified {@link Path2d} object
312         *
313         * @param line
314         *            the line
315         * @param thickness
316         *            the stroke width
317         * @param col
318         *            The colour in which to draw the line.
319         */
320        public void drawLine(final Path2d line, final int thickness, final Q col) {
321                drawPath(line, thickness, col);
322        }
323
324        /**
325         * Draw a path from the specified {@link Path2d} object
326         *
327         * @param path
328         *            the path
329         * @param thickness
330         *            the stroke width
331         * @param col
332         *            The colour in which to draw the line.
333         */
334        public void drawPath(final Path2d path, final int thickness, final Q col) {
335                final Iterator<Line2d> it = path.lineIterator();
336
337                while (it.hasNext()) {
338                        final Line2d line = it.next();
339                        this.drawLine((int) line.begin.getX(), (int) line.begin.getY(), (int) line.end.getX(), (int) line.end.getY(),
340                                        thickness, col);
341                }
342        }
343
344        /**
345         * Draw the given list of lines using {@link #drawLine(Path2d, int, Object)}
346         * with the given colour and thickness.
347         *
348         * @param lines
349         *            The list of lines to draw.
350         * @param thickness
351         *            the stroke width
352         * @param col
353         *            The colour to draw each point.
354         */
355        public void drawLines(final Iterable<? extends Path2d> lines, final int thickness, final Q col) {
356                drawPaths(lines, thickness, col);
357        }
358
359        /**
360         * Draw the given list of lines using {@link #drawLine(Path2d, int, Object)}
361         * with the given colour and thickness.
362         *
363         * @param paths
364         *            The list of paths to draw.
365         * @param thickness
366         *            the stroke width
367         * @param col
368         *            The colour to draw each point.
369         */
370        public void drawPaths(final Iterable<? extends Path2d> paths, final int thickness, final Q col) {
371                for (final Path2d path : paths)
372                        this.drawLine(path, thickness, col);
373        }
374
375        /**
376         * Draw a dot centered on the given location (rounded to nearest integer
377         * location) at the given size and with the given color.
378         *
379         *
380         * @param p
381         *            The coordinates at which to draw the point
382         * @param col
383         *            The colour to draw the point
384         * @param size
385         *            The size at which to draw the point.
386         */
387        public abstract void drawPoint(Point2d p, Q col, int size);
388
389        /**
390         * Draw the given list of points using
391         * {@link #drawPoint(Point2d, Object, int)} with the given colour and size.
392         *
393         * @param pts
394         *            The list of points to draw.
395         * @param col
396         *            The colour to draw each point.
397         * @param size
398         *            The size to draw each point.
399         */
400        public void drawPoints(final Iterable<? extends Point2d> pts, final Q col, final int size) {
401                for (final Point2d p : pts)
402                        this.drawPoint(p, col, size);
403        }
404
405        /**
406         * Draw the given polygon in the specified colour with the given thickness
407         * lines.
408         *
409         *
410         * @param p
411         *            The polygon to draw.
412         * @param thickness
413         *            The thickness of the lines to use
414         * @param col
415         *            The colour to draw the lines in
416         */
417        public abstract void drawPolygon(Polygon p, int thickness, Q col);
418
419        /**
420         * Draw the given polygon in the specified colour. Uses
421         * {@link #drawPolygon(Polygon, int, Object)} with line thickness 1.
422         *
423         *
424         * @param p
425         *            The polygon to draw.
426         * @param col
427         *            The colour to draw the polygon in.
428         */
429        public void drawPolygon(final Polygon p, final Q col) {
430                this.drawPolygon(p, 1, col);
431        }
432
433        /**
434         * Draw a horizontal line with the specified colour.
435         *
436         * @param x1
437         *            starting x (inclusive)
438         * @param x2
439         *            ending x (inclusive)
440         * @param y
441         *            y
442         * @param col
443         *            the colour
444         */
445        protected abstract void drawHorizLine(int x1, int x2, int y, Q col);
446
447        /**
448         * Draw the given polygon, filled with the specified colour.
449         *
450         * @param p
451         *            The polygon to draw.
452         * @param col
453         *            The colour to fill the polygon with.
454         */
455        public void drawPolygonFilled(Polygon p, final Q col) {
456                // clip to the frame
457                // p = p.intersect(this.targetImage.getBounds().asPolygon());
458
459                this.drawPolygon(p, col);
460
461                if (p.getNumInnerPoly() == 1) {
462                        ScanRasteriser.scanFill(p.points, new ScanLineListener() {
463                                @Override
464                                public void process(final int x1, final int x2, final int y) {
465                                        ImageRenderer.this.drawHorizLine(x1, x2, y, col);
466                                }
467                        });
468                } else {
469                        // final ConnectedComponent cc = new ConnectedComponent(p);
470                        // cc.process(new BlobRenderer<Q>(this.targetImage, col));
471
472                        final int minx = Math.max(0, (int) Math.round(p.minX()));
473                        final int maxx = Math.min((int) Math.round(p.maxX()), targetImage.getWidth() - 1);
474                        final int miny = Math.max(0, (int) Math.round(p.minY()));
475                        final int maxy = Math.min((int) Math.round(p.maxY()), targetImage.getHeight() - 1);
476
477                        final Pixel tmp = new Pixel();
478                        for (tmp.y = miny; tmp.y <= maxy; tmp.y++) {
479                                for (tmp.x = minx; tmp.x <= maxx; tmp.x++) {
480                                        if (p.isInside(tmp))
481                                                this.targetImage.setPixel(tmp.x, tmp.y, col);
482                                }
483                        }
484                }
485        }
486
487        /**
488         * Draw the given shape in the specified colour with the given thickness
489         * lines.
490         *
491         * @param s
492         *            The shape to draw.
493         * @param thickness
494         *            The thickness of the lines to use
495         * @param col
496         *            The colour to draw the lines in
497         */
498        public void drawShape(final Shape s, final int thickness, final Q col) {
499                this.drawPolygon(s.asPolygon(), thickness, col);
500        }
501
502        /**
503         * Draw the given shape in the specified colour. Uses
504         * {@link #drawPolygon(Polygon, int, Object)} with line thickness 1.
505         *
506         * @param p
507         *            The shape to draw.
508         * @param col
509         *            The colour to draw the polygon in.
510         */
511        public void drawShape(final Shape p, final Q col) {
512                this.drawShape(p, 1, col);
513        }
514
515        /**
516         * Draw the given shape, filled with the specified colour.
517         *
518         * @param s
519         *            The shape to draw.
520         * @param col
521         *            The colour to fill the polygon with.
522         */
523        public void drawShapeFilled(final Shape s, Q col) {
524                col = this.sanitise(col);
525                if (s instanceof Polygon) {
526                        this.drawPolygonFilled((Polygon) s, col);
527                } else {
528                        this.drawShape(s, col);
529
530                        final int minx = (int) Math.max(0, Math.round(s.minX()));
531                        final int maxx = (int) Math.min(this.targetImage.getWidth(), Math.round(s.maxX()));
532                        final int miny = (int) Math.max(0, Math.round(s.minY()));
533                        final int maxy = (int) Math.min(this.targetImage.getHeight(), Math.round(s.maxY()));
534
535                        for (int y = miny; y <= maxy; y++) {
536                                for (int x = minx; x <= maxx; x++) {
537                                        final Pixel p = new Pixel(x, y);
538                                        if (s.isInside(p))
539                                                this.targetImage.setPixel(p.x, p.y, col);
540                                }
541                        }
542                }
543        }
544
545        /**
546         * Render the text in the given font with the default style.
547         *
548         * @param <F>
549         *            the font
550         * @param text
551         *            the text
552         * @param x
553         *            the x-ordinate
554         * @param y
555         *            the y-ordinate
556         * @param f
557         *            the font
558         * @param sz
559         *            the size
560         */
561        public <F extends Font<F>> void drawText(final String text, final int x, final int y, final F f, final int sz) {
562                final FontStyle<Q> sty = f.createStyle(this);
563                sty.setFontSize(sz);
564                f.getRenderer(this).renderText(this, text, x, y, sty);
565        }
566
567        /**
568         * Render the text in the given font in the given colour with the default
569         * style.
570         *
571         * @param <F>
572         *            the font
573         * @param text
574         *            the text
575         * @param x
576         *            the x-ordinate
577         * @param y
578         *            the y-ordinate
579         * @param f
580         *            the font
581         * @param sz
582         *            the size
583         * @param col
584         *            the font color
585         */
586        public <F extends Font<F>> void drawText(final String text, final int x, final int y, final F f, final int sz,
587                        final Q col)
588        {
589                final FontStyle<Q> sty = f.createStyle(this);
590                sty.setFontSize(sz);
591                sty.setColour(col);
592                f.getRenderer(this).renderText(this, text, x, y, sty);
593        }
594
595        /**
596         * Render the text in the given font with the default style.
597         *
598         * @param <F>
599         *            the font
600         * @param text
601         *            the text
602         * @param pt
603         *            the coordinate to render at
604         * @param f
605         *            the font
606         * @param sz
607         *            the size
608         */
609        public <F extends Font<F>> void drawText(final String text, final Point2d pt, final F f, final int sz) {
610                final FontStyle<Q> sty = f.createStyle(this);
611                sty.setFontSize(sz);
612                f.getRenderer(this).renderText(this, text, (int) pt.getX(), (int) pt.getY(), sty);
613        }
614
615        /**
616         * Render the text in the given font in the given colour with the default
617         * style.
618         *
619         * @param <F>
620         *            the font
621         * @param text
622         *            the text
623         * @param pt
624         *            the coordinate to render at
625         * @param f
626         *            the font
627         * @param sz
628         *            the size
629         * @param col
630         *            the font colour
631         */
632        public <F extends Font<F>> void drawText(final String text, final Point2d pt, final F f, final int sz, final Q col) {
633                final FontStyle<Q> sty = f.createStyle(this);
634                sty.setFontSize(sz);
635                sty.setColour(col);
636                f.getRenderer(this).renderText(this, text, (int) pt.getX(), (int) pt.getY(), sty);
637        }
638
639        /**
640         * Render the text with the given {@link FontStyle}.
641         *
642         * @param text
643         *            the text
644         * @param x
645         *            the x-ordinate
646         * @param y
647         *            the y-ordinate
648         * @param f
649         *            the font style
650         */
651        public void drawText(final String text, final int x, final int y, final FontStyle<Q> f) {
652                f.getRenderer(this).renderText(this, text, x, y, f);
653        }
654
655        /**
656         * Render the text with the given {@link FontStyle}.
657         *
658         * @param text
659         *            the text
660         * @param pt
661         *            the coordinate to render at
662         * @param f
663         *            the font style
664         */
665        public void drawText(final String text, final Point2d pt, final FontStyle<Q> f) {
666                f.getRenderer(this).renderText(this, text, (int) pt.getX(), (int) pt.getY(), f);
667        }
668
669        /**
670         * Render the text using its attributes.
671         *
672         * @param text
673         *            the text
674         * @param x
675         *            the x-ordinate
676         * @param y
677         *            the y-ordinate
678         */
679        public void drawText(final AttributedString text, final int x, final int y) {
680                FontRenderer.renderText(this, text, x, y);
681        }
682
683        /**
684         * Render the text using its attributes.
685         *
686         * @param text
687         *            the text
688         * @param pt
689         *            the coordinate to render at
690         */
691        public void drawText(final AttributedString text, final Point2d pt) {
692                FontRenderer.renderText(this, text, (int) pt.getX(), (int) pt.getY());
693        }
694
695        /**
696         * Draw a cubic Bezier curve into the image with 100 point accuracy.
697         *
698         * @param p1
699         *            One end point of the line
700         * @param p2
701         *            The other end point of the line
702         * @param c1
703         *            The control point associated with p1
704         * @param c2
705         *            The control point associated with p2
706         * @param thickness
707         *            The thickness to draw the line
708         * @param col
709         *            The colour to draw the line
710         * @return The points along the bezier curve
711         */
712        public Point2d[] drawCubicBezier(final Point2d p1, final Point2d p2,
713                        final Point2d c1, final Point2d c2, final int thickness, final Q col)
714        {
715                final List<Point2d> points = new ArrayList<Point2d>();
716
717                final CubicCurve2D c = new CubicCurve2D.Double(
718                                p1.getX(), p1.getY(), c1.getX(), c1.getY(),
719                                c2.getX(), c2.getY(), p2.getX(), p2.getY());
720                BezierUtils.adaptiveHalving(c, new SimpleConvexHullSubdivCriterion(),
721                                new CubicSegmentConsumer()
722                {
723                                        @Override
724                                        public void processSegment(final CubicCurve2D segment,
725                                                        final double startT, final double endT)
726                        {
727                                                if (0.0 == startT)
728                                                        points.add(new Point2dImpl(
729                                                                        (float) segment.getX1(), (float) segment.getY1()));
730
731                                                points.add(new Point2dImpl(
732                                                                (float) segment.getX2(), (float) segment.getY2()));
733                                        }
734                                });
735
736                Point2d last = null;
737                for (final Point2d p : points) {
738                        if (last != null)
739                                this.drawLine((int) last.getX(), (int) last.getY(),
740                                                (int) p.getX(), (int) p.getY(), thickness, col);
741                        last = p;
742                }
743
744                return points.toArray(new Point2d[1]);
745        }
746
747        /**
748         * Draw a Quadratic Bezier curve
749         *
750         * @param p1
751         * @param p2
752         * @param c1
753         * @param thickness
754         * @param colour
755         * @return a set of points on the curve
756         */
757        public Point2d[] drawQuadBezier(final Point2d p1, final Point2d p2, final Point2d c1,
758                        final int thickness, final Q colour)
759        {
760                final List<Point2d> points = new ArrayList<Point2d>();
761
762                final QuadCurve2D c = new QuadCurve2D.Double(
763                                p1.getX(), p1.getY(), c1.getX(), c1.getY(), p2.getX(), p2.getY());
764                BezierUtils.adaptiveHalving(c, new SimpleConvexHullSubdivCriterion(),
765                                new QuadSegmentConsumer()
766                {
767                                        @Override
768                                        public void processSegment(final QuadCurve2D segment, final double startT, final double endT) {
769                                                if (0.0 == startT)
770                                                        points.add(new Point2dImpl(
771                                                                        (float) segment.getX1(), (float) segment.getY1()));
772
773                                                points.add(new Point2dImpl(
774                                                                (float) segment.getX2(), (float) segment.getY2()));
775                                        }
776                                });
777
778                Point2d last = null;
779                for (final Point2d p : points) {
780                        if (last != null)
781                                this.drawLine((int) last.getX(), (int) last.getY(),
782                                                (int) p.getX(), (int) p.getY(), thickness, colour);
783                        last = p;
784                }
785
786                return points.toArray(new Point2d[1]);
787
788        }
789
790        /**
791         * Get the default foreground colour.
792         *
793         * @return the default foreground colour.
794         */
795        public abstract Q defaultForegroundColour();
796
797        /**
798         * Get the default foreground colour.
799         *
800         * @return the default foreground colour.
801         */
802        public abstract Q defaultBackgroundColour();
803
804        /**
805         * Get the target image
806         *
807         * @return the image
808         */
809        public I getImage() {
810                return this.targetImage;
811        }
812
813        /**
814         * Change the target image of this renderer.
815         *
816         * @param image
817         *            new target
818         */
819        public void setImage(final I image) {
820                this.targetImage = image;
821        }
822
823        /**
824         * Get the render hints object associated with this renderer
825         *
826         * @return the render hints
827         */
828        public RenderHints getRenderHints() {
829                return this.hints;
830        }
831
832        /**
833         * Set the render hints associated with this renderer
834         *
835         * @param hints
836         *            the new hints
837         */
838        public void setRenderHints(final RenderHints hints) {
839                this.hints = hints;
840        }
841
842        /**
843         * Sanitize the colour given to fit this image's pixel type.
844         *
845         * @param size
846         *            The colour to sanitize
847         * @return The array
848         */
849        protected abstract Q sanitise(Q colour);
850}