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.List;
038
039import org.openimaj.image.Image;
040import org.openimaj.image.pixel.ConnectedComponent;
041import org.openimaj.image.pixel.Pixel;
042import org.openimaj.image.processor.connectedcomponent.render.BlobRenderer;
043import org.openimaj.image.renderer.ScanRasteriser.ScanLineListener;
044import org.openimaj.image.typography.Font;
045import org.openimaj.image.typography.FontRenderer;
046import org.openimaj.image.typography.FontStyle;
047import org.openimaj.math.geometry.line.Line2d;
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, 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 Line2d 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 Line2d line, final int thickness, final Q col) {
321                this.drawLine((int) line.begin.getX(), (int) line.begin.getY(), (int) line.end.getX(), (int) line.end.getY(),
322                                thickness, col);
323        }
324
325        /**
326         * Draw the given list of lines using {@link #drawLine(Line2d, int, Object)}
327         * with the given colour and thickness.
328         * 
329         * @param lines
330         *            The list of lines to draw.
331         * @param thickness
332         *            the stroke width
333         * @param col
334         *            The colour to draw each point.
335         */
336        public void drawLines(final Iterable<? extends Line2d> lines, final int thickness, final Q col) {
337                for (final Line2d line : lines)
338                        this.drawLine(line, thickness, col);
339        }
340
341        /**
342         * Draw a dot centered on the given location (rounded to nearest integer
343         * location) at the given size and with the given color.
344         * 
345         * 
346         * @param p
347         *            The coordinates at which to draw the point
348         * @param col
349         *            The colour to draw the point
350         * @param size
351         *            The size at which to draw the point.
352         */
353        public abstract void drawPoint(Point2d p, Q col, int size);
354
355        /**
356         * Draw the given list of points using
357         * {@link #drawPoint(Point2d, Object, int)} with the given colour and size.
358         * 
359         * @param pts
360         *            The list of points to draw.
361         * @param col
362         *            The colour to draw each point.
363         * @param size
364         *            The size to draw each point.
365         */
366        public void drawPoints(final Iterable<? extends Point2d> pts, final Q col, final int size) {
367                for (final Point2d p : pts)
368                        this.drawPoint(p, col, size);
369        }
370
371        /**
372         * Draw the given polygon in the specified colour with the given thickness
373         * lines.
374         * 
375         * 
376         * @param p
377         *            The polygon to draw.
378         * @param thickness
379         *            The thickness of the lines to use
380         * @param col
381         *            The colour to draw the lines in
382         */
383        public abstract void drawPolygon(Polygon p, int thickness, Q col);
384
385        /**
386         * Draw the given polygon in the specified colour. Uses
387         * {@link #drawPolygon(Polygon, int, Object)} with line thickness 1.
388         * 
389         * 
390         * @param p
391         *            The polygon to draw.
392         * @param col
393         *            The colour to draw the polygon in.
394         */
395        public void drawPolygon(final Polygon p, final Q col) {
396                this.drawPolygon(p, 1, col);
397        }
398
399        /**
400         * Draw a horizontal line with the specified colour.
401         * 
402         * @param x1
403         *            starting x (inclusive)
404         * @param x2
405         *            ending x (inclusive)
406         * @param y
407         *            y
408         * @param col
409         *            the colour
410         */
411        protected abstract void drawHorizLine(int x1, int x2, int y, Q col);
412
413        /**
414         * Draw the given polygon, filled with the specified colour.
415         * 
416         * @param p
417         *            The polygon to draw.
418         * @param col
419         *            The colour to fill the polygon with.
420         */
421        public void drawPolygonFilled(final Polygon p, final Q col) {
422                this.drawPolygon(p, col);
423
424                if (p.getNumInnerPoly() == 1) {
425                        ScanRasteriser.scanFill(p.points, new ScanLineListener() {
426                                @Override
427                                public void process(final int x1, final int x2, final int y) {
428                                        ImageRenderer.this.drawHorizLine(x1, x2, y, col);
429                                }
430                        });
431                } else {
432                        final ConnectedComponent cc = new ConnectedComponent(p);
433                        cc.process(new BlobRenderer<Q>(this.targetImage, col));
434                }
435        }
436
437        /**
438         * Draw the given shape in the specified colour with the given thickness
439         * lines.
440         * 
441         * @param s
442         *            The shape to draw.
443         * @param thickness
444         *            The thickness of the lines to use
445         * @param col
446         *            The colour to draw the lines in
447         */
448        public void drawShape(final Shape s, final int thickness, final Q col) {
449                this.drawPolygon(s.asPolygon(), thickness, col);
450        }
451
452        /**
453         * Draw the given shape in the specified colour. Uses
454         * {@link #drawPolygon(Polygon, int, Object)} with line thickness 1.
455         * 
456         * @param p
457         *            The shape to draw.
458         * @param col
459         *            The colour to draw the polygon in.
460         */
461        public void drawShape(final Shape p, final Q col) {
462                this.drawShape(p, 1, col);
463        }
464
465        /**
466         * Draw the given shape, filled with the specified colour.
467         * 
468         * @param s
469         *            The shape to draw.
470         * @param col
471         *            The colour to fill the polygon with.
472         */
473        public void drawShapeFilled(final Shape s, Q col) {
474                col = this.sanitise(col);
475                if (s instanceof Polygon) {
476                        this.drawPolygonFilled((Polygon) s, col);
477                } else {
478                        this.drawShape(s, col);
479
480                        final int minx = (int) Math.max(0, Math.round(s.minX()));
481                        final int maxx = (int) Math.min(this.targetImage.getWidth(), Math.round(s.maxX()));
482                        final int miny = (int) Math.max(0, Math.round(s.minY()));
483                        final int maxy = (int) Math.min(this.targetImage.getHeight(), Math.round(s.maxY()));
484
485                        for (int y = miny; y <= maxy; y++) {
486                                for (int x = minx; x <= maxx; x++) {
487                                        final Pixel p = new Pixel(x, y);
488                                        if (s.isInside(p))
489                                                this.targetImage.setPixel(p.x, p.y, col);
490                                }
491                        }
492                }
493        }
494
495        /**
496         * Render the text in the given font with the default style.
497         * 
498         * @param <F>
499         *            the font
500         * @param text
501         *            the text
502         * @param x
503         *            the x-ordinate
504         * @param y
505         *            the y-ordinate
506         * @param f
507         *            the font
508         * @param sz
509         *            the size
510         */
511        public <F extends Font<F>> void drawText(final String text, final int x, final int y, final F f, final int sz) {
512                final FontStyle<Q> sty = f.createStyle(this);
513                sty.setFontSize(sz);
514                f.getRenderer(this).renderText(this, text, x, y, sty);
515        }
516
517        /**
518         * Render the text in the given font in the given colour with the default
519         * style.
520         * 
521         * @param <F>
522         *            the font
523         * @param text
524         *            the text
525         * @param x
526         *            the x-ordinate
527         * @param y
528         *            the y-ordinate
529         * @param f
530         *            the font
531         * @param sz
532         *            the size
533         * @param col
534         *            the font color
535         */
536        public <F extends Font<F>> void drawText(final String text, final int x, final int y, final F f, final int sz,
537                        final Q col)
538        {
539                final FontStyle<Q> sty = f.createStyle(this);
540                sty.setFontSize(sz);
541                sty.setColour(col);
542                f.getRenderer(this).renderText(this, text, x, y, sty);
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 pt
553         *            the coordinate to render at
554         * @param f
555         *            the font
556         * @param sz
557         *            the size
558         */
559        public <F extends Font<F>> void drawText(final String text, final Point2d pt, final F f, final int sz) {
560                final FontStyle<Q> sty = f.createStyle(this);
561                sty.setFontSize(sz);
562                f.getRenderer(this).renderText(this, text, (int) pt.getX(), (int) pt.getY(), sty);
563        }
564
565        /**
566         * Render the text in the given font in the given colour with the default
567         * style.
568         * 
569         * @param <F>
570         *            the font
571         * @param text
572         *            the text
573         * @param pt
574         *            the coordinate to render at
575         * @param f
576         *            the font
577         * @param sz
578         *            the size
579         * @param col
580         *            the font colour
581         */
582        public <F extends Font<F>> void drawText(final String text, final Point2d pt, final F f, final int sz, final Q col) {
583                final FontStyle<Q> sty = f.createStyle(this);
584                sty.setFontSize(sz);
585                sty.setColour(col);
586                f.getRenderer(this).renderText(this, text, (int) pt.getX(), (int) pt.getY(), sty);
587        }
588
589        /**
590         * Render the text with the given {@link FontStyle}.
591         * 
592         * @param text
593         *            the text
594         * @param x
595         *            the x-ordinate
596         * @param y
597         *            the y-ordinate
598         * @param f
599         *            the font style
600         */
601        public void drawText(final String text, final int x, final int y, final FontStyle<Q> f) {
602                f.getRenderer(this).renderText(this, text, x, y, f);
603        }
604
605        /**
606         * Render the text with the given {@link FontStyle}.
607         * 
608         * @param text
609         *            the text
610         * @param pt
611         *            the coordinate to render at
612         * @param f
613         *            the font style
614         */
615        public void drawText(final String text, final Point2d pt, final FontStyle<Q> f) {
616                f.getRenderer(this).renderText(this, text, (int) pt.getX(), (int) pt.getY(), f);
617        }
618
619        /**
620         * Render the text using its attributes.
621         * 
622         * @param text
623         *            the text
624         * @param x
625         *            the x-ordinate
626         * @param y
627         *            the y-ordinate
628         */
629        public void drawText(final AttributedString text, final int x, final int y) {
630                FontRenderer.renderText(this, text, x, y);
631        }
632
633        /**
634         * Render the text using its attributes.
635         * 
636         * @param text
637         *            the text
638         * @param pt
639         *            the coordinate to render at
640         */
641        public void drawText(final AttributedString text, final Point2d pt) {
642                FontRenderer.renderText(this, text, (int) pt.getX(), (int) pt.getY());
643        }
644
645        /**
646         * Draw a cubic Bezier curve into the image with 100 point accuracy.
647         * 
648         * @param p1
649         *            One end point of the line
650         * @param p2
651         *            The other end point of the line
652         * @param c1
653         *            The control point associated with p1
654         * @param c2
655         *            The control point associated with p2
656         * @param thickness
657         *            The thickness to draw the line
658         * @param col
659         *            The colour to draw the line
660         * @return The points along the bezier curve
661         */
662        public Point2d[] drawCubicBezier(final Point2d p1, final Point2d p2,
663                        final Point2d c1, final Point2d c2, final int thickness, final Q col)
664        {
665                final List<Point2d> points = new ArrayList<Point2d>();
666
667                final CubicCurve2D c = new CubicCurve2D.Double(
668                                p1.getX(), p1.getY(), c1.getX(), c1.getY(),
669                                c2.getX(), c2.getY(), p2.getX(), p2.getY());
670                BezierUtils.adaptiveHalving(c, new SimpleConvexHullSubdivCriterion(),
671                                new CubicSegmentConsumer()
672                                {
673                                        @Override
674                                        public void processSegment(final CubicCurve2D segment,
675                                                        final double startT, final double endT)
676                                        {
677                                                if (0.0 == startT)
678                                                        points.add(new Point2dImpl(
679                                                                        (float) segment.getX1(), (float) segment.getY1()));
680
681                                                points.add(new Point2dImpl(
682                                                                (float) segment.getX2(), (float) segment.getY2()));
683                                        }
684                                }
685                                );
686
687                Point2d last = null;
688                for (final Point2d p : points) {
689                        if (last != null)
690                                this.drawLine((int) last.getX(), (int) last.getY(),
691                                                (int) p.getX(), (int) p.getY(), thickness, col);
692                        last = p;
693                }
694
695                return points.toArray(new Point2d[1]);
696        }
697
698        /**
699         * Draw a Quadratic Bezier curve
700         * 
701         * @param p1
702         * @param p2
703         * @param c1
704         * @param thickness
705         * @param colour
706         * @return a set of points on the curve
707         */
708        public Point2d[] drawQuadBezier(final Point2d p1, final Point2d p2, final Point2d c1,
709                        final int thickness, final Q colour)
710        {
711                final List<Point2d> points = new ArrayList<Point2d>();
712
713                final QuadCurve2D c = new QuadCurve2D.Double(
714                                p1.getX(), p1.getY(), c1.getX(), c1.getY(), p2.getX(), p2.getY());
715                BezierUtils.adaptiveHalving(c, new SimpleConvexHullSubdivCriterion(),
716                                new QuadSegmentConsumer()
717                                {
718                                        @Override
719                                        public void processSegment(final QuadCurve2D segment, final double startT, final double endT)
720                                        {
721                                                if (0.0 == startT)
722                                                        points.add(new Point2dImpl(
723                                                                        (float) segment.getX1(), (float) segment.getY1()));
724
725                                                points.add(new Point2dImpl(
726                                                                (float) segment.getX2(), (float) segment.getY2()));
727                                        }
728                                }
729                                );
730
731                Point2d last = null;
732                for (final Point2d p : points) {
733                        if (last != null)
734                                this.drawLine((int) last.getX(), (int) last.getY(),
735                                                (int) p.getX(), (int) p.getY(), thickness, colour);
736                        last = p;
737                }
738
739                return points.toArray(new Point2d[1]);
740
741        }
742
743        /**
744         * Get the default foreground colour.
745         * 
746         * @return the default foreground colour.
747         */
748        public abstract Q defaultForegroundColour();
749
750        /**
751         * Get the default foreground colour.
752         * 
753         * @return the default foreground colour.
754         */
755        public abstract Q defaultBackgroundColour();
756
757        /**
758         * Get the target image
759         * 
760         * @return the image
761         */
762        public I getImage() {
763                return this.targetImage;
764        }
765
766        /**
767         * Change the target image of this renderer.
768         * 
769         * @param image
770         *            new target
771         */
772        public void setImage(final I image) {
773                this.targetImage = image;
774        }
775
776        /**
777         * Get the render hints object associated with this renderer
778         * 
779         * @return the render hints
780         */
781        public RenderHints getRenderHints() {
782                return this.hints;
783        }
784
785        /**
786         * Set the render hints associated with this renderer
787         * 
788         * @param hints
789         *            the new hints
790         */
791        public void setRenderHints(final RenderHints hints) {
792                this.hints = hints;
793        }
794
795        /**
796         * Sanitize the colour given to fit this image's pixel type.
797         * 
798         * @param size
799         *            The colour to sanitize
800         * @return The array
801         */
802        protected abstract Q sanitise(Q colour);
803}