Assignment 08: Image conscious

Due Friday, March 20, before midnight

The goals for this assignment are:

  • Read and write binary PPM files

  • Write simple multi-process programs

Update your repository

Do a git pull to obtain the basecode for this assignment.

Your repository should now contain a new folder named A08.

PPM

For this question, you will implement two functions, read_ppm and write_ppm, that read and write binary PPM files.

Background

PPM (Portable Pix Map) is an image file format that stores the colors of an image as a 2D array of colors. Each color is represented as a RGB triplet, representing red, green and blue respectively. The properties of the image, such as its size and color format, are specified at the start of the file (called its "header information"). PPM supports both ASCII (plain text) and binary data (raw).

We will be working only with PPM’s binary format!

For example, consider the following image

example5000

The above image contains a 4x4 grid of colored pixels. Each pixel is a triplet of red-blue-green (RGB) color values, each stored as an unsigned char. Unsigned chars have values which range from 0 to 255, where smaller values correspond to darker colors. The triplet (0,0,0) corresponds to black. The triplet (255,255,255) corresponds to white. The triplet (255,0,0) corresponds to red. This system of colors is called the RGB Color Model and it is a common standard for representing colors on a computer.

The RGB colors for the pixels in the above image are as follows

(0,0,0)      (100,0,0)    (0,0,0)     (255,0,255)
(0,0,0)      (0,255,175)  (0,0,0)     (0,0,0)
(0,0,0)      (0,0,0)      (0,15,175)  (0,0,0)
(255,0,255)  (0,0,0)      (0,0,0)     (255,255,255)

In binary format (also called raw format), the image is stored as follows. To see for yourself, do hexedit feep-raw.ppm.

00000000   50 36 0A 23  20 43 72 65  61 74 65 64  20 62 79 20  47 49 4D 50  20 76 65 72  P6.# Created by GIMP ver
00000018   73 69 6F 6E  20 32 2E 31  30 2E 32 34  20 50 4E 4D  20 70 6C 75  67 2D 69 6E  sion 2.10.24 PNM plug-in
00000030   0A 34 20 34  0A 32 35 35  0A 00 00 00  64 00 00 00  00 00 FF 00  FF 00 00 00  .4 4.255....d...........
00000048   00 FF AF 00  00 00 00 00  00 00 00 00  00 00 00 00  0F AF 00 00  00 FF 00 FF  ........................
00000060   00 00 00 00  00 00 FF FF  FF                                                  .........

The leftmost column is byte number in hexadecimal. For example, 0x18 is 24 in decimal. There are 24 bytes on the first row on output. The rightmost column displays the raw data in ASCII, using . for non-visible ASCII codes, such as '\0' and 'Escape'.

Regardless of format, every PPM file starts the following information in its header.

  • A two-byte "magic number" indicating the type of PPM. Binary types start with "P6". In hexedit, this corresponds to the binary values 50 36 A0, e.g. the ascii values P, 6, and \n.

  • Width and height as ASCII decimal integers (separated by whitespace). Above this corresponds to the binary values 34 20 24 0A, or ascii values 4 4\n.

  • Maximum color value as an ASCII decimal integer. Above this corresponds to the values 32 35 35 0A, or ascii 255\n. You can assume the Maxval is less than 256, meaning each RGB value is 1 byte.

  • A raster (e.g. the array of pixels) of Height number of rows and Width number of columns, in order from top to bottom.

PPM images may contain comments in their header. These must be on their own line and start with the symbol . The above example has the comment Created by GIMP version 2.10.24 PNM plug-in
PPM files can be viewed using tools such as Photoshop and Gimp. If you are using VS Code, I recommend installing the extension, "PBM/PPM/PGM Viewer" by ngtystr, so you can easily view your PPM files.

Read PPM

In the file, ../common/read_ppm.c, implement the following two functions.

  • struct ppm_pixel* read_ppm(const char* filename, int* width, int* height)

    • Param filename: the image PPM to open and read

    • Param width: the address of an integer into which we will place the width of the loaded image (e.g. pass by pointer parameter)

    • Param height: the address of an integer into which we will place the height of the loaded image (e.g. pass by pointer parameter)

    • Return struct ppm_pixel*: a flat 2D array of struct ppm_pixel. Returns NULL if an error occurs. The caller is responsible for freeing the array.

  • struct ppm_pixel** read_ppm_2d(const char* filename, int* width, int* height)

    • Param filename: the image PPM to open and read

    • Param width: the address of an integer into which we will place the width of the loaded image (e.g. pass by pointer parameter)

    • Param height: the address of an integer into which we will place the height of the loaded image (e.g. pass by pointer parameter)

    • Return struct ppm_pixel**: an array of arrays 2D matrix of struct ppm_pixel. Returns NULL if an error occurs. The caller is responsible for free the array.

Each of these functions read a PPM file stored in binary format and returns a 2D array of struct pixel. A struct pixel has the following definition

struct ppm_pixel {
    unsigned char red;
    unsigned char green;
    unsigned char blue;
};
You will re-use this function in future assignments. For this reason, we place its implementation in it’s own file, read_ppm.c, and use a header file, read_ppm.h, to include it in our main application.

In the file, ../common/test_read.c, write a short test that calls your function and prints the contents of feep-raw.ppm like so:

$ make test_read
gcc -g -Wall -Wvla -Werror test_read.c read_ppm.c -o test_read
$ ./test_read
Testing file feep-raw.ppm with flat array:
(0,0,0) (100,0,0) (0,0,0) (255,0,255)
(0,0,0) (0,255,175) (0,0,0) (0,0,0)
(0,0,0) (0,0,0) (0,15,175) (0,0,0)
(255,0,255) (0,0,0) (0,0,0) (255,255,255)

Testing file feep-raw.ppm with array of arrays:
(0,0,0) (100,0,0) (0,0,0) (255,0,255)
(0,0,0) (0,255,175) (0,0,0) (0,0,0)
(0,0,0) (0,0,0) (0,15,175) (0,0,0)
(255,0,255) (0,0,0) (0,0,0) (255,255,255)

Requirements/Hints:

  • Your function should return NULL if the filename is invalid

  • Your function should return NULL if memory cannot be allocated for the image data

  • Your function should return a pointer to the array you create in read_ppm

  • You can assume that it is safe to read the header line by line using a function such as fgets or fscanf.

  • Do not modify read_ppm.h. And do not remove the version of the function you do not implement!

  • Make sure your program compiles using the Makefile

  • Your solution should support an optional comment line.

Write PPM

Implement a new function, write_ppm, defined in ../common/write_ppm.c. Similarly to read_ppm, you should implement both definitions of write_ppm.

  • int write_ppm(const char* filename, struct ppm_pixel* pixels, int width, int height)

    • Param filename: the image PPM to write

    • Param struct ppm_pixel*: a flat 2D array of struct ppm_pixel to write.

    • Param width: the width of the image

    • Param height: the height of the image

    • Return (int): Returns 1 if an error occurred; otherwise, returns 0.

  • int write_ppm_2d(const char* filename, struct ppm_pixel** pixels, int width, int height)

    • Param filename: the image PPM to write

    • Param struct ppm_pixel**: a 2D matrix array of arrays of struct ppm_pixel to write.

    • Param width: the width of the image

    • Param height: the height of the image

    • Return (int): Returns 1 if an error occurred; otherwise, returns 0.

In the file, test_write.c, write an application that tests both your write functions. Your application should create a 4 by 4 image with alternating stripes and save it to the files stripes_flat.ppm and stripes_aoa.ppm.

$ make test_write
gcc -g -Wall -Wvla -Werror test_write.c write_ppm.c read_ppm.c -o test_write
$ ./test_write
Writing stripes-flat.ppm (4 4)
Writing stripes-aoa.ppm (4 4)

The written images should both look the same. You can use any colors you like. Below, I used black and white.

stripes

Requirements/Hints:

  • Use fprintf to write the header and fwrite to write the binary Data to your file.

  • Do not modify write_ppm.h. And do not remove the version of the function you do not implement!

  • Make sure your program compiles using the Makefile

  • You can optionally write a comment if you wish.

Glitch

Glitch Art is the practice of leveraging technological errors for artistic purposes.

JoeLatimer.com Blog Featured Image Landscape 08 31 19

First, implement a new function, write_ppm, defined in read_ppm.c. Similarly to Assignment 04, you should change the definition of write_ppm if you use an "array of arrays" to store your pixels. Be careful that you do not change the definition of ppm_pixel!

Then write a program, glitch.c, that reads in a PPM file and "glitches" it. Your program should save the modified PPM in a new file with the suffix "-glitch". For example, if you load in the file "monalisa.ppm", you should output a file named "monalisa-glitch.ppm".

$ ./glitch monalisa.ppm
Reading monalisa.ppm with width 606 and height 771
Writing file monalisa-glitch.ppm

Original Image

Glitched Image

monalisa
monalisa glitch v2.0

Requirements/Hints:

  • Read in a file using a command line argument and save the result to [filename]-glitch.ppm

  • To start, implement a minimal glitch, which shifts each color value by either 1 or 2 bits, choosen at random, e.g. newcolorvalue = oldcolorvalue << (rand() % 2);. Your result should look like the example below.

  • Submit your glitched images as part of your submission!

Original Image

Result of random bit shift

monalisa
monalisa glitch simple

Be creative with your glitches! You can use a combination of bit operations, switching colors, adding colors, etc. Anything goes! Also, feel free to use your own images. PPM images can be exported using Gimp or Photoshop.

Submit your work

Push you work to github to submit your work.

$ cd A08
$ git status
$ git add *.c
$ git commit -m "assignment complete"
$ git status
$ git push

Grading Rubric

Assignment rubrics

Grades are out of 4 points.

  • (1.5 points) Read PPM

    • (0.6 points) correct behavior: reads the PPM file in binary format

    • (0.8 points) no memory errors, style

  • (1.5 points) Write PPM

    • (0.6 points) correct behavior: write the PPM file in binary format

    • (0.8 points) no memory errors, style

  • (1 points) glitch

    • (0.4 points) correct behavior: computes and saves a glitched image

    • (0.6 points) no memory errors, style

Code rubrics

For full credit, your C programs must be feature-complete, robust (e.g. run without memory errors or crashing) and have good style.

  • Some credit lost for missing features or bugs, depending on severity of error

  • -5% for style errors. See the class coding style here.

  • -50% for memory errors

  • -100% for failure to checkin work to Github

  • -100% for failure to compile on linux using make