CS 105 - Understanding Our Algorithmic World

CS 105 - Exercise Thirteen

Goals

  • Learn how to manipulate images with Snap!
  • Learn a little bit about color
  • Get more practice with lists and higher-order blocks

Prerequisite

There is no starter code for this exercise, so go ahead and visit https://snap.berkeley.edu/snap/snap.html and make sure you are logged in.

Objective

Our goal with this exercise is to show you a collection of ways that you can manipulate images at the pixel level (and hopefully learn a little bit about how filters you may have used in the past might work).

I have some tasks for you, but I hope that you take the opportunity to play around a little bit.

Getting an image

As I showed you during the lecture, we can get images into Snap! very easily by just dragging them into the working area.

There, is, however, a second way -- you can use the camera that you have become somewhat accustomed to staring into at the top of your computer.

Go to the Sensing palette. Pull out video capture. Click on the predicate input until it shows a green check box. Then click on the block. Your browser should ask for permission to use your camera, and then you should show up on stage.

You will notice that the image is pale -- it is set to be semi-transparent. You can control it with vide transparency block.

You can turn the video off again by clicking the predicate again so it shows a red check and then clicking the block again.

However, that doesn't get you a picture.

Pull out the video on block. Set the first input to 'snap' and the second to 'stage'.

If you click on it, you will see a little picture pop up in the report bubble.

We want to work with it, so... hopefully you can hear this coming already... we want to save it in a variable. Create a new variable called pic and set it to a picture:

save picture

Run your block to get a good picture of yourself (or at least one you can stand looking at for the next hour or so).

To see what the picture looks like, use switch costume to see the image on the sprite.

When you are happy, turn off the video capture.

Check out the pixels

The picture data in pic is considered to be a "costume". This is essentially a wrapper around the pixel data with some extra information about the dimensions. To get the actual pixels so we can look at them (and manipulate them), we need to use the of costume block. Change the first input to 'pixels' and load pic into the second input.

If you click on this reporter, you will see the long list of pixel data. Remember that while this looks like a table when you display it, it is a list of lists, where the sub-lists are representing single pixels in the image.

Adjust the brightness

You first task will be to create a new block: set brightness block.

This block takes two arguments, a brightness and a pixel. We will consider the brightness to be a percentage, so numbers less than 1 will make the image darker, and numbers greater than 1 will make it brighter. We will multiply this value by the values stored in each pixel to create a new image.

Now, we could just apply this multiplication to the entire image at once (which I did in the lecture). Unfortunately, this only kind of works. The problem is that it changes the alpha channel as well. We could trim off the alpha value (which has the effect of making it always 255), but we are going to want to work at the pixel level for our next operations, so we will do that for this one as well (despite it being slower).

The idea behind making a block that just operates on a single pixel is that the logic is easier (we only have to worry about a single pixel), and we can easily apply it to the entire image with teh map block.

Making the block

Recall that each pixel is just a list of four elements. The first three are the red, green, and blue channels of the color, and the fourth is the alpha value.

You want to report a new list of four values. For the three color channels, multiple each one by the brightness. For the alpha channel, just report the original value from the pixel.

Here is what the block should output given a pixel:

set brightness example

Apply it to the image

Once the block is working (and not before), use map block to apply the new reporter to the pixels of your image. Wrap the whole thing in a switch costume block so you can check out your handiwork.

It will take a couple of seconds for the image to be processed.

Make the image grayscale

As I told you in the lecture, a grayscale image is one in which the pixel value is really just the intensity of the light. We could represent each pixel with a single value. However, when our images have RGB channels, we can achieve the same thing by setting all three channels to the same value. I showed you how we could look at the amount of light was stored in the red channel, by duplicating its value across all three channels.

The resulting image is grayscale, but diverges from our perception of how much light was actually shining on the subjects of our pictures. We would like to get closer to the "luminance" of the pixels. While there is a formula for the luminance, we are going to keep things simple and just average the values in the three channels together. This at least gets all three channels involved in the output (the reason that this isn't quite the correct solution is because our eyes have different sensitivities to red, green and blue).

Make a block

Create a new block: grayscale block.

This will work like the set brightness block. We will work at the pixel level first, and then apply it to the pixels later.

Inside of the new block, create a new script variable called avg. Set it equal to the average of the three color channels.

Report a list of four elements. The first three channels should all be the computed average. The fourth should be the original alpha value.

grayscale example

Apply it to the image

Use the same process as you used for the set brightness block to map the new reporter over the entire image.

Posterize

I showed you a simple example of posterizing in the lecture. In essence, the process of posterizing is to reduce the number of colors in an image (historically, printing in color was expensive and each color required a separate print run, so the fewer colors the better).

In the example I showed you, I divided all of the pixel values by 100, rounded to throw away the fractional part, and then multiplied by 100 again. This meant each color channel could be 0, 100, or 200. This gives us 9 possible colors.

While this process works, and gives us an interesting stylized look, we can make the output much more interesting looking.

Another approach to posterizing is to convert the image to grayscale, simplify that image, and then assign whatever colors we like to the different levels.

With a looming election, I was reminded of the Obama "Hope" poster by Shepard Fairley. So I borrowed the color scheme.

posterized portrait

Make a block

Create a new block: posterize block

You can actually duplicate the grayscale block to give you your starting place.

You will start the same way -- compute the average of the three color channels.

However, you won't assemble a new pixel based on that value.

We also don't need to simplify the average value either. Instead, I would like you to think about numeric ranges. For example:

if avg < 64
  report color 1
else
  if avg < 128
    report color 2
  else
    ... and so one

Notice that these ranges will simplify the color palette by using a single color to represent a collection of values found in the original image.

Colors

When you report the color, you just need to report a pixel (list) with your desired color.

To get the "Hope" colors, I used (0,49,79), (187,21,27), (114,151,160), (215,210,170), (255,230,170), and (255,255,255) (in that order). You can use this color scheme, or you can make your own. There are a number of different sites that will help you create color palettes that work together, like Adobe Color and coolors (click the hex string at the bottom of the stripes to get the 0-255 values for the color channels). The requirement is that you should use between four and ten different colors.

Tuning the ranges

I would start by evenly dividing the 0-255 range into roughly equal sized bands (e.g., if you want six colors, you could break at 42, 84, 126, etc...). This may look fine, or it may not. You are welcome (encouraged) to tinker with the ranges to achieve a look you like. (Note that while we can match the colors of the "Hope" poster, we won't quite gt the same look because there is a lot of image simplification that also happened, which we aren't able to perform).

What I will be looking for

  • You should have a picture stored in the variable pic
  • You should produce three new blocks: set brightness block, grayscale block, and posterize block
  • You should have a script incorporating set brightness block which shows the image with the brightness adjusted when run
  • You should have a script incorporating grayscale block which shows a grayscale version of the image when run
  • You should have a script incorporating posterize block which shows a posterized version of the image with at least four colors, but no more than ten different colors when run

Submitting

Share the project using the instructions from exercise 1.

Visit the exercise page on Canvas to submit the URL.

Note: If you were playing around and dragged some extra images in (which would be great!), you may not be able to save your work because there is a cap on the size of projects that are saved on the Snap! servers. You can either delete the extra images back out, or save the project to your computer and upload the file to Canvas.