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

Laboratory: Storing Images with Pixmaps


Summary: In this laboratory, you will further explore the use of pixmaps to represent an image.

Preparation

Add the preliminary versions of rgb.write and rgb.read to your definitions pane.
;;; 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: Reading and Writing Simple Colors

a. Write the three primaries (red, green, blue), the three secondaries (cyan, magenta, and yellow), and black and white to the file some-colors.txt. That is, open a port to that file, write the colors, and then close the port.

b. Open some-colors.txt and see if the numbers make sense.

c. Using rgb.read, read back all the colors from the file, and verify that you have the same colors.

Exercise 2: Reading and Writing Components

a. Update the definitions of rgb.write and rgb.read so that they write each component separately.

(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 primaries, the three secondaries, and black and white to the file some-colors.txt.

c. Open some-colors.txt and see if the numbers make sense.

d. Using rgb.read, read back all the colors from the file, and verify that you have the same colors.

Exercise 3: Writing Images By Hand

a. Create an interesting 4x3 image. If you don't want to figure out an interesting image yourself, you can use the following set of commands.

(define canvas (image.new 4 3))
(image.set-pixel! canvas 0 0 (rgb.new 0 0 255))
(image.set-pixel! canvas 0 1 (rgb.new 16 16 240))
(image.set-pixel! canvas 0 2 (rgb.new 32 32 224))
(image.set-pixel! canvas 1 2 (rgb.new 48 48 208))
(image.set-pixel! canvas 2 2 (rgb.new 64 64 192))
(image.set-pixel! canvas 3 2 (rgb.new 80 80 176))
(image.set-pixel! canvas 3 1 (rgb.new 96 96 160))
(image.set-pixel! canvas 3 0 (rgb.new 112 112 144))

b. Write that image to a file by hand, using code similar to that in the reading. Here's a start.

> (define pixmap (open-output-file "image1.pixmap"))
> (rgb.write (image.get-pixel canvas 0 0) pixmap)
> (rgb.write (image.get-pixel canvas 1 0) pixmap)
> (rgb.write (image.get-pixel canvas 2 0) pixmap)
> (rgb.write (image.get-pixel canvas 3 0) pixmap)
> (rgb.write (image.get-pixel canvas 0 1) pixmap)
> (rgb.write (image.get-pixel canvas 1 1) pixmap)
...
...
...
> (close-output-port pixmap)

c. What do you expect the file to contain?

d. Open the file you've created and verify that it has the form you expected.

Exercise 4: Writing Images with image.write-pixmap

a. Add image.write-pixmap to your definitions window. You can find the definition of this procedure at the end of the lab.

b. Change the image from the previous exercise, using a command like

> (image.map! rgb.complement canvas)

c. Using image.write-pixmap, write the modified image to the file image2.pixmap.

d. What values do you expect that file to have?

e. Verify that the file contains the values you were expecting.

Exercise 5: Reading Images

a. Add image.read-pixmap to your definitions window. You can find the definition of this procedure at the end of the lab.

b. Clear your current canvas. In the GIMP, you can select all and then delete. You can also use the following commands, that do exactly the same thing.

> (image.select-all! canvas)
> (image.clear-selection! canvas)
> (envt.update-displays!)

c. Using image.read-pixmap!, fill in the canvas from image1.pixmap

d. Clear the canvas again.

e. Using image.read-pixmap!, fill in the canvas from image2.pixmap

Exercise 6: Reading Into Other Size Images

a. Create four new images --- canvas6x2, canvas3x4, canvas2x6, and canvas12x1 --- whose sizes match their names.

b. What do you expect to have happen if we read from image1.pixmap into each of these images?

c. Check your answer experimentally.

d. Create two new images, canvas20x1 and canvas3x3, whose sizes match their names.

e. What do you expect to have happen if we read from image1.pixmap into each of these images?

f. Check your answer experimentally.

Exercise 7: Including the Image Size

As the previous exercise suggests, one problem with the way we've chosen to store images in files is that we lose the size of the image. But without the size, the representation is not sufficient to restore the image. What can we do? We can first write the width and height of the image to the file.

a. Rewrite image.write-pixmap so that it first writes the size of the image to the file.

b. Rewrite image.read-pixmap! so that it verifies that the image stored in the file is the same size as the given image.

c. Write a new procedure, (image.read-pixmap filename), the takes as a parameter only a file name, reads the image size from the file, creates a new image of the appropriate size, and fills in the image from the file.

Exercise 8: Writing Pixmaps By Hand

a. Create a file, image3.pixmap, with the following contents.

  4
  3
  255 0 0
  255 128 0
  128 255 0
  0 255 0
  128 0 128
  128 128 128
  128 192 128
  128 255 128
  0 0 255
  128 128 255
  192 192 255
  255 255 255

b. What do you expect to have happen if you load that file with your newly-written image.read-pixmap.

c. Check your answer experimentally.

For Those With Extra Time

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 pixmap file into the 4x3 section starting at 5,7, we might write

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

Your procedure should verify that the width and height stored in the pixmap file match the width and height of the region.

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

Pick a reasonably small image of your choice and write it to a file with image.write-pixmap

See what effects you can obtain by reading that file into an image of a very different shape.

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)))
      (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:
;;;   image, an image
;;;   filename, a string
;;; Purpose:
;;;   Read pixmap data from the specified file, storing the results
;;;   into image.
;;; Produces:
;;;   [Nothing; called for the side effect]
;;; Preconditions:
;;;   filename names a file.
;;;   That file was created by image.write-pixmap.
;;;   That file contains (image.width image)x(image.height image)
;;;     colors.
;;; Postconditions:
;;;   image has been updated to contain the image described by file.
(define image.read-pixmap!
  (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)))
            (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))))))))))

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.