| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 |
- /*
- * For this assigment you will write some functions that help
- * accomplish the following procedure:
- * (1) Load an image from disk
- * (2) Perform linear-normalization on the image (formula given)
- * (3) Save the image in ppm format (this is done for you)
- *
- * You will also need to set up your own environment to run and test
- * your code. No Makefile will be supplied. You must generate your own
- * testcases and make sure your code works yourself.
- *
- * Writing part (1) of this assignment is tricky, because you cannot
- * control data coming from outside of your program. Because you cannot
- * control the data, you cannot trust it either, and so you must check
- * _everything_. Just to be clear, you must check, ...
- *
- * E.V.E.R.Y.T.H.I.N.G
- *
- * Most dangerous real-world computer bugs are related to programmers
- * not sufficiently checking the inputs to their programs. So for
- * this assignment, you will need to think very carefully about what
- * could go wrong when reading a file. If anything goes wrong, you
- * return NULL and leak no resources. In this way your code will be
- * bullet-proof; even a malicious user cannot crash your program.
- *
- * You are supplied with a few test images, including corrupt test
- * images. These test-cases are provided in order to simplify and
- * standardise the assignment, and ease the learning process. However,
- * in real-world scenarios, you would need to generate your own
- * testcases, including corrupt test cases. By the end of this
- * assignment, you should understand how to go about doing that.
- *
- * Writing part (2) requires implementing a simple math formula to
- * scale the intensity values in the input image. You have the
- * expected output files in the "expected" directories. You can
- * test if you have the correct solution by comparing your output
- * file with the "expected" directory.
- */
- /*
- * == HINTS ==
- *
- * (1) Although a file is just a stream of bytes (1s and 0s), logically
- * speaking, they are generally divided into a "header" structure
- * and a "data" structure. The "header" explains the layout of the
- * "data".
- *
- * (2) When reading and writing binary files, it helps to look at the
- * files in a hex-editor. Hex editors show a file as a sequence
- * of hexidecimal (base-16) numbers. (If you don't yet understand
- * base-16 notation, then now is a good time to learn this simple
- * and important concept.) You can ask emacs to show you the raw
- * byte-structure of a file by using "hexl-mode". (M-x hexl-mode)
- *
- * (3) Write your own makefile -- you already have examples from
- * previous assignments. Your best of compiling your code with make
- * but running tests by hand.
- *
- * (4) An alternative to Make is to simply write a bash script. This
- * is extremely easy. A bash script is just a sequence of commands
- * that you would otherwise type into the terminal. You put
- * "#!/bin/bash" on the very first line, change the permission, and
- * you are done.
- *
- * (5) Don't forget to run your code through valgrind. You must _never_
- * leak memory.
- *
- * (6) You can compare two binary files using the "md5sum" program:
- *
- * > md5sum expected/01-smile.ppm
- * b981173424d0ba22bf406fa0bf37cfe1 expected/01-smile.ppm
- * > md5sum outputs/01-smile.ppm
- * b981173424d0ba22bf406fa0bf37cfe1 outputs/01-smile.ppm
- *
- * If two files have _exactly_ the same bytes, then their md5sum
- * will be the same.
- *
- * (7) Don't leave this to the last minute.
- *
- */
- #include <stdio.h>
- #include <stdlib.h>
- #include <assert.h>
- #include <string.h>
- #include "pa06.h"
- /*
- * ===================================================================
- * Load an image from disk in the "ece264" format.
- *
- * The "ece264" image format supports 8-bit grayscale images. That
- * means that every pixel in the image has a single 8-bit value
- * (0..255), which denotes the intensity, or amount of whiteness at
- * that pixel.
- *
- * The file has a 16 byte header whose binary layout is given by the
- * struct ImageHeader in the file "pa06.h". The full explanation of
- * this header is:
- *
- * + 4 byte integer: "magic number" 0x21343632.
- * The first 4 bytes of the file always start with
- * this number (in little-endian format). If this
- * number is absent, then there is something wrong
- * with the file.
- * + 4 byte integer: width of the image
- * + 4 byte integer: height of the image
- * + 4 byte integer: length of an ASCII string file comment including
- * the NULL-byte
- *
- * The next N bytes is a null-termianted ASCII string. The length of
- * the string is specified in the last field of the header. The length
- * _includes_ the trailing NULL byte.
- *
- * After the ASCII string, there are width*height bytes of pixel data.
- * Each byte is /unsigned/, and represents the intensity of a pixel in
- * the range [0..255]. The intensity of the pixel is its "whiteness".
- * A value of 255 denotes a fully white pixel, and a value of 0
- * denotes a fully black pixel.
- *
- * The pixels are stored in "row-major-order" from top-to-bottom.
- * That means that the first byte if the intensity of pixel [0, 0],
- * which is the top-left corner of the image. After reading "width"
- * number of pixels, you will arrive at the start of the second row
- * of pixels, which is the intensity of coordinate [0, 1]: the first
- * pixel of the second line of the image.
- *
- * In general, pixel[x, y] == image->data[x + y * width] where (x, y)
- * is the x-y co-ordinate of the pixel, with x increasing left-to-
- * right, and y increasing top-to-bottom.
- *
- * To complete this function, you must:
- * (1) use fopen to open the file and check that the file was truly
- * opened
- * (2) read the 16 byte header, checking that you actually read 16
- * bytes. (There is no guarantee that the input file is 16 bytes
- * long.)
- * (3) Check that the magic-bits match ECE264_IMAGE_MAGIC_BITS.
- * (4) Check that the image width and height is sane. (i.e., > zero.)
- * (5) Malloc memory for the comment. (The comment_len _includes_ the
- * null byte on the string.)
- * (6) Check that malloc returned a non-NULL pointer if comment_len
- * is greater than zero.
- * (7) Read the string from disk, checking that you read every byte
- * successfully.
- * (8) Malloc memory for the pixel data. There are width * height
- * pixels, and each pixel uses sizeof(uint8_t) bytes.
- * (9) Read the pixel data from disk, making sure that you read every
- * bytes successfully.
- * (10) Attempt to read one more pixel from file and check that this
- * FAILS. (There should be no additional bytes at the end of the
- * file.)
- * (11) If anything went wrong in steps (1-10), then you must clean up
- * any resources used, and then return NULL. Otherwise you need
- * to allocate a struct Image pointer, fill in its values and
- * return it.
- *
- * LEAK NO RESOURCES
- *
- * Good luck.
- */
- struct Image * loadImage(const char* filename)
- {
- FILE *file;
- struct ImageHeader header;
-
- file = fopen(filename, "r");
- if(file == NULL)
- return NULL;
- if(fread(&header, 1, sizeof(struct ImageHeader), file)
- != sizeof(struct ImageHeader))
- return NULL;
-
- if(header.magic_bits != ECE264_IMAGE_MAGIC_BITS ||
- header.width < 1 || header.height < 1)
- return NULL;
-
- char *comment = malloc(header.comment_len * sizeof(char));
- if(header.comment_len > 0 && comment == NULL)
- return NULL;
- fread(comment, sizeof(char), header.comment_len, file);
-
- char *data = calloc(header.width * header.height + 1, sizeof(char));
- if(data == NULL)
- return NULL;
- fread(data, 1, header.width * header.height, file);
- fclose(file);
-
- struct Image *image = malloc(sizeof(struct Image));
- image->comment = malloc(header.comment_len * sizeof(char));
- image->data = malloc(header.width * header.height * sizeof(uint8_t*));
- image->width = header.width;
- image->height = header.height;
- strcpy(image->comment, (const char *)comment);
- strcpy((char *)image->data,(const char *)data);
- free(comment);
- free(data);
- return image;
- }
- /*
- * ===================================================================
- * Free memory for an image structure
- *
- * loadImage(...) (above) allocates memory for an image structure.
- * This function must clean up any allocated resources. If image is
- * NULL, then it does nothing. (There should be no error message.) If
- * you do not write this function correctly, then valgrind will
- * report an error.
- */
- void freeImage(struct Image * image)
- {
- if(image == NULL)
- return;
-
- free(image->comment);
- free(image->data);
- free(image);
- }
- /*
- * ===================================================================
- * Performs "linear normalization" on the passed image
- *
- * Imagine that the intensity values in your input image are in the
- * range [50..180]. The image looks gray and washed out. You can
- * "normalize" the image, which means apply some mathematical filter
- * that "stretches" the intensity values to give a more satisfying
- * image. We're going to use "linear normalization" which is the
- * simplest normalization method. It works as follows:
- *
- * (1) Find the min and max intensity value of pixels in the image
- * (2) Now scale each pixel so that its new intensity is:
- *
- * pixel[i] = (pixel[i] - min) * 255.0 / (max - min)
- *
- * That's it! For the above example the equation is:
- *
- * pixel[i] = (pixel[i] - 50) * 255.0 / (180 - 50)
- *
- * To implement this, you need two for loops, one after the other.
- * The first for-loop completes step (1), and the second for-loop
- * to complete step (2).
- */
- void linearNormalization(struct Image * image)
- {
- int min = 255;
- int max = 0;
- int i;
- for(i = 0; i < sizeof(image->data); i++)
- {
- if(image->data[i] < min)
- min = image->data[i];
- if(image->data[i] > max)
- max = image->data[i];
- }
- for(i = 0; i < sizeof(image->data); i++)
- {
- image->data[i] = (image->data[i] - min) * 255.0 / (max - min);
- }
- }
|