Fundamentals of Computer Science I: Media Computing (CS151.02 2007F)

Laboratory: Representing Images with Binary Data


Summary: In this laboratory, you will explore ways to store images more efficiently by representing the data in an encoded form rather than in a human-readable form.

Preparation

a. Review the lab from the previous day.

b. Add to your definitions pane the revised procedures created during that lab, image.write-pixmap and image.read-pixmap. If you don't have that code, or your don't trust your code, you can find it at the end of this lab.

c. Add to your definitions pane the file.size and image.random-fill! procedures, also available at the end of this lab.

d. Add to your definitions pane the first versions of rgb.write and rgb.read.

;;; 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)
    (write color port)
    (newline 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)
    (read port)))

Exercises

Exercise 1: File Sizes

a. Create a new 10x10 image named all-white, all of whose pixels are white.

b. Using image.write-pixmap, save that image to a file named all-white.pixmap.

c. How many characters do you expect to be in that file?

d. Check your answer experimentally, using file.size. If you're not sure about why you got the number you did, you may want to open all-white.pixmap to look at what form it has.

e. Make a new 10x10 image, all-black, in which all of the pixels are black. (An easy way to do this is to first set the background color to black, and then create the new image.)

f. Using image.write-pixmap, save that new image to all-black.pixmap.

g. How many characters do you expect to be in that file?

h. Check your answer experimentally, using file.size. If you're not sure about why you got the number you did, you may want to open all-black.pixmap to look at what form it has.

i. Create another 10x10 image and name it random-colors. Fill the image with random colors, using image.random-fill!.

j. Using image.write-pixmap, save that image to random-colors.pixmap.

k. How many characters do you expect to be in that file?

l. Check your answer experimentally, using file.size. If you're not sure about why you got the number you did, you may want to open random-colors.pixmap to look at what form it has.

m. If you got different file sizes for the three files, explain why.

Exercise 2: File Sizes, Revisited

a. Replace the definitions of rgb.write and rgb.read with the component-based human-readable versions below.

(define rgb.write
  (lambda (color port)
    (write (rgb.red color) port)
    (display " " port)
    (write (rgb.green color) port)
    (display " " port)
    (write (rgb.blue color) port)
    (newline port)))

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

b. Write the three images you created in Exercise 1 to three files, new-all-white.pixmap, new-all-black.pixmap, and new-random-colors.pixmap.

c. Predict the size of each file.

d. Check your predictions using file.size.

e. Using these results, predict the size of a file for a 10x20 image of random pixels.

f. Check your answer experimentally.

Exercise 3: Writing and Reading Integers as Data

a. Add the definitions of int.write, int.read, and int.peek to your definitions pane.

(define int.write
  (lambda (int port)
    (write-char (integer->char (+ int 1)) port)))

(define int.read
  (lambda (port)
    (let ((ch (read-char port)))
      (if (eof-object? ch)
          ch
          (- (char->integer ch) 1)))))

(define int.peek
  (lambda (port)
    (let ((ch (peek-char port)))
      (if (eof-object? ch)
          ch
          (- (char->integer ch) 1)))))

b. Using int.write, write five bytes of your choice to a file named data. For example,

> (define dataport (open-output-file "data"))
> (int.write 6 dataport)
> (int.write 17 dataport)
> (int.write 64 dataport)
> (int.write 32 dataport)
> (int.write 42 dataport)
> (close-output-port dataport)

c. Predict the size of that file.

d. Using file.size, determine the size of the file.

e. Read the data back from the file using int.read.

> (define inport (open-input-file "data"))
> (int.read inport)
6
...
> (int.read inport)
<eof>
> (close-input-port inport)

f. What do you expect to have happen if you read the data back from the file using read-char?

g. Check your answer experimentally.

> (define inport (open-input-file "data"))
> (read-char inport)
...
> (read-char inport)
<eof>
> (close-input-port inport)

h. What do you expect to have happen if you read the data back from the file using read?

i. Check your answer experimentally.

> (define inport (open-input-file "data"))
> (read inport)
...
> (read inport)
<eof>
> (close-input-port inport)

j. What do your results suggest about reading values from a file created by int.write?

Exercise 4: Reading Characters as Integers

a. As you may recall, the file /home/rebelsky/glimmer/samples/hi.txt contains the text Hi There!. What do you expect to have happen if you use int.read to read from that file?

b. Check your answer experimentally.

> (define inport (open-input-file "/home/rebelsky/glimmer/samples/hi.txt"))
> (int.read inport)
...
> (int.read inport)
<eof>
> (close-input-port inport)

Exercise 5: Colors as Data

As you may recall, we suggested in the reading that it is useful to represent colors as byte sequences, rather than in human-readable form. To do so, we need to make some changes to our code.

a. List the changes you expect to have to make in order to use bytes, rather than text, for storing our images.

b. Replace the definitions of rgb.write and rgb.read to the ones that use int.write and int.read.

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

(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)))))

c. Update the definition of image.write-pixmap to (i) drop any calls to newline and (ii) use int.write to write the width and height.

d. Update the definition of image.read-pixmap to use int.read to read the width and height.

e. Using these updated versions, write and then read a small sample image. (Note that your old saved files won't work, because we changed the method for storing the data.)

Exercise 6: File Sizes, Re-Revisited

In case you've forgotten, the point of all of this work was to write our images using smaller files. Let's see if our strategy worked, using the same examples we used before.

a. Create three images, each 10x10, the first filled with all white pixels, the second with all black pixels, the third with random pixels.

b. Write the three images to three files, newer-all-white.pixmap, newer-all-black.pixmap, and newer-random-colors.pixmap.

c. Predict the size of each file.

d. Check your predictions using file.size.

e. Using these results, predict the size of a file for a random 10x20 image.

f. Check your answer experimentally.

g. Reflect on what you learned from all of today's work.

For Those With Extra Time

The following extra exercises are repeated from the laboratory on pixmaps, as it is not likely that many students had the opportunity to do them in the first lab.

Extra 1: Reading Sections of Images

Right now, when you read a pixmap into a larger image, it fills in the larger image, row-by-row, until you run out of colors. Instead, we could specify the section of the larger image to fill in. What should we specify? Presumably, the left edge, top edge, width, and height of the region to fill in.

Write a procedure, (region.read-pixmap! image filename left top width height), that reads from a pixmap into a section of an image.

For example, to read from a recently created image file into the 4x3 section starting at 5,7, we might write

> (region.read-pixmap! big-canvas "image3.pixmap" 5 7 4 3)

Extra 2: Writing Sections of Images

If we're willing to read pixmaps into sections of images, we might as well provide the converse operations: Writing pixmaps from sections of images.

Write a procedure, (region.write-pixmap image file left top width height), that writes a region of an image to the specified file.

Explorations

As you've seen, the computer is willing to treat almost any file as data. That is, even if you've written text, you can read that text back as numbers.

Here's a version of image.read-pixmap (renamed appropriately), that reads an image without paying attention to the size.

(define image.read-pixels!
  (lambda (image filename)
    (let ((width (image.width image))
          (height (image.height image))
          (port (open-input-file filename)))
      (let kernel ((col 0)
                   (row 0))
         (cond
           ((> (+ col row) (+ (- width 1) (- height 1)))
            (close-input-port port)
            image)
           ((eof-object? (peek-char port))
            (close-input-port port)
            (throw "image.read-pixels!: Premature end of file."))
           ((>= col width)
            (kernel 0 (+ row 1)))
           (else
            (image.set-pixel! image col row (rgb.read port))
            (kernel (+ col 1) row)))))))

Pick a few interesting text files (e.g., papers you've written, programs you've written for this class, assorted Web pages ) and turn them into images.

Notes

Useful Procedures

;;; Procedure:
;;;   image.write-pixmap
;;; Parameters:
;;;   image, an image
;;;   filename, a string
;;; Purpose:
;;;   Writes the pixmap information on image to the file.
;;; Produces:
;;;   [Nothing; Called for the side effect.]
;;; Preconditions:
;;;   filename is a valid file name.
;;; Postconditions:
;;;   The file named by filename now contains a sequence of integers,
;;;   one for each RGB color in image.
(define image.write-pixmap
  (lambda (image filename)
    (let ((width (image.width image))
          (height (image.height image))
          (port (open-output-file filename)))
      (write width port)
      (newline port)
      (write height port)
      (newline port)
      (let kernel ((col 0)
                   (row 0))
         (cond
           ((>= row height)
            (close-output-port port)
            image)
           ((>= col width)
            (kernel 0 (+ row 1)))
           (else
            (rgb.write (image.get-pixel image col row) port)
            (kernel (+ col 1) row)))))))

;;; 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))))))))))

;;; Procedure:
;;;   image.random-fill!
;;; Parameters:
;;;   image, an image
;;; Purpose:
;;;   Fills the image with a random set of colors.
;;; Produces:
;;;   [Nothing; called for the side effect]
;;; Preconditions:
;;;   image identifies a valid image.
;;; Postconditions:
;;;   Every pixel in the image has an unpredictable color.
(define image.random-fill!
  (lambda (image)
    (region.compute-pixels! image 0 0 
                            (- (image.width image) 1) (- (image.height image) 1)
                            (lambda (pos)
                              (rgb.new (random 255) (random 255) (random 255))))))

;;; Procedure:
;;;   file.size
;;; Parameters:
;;;   filename, a string
;;; Purpose:
;;;   Determines the number of characters in the file.
;;; Produces:
;;;   s, an integer.
;;; Preconditions:
;;;   filename names a valid file.
;;; Postconditions:
;;;   s is the size of file.  That is,
;;;    s read-char commands from file will succeed.
;;;    The s+1st read from file will fail.
(define file.size
  (lambda (fname)
    (file.size-kernel (open-input-file fname) 0)))

(define file.size-kernel
  (lambda (source size)
    (cond
      ((eof-object? (read-char source))
       (close-input-port source)
       size)
      (else
       (file.size-kernel source (+ size 1))))))

Creative Commons License

Samuel A. Rebelsky, rebelsky@grinnell.edu

Copyright 2007 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.