Laboratory: Encoding Images with Color Palettes Summary: In this laboratory, you will explore the use of palettes to more efficiently store images and the effects the approximations such palettes require have on images.
Preparation a. This laboratory requires a wide variety of procedures, some introduced in this reading, some introduced in previous readings, and some of which you've written. We have reproduced all of those procedures at the end of this reading. Copy them into your definitions pane (or your library). b. We'll do some work with reading and writing images. Create a 10x10 image called canvas for such experimentation and fill it with random colors. (define canvas (image-show (image-new 10 10))) (image-random-fill! canvas) You'll probably want to zoom in on this image. c. We'll also do some work with an existing image, which we'll call picture. You might use our long-suffering kitten. (define picture (image-show (image-load "/home/rebelsky/MediaScheme/Images/kitten.png"))) You can also use a public-domain photograph of a butterfly, taken from http://public-photo.net/displayimage-2139.html. (define picture (image-show (image-load "/home/rebelsky/MediaScheme/Images/butterfly.png")))
Exercises
Exercise 1: Experiments with the Simple Encoding As you may recall from the reading, rgb->approx approximates an RGB color by converting it to a number between 0 and 255 that represents a nearby color. Another function, approx->rgb converts the number to that nearby color. a. Pick five colors and ensure that rgb->approx converts each to a number between 0 and 255. Try to pick colors that are not at the extremes (that is, that have components whose values are not just 0 and 255). You may find it easiest to do this process with map. > (map (compose rgb->approx color-name->rgb) (list "red" ....)) b. Convert those approximations back to RGB colors with approx->rgb. Then, compare those values to the original colors. You might want to try something like the following: > (define colors (map color-name->rgb (list "red" ...)) > (map rgb->string colors) > (map rgb->color-name colors) > (define encodings (map rgb->approx colors)) > (define approximations (map approx->rgb encodings)) > (map rgb->string approximations) > (map rgb->color-name approximations) c. What do you notice about the component values of these various colors? d. Note that we can approximate an RGB color by encoding it and then decoding it. Write a procedure, rgb-approximate, that does just that. (Note that rgb-approximate takes as input an RGB color and returns as output an RGB color. You should use rgb->approx and approx->rgb to do the approximation.) e. Using image-variant, apply rgb-approximate to canvas (which should contain random colors). > (define approximated (image-variant canvas rgb-approximate)) > (image-show approximated) f. How good is the approximation? What could you do to improve the approximation? g. Using image-variant, apply rgb-approximate to picture. h. Does this experiment change your perspective on the success of the approximation?
Exercise 2: Rainbow Palettes As you may recall, palette-encode-color encodes a color using a color palette (a list of colors) and palette-decode-color decodes a color using a palette. a. Add the following definition of a simple color palette to your definitions pane. (define rainbow-plus (map color->rgb (list "red" "orange" "yellow" "green" "blue" "indigo" "violet" "black" "white"))) b. Using that palette, encode the same colors you encoded in exercise 1. Are these encodings better or worse? Why? c. Write a procedure, (palette-select palette color), that selects the color in palette used to represent color. (Your procedure will make use of palette-encode-color and palette-decode-color.) d. See what happens if you apply that procedure to canvas with (image-variant canvas (lambda (color) (palette-select rainbow-plus color))) What does that tell you about the usefulness of this palette? e. See what happens if you apply that procedure to picture. What does that tell you about the usefulness of this palette?
Exercise 3: Palettes from Images Clearly, using a small palette of fairly restricted colors is not our best approach for approximating an image. What can we do instead? We can choose colors that already appear in an image. Consider the following procedure. a. What do you expect the result to look like if you use image-sample-colors to sample colors from picture? b. Check your answer experimentally. c. What do you expect the result to look like if you use image-sample-colors to sample colors from canvas? About how many colors do you expect to be in the list? d. Check your answer experimentally. e. As you've probably noticed, the number of colors that image-sample-colors returns depends on the size of the image it is working with. Why? Because it samples every 411th pixel. We could, of course, change the 411 to some smaller number when we were sampling smaller images, but it would be painful to have to change image-sample-colors each time we wanted to work with a different image. Instead, we could add a parameter, n to represent the number of samples to take (i.e, the size of the palette). We could then use n to compute the distance between samples. (That is, replace 411 with a formula that involves width, height, and n so that we get approximately n colors selected.) Make those changes.
Exercise 4: Palettes from Images, Revisited a. Using your modified version of image-sample-colors, create palettes of size 2, 4, 8, and 16 from picture. > (define palette-2 (image-sample-colors picture 2)) > (define palette-4 (image-sample-colors picture 4)) > (define palette-8 (image-sample-colors picture 8)) > (define palette-16 (image-sample-colors picture 16)) b. Here's a procedure, (palette-map palette image), which maps a palette onto an image by converting each color in the image to the closest color in the palette. Using that procedure, map each of your palettes onto canvas. (Yes, although we created the palettes with picture, you should see what happens with canvas.) d. As you may recall, image-variant can take a while with larger images. Let's see the effect of palette size on the amount of time it takes to apply a palette to picture First, map palette-2 onto picture and, using a clock or by counting one-Mississippi two-Mississippi, determine approximately how many seconds it takes. Next, map palette-4 onto picture and, using a clock, determine approximately how many seconds it takes. Finally, predict how long it will take to map palettes of size 8 and 16. If you have time, check those results experimentally. e. As you may have noted, the bigger the palette, the slower it is to compute a variant. At the same time, it is likely that the bigger the palette, the better the approximation. To test this hypothesis, we'll need a relatively small image to work with. However, we want a sensible image, rather than a random image. Pick a 64x64 square from picture, copy it, create a new 64x64 image called selection, and paste into that image. f. Map palette-16 onto selection. Do you consider that a reasonable approximation? g. Create a new palette, selection-16, by selecting 16 colors from selection, rather than from picture. Then, map that palette onto selection. Is the approximation better? Worse?
Exercise 5: Storing with Palettes Although we've been exploring palettes by looking at what happens when we use them interactively with images, most frequently palettes are used when saving and restoring files. a. Write (palette-save-image palette image filename), which saves a palettized pixmap of an image to a file. The procedure should use the following format for the file: The width of the image. The height of the image. The colors in the image, represented as indices into the palette. In writing this procedure, you may want to use image-write-pixmap as a pattern for how to write an image to a file. b. Write (palette-load-image palette filename), which loads the palettized pixmap of an image from a file, assuming that the image was stored using the same palette. In writing this procedure, you may want to use image-read-pixmap as a pattern for how to read an image from a file.
For Those With Extra Time
Extra 1: More Efficient Encoding One disadvantage of the current version of palette-encode-color is that it ends up scanning the list of colors twice: once to find the closest color and once to find the index of that color. We might instead write a procedure that does the two activities simultaneously. The following provides one implementation of that technique. a. Confirm, using a variety of examples, that it seems to work correctly. b. Explain, in your own words, how this procedure works. c. Rewrite palette-encode-color to use rgb-index-of-closest. d. Rerun one of your timing tests to determine whether this change had any effect.
Extra 2: Storing Palettes Reproducing the saved image accurately requires that our image file include not just the indices into the palette, but also the palette itself. Rewrite palette-save-image and palette-load-image to use this technique. You may find the following procedures helpful.
Extra 3: Better Palettes Some disadvantages of the current mechanism for selecting palettes based on an image are (1) it may result in duplicate colors; (2) it may contain a variety of close colors and, but doing so, approximate some colors better than others. An alternative is to over-sample the image, getting about twice as many colors as you want in the palette, and then to hone the palette, removing or averaging nearby colors. a. Write a procedure, (palette-slightly-improve palette) that improves the palette by replacing one pair of nearby colors with their average. b. Write a procedure, (palette-improve palette n), that repeatedly calls palette-slightly-improve until there are n colors left. c. Determine experimentally whether this technique makes better palettes.
Explorations
Exploration 1: Switching Palettes You can obtain some very interesting effects by encoding an image using one palette and then decoding it using another. a. Define two palettes, each with sixteen colors. Name them pal and ette. b. Rather than storing the image to a file and then reloading it using the new palette, we will use image-variant to build a new version of the image. > (image-variant (lambda (color) (palette-decode palette (palette-encode palette color))) canvas
Notes
Useful Procedures ; +--------------------------+---------------------------------------- ; | Approximating RGB Colors | ; +--------------------------+ ; +------------------------+------------------------------------------ ; | Palette-Based Encoding | ; +------------------------+ ; +---------------------+--------------------------------------------- ; | Reading and Writing | ; +---------------------+ ; +-------------------------+----------------------------------------- ; | Miscellaneous Utilities | ; +-------------------------+ ; +--------------+---------------------------------------------------- ; | Student Work | ; +--------------+