This is an old revision of the document!


Introduction

For this assignment, we will work with images and implement some basic image manipulations. We will work with two types of images: greyscale and RGB.

Image representation

Internally, images are modeled as two-dimensional matrices of pixels; in Haskell we will model this by using lists of lists.

  • A greyscale image is a [[Int]]. Each Int is a value in the range 0-255 and represents the brightness of a pixel: 0 is pure black, 255 is pure white.
  • An RGB image is a [[(Int, Int, Int)]]. Each (Int, Int, Int) contains three values in the range 0-255, with the intensity for that pixel's primary colors: Red, Green and Blue.

For each of the two formats (greyscale and RGB), you are asked to implement the following image transformations:

1.1. vertical flip

1.2. horizontal flip

1.3. 90 degree left-rotation

1.4. 90 degree right-rotation

1.5. 180 degree left-rotation

1.6. 180 degree right-rotation

1.7. color inversion

  • you will have to flip a color value to the other side of the 0-255 interval

2.1. cropping with a rectangle selection

  • your function should take a rectangle modelled by a (Int, Int, Int, Int) tuple (the first two Ints are the coordinates of the top-right corner, the next two Ints are the height and width of the rectangle). It should return a new image with the same height and width as the rectangle selection containing the pixels in the image which fall withing the rectangle's area.

2.2. brightness adjustment

  • your function should take an adjustment value (an Int) with which to increase the value of each color (remember that 0 is black and 255 is the pure color, either white for greyscale, or R/G/B for color images).

2.3. masking with a bitmap mask

  • your function should take a mask represented as a two-dimensional bitmap with the same size as the image (a [[Bool]]). The resulting image should look like this: if the mask has False at position (x, y) the pixel at position (x, y) is black; otherwise it retains its original value.

2.4. superimposing another picture with a transparency factor

  • your function will take an additional image and a factor alpha that affects the transparency of this second image. The result should be an image whose pixel values are a linear combination of the two input images and the factor alpha. For example, if alpha is 0, the result is identical to the first image; if alpha is 1, the result is identical to the second one.

2.5. scaling with a horizontal and a vertical factor

  • your function will take two scaling factors (a (Float, Float)), for vertical and horizontal scaling. For upscaling (factors larger than 1), you should duplicate lines (vertical) or columns (horizontal). For downscaling (factors lesser than 1), you should remove every ^{th}$ line/column, where $ is $1 / scaling\_factor$.

2.6. color swapping

  • this transformation is relevant only for RGB images. Your function should swap the colors for each pixel (the exact permutations is your choice).

2.7. conversion to greyscale

  • this transformation is relevant only for RGB images.. Your function should convert an RGB image ([[(Int, Int, Int)]]) to a greyscale one ([[Int]). For each pixel, you can set the grey intensity to the average of its three colors.

For all the tasks listed above you do not need to implement any sanity check on the inputs; you can safely assume all inputs are valid. For example, you can assume that all lists of lists you work with are valid two-dimensional matrices (no line is longer or shorter than the others); all color values are between 0 and 255; for cropping, the rectangle described will always fall within the bounds of the image; for masking, the mask will always be the same size as the image; etc.

Image processing is a vast, complex subject in itself; often, there are multiple ways of achieving an effect, distinguished by output quality, ease of implementation, resource usage etc. Our focus here is simply on how to program in a functional style, so we don't really aim for performance and smooth results.

For example, the scaling algorithm presented (a form of nearest-neighbor interpolation}) is easy to write, but its results are poor. You can see here a quality comparison of various algorithms.

Helper code

For very basic testing, you can define your own test-cases. However, to give you a big-picture view and some satisfying results, we offer you a testing framework for Netpbm ASCII-format images.

Netpbm a simplistic pixel-map image format, capable of modelling both greyscale and RGB pictures.

A Netpbm image file has the following structure:

  • a header consisting of a capital “P” followed by a digit indicating the format (2 for greyscale, 3 for RGB).
  • two integers representing the width and height of the image in pixels
  • a maximum color value (for our purposes, this will always be 255)
  • the pixels of the image; for greyscale images, each pixel is a value in the range 0-255; for RGB pictures, each pixel consists of three consecutive values in the range 0-255 (for Red, Green and Blue).

The code provided reads and parses a Netpbm image, then appropriately calls the functions you implemented to perform a transformation, creating an output file which you can then inspect, either as values in a text editor, or as an image in an image viewer.

We provide you with a few Netpbm samples, but you can create your own images. Most image editors (e.g. Photoshop, GIMP) should be able to convert an arbitrary image to a Netpbm format; when asked about `data formatting'' (or similar terms), select “ASCII”, not “raw”.

A warning: for various reasons the resulting code is quite inefficient, so it will take a few seconds to process even small images. The bigger the image, the longer the processing time.

Examples

TODO

Usage

TODO

Scoring

TODO

Submission

TODO