CSE 457

Introduction to Computer Graphics
1998 Winter Quarter



Project 1 help session

This page is here to help you become familiar with the SGI Indy workstations as quickly as possible. This page is also designed to assist you in completing project 1, Impressionist. The following are quick tutorials for using subsets of OpenGL, libui, CaseVision, as well as to help you understand the basics of image processing -- enough to get you through project 1 -- and, for those of you who have not created your own home page, there is a tutorial for creating a very basic one as well.

Click here to download the lecture slides from the help session.




Event driven programming with libui tutorial

Event driving programming is a programming style that is most commonly used when interfacing to a graphical user interfaces (GUI) such as Windows95/NT or X. Although it is a very common programming style, it isn't given significant attention here, so this may be the first time that many of you have seen it. Although event driven programming may seem intimidating at first, it is not that bad once you get used to it. You just have to think in a different way. In order to make your life easier (trust me on this one...), we have come up with a library called libui that abstracts away many of the headaches that come with the territory.

Libui is fully documented on the web. Please see the documentation for any specific questions about libui syntax. Please see the lecture slides if you want a general overview of event driven programming and of how libui takes advantage of the event driven programming model. If all else fails, send us mail at cse457staff@cs.washington.edu




OpenGL tutorial

OpenGL - 2D/3D Graphics Library developed at Silicon Graphics Inc. OpenGL has gained widespread industry acceptance in the high-end 3D markets, as well as in the low-end consumer markets. OpenGL's main thrust is as a 3D programming interface. Although it is targeted to the 3D market, it is more than adequate for use in 2D. OpenGL provides fine grained control over images as well as 3D objects. If you plan on continuing on in the computer graphics field after 457, knowing OpenGL is a must.

Basic features:

Other features:

Graphics Primitives
We will use OpenGL in the Impressionist program to draw the various brush strokes. We will mainly stick to drawing lines and points. If you choose to do so, you can make fancier brushes using some of the other primitives (such as triangles, squares, and general polygons) for extra bells and whistles. Here's a brief introduction (you'll find more in the man pages and reference manuals)

Example Code -- primitives: The following bits of code show you how draw single-pixel dots, a triangle outline (see OpenGL manual for other ways to do this), and a filled triangle (as a three-sided polygon).


  uiSetCurrentWindow( drawWind, 0 ); // libui call
  glColor3f( red, green, blue );    

  // drawing a single-pixel dot to the 
  // window, at pixel coordinate (x,y)
  glBegin( GL_POINTS );
  glVertex2i( x, y );
  glEnd();

  // drawing an outlined triangle (many ways to 
  // do this) having vertices A, B, and C
  glBegin( GL_LINE_STRIP );
  glVertex2i( Ax, Ay );
  glVertex2i( Bx, By );
  glVertex2i( Cx, Cy );
  glVertex2i( Ax, Ay );
  glEnd();

  // drawing a filled triangle having 
  // vertices A, B, and C
  glBegin( GL_POLYGON );
  glVertex2i( Ax, Ay );
  glVertex2i( Bx, By );
  glVertex2i( Cx, Cy );
  glEnd();

  glFlush();   // don't forget this!

Drawing images and other data You could draw images one pixel at a time -- setting the color each time, telling OpenGL to explicitly draw each pixel at each coordinate -- but this would be very inefficient and doesn't always work anyway. That's why OpenGL provides the following routines (and others).

The main routines:

Other useful ones:

Example Code: raster routines

  // this routine will read the data from one window,
  // and draw it on another

  // allocates a chunk of memory to store enough data for 3 channels (r,g,b)
  // of data for width*height pixels
  unsigned char data[DRAW_HEIGHT][DRAW_WIDTH][3]

  // read the data on one window
  uiSetCurrentWindow( drawWind, uiNormal );     // libui call
  glReadBuffer( GL_FRONT );
  glReadPixels( 0, 0, DRAW_WIDTH, DRAW_HEIGHT, GL_RGB, GL_UNSIGNED_BYTE,
                &data[0][0][0] );
  
  // now send it back out
  uiSetCurrentWindow( otherWind, uiNormal );    // libui call
  // set the right buffer and the right position, just in case
  glDrawBuffer( GL_FRONT );
  glRasterPos2i( 0, 0 );
  glDrawPixels( DRAW_WIDTH, DRAW_HEIGHT, GL_RGB, GL_UNSIGNED_BYTE,
                &data[0][0][0] );

Notice that this example uses data stored as three bytes representing RGB (24-bit) color. If you want to use indexed color, you will want to see the documentation for glReadPixels and glDrawPixels for how to handle this.

Handling exposure events two schools:




Basic OpenGL Transformations

In the Impressionist project, you will have to control various aspects of the brush strokes, such as their position and orientation. Since you will be using OpenGL to complete this project, you will find it very beneficial to know a little bit about how "transformations" (shifting, rotation, scaling, etc,) are done in OpenGL.

Recall that OpenGL is basically a state machine. For many aspects of it, you setup certain parameters, and until you change them, GL will use those parameters for everything it draws.

You have probably already seen how this is used for things like object color (via glColor) and drawing mode (via glBegin / glEnd). However, there are also state variables for things like position (accomplished via "translation", or shifting) and direction (accomplished via rotation).

So how does this apply to Impressionist? Recall that you'll be drawing various brush strokes on a digital canvas, each at a different position and in a different direction. By setting the GL state variables for position and orientation before drawing your brush, you can use the same code regardless of the brush's position or orientation. The stroke will automatically be drawn at the correct position and in the correct direction!

Here are the OpenGL calls needed to do some simple image transformations.

Pushing/Popping Matrices

Image transformations are accomplished using matrices. A series of matrices are multiplied to produce a given image transformation. Without going into too much detail, let's just say that you'll want to save your current transformation matrix ("push" it onto a matrix stack) before doing your brush-specific translation/rotation, and restore the original matrix ("pop" it off the matrix stack) when you're done with that brush stroke.

Here are the calls you need to use.

glPushMatrix();

<<Do the translation, rotation>>
<<Draw the brush stroke>>

glPopMatrix();

Translation

To draw at a certain point, you'll want to "translate" your origin to that position. Here is the call you use in OpenGL to do 2D translation:
glTranslate*( startX, startY, 0.0 );
(Note: the * is replaced by a letter that depends on the type of the parameters you pass.)

Rotation

You can also take advantage of OpenGL to automatically rotate the things you draw. Here is the OpenGL call you'll want to use:
glRotate*( angle, 0.0, 0.0, 1.0 );
(Note: we use 0.0, 0.0, 1.0 because we want to rotate around the z-axis.)


Impressionist -- Important Project Files

To help you wade through all of the files we give you for Impressionist, here is a brief list of the more important files and what they're used for: You will most likely have to modify all of them.


Image Processing

To implement some of the requirements of this project, you will need to know a little about image processing. More specifically, this document will cover some of the aspects of edge detection and image gradients.

Some Basics

Often in image processing, we apply an operation to just a small region of an image at a time. One of the most common techniques used for applying such operations is by using something called a filter. Diffenent image filters are distinguished by their kernel and their support. Basically, you can think of a filter kernel as a grid of values that we overlay on top of the pixels of an image. Here is an example of a kernel:

0

1

1

1

0

1

1

2

1

1

1

2

3

2

1

1

1

2

1

1

0

1

1

1

0

We apply an operation to a pixel in the image as follows. First, imagine placing the center of the kernel on top of the pixel of interest. Multiply the value at each position in the kernel by the color of the pixel underneath that position. Then, add up all these products, and divide by the sum of the values in the kernel (if it's nonzero).

We can do many interesting things by changing the values in the kernel. The rest of this section will explain how kernels and other image manipulation methods are used to find edges and gradients in an image.

Image Blurring

The first step in finding edges and gradients in an image is to blur the image. (Note: for finding edges and image gradients, we use a grayscale version of the original image.) Blurring reduces the "noise" (randomness) in the picture, and allows us to find edges and determine image gradients more accurately.

There are several choices for the filter kernel to use for blurring image. We will present three of them here.

In general, which filter you should use depends on the image you're manipulating and what you want to accomplish. The box filter is the easiest to implement, but the Bartlett and Gaussian filters often produce "better" blurring results.

Edge Detection

Once you have blurred the grayscale version of the original image, you are ready to do edge detection. The method of edge detection we present here is known as Sobel edge detection.

To do Sobel edge detection, we once again use filters. This time we make two passes over the image, using a different kernel each time. Here are the filter kernels that are used.

1

2

1

0

0

0

-1

-2

-1

1

0

-1

2

0

-2

1

0

-1

By using both these filter kernels on each pixel in the image, we get two new values at each pixel location (call them temp1 and temp2). We then produce a single value at each location using this formula: sqrt( temp1*temp1 + temp2*temp2 ). (You may notice a striking similarity to the distance formula used in mathematics.) Finally, we say that if this single value is above a certain threshold (usually specified by the user), then there is an edge at that location.

Image Gradients

Given a blurred image, we can also produce the gradients for an image. To intuitively understand what the image gradient is, recall what a gradient in is in mathematics --- the direction of maximum rate of change. For a surface (such as an array of pixels), the gradient is the direction in which the color changes the fastest (and perpendicular to the gradient, the color changes the slowest --- i.e., it's close to the same color).

Why is this important to us (and important for the Impressionist program, in particular)? Well, if we know (at each pixel) the direction in which the color is changing the least, we can orient our brush strokes in that direction to make it more realistic. (Because if we paint a stroke in a certain direction, the color of the paint stays the same during that stroke!)

So how do we implement this? If you answer contained the word "filters," you're on the right track. We will actually produce two gradients, the x-gradient (telling how fast the color changes along each row), and the y-gradient (telling how fast the color changes along each column). Here are the kernels that are used.

x-gradient filter kernel:

-1

1

y-gradient filter kernel:

1

-1

Using these kernels gives us two separate images, one of which tells us how fast the color changes (at each pixel) in the x-direction, and one telling us how fast it changes in the y-direction. Finally, we use this information to determine the direction of the maximum rate of change in color, and we orient our brush strokes perpendicular to that direction.




Debuggers on the Instructional SGIs

While printf is your friend as well as mine when it comes time to debug things, you can save a lot of time by investing a small amount of time to learn how to use a debugger.

We have a choice of debuggers on the SGIs in 228. We have a graphical debugger called CaseVision, as well as the standard text based dbx and gdb debuggers. I personally prefer dbx or gdb because there is not as much overhead associated in using them and you are pretty much guaranteed that they exist everywhere. Of course a text based, low overhead program is much more difficult to use, so if it is simplicity you're going after, CaseVision is probably the choice for you.



CaseVision tutorial

CaseVision is a very handy graphical debugging tool that will help you greatly. Unfortunately (and quite ironically), it has a few bugs. These are namely in how it handles killing processes, so be sure to check for stray ones. Also, there are a few problems in debugging C++ code, so beware because we will be using C++ all quarter. dbx on the SGIs handles C++ well. I've never really used gdb, but I'm sure it handles C++ as well.

dbx

To use dbx, you will also have to compile with the -g option. Once you have a core, type in:
prompt% dbx (executable_name) core
At the dbx prompt, you can type in t (which stands for trace) to get a stack trace of the programs execution state when it core dumped.

You can also type in w (which stands for where) to see exactly at what line of code the core dump occurred.

To navigate up and down through the stack frames, you can use the up and down commands.

To print out variables, use p variableName.

There are many more commands that dbx supports. Type in help at dbx's command line for a complete listing. You can also check out the dbx man page.


gdb

I've never really used gdb, so I'm not much help on how to use it. It's commands are similar to dbx. As a matter of fact, you load a core file in the same way that you do so in dbx.

To get a complete listing of the commands that gdb supports, type help at gdb's command line. You can also check out the gdb man page.




Home page tutorial

  1. Take a picture of yourself

  2. Make a www sub-directory in your home directory on sanjuan or orcas, and move your .gif image into the new directory.

    You can reach your home directory (on sanjuan/orcas) from the SGI's with:

    /homes/iws/yourname

    You cannot, however, reach the SGI directories from sanjuan or orcas.

  3. For starters look at one of the TA's or a friends home page (or click here for a dummy page), and save the HTML source for that page by choosing the File/Save As menu option and then selecting the HTML format. Load the file into a text editor and modify it to your liking, and save it as index.html in your www directory. There will be a line of HTML text like
    < img src="tas_name.gif">

    that you should change to
    < img src="your_name.gif">

  4. Access

  5. Browse the web to get ideas for your home page, and when you find one you like, select the File/View Source menu option to see the HTML source code will appear in the window. This is a great way to learn the features of HTML -- well, the way I learned.
  6. For an official HTML primer, click here

Send questions or comments to Mark Manca (mark@cs.washington.edu)