1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package org.openimaj.image.renderer;
31
32 import org.openimaj.image.FImage;
33 import org.openimaj.image.pixel.Pixel;
34 import org.openimaj.image.renderer.ScanRasteriser.ScanLineListener;
35 import org.openimaj.math.geometry.line.Line2d;
36 import org.openimaj.math.geometry.point.Point2d;
37 import org.openimaj.math.geometry.point.Point2dImpl;
38 import org.openimaj.math.geometry.shape.Polygon;
39
40
41
42
43
44
45
46
47 public class FImageRenderer extends ImageRenderer<Float, FImage> {
48
49
50
51
52
53
54
55 public FImageRenderer(final FImage targetImage) {
56 super(targetImage);
57 }
58
59
60
61
62
63
64
65
66
67 public FImageRenderer(final FImage targetImage, final RenderHints hints) {
68 super(targetImage, hints);
69 }
70
71 @Override
72 public Float defaultForegroundColour() {
73 return 1f;
74 }
75
76 @Override
77 public Float defaultBackgroundColour() {
78 return 0f;
79 }
80
81
82
83
84
85
86
87 @Override
88 public void drawLine(final int x1, final int y1, final double theta, final int length, final int thickness,
89 final Float grey)
90 {
91 final int x2 = x1 + (int) Math.round(Math.cos(theta) * length);
92 final int y2 = y1 + (int) Math.round(Math.sin(theta) * length);
93
94 this.drawLine(x1, y1, x2, y2, thickness, grey);
95 }
96
97
98
99
100
101
102
103 @Override
104 public void drawLine(final int x0, final int y0, final int x1, final int y1, final int thickness, final Float grey) {
105 this.drawLine((float) x0, (float) y0, (float) x1, (float) y1, thickness, grey);
106 }
107
108 @Override
109 public void drawLine(final float x0, final float y0, final float x1, final float y1, final int thickness,
110 final Float grey)
111 {
112 switch (this.hints.drawingAlgorithm) {
113 case ANTI_ALIASED:
114 if (thickness <= 1) {
115 this.drawLineXiaolinWu(x0, y0, x1, y1, grey);
116 } else {
117 final double theta = Math.atan2(y1 - y0, x1 - x0);
118 final double t = thickness / 2;
119 final double sin = t * Math.sin(theta);
120 final double cos = t * Math.cos(theta);
121
122 final Polygon p = new Polygon();
123 p.addVertex(new Point2dImpl((float) (x0 - sin), (float) (y0 + cos)));
124 p.addVertex(new Point2dImpl((float) (x0 + sin), (float) (y0 - cos)));
125 p.addVertex(new Point2dImpl((float) (x1 + sin), (float) (y1 - cos)));
126 p.addVertex(new Point2dImpl((float) (x1 - sin), (float) (y1 + cos)));
127
128 this.drawPolygonFilled(p, grey);
129 }
130 break;
131 default:
132 this.drawLineBresenham(Math.round(x0), Math.round(y0), Math.round(x1), Math.round(y1), thickness, grey);
133 }
134 }
135
136 private float fpart(final float f) {
137 return f - (int) f;
138 }
139
140 private float rfpart(final float f) {
141 return 1 - this.fpart(f);
142 }
143
144 private void plot(final int a, final int b, final float c, final float grey, final boolean reversed) {
145 int x, y;
146 if (reversed) {
147 y = a;
148 x = b;
149 } else {
150 x = a;
151 y = b;
152 }
153
154 if (x >= 0 && x < this.targetImage.width && y >= 0 && y < this.targetImage.height && !Float.isNaN(c)) {
155 this.targetImage.pixels[y][x] = c * grey + (1 - c) * this.targetImage.pixels[y][x];
156 }
157 }
158
159
160
161
162
163
164 protected void drawLineXiaolinWu(float x1, float y1, float x2, float y2, final Float grey) {
165 float dx = x2 - x1;
166 float dy = y2 - y1;
167 boolean reversed = false;
168
169 if (Math.abs(dx) < Math.abs(dy)) {
170 float tmp;
171 tmp = x1;
172 x1 = y1;
173 y1 = tmp;
174 tmp = x2;
175 x2 = y2;
176 y2 = tmp;
177 tmp = dx;
178 dx = dy;
179 dy = tmp;
180 reversed = true;
181 }
182
183 if (x2 < x1) {
184 float tmp;
185 tmp = x1;
186 x1 = x2;
187 x2 = tmp;
188 tmp = y1;
189 y1 = y2;
190 y2 = tmp;
191 }
192
193 final float gradient = dy / dx;
194
195
196 int xend = Math.round(x1);
197 float yend = y1 + gradient * (xend - x1);
198 float xgap = this.rfpart(x1 + 0.5f);
199 final int xpxl1 = xend;
200 final int ypxl1 = (int) (yend);
201 this.plot(xpxl1, ypxl1, this.rfpart(yend) * xgap, grey, reversed);
202 this.plot(xpxl1, ypxl1 + 1, this.fpart(yend) * xgap, grey, reversed);
203 float intery = yend + gradient;
204
205
206
207 xend = Math.round(x2);
208 yend = y2 + gradient * (xend - x2);
209 xgap = this.fpart(x2 + 0.5f);
210 final int xpxl2 = xend;
211 final int ypxl2 = (int) (yend);
212 this.plot(xpxl2, ypxl2, this.rfpart(yend) * xgap, grey, reversed);
213 this.plot(xpxl2, ypxl2 + 1, this.fpart(yend) * xgap, grey, reversed);
214
215
216 for (int x = xpxl1 + 1; x <= xpxl2 - 1; x++) {
217 this.plot(x, (int) (intery), this.rfpart(intery), grey, reversed);
218 this.plot(x, (int) (intery) + 1, this.fpart(intery), grey, reversed);
219 intery += gradient;
220 }
221 }
222
223
224
225
226
227
228 protected void drawLineBresenham(int x0, int y0, int x1, int y1, int thickness, final Float grey) {
229 final Line2d line = new Line2d(new Point2dImpl(x0, y0), new Point2dImpl(x1, y1))
230 .lineWithinSquare(this.targetImage
231 .getBounds());
232 if (line == null)
233 return;
234
235 x0 = (int) line.begin.getX();
236 y0 = (int) line.begin.getY();
237 x1 = (int) line.end.getX();
238 y1 = (int) line.end.getY();
239
240 final double theta = Math.atan2(y1 - y0, x1 - x0);
241 thickness = (int) Math.round(thickness * Math.max(Math.abs(Math.cos(theta)), Math.abs(Math.sin(theta))));
242
243 final int offset = thickness / 2;
244 final int extra = thickness % 2;
245
246
247 int Dx = x1 - x0;
248 int Dy = y1 - y0;
249 final boolean steep = (Math.abs(Dy) >= Math.abs(Dx));
250 if (steep) {
251 int tmp;
252
253 tmp = x0;
254 x0 = y0;
255 y0 = tmp;
256
257 tmp = x1;
258 x1 = y1;
259 y1 = tmp;
260
261
262 Dx = x1 - x0;
263 Dy = y1 - y0;
264 }
265 int xstep = 1;
266 if (Dx < 0) {
267 xstep = -1;
268 Dx = -Dx;
269 }
270 int ystep = 1;
271 if (Dy < 0) {
272 ystep = -1;
273 Dy = -Dy;
274 }
275 final int TwoDy = 2 * Dy;
276 final int TwoDyTwoDx = TwoDy - 2 * Dx;
277 int E = TwoDy - Dx;
278 int y = y0;
279 int xDraw, yDraw;
280 for (int x = x0; x != x1; x += xstep) {
281 if (steep) {
282 xDraw = y;
283 yDraw = x;
284 } else {
285 xDraw = x;
286 yDraw = y;
287 }
288
289 if (xDraw >= 0 && xDraw < this.targetImage.width && yDraw >= 0 && yDraw < this.targetImage.height) {
290 if (thickness == 1) {
291 this.targetImage.pixels[yDraw][xDraw] = grey;
292 } else if (thickness > 1) {
293 for (int yy = yDraw - offset; yy < yDraw + offset + extra; yy++)
294 for (int xx = xDraw - offset; xx < xDraw + offset + extra; xx++)
295 if (xx >= 0 && yy >= 0 && xx < this.targetImage.width && yy < this.targetImage.height)
296 this.targetImage.pixels[yy][xx] = grey;
297 }
298 }
299
300
301 if (E > 0) {
302 E += TwoDyTwoDx;
303 y = y + ystep;
304 } else {
305 E += TwoDy;
306 }
307 }
308 }
309
310
311
312
313
314
315
316 @Override
317 public void drawPoint(final Point2d p, final Float grey, final int size) {
318
319 if (!this.targetImage.getBounds().isInside(p))
320 return;
321 final int halfsize = (size + 1) / 2;
322
323 final int x = Math.round(p.getX());
324 final int y = Math.round(p.getY());
325 final int startx = Math.max(0, x - (halfsize - 1));
326 final int starty = Math.max(0, y - (halfsize - 1));
327 final int endx = Math.min(this.targetImage.width, x + halfsize);
328 final int endy = Math.min(this.targetImage.height, y + halfsize);
329
330 for (int j = starty; j < endy; j++) {
331 for (int i = startx; i < endx; i++) {
332 this.targetImage.pixels[j][i] = grey;
333 }
334 }
335 }
336
337
338
339
340
341
342
343 @Override
344 public void drawPolygon(final Polygon p, final int thickness, final Float grey) {
345 if (p.nVertices() < 2)
346 return;
347
348 Point2d p1, p2;
349 for (int i = 0; i < p.nVertices() - 1; i++) {
350 p1 = p.getVertices().get(i);
351 p2 = p.getVertices().get(i + 1);
352 this.drawLine(p1.getX(), p1.getY(), p2.getX(), p2.getY(), thickness, grey);
353 }
354
355 p1 = p.getVertices().get(p.nVertices() - 1);
356 p2 = p.getVertices().get(0);
357 this.drawLine(p1.getX(), p1.getY(), p2.getX(), p2.getY(), thickness, grey);
358
359 for (final Polygon i : p.getInnerPolys())
360 drawPolygon(i, thickness, grey);
361 }
362
363 @Override
364 protected void drawHorizLine(final int x1, final int x2, final int y, final Float col) {
365 if (y < 0 || y > this.targetImage.getHeight() - 1)
366 return;
367
368 final int startx = Math.max(0, Math.min(x1, x2));
369 final int stopx = Math.min(Math.max(x1, x2), this.targetImage.getWidth() - 1);
370 final float[][] img = this.targetImage.pixels;
371 final float c = col;
372
373 for (int x = startx; x <= stopx; x++) {
374 img[y][x] = c;
375 }
376 }
377
378 @Override
379 protected Float sanitise(final Float colour) {
380 return colour;
381 }
382
383 @Override
384 public void drawPolygonFilled(Polygon p, final Float col) {
385
386
387
388 this.drawPolygon(p, col);
389
390 if (p.getNumInnerPoly() == 1) {
391 ScanRasteriser.scanFill(p.points, new ScanLineListener() {
392 @Override
393 public void process(final int x1, final int x2, final int y) {
394 FImageRenderer.this.drawHorizLine(x1, x2, y, col);
395 }
396 });
397 } else {
398 final int minx = Math.max(0, (int) Math.round(p.minX()));
399 final int maxx = Math.min((int) Math.round(p.maxX()), targetImage.width - 1);
400 final int miny = Math.max(0, (int) Math.round(p.minY()));
401 final int maxy = Math.min((int) Math.round(p.maxY()), targetImage.height - 1);
402
403 final Pixel tmp = new Pixel();
404 for (tmp.y = miny; tmp.y <= maxy; tmp.y++) {
405 for (tmp.x = minx; tmp.x <= maxx; tmp.x++) {
406 if (p.isInside(tmp))
407 this.targetImage.pixels[tmp.y][tmp.x] = col;
408 }
409 }
410 }
411 }
412 }