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 org.openimaj.image.FImage; 033import org.openimaj.math.geometry.line.Line2d; 034import org.openimaj.math.geometry.point.Point2d; 035import org.openimaj.math.geometry.point.Point2dImpl; 036import org.openimaj.math.geometry.shape.Polygon; 037 038/** 039 * {@link ImageRenderer} for {@link FImage} images. Supports both anti-aliased 040 * and fast rendering. 041 * 042 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 043 * 044 */ 045public class FImageRenderer extends ImageRenderer<Float, FImage> { 046 047 /** 048 * Construct with given target image. 049 * 050 * @param targetImage 051 * the target image. 052 */ 053 public FImageRenderer(final FImage targetImage) { 054 super(targetImage); 055 } 056 057 /** 058 * Construct with given target image and rendering hints. 059 * 060 * @param targetImage 061 * the target image. 062 * @param hints 063 * the render hints 064 */ 065 public FImageRenderer(final FImage targetImage, final RenderHints hints) { 066 super(targetImage, hints); 067 } 068 069 @Override 070 public Float defaultForegroundColour() { 071 return 1f; 072 } 073 074 @Override 075 public Float defaultBackgroundColour() { 076 return 0f; 077 } 078 079 /** 080 * {@inheritDoc} 081 * 082 * @see org.openimaj.image.renderer.ImageRenderer#drawLine(int, int, double, 083 * int, int, java.lang.Object) 084 */ 085 @Override 086 public void drawLine(final int x1, final int y1, final double theta, final int length, final int thickness, 087 final Float grey) 088 { 089 final int x2 = x1 + (int) Math.round(Math.cos(theta) * length); 090 final int y2 = y1 + (int) Math.round(Math.sin(theta) * length); 091 092 this.drawLine(x1, y1, x2, y2, thickness, grey); 093 } 094 095 /** 096 * {@inheritDoc} 097 * 098 * @see org.openimaj.image.renderer.ImageRenderer#drawLine(int, int, int, 099 * int, int, java.lang.Object) 100 */ 101 @Override 102 public void drawLine(final int x0, final int y0, final int x1, final int y1, final int thickness, final Float grey) { 103 this.drawLine((float) x0, (float) y0, (float) x1, (float) y1, thickness, grey); 104 } 105 106 @Override 107 public void drawLine(final float x0, final float y0, final float x1, final float y1, final int thickness, 108 final Float grey) 109 { 110 switch (this.hints.drawingAlgorithm) { 111 case ANTI_ALIASED: 112 if (thickness <= 1) { 113 this.drawLineXiaolinWu(x0, y0, x1, y1, grey); 114 } else { 115 final double theta = Math.atan2(y1 - y0, x1 - x0); 116 final double t = thickness / 2; 117 final double sin = t * Math.sin(theta); 118 final double cos = t * Math.cos(theta); 119 120 final Polygon p = new Polygon(); 121 p.addVertex(new Point2dImpl((float) (x0 - sin), (float) (y0 + cos))); 122 p.addVertex(new Point2dImpl((float) (x0 + sin), (float) (y0 - cos))); 123 p.addVertex(new Point2dImpl((float) (x1 + sin), (float) (y1 - cos))); 124 p.addVertex(new Point2dImpl((float) (x1 - sin), (float) (y1 + cos))); 125 126 this.drawPolygonFilled(p, grey); 127 } 128 break; 129 default: 130 this.drawLineBresenham(Math.round(x0), Math.round(y0), Math.round(x1), Math.round(y1), thickness, grey); 131 } 132 } 133 134 private float fpart(final float f) { 135 return f - (int) f; 136 } 137 138 private float rfpart(final float f) { 139 return 1 - this.fpart(f); 140 } 141 142 private void plot(final int a, final int b, final float c, final float grey, final boolean reversed) { 143 int x, y; 144 if (reversed) { 145 y = a; 146 x = b; 147 } else { 148 x = a; 149 y = b; 150 } 151 152 if (x >= 0 && x < this.targetImage.width && y >= 0 && y < this.targetImage.height && !Float.isNaN(c)) { 153 this.targetImage.pixels[y][x] = c * grey + (1 - c) * this.targetImage.pixels[y][x]; 154 } 155 } 156 157 /* 158 * Implementation of Xiaolin Wu's anti-aliased line drawing algorithm. Based 159 * on the wikipedia article: 160 * http://en.wikipedia.org/wiki/Xiaolin_Wu's_line_algorithm 161 */ 162 protected void drawLineXiaolinWu(float x1, float y1, float x2, float y2, final Float grey) { 163 float dx = x2 - x1; 164 float dy = y2 - y1; 165 boolean reversed = false; 166 167 if (Math.abs(dx) < Math.abs(dy)) { 168 float tmp; 169 tmp = x1; 170 x1 = y1; 171 y1 = tmp; 172 tmp = x2; 173 x2 = y2; 174 y2 = tmp; 175 tmp = dx; 176 dx = dy; 177 dy = tmp; 178 reversed = true; 179 } 180 181 if (x2 < x1) { 182 float tmp; 183 tmp = x1; 184 x1 = x2; 185 x2 = tmp; 186 tmp = y1; 187 y1 = y2; 188 y2 = tmp; 189 } 190 191 final float gradient = dy / dx; 192 193 // handle first endpoint 194 int xend = Math.round(x1); 195 float yend = y1 + gradient * (xend - x1); 196 float xgap = this.rfpart(x1 + 0.5f); 197 final int xpxl1 = xend; // this will be used in the main loop 198 final int ypxl1 = (int) (yend); 199 this.plot(xpxl1, ypxl1, this.rfpart(yend) * xgap, grey, reversed); 200 this.plot(xpxl1, ypxl1 + 1, this.fpart(yend) * xgap, grey, reversed); 201 float intery = yend + gradient; // first y-intersection for the main 202 // loop 203 204 // handle second endpoint 205 xend = Math.round(x2); 206 yend = y2 + gradient * (xend - x2); 207 xgap = this.fpart(x2 + 0.5f); 208 final int xpxl2 = xend; // this will be used in the main loop 209 final int ypxl2 = (int) (yend); 210 this.plot(xpxl2, ypxl2, this.rfpart(yend) * xgap, grey, reversed); 211 this.plot(xpxl2, ypxl2 + 1, this.fpart(yend) * xgap, grey, reversed); 212 213 // main loop 214 for (int x = xpxl1 + 1; x <= xpxl2 - 1; x++) { 215 this.plot(x, (int) (intery), this.rfpart(intery), grey, reversed); 216 this.plot(x, (int) (intery) + 1, this.fpart(intery), grey, reversed); 217 intery += gradient; 218 } 219 } 220 221 /* 222 * Implementation of Bresenham's fast line drawing algorithm. Based on the 223 * wikipedia article: 224 * http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm 225 */ 226 protected void drawLineBresenham(int x0, int y0, int x1, int y1, int thickness, final Float grey) { 227 final Line2d line = new Line2d(new Point2dImpl(x0, y0), new Point2dImpl(x1, y1)) 228 .lineWithinSquare(this.targetImage 229 .getBounds()); 230 if (line == null) 231 return; 232 233 x0 = (int) line.begin.getX(); 234 y0 = (int) line.begin.getY(); 235 x1 = (int) line.end.getX(); 236 y1 = (int) line.end.getY(); 237 238 final double theta = Math.atan2(y1 - y0, x1 - x0); 239 thickness = (int) Math.round(thickness * Math.max(Math.abs(Math.cos(theta)), Math.abs(Math.sin(theta)))); 240 241 final int offset = thickness / 2; 242 final int extra = thickness % 2; 243 244 // implementation of Bresenham's algorithm from Wikipedia. 245 int Dx = x1 - x0; 246 int Dy = y1 - y0; 247 final boolean steep = (Math.abs(Dy) >= Math.abs(Dx)); 248 if (steep) { 249 int tmp; 250 // SWAP(x0, y0); 251 tmp = x0; 252 x0 = y0; 253 y0 = tmp; 254 // SWAP(x1, y1); 255 tmp = x1; 256 x1 = y1; 257 y1 = tmp; 258 259 // recompute Dx, Dy after swap 260 Dx = x1 - x0; 261 Dy = y1 - y0; 262 } 263 int xstep = 1; 264 if (Dx < 0) { 265 xstep = -1; 266 Dx = -Dx; 267 } 268 int ystep = 1; 269 if (Dy < 0) { 270 ystep = -1; 271 Dy = -Dy; 272 } 273 final int TwoDy = 2 * Dy; 274 final int TwoDyTwoDx = TwoDy - 2 * Dx; // 2*Dy - 2*Dx 275 int E = TwoDy - Dx; // 2*Dy - Dx 276 int y = y0; 277 int xDraw, yDraw; 278 for (int x = x0; x != x1; x += xstep) { 279 if (steep) { 280 xDraw = y; 281 yDraw = x; 282 } else { 283 xDraw = x; 284 yDraw = y; 285 } 286 // plot 287 if (xDraw >= 0 && xDraw < this.targetImage.width && yDraw >= 0 && yDraw < this.targetImage.height) { 288 if (thickness == 1) { 289 this.targetImage.pixels[yDraw][xDraw] = grey; 290 } else if (thickness > 1) { 291 for (int yy = yDraw - offset; yy < yDraw + offset + extra; yy++) 292 for (int xx = xDraw - offset; xx < xDraw + offset + extra; xx++) 293 if (xx >= 0 && yy >= 0 && xx < this.targetImage.width && yy < this.targetImage.height) 294 this.targetImage.pixels[yy][xx] = grey; 295 } 296 } 297 298 // next 299 if (E > 0) { 300 E += TwoDyTwoDx; // E += 2*Dy - 2*Dx; 301 y = y + ystep; 302 } else { 303 E += TwoDy; // E += 2*Dy; 304 } 305 } 306 } 307 308 /** 309 * {@inheritDoc} 310 * 311 * @see org.openimaj.image.renderer.ImageRenderer#drawPoint(org.openimaj.math.geometry.point.Point2d, 312 * java.lang.Object, int) 313 */ 314 @Override 315 public void drawPoint(final Point2d p, final Float grey, final int size) { 316 317 if (!this.targetImage.getBounds().isInside(p)) 318 return; 319 final int halfsize = (size + 1) / 2; // 3 == 2, 4 = 2, 5 = 3, 6 = 3 etc. 320 // TODO anti-aliased point rendering 321 final int x = Math.round(p.getX()); 322 final int y = Math.round(p.getY()); 323 final int startx = Math.max(0, x - (halfsize - 1)); 324 final int starty = Math.max(0, y - (halfsize - 1)); 325 final int endx = Math.min(this.targetImage.width, x + halfsize); 326 final int endy = Math.min(this.targetImage.height, y + halfsize); 327 328 for (int j = starty; j < endy; j++) { 329 for (int i = startx; i < endx; i++) { 330 this.targetImage.pixels[j][i] = grey; 331 } 332 } 333 } 334 335 /** 336 * {@inheritDoc} 337 * 338 * @see org.openimaj.image.renderer.ImageRenderer#drawPolygon(org.openimaj.math.geometry.shape.Polygon, 339 * int, java.lang.Object) 340 */ 341 @Override 342 public void drawPolygon(final Polygon p, final int thickness, final Float grey) { 343 if (p.nVertices() < 2) 344 return; 345 346 Point2d p1, p2; 347 for (int i = 0; i < p.nVertices() - 1; i++) { 348 p1 = p.getVertices().get(i); 349 p2 = p.getVertices().get(i + 1); 350 this.drawLine(p1.getX(), p1.getY(), p2.getX(), p2.getY(), thickness, grey); 351 } 352 353 p1 = p.getVertices().get(p.nVertices() - 1); 354 p2 = p.getVertices().get(0); 355 this.drawLine(p1.getX(), p1.getY(), p2.getX(), p2.getY(), thickness, grey); 356 357 for (final Polygon i : p.getInnerPolys()) 358 drawPolygon(i, thickness, grey); 359 } 360 361 @Override 362 protected void drawHorizLine(final int x1, final int x2, final int y, final Float col) { 363 if (y < 0 || y > this.targetImage.getHeight() - 1) 364 return; 365 366 final int startx = Math.max(0, Math.min(x1, x2)); 367 final int stopx = Math.min(Math.max(x1, x2), this.targetImage.getWidth() - 1); 368 final float[][] img = this.targetImage.pixels; 369 final float c = col; 370 371 for (int x = startx; x <= stopx; x++) { 372 img[y][x] = c; 373 } 374 } 375 376 @Override 377 protected Float sanitise(final Float colour) 378 { 379 return colour; 380 } 381}