This filter replicates the world as seen through a pair of rose-tinted glasses. It works by individually multiplying the red, green and blue channels of the original image by ratios in the 0-1 range which correspond to the RGB values of a "rose" color. This effectively simulates the filtering of different wavelengths of light through colored glass.
Before
After
def roseColoredGlasses(pic): rRose = 255 gRose = 63 bRose = 127 multiply(pic, rRose, gRose, bRose) def multiply(pic, r, g, b): for p in getPixels(pic): setRed(p, getRed(p) * r / 255) setGreen(p, getGreen(p) * g / 255) setBlue(p, getBlue(p) * b / 255) repaint(pic)
This filter inverts the color values of an image, producing an effect similar to that of a film negative. It works by subtracting the values of the red, green and blue channels from 255, the maximum value of each of the channels.
Before
After
def invert(p): return 255 - p def makeNegative(pic): for p in getPixels(pic): setRed(p, invert(getRed(p))) setGreen(p, invert(getGreen(p))) setBlue(p, invert(getBlue(p))) repaint(pic)
A naive implementation of black and white simply averages the values of the red, green and blue channels of a pixel for each pixel in an image. While this approach works, it does not accurately model the luminance values one would expect to see in a chemically exposed black and white photograph. Taking the weighted average of the three color channels, using coefficients derived from human perception, yields a much more convincing result.
Before
After
def luminance(r, g, b): return r*0.299 + g*0.587 + b*0.114 def betterBnW(pic): for p in getPixels(pic): l = luminance(getRed(p), getGreen(p), getBlue(p)) setRed(p, l) setGreen(p, l) setBlue(p, l) repaint(pic)
This simple filter takes all of the pixels on the bottom half of an image and mirrors them across the horizontal center line of the image. Similar to the way we inverted color values to achieve our negative effect, here we use the difference between a pixel's Y coordinate and the total height of the image to determine where it should be "flipped" to.
Before
After
def mirrorHorizontalInverse(pic): height = getHeight(pic) for x in range(0, getWidth(pic)): for y in range(0, height//2): dst = getPixel(pic, x, y) src = getPixel(pic, x, height - 1 - y) setColor(dst, getColor(src)) repaint(pic)
This is a simple routine that reduces the size of an image by half. It works by only copying every second pixel from the original image into a new, blank image. The pixels are skipped by simply doubling the X and Y coordinates of the smaller image when selecting a pixel from the original image to copy.
Before
After
def shrink(pic): copy = makeEmptyPicture(getWidth(pic)//2, getHeight(pic)//2) for x in range(0, getWidth(copy)): for y in range(0, getHeight(copy)): dst = getPixel(copy, x, y) src = getPixel(pic, min(x*2, getWidth(pic)), min(y*2, getHeight(pic))) setColor(dst, getColor(src)) return copy
This is a collage made from multiple images programmatically layered ontop of one another. Of note is that many of the above filters have been applied to the images in this collage.
def noBlue(pic): for p in getPixels(pic): setBlue(p, 0) return pic def lessRed(pic, percent): scalar = (100 - percent) / 100 for p in getPixels(pic): setRed(p, getRed(p) * scalar) return pic def roseColoredGlasses(pic): rRose = 255 gRose = 63 bRose = 127 pic = multiply(pic, rRose, gRose, bRose) return pic def multiply(pic, r, g, b): for p in getPixels(pic): setRed(p, getRed(p) * r / 255) setGreen(p, getGreen(p) * g / 255) setBlue(p, getBlue(p) * b / 255) return pic def luminance(r, g, b): return r*0.299 + g*0.587 + b*0.114 def betterBnW(pic): for p in getPixels(pic): l = luminance(getRed(p), getGreen(p), getBlue(p)) setRed(p, l) setGreen(p, l) setBlue(p, l) return pic def mirrorVertical(pic): width = getWidth(pic) for x in range(width//2, width): for y in range(0, getHeight(pic)): dst = getPixel(pic, x, y) src = getPixel(pic, width - 1 - x, y) setColor(dst, getColor(src)) return pic background = makePicture("...background.jpg") boots = makePicture("...boots.jpg") campfire = makePicture("...campfire.jpg") foggy = makePicture("...foggy.jpg") forest = makePicture("...forest.jpg") moose = makePicture("...moose.jpg") stove = makePicture("...stove.jpg") tent = makePicture("...tent.jpg") collage = copyPlace(betterBnW(boots), background, 400, 200) collage = copyPlace(noBlue(campfire), collage, 200, 1000) collage = copyPlace(foggy, collage, 400, 1800) collage = copyPlace(forest, collage, 2000, 200) collage = copyPlace(mirrorVertical(moose), collage, 1200, 1050) collage = copyPlace(lessRed(stove, 50), collage, 2000, 1800) collage = copyPlace(roseColoredGlasses(tent), collage, 2200, 1000)
This is a filter for reducing red-eye in photographs. The base routine takes an (x,y) coordinate in the image and a radius to approximate the shape of the pupil. Each pixel within this circular region is then checked for similarity to a pure red color using geometric distance, where the threshold parameter determines the level of "redness" in a pixel required for further processing to apply. Any pixels which pass the threshold test then have their red channel reduced according to the amount parameter. The program repeats this process for a user-selected number of eyes.
Before
After
def correctRedEye(pic, xCenter, yCenter, radius, thresh, amount): for x in range(xCenter - radius, xCenter + radius): for y in range(yCenter - radius, yCenter + radius): if math.sqrt(math.pow(float(x - xCenter), 2) + math.pow(float(y - yCenter), 2)) <= radius: p = getPixel(pic, x, y) if distance(getColor(p), red) < thresh * math.sqrt(3 * math.pow(255, 2)): setColor(p, makeColor((1 - amount) * getRed(p), getGreen(p), getBlue(p))) return pic """ Values Used: Number of Corrections: 2 Eye X-Center: 160 Eye Y-Center: 275 Eye Radius: 10 Red Selection Threshold (0-1): 0.4 Red Reduction Amount (0-1): 1 Eye X-Center: 257 Eye Y-Center: 280 Eye Radius: 10 Red Selection Threshold (0-1): 0.4 Red Reduction Amount (0-1): 1 """ pic = makePicture(pickAFile()) explore(pic) for i in range(0, input("Number of Corrections: ")): pic = correctRedEye( pic, int(input("Eye X-Center: ")), int(input("Eye Y-Center: ")), int(input("Eye Radius: ")), float(input("Red Selection Threshold (0-1): ")), float(input("Red Reduction Amount (0-1): "))) show(pic)
This filter creates a step-like effect in each of the three color channels of an image. Red, green and blue values are separated into ranges, and each range is collapsed to a single value. This results in an almost painterly effect.
Before
After
def eightBitFloor(val): if val < 64: return 31 if val < 128: return 95 if val < 192: return 159 else: return 223 def posterize(pic): for p in getPixels(pic): setRed(p, eightBitFloor(getRed(p))) setGreen(p, eightBitFloor(getGreen(p))) setBlue(p, eightBitFloor(getBlue(p))) return pic
This well-known technique measures the geometric distance between a color and a provided "chroma key" (in this case, green!). Pixels within the provided threshold are then replaced with corresponding pixels from a second image. This results in a layering effect commonly used in cinema and television to digitally insert actors into virtual scenes. Difficulties arise near the edge of a subject, where light reflecting off the green screen casts a subtle green glow. In this simple implementation, careful adjustment of the threshold parameter resulted in an acceptable end result. Professional green screen software allows for fine tuning of the selection by exposing multiple chroma keys, each with a unique threshold, to provide subtle blending around edges.
Before (Background)
Before (Foreground)
After
def chromaKey(foreground, background, key, thresh): copy = makeEmptyPicture(getWidth(foreground), getHeight(background)) for x in range(0, getWidth(foreground)): for y in range(0, getHeight(foreground)): srcFore = getPixel(foreground, x, y) srcBack = getPixel(background, x, y) dst = getPixel(copy, x, y) if distance(getColor(srcFore), key) < thresh * math.sqrt(3 * math.pow(255, 2)): setColor(dst, getColor(srcBack)) else: setColor(dst, getColor(srcFore)) return copy foreground = makePicture(pickAFile()) background = makePicture(pickAFile()) explore(foreground) output = input("Save To (Escape Backslashes): ") key = pickAColor() while True: thresh = input("Threshold (-1 to Save): ") if thresh < 0: break pic = chromaKey(foreground, background, key, thresh) show(pic) writePictureTo(pic, output)
This is a "home made" Thanksgiving card. It uses a technique similar to the Green Screen filter to create solid-colored stencils out of photograpphs (in this instance, photographs of autumnal leaves). These stencils are then applied to a base image in a random quantity, distribution, color and size. By varying only the green channel of the stencil's color (leaving red at 255 and blue at 0) leaves stay within a red-orange-yellow family of colors.
def makeStencil(pic, color, threshold): expandedThreshold = threshold * (math.sqrt(math.pow(255, 2) + math.pow(255, 2) + math.pow(255, 2))) stencil = makeEmptyPicture(getWidth(pic), getHeight(pic)) for x in range(0, getWidth(pic)): for y in range(0, getHeight(pic)): src = getPixel(pic, x, y) dst = getPixel(stencil, x, y) if distance(getColor(src), getColor(dst)) < expandedThreshold: setColor(dst, black) else: setColor(dst, white) return stencil def applyStencil(pic, stencil, color, xOffset, yOffset, scale): invScale = int(math.floor(1 / scale)); for x in range(0, getWidth(stencil), invScale): for y in range(0, getHeight(stencil), invScale): if getColor(getPixel(stencil, x, y)) == white: xPrime = xOffset + int(x / invScale) if xPrime >= 0 and xPrime < getWidth(pic): yPrime = yOffset + int(y / invScale) if yPrime >= 0 and yPrime < getHeight(pic): setColor(getPixel(pic, xPrime, yPrime), color) def paintLeaves(pic, stencil, minCount, maxCount): for i in range(0, random.randrange(minCount, maxCount)): applyStencil( pic, stencil, makeColor(255, random.randrange(0, 255), 0), random.randrange(-getWidth(stencil), getWidth(card) + getWidth(stencil)), random.randrange(-getHeight(stencil), getHeight(card) + getHeight(stencil)), random.uniform(0.2, 0.5)) leaf1Stencil = makeStencil(makePicture("...leaf1.jpg"), white, 0.25) leaf2Stencil = makeStencil(makePicture("...leaf2.jpg"), white, 0.25) leaf3Stencil = makeStencil(makePicture("...leaf3.png"), white, 0.15) leaf4Stencil = makeStencil(makePicture("...leaf4.png"), white, 0.25) card = makeEmptyPicture(2100, 1500) paintLeaves(card, leaf1Stencil, 25, 50) paintLeaves(card, leaf2Stencil, 25, 50) paintLeaves(card, leaf3Stencil, 25, 50) paintLeaves(card, leaf4Stencil, 25, 50) paintLeaves(card, leaf1Stencil, 25, 50) paintLeaves(card, leaf2Stencil, 25, 50) paintLeaves(card, leaf3Stencil, 25, 50) paintLeaves(card, leaf4Stencil, 25, 50) addTextWithStyle( card, 275, 700, "Happy Thanksgiving!", makeStyle("serif", italic, 180), black)
This filter converts an image into something resembling a line drawing or charcoal sketch. This is achieved by comparing the luminance of each pixel (where luminance is defined as the weighted average used in the Better Black & White filter above) to the luminance of its neighbor pixels. If the difference between a pixel's luminance and its neighboring pixels' luminance is outside of the provided threshold value, the pixel is made black, otherwise the pixel is made white. Using a lower threshold results in an image more similar to a charcoal sketch, where subtle luminance changes yield a swath of fine dots. Higher threshold values create something more like a line drawing, where only particularly sharp luminance changes are reflected in the final image.
Before
After
def luminance(p): return getRed(p)*0.299 + getGreen(p)*0.587 + getBlue(p)*0.114 def lineArt(pic, threshold): for x in range(0, getWidth(pic) - 1): for y in range(0, getHeight(pic) - 1): p = getPixel(pic, x, y) r = getPixel(pic, x+1, y) b = getPixel(pic, x, y+1) if abs(luminance(p) - luminance(r)) > threshold and abs(luminance(p) - luminance(b)) > threshold: setColor(p, black) else: setColor(p, white) return pic pic = lineArt(makePicture(pickAFile()), 2.5)