Chapter 6: Modifying Pixels by Position Chapter Learning Goals Chapter Learning Goals There are four conditional statements in this program for turning the red flowers in the arch into moon-colored flowers. One of the generated this image. Which one? Pixels are in a matrix Matrices have two dimensions: A height and a width We can reference any element in the matrix with (x,y) or (horizontal, vertical) We refer to those coordinates as index numbers or indices We sometimes want to know where a pixel is, and getPixels doesn't let us know that. Arrays vs Matrices Pixels in a Matrix >>> h = makePicture("horse.jpg") >>> print getHeight(h) 640 >>> print getWidth(h) 480 >>> print getPixel(h,0,0) Pixel red=62 green=78 blue=49 >>> print getPixel(h,479,639) Pixel red=113 green=89 blue=77 >>> print getPixel(h,480,640) getPixel(picture,x,y): x (= 480) is less than 0 or bigger than the width (= 479) The error was: Inappropriate argument value (of correct type). An error occurred attempting to pass an argument to a function. Indices from the function range Range returns a sequence between its first two inputs, possibly using a third input as the increment >>> print range(1,4) [1, 2, 3] >>> print range(-1,3) [-1, 0, 1, 2] >>> print range(1,10,2) [1, 3, 5, 7, 9] >>> print range(3) [0,1,2] Notice: • End value is never included. • range(0,10) ends at 9. • If you leave out a start value, it's assumed to be zero. Side Note: That thing in [] is a sequence >>> a=[1,2,3] >>> print a [1, 2, 3] >>> a = a + 4 An attempt was made to call a function with a parameter of an invalid type >>> a = a + [4] >>> print a [1, 2, 3, 4] >>> a[0] 1 We can assign names to sequences, print them, add items to sequences, and access individual pieces of them. We can also use for loops to process each element of a sequence. We can use range to generate index numbers We'll do this by working the range from 0 to the height-1, and 0 to the width-1. Using the range function will make it easy to start from 0 and stop before the end value. But we'll need more than one loop. Each for loop can only change one variable, and we need two for indexing a matrix Working the pixels by number To use range, we'll have to use nested loops One to walk the width, the other to walk the height Be sure to watch your blocks (i.e., indentation) carefully! def increaseRed2(picture): for x in range(0,getWidth(picture)): for y in range(0,getHeight(picture)): px = getPixel(picture,x,y) value = getRed(px) setRed(px,value*1.1) What's going on here? The first time through the first loop, x is the name for 0. We'll be processing the first column of pixels in the picture. def increaseRed2(picture): for x in range(0,getWidth(picture)): for y in range(0,getHeight(picture)): px = getPixel(picture,x,y) value = getRed(px) setRed(px,value*1.1) Now, the inner loop Next, we set y to 0. We're now going to process each of the pixels in the first column. def increaseRed2(picture): for x in range(0,getWidth(picture)): for y in range(0,getHeight(picture)): px = getPixel(picture,x,y) value = getRed(px) setRed(px,value*1.1) Process a pixel With x = 0 and y = 0, we get the leftmost pixel and increase its red by 10% def increaseRed2(picture): for x in range(0,getWidth(picture)): for y in range(0,getHeight(picture)): px = getPixel(picture,x,y) value = getRed(px) setRed(px,value*1.1) Next pixel Next we set y to 1 (next value in the sequence range(0,getHeight(picture)) def increaseRed2(picture): for x in range(0,getWidth(picture)): for y in range(0,getHeight(picture)): px = getPixel(picture,x,y) value = getRed(px) setRed(px,value*1.1) Process pixel (0,1) x is still 0, and now y is 1, so increase the red for pixel (0,1) def increaseRed2(picture): for x in range(0,getWidth(picture)): for y in range(0,getHeight(picture)): px = getPixel(picture,x,y) value = getRed(px) setRed(px,value*1.1) We continue along this way, with y taking on every value from 0 to the height of the picture (minus 1). Finally, next column Now that we're done with the loop for y, we get back to the FOR loop for x. x takes on the value 1, and we go back to the y loop to process all the pixels in the column x=1. def increaseRed2(picture): for x in range(0,getWidth(picture)): for y in range(0,getHeight(picture)): px = getPixel(picture,x,y) value = getRed(px) setRed(px,value*1.1) Lightening a Picture with Nested Loops def lighten2(picture): for x in range(0,getWidth(picture)): for y in range(0,getHeight(picture)): px = getPixel(picture,x,y) color = getColor(px) color = makeLighter(color) setColor(px,color) A Which function did A? B Reducing Red Eye with Nested Loops def removeRedEye2(pic,sX,sY,eX,eY,endColor): for x in range(sX,eX): for y in range(sY,eY): This version is far faster than px = getPixel(pic,x,y) the previous if (distance(red,getColor(px)) < 165): version setColor(px,endColor) Timing >>> b = makePicture("bridge.jpg") >>> b Picture, filename /Users/guzdial/Desktop/mediasources4ed/bridge.jpg height 640 width 480 >>> yellowbox1(b) Time: 1.317775 >>> yellowbox2(b) Time: 0.00316000000001 from time import clock def yellowbox1(pict): start = clock() for px in getPixels(pict): x = getX(px) y = getY(px) if 10 <= x < 20 and 10 <= y < 20: setColor(px,yellow) print "Time:",clock()-start def yellowbox2(pict): start = clock() for x in range(10,20): for y in range(10,20): setColor(getPixel(pict,x,y),yellow) print "Time:",clock()-start What can you do if you know where the pixels are? One answer: Mirroring Imagine a mirror horizontally across the picture, or vertically What would we see? How do generate that digitally? We simply copy the colors of pixels from one place to another Work it out with matrices mirrorPoint is halfway across: getWidth(picture)/2 If left pixel is at (x,y), right pixel is at (width-x-1,y) Recipe for mirroring def mirrorVertical(source): mirrorPoint = getWidth(source) / 2 width = getWidth(source) for y in range(0,getHeight(source)): for x in range(0,mirrorPoint): leftPixel = getPixel(source,x,y) rightPixel = getPixel(source,width - x - 1,y) color = getColor(leftPixel) setColor(rightPixel,color) Can we do it with a horizontal mirror? def mirrorHorizontal(source): mirrorPoint = getHeight(source) / 2 height = getHeight(source) for x in range(0,getWidth(source)): for y in range(0,mirrorPoint): topPixel = getPixel(source,x,y) bottomPixel = getPixel(source,x,height - y - 1) color = getColor(topPixel) setColor(bottomPixel,color) Of course! What if we wanted to copy bottom to top? Very simple: Swap the order of pixels in the bottom lines def mirrorBotTop(source): mirrorPoint = getHeight(source) / 2 height = getHeight(source) for x in range(0,getWidth(source)): for y in range(0,mirrorPoint): topPixel = getPixel(source,x,y) bottomPixel = getPixel(source,x,height - y - 1) color = getColor(bottomPixel) setColor(topPixel,color) Mirroring bottom to top Doing something useful with mirroring Mirroring can be used to create interesting effects, but it can also be used to create realistic effects. Consider this image from a trip to Athens, Greece. Can we “repair” the temple by mirroring the complete part onto the broken part? Figuring out where to mirror Use MediaTools to find the mirror point and the range that we want to copy Writing functions for specific files…generally The function to mirror the temple needs to work for one and only one file. But we still don't want to write out the whole path. setMediaPath() allows us to pick a directory where our media will be stored. getMediaPath(filename) will generate the entire path for us to the filename in the media directory THIS ONLY WORKS WHEN WE'RE ACCESSING FILES IN THE MEDIA DIRECTORY AND WHERE WE HAVE SET THE PATH FIRST! Some Utility Functions If you know the name of the file, searching for it with pickAFile() feels tedious You can set and get a media folder (path) for remembering a place where your media will be coming from (or going to) setMediaPath() lets you pick a file in your media folder getMediaPath(basefilename) lets you generate a complete filename out of only the last part Example >>> setMediaPath() New media folder: C:\Documents and Settings\Mark Guzdial\My Documents\mediasources\ >>> getMediaPath("barbara.jpg") 'C:\\Documents and Settings\\Mark Guzdial\\My Documents\\mediasources\\barbara.jpg' >>> barb=makePicture(getMediaPath("barbara.jpg")) Program to mirror the temple def mirrorTemple(): source = makePicture(getMediaPath("temple.jpg")) mirrorPoint = 276 for x in range(13,mirrorPoint): for y in range(27,97): pleft = getPixel(source,x,y) pright = getPixel(source,mirrorPoint + mirrorPoint - 1 - x,y) setColor(pright,getColor(pleft)) show(source) return source Did it really work? It clearly did the mirroring, but that doesn't create a 100% realistic image. Check out the shadows: Which direction is the sun coming from? Understanding the Temple Fix What is the very first transfer of pixels from and to? Which (x,y) pixel from? Which (x,y) pixel to? What is second? How many pixels get copied? Adding print statements to see what's happening def mirrorTemple(): source = makePicture(getMediaPath("temple.jpg")) mirrorPoint = 276 for x in range(13,mirrorPoint): for y in range(27,97): print "Copying color from",x,y, " to ",mirrorPoint + mirrorPoint - 1 - x, y pleft = getPixel(source,x,y) pright = getPixel(source,mirrorPoint + mirrorPoint - 1 - x,y) setColor(pright,getColor(pleft)) show(source) return source First pixels are either side of the mirrorpoint, then moving down >>> p2=mirrorTemple() Copying color from 13 27 to 538 27 Copying color from 13 28 to 538 28 Copying color from 13 29 to 538 29 Counting pixels def mirrorTemple(): source = makePicture(getMediaPath("temple.jpg")) mirrorPoint = 276 count = 0 for x in range(13,mirrorPoint): for y in range(27,97): pleft = getPixel(source,x,y) pright = getPixel(source,mirrorPoint + mirrorPoint - 1 - x,y) setColor(pright,getColor(pleft)) count = count + 1 show(source) print "We copied",count,"pixels" return source Counting pixels >>> p2=mirrorTemple() We copied 18410 pixels Where did that come from? How many rows? Y goes from 27 to 97 = 70 rows of pixels How many columns? X goes from 13 to 276 = 263 columns of pixels 70 * 263 = 18410 A Which function did A? A B The same function below did the two pictures, but with one line difference. Which one did which picture? Replacing colors in a range Get the range using MediaTools def turnRedInRange(): brown = makeColor(57,16,8) file=“/Users/guzdial/mediasources/barbara.jpg" picture=makePicture(file) for x in range(70,168): for y in range(56,190): px=getPixel(picture,x,y) color = getColor(px) if distance(color,brown)<50.0: redness=getRed(px)*1.5 setRed(px,redness) show(picture) return(picture) Walking this code Like last time: Don't need input, same color we want to change, same file, make a picture def turnRedInRange(): brown = makeColor(57,16,8) file=“/Users/guzdial/mediasources/barbara.jpg" picture=makePicture(file) for x in range(70,168): for y in range(56,190): px=getPixel(picture,x,y) color = getColor(px) if distance(color,brown)<50.0: redness=getRed(px)*1.5 setRed(px,redness) show(picture) return(picture) The nested loop I used MediaTools to find the rectangle where most of the hair is that I want to change def turnRedInRange(): brown = makeColor(57,16,8) file=“/Users/guzdial/mediasources/barbara.jpg“ picture=makePicture(file) for x in range(70,168): for y in range(56,190): px=getPixel(picture,x,y) color = getColor(px) if distance(color,brown)<50.0: redness=getRed(px)*1.5 setRed(px,redness) show(picture) return(picture) Same thing as last time (could raise threshold now) Then we're looking for a close-match on hair color, and increasing the redness def turnRedInRange(): brown = makeColor(57,16,8) file=“/Users/guzdial/mediasources/barbara.jpg“ picture=makePicture(file) for x in range(70,168): for y in range(56,190): px=getPixel(picture,x,y) color = getColor(px) if distance(color,brown)<50.0: redness=getRed(px)*1.5 setRed(px,redness) show(picture) return(picture) Could we do this without nested loops? Yes, but complicated IF def turnRedInRange2(): brown = makeColor(57,16,8) file=“/Users/guzdial/mediasources/barbara.jpg“ picture=makePicture(file) for p in getPixels(picture): x = getX(p) y = getY(p) if x >= 70 and x < 168: if y >=56 and y < 190: color = getColor(p) if distance(color,brown)<100.0: redness=getRed(p)*2.0 setRed(p,redness) show(picture) return picture Moving pixels across pictures We've seen using index variables to track the pixel position we're working with in a picture. We can copy between pictures, if we keep track of: The source index variables Where we're getting the pixels from The target index variables Where we're putting the pixels at (Not really copying the pixels: Replicating their color.) What can you do then? What can you do when copying from one picture to another? Collages: Copy several pictures onto one Cropping: You don't have to take the whole picture Scaling: Make a picture smaller, or larger when copying it Copying pixels In general, what we want to do is to keep track of a sourceX and sourceY, and a targetX and targetY. We increment (add to them) in pairs sourceX and targetX get incremented together sourceY and targetY get incremented together The tricky parts are: Setting values inside the body of loops Incrementing at the bottom of loops Copying Barb to a canvas def copyBarb(): # Set up the source and target pictures barbf=getMediaPath("barbara.jpg") barb = makePicture(barbf) canvasf = getMediaPath("7inX95in.jpg") canvas = makePicture(canvasf) # Now, do the actual copying targetX = 0 for sourceX in range(0,getWidth(barb)): targetY = 0 for sourceY in range(0,getHeight(barb)): color = getColor(getPixel(barb,sourceX,sourceY)) setColor(getPixel(canvas,targetX,targetY), color) targetY = targetY + 1 targetX = targetX + 1 show(barb) show(canvas) return canvas Comments Python ignores from “#” through the rest of the line If you start a line with “#”, the whole line is ignored Why do we want lines to be ignored? To be able to leave notes to ourselves or someone else about how the program works Walking through the copying function First, get the source (barb) and target (canvas) files and pictures as names we can use later. def copyBarb(): # Set up the source and target pictures barbf=getMediaPath("barbara.jpg") barb = makePicture(barbf) canvasf = getMediaPath("7inX95in.jpg") canvas = makePicture(canvasf) # Now, do the actual copying targetX = 0 for sourceX in range(0,getWidth(barb)): targetY = 0 for sourceY in range(0,getHeight(barb)): color = getColor(getPixel(barb,sourceX,sourceY)) setColor(getPixel(canvas,targetX,targetY), color) targetY = targetY + 1 targetX = targetX + 1 show(barb) show(canvas) return canvas The actual copy We get the color of the pixel at sourceX and sourceY We set (copy) the color to the pixel in the target picture at targetX and targetY def copyBarb(): # Set up the source and target pictures barbf=getMediaPath("barbara.jpg") barb = makePicture(barbf) canvasf = getMediaPath("7inX95in.jpg") canvas = makePicture(canvasf) # Now, do the actual copying targetX = 0 for sourceX in range(0,getWidth(barb)): targetY = 0 for sourceY in range(0,getHeight(barb)): color = getColor(getPixel(barb,sourceX,sourceY)) setColor(getPixel(canvas,targetX,targetY), color) targetY = targetY + 1 targetX = targetX + 1 show(barb) show(canvas) return canvas Setting up the copy loop targetX gets set to 0 at the beginning sourceX will range across the width of the source picture INSIDE the loop, we set targetY to 0 Inside because we want it to start at 0 each time we do a new X sourceY will range from 0 to one less height of source def copyBarb(): # Set up the source and target pictures barbf=getMediaPath("barbara.jpg") barb = makePicture(barbf) canvasf = getMediaPath("7inX95in.jpg") canvas = makePicture(canvasf) # Now, do the actual copying targetX = 0 for sourceX in range(0,getWidth(barb)): targetY = 0 for sourceY in range(0,getHeight(barb)): color = getColor(getPixel(barb,sourceX,sourceY)) setColor(getPixel(canvas,targetX,targetY), color) targetY = targetY + 1 targetX = targetX + 1 show(barb) show(canvas) return canvas Ending the loop Just before we end the sourceY loop, we increment targetY It's now set up for the next time through the loop It's set correctly for the next value of sourceY Just before we end the sourceX loop, we increment the targetX Note carefully the indentation to figure out which goes with which loop def copyBarb(): # Set up the source and target pictures barbf=getMediaPath("barbara.jpg") barb = makePicture(barbf) canvasf = getMediaPath("7inX95in.jpg") canvas = makePicture(canvasf) # Now, do the actual copying targetX = 0 for sourceX in range(0,getWidth(barb)): targetY = 0 for sourceY in range(0,getHeight(barb)): color = getColor(getPixel(barb,sourceX,sourceY)) setColor(getPixel(canvas,targetX,targetY), color) targetY = targetY + 1 targetX = targetX + 1 show(barb) show(canvas) return canvas What's this naming something as itself? targetX = targetX + 1 This isn't really naming something as itself targetX + 1 is evaluated It will result in the number after targetX targetX = then sets the value of targetX The result is that targetX gets incremented by 1 Ending the copy function At the very end, we show the source and target And return the modified target. def copyBarb(): # Set up the source and target pictures barbf=getMediaPath("barbara.jpg") barb = makePicture(barbf) canvasf = getMediaPath("7inX95in.jpg") canvas = makePicture(canvasf) # Now, do the actual copying targetX = 0 for sourceX in range(0,getWidth(barb)): targetY = 0 for sourceY in range(0,getHeight(barb)): color = getColor(getPixel(barb,sourceX,sourceY)) setColor(getPixel(canvas,targetX,targetY), color) targetY = targetY + 1 targetX = targetX + 1 show(barb) show(canvas) return canvas Works either way def copyBarb2(): # Set up the source and target pictures barbf=getMediaPath("barbara.jpg") barb = makePicture(barbf) canvasf = getMediaPath("7inX95in.jpg") canvas = makePicture(canvasf) # Now, do the actual copying sourceX = 0 for targetX in range(0,getWidth(barb)): sourceY = 0 for targetY in range(0,getHeight(barb)): color = getColor(getPixel(barb,sourceX,sourceY)) setColor(getPixel(canvas,targetX,targetY), color) sourceY = sourceY + 1 sourceX = sourceX + 1 show(barb) show(canvas) return canvas As long as we increment sourceX and targetX together, and sourceY and targetY together, it doesn't matter which is in the for loop and which is incremented via expression Copying into the middle of the canvas def copyBarbMidway(): # Set up the source and target pictures barbf=getMediaPath("barbara.jpg") barb = makePicture(barbf) canvasf = getMediaPath("7inX95in.jpg") canvas = makePicture(canvasf) # Now, do the actual copying targetX = 100 for sourceX in range(0,getWidth(barb)): targetY = 100 for sourceY in range(0,getHeight(barb)): color = getColor(getPixel(barb,sourceX,sourceY)) setColor(getPixel(canvas,targetX,targetY), color) targetY = targetY + 1 targetX = targetX + 1 show(barb) show(canvas) return canvas Copying a Horse to a Canvas def copyHorse(): # Set up the source and target pictures src = makePicture("horse.jpg") canvas = makeEmptyPicture(1000,1000) # Now, do the actual copying targetX = 0 for sourceX in range(0,getWidth(src)): targetY = 0 for sourceY in range(0,getHeight(src)): color = getColor(getPixel(src,sourceX,sourceY)) setColor(getPixel(canvas,targetX,targetY), color) targetY = targetY + 1 targetX = targetX + 1 show(src) show(canvas) return canvas Copying another way def copyHorse2(): # Set up the source and target pictures src = makePicture("horse.jpg") canvas = makeEmptyPicture(1000,1000) # Now, do the actual copying sourceX = 0 for targetX in range(0,getWidth(src)): sourceY = 0 for targetY in range(0,getHeight(src)): color = getColor(getPixel(src,sourceX,sourceY)) setColor(getPixel(canvas,targetX,targetY), color) sourceY = sourceY + 1 sourceX = sourceX + 1 show(src) show(canvas) return canvas Copying to a new Location def copyHorseMidway(): # Set up the source and target pictures src = makePicture("horse.jpg") canvas = makeEmptyPicture(1000,1000) # Now, do the actual copying targetX = 100 for sourceX in range(0,getWidth(src)): targetY = 200 for sourceY in range(0,getHeight(src)): color = getColor(getPixel(src,sourceX,sourceY)) setColor(getPixel(canvas,targetX,targetY), color) targetY = targetY + 1 targetX = targetX + 1 show(src) show(canvas) return canvas Cropping just the Face def copyHorseFace(): # Set up the source and target pictures src = makePicture("horse.jpg") canvas = makeEmptyPicture(1000,1000) # Now, do the actual copying targetX = 100 for sourceX in range(104,267): targetY = 200 for sourceY in range(114,422): color = getColor(getPixel(src,sourceX,sourceY)) setColor(getPixel(canvas,targetX,targetY), color) targetY = targetY + 1 targetX = targetX + 1 #show(src) show(canvas) return canvas Same cropping, different loops def copyHorseFace2(): # Set up the source and target pictures src=makePicture("horse.jpg") canvas = makeEmptyPicture(1000,1000) # Now, do the actual copying sourceX = 104 for targetX in range(100,100+163): sourceY = 114 for targetY in range(200,200+308): color = getColor(getPixel(src,sourceX,sourceY)) setColor(getPixel(canvas,targetX,targetY), color) sourceY = sourceY + 1 sourceX = sourceX + 1 show(canvas) return canvas Copying: How it works Here's the initial setup: Copying: How it works 2 After incrementing the sourceY and targetY once (whether in the for or via expression): Copying: How it works 3 After yet another increment of sourceY and targetY: When we finish that column, we increment sourceX and targetX, and start on the next column. Copying: How it looks at the end Eventually, we copy every pixel Making a collage Could we do something to the pictures we copy in? Sure! Could either apply one of those functions before copying, or do something to the pixels during the copy. Could we copy more than one picture! Of course! Make a collage! def createCollage(): flower1=makePicture(getMediaPath("flower1.jpg")) print flower1 flower2=makePicture(getMediaPath("flower2.jpg")) print flower2 canvas=makePicture(getMediaPath("640x480.jpg")) print canvas #First picture, at left edge targetX=0 for sourceX in range(0,getWidth(flower1)): targetY=getHeight(canvas)-getHeight(flower1)-5 for sourceY in range(0,getHeight(flower1)): px=getPixel(flower1,sourceX,sourceY) cx=getPixel(canvas,targetX,targetY) setColor(cx,getColor(px)) targetY=targetY + 1 targetX=targetX + 1 #Second picture, 100 pixels over targetX=100 for sourceX in range(0,getWidth(flower2)): targetY=getHeight(canvas)-getHeight(flower2)-5 for sourceY in range(0,getHeight(flower2)): px=getPixel(flower2,sourceX,sourceY) cx=getPixel(canvas,targetX,targetY) setColor(cx,getColor(px)) targetY=targetY + 1 targetX=targetX + 1 Page 170-171 (4ed edition) #Third picture, flower1 negated negative(flower1) targetX=200 for sourceX in range(0,getWidth(flower1)): targetY=getHeight(canvas)-getHeight(flower1)-5 for sourceY in range(0,getHeight(flower1)): px=getPixel(flower1,sourceX,sourceY) cx=getPixel(canvas,targetX,targetY) setColor(cx,getColor(px)) targetY=targetY + 1 targetX=targetX + 1 #Fourth picture, flower2 with no blue clearBlue(flower2) targetX=300 for sourceX in range(0,getWidth(flower2)): targetY=getHeight(canvas)-getHeight(flower2)-5 for sourceY in range(0,getHeight(flower2)): px=getPixel(flower2,sourceX,sourceY) cx=getPixel(canvas,targetX,targetY) setColor(cx,getColor(px)) targetY=targetY + 1 targetX=targetX + 1 #Fifth picture, flower1, negated with decreased red decreaseRed(flower1) targetX=400 for sourceX in range(0,getWidth(flower1)): targetY=getHeight(canvas)-getHeight(flower1)-5 for sourceY in range(0,getHeight(flower1)): px=getPixel(flower1,sourceX,sourceY) cx=getPixel(canvas,targetX,targetY) setColor(cx,getColor(px)) targetY=targetY + 1 targetX=targetX + 1 show(canvas) return(canvas) Can we make that easier? The collage code is long, yet simple. It's the same thing overand-over. We can generalize that copying loop, and with parameters, use it in many places. def copy(source, target, targX, targY): targetX = targX for sourceX in range(0,getWidth(source)): targetY = targY for sourceY in range(0,getHeight(source)): px=getPixel(source,sourceX,sourceY) tx=getPixel(target,targetX,targetY) setColor(tx,getColor(px)) targetY=targetY + 1 targetX=targetX + 1 Exact same collage! def createCollage2(): flower1=makePicture(getMediaPath("flower1.jpg") ) print flower1 flower2=makePicture(getMediaPath("flower2.jpg" )) print flower2 canvas=makePicture(getMediaPath("640x480.jpg" )) print canvas #First picture, at left edge copy(flower1,canvas,0,getHeight(canvas)getHeight(flower1)-5) #Second picture, 100 pixels over copy(flower2,canvas,100,getHeight(canvas)getHeight(flower2)-5) #Third picture, flower1 negated negative(flower1) copy(flower1,canvas,200,getHeight(canvas)getHeight(flower1)-5) #Fourth picture, flower2 with no blue clearBlue(flower2) copy(flower2,canvas,300,getHeight(canvas)getHeight(flower2)-5) #Fifth picture, flower1, negated with decreased red decreaseRed(flower1) copy(flower1,canvas,400,getHeight(canvas)getHeight(flower2)-5) return canvas Make sure you have this Typed IN to Work From: Copy Any Pic Bigger or Smaller? Does this function make the src bigger or smaller on the canvas? 1. Increases size by 50% 2. Decreases size by 50% 3. Decreases size by 50% and skips some pixels in target 4. Increases size by 50% and skips some pixels in target >>> c = makePicture("caterpillar.jpg") >>> copyPicDifferent(c,c1,10,10) >>> explore(c1) >>> copyAnyPic(c,c1,10,10) >>> explore(c1) Transformation = Small changes in copying Making relatively small changes in this basic copying program can make a variety of transformations. Change the targetX and targetY, and you copy wherever you want Cropping: Change the sourceX and sourceY range, and you copy only part of the program. Rotating: Swap targetX and targetY, and you end up copying sideways Scaling: Change the increment on sourceX and sourceY, and you either grow or shrink the image. Copying face to small canvas def copyHorseFaceSmall(): # Set up the source and target pictures src = makePicture("horse.jpg") canvas = makeEmptyPicture(163,308) # Now, do the actual copying targetX = 0 for sourceX in range(104,267): targetY = 0 for sourceY in range(114,422): color = getColor(getPixel(src,sourceX,sourceY)) setColor(getPixel(canvas,targetX,targetY), color) targetY = targetY + 1 targetX = targetX + 1 show(canvas) return canvas Changing while copying def copyHorseFaceSmallBlack(): hcol = makeColor(216,169,143) # Set up the source and target pictures src = makePicture("horse.jpg") canvas = makeEmptyPicture(163,308) # Now, do the actual copying targetX = 0 for sourceX in range(104,267): targetY = 0 for sourceY in range(114,422): color = getColor(getPixel(src,sourceX,sourceY)) if distance(color,hcol) < 40: setColor(getPixel(canvas,targetX,targetY), black) else: setColor(getPixel(canvas,targetX,targetY), color) targetY = targetY + 1 targetX = targetX + 1 show(canvas) Copying vs. Referencing >>> a = 100 >>> b = a >>> print a,b 100 100 >>> b = 200 >>> print a,b 100 200 >>> a = 300 >>> print a,b 300 200 Referencing works differently >>> pictureBarb = makePicture("barbara.jpg") >>> pixel1 = getPixelAt(pictureBarb,0,0) >>> print pixel1 Pixel red=168 green=131 blue=105 >>> anotherpixel = pixel1 >>> print anotherpixel Pixel red=168 green=131 blue=105 >>> setColor(pixel1,black) >>> print pixel1 Pixel red=0 green=0 blue=0 >>> print anotherpixel Pixel red=0 green=0 blue=0 Making a collage Could we do something to the pictures we copy in? Sure! Could either apply one of those functions before copying, or do something to the pixels during the copy. Could we copy more than one picture! Of course! Make a collage! def createCollage(): flower1=makePicture(getMediaPath("flower1.jpg")) print flower1 flower2=makePicture(getMediaPath("flower2.jpg")) print flower2 canvas=makePicture(getMediaPath("640x480.jpg")) print canvas #First picture, at left edge targetX=0 for sourceX in range(0,getWidth(flower1)): targetY=getHeight(canvas)-getHeight(flower1)-5 for sourceY in range(0,getHeight(flower1)): px=getPixel(flower1,sourceX,sourceY) cx=getPixel(canvas,targetX,targetY) setColor(cx,getColor(px)) targetY=targetY + 1 targetX=targetX + 1 #Second picture, 100 pixels over targetX=100 for sourceX in range(0,getWidth(flower2)): targetY=getHeight(canvas)-getHeight(flower2)-5 for sourceY in range(0,getHeight(flower2)): px=getPixel(flower2,sourceX,sourceY) cx=getPixel(canvas,targetX,targetY) setColor(cx,getColor(px)) targetY=targetY + 1 targetX=targetX + 1 Page 91-92 (2ed edition) #Third picture, flower1 negated negative(flower1) targetX=200 for sourceX in range(0,getWidth(flower1)): targetY=getHeight(canvas)-getHeight(flower1)-5 for sourceY in range(0,getHeight(flower1)): px=getPixel(flower1,sourceX,sourceY) cx=getPixel(canvas,targetX,targetY) setColor(cx,getColor(px)) targetY=targetY + 1 targetX=targetX + 1 #Fourth picture, flower2 with no blue clearBlue(flower2) targetX=300 for sourceX in range(0,getWidth(flower2)): targetY=getHeight(canvas)-getHeight(flower2)-5 for sourceY in range(0,getHeight(flower2)): px=getPixel(flower2,sourceX,sourceY) cx=getPixel(canvas,targetX,targetY) setColor(cx,getColor(px)) targetY=targetY + 1 targetX=targetX + 1 #Fifth picture, flower1, negated with decreased red decreaseRed(flower1) targetX=400 for sourceX in range(0,getWidth(flower1)): targetY=getHeight(canvas)-getHeight(flower1)-5 for sourceY in range(0,getHeight(flower1)): px=getPixel(flower1,sourceX,sourceY) cx=getPixel(canvas,targetX,targetY) setColor(cx,getColor(px)) targetY=targetY + 1 targetX=targetX + 1 show(canvas) return(canvas) Can we make that easier? The collage code is long, yet simple. It's the same thing overand-over. We can generalize that copying loop, and with parameters, use it in many places. def copy(source, target, targX, targY): targetX = targX for sourceX in range(0,getWidth(source)): targetY = targY for sourceY in range(0,getHeight(source)): px=getPixel(source,sourceX,sourceY) tx=getPixel(target,targetX,targetY) setColor(tx,getColor(px)) targetY=targetY + 1 targetX=targetX + 1 Exact same collage! def createCollage2(): flower1=makePicture(getMediaPath("flower1.jpg") ) print flower1 flower2=makePicture(getMediaPath("flower2.jpg" )) print flower2 canvas=makePicture(getMediaPath("640x480.jpg" )) print canvas #First picture, at left edge copy(flower1,canvas,0,getHeight(canvas)getHeight(flower1)-5) #Second picture, 100 pixels over copy(flower2,canvas,100,getHeight(canvas)getHeight(flower2)-5) #Third picture, flower1 negated negative(flower1) copy(flower1,canvas,200,getHeight(canvas)getHeight(flower1)-5) #Fourth picture, flower2 with no blue clearBlue(flower2) copy(flower2,canvas,300,getHeight(canvas)getHeight(flower2)-5) #Fifth picture, flower1, negated with decreased red decreaseRed(flower1) copy(flower1,canvas,400,getHeight(canvas)getHeight(flower2)-5) return canvas Rotating the copy def flipHorseSideways(): # Set up the source and target pictures src = makePicture("horse.jpg") canvas = makeEmptyPicture(1000,1000) # Now, do the actual copying targetX = 0 for sourceX in range(0,getWidth(src)): targetY = 0 for sourceY in range(0,getHeight(src)): color = getColor(getPixel(src,sourceX,sourceY)) # Change is here setColor(getPixel(canvas,targetY,targetX), color) targetY = targetY + 1 targetX = targetX + 1 show(canvas) return canvas Rotating: How it works We increment the same, but we use targetX for the Y coordinate and targetY for the X coordinate Rotate: How it ends Same amount of increment, even same values in the variables, but a different result. Doing a real rotation def rotateHorseSideways(): # Set up the source and target pictures src = makePicture("horse.jpg") canvas = makeEmptyPicture(1000,1000) # Now, do the actual copying targetX = 0 width = getWidth(src) for sourceX in range(0,getWidth(src)): targetY = 0 for sourceY in range(0,getHeight(src)): color = getColor(getPixel(src,sourceX,sourceY)) # Change is here setColor(getPixel(canvas,targetY,width - targetX 1), color) targetY = targetY + 1 targetX = targetX + 1 show(canvas) return canvas Scaling Scaling a picture (smaller or larger) has to do with sampling the source picture differently When we just copy, we sample every pixel If we want a smaller copy, we skip some pixels We sample fewer pixels If we want a larger copy, we duplicate some pixels We over-sample some pixels Scaling the horse face down def copyHorseFaceSmaller(): # Set up the source and target pictures src=makePicture("horse.jpg") canvas = makeEmptyPicture(82,155) # Now, do the actual copying sourceX = 104 for targetX in range(0,int(163/2)): sourceY = 114 for targetY in range(0,int(308/2)): color = getColor(getPixel(src,sourceX,sourceY)) setColor(getPixel(canvas,targetX,targetY), color) sourceY = sourceY + 2 sourceX = sourceX + 2 show(canvas) return canvas Scaling the picture down def copyBarbsFaceSmaller(): # Set up the source and target pictures barbf=getMediaPath("barbara.jpg") barb = makePicture(barbf) canvasf = getMediaPath("7inX95in.jpg") canvas = makePicture(canvasf) # Now, do the actual copying sourceX = 45 for targetX in range(100,100+((200-45)/2)): sourceY = 25 for targetY in range(100,100+((200-25)/2)): color = getColor(getPixel(barb,sourceX,sourceY)) setColor(getPixel(canvas,targetX,targetY), color) sourceY = sourceY + 2 sourceX = sourceX + 2 show(barb) show(canvas) return canvas Scaling Up: Growing the picture To grow a picture, we simply duplicate some pixels We do this by incrementing by 0.5, but only use the integer part. >>> print int(1) 1 >>> print int(1.5) 1 >>> print int(2) 2 >>> print int(2.5) 2 Scaling up def copyHorseLarger(): # Set up the source and target pictures src = makePicture("horse.jpg") w = getWidth(src) h = getHeight(src) canvas = makeEmptyPicture(w*2,h*2) srcPixels = getPixels(src) trgPixels = getPixels(canvas) trgIndex = 0 # Now, do the actual copying for pixel in srcPixels: color = getColor(pixel) # Once trgPixel = trgPixels[trgIndex] setColor(trgPixel,color) trgIndex = trgIndex + 1 # Twice trgPixel = trgPixels[trgIndex] setColor(trgPixel,color) trgIndex = trgIndex + 1 show(canvas) return canvas Scaling up just the Face def copyHorseFaceLarger(): # Set up the source and target pictures src=makePicture("horse.jpg") canvas = makeEmptyPicture(163*2,308*2) # Now, do the actual copying sourceX = 104 for targetX in range(0,163*2): sourceY = 114 for targetY in range(0,308*2): srcpx = getPixel(src,int(sourceX),int(sourceY)) color = getColor(srcpx) setColor(getPixel(canvas,targetX,targetY), color) sourceY = sourceY + 0.5 sourceX = sourceX + 0.5 show(canvas) return canvas Scaling the picture up def copyBarbsFaceLarger(): # Set up the source and target pictures barbf=getMediaPath("barbara.jpg") barb = makePicture(barbf) canvasf = getMediaPath("7inX95in.jpg") canvas = makePicture(canvasf) # Now, do the actual copying sourceX = 45 for targetX in range(100,100+((200-45)*2)): sourceY = 25 for targetY in range(100,100+((200-25)*2)): color = getColor(getPixel(barb,int(sourceX),int(sourceY))) setColor(getPixel(canvas,targetX,targetY), color) sourceY = sourceY + 0.5 sourceX = sourceX + 0.5 show(barb) show(canvas) return canvas Scaling up: How it works Same basic setup as copying and rotating: Scaling up: How it works 2 But as we increment by only 0.5, and we use the int() function, we end up taking every pixel twice. Here, the blank pixel at (0,0) in the source gets copied twice onto the canvas. Scaling up: How it works 3 Black pixels gets copied once… Scaling up: How it works 4 And twice… Scaling up: How it ends up We end up in the same place in the source, but twice as much in the target. Notice the degradation: Gaps that weren't there previously Curves would get “choppy”: Pixelated What to do? How do we clear up the degradation of scaling up? Variety of techniques, but mostly following the same basic idea: Use the pixels around to figure out what color a new pixel should be, then somehow (e.g., by averaging) compute the right color. Different techniques look at different pixels and compute different averages in different ways. A simple Blur def blur(source): target=duplicatePicture(source) for x in range(1, getWidth(source)-1): for y in range(1, getHeight(source)-1): top = getPixel(source,x,y-1) left = getPixel(source,x-1,y) bottom = getPixel(source,x,y+1) right = getPixel(source,x+1,y) center = getPixel(target,x,y) newRed=(getRed(top)+ getRed(left) + getRed(bottom) + getRed(right) + getRed(center))/5 newGreen=(getGreen(top) + getGreen(left) + getGreen(bottom)+getGreen(right)+getGreen(center))/5 newBlue=(getBlue(top) + getBlue(left) + getBlue(bottom) + getBlue(right)+ getBlue(center))/5 setColor(center, makeColor(newRed, newGreen, newBlue)) A different blur We'll see pass and else def blur(pic,size): later, but you can for pixel in getPixels(pic): currentX = getX(pixel) probably get a sense currentY = getY(pixel) here of what's going r=0 g=0 on. b=0 count = 0 for x in range(currentX - size,currentX + size): for y in range(currentY - size, currentY + size): if(x<0) or (y<0) or (x >= getWidth(pic)) or (y >=getHeight(pic)): pass # Skip if we go off the edge else: r = r + getRed(getPixel(pic,x,y)) g = g + getGreen(getPixel(pic,x,y)) b = b + getBlue(getPixel(pic,x,y)) count = count + 1 newColor = makeColor(r/count,g/count,b/count) setColor(pixel,newColor) Blurring out the pixelation Average from four sides, to compute new color Things to try: Can you come up with general copy, rotate, copy, and scale functions? Take input pictures and parameters Return the canvas the correct transformation applied Also think about generalizing the transformations: Scaling up and down by non-integer amounts Rotating by something other than 90 degree increments Blending pictures How do we get part of one picture and part of another to blur together, so that we see some of each? It's about making one a bit “transparent.” Video cards sometimes support this transparency in hardware, called an alpha level to each pixel. We do it as a weighted sum If it's 50-50, we take 50% of red of picture1's pixels + 50% of red of picture2's pixels, and so on for green and blue, across all overlapping pixels. Example blended picture Blended here Blending code (1 of 3) def blendPictures(): barb = makePicture(getMediaPath("barbara.jpg")) katie = makePicture(getMediaPath("Katie-smaller.jpg")) canvas = makePicture(getMediaPath("640x480.jpg")) #Copy first 150 columns of Barb sourceX=0 Straightforward copy of for targetX in range(0,150): 150 column's of Barb's sourceY=0 picture for targetY in range(0,getHeight(barb)): color = getColor(getPixel(barb,sourceX,sourceY)) setColor(getPixel(canvas,targetX,targetY),color) sourceY = sourceY + 1 sourceX = sourceX + 1 Blending code (2 of 3) #Now, grab the rest of Barb and part of Katie # at 50% Barb and 50% Katie overlap = getWidth(barb)-150 sourceX=0 for targetX in range(150,getWidth(barb)): sourceY=0 for targetY in range(0,getHeight(katie)): bPixel = getPixel(barb,sourceX+150,sourceY) kPixel = getPixel(katie,sourceX,sourceY) newRed= 0.50*getRed(bPixel)+0.50*getRed(kPixel) newGreen=0.50*getGreen(bPixel)+0.50*getGreen(kPixel ) newBlue = 0.50*getBlue(bPixel)+0.50*getBlue(kPixel) color = makeColor(newRed,newGreen,newBlue) setColor(getPixel(canvas,targetX,targetY),color) sourceY = sourceY + 1 sourceX = sourceX + 1 Here's the trick. For each pixel, grab 50% of each red, green and blue Blending code (3 of 3) # Last columns of Katie sourceX=overlap for targetX in range(150+overlap,150+getWidth(katie)): sourceY=0 for targetY in range(0,getHeight(katie)): color = getColor(getPixel(katie,sourceX,sourceY)) setColor(getPixel(canvas,targetX,targetY),color) sourceY = sourceY + 1 sourceX = sourceX + 1 show(canvas) return canvas def lineExample(): img = makePicture(pickAFile()) verticalLines(img) horizontalLines(img) show(img) return img Drawing lines on Carolina def horizontalLines(src): for x in range(0,getHeight(src),5): for y in range(0,getWidth(src)): setColor(getPixel(src,y,x),black) def verticalLines(src): for x in range(0,getWidth(src),5): for y in range(0,getHeight(src)): setColor(getPixel(src,x,y),black) We can use the color name “black” – it's pre-defined for us. Yes, some colors are already defined Colors defined for you already: black, white, blue, red, green, gray, lightGray, darkGray, yellow, orange, pink, magenta, and cyan That's tedious That's slow and tedious to set every pixel you want to make lines and text, etc. What you really want to do is to think in terms of your desired effect (think about “requirements” and “design”) New functions addText(pict,x,y,string) puts the string starting at position (x,y) in the picture addLine(picture,x1,y1,x2,y2) draws a line from position (x1,y1) to (x2,y2) addRect(pict,x1,y1,w,h) draws a black rectangle (unfilled) with the upper left hand corner of (x1,y1) and a width of w and height of h addRectFilled(pict,x1,y1,w,h,color) draws a rectangle filled with the color of your choice with the upper left hand corner of (x1,y1) and a width of w and height of h The mysterious red box on the beach def addABox(): beach = makePicture(getMediaPath("beach-smaller.jpg")) addRectFilled(beach,150,150,50,50,red) show(beach) return beach Example picture def littlepicture(): canvas=makePicture(getMediaPath("640x480.jpg")) addText(canvas,10,50,"This is not a picture") addLine(canvas,10,20,300,50) addRectFilled(canvas,0,200,300,500,yellow) addRect(canvas,10,210,290,490) return canvas A thought experiment Look at that previous page: Which has a fewer number of bytes? The program that drew the picture The pixels in the picture itself. It's a no-brainer The program is less than 100 characters (100 bytes) The picture is stored on disk at about 15,000 bytes Vector-based vs. Bitmap Graphical representations Vector-based graphical representations are basically executable programs that generate the picture on demand. Postscript, Flash, and AutoCAD use vector-based representations Bitmap graphical representations (like JPEG, BMP, GIF) store individual pixels or representations of those pixels. JPEG and GIF are actually compressed representations Vector-based representations can be smaller Vector-based representations can be much smaller than bit-mapped representations Smaller means faster transmission (Flash and Postscript) If you want all the detail of a complex picture, no, it's not. But vector-based has more value than that Imagine that you're editing a picture with lines on it. If you edit a bitmap image and extend a line, it's just more bits. There's no way to really realize that you've extended or shrunk the line. If you edit a vector-based image, it's possible to just change the specification Change the numbers saying where the line is Then it really is the same line That's important when the picture drives the creation of the product, like in automatic cutting machines How are images compressed? Sometimes lossless using techniques like run length encoding (RLE) Instead of this: BBYYYYYYYYYBB We could say “9 Y's” like this: BB9YBB Lossy compression (like JPEG and GIF) loses detail, some of which is invisible to the eye. When changing the picture means changing a program… In a vector-based drawing package, changing the drawing is changing a program. How could we reach in and change the actual program? We can using string manipulation The program is just a string of characters We want to manipulate those characters, in order to manipulate the program Example programmed graphic If I did this right, we perceive the left half as lighter than the right half In reality, the end quarters are actually the same colors. Building a programmed graphic def greyEffect(): # Third, 100 colums of increasing greyness, from file = getMediaPath("640x480.jpg") 0 pic = makePicture(file) greyLevel = 0 # First, 100 columns of 100-grey for x in range(200,300): grey = makeColor(100,100,100) grey = makeColor(greyLevel, greyLevel, for x in range(1,100): greyLevel) for y in range(1,100): for y in range(1,100): setColor(getPixel(pic,x,y),grey) setColor(getPixel(pic,x,y),grey) # Second, 100 columns of increasing greyness greyLevel = greyLevel + 1 greyLevel = 100 # Finally, 100 columns of 100-grey for x in range(100,200): grey = makeColor(100,100,100) grey = makeColor(greyLevel, greyLevel, for x in range(300,400): greyLevel) for y in range(1,100): for y in range(1,100): setColor(getPixel(pic,x,y),grey) setColor(getPixel(pic,x,y),grey) return pic greyLevel = greyLevel + 1 Another Programmed Graphic def coolpic(): canvas=makePicture(getMediaPath("640x480.jpg")) for index in range(25,1,-1): color = makeColor(index*10,index*5,index) addRectFilled(canvas,0,0,index*10,index*10,color) show(canvas) return canvas And another def coolpic2(): canvas=makePicture(getMediaPath("640x480.jpg")) for index in range(25,1,-1): addRect(canvas,index,index,index*3,index*4) addRect(canvas,100+index*4,100+index*3,index*8,index*10) show(canvas) return canvas Why do we write programs? Could we do this in Photoshop? Maybe I'm sure that you can, but you need to know how. Could I teach you to do this in Photoshop? Maybe Might take a lot of demonstration But this program is an exact definition of the process of generating this picture It works for anyone who can run the program, without knowing Photoshop We write programs to encapsulate and communicate process If you can do it by hand, do it. If you need to teach someone else to do it, consider a program. If you need to explain to lots of people how to do it, definitely use a program. If you want lots of people to do it without having to teach them something first, definitely use a program.