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

Assignment 14: A Procedure is Worth a Thousand Images


Due: 4 p.m., Friday, 16 November 2007

Summary: In this assignment, you will further explore the design of procedures for the Gimp to draw simple pictures and how you might parameterize those pictures.

Purposes: To give you further experience with files and with drawing. To help you think more about parameters.

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.02 2007F Assignment 14 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.

Preliminaries

One of the advantages of scripting simple images is that you can parameterize those images; drawing different images based on different choices. For example, a procedure that draws a face might take as parameters the “roundness” of the face, the color of the eyes, and the type of hair.

In some situations, it is equally interesting to make the parameters control aspects of the image, but in a less obvious way. For example, one might take a number as a parameter to the face-drawing routine and use that number to determine the type of hair. (The programmer might reveal the translation of number to hair type to the client, or may leave it as a secret.)

Assignment

In this assignment, you will write the latter kind of parameterized drawing procedure. In particular, you should write a procedure, (create-drawing filename val1 val2 val3), that stores in the file named by filename a drawing that is based on val1, val2, and val3, each of which is a number between 0 and 9.

This drawing files should have the form from the previous assignment. That is, each line of the file should be one of the basic drawing objects followed by color, brush (in some cases), and other parameters.

Once this procedure is available, you can then easily draw “random” pictures by selecting random values for the parameters.

We recommend that you choose a relatively straightforward category of image, such as a building or a cartoonish face.

Potentially Helpful Hints

There are a number of ways to convert numbers to aspects of your drawing. For example, if your drawing is of a face, you can use the first number to specify the width of the face with something like the following:

(let ((face-height 120)
      (face-width (+ 100 (* val1 4))))
  ...)

It is possible to use the number to select between different categorical values, such as eye colors.

(define eyecolors 
        (list color.blue color.green color.color.copper color.grey
              color.brown color.dim-grey color.steel-blue color.pale-blue
              color.dark-brown color.very-dark-brown))
(let ((eyecolor (list-ref eyecolors val2)))
  ...)

It is even possible to use one value to modify two different attributes. For example, in the following, val3 is used to select both the brush used to draw the hair and the kind of hair drawn. Since there are five different brushes and two kinds of hair, each different number gives a different kind of hair.

(define hair-brushes
  (list "Calligraphic Brush" "Circle (03)" "Circle (05)" 
        "Circle Fuzzy (03)" "Felt Pen"))
...
(set-brush (list-ref hair-brushes (modulo val3 5)))
(if (< val3 5)
    (draw-curly-hair)
    (draw-straight-hair))

Important Evaluation Criteria

Our first question will be whether or not your code will successfully generate 1000 different images. (That is, you should ensure that each different set of parameters will, in fact, generate a different image.)

Our second question will be how creatively you use these various parameters.

We will also consider common coding criteria, such as clarity, elegance, and efficiency.

Appendix: Reading and writing objects

To read and write object files, you may use your own solution to the previous assignment, or you may use the following lengthy but fairly straightforward solution.

(define image.read-object!
  (lambda (image port)
    (let* ((type (read port))
           (stroked? (list.contains? 
                       (list "empty ellipse" "empty circle" 
                             "empty rectangle" "empty square" 
                             "line")
                       type))
           (color (read port))
           (brush (if stroked? (read port) (envt.get-brush))))
      (cond 
        ; If we are at eof, return eof.
        ((eof-object? type) type)
        ; Otherwise, there is more to draw.
        (else
          ; Set the color and brush.
          (envt.set-fgcolor! (cname->rgb color))
          (envt.set-brush! brush)
          ; Select the shape type, read the remaining data, and draw it.
          (cond
            ((list.contains? (list "empty ellipse" "filled ellipse") type)
             (let* ((left (read port))
                    (top (read port))
                    (width (read port))
                    (height (read port)))
               (image.select-ellipse! image 
                                      selection.replace
                                      left top width height)
               (if stroked?
                   (image.stroke! image)
                   (image.fill! image))
               (image.select-nothing! image)))
            ((list.contains? (list "empty circle" "filled circle") type)
             (let* ((left (read port))
                    (top (read port))
                    (diameter (read port)))
               (image.select-ellipse! image 
                                      selection.replace
                                      left top diameter diameter)
               (if stroked?
                   (image.stroke! image)
                   (image.fill! image))
               (image.select-nothing! image)))
            ((list.contains? (list "empty rectangle" "filled rectangle") type)
             (let* ((left (read port))
                    (top (read port))
                    (width (read port))
                    (height (read port)))
               (image.select-rectangle! image 
                                        selection.replace
                                        left top width height)
               (if stroked?
                   (image.stroke! image)
                   (image.fill! image))
               (image.select-nothing! image)))
            ((list.contains? (list "empty square" "filled square") type)
             (let* ((left (read port))
                    (top (read port))
                    (side (read port)))
               (image.select-rectangle! image 
                                        selection.replace
                                        left top side side)
               (if stroked?
                   (image.stroke! image)
                   (image.fill! image))
               (image.select-nothing! image)))
            ((equal? type "line")
             (let* ((start-col (read port))
                    (start-row (read port))
                    (end-col (read port))
                    (end-row (read port)))
               (image.draw-line! image start-col start-row end-col end-row)))))))))


(define image.read-objects!
  (lambda (image filename)
    (let kernel ((in-port (open-input-file filename)))
      (cond 
        ; If we are at the end of the file, close the port and return the image.
        ((eof-object? (peek-char in-port))
         (close-input-port in-port)
         image)
        ; Otherwise, read the next object and recursively call the kernel.
        (else
         (image.read-object! image in-port)
         (kernel in-port))))))
        

(define list.contains?
  (lambda (lst val)
    (and (not (null? lst))
         (or (equal? (car lst) val)
             (list.contains? (cdr lst) val)))))

(define empty-ellipse.write
  (lambda (port color brush left top width height)
    (write "empty ellipse" port)
    (display " " port)    
    (write (if (rgb? color) (rgb->cname color) color) port)
    (display " " port)
    (write brush port)
    (display " " port)
    (write left port)
    (display " " port)
    (write top port)
    (display " " port)
    (write width port)
    (display " " port)
    (write height port)
    (newline port)))

(define filled-ellipse.write
  (lambda (port color left top width height)
    (write "filled ellipse" port)
    (display " " port)    
    (write (if (rgb? color) (rgb->cname color) color) port)
    (display " " port)
    (write left port)
    (display " " port)
    (write top port)
    (display " " port)
    (write width port)
    (display " " port)
    (write height port)
    (newline port)))

(define empty-circle.write
  (lambda (port color brush left top diameter)
    (write "empty circle" port)
    (display " " port)    
    (write (if (rgb? color) (rgb->cname color) color) port)
    (display " " port)
    (write brush port)
    (display " " port)
    (write left port)
    (display " " port)
    (write top port)
    (display " " port)
    (write diameter port)
    (newline port)))

(define filled-circle.write
  (lambda (port color left top diameter)
    (write "filled circle" port)
    (display " " port)    
    (write (if (rgb? color) (rgb->cname color) color) port)
    (display " " port)
    (write left port)
    (display " " port)
    (write top port)
    (display " " port)
    (write diameter port)
    (newline port)))

(define empty-rectangle.write
  (lambda (port color brush left top width height)
    (write "empty rectangle" port)
    (display " " port)    
    (write (if (rgb? color) (rgb->cname color) color) port)
    (display " " port)
    (write brush port)
    (display " " port)
    (write left port)
    (display " " port)
    (write top port)
    (display " " port)
    (write width port)
    (display " " port)
    (write height port)
    (newline port)))

(define filled-rectangle.write
  (lambda (port color left top width height)
    (write "filled rectangle" port)
    (display " " port)    
    (write (if (rgb? color) (rgb->cname color) color) port)
    (display " " port)
    (write left port)
    (display " " port)
    (write top port)
    (display " " port)
    (write width port)
    (display " " port)
    (write height port)
    (newline port)))

(define empty-square.write
  (lambda (port color brush left top side)
    (write "empty square" port)
    (display " " port)    
    (write (if (rgb? color) (rgb->cname color) color) port)
    (display " " port)
    (write brush port)
    (display " " port)
    (write left port)
    (display " " port)
    (write top port)
    (display " " port)
    (write side port)
    (newline port)))

(define filled-square.write
  (lambda (port color left top side)
    (write "filled square" port)
    (display " " port)    
    (write (if (rgb? color) (rgb->cname color) color) port)
    (display " " port)
    (write left port)
    (display " " port)
    (write top port)
    (display " " port)
    (write side port)
    (newline port)))

(define line.write
  (lambda (port color brush start-col start-row end-col end-row)
    (write "line" port)
    (display " " port)    
    (write (if (rgb? color) (rgb->cname color) color) port)
    (display " " port)
    (write brush port)
    (display " " port)
    (write start-col port)
    (display " " port)
    (write start-row port)
    (display " " port)
    (write end-col port)
    (display " " port)
    (write end-row port)
    (newline port)))

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.