Fundamentals of Computer Science I: Media Computing (CS151.01 2008S)

Assignment 9: Palettes and Efficiency


Due: 9:00 a.m., Wednesday, 9 April 2008

Summary: In this homework, you'll write procedures to load images from files that were stored using a color palette. You'll write one version that uses lists to represent palettes and another version that uses vectors. Finally, you'll compare the speed of both versions.

Purposes: To bring closure to our consideration of using palettes to store image data efficiently. To gain more experience using vectors. To compare the efficiency of lists and vectors in a (somewhat) realistic context.

Expected Time: Two to three hours.

Collaboration: We encourage you to work in groups of size three. You may, however, work alone or work in a group of size two or size four. You may discuss this assignment with anyone, provided you credit such discussions when you submit the assignment.

Submitting: Email your answer to . The title of your email should have the form CSC151.01 2008S Assignment 9: Palettes and Efficiency and should contain your answers to all parts of the assignment. Scheme code should be in the body of the message.

Warning: So that this assignment is a learning experience for everyone, we may spend class time publicly critiquing your work.

Introduction

In the reading and lab on palettes, we considered the use of list-based palettes to let us store images using smaller files (at the cost of some inaccuracy in the stored image). In the reading on vectors, we started to consider how vector-based palettes would let us load images faster. In this assignment, you will write procedures to load images using both list-based and vector-based palettes, and compare the speed of the two methods.

To be able to accurately reproduce an image using the palette approach, the file must contain not only a color index for each pixel, but also the list of colors that make up the palette. Here is an example of such a file:

3 
0 0 0 
128 128 128 
255 255 255
4 4
0 0 0 1 0 0 1 2 0 1 2 2 1 2 2 2

Here's what's going on in this file:

  • The first line indicates that there are three colors in the palette.
  • The next three lines give the RGB components for those three colors.
  • The fifth line contains the width and height of the image.
  • The final line gives an index into the palette for each of the 16 pixels.

What should the image look like? 0 corresponds to black, 1 to grey, and 2 to white. We should expect to see that the upper left of the image is black, the lower right is white, and there is a grey line dividing the two. Try drawing the image on paper to see.

Preliminaries

a. Copy the sample data above into a new file and save the file as sample.pal.

b. Copy and paste the supporting code at the end of this assignment into DrFu.

b. If you already have a list of colors to use as a palette, here is code to save an image using that palette. (You may have written similar procedures for the lab on palettes.) Copy and paste this code into DrFu. Read it and make sure you understand what is going on.

;;; Procedure:
;;;   rgb-list-write
;;; Parameters:
;;;   colors, a list of RGB colors
;;;   outport, an output port
;;; Purpose:
;;;   Writes the elements of colors to outport.
;;; Produces:
;;;   [Nothing; called for the side effect]
;;; Preconditions:
;;;   outport is open for writing
;;; Postconditions:
;;;   The file associated with outport now contains all the colors in
;;;   colors.
(define rgb-list-write
  (lambda (colors outport)
    (foreach! (lambda (color) (rgb-write color outport))
              colors)))

;;; Procedure:
;;;   palette-save-image
;;; Parameters:
;;;   palette, a list of rgb colors
;;;   image, an image
;;;   filename, a string
;;; Purpose:
;;;   Save the image using the palette to represent colors in the image.
;;; Produces:
;;;   [Nothing; called for the side effect.]
;;; Preconditions:
;;;   filename is a valid file name.
;;;   palette contains only rgb colors.
;;;   palette-encode-color, int-write, and rgb-write are defined.
;;; Postconditions:
;;;   The file now contains a sequence of integers as follows:
;;;     the number of colors in the palette,
;;;     a representation for each color in the palette as produced by rgb-write,
;;;     the width and height of the image,
;;;     and an index representing the color of each pixel in the image.
(define palette-save-image
  (lambda (palette image filename)
    (let ((port (open-output-file filename))
          (width (image-width image))
          (height (image-height image)))
      ; Write the number of colors in the palette.
      (int-write (length palette) port)
      ; Write the colors in the palette.
      (rgb-list-write palette port)
      ; Write the width and height of the image.
      (int-write width port)
      (int-write height port)
      ; Write the pixel data.
      (let kernel ((col 0)
                   (row 0))
        (cond
          ((>= row height)
           (close-output-port port)
           image)
          ((>= col width)
           (kernel 0 (+ row 1)))
          (else
           (int-write (palette-encode-color palette
                                            (image-get-pixel image col row))
                      port)
           (kernel (+ col 1) row)))))))

d. Use palette-save-image to save a very small image of your choice, using a small palette of your choice (such as rainbow-plus from the lab on palettes). Verify that the file produced is similar to the example given in the preliminaries above.

Assignment

Problem 1: Loading files using list-based palettes

Write (palette-load-image filename), which loads an image from a file. The procedure should assume the file has the following format:

  • The number of colors in the palette.
  • The colors in the palette, represented as RGB components.
  • The width of the image.
  • The height of the image.
  • The colors in the image, represented as indexes into the palette.

When your procedure works, you should be able to load the sample image from the preliminaries and see it in all its diagonal glory.

> (image-show (palette-load-image "sample.pal")) 

In writing this procedure, you may want to use image-read-pixmap as a pattern for how to read an image from a file.

;;; Procedure:
;;;   image-read-pixmap
;;; Parameters:
;;;   filename, a string
;;; Purpose:
;;;   Read pixmap data from the specified file, returning a new
;;;   image from that data.
;;; Produces:
;;;   image, an image.
;;; Preconditions:
;;;   filename names a file.
;;;   That file was created by image-write-pixmap.
;;; Postconditions:
;;;   image contains teh same colors in the same positions as the image 
;;;   previously written with image-write-pixmap.
(define image-read-pixmap
  (lambda (filename)
    (let* ((port (open-input-file filename))
           (width (read port))
           (height (read port))
           (image (image-new width height)))
      (let kernel ((col 0)
                   (row 0))
         (cond
           ((> (+ col row) (+ (- width 1) (- height 1)))
            (let ((next-color (rgb-read port)))
              (close-input-port port)
              (if (not (eof-object? next-color))
                  (throw "image-read-pixmap!: Data remain in file after image was competely read")))
            image)
           ((eof-object? (peek-char port))
            (close-input-port port)
            (throw "image-read-pixmap!: Premature end of file."))
           ((>= col width)
            (kernel 0 (+ row 1)))
           (else
            (let ((next-color (rgb-read port)))
              (cond 
                ((eof-object? next-color)
                 (close-input-port port)
                 (throw "image-read-pixmap!: Premature end of file."))
                (else
                 (image-set-pixel! image col row next-color)
                 (kernel (+ col 1) row))))))))))

You will also find it helpful to use the following procedure to read the list of RGB colors from the file.

;;; Procedure:
;;;   rgb-list-read
;;; Parameters:
;;;   source, an input port
;;;   n, a non-negative integer
;;; Purpose:
;;;   Reads n RGB colors from source, returning them as a list.
;;; Produces:
;;;   colors, a list of RGB colors
;;; Preconditions:
;;;   source is open for reading
;;; Postconditions:
;;;   colors contains the first n colors in source.
(define rgb-list-read
  (lambda (source n)
     (if (> n 0)
         (cons (rgb-read source)
               (rgb-list-read source (- n 1)))
         null)))

Problem 2: Loading files using vector-based palettes

In this part, you will adapt the code from Problem 1 to represent the palette using a vector rather than a list. The format of the files will be exactly the same. We will just change the data structure we use to store the palette while the image is being loaded.

a. Write the procedure (rgb-vector-read source n), which reads n colors from source and stores them in a vector of the appropriate size.

You can use rgb-list-read as a model, but remember that recursively filling in a vector is quite different from recursively building a list. You may want to also look at your definition of vector-fill! from the lab on vectors.

Be sure to test your procedure before you go on.

b. Write (vpalette-load-image filename). The new procedure should be very similar to palette-load-image; it will just use vectors instead of lists when reading and using the palette.

Problem 3: Comparing efficiency of loading files with list- and vector-based palettes

For list-based palettes, the bigger the palette is, the longer it will take to load the image. (Why?)

We've constructed a series of images saved using progressively larger palettes, from 2 colors all the way up to 32 colors. You can find these files in the following locations:

/home/rebelsky/glimmer/samples/palettes/flower-2.pal
/home/rebelsky/glimmer/samples/palettes/flower-4.pal
/home/rebelsky/glimmer/samples/palettes/flower-8.pal
/home/rebelsky/glimmer/samples/palettes/flower-16.pal
/home/rebelsky/glimmer/samples/palettes/flower-32.pal

You can find the original JPEG image in /home/rebelsky/glimmer/samples/palettes/flower.jpg. Load and show this image so you can see what it looks like.

Do the following experiments. Report your measurements and predictions in your writeup.

a. Using a clock, measure how many seconds it takes to load flower-2.pal using palette-load-image. (Be sure to write the answer down!) Do the same for flower-4.pal and flower-8.pal. Then, predict how long it will take to load flower-16.pal and flower-32.pal. If you have the patience, check your predictions by loading the files.

b. Do you expect loading the same images with vpalette-load-image to follow the same pattern? Why or why not?

c. Now, test your hypothesis. Repeat the instructions from part a using vpalette-load-image. Be sure to record your timings and your predictions.

d. You may have been surprised by what you found in part (c). To understand what is happening, try the following experiment.

Make a copy of the palette-load-image procedure and change the name to palette-load-image-without-lookup. Then, modify the procedure so that it does not use the palette to convert the color code to a color; instead, just use the color code itself as the color. (This will result in an all-black image, but that's OK---this is an experiment.) Finally, run your new procedure on all the different flower-n.pal files.

Now, try to explain what you found in part (c).

Important Evaluation Criteria

Our primary considerations will be the correctness of your code (that is, can it successfully load .pal files using the required techniques?) and the clarity with which you present your findings from Problem 3. Our secondary concerns will be the elegance and conciseness of your solutions.

Useful code

; +------------------------+------------------------------------------
; | Palette-Based Encoding |
; +------------------------+

;;; Procedure:
;;;   palette-encode-color
;;; Parameters:
;;;   palette, a nonempty list of RGB colors
;;;   color, an RGB color
;;; Purpose:
;;;   Determine a number that appropriately encodes color using
;;;   the given palette.
;;; Produces:
;;;   encoding, an integer
;;; Preconditions:
;;;   [No additional]
;;; Postconditions:
;;;   0 <= encoding < (length palette)
;;;   (rgb-distance color (list-ref palette encoding)) <=
;;;     (rgb-distance color (list-ref palette i))
;;;     for all i, 0 <= i < (length palette)
(define palette-encode-color
  (lambda (palette color)
    (list-index-of palette (rgb-closest color palette))))
 
;;; Procedure:
;;;   palette-decode-color
;;; Parameters:
;;;   palette, a nonempty list of RGB colors
;;;   code, an integer
;;; Purpose:
;;;   Converts an encoding (created by palette-encode-color) back
;;;   to a color.
;;; Produces:
;;;   color, an RGB color
;;; Preconditions:
;;;   0 <= code < (length palette)
;;;   code was created with (rgb-encode palette some-color)
;;; Postconditions:
;;;   color is close to some-color
(define palette-decode-color
  (lambda (palette code)
    (list-ref palette code)))

; +---------------------+---------------------------------------------
; | Reading and Writing |
; +---------------------+

;;; Procedure:
;;;   rgb-write
;;; Parameters:
;;;   color, an rgb color
;;;   port, the port to which to write the color
;;; Purpose:
;;;   Write the color to the specified port
;;; Preconditions:
;;;   port is open for writing.
;;; Postconditions:
;;;   When read with rgb-read, the data just appended to the file
;;;   will give color.

(define rgb-write
  (lambda (color port)
    (int-write (rgb-red color) port)
    (int-write (rgb-green color) port)
    (int-write (rgb-blue color) port)))

;;; Procedure:
;;;   rgb-read
;;; Parameters:
;;;   port, the name of an input port
;;; Purpose:
;;;   Reads an RGB color from the file.
;;; Produces:
;;;   color, an RGB color (or <eof>, if the port is at the end of
;;;     the file)
;;; Preconditions:
;;;   port is open for reading.
;;;   port is at the place in a file after which the next data was
;;;     written by rgb-write.
;;; Postconditions:
;;;   color is the color written by the call to rgb-write mentioned
;;;     in the preconditions.

(define rgb-read
  (lambda (port)
    (let* ((red (int-read port))
           (green (int-read port))
           (blue (int-read port)))
       (if (eof-object? blue) 
           blue
           (rgb-new red green blue)))))

(define int-write
  (lambda (int port)
    (write int port)
    (display " " port)))

(define int-read 
  (lambda (port)
    (read port)))

; +-------------------------+-----------------------------------------
; | Miscellaneous Utilities |
; +-------------------------+

;;; Procedure:
;;;   rgb-distance
;;; Parameters:
;;;   c1, an RGB color
;;;   c2, an RGB color
;;; Purpose:
;;;   Compute a "distance" between c1 and c2 that tells us how
;;;   well c1 estimates c2 (or vice versa).
;;; Produces:
;;;   distance, a real number
;;; Preconditions:
;;;   [No additional]
;;; Postconditions:
;;;   For any color c,
;;;     (rgb-distance c c) is 0 
;;;   If d is likely to be perceived as closer to c than e is to c, then
;;;     (rgb-distance c d) < (rgb-distance c e)
(define rgb-distance
  (lambda (c1 c2)
    (+ (square (- (rgb-red c1) (rgb-red c2)))
       (square (- (rgb-green c1) (rgb-green c2)))
       (square (- (rgb-blue c1) (rgb-blue c2))))))

;;; Procedure:
;;;   rgb-closer
;;; Parameters:
;;;   color, an RGB color
;;;   estimate1, an RGB color
;;;   estimate2, an RGB color
;;; Purpose:
;;;   Determines which of estimate1 and estimate2 is closer to color.
;;; Produces:
;;;   closer, an RGB color
;;; Preconditions:
;;;   [No additional]
;;; Postconditions:
;;;   closer is either estimate1 or estimate2
;;;   (rgb-distance color closer) <= (rgb-distance color estimate1)
;;;   (rgb-distance color closer) <= (rgb-distance color estimate2)
(define rgb-closer
  (lambda (color estimate1 estimate2)
    (if (<= (rgb-distance color estimate1) (rgb-distance color estimate2))
        estimate1
        estimate2)))

;;; Procedure:
;;;   rgb-closest
;;; Parameters:
;;;   color, an RGB color
;;;   colors, a list of RGB colors
;;; Purpose:
;;;   Determine the element of colors that is closest to
;;;   color
;;; Produces:
;;;   closest, an RGB color
;;; Preconditions:
;;;   colors is nonempty.
;;; Postconditions:
;;;   closest is an element of colors.
;;;   (rgb-distance color closest) <= (rgb-distance color c)
;;;     for all c in colors
(define rgb-closest
  (lambda (color colors)
    (let kernel ((remaining-colors (cdr colors))
                 (closest-so-far (car colors)))
      (if (null? remaining-colors)
          closest-so-far
          (kernel (cdr remaining-colors)
                  (rgb-closer color closest-so-far (car remaining-colors)))))))

;;; Procedure:
;;;   list-index-of
;;; Parameters:
;;;   lst, a list of values
;;;   val, a value
;;; Purpose:
;;;   Find an index of val in lst.
;;; Produces:
;;;   index, a number (or #f)
;;; Preconditions:
;;;   [No additional]
;;; Postconditions:
;;;   If there exists a value in lst equal to val, then
;;;     (list-ref lst index) equals val
;;;   If no such value exists, then
;;;     index is #f
(define list-index-of
  (lambda (lst val)
    (cond
      ((null? lst)
       #f)
      ((equal? val (car lst))
       0)
      (else
       (+ 1 (list-index-of (cdr lst) val))))))

Creative Commons License

Samuel A. Rebelsky, rebelsky@grinnell.edu

Copyright (c) 2007-8 Janet Davis, Matthew Kluber, and Samuel A. Rebelsky. (Selected materials copyright by John David Stone and Henry Walker and used by permission.)

This material is based upon work partially supported by the National Science Foundation under Grant No. CCLI-0633090. Any opinions, findings, and conclusions or recommendations expressed in this material are those of the author(s) and do not necessarily reflect the views of the National Science Foundation.

This work is licensed under a Creative Commons Attribution-NonCommercial 2.5 License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc/2.5/ or send a letter to Creative Commons, 543 Howard Street, 5th Floor, San Francisco, California, 94105, USA.