Today: digital images, RGB color, foreach loop,

PSA - What is "done" for a homework?

Themes

"Science is what we understand well enough to explain to a computer. Art is everything else we do." - Donald Knuth

Image - Numbers - Code


Digital Images

RGB Color Scheme

Live RGB LED Demo

Image Code - Pixels, Coordinates, RGB

# Load an image from the filesystem
# into RAM in variable named "image".
# Now the image can be manipulated by code.

image = SimpleImage('flowers.jpg')

alt: image var referring to image made of pixels in x,y grid

Best Loop Form: for

for x in collection:
    # use x in here
    x.do_something()

Image1 Code Examples

> Image1 Functions

Image Foreach - a. red_channel()

alt: pixel var points to each pixel in turn for each loop iteration

def red_channel(filename):
    """
    Creates an image for the given filename.
    Changes the image as follows:
    For every pixel, set green and blue values to 0
    yielding the red channel.
    Return the changed image.
    """
    
    for pixel in image:
        pixel.green = 0
        pixel.blue = 0
    return image

Side trip about math

Minimal Math

Update Variable: x = x + 1

x = 6
x = x + 1
# what is x now?

image1-b. Make Image Darker

    for pixel in image:
        pixel.red = pixel.red / 2
        pixel.green = pixel.green / 2
        pixel.blue = pixel.blue / 2
        # or shorthand form:
        # pixel.red /= 2

Relative Variable Shorthand: += -= *=

Shorthand way to write x = x + 1

x += 1

Shorthand for x = x * 2

x *= 2  # double x
>>> x = 10
>>> x += 3
>>> x
13
>>> x *= 2
>>> x
26

Image2 Puzzles

> Image2 Puzzles

Strategy Note: Detail Oriented

Image Coordinate System

Previously loaded image into memory like this. Now look at the x/y coordinate scheme of the pixels.

image = SimpleImage(filename)
x,y grid of pixels for example image width=100 and height=50

image.get_pixel(x, y)

# Sets the .red of the origin pixel at x=10 y=20 to 0
pixel = image.get_pixel(10, 20)
pixel.red = 0

for x in range(10):

4 Image Range Problems

> Image Range1

a. and b. are examples, c. is a key example, d. is for next time

Previously we used foreach to access every pixel. Here we'll use nested-range loops - another form of iteration. Understanding nested loops is step up in programming power.

Generating all x,y numbers for an image

Nested Loops

a. Darker nested-loop Example

Nested loop code:

    image = SimpleImage(filename)
    for y in range(image.height):
        for x in range(image.width):
            pixel = image.get_pixel(x, y)
            # use pixel.red here

Here is a picture, showing the order the nested y/x loops go through all the pixels - all of the top y=0 row, then the next y=1 row, and so on.

alt: nested loop order, top row, next row, and so on

Note: How to Make 2 Pixels Look The Same?

b.red = a.red
b.green = a.green
b.blue = a.blue

The following approach looks reasonable but does not work - changes the variable b to point to the same pixel as a

b = a

b. mirror1

alt: pixel in original, pixel_left in out image

def mirror1(filename):
    """
    Code is provided - this is an example.
    Read the original image at the given filename.
    Create a new 'out' image twice as wide as the original.
    Copy the original image to the left half of out, leaving
    the right half blank (this is a halfway-point to the image2).
    """
    image = SimpleImage(filename)
    # SimpleImage.blank(width, height) - returns a new
    # image with size given in its 2 parameters.
    # Here, create out image with width * 2 or first image:
    out = SimpleImage.blank(image.width * 2, image.height)
    for y in range(image.height):
        for x in range(image.width):
            pixel = image.get_pixel(x, y)
            # left copy
            pixel_left = out.get_pixel(x, y)
            pixel_left.red = pixel.red
            pixel_left.green = pixel.green
            pixel_left.blue = pixel.blue
            # right copy
            # nothing!
    return out

c. mirror2

mirror2 Pydoc: Like mirror1, but also copy the original image to the right half of "out", but as a horizontally reversed mirror image. So the left half is a regular copy, and the right half is a mirror image. (Starter code does the left half).

What is the pixel_right x,y for each?
A: (0, 0)
B: (1, 0)
C: (2, 0)
D: (99, 0)
ABCD Table Solution
A: (0, 0) out: (199, 0)
B: (1, 0) out: (198, 0)
C: (2, 0) out: (197, 0)
D: (99, 0) out: (100, 0)
Looking at above, work out that formula is
out_x = out.width - 1 - x

alt: figure dest x,y for source points A B C

mirror2 Solution Code

def mirror2(filename):
    """
    Like mirror1, but also copy the original image
    to the right half of "out", but as a horizontally reversed
    mirror image. So the left half is a regular copy,
    and the right half is a mirror image.
    (Starter code does the left half).
    """
    image = SimpleImage(filename)
    out = SimpleImage.blank(image.width * 2, image.height)
    for y in range(image.height):
        for x in range(image.width):
            pixel = image.get_pixel(x, y)
            # left copy
            pixel_left = out.get_pixel(x, y)
            pixel_left.red = pixel.red
            pixel_left.green = pixel.green
            pixel_left.blue = pixel.blue
            # right copy
            # this is the key spot
            # have: pixel at x,y in image
            # want: pixel_right at ??? to write to
            pixel_right = out.get_pixel(out.width - 1 - x, y)
            pixel_right.red = pixel.red
            pixel_right.green = pixel.green
            pixel_right.blue = pixel.blue
    return out

Aside: Parameter Passing Example

d. shrink

shrink(filename): Create a new "out" image half the width and height of the original. Set pixels at x=0 1 2 3 in out, from x=0 2 4 6 in original, and likewise in the y direction.

In effect this shrinks the image by 2x in both dimensions.

alt: out image is half the size of original, x=0 1 2 goes to x=0 2 4

Loop over out vs. original

Up to now, the nested loops hit every pixel in the original image. For this problem, looping over the original doesn't work cleanly, since the out image will only include 25% of the original pixels - looping over the original pixels would need to disregard 75% of the pixels as we go.

Solution: loop over the x,y of the "out" pixels instead. For each out x,y, compute the corresponding "in" x,y to read from in the original image.

Here is the nearly complete code - complete the code for x_in and y_in as a function of x y

def shrink(filename):
    """
    Create a new "out" image half the width and height
    of the original.
    Set pixels at x=0 1 2 3 in out, from x=0 2 4 6 in original,
    and likewise in the y direction.
    """
    image = SimpleImage(filename)
    out = SimpleImage.blank(image.width // 2, image.height // 2)
    # Here looping x,y over out, not original
    for y in range(out.height):
        for x in range(out.width):
            pixel_out = out.get_pixel(x, y)
            # your code here - compute x_in y_in used below
            x_in = None
            y_in = None
            
            # Copy from in to out
            pixel_in = image.get_pixel(x_in, y_in)
            pixel_out.red = pixel_in.red
            pixel_out.green = pixel_in.green
            pixel_out.blue = pixel_in.blue
    return out