Functional Problem Solving (CSC 151 2015F) : Assignments

Assignment 3: Color Transformations and Image Filters


This is a new assignment for Fall 2015. More sample images coming soon!

Due: 10:30 p.m., 15 September 2015

Summary: In this assignment, you will explore mechanisms for transforming colors and see how those mechanisms can then be applied to images. Our primary focus is on color transformations.

Purposes: To give you more experience with colors and color transformations. To give you more experience writing your own functions. To have a bit of fun.

Collaboration: You must work with assigned partners on this assignment. You may discuss this assignment with anyone, provided you credit such discussions when you submit the assignment.

Wrapper (Prologue): Individually read through this assignment and make sure that you understand what is required. Then use the form available at http://bit.ly/151hw3pro to indicate (a) how long you think this assignment will take and (b) what you think will be the most challenging aspect of this assignment.

Wrapper (Epilogue): When you are done with the assignment, fill out the form available at http://bit.ly/151hw3epi to indicate (a) how long the assignment took, (b) what the most challenging part of the assignment was, and (c) something important you learned from doing the assignment. If you find that the assignment took much less or much more time than you expected, also include (d) a note as to what might have led to that difference.

Submitting: Email your answer to . The title of your email should have the form CSC 151.01 Assignment 3: Color Filters and should contain your answers to all parts of the assignment. Scheme code should be in the body of the message. You should not attach any images; we should be able to re-create them from your code.

Warning: So that this assignment is a learning experience for everyone, we may spend class time publicly critiquing your work.

Background

You have recently begun to explore the RGB color model as well as functions that transform RGB colors. You have seen a wide variety of built-in color transformations, including irgb-complement, irgb-lighter, and irgb-redder. You have also started to might write your own color transformations. You have learned a variety of mechanisms for writing color transformations.

  • You can use the composition operator, o to combine other unary transformations into a single transformation. For example, you might write a procedure that cuts the high components with the following code.
    (define irgb-limit-high (o irgb-darker irgb-darker irgb-lighter irgb-lighter))
    
  • You can use the section procedure to fill in one parameter of a binary color operation. For example, you might write a procedure that decreases the red component by 64 with the following code.
    (define irgb-decrease-red (section irgb-subtract <> (irgb 64 0 0)))
    
  • You can use a generalized lambda expression to write your transformation, using a pattern something like the following.
    (define irgb-transform
      (lambda (color)
        (irgb (function-to-compute-new-red-component color)
              (function-to-compute-new-green-component color)
              (function-to-compute-new-blue-component color))))
    
    For example, to decrease each of the components by 32, you might write:
    (define irgb-subtract-32
      (lambda (color)
        (irgb (- (irgb-red color) 32)
              (- (irgb-green color) 32)
              (- (irgb-blue color) 32))))
    

As you may have noted, by using image-variant and a color transformation, we have effectively written a simple image filter, akin to those that come with Adobe Photoshop and other image editing applications.

In this assignment, you will build some color transformations and explore their utility as image filters.

Assignment

Problem 1: Extreme Components

Write a procedure, (irgb-extreme color), that takes one parameter, an integer-encoded RGB color, and turns each component to 255 if it is at least 128 and to 0 if it is less than 128.

> (irgb->string (irgb-extreme (irgb 0 64 200)))
"0/0/255"
> (irgb->string (irgb-extreme (irgb 128 130 0)))
"255/255/0"

Hint: A hint for this problem appears in a recent lab.

Problem 2: Dominant Components

Write a procedure, (irgb-dominant-component color), that takes one parameter, an integer-encoded RGB color, and produces a new color in which each component is 255 if it is the largest component (or tied for largest) and 0 otherwise.

> (irgb->string (irgb-dominant-component (irgb 0 5 0)))
"0/255/0"
> (irgb->string (irgb-dominant-component (irgb 200 199 199)))
"255/0/0"
> (irgb->string (irgb-dominant-component (irgb 10 0 10)))
"255/0/255"

Hint: You should be able to do this with a clever combination of max, division, rounding, and multiplication.

Problem 3: Flattening

A common common technique for manipulating images is known as “flattening” the image. In general, we flatten an image by restricting the values of each component to multiples of a certain value. For example, we might ensure that the components are each multiples of 16, 32, or 64. (We'll use 255 instead of 256 for the highest multiple.)

How do we convert each component to the appropriate multiple? Consider the case of multiples of 32. If we divide the component by 32, round, and then multiply by 32, we'll get the nearest multiple of 32. For example,

> (* 32 (round (/ 11 32)))
0
> (* 32 (round (/ 21 32)))
32
> (* 32 (round (/ 71 32)))
64
> (* 32 (round (/ 91 32)))
96
> (* 32 (round (/ 211 32)))
224
> (* 32 (round (/ 255 32)))
256

As the last example suggests, we may sometimes get a number outside of the range 0..255. Fortunately, the irgb and irgb-new functions treat 256 the same as 255.

Write a procedure, (irgb-flatten-64 irgb-color), that flattens an IRGB color by converting each component to the nearest multiple of 64.

You may then want to see the effect this procedure has on various images.

> (define kitten (image-load "/home/rebelsky/Desktop/kitten.jpg"))
> (image-show kitten)
> (image-show (image-variant kitten irgb-flatten-64))

Hint: The sample code for computing nearest multiples of 32 should help.

Problem 4: Cycling Through Colors

As you've seen, when we apply the typical color transformation, such as irgb-darker or irgb-redder, we eventually reach a limit of 0 or 255. But we can get some interesting effects by “wrapping around” at the end. For example, here's the output from a function that adds 90 to a number, wrapping when we hit 255.

> (cyclic-add-90 75)
165 ; 75 + 90 = 165
> (cyclic-add-90 165)
255 ; 165 + 90 = 255
> (cyclic-add-90 166)
0 ; 166 + 90 = 256, wrap around to 0
> (cyclic-add-90 167)
1 ; 167 + 90 = 257, wrap around to 1
> (cyclic-add-90 255)
89 ; we wrap around because we hit 255
> (cyclic-add-90 89)
179 ; 89 + 90 = 179
> (cyclic-add-90 179)
13 ; 179 + 90 = 269, 269 - 256 = 13

As you might expect, cyclic-add-90 can be written in a variety of ways, combining addition and remainder or modulo.

(define cyclic-add-90
  (lambda (val)
    (modulo (+ val 90) 256)))
(define cyclic-add-90 (o (section modulo <> 256) (section + <> 90)))

Write a procedure, (irgb-cyclic-add color1 color2), that takes two colors as input and produces a new color formed by the cyclic addition of the corresponding components of the two colors.

Problem 5: HSV Transforms

As we learned in the reading on design and color representing colors in terms of hue, saturation, and value is an alternative to RGB representation. Hue represents the pure color (e.g., red, blue, yellow, green, or a combination of one of these). Saturation represents the “colorfulness” of the hue in the color. For instance, a completely saturated color would be a pure hue (like red), while a less saturated color might appear just as bright but somewhat faded (perhaps rose or pink). Value represents the brightness or darkness of the color.

As shown below, hue is represented as an angle, or a point on a circle. Thus, the values 0-360 sweep through colors red (0 degrees), yellow (60 degrees), green (120 degrees), cyan (180 degrees), blue (240 degrees), magenta (300 degrees), and back to red (at 360 or 0 degrees).

Being able to manipulate the hue in a color can actually be quite useful. Fortunately, Mediascheme can convert between HSV colors and integer-encoded RGB colors. We convert from HSV to RGB with the procedure (hsv->irgb hsv-color), where hsv-color is created using the hsv procedure. For example, to create a magenta-like color with 50% saturation and 25% value, we would use (hsv->irgb (hsv 300 1/2 1/4)) or (hsv->irgb (hsv 300 .5 .25)).

Mediascheme can also extract the, hue, saturation, and value from an RGB color with the procedures (irgb->hue irgb-color) (irgb->saturation irgb-color) and (irgb->value irgb-color).

a. Write a procedure, (irgb-change-hue irgb-color hue), that takes an integer-encoded RGB color and a hue value (in the range 0-360) as parameters and creates a new integer-encoded RGB color using the given hue with the saturation and value of irgb-color.

b. Write a procedure, (irgb-true-complement irgb-color) that finds the true complement of irgb-color, one that is 180 degrees away on the color wheel but with the same saturation and value.

c. Write a procedure, (irgb-change-value irgb-color value), that takes an integer-encoded RGB color and a value (in the range 0-1) as parameters and creates a new integer-encoded RGB color using the given value with the hue and saturation of irgb-color.

Problem 6: Hue-Based Transforms

Color transformations based on hue be can visually interesting.

Original Image Hue rotated 30 deg Hue rotated 90 deg

Write a procedure (irgb-rotate-hue irgb-color angle) that takes an integer-encoded RGB color and an angle as parameters and produces a new integer-encoded RGB color where the HSV equivalent has a hue rotated by angle degrees, a number between 0-360.

Hint: If the rotated angle is greater than 360, be sure to wrap around properly (e.g., using modulo) to get the correct hue angle.

Warning: When you apply irgb-rotate-hue thousands of times (as you will in an image of non-trivial size), it is likely to take some time. If you conduct experiments during development, do those experiments on small images.

Important Evaluation Criteria

We will judge your solutions on their correctness, concision, and cleverness.

Extra Credit: Extracting the Hue

Those of you who want a particularly challenging extra credit problem can consider how you might compute the hue of an RGB colors. We'll provide some information, but not all of the details.

Before we describe how to calculate hue, we need some basic values to refer to. Let red, green, and blue) refer to the red, green, and blue components of an RGB color, respectively. The chroma of a color is the largest of the RGB components minus the smallest of the RGB components. For example, the chroma of (128,64,50) is 128-50, or 78; the chroma of (0,255,0) is 255-0, or 255. The chroma of (255,255,255) is 255-255, or 0.

The raw hue can then be calculated as follows:

  • The raw hue is (green-blue)/chroma when red is a largest component.
  • The raw hue is 2 + (blue-red)/chroma when green is a largest component.
  • The raw hue is 4 + (red-green)/chroma when blue is a largest component.
  • The raw hue is undefined if chroma=0. That makes sense, because when all the components are the same we would have a gray, which has no color. (It's also hard to do the above formulae, since they divide by the chroma.) In this case, one convention is to set the raw hue to 0.

Note that the numerators of the fractions make some intuitive sense. For example, if the red component is largest, and the green component is larger than the blue component, then we should move counter-clockwise (positive), toward green. And, as we'd hope, the (green-blue) is positive. Similarly, if the red component is largest and the blue component is larger than the green component, then we should move counter-clockwise (negative), toward blue. And, as we'd hope, the (green-blue) is negative.

The raw hue as given above produces a value between -1 and 6 (corresponding to the 6 cardinal colors described above). Why would we end up with a negative number? Well, we just saw that colors in which red dominates that have a larger blue component shift by a negative value.

If the raw hue is negative, we should add 6 to get us back to a positive representation.

The final result is converted to the range 0-359 by multiplying by 60 degrees (which is 360/6).

What do we do if two components are equal and larger than the third? It turns out that the formulae are designed so that you can use any of them. You could also use the appropriate pair and average their final results. For example, if both red and blue are 200 and green is 100, the first formula gives us (100-200)/100 = -1 and the second formula gives us 4+(200-100)/100 = 5. When we add 6 to the -1, both formulae give us 5. We multiply by 60, and get 300.

Now that you know how to compute a hue, it's time to think about code. The informal algorithm above has a lot of conditionals. But you don't know how to write conditionals. That's okay; we've found that we can do without conditionals and still get conditional-like behavior. For example, (min top (max bottom val)) computes a value that is equal to bottom, if val is less than bottom; equal to top, if value is greater than top; and just val, if val is between bottom and top. Similarly, in a problem above, you wrote code to convert a number to 0 if it was less than 128 and to 255 if it was at least 128.

Implement a procedure, (rgb2hue color) that computes the hue of an RGB color using this strategy. You may not use conditionals in this computation, nor may you use irgb->hue or similar functions. You may, however, write helper functions.

Acknowledgments

The HSV hexagon is adapted from an original by Jacob Rus. Both the original and our version are licensed under the Creative Commons Attribution-Share Alike 3.0 Unported license.

The image of flowers was provided to us by Prof. Weinman and used with his permission.