Chapter 6: Modifying Pixels by Position

advertisement
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.
Download