views:

230

answers:

5

I am working with a Microchip dsPIC33FJ128GP802. It's a small DSP-based microcontroller, and it doesn't have much power (40 million instructions per second). I'm looking for a way to render a convex (i.e. simple) polygon. I am only dealing with 2D shapes, integer math, and set or clear pixels (i.e. 1 bit per pixel.) I already have routines for drawing fast horizontal and vertical lines (writing up to 16 pixels in 88 cycles), so I would like to use a scanline algorithm.

However, all the algorithms I have found seem to depend on division (which takes 18 cycles on this processor) and floating point math (which is emulated in software and so is very slow; it also takes up a lot of ROM), or assume that I have a large amount of memory. I only have 2K left, ~14K is used for graphics RAM of my 16K. So does anyone know of any good, embedded machine algorithms they can point me to with a simple C or pseudocode implementation which I can implement in assembly? Preferably on the 'net, I don't live near any good bookstores with many programming books.

Thanks. :)

EDIT: Clarification, this is a polygon filling algorithm I'm looking for. I can implement a polygon outline algorithm using Bresenham's line drawing algorithm (as Marc B suggests.)

EDIT #2: I wanted to let everyone know I got a basic algorithm up in Python. Here's a link to the code. Public domain code.

http://dl.dropbox.com/u/1134084/bresenham_demos.py

+6  A: 

How about Bresenham's Line algorithm? After some setup, it's pure integer math, and can be adapted to draw a polygon by simple iteration of starting points along the polygon edges.

comments followup:

I'll try to draw this in ASCII, but it'll probably look like crud. Bresenham's can be used to draw a filled polygon by picking a starting edge, and iteratively moving a bresenham line across the canvas parallel to that point.

Let's say you've got some points like this:

*(1)
                      *(3)

    *(2)         
                 *(4)

These are numbered in left-right sort priority, so you pick the left-most starting point (1) and decide if you want to go vertically (start 1,2) or horizontally (1,3). That'd probably depend on how your DSP does its display, but let's go with vertical.

So... You use the 1-2 line as your starting bresenham line. You calculate the starting points of your fill lines by using lines 1-3 and 2-4 as your start/end points. Start a bresenham calculation for each, and draw another Bresenham between those two points. Kinda like:

1.1 -> 2.1, then 1.2 -> 2.2, then 1.3 -> 2.3

etc... until you reach the end of either of those lines. In this case, that'd be when the lower starting point reaches (4). At that point, you start iterating up the 4,3 line, until you reach point 3 with both starting points, and you're done.

*-------
 \\\\\\\\              *
  \\\\\\\\ 
   *-----\\         
         -------  *

Where the dashes are the starting points you calculated along 1-3 and 2-4, and the slashes are the fill lines.

Of course, this only works if the points are properly sorted, and you've got a convex polygon. If it's concave, you'll have to be very careful to not let your fill lines cross over the border, or do some pre-processing and subdivide the original poly into two or more convex ones.

Marc B
Oops, I guess I wasn't clear. I want a polygon filling algorithm. I'll edit the post to reflect that.
Thomas O
Marc still has a great answer. To adapt his answer, simply do this: choose a left-most point and one of its neighbors. If the two points are vertically connected, draw that line and replace the chosen neighbor with the next neighbor-over. Continue eliminating vertically adjacent neighbors in this way. Now do the same to find a non-vertically adjacent 2nd neighbor in the opposite "direction" around the polygon. You now have the parameters for two lines which you will *simultaneously render* using Bresenham's. Your coordinate iterator (the `for` loop on *x*) will draw one vertical line...
Heath Hunnicutt
...at each *x* ordinate. You will only iterate to the lesser of the two neighbors' *x* ordinate. Once you have "reached" that *x* ordinate, you must replace the Bresenham parameters that stemmed from that neighbor -- use the 'next' neighbor going around the poly away from the initial, left-most (i.e., *x*-least), point. Keep swapping out edge parameters that have been completed with the params for the 'next' neighbor' until you either reach vertically adjacent neighbors (i.e., vertical right edge of poly) or identical neighbors (having apparently gone through all vertices).
Heath Hunnicutt
Thanks for that long comment (I think you should have posted it as an answer.) It sounds like a good idea and I'll have to think carefully before implementing it. Unfortunately, Bresenham's algorithm isn't particularly fast compared to drawing straight lines. In my program, each pixel requires address decode, reading, masking and writing per pixel, which adds up (~30 cycles to set a pixel.) Which means drawing a 100 pixel line takes 3,000 cycles plus overhead. My fast pixel writing algorithms depend on pixels being in the same or adjacent words which is why I want to use scanlines if possible
Thomas O
@Thomas - I want Marc to get the accepted answer. I would feel like a thief stealing his suggestion to use Bresenham's. In my long comment, you would never render anything other than a vertical line. You calculate *y1* and *y2* for the edges using Bresenham, but you are calculating for the same *x* -- a vertical line. This may lead to a need for to special-case neighbors with verticals, so I threw that in also. But the key point is you Bresenham along the edges (which you must already somehow iterate along) and only ever render by vertical raster. You can easily convert this to...
Heath Hunnicutt
...render by horizontal raster, just replace "vertic" with "horiz" and every "x" and "y" transmute to the other. You will always have to iterate over the parametric equation of the polygon edge segments -- otherwise how could you possibly calculate the extents of the 2d set? Given that you *must* iterate along the parametric edges, using Bresenham to do so is your route to integer speed. It's the canonical good answer, and I suspect my huge comment is what Marc B had in mind to begin with as he wrote "iteration of starting points along the polygon edges."
Heath Hunnicutt
Thanks again for clearing that up. I'm going to be doing some reading about this and hopefully I'll get a basic version working soon. And to clarify, the project I am working on is a software based PAL/NTSC video overlay. 256x192 pixels are orientated horizontally and each word is stored little endian, there are two framebuffers of 6KB each. This project will likely be an open source hardware/software project too. It would cost me ~£259 to get a graphical OSD, I have built one for about £10 in parts and several weeks in coding.
Thomas O
Can I just sneak in here and say this is an epic, enjoyable, and insightful thread? This is why I love SO! :-)
glowcoder
@Thomas -- It sounds like you've got a product. Want a US distributor? :)
Heath Hunnicutt
Hah, I'm not in it for the money... just the thrills. :p Not that I wouldn't leave my options open.
Thomas O
+3  A: 

Thomas, if you have a Bresenham line drawing algorithm available, then use it as a basis for further enhancement: divide your polygon to sub-polygons with an horizontal cutting line through every vertex. Then, start tracing the 2 left and right sides of each of these sub-polys, using Bresenham. This way you have the 2 end-points of each scan line in your polygon.

ysap
+1  A: 

It may be easier to break the problem into two parts. First, locate/write an algorithm that draws and fills a triangle. Second, write an algorithm that breaks up an arbitrary polygon into triangles (using different combinations of the vertices).

To draw/fill a triangle, use Bresenham's Line Algorithm to simultaneously draw a line between points 0 and 1, and between 1 and 2. For each input point x, draw the pixel if it is equal to or in between the y points generated by the two lines. When you reach one endpoint, continue by using the unfinished side and the side that has not yet been used.

Edit: To break your convex polygon into triangles, arrange the points in order and call them P1, P2, ... PN. Let P1 be your "root" point, and build triangles using that point and combinations of adjacent points. For example, a pentagon would yield the three triangles P1-P2-P3, P1-P3-P4, and P1-P4-P5. In general, a convex polygon with N sides will decompose into N-2 triangles.

bta
The OP says he wants to render *convex* polygons; triangulation is easy in this case.
lhf
@lhf- A convex polygon can be deconstructed into triangles. If you have an optimized triangle-drawing routine and you can break your shape into triangles, then you can draw the shape.
bta
+2  A: 

You may want to look at Michael Abrash's articles on Dr Dobbs about polygon fill/raster/etc. It uses fixed-point math

Jaime
@Jaime - upvote just for remembering this article...
ysap
+1  A: 

I would start by converting the polygon to a collection of triangles and render those, because triangles are easy to render by scanlines. Although even so there are some details.

Essentially, the draw-triangle sub-procedure will be given a raw triangle and proceed:

  1. Reject degenerate triangles (where two of the three vertices overlap).
  2. Sort the vertices in Y (since there are only three you can hardcode the sorting logic).
  3. Now, at this point you should know that there will be three kinds of triangles: ones with a flat top, ones with a flat bottom, and "general" triangles. You want to handle a general triangle by essentially splitting it into one each of the flat types. This is because you don't want to have an if test every scanline to detect if the slope changed.
  4. To render a flat triangle, you would run two Bresenham algorithms in parallel to iterate the pixels comprising the edges, and use the points they give you as the endpoints of each horizontal scanline.
Cirno de Bergerac