CSc 220 Assignment 4

What Does Random Look Like?

Assigned
Due

Mar 4
80 pts
Mar 25

Modified 3/5 to add a needed #include directive to circles.cpp, update the Makefile to work better with JGrasp, and add some additional advice at the bottom of this page.

This project involves writing a small piece to complete a larger program. There's a good bit of code here, but you don't have to write very much. You are given several files, including two example mains, and are to supply some missing classes to complete the larger programs. These will then need to be compiled from multiple files, the ones given and the ones you write. The grad program creates randomly-generated images that might be suitable for desktop backgrounds and such. Here is one:

When the program is run, it needs the name of the file to write, and the numbers of rows and columns (height and width) in pixels. You can specify these on the command line, or it will ask for these if they are missing. The above picture was created by running grad example1.jpg 100 400.

Grad produces a standard jpg file which you can view in a browser or most any sort of image software.

Given Files

The files jpge.h and jpge.cpp provide the class jpeg_writer which can write a stream of pixels to a file to form an image. It is based on this code, with a simplified interface. Look at class jpeg_writer to see all of the several methods, but the most relevant ones are:

jpeg_writer(filname, width, height)
This constructs the writer object to write to a particular file name, with a particular width and height.
w.next_pixel(r, g, b)
Where w is a jpeg_writer object, and r, g and b, are type unsigned char, simply adds the next pixel to the image. The pixels are added from left to right, top to bottom.
w.close()
Close the image writing. Call this after sending the last pixel.

Another is grad.cpp which is the program mentioned above that generates a random gradient image (after you supply a couple of needed classes). It needs to know a file name, number of rows, and number of columns, which can be given on the command line, or the program will ask for them if not.

bennet$ ./grad File name to write: joe.jpg Number of rows: 350 Number of columns: 600 bennet$./grad fred.jpg 400 500
Here I've run the program twice, the first one writes a file joe.jpg which is 350 pixels high and 600 wide, and the second a file named fred.jpg which is 400 tall and 500 wide.

Finally, the program circles.cpp generates a picture with random circles. Runing it is similar, except it also asks how many circles to draw. These are all given random colors, sizes, and positions, and written on a randomly-colored background.

Needed Classes

You must create two classes, one to represent pixels, and one to represent the image. The pixel class simply represents a color as red, green and blue magnitudes, expressed as double values from 0.0 to 1.0. It has the following interface:
pixel()
pixel(r, g, b)
The constructor can take three double values, 0.0 through 1.0, for each color, or can be constructed with no parameters, which sets to white (all 1.0).
p.set(r, g, b)
Set the colors of the existing pixel p to the indicated values.
p.r()
p.g()
p.b()
For a pixel p, extractors for each color. Each returns double.
p.r(c)
p.g(c)
p.b(c)
For a pixel p, setters for each color. Each takes double and returns void.
p.rb()
p.gb()
p.bb()
For a pixel p, for each color a method that returns that color as byte magnitude. The return value is unsigned char, and has a value in the range 0 to 255 converted from the original floating point value.
p.random()
Set the value of pixel p to a randomly-chosen color.
The second class you need to create is image, which represents a 2-d array of pixel. It should have the following interface:
image(nrow, ncol, startpix)
Create an image object with the indicated numbers of rows and colums (integer values), and a starting pixel value to fill the image. The starting value may be omitted, and defaults to white (1.0, 1.0, 1.0).
im.rows()
im.cols()
For an image im, extract the size parameters from construction.
im.set(row, col, color)
im.fetch(row, col)
For an image im, set or fetch the pixel at the indicated row or column. The row and col values are integers, and the color is a pixel object. The set method returns void, and fetch returns a pixel object.
im.save(filename)
Save the image in the specified file. The parameter is a string.

The pixel class should be fairly straightforward. It holds three private double values and implements the methods described. The byte values returned by rb, gb, bb are computed by multiplying the component brightness, 0.0 through 1.0 by 255.

To generate a random color value, use the library rand() call. This returns an integer, as the documentation says, in the range 0 to RAND_MAX. So, to create a random number from 0.0 to 1.0, compute rand()/RAND_MAX, with appropriate casting, so you get a floating point division. Your class does not need to call srand.

The class image needs to implement a two-dimensional array of pixels. There are a number of ways to do this, but I think the standard vector is the most reasonable. There are two ways to implement it:
  1. A single vector holding nrow×ncol pixels.
  2. A vector of vectors of pixels: a vector nrow long, with each slot holding a vector of pixels ncol long, representing that row.
For option 1, your set and fetch must compute the location inside the the vector based on the values of row and col. You store the rows one after another in the vector. For instance, suppose you have an image of three rows and four columns. The client designates the positions by row and column, like this:
0,00,10,20,3
1,01,11,21,3
2,02,12,22,3
But you store them in the vector linearly:
01234567891011
0,00,10,20,31,01,11,21,32,02,12,22,3
So you must compute the linear location from the row and column. Multiply the row by the size of a row (the width) to get the position of the row in the linear array, then add the column. For instance, the pixel at 1,2 in the grid is located at 1*4+2 = 6 in the linear layout. So if your vector is v, just look at v[6].

Option 2 is to actually store in two dimensions, using a vector of vectors. If you do this you can reach a pixel directly, as v[1][2]. Since vectors are generally born empty, either solution requires your constructor to fill the vector with the default pixel, either white or the one provided. (You might check out the resize method.) The two-dimensional version will be more complicated to initialize, since you will need to resize both the base vector, then the vector for each row, probably needing a loop.

Your save method will use the jpeg_writer object as described above. Just create the object, then loop through the image data and add each pixel. For option 1 above, just loop through the vector. Option 2 will need a double loop, outer through rows and inner through the contents of the row. Use the unsigned byte values of the colors since that's what that class expects.

I was lazy and put both my pixel and image classes in a single .h file. You may do that, or place each class in its own .h file. You can also split into .h and .cpp files if you have a long method. Your .h file (or files) should use the #ifndef/#define/#endif madness to prevent multiple definition errors, as discussed in class.

Download and Setup

The download files are grad.cpp, circles.cpp, jpge.h and jpge.cpp. Make a new directory, and put them there. Add whatever file or files contain your two classes to complete the program, but do not change the downloaded files. Alternatively, you can also download them all in this zip file.

If you are using CodeBlocks, you will need t create a project and add all your files to it. Other IDEs may work this way as well.

If you are using an IDE, a fairly smart one should be able to figure out how to build the programs. The commands for grad are given here. If you don't create your own .cpp file, omit mention of yours.cpp and yours.o.

g++ -c grad.cpp g++ -c jpge.cpp g++ -c yours.cpp g++ -o grad grad.o yours.o jpge.o
This compiles each component C++ file to an object file, then combines the object files to create the executable. Each of the first three steps produces one of the .o files combined in the last. Compiling circles is the same, using the other file name. The support files only need to be compiled once.

If you download the zip file above, it includes a Makefile that will automate the build on a Unix (or probably Mac) command line. It assumes that you put your class in img.h and did not create an img.cpp. If you do otherwise, edit the names or add the img.cpp as shown in comments. Then say make grad or make circles.

Making A Stub

It is very helpful to create stub versions of the two required classes. The two existing programs, grad.cpp and circles.cpp, assume these are together in a file called img.h. (You may change this name by modifying the #include directives in those files, and also any references in the Makefile, if you are using that.)

Making stubs can be useful to get your compiling setup before you have to start thinking about how to make everything work. Just create a file, probably img.h, in which you define the two classes. For each, create a definition of each public method having the correct parameter types and return value, but doing nothing. Void functions will have empty bodies. A function with a real return value will need to have just a return statement sending a constant of the appropriate type. Add this to the other files, and you should be able to compile an executable. Then you can start filling in object data and correct methods so it actually works. Building a stub file is a bit tedious, but not difficult, and will help get yout started.

Using JGrasp

Here's how I managed to build the assignment using JGrasp.
  1. Download the zip file and unpack into some working directory.
  2. Make a stub file as descussed above.
  3. Build the project with make by using the hammer icon in JGrasp.

But, when I tried it on Windows in the lab, this didn't work because JGrasp could not find the make program. (It does actually tell you that make was not found.) It may work fine for you; it depends on where you got your C++ compiler.

The lab computer got its C++ compiler from the installation of CodeBlocks, and the problem is that they install make under a different name. I could not find a way to tell JGrasp to use a different name, so I told the system to do so. CodeBlocks stores its compiler files in the directory C:\Program Files\CodeBlocks\MinGW\bin. The make program is called mingw32-make instead of just make. To fix this, I created a text file under the name C:\Program Files\CodeBlocks\MinGW\bin\make.bat containing the single line mingw32-make %*. This makes a Windows batch file for the make command, that simply runs the actual command under its different name. This fixed the problem. If you got your C++ by installing the mingw compiler directly as described here, then you may have the same problem, except you will need to store the bat file file in C:\MinGW\bin instead of C:\Program Files\CodeBlocks\MinGW\bin.

Submission

When your program works, is well-commented, and nicely indented, submit over the web here. Submit what you write. I don't need copies of the downloaded files, which you should not have changed.