Due Wednesday, April 20th, 11:59pm

Goals for this homework

  • More practice with multi-dimensional arrays
  • Practice using structs
  • Practice using pointers and performing memory allocation
  • Introduce an event loop

You are expected to complete this assignment individually. If you need help, you are invited to come to office hours and/or ask questions on piazza. Clarification questions about the assignments may be asked publicly. Once you have specific bugs related to your code, make the posts private.

This homework has several problems. We are also providing you with some resources on printf and error handling for this assignment.

You should submit four files for this assignment ( hw3.h, hw3.c, hw3_main.c, and Makefile) in your subversion repository as directed below.

PPM Images

For problems 2 and 3, you will be using ppm, a simple image format for storing pictures (ppm stands for "portable pixel map"). The ppm format exists independent of C programming; it is a simple plaintext image format that you can produce (or consume) with programs written in any language. It is not an efficient representation, but it has the great virtue of being human-readable, and so it is relatively easy to examine and debug.

Colors in programs are stored as rgb - red, green, and blue. In our ppm format, we will allow the values to range from no color (0) to full saturated (255). Any color can be created using these three numbers. As you would expected, red is (255,0,0), green is (0,255,0), and blue is (0,0,255). When combining colors, though, you need to remember that these are light palettes, not paint palettes. Therefore, white is the saturation of all colors of light (255,255,255), and black is the absence of light (0,0,0). To get the values for red, orange, yello, green, blue, and purple, you can look at this website.

A plain ppm image is a text file containing the following:

  1. the "magic number" P3, which is a code identifying the file as a plain ppm,
  2. whitespace,
  3. an ASCII decimal number signifying the width of the image in pixels,
  4. whitespace,
  5. an ASCII decimal number signifying the height of the image in pixels,
  6. whitespace,
  7. an ASCII decimal number signifying the maximum color value allowed in any color component (use 255 in the present exercise),
  8. a single whitespace character (usually \n), and
  9. a sequence of ASCII decimal numbers, interspersed with arbitrary whitespace, constituting the red, green, and blue values of each pixel. The order is "text order" — you read pixel-by-pixel from left to right across each row, row-by-row.
The length of each line in a plain ppm file must not exceed 70 characters. (This constraint is difficult to violate.)

For example: the following is a 3x3 square image of the HTML color dodgerblue (which has RGB values 30, 144, 255) in plain ppm:

P3
3 3
255
30 144 255
30 144 255
30 144 255
30 144 255
30 144 255
30 144 255
30 144 255
30 144 255
30 144 255
The first three triples are the three pixels in the first row (left to right), the second three pixels are the second row, the last three are the third row.

Assuming this text were saved in a file named dodgerblue.ppm, you could* (see below) view the file with the command-line program display, like so:

$ display dodgerblue.ppm
*However, at the time of this writing display is not properly configured on the CSIL Macs, so you won't be able to use it today; as such, please use Photoshop to view the PPMs. (Using Photoshop to view a PPM is sort of like taking a stretch limousine to the corner store, but it will suffice for the time being). We will work on getting display working in the meantime (it's a much lighter weight, open-source tool, and you can all install it on your own machines).

User Input

The last problem requires user input. Because we have not covered user input in class, we will only get the simplest of input. Each time you want user input, you will ask a question. You will receive only integer responses and string responses. You will not get two responses in one line. Each query will have only one response. If you need two pieces of information, ask for them separately.

For example, if I want to ask the user how many students are in the class, this is the code to do so. Note the last step - calling atoi to take the string representation of the number and translating it into the integer.

/* make a spot to put what we read in */
char buffer[100];
/* ask the question */
printf("How many students are in the class? ");
/* read in up to 100 characters (or to the \n, whichever comes first), place
 * those in the buffer, and read it from stdin, which is the keyboard
 */
fgets(buffer, 100, stdin);
/* translate the first thing from a set of characters to the integer number */
int num_students = atoi(buffer);
If we want to get a string result, the complicating factor is that fgets reads in the string and a '\n' before the '\0'. We need to get rid of the '\n'. In this code, we'll assume buffer was already declared and allocated - we can use the same buffer for all of the questions.
char *response;
printf("What is your name? ");
fgets(buffer,100,stdin);
/* now call strdup to make a new string to hold it so we can reuse buffer*/
response = strdup(buffer);
/* now get rid of the '\n' */
int length = strlen(response); // step 1: find out length
response[length-1] = '\0'; // step 2: put '\0' there to end string one earlier

Set Up

Because you are adding a second set of files to your directory, you need to add a second target to your Makefile. In your makefile, add another two lines (with a space between these and the ones already there).

hw3: hw3.h hw3.c hw3_main.c
	clang -Wall -o hw3 hw3.c hw3_main.c

Now you need to make the skeleton code so that your program will minimally execute. You must do this in case you do not complete your assignment. Our testing infrastructure needs to compile and execute even if you did not complete the entire assignment.

  • Step 1: Create hw3.h.
    Start with the standard preprocessor guards to make sure the .h file is not read twice:
    #ifndef HW3_H
    #define HW3_H

    Add prototypes for all functions in this assignment, with a function comment preceding it. For example:
    /* surface_area_cylinder
     * Calculates the surface area of a cylinder given height and radius
     * inputs: height, radius
     * output: double
     */
    double surface_area_cylinder(double height, double radius);
    

    End with the endif for the preprocessor guard
    #endif
  • Step 2: Create hw3.c. For each function, implement the function with a single line - return with the right type. For example:
    double surface_area_cylinder(double height, double radius)
    {
    	return 0.0;
    }
    
  • Step 3: Create hw3_main.c. For each function, put in a single function call. For example:
    int main()
    {
    	surface_area_cylinder(1.0, 5.0);
    	// add the rest of the function calls here
    }
    
First get this compiling and running. It won't print out anything, but this will mean that your code will compile and execute with our infrastructure. This must work in order to get any points in this course. Do this first, not last.

Problem 1: digit_ and number_string

A digit, such as 7, can be written as an English word, such as "seven," and 79 as "seventy-nine." This word has a certain number of letters; in this case, five and twelve, respectively. Write a function, digit_string that takes in a single number,no more than one digit, represented as an unsigned int, and returns a C string representing that number. Remember that in C, a string is an array of characters ending with '\0'.

If the user enters anything other than a single digit number, print out an error "error (digit_string): [description of error]". Remember, as described above, to use fprintf rather than printf and send the output to stderr. Return NULL to indicate that an improper input was received.

Note: You may use string functions for this problem.

The function headers must be as follows:

char* digit_string(unsigned int digit);

Now expand on this by creating number_string that returns a string for a number with up to three digits. 127 is written "one hundred twenty-seven" and 513 is written "five hundred thirteen".

char* number_string(unsigned int number);
Don't forget to complete this portion, test it, and commit it before moving on!

Problem 2: picture patterns

In problem 2, you are going to create an image and store it in a structure. For this, you'll need two struct definitions. Place these in your hw3.h file.
typedef struct {
	int red, green, blue;
} pixel;

typedef struct {
	int height, width;
	 pixel **pixels;
} image;

For your first function, create vertical stripes in an image of height h and width w. The left-most stripe contains pixels p1, second stripe is p2, third stripe is p1, etc.

If h or w is 0, then set pixels to NULL and height and width to 0.

image vertical_stripes(pixel p1, pixel p2, unsigned int h, unsigned int w);

For your second function, create diagonal stripes in an image of height h and width w. The stripes are at a 45 degree angle, going up and to the right (or down and to the left). In the top row, the pixels alternate in pairs. The top-left pixel is p1, the one next to it is also p1, but the one next to that is p2, and the one next to that also p2, the one next to that is p1, etc. In the second row, the pixels are offset by one. The first pixel is p1, but the second two pixels are p2, the next two p1, next two p2, etc.

If h or w is 0, then set pixels to NULL and height and width to 0.

image diagonal_stripes(pixel p1, pixel p2, unsigned int h, unsigned int w);

Bonus: You will receive 2 points of extra credit if your problem 2 is entirely correct and you allocate the minimum amount of memory necessary to create the patterns.

Problem 3: print ppm

You are going to write a function that writes the image to a file. I will give you the code to open and close the file. Instead of printing with printf, print with fprintf, as shown below. The skeleton of the function is given - you just need to fill in the missing parts.

int print_ppm(char *filename, image pic)
{
	FILE *fp;
	fp = fopen(filename,"w");
	if (!fp)
	{
		fprintf(stderr,"error: could not open file %s\n",filename);
		return 0;
	}

	fprintf(fp,"P3\n");
	fprintf(fp,"%d %d\n",pic.width,pic.height);
	fprintf(fp,"255\n");

	// print out all of the pixels, row by row, one per line

	fclose(fp);
	return 1;
}
If I made file "hello.txt", I can look at it on the command line:
$ cat hello.txt

Don't forget to complete this portion, test it, and commit it before moving on!

Problem 4: event loop

In order to create an application that someone can use, you need an event loop. The event loop asks the user what he/she wants to do, then asks any questions that are necessary to perform that particular task. Once complete, it comes back and asks the user again what he/she would like to do.

This can also provide you a way of testing your code without having all of the tests hard-coded into main.


You have written two functions to create images, and they can create patterns of different colors. You are going to write an event loop that continually asks the user what he/she wants to do (create image diagonal or vertical stripes), what color the user wants (red, orange, yellow, green, blue, purple), what height and width, and what the filename should be.

Then you will call the appropriate functions you already wrote to accomplish the task.

In order to make sure we all have the same questions and question order (the testing infrastructure will need everyone's to be the same), here is code that implements the questions for a run of the program:

	void event_loop()
	{
		printf("What would you like to do?\n");
		printf("(0 = quit, 1 = vertical stripes, 2 = diagonal): ");
		// read in answer, interpret it
		printf("What is the first color?\n");
		printf("(red, orange, yellow, green, blue, purple): ");
		// read in answer, interpret it
		printf("What is the second color?\n");
		printf("(red, orange, yellow, green, blue, purple): ");
		// read in answer, interpret it
		printf("What height? ");
		// read in answer, interpret it
		printf("What width? ");
		// read in answer, interpret it
		printf("Filename of file to write it in: ");
		// read in answer, interpret it
	}
	
Your job is to fill in the code that reads in each answer, translates it so it is useful to the program, and does any action based on it. You also need to loop rather than just go through once.

So that everyone agrees on the rgb values of different colors, here are the rgb values we'll use:

  • red: 255,0,0
  • orange: 255, 128, 0
  • yellow: 255, 255, 0
  • green: 0, 255, 0
  • blue: 0, 0, 255
  • purple: 128, 0, 255

Submit

At this point, you should have done the following:
  • Created four files and filled in the proper information: hw3.h, hw3.c, hw3_main.c inside your hw3 directory.
  • $ svn add hw3.h hw3.c hw3_main.c
    
  • Implemented all of your functions. If not, you at least need skeletons of your functions so that our automated tests will compile for the functions you did write.
  • Compiled your executable manually and with the Makefile
  • Implemented your test cases
  • Executed your code
  • Debugged your code
  • $ svn commit -m "hw3 complete"