Goals for this homework

  • Practice with multi-dimensional arrays
  • Practice using pointers

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 exercises. 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.

Steganography

The Wikipedia entry for steganography provides the following:

Steganography is the art and science of writing hidden messages in such a way that no one apart from the intended recipient knows of the existence of the message; this is in contrast to cryptography, where the existence of the message itself is not disguised, but the content is obscured. . .

Generally, a steganographic message will appear to be something else: a picture, an article, a shopping list, or some other message. This apparent message is the covertext. For instance, a message may be hidden by using invisible ink between the visible lines of innocuous documents.

The advantage of steganography over cryptography alone is that messages do not attract attention to themselves, to messengers, or to recipients. An unhidden coded message, no matter how unbreakable it is, will arouse suspicion and may in itself be incriminating, as in countries where encryption is illegal.

For this assignment, you will construct a very basic steganographic system by implementing a pair of data transformations. These two transformations provide the means to hide and later extract an approximation of a secret image in a cover image.


In the image below, the original image is on the left, and the image we are trying to store is in the middle. Notice that the image we are hiding is black and white. If we were to allow storing rich images, that would be too much information to hide without altering the look of the original picture. Instead, we are going to change it in a way that is visually indistinguishable from the original picture. The picture on the right is after the encoding of the black and white picture into the color picture.

We will encode one bit of information for each r, g, and b value. Let's take the red value. If the red value is "light" (more than half its saturation value), then we will make sure the red value of the new image is odd. If it is "dark" (less than half its saturation value), then it will be forced to be even.

To extract this same value, we determine whether or not the pixel in the encoded image is odd or even. If it is even, then red is set to 0. If it is odd, then red is set to the maximum value.

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.

File Input

In this exercise, you will be provided images that you need to read into r,g,b arrays. You have also been provided functions that will read and write PNG files. The functions are declared and implemented in hw3_provided.h and hw3_provided.c, respectively. You should already have those files from your completed warmup.

Exercise 1: Hiding Secrets (Encoding)

void hide_image(
	unsigned int ref_r[ROWS][COLS], 
	unsigned int ref_g[ROWS][COLS], 
	unsigned int ref_b[ROWS][COLS], 
	unsigned int hid_r[ROWS][COLS], 
	unsigned int hid_g[ROWS][COLS], 
	unsigned int hid_b[ROWS][COLS], 
	unsigned int res_r[ROWS][COLS], 
	unsigned int res_g[ROWS][COLS], 
	unsigned int res_b[ROWS][COLS], 
	unsigned int height, unsigned int width);

In this exercise, you will write the code to hide an image. You are using three images. The first, the reference image, is where you will hide the image. The second, hidden image, is the image you want to hide. The third, result, is the resulting image. All images are the same height and width. Height and width must be smaller than ROWS and COLS, respectively.

Because we haven't covered structs yet, each image is annoyingly split up into three double-arrays, each storing the values for one color component of the pixel. This will change once we cover structs.

You are going to do this by changing the lowest digit by no more than 1. If the hidden image has a value at least 128, then the value in the resulting image needs to be odd. If the reference picture's value is currently even, you need to increase the value by 1 in the resulting image. If the hidden image has a value less than 128, then the value of the resulting image needs to be even. If the current value in the reference image is odd, then you need to decrease the value by 1 in the resulting image.

The reference and hidden images must not change - those are input parameters. Only the result should change.

Exercise 2: Extracting Secrets (Decoding)

void extract_image(
	unsigned int res_r[ROWS][COLS], 
	unsigned int res_g[ROWS][COLS], 
	unsigned int res_b[ROWS][COLS], 
	unsigned int hid_r[ROWS][COLS], 
	unsigned int hid_g[ROWS][COLS], 
	unsigned int hid_b[ROWS][COLS], 
	unsigned int height, unsigned int width);

Our encryption scheme loses data - if you have an image that you want to hide that has many colors, those will be collapsed into a very small set of colors. That is okay and expected.

In this part, you will extract the hidden image (which may or may not be identical to the original hidden image). You will look at each pixel's r,g, and b values. If the value is odd, then the resulting image value is 255. If it is even, then the resulting image value is 0.

res is the image that is encoded. This is an in parameter. hid is where you will put the image you extract. This is the hidden image. It is an out parameter.

Exercise 3: put it all together

	void encode(char *ref_filename, char *hid_filename, char *enc_filename);
	void decode(char *enc_filename, char *hid_filename);

Write two functions. The first, encode, takes the filename of the reference picture, picture to hide, and resulting picture. It opens any necessary files, reads in any necessary images, produces the resulting image, and writes any necessary files. Decode receives the filenames of the encrypted file and the hidden file. It extracts the hidden file from the encrypted file. Note: Because information can be lost during encoding, the input hid_file from encode may not be identical to the output hid_file in the decoding.

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"