answer06.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. /*
  2. * For this assigment you will write some functions that help
  3. * accomplish the following procedure:
  4. * (1) Load an image from disk
  5. * (2) Perform linear-normalization on the image (formula given)
  6. * (3) Save the image in ppm format (this is done for you)
  7. *
  8. * You will also need to set up your own environment to run and test
  9. * your code. No Makefile will be supplied. You must generate your own
  10. * testcases and make sure your code works yourself.
  11. *
  12. * Writing part (1) of this assignment is tricky, because you cannot
  13. * control data coming from outside of your program. Because you cannot
  14. * control the data, you cannot trust it either, and so you must check
  15. * _everything_. Just to be clear, you must check, ...
  16. *
  17. * E.V.E.R.Y.T.H.I.N.G
  18. *
  19. * Most dangerous real-world computer bugs are related to programmers
  20. * not sufficiently checking the inputs to their programs. So for
  21. * this assignment, you will need to think very carefully about what
  22. * could go wrong when reading a file. If anything goes wrong, you
  23. * return NULL and leak no resources. In this way your code will be
  24. * bullet-proof; even a malicious user cannot crash your program.
  25. *
  26. * You are supplied with a few test images, including corrupt test
  27. * images. These test-cases are provided in order to simplify and
  28. * standardise the assignment, and ease the learning process. However,
  29. * in real-world scenarios, you would need to generate your own
  30. * testcases, including corrupt test cases. By the end of this
  31. * assignment, you should understand how to go about doing that.
  32. *
  33. * Writing part (2) requires implementing a simple math formula to
  34. * scale the intensity values in the input image. You have the
  35. * expected output files in the "expected" directories. You can
  36. * test if you have the correct solution by comparing your output
  37. * file with the "expected" directory.
  38. */
  39. /*
  40. * == HINTS ==
  41. *
  42. * (1) Although a file is just a stream of bytes (1s and 0s), logically
  43. * speaking, they are generally divided into a "header" structure
  44. * and a "data" structure. The "header" explains the layout of the
  45. * "data".
  46. *
  47. * (2) When reading and writing binary files, it helps to look at the
  48. * files in a hex-editor. Hex editors show a file as a sequence
  49. * of hexidecimal (base-16) numbers. (If you don't yet understand
  50. * base-16 notation, then now is a good time to learn this simple
  51. * and important concept.) You can ask emacs to show you the raw
  52. * byte-structure of a file by using "hexl-mode". (M-x hexl-mode)
  53. *
  54. * (3) Write your own makefile -- you already have examples from
  55. * previous assignments. Your best of compiling your code with make
  56. * but running tests by hand.
  57. *
  58. * (4) An alternative to Make is to simply write a bash script. This
  59. * is extremely easy. A bash script is just a sequence of commands
  60. * that you would otherwise type into the terminal. You put
  61. * "#!/bin/bash" on the very first line, change the permission, and
  62. * you are done.
  63. *
  64. * (5) Don't forget to run your code through valgrind. You must _never_
  65. * leak memory.
  66. *
  67. * (6) You can compare two binary files using the "md5sum" program:
  68. *
  69. * > md5sum expected/01-smile.ppm
  70. * b981173424d0ba22bf406fa0bf37cfe1 expected/01-smile.ppm
  71. * > md5sum outputs/01-smile.ppm
  72. * b981173424d0ba22bf406fa0bf37cfe1 outputs/01-smile.ppm
  73. *
  74. * If two files have _exactly_ the same bytes, then their md5sum
  75. * will be the same.
  76. *
  77. * (7) Don't leave this to the last minute.
  78. *
  79. */
  80. #include <stdio.h>
  81. #include <stdlib.h>
  82. #include <assert.h>
  83. #include <string.h>
  84. #include "pa06.h"
  85. /*
  86. * ===================================================================
  87. * Load an image from disk in the "ece264" format.
  88. *
  89. * The "ece264" image format supports 8-bit grayscale images. That
  90. * means that every pixel in the image has a single 8-bit value
  91. * (0..255), which denotes the intensity, or amount of whiteness at
  92. * that pixel.
  93. *
  94. * The file has a 16 byte header whose binary layout is given by the
  95. * struct ImageHeader in the file "pa06.h". The full explanation of
  96. * this header is:
  97. *
  98. * + 4 byte integer: "magic number" 0x21343632.
  99. * The first 4 bytes of the file always start with
  100. * this number (in little-endian format). If this
  101. * number is absent, then there is something wrong
  102. * with the file.
  103. * + 4 byte integer: width of the image
  104. * + 4 byte integer: height of the image
  105. * + 4 byte integer: length of an ASCII string file comment including
  106. * the NULL-byte
  107. *
  108. * The next N bytes is a null-termianted ASCII string. The length of
  109. * the string is specified in the last field of the header. The length
  110. * _includes_ the trailing NULL byte.
  111. *
  112. * After the ASCII string, there are width*height bytes of pixel data.
  113. * Each byte is /unsigned/, and represents the intensity of a pixel in
  114. * the range [0..255]. The intensity of the pixel is its "whiteness".
  115. * A value of 255 denotes a fully white pixel, and a value of 0
  116. * denotes a fully black pixel.
  117. *
  118. * The pixels are stored in "row-major-order" from top-to-bottom.
  119. * That means that the first byte if the intensity of pixel [0, 0],
  120. * which is the top-left corner of the image. After reading "width"
  121. * number of pixels, you will arrive at the start of the second row
  122. * of pixels, which is the intensity of coordinate [0, 1]: the first
  123. * pixel of the second line of the image.
  124. *
  125. * In general, pixel[x, y] == image->data[x + y * width] where (x, y)
  126. * is the x-y co-ordinate of the pixel, with x increasing left-to-
  127. * right, and y increasing top-to-bottom.
  128. *
  129. * To complete this function, you must:
  130. * (1) use fopen to open the file and check that the file was truly
  131. * opened
  132. * (2) read the 16 byte header, checking that you actually read 16
  133. * bytes. (There is no guarantee that the input file is 16 bytes
  134. * long.)
  135. * (3) Check that the magic-bits match ECE264_IMAGE_MAGIC_BITS.
  136. * (4) Check that the image width and height is sane. (i.e., > zero.)
  137. * (5) Malloc memory for the comment. (The comment_len _includes_ the
  138. * null byte on the string.)
  139. * (6) Check that malloc returned a non-NULL pointer if comment_len
  140. * is greater than zero.
  141. * (7) Read the string from disk, checking that you read every byte
  142. * successfully.
  143. * (8) Malloc memory for the pixel data. There are width * height
  144. * pixels, and each pixel uses sizeof(uint8_t) bytes.
  145. * (9) Read the pixel data from disk, making sure that you read every
  146. * bytes successfully.
  147. * (10) Attempt to read one more pixel from file and check that this
  148. * FAILS. (There should be no additional bytes at the end of the
  149. * file.)
  150. * (11) If anything went wrong in steps (1-10), then you must clean up
  151. * any resources used, and then return NULL. Otherwise you need
  152. * to allocate a struct Image pointer, fill in its values and
  153. * return it.
  154. *
  155. * LEAK NO RESOURCES
  156. *
  157. * Good luck.
  158. */
  159. struct Image * loadImage(const char* filename)
  160. {
  161. FILE *file;
  162. struct ImageHeader header;
  163. file = fopen(filename, "r");
  164. if(file == NULL)
  165. return NULL;
  166. if(fread(&header, 1, sizeof(struct ImageHeader), file)
  167. != sizeof(struct ImageHeader))
  168. return NULL;
  169. if(header.magic_bits != ECE264_IMAGE_MAGIC_BITS ||
  170. header.width < 1 || header.height < 1)
  171. return NULL;
  172. char *comment = malloc(header.comment_len * sizeof(char));
  173. if(header.comment_len > 0 && comment == NULL)
  174. return NULL;
  175. fread(comment, sizeof(char), header.comment_len, file);
  176. char *data = calloc(header.width * header.height + 1, sizeof(char));
  177. if(data == NULL)
  178. return NULL;
  179. fread(data, 1, header.width * header.height, file);
  180. fclose(file);
  181. struct Image *image = malloc(sizeof(struct Image));
  182. image->comment = malloc(header.comment_len * sizeof(char));
  183. image->data = malloc(header.width * header.height * sizeof(uint8_t*));
  184. image->width = header.width;
  185. image->height = header.height;
  186. strcpy(image->comment, (const char *)comment);
  187. strcpy((char *)image->data,(const char *)data);
  188. free(comment);
  189. free(data);
  190. return image;
  191. }
  192. /*
  193. * ===================================================================
  194. * Free memory for an image structure
  195. *
  196. * loadImage(...) (above) allocates memory for an image structure.
  197. * This function must clean up any allocated resources. If image is
  198. * NULL, then it does nothing. (There should be no error message.) If
  199. * you do not write this function correctly, then valgrind will
  200. * report an error.
  201. */
  202. void freeImage(struct Image * image)
  203. {
  204. if(image == NULL)
  205. return;
  206. free(image->comment);
  207. free(image->data);
  208. free(image);
  209. }
  210. /*
  211. * ===================================================================
  212. * Performs "linear normalization" on the passed image
  213. *
  214. * Imagine that the intensity values in your input image are in the
  215. * range [50..180]. The image looks gray and washed out. You can
  216. * "normalize" the image, which means apply some mathematical filter
  217. * that "stretches" the intensity values to give a more satisfying
  218. * image. We're going to use "linear normalization" which is the
  219. * simplest normalization method. It works as follows:
  220. *
  221. * (1) Find the min and max intensity value of pixels in the image
  222. * (2) Now scale each pixel so that its new intensity is:
  223. *
  224. * pixel[i] = (pixel[i] - min) * 255.0 / (max - min)
  225. *
  226. * That's it! For the above example the equation is:
  227. *
  228. * pixel[i] = (pixel[i] - 50) * 255.0 / (180 - 50)
  229. *
  230. * To implement this, you need two for loops, one after the other.
  231. * The first for-loop completes step (1), and the second for-loop
  232. * to complete step (2).
  233. */
  234. void linearNormalization(struct Image * image)
  235. {
  236. int min = 255;
  237. int max = 0;
  238. int i;
  239. for(i = 0; i < sizeof(image->data); i++)
  240. {
  241. if(image->data[i] < min)
  242. min = image->data[i];
  243. if(image->data[i] > max)
  244. max = image->data[i];
  245. }
  246. for(i = 0; i < sizeof(image->data); i++)
  247. {
  248. image->data[i] = (image->data[i] - min) * 255.0 / (max - min);
  249. }
  250. }