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}