Skip to content

Instantly share code, notes, and snippets.

@axiomsofchoice
Created March 19, 2011 00:22
Show Gist options
  • Save axiomsofchoice/877070 to your computer and use it in GitHub Desktop.
Save axiomsofchoice/877070 to your computer and use it in GitHub Desktop.
Using a mask and animation to demonstrate Moire patterns
Display the source blob
Display the rendered blob
Raw
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="1000px" height="1000px" viewBox="0 0 1000 1000" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
onload="setupPage();">
<!-- Based on http://www.youtube.com/watch?v=SvNjsOl1Ag0 -->
<desc>Using a mask and animation to demonstrate Moire patterns</desc>
<script type="application/ecmascript"> <![CDATA[
// These variable have global scope since it's tricky to
// put them into the recursive function call expression to setTimeout()
var screenPattern;
var internalRect;
var shiftAmount = 96;
var animationIncrement = 100;
// Recursively call this function to perform the animation
function animateShift() {
var x = parseInt(screenPattern.getAttribute("x"));
screenPattern.setAttribute("x", x+1);
if(shiftAmount>=1) {
shiftAmount = shiftAmount - 1 ;
setTimeout ( "animateShift()", animationIncrement );
}
}
function setupPage() {
screenPattern = document.getElementById("screenPattern");
animateShift();
}
]]> </script>
<defs>
<!-- Screen pattern of vertical bars of widths black:white :: 6:1 -->
<pattern id="screenPattern" x="0" y="0" width="7" height="1"
patternUnits="userSpaceOnUse">
<rect x="0" y="0" width="6" height="1" fill="black" />
</pattern>
<!-- An inverse of the screen pattern to be applied to various frame of the animation -->
<pattern id="inverseScreenPattern" x="0" y="0" width="7" height="1"
patternUnits="userSpaceOnUse">
<rect x="6" y="0" width="1" height="1" fill="white" opacity="1" />
</pattern>
<mask id="screenMask" maskUnits="userSpaceOnUse"
x="0" y="0" width="800" height="300">
<rect x="0" y="0" width="800" height="300" fill="url(#inverseScreenPattern)" />
</mask>
<!-- The "base" circle upon which each frame of the animation will modify -->
<circle id="myCircle" cx="40" cy="40" r="30" fill="black" />
</defs>
<!-- Draw the various frames and apply the screen over the top. -->
<use xlink:href="#myCircle" transform="translate(20,20)" mask="url(#screenMask)" />
<use xlink:href="#myCircle" transform="translate(40,40)" mask="url(#screenMask)" />
<use xlink:href="#myCircle" transform="translate(60,60)" mask="url(#screenMask)" />
<use xlink:href="#myCircle" transform="translate(80,80)" mask="url(#screenMask)" />
<use xlink:href="#myCircle" transform="translate(100,100)" mask="url(#screenMask)" />
<use xlink:href="#myCircle" transform="translate(120,120)" mask="url(#screenMask)" />
<rect id="myRect" x="0" y="0" width="800" height="300" fill="url(#screenPattern)" />
</svg>
import Image, ImageSequence, ImageChops, ImageDraw
import itertools
# For details on PIL: http://www.pythonware.com/library/pil/handbook/index.htm
# Note that we're not using the alpha channel here, just the standard RGB
# Some good source images
# http://upload.wikimedia.org/wikipedia/commons/b/b4/Vortex-street-animation.gif
# http://www.bluffton.edu/~bergerd/classes/cem221/sn-e/SN2.gif
# http://upload.wikimedia.org/wikipedia/commons/8/81/ADN_animation.gif
# http://www.sci.sdsu.edu/multimedia/mitosis/mitosis.gif
# ????
# This gives the number of frames in the final animation
resultNumFrames = 8
# Get the animation file and determine it's size
animations = { "vortex": Image.open("Vortex-street-animation.gif"),
"sn2": Image.open("SN2.gif"),
"dna": Image.open("ADN_animation.gif"),
"mitosis": Image.open("mitosis.gif") }
for (n, a) in animations.iteritems():
print "%s has %s\n" % (n, a.info)
im = animations["mitosis"]
(xsize, ysize) = im.size
# Extract every resultNumFrames of the frames of the animation
rawSourceFrames = [frame.convert("RGB") for frame in ImageSequence.Iterator(im)]
sourceFrames = [frame.copy() for frame in rawSourceFrames[:resultNumFrames]]
for (frame,i) in zip(sourceFrames, range(len(sourceFrames))):
frame.save("frame"+str(i)+".PNG")
def buildGridAndMasks(numframes, xsize, ysize):
""" This helper function builds a grid viewer image and a collection of
frame mask images to be applied to the """
# First we build the grid
grid = Image.new("RGB", (xsize, ysize), (256,256,256))
maskDraw = ImageDraw.Draw(grid, "RGB")
# Draw the lines of the grid
for i in range(xsize / numframes):
maskDraw.rectangle([ (i*numframes, 0),
(i*numframes + (resultNumFrames-2), ysize)],
fill=(0,0,0))
del maskDraw
# Next we make the masks, along the same principles, but shift
# the position by one for each frame
masks = []
for fi in range(numframes):
mask = Image.new("RGBA", (xsize, ysize), (0,0,0,0))
maskDraw = ImageDraw.Draw(mask, "RGBA")
# Draw the lines of the mask
for i in range(xsize / numframes):
maskDraw.rectangle([ (i*numframes - fi, 0),
(i*numframes - fi + (resultNumFrames-2), ysize) ],
fill=(0,0,0,256))
del maskDraw
masks.append(mask)
return (grid, masks)
(grid,masks) = buildGridAndMasks(resultNumFrames, xsize, ysize)
blankcanvas = Image.new("RGB", (xsize, ysize), (0,0,0))
# We will accumulate the results to a new image, rather than using the
# original animate gif
intermediates = map(lambda (image1, image2) : Image.composite(blankcanvas, image1, image2),
zip(sourceFrames, masks) )
# Save the intermediates (for debugging)
map( lambda (a,b) : a.save("foo-"+str(b)+".png"), zip (intermediates, range(len(intermediates))))
compositeIm = reduce( lambda img1, img2 : ImageChops.add(img1, img2), intermediates)
# Save the grid image and final composite image
grid.save("grid-1.PNG")
compositeIm.save("moire-1.PNG")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment