Computer Graphics Lab 2: Lines and Ellipses

Ben Mitchell and Zach Pezzementi

Lab Description

The task in this lab was to design an API for interacting with images, and implement some basic functions for plotting points, and drawing lines, circles, and ellipses.

Lines

We implemented a version of Bresenham's line drawing algorithm. It uses entirely integer math, and does some clever bit shifting to avoid having to multiply. It's not terribly optomized, in that it doesn't start from both ends or anything, but our plot function (which it calls to actually write a pixel to an image) is an inline function, which makes it pretty speedy. Using Bruce's benchmarking tool (modified somewhat to work with our API), we were averaging something like 550,000 lines per second on an Intel P4 running at 2.6 GHz when compiled with gcc 2.95.4, the Debian Stable build.

We wrote two versions of drawLine, one of which uses pixel centers as coordinate axis intersections, and the other of which uses the upper left (in screen space) corner of pixels. The latter is better suited to drawing polygons, because it allows us to have polygons that share verticies without having them overlap. It also gives polygons the correct geometric area with respect to their mathamatical side length. The pixel-centered version is useful to have around, however, for drawing lines that you want to include two known pixels as endpoints.

Circles

The circle algorithm we used was also concieved by Bresenham. We chose to draw in the upper half of the lower right quadrant of the circle, since this gives the circle the correct area, and then transforming the x and y coordinates so that the entire circle was drawn (taking advantage of the eight-way symmetry). By drawing lines between points horizontally accross the circle, we were able to easilly fill the circles in. When using alphp-blending to shade filled circles, it was neccessary to make sure we didn't draw the same lines twice, as this would make the color of those lines more intense.

Ellipses

The axis alligned ellipses were drawn in a fashion very similar to the circles, and were filled the same way. We used the code from Bruce's graphics web page (originally taken from Hearn and Baker) for this algorithm.

The arbitrarilly oriented ellipses were more complex, and we used a 2002 paper by C. Bond called "A New Algorithm for Scan Conversion of a General Ellipse" as a reference. We would like to note that it has a typo; the equation for 'd' on page 6 should read "2C..." rather than just "C...". It took us a very long time to figure out what was wrong, since we had derived all the other equations ourselves, but not the initial one. With this correction made, and having done all the math correctly, this algorithm works very nicely. The state variable 'd' must be a floating point number, but everything else can be safely left as an integer with no ill effects, and the algorithm is incremental, involving only additions and multiplications at each step to get the next value of 'd'. No trigonometric functions, and no square roots fall inside the update loop, though several are needed in the initialization. We do not have a filled in version of this function, since these ellipses are only symmetric accross a single axis, which means we cannot simply draw lines between points that are accross from each other. We would need to implement a flood-fill algorithm, and we have not done so yet.

Lab 2 Q&A

Bruce: Who did you work with on this assignment, and what tasks did each of you do?

The Graphics Team (TGT): Well Bruce, [Zach and Ben] both worked on this assignment together; it was really a cooperative effort. Ben got involved pretty early on in working on the oriented ellipses, which took a bunch of time, so Zach did a bunch of the work on the basic lines. Zach also worked on the axis-alligned ellipses, and Ben made the circles. Ben also wrote the code to do alpha-shading, but says, "it was pretty simple; it just didn't take that much time. Mostly it was a question of copying the existing function code, and calling shade() instead of plot()."

Bruce: Describe the API for your graphics environment. This should be a mini-manual on how to use your graphics system.

TGT:

The first thing to be aware of is that we chose to implement our graphics environment in C++, to make use of all of the advantages of classes and object-orientation. For example, this framework allows us to declare an instance of an image on the stack with a single command which does all of the initialization of all relevant state variables as well as memory allocation without having to do it all by hand every time. Not only is this easier on us, but it makes code much easier to read and debug.

The main class that we defined to deal with images is GraphicImage. This class handles allocation and file IO as well as providing an interface for all of our graphics primitives. It currently supports ppm and pgm filetypes, but could easily be expanded to handle other image formats without the need for modification to any user-level code. Thus, old programs could be recompiled with the new library and be able to read and manipulate the new image formats. GraphicImage contains member functions to initialize the image to black, as well as a number of accessors and mutators as follow:

    int intensity(const Point &p);

    /* Basic accessors and mutators.
     * These are inline functions, and appear below.
     */
    Pixel get(const Point &p); // returns the value at a point
    Pixel get(int x, int y); // as above, but takes x, y instead of a point
    
    void plot(int x, int y, GraphicContent *gc); // turns on the specified pixel
    void plot(const Point &p, GraphicContent *gc); 
    void shade(int x, int y, GraphicContent *gc); // like plot, but uses alpha blending
    void shade(const Point &p, GraphicContent *gc); 

    /* Graphics primitives for drawing in images.
     * Code appears in DrawShapes.cpp
     */

    void drawLineC(const Point &p1, const Point &p2, GraphicContent *gc); // pixel-center coordinate system line algorithm
    void shadeDrawLineC(const Point &p1, const Point &p2, GraphicContent *gc); // as above, but does shading
    void drawLine(const Point &p1, const Point &p2, GraphicContent *gc); // pixel-corner coordinate system line algorithm
    void shadeDrawLine(const Point &p1, const Point &p2, GraphicContent *gc); // as above, but does shading
    void drawPolyline(const PointList &p, GraphicContent *gc); // draw lines between each pair of points in the list
    void shadeDrawPolyline(const PointList &p, GraphicContent *gc);
    void drawPolygon(const PointList &p, GraphicContent *gc); // draw lines between each pair of points, connecting the last and the first
    void shadeDrawPolygon(const PointList &p, GraphicContent *gc);
    void fillPolygon(const PointList &p, GraphicContent *gc); // scanline polygon fill algorithm
    void shadeFillPolygon(const PointList &p, GraphicContent *gc);
    void drawRectangle(const Point &p1, const Point &p2, GraphicContent *gc); // takes opposite corners of a rectangle, and draws a box
    void drawCircle(const Point &c, int r, GraphicContent *gc); // draws a Bresenham's circle
    void shadeDrawCircle(const Point &c, int r, GraphicContent *gc);

    void drawEllipse(const Point ¢er, int Rx, int Ry, GraphicContent *gc); // draws an axis-alligned ellipse
    void fillEllipse(const Point &c, int Rx, int Ry, GraphicContent *gc); // fills an axis-alligned ellipse
    void shadeFillEllipse(const Point &c, int Rx, int Ry, GraphicContent *gc);
    void fillCircle(const Point &c, int r, GraphicContent *gc); // fills a circle
    void shadeFillCircle(const Point &c, int r, GraphicContent *gc);

    void drawOrientedEllipse(const Point ¢er, double semimajor, double semiminor, double theta, GraphicContent *gc); // draws an arbitrarily oriented ellipse
    void shadeDrawOrientedEllipse(const Point ¢er, double semimajor, double semiminor, double theta, GraphicContent *gc);

    /* These are general image manipulation functions.
     * Code appears in ManipulateImage.cpp
     */
    void goodResize(int newx, int newy); // not fast, but does a good job by projecting pixel corners from and doing area-based averaging
    void reflectv(void); // reflect vertically
    void reflecth(void); // reflect horizontally
    void rotatecw(void); // rotate clockwise 90 degrees
    void rotateccw(void); // rotate counter-clockwise 90 degrees
    
You may have noticed, Bruce, that all of the above drawing functions take a pointer to something called a "GraphicContent." This is a class that contains the state variables that tell plot what to draw. This includes color, and alpha, which is a float between 0 and 1. Actually, the plot function ignores alpha; it's only used if you call shade instead of plot, which is why we need two prototypes for every function. This class may later get other things, such as background color, texture fill pattern, brush width or type, or other attributes. So far, all the fields of this class are public, and can be set by user-space programs, though it is possible that this will change in the future, and we will add accessors and mutators to deal with things which are more complicated.

Bruce: Is your first required image consistent with how you would match screen coordinates to the true mathematical lines? Why, why not?

TGT: Yes, the screen coordinates match up as long as you can remember how to decipher the mathematical coordinate system with the y-axis oriented downwards and the origin in the upper left corner of the upper left pixel. The fact that this corresponds to the "lower-left" corner in terms of value of y and in terms of the normal coordinate system can be confusing. Since we generated the first required image using this corner-oriented coordinate system, rather than a pixel-center-oriented system, the lines appear to be of the correct length and orientation, though there is of course some aliasing.

Bruce: If you extended this assignment in any way, describe what you did and how you did it. Include pictures to support your description.

TGT:

Well Bruce, both our circles and our ellipses can be filled. Additionally, lists of points can be passed to drawPolyline and drawPolygon, and will be drawn in the specified order (drawPolygon connects the last to the first). Also, all drawing primitives (both draw and fill) have a shading version, which does alpha blending based on a constant that is part of the GraphicContent, which also contains the pen color, among other things. These cause the plotted color to be mixed with whatever it is being drawn over to a degree weighted by the constant alpha (range 0 - 1). We also implemented arbitrarily oriented ellipses using an incremental, mostly integer algorithm (see above).

Also, we drew some cool pictures. Which I guess is not technically an extension, but there was nowhere else they really fit in, so they're here at the end.
Here they are. Enterprise by Ben, Borg Cube by Zach. (Note: they're scaled down so they both fit next to each other. Right click on one and say "view image" if you want to see each one full scale.)