CPS 100E, Fall 1996 Lab 4

Bit-mapped Graphics and Matrices

(You may find it easier to read this lab using Netscape or another browser, the URL is http://www.cs.duke.edu/~ola/courses/cps100e/lab/lab4.html)

The goals of this lab include doing the specific tasks outlined below and understanding general concepts behind the tasks.


In this lab, you'll investigate some very basic techniques used in image processing. You will be enhnacing a program that manipulates bitmap pictures. Some of this material is described in Sections 9.2-9.4 and 11.5 of Astrachan/Tapestry. The lab uses matrices to store bitmaps (pictures).

Lab 4 table of contents

[ Bit Maps | The Program | Implementations of Member Functions | Modification to Read | Submit | Extra Credit ]

For this lab you'll first write the member function to read image files from disk, then you'll write some function to manipulate images. Finally, you'll modify the Pixmap class to make it more memory efficient.

Bit Map Background

Many different forms of graphical images exist. Common forms include gif , tiff , and X-window dumps . In this assignment a form of image called a bitmap will be used. These bitmaps can be black-and-white, gray-scale, and color. This assignment uses only black-and-white and gray-scale images.

Pixels

An image can be represented as a 2-dimensional grid of pixels, each of which can be off (white) or on (black). Color and gray-scale images can be represented using multi-valued pixels. For example, numbers from 0 to 255 can represent different shades of gray. A bitmap is a 2-D grid of 0's and 1's, where a 0 corresponds to an off pixel and a 1 corresponds to an on pixel. For example, the following is a bitmap which represents a 9x8 picture of a `<' sign. The bitmap of 0's and 1's is shown on the left, the image the usepix will display is on the right.

0 0 0 0 0 1 1 0
0 0 0 0 1 1 0 0
0 0 0 1 1 0 0 0
0 0 1 1 0 0 0 0
0 1 1 0 0 0 0 0
0 0 1 1 0 0 0 0
0 0 0 1 1 0 0 0
0 0 0 0 1 1 0 0
0 0 0 0 0 1 1 0

Representing images as bitmaps is very natural, and some reasonably nice tools for drawing and displaying bitmaps can be found in the UNIX environment. In this lab we will use xv to display our images. Although representing such images using the '0' and '1' characters (or the numbers 0 and 1) may be wasteful of space (a character is 8-bits and as a '0' or '1' represents only 1-bit of information), it is conceptually simple and methods for compressing such images exist.

back to lab contents


Compiling/Running Usepix

In this section of the lab you'll copy files, compile a basic image processing program, and manipulate a "random" image.

First change into your cps100e subdirectory (type pwd to verify where you are). Create a lab4 subdirectory by typing mkdir lab4 and change into this subdirectory (be sure to check that you're in the lab4 subdirectory.) Now copy the files for the lab (don't forget the . when copying).

cp ~ola/cps100e/lab4/* .

You should see the files listed below (these are links to the files in case you use Netscape, and for users outside of Duke).

Be sure you're in the lab4 subdirectory, and check to see that all files are there (type ls). Then, from an xterm window (at the prompt [1] ola@teer8% or similar) compile the first version of the program by typing: make usepix. This should compile several files and link them together with the library libtapestry.a. Now run the program by typing: usepix at the prompt. When you run the program, you can "load" a file, but the read routine is not complete. Use the 'l' (for load) option then type the number of an image. To display an image choose the 'd' option. Your program will display a random image that is the same size as the image in the file because the Read member functions are not implemented.

back to lab contents


Implementations of Pixmap Functions:

In this part of the lab you'll implement changes to The Pixmap class (in pixmap.cc). You will implement member functions ReadNormalFile, ReadCompressedFile, Invert, and HorizReflect functions.

  • Run emacs, either by clicking on the emacs icon in the menubar (if there is one) or typing emacs & at a prompt.

  • Load (use the file menu or C-x C-f) the file pixmap.cc.

Reading Images

Images are stored in a certain format as follows:
 image type (C1, P1, P2)
 # comment string
 cols rows
 optional gray levels
 pixel value pixel value ...
 pixel value pixel value ...
The pixels are stored row by row (this is called row-major order). For example, the < sign shown above is stored in a file as shown below (you can read this file in when you run the program!)

  P1                                       
  # CREATOR: XV Version 3.00 11/29/94
    8 9                                    
    0 0 0 0 0 1 1 0                        
    0 0 0 0 1 1 0 0                        
    0 0 0 1 1 0 0 0
    0 0 1 1 0 0 0 0
    0 1 1 0 0 0 0 0
    0 0 1 1 0 0 0 0
    0 0 0 1 1 0 0 0
    0 0 0 0 1 1 0 0
    0 0 0 0 0 1 1 0
You need to write the function to read the pixel values. First you'll write ReadNormalFile.
  • Remove the call to the function RandomImage.

  • Write nested for loops to read every value in the file and store the value in the matrix myImage (see pixmap.h for the declaration of myImage.) The outer loop visits every row (loop to myImage.Rows()) and the inner loop visits every column (loop to myImage.Cols()).

    The body of the inner loop should read a value from the file (use >>, the extraction operator) and store the value in myImage

  • After adding the loop, recompile (type make usepix from within emacs or from the shell prompt.) Then run the program, read an image ending with .pgm or .pbm and choose display. Hopefully this will work and you can go on to the next step. Otherwise reason about your code and/or ask a UTA/TA for help.

When you can read and display .pgm and .pbm files you should try the Vertical Reflect option and re-display. You'll notice a change. You'll next implement Invert and Horizontal Reflect.

Inverting Images

Implement the Invert() member function. This function changes all black pixels to white and all white pixels to black (0 becomes 1 and 1 becomes 0.) For gray-scale images this is also true, but light-gray images become dark-gray and vice versa. To do this you'll change each pixel using the statement below.

myImage[j][k] = myGrayLevel - myImage[j][k];

Include this statement in the body of two nested loops that run j and k appropriately through all rows and columns. Check you invert function by reading in an image (try one of the owen or idiot images), inverting it, and displaying it.

Reflecting Images

Fill in the code for the HorizReflect member function. This function is supposed to reflect an image in a horizontal line drawn across the middle of the image, i.e., the image is flipped upside down.

You should look at the VertReflect member function which reflects left-to-right (through a vertical line). for help in how to reflect a pixmap. Note that in VertReflect the left-half columns are swapped with the right-half columns inside a loop that visits every row.

However, you will want to reflect the pixmap horizontally rather than vertically.

When you're done, check your function by reflecting and displaying an image.

Reading Compressed Images

This is extra credit, you don't have to do this part. Reading compressed images is worth 2 extra lab points.

For this part of the lab you'll add code to read compressed images. Only black and white images are compressed. Images are compressed by storing a count of consecutive 0's and 1's rather than the 0's and 1's themselves. For example, rather than storing

 0 0 0 0 0  1 1 1 1 0 0 1 1 1 1 1 1 
we can store the counts of consecutive 0's and 1's:
  5 4 2 6

As a further example, the < image above is compressed as follows (note that the # of rows and columns is the same as in the uncompressed image.)

C1                     
# CREATOR: CPS06 Pixmap 9/25/95
8 9                    
5 2 5 2 5 2 5 2 5 2    
7 2 7 2 7 2 7 2 1      

To read a compressed image you'll fill in the code for the member function ReadCompressedFile. You need to read each number (e.g., 5 2 5 2 ...) in the < image above and store the right number of 0's and 1's based on these numbers. Rather than using nested for loops you should use the loop pattern below. This loop reads each number, then uses a for loop to set the correct numbers of 0's and 1's. By convention the first number in a file is always some number of consecutive zeros (there might be zero 0's).

int num; // read from file int row = 0; // current row int col = 0; // current col int fillnum = 0; // this is always 0 or 1, start at 0 while (input >> num) // read each entry in compressed file { int k; for (k=0; k < num; k++) // fill in run entrys of myImage { ____________ = fillnum; // fill in myImage[row][col] //Update the indices row and col as necessary. } fillnum = 1 - fillnum; //Toggle value of fillnum 0->1 and 1->0 } To update row and col you'll need to increment col (this moves across one row). When col is too large you'll change rows and reset col to 0. You'll know when this part works because you'll be able to display the "mystery" files you can load.

back to lab contents


Modifications to Pixmap Class:

In the current implementation of the Pixmap class, the Matrix myImage is resized every time a new image is read in. Sometimes this wastes memory since there is no need to resize the Matrix if it is already large enough to store the image being read. We will modify the class to avoid resizing always by keeping track separately of the capacity of the Matrix myImage and the size of the image stored in the Matrix.

Load (use the file menu or C-x C-f) the file pixmap.h.

  • Add the variables myRows and myCols to the private section of the Pixmap class. These are int private variables and will be set to the # rows and # cols of the image actually stored in myImage. The Matrix myImage keeps track of its own capacity.

  • Change the emacs active buffer to be pixmap.cc, either use the menu or type C-x b and type the buffer you want to switch to (pixmap.cc) or use the default if it's ok. In the constructor Pixmap::Pixmap(), initialize myRows and myCols to 0.

  • In the constructor Pixmap::Pixmap(int rows, int cols), initialize myRows to rows and myCols to cols.

  • In the function Read, add code to check to see if you need to resize the Matrix, this happens when myCols or myRows is larger than the capacity of myImage: input >> myCols >> myRows; // rows and cols if (myRows > myImage.Rows() || myCols > myImage.Cols()) { myImage.SetSize(myRows, myCols); }

  • In the emacs session. Go to the first line of the file, you can do this with M-< (that's Escape <) or you can type C-x g 1 (control-x g is goto-line, if that doesn't work you may need to type M-x goto-line.) You'll change some text, but you'll control the changes

    Type M-x query-replace, that's Escape-x, followed (in the minibuffer) by "query-replace". You can hit the TAB key after "repl" and the command will be completed (partially). If you hit TAB again, the choices will appear and you can type "st" TAB to complete the command, then hit return.

  • At the prompt Replace string: type "myImage.Rows()" and press return.

  • At the prompt Replace string myImage.Rows() with: type "myRows" and press return. Emacs will then ask you for each string myImage.Rows() in the file if you want to replace it with myRows. Hit 'y' for every value except the if statement you just wrote, hit 'n' if you don't want to replace the string. You'll need to make the same changes with myImage.Cols() and myCols.

You probably won't notice any change in the performance of your program, but you should be sure to recompile and re-run it.

back to lab contents


Submitting The Lab

To submit assignments you'll run the command below, but substitute your section number (1, 2, or 3) for N.
    submit100e lab4.N README pixmap.cc pixmap.h

You can enter the files in any order (you don't need to submit usepix.cc because it didn't change, (but you can submit it.)

Remember that every assignment must have a README file submitted with it (please use all capital letters). Include your name, the date, and an estimate of how long you worked on the assignment in the README file. You must also include a list of names of all those people with whom you collaborated on the assignment.

You also must turn in the inlab questions either by turning in the sheet during lab or by submitting the answers with your README.

back to lab contents


Extra Credit

(This extra credit is worth 5 points, counted towards either labs or quizzes). For extra credit, you will implement the Expand member function that makes an image bigger. This is tricky, the process is diagrammed below where the bottom-right pixel is enlarged by a factor of 3 X 2 (row X col).

*

First the Matrix myImage is resized, then starting in the lower-right corner, and working back to the upper-left corner, each pixel is expanded by rowExp X colExp. This most likely will involve a quadruply nested loops. The outer loops visit every pixel in the original image. The inner two loops copy the pixel into an enlarged area of the Matrix. It's possible to do this with two loops, but that's tricky too.

    submit100e lab4.N.xtra README pixmap.cc pixmap.h