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}