Lab 03 - Picture Puzzles & Filters

Due by 11:59pm on 2023-06-29..

Starter Files

Download lab03.zip. Inside the archive, you will find starter files for the questions in this lab.

Topics

Image Review

Pixels

  • Digital images consist of pixels
  • A pixel is a small square, showing a single color

RGB

  • Each pixel can be controlled using a combination of red, green and blue
  • Each color ranges from a value of 0 (minimum) to 255 (maximum)
  • These colors mix to form the color of the pixel

Coding with Images

To start coding with images, install the BYU image library by either typing into the terminal:

pip install byuimage

or

python3 -m pip install byuimage

Then at the start of your python file, you need to import or load the BYU image library for your program:

from byuimage import Image

The following is an example on how you can go through each of the pixels in an image, modify them, and show the result:

from byuimage import Image

flower_image = Image("flower.jpeg")
for pixel in flower_image:
pixel.green = 0
pixel.blue = 0
flower_image.show()

Required Questions

Download the lab03.zip file, unzip it, and move it to your cs111/labs folder. You should find within it:

  • test_files/copper.png
  • test_files/iron.png
  • test_files/west.png
  • test_files/cougar.png

Inside of your lab03 folder, create a python file called lab03.py to write your code in.

Q1: Iron Puzzle

This is the iron.png file in the zip file you downloaded:

iron

This picture contains an image of something famous; however, the image has been distorted. The famous object is all in blue values, but the blue values have all been divided by 10, so they are too small by a factor of 10. The red and green values are all just meaningless random values (“noise”) added to obscure the real image. You must undo these distortions to reveal the real image.

All your code will be in a function called iron_puzzle(filename). It takes one parameter, the filename of the image with the puzzle in it. Your code should look like this:

def iron_puzzle(filename):
# load the image, solve the puzzle by modifying the image, return the modified image


solution = iron_puzzle("test_files/iron.png")
solution.show()

Note: TA's should demonstrate how to solve this problem.

Hint: You will need to loop over all pixels in the image. First, set all the red and green values to 0 to get them out of the way. Look at the result. If you look very carefully, you may see the real image, although it is very very dark (way down towards 0). Now multiply each blue value by 10 scaling it back up to approximately its proper value.

Q2: Copper Puzzle

Now that you have some understanding on how to solve these image puzzles, let's do another one. Use the copper.png file that is in the zip file you downloaded. It too has been distored:

copper

Write a function called copper_puzzle(filename) to recover the hidden image.

The hidden image is in the blue and green values; however, all the blue and green values have all been divided by 20, so the values are very small. The red values are all just random numbers, noise added on top to obscure things. Undo these distortions to reveal the true image.

You should be able to run your code with:

solution = copper_puzzle("test_files/copper.png")
solution.show()

What is the hidden image?

What happens if you fix only the green values or only the blue values?

Q3: West Puzzle

west

The hidden image is exclusively in the blue values, so set all red and green values to 0. The hidden image is encoded using only the blue values that are less than 16 (that is, 0 through 15). If a blue value is less than 16, multiply it by 16 to scale it up to its proper value. Alternately if a blue value for any pixel is 16 or more, it is random garbage and should be ignored (interpreted as 0). This should yield the recovered image, but all in the blue channel.

Write a function called west_puzzle that takes has one parameter filename to recover the hidden image.

After commenting out or removing the call to the iron_puzzle function, you should be able to run your code with:

solution = west_puzzle("test_files/west.png")
solution.show()

Hint: Use if-else logic along with other pixel techniques to recover the true image.

Q4: Darken Image

Note: The following functions will be used as part of Project 1 so it is in your interest to get them completed in lab so you don't have to finish them on your own.

For this problem, write a function that can darken an image an arbitarary amount. The function should be:

def darken(filename, percent):

The filename parameter contains the filename of an image to darken, and the percent parameter tells you how much to darken the image. percent must be a number between 0 and 1. You should return the modified image.

Additionally, we can darken a pixel 80% by doing:

pixel.red = pixel.red * 0.2
pixel.green = pixel.green * 0.2
pixel.blue = pixel.blue * 0.2

thus leaving a pixel 80% darker or 20% as bright.

Use cougar.png or download an image from Unsplash to use for this problem and move it to your lab03 folder. Unsplash provides free pictures that are legal to use as you want. You should always practice following copyright rules, so use a site like Unsplash instead of Google Images. Skip over the premium images that are shown on Unsplash and use the free ones.

Important: Download the small version of images so your code will run faster (since you will have fewer pixels to loop over).

Try darkening with various parameters:

solution = darken("myImage.jpeg", 0.3)
solution.show()
solution = darken("myImage.jpeg", 0.5)
solution.show()
solution = darken("myImage.jpeg", 0.8)
solution.show()

What happens if you use “0” or “1”?

Q5: Grayscale Filter

For this problem, write a function called grayscale(filename) that takes the filename of an image and returns an image that is gray. You can use the same image you previously downloaded

To make an image gray, we will use this algorithm:

  • Loop through all of the pixels
  • For each pixel, calculate the average color:
average = (pixel.red + pixel.green + pixel.blue) / 3
  • Set the red, green, and blue pixels equal to the average

You should be able to call your function like this:

gray = grayscale("myImage.jpeg")
gray.show()

Q6: Sepia Filter

You have probably used a sepia filter for a photo before. Write a function called sepia(filename), which takes a filename parameter and returns modified image that uses a sepia filter.

You will need to loop over all pixels, and then for each pixel, compute these values:

true_red = 0.393*pixel.red + 0.769*pixel.green + 0.189*pixel.blue
true_green = 0.349*pixel.red + 0.686*pixel.green + 0.168*pixel.blue
true_blue = 0.272*pixel.red + 0.534*pixel.green + 0.131*pixel.blue

Then, set each pixel color. For example:

pixel.red = true_red

Finally, it may be the case that the color value is greater than 255. If that happens, set that pixel's color value to the maximum value of 255. For example:

if pixel.red > 255:
pixel.red = 255

Run your function like previously:

solution = sepia("myImage.jpeg")
solution.show()

Creating New Images

To create a white blank image with certain dimensions, you can use Image.blank(width, height) where width and height are integers. For example,

image = Image.blank(100, 50) # 100 pixels wide, 50 pixels high

After creating a new image using, you can also access the image's height, width, and individual pixels using the following:

  • image.height
  • image.width
  • image.get_pixel(x,y)

With these we are allowed a new way of going through each pixel in the image. For example:

image = Image.blank(100, 50)
for y in range(image.height):
for x in range(image.width)
pixel = image.get_pixel(x,y)
# etc.

Note: You can also think of the first for loop as going through each row where y is a individual row index, and you can think of the second for loop as going through each column or slot in the row where x is a individual column or slot index


Q7: Bottom Border

Write a function called create_bottom_border(filename, weight) that adds a blue border to the bottom of the image given the weight. For example, with cougar.png and weight = 25:

cougar answer

The image size should not decrease. You should increase the image's height by weight pixels where the new area is blue.

To do this you will have to

  1. Create a new larger image
  2. Copy the contents of the old image to the new image
  3. Make the new area blue

Hints

  • To copy the contents of one pixel to another, we can do something like new_pixel.red = original_pixel.red for each of the colors.
  • Use the double for loop strategy of going through the pixels in the image mentioned above with get_pixel(x,y). Doing this will allow us to use both pixels for copying

Submit

If you attend the lab, you don't have to submit anything.

If you don't attend the lab, you will have to submit working code. Submit the lab03.py file on Canvas to Gradescope in the window on the assignment page.


Extra Practice

Stripes

Let's bring everything together. Write a function called create_stripes(filename) that takes in an image file. You should:

  • Create a new blank image with the same dimensions as the image given in filename, but the width is an extra 50 pixles and the height is an extra 25 pixels.
  • With the following priority:
    1. Make the pixel green if it is on a even row
    2. Make the pixel blue if it on a odd column (or slot)
    3. Otherwise, make the pixel red
  • Return the newly edited image

Your image should look something like this (perhaps after you zoom in several times)

stripes

Keep in mind that we start counting x and y at 0, so despite the fact that to us the first row is green, it is technically the 0th row which is why we made it green. (Same scenario with the columns/slots.)

© 2023 Brigham Young University, All Rights Reserved