Functional Problem Solving (CSC 151 2014F) : Readings
Primary: [Front Door] [Schedule] - [Academic Honesty] [Disabilities] [Email] - [FAQ] [Teaching & Learning] [Grading] [Rubric] - [Calendar]
Current: [Assignment] [EBoard] [Lab] [Outline] [Reading]
Sections: [Assignments] [EBoards] [Examples] [Handouts] [Labs] [Outlines] [Readings]
Reference: [Setup] [VM] [Errors] - [Functions A-Z] [Functions By Topic] - [Racket] [Scheme Report (R5RS)] [R6RS] [TSPL4]
Related Courses: [Davis (2013F)] [Rebelsky (2014S)] [Weinman (2014F)]
Misc: [Submit Questions] - [SamR] [Glimmer Labs] [CS@Grinnell] [Grinnell] - [Issue Tracker (Course)]
Summary: We explore the basic operations for creating and saving images.
The focus of this course is image making and manipulation. That means
that something like “images” will be one of the basic data
types we explore. In the course, when we say “image”,
we essentially mean “an image that Gimp knows how to work with”.
Right now, we know only two procedures for dealing with images.
image-show shows an existing image and
drawing->image takes a drawing and creates
a new image. But what if we want to do more? In this reading, we
explore some of the other basic image operations.
Each time you encounter a new type in Scheme (or any programming langauge), you should familiarize yourself with a variety of characteristics of the type. Here are some questions we tend to ask ourselves when we encounter a new type, along with some answers for images?
drawing->image. We'll soon see that you
can create a new, “blank” image and that you can
load an image from a file. We'll discover other ways to create
images throughout the semester.
image-show. But we can also save them
to files, transform them in a variety of ways, and even render
new drawings on top of them.
As we explore new types, we also often write programs to help us better understand the type.
image-show
As you saw in the early explorations of the drawing type, drawings
are conceptual. They need to be rendered. Some of us can think
well in the conceptual space, but most of us need to be able to see
the images we create. The image-show procedure
lets us see images.
Recently, you may have learned about procedures with “side
effects” - procedures that we call not to compute a value,
but to change something. Note that image-show
is called primarily for its side effect - it adds a window onto
the screen. The image is there already, and we don't change the
image, but we do change the state of our computer system.
Note that image-show introduces a potentially
problematic relationship with Gimp. If you show an image (or, more
precisely, if Racket tells Gimp to show an image), then Gimp has some
power over the image. In particular if you close an image
in Gimp, you may not be able to access it any more in Scheme.
For example,
>(define canvas (drawing->image my-masterpiece 400 300))>canvas1>(image-show canvas)1; A window showing our masterpiece appears.>(image-show canvas)1; Another window showing our masterpiece appears. : We close the second window.>(image-show canvas)1; Yet another window showing our masterpiece appears. ; We close both windows.>(image-show canvas)image-show: expects type <image> for 1st parameter, given 1 in (image-show 1)
image-new
We've seen one way to create images - we can use
drawing->image. But what if we want a blank
image? (Why would we want a blank image? We'll soon learn some ways
to manipulate blank images.) One option is to use
drawing->image, but with a really small white
drawing. But that seems a but of a kludge. Fortunately, there is
a image-new procedure.
image-new takes two parameters, a width and
a height, and creates a blank image of the specified size.
That image is not yet shown. You will still have to show it
with image-show.
Are there other reasons to use image-new rather
than just just drawing->image. There's a
subtle one. drawing->image always creates the
drawing on a white canvas. In contrast, image-new
creates an image the same color as the current Gimp background color.
We can simulate this effect by using a large colored rectangle, but sometimes
it's easier to just have a procedure that does what we want.
drawing-render!
Right now, we know that if we have a drawing, we can easily convert it
to an image with drawing->image. But what if
we want to add a drawing to an existing image -
whether an image created by image-new (which can
be a different color than white), an image created by an earlier to
call to drawing->image, or an image created in
another way? The procedure (
renders a drawing on an existing image. This procedure does not create
a new drawing. It does not modify the drawing. But it modifies the
image by adding to it.
drawing-render!
drawing image)
Scheme programmers have some conventions to remind ourselves about
certain kinds of side effects. In particular, when we write a
procedure that modifies one of its parameters, we usually end its name
with an exclamation point. In this case, to reminder ourselves that
drawing-render! modifies the image, we include an
exclamation point in its name. (Yes, it's not always obvious what
parameter is being modified. That's one reason we also document
procedures.)
Since you are likely to want to use the image (e.g., to render
something else, to show it, or to save it),
drawing-render! returns the image.
image-load
Up to now, we've created all of our drawings “from scratch”
(or at least from unit squares and circles). But we may want to
work with existing images. You can make some images in Gimp, and you
can load images in Gimp. But we want to be able to work programmatically.
Hence, Mediascheme includes an image-load function.
image-load takes one parameter, a string that
gives the full path to an image file, loads that file, and returns
an integer that we can use to identify the image.
Does image-load have side effects? That's
debatable. It doesn't change the underlying file. But it does
add an image to our environment. In that sense, it's much like
drawing->image. Hence, we do not end its
name with an exclamation point.
image-save
It's useful to be able to load images so that we can manipulate them.
It's even more useful to be able to save images that we've
created. The procedure (
saves an image to a specified file. You should provide the full path
name to the file, surrounding it by quotes. For example, you might
use something like image-save
image filename)"/home/student/images/masterpiece.png".
The suffix you give to the file name determines the type of file
that is saved - jpg, gif, png, and so forth.
Note that you can use image-save with any image
we make, whether it's created with drawing->image,
image-new, image-load, or
anything else we learn.
image-width and image-height
We're almost done with the image procedures. All that's left is to
be able to get some basic information on an image. The most basic
information we might want is the width and height of the image,
which we can get with ( and
image-width
image)(.
image-height
image)
Are there others? You may recall that for drawings, we were able to get their left edge, their top edge, their width, their height, their type (ellipse, rectangle, or group), and, sometimes, their color. It doesn't make sense to ask for the top and left edge of an image. Those are always 0. It's not immediately clear what the “type” of an image is. And typical images have many colors, so we won't ask about the overall color of the image. (However, we'll soon be able to ask about the color at individual points in the image.) It looks like width and height will suffice, at least for now.
How might we use all of these procedures? Here's a simple example: We'll take a drawing and rescale it to fit on the image. (In the lab, you'll have an opportunity to think about shifting the drawing, too.)
So, suppose we have a drawing that's w units wide and h units high, and we want to make it fit onto an image that's W units wide and H units high. What should we do? A bit of simple reflection and some math suggests that we should scale the width by W/w, and the height by H/h.
How do we get those four values? With drawing-width,
drawing-height, image-width,
and image-height.
;;; Procedure:
;;; scare! (SCale And REnder)
;;; Parameters:
;;; drawing, a drawing
;;; image, an image
;;; Purpose:
;;; scale the drawing to the size of the image and then render it
;;; on the image
;;; Produces:
;;; image, the updated image
;;; Preconditions:
;;; drawing and image both have a positive width and height
;;; Postconditions:
;;; An appropriately scaled version of drawing appears on image
(define scare!
(lambda (drawing image)
(drawing-render!
(hscale-drawing (/ (image-width image) (drawing-width drawing))
(vscale-drawing (/ (image-height image) (drawing-height drawing))
drawing))
image)))
The scare! procedure let us explore three of the
new image functions: drawing-render!,
drawing-width, and drawing-height.
What about the file operations?
Here are a collection of procedures that, when taken together, add a small icon to the bottom-right corner of an existing image and save it in a new file.
;;; Procedure:
;;; claim
;;; Parameters:
;;; source, a string that names an existing image file
;;; target, a string that names an image file
;;; Purpose:
;;; Add a little "ownership" symbol to the image in source,
;;; saving the result in target.
;;; Produces:
;;; The name of the target file.
;;; Preconditions:
;;; You have permission to access the source file (in the computer sense,
;;; the legal sense, and the ethical sense).
;;; You have permission to write the target file.
;;; The target file does not yet exist.
;;; Postconditions:
;;; target now exists.
;;; target contains an image much like source.
;;; target contains a small icon to distinguish itself from source.
(define claim
(lambda (source target)
(image-save (image-claim! (image-load source)) target)))
;;; Procedure:
;;; image-claim!
;;; Parameters:
;;; source, an image
;;; Purpose:
;;; Add a little "ownership" symbol to source.
;;; Produces:
;;; source, the same image, now modified
;;; Preconditions:
;;; [No additional]
;;; Postconditions:
;;; source now contains a small icon to distinguish itself from
;;; the previous version.
(define image-claim!
(lambda (source)
(drawing-render!
(hshift-drawing
(- (image-width source) 5)
(vshift-drawing
(- (image-height source) 3)
claim-icon))
source)))
;;; Name:
;;; claim-icon
;;; Type:
;;; drawing
;;; Contents:
;;; A little icon we use to "claim" images.
(define claim-icon
(drawing-group
(scale-drawing 50 (recolor-drawing "red" drawing-unit-circle))
(scale-drawing 40 drawing-unit-circle)
(scale-drawing 30 (recolor-drawing "red" drawing-unit-circle))
(scale-drawing 25 drawing-unit-circle)))
Note that claim! takes advantage of the way
Scheme evaluates expressions. Since the image-load
call is innermost, it evaluates that first. Hence, the
(image-save (image-claim! (image-load source)) target)))
gets interpreted as “First load the source image, then claim the image
using image-claim!, then save it in the
target file.”
The reading gives you four questions you should ask when learning a new type, but they are also useful for comparing and reviewing types.
Compare and contrast the answers to the four questions for an image type with the answers you would give for the drawing type.