├── README.md ├── example.sh ├── glmagic.py ├── magic-shark.png ├── magic-teapot.png ├── magicpy.py ├── make_movie.sh ├── sample ├── pattern1.gif ├── pattern2.gif ├── pattern3.gif └── pattern4.gif ├── shark.png ├── teapot.gif └── teapot.png /README.md: -------------------------------------------------------------------------------- 1 | magicpy 2 | ======= 3 | An autostereogram (MagicEye) image generator written in Python. 4 | 5 | Description 6 | ----------- 7 | Takes a grayscale depthmap (where white is closest, black is farthest), and generates a [random dot autostereogram](http://en.wikipedia.org/wiki/Autostereogram) (MagicEye) image of the same size. 8 | 9 | By default, a random pattern 1/8 the size of the depthmap is repeated and offset to create the effect. There seems to be an art to choosing the right pattern size. 10 | 11 | If you string together multiple stereograms, you can make [cool "MagicEye" movies!](http://synesthesiam.com/code.php#stereograms) 12 | 13 | Examples 14 | -------- 15 | # Use default settings (1/8 sized pattern) 16 | $ python magicpy.py shark.png -o magic-shark.png 17 | 18 | # Make random pattern 1/10 of the depthmap size 19 | $ python magicpy.py -p 10 shark.png -o magic-shark.png 20 | 21 | ![shark.png](https://raw.github.com/synesthesiam/magicpy/master/shark.png) 22 | ![magic-shark.png](https://raw.github.com/synesthesiam/magicpy/master/magic-shark.png) 23 | 24 | 25 | glmagic 26 | ======= 27 | An OpenGL-based depth map generator (used to make frames for a stereogram movie). 28 | 29 | Description 30 | ----------- 31 | Renders frames from an OpenGL scene to a series of depthmaps that can be converted and combined into a stereogram movie. 32 | You can make your own movies by modifying the **render** function inside glmagic.py. 33 | 34 | Examples 35 | -------- 36 | # Make depthmap frames with the GLUT teapot 37 | $ python glmagic.py 38 | 39 | # Generate an animated GIF of the teapot 40 | $ ./make-movie.sh 41 | 42 | ![teapot.png](https://raw.github.com/synesthesiam/magicpy/master/teapot.png) 43 | ![magic-teapot.png](https://raw.github.com/synesthesiam/magicpy/master/magic-teapot.png) 44 | -------------------------------------------------------------------------------- /example.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | python magicpy.py shark.png -o magic-shark.png 3 | -------------------------------------------------------------------------------- /glmagic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2012 Michael Hansen (mihansen@indiana.edu) 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import argparse, math, numpy as np 24 | from PIL import Image 25 | from OpenGL.GL import * 26 | from OpenGL.GLUT import * 27 | 28 | snap_index = 1 29 | angle = 0.0 30 | 31 | def save_snapshot(args, index, format_str): 32 | # Grab depth buffer (grayscale) 33 | pixel_str = glReadPixels(0, 0, args.width, args.height, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE) 34 | depth = Image.fromstring("L", (args.width, args.height), pixel_str) 35 | data = np.array(depth) 36 | 37 | # Normalize and invert (make white closest) 38 | data = 255 - ((data - data.min()) * (255/data.max())) 39 | 40 | # Save to image file 41 | depth = Image.fromarray(data, mode="L") 42 | depth.save(format_str.format(args.prefix, index, args.extension)) 43 | 44 | def render(args, window, format_str): 45 | global snap_index, angle 46 | 47 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) 48 | glLoadIdentity() 49 | 50 | # ===== Render a teapot ===== 51 | glRotate(angle, 1, 1, 1) 52 | glutSolidTeapot(0.5) 53 | # =========================== 54 | 55 | glutSwapBuffers() 56 | 57 | if args.snapshots > 0: 58 | # Save snapshot and advance frame 59 | save_snapshot(args, snap_index, format_str) 60 | angle = (angle + args.rotation) % 360 61 | args.snapshots -= 1 62 | snap_index += 1 63 | glutPostRedisplay() 64 | else: 65 | glutDestroyWindow(window) 66 | 67 | 68 | if __name__ == "__main__": 69 | 70 | # Parse command-line arguments 71 | parser = argparse.ArgumentParser(description = "OpenGL autostereogram (MagicEye) generator") 72 | parser.add_argument("-s", "--snapshots", type=int, default=36, 73 | help="Number of snapshots to take") 74 | 75 | parser.add_argument("-r", "--rotation", type=float, default=10.0, 76 | help="Amount to increase rotation angle by after each shot") 77 | 78 | parser.add_argument("-p", "--prefix", type=str, default="snapshot_", 79 | help="Prefix for generated stereogram images") 80 | 81 | parser.add_argument("-e", "--extension", type=str, default="png", 82 | help="File extension for generated stereogram images") 83 | 84 | parser.add_argument("--width", type=int, default=800, 85 | help="Width of window and output image") 86 | 87 | parser.add_argument("--height", type=int, default=600, 88 | help="Height of window and output image") 89 | 90 | args = parser.parse_args() 91 | 92 | # Set up GLUT 93 | glutInit([]) 94 | glutInitWindowSize(args.width, args.height) 95 | glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE) 96 | window = glutCreateWindow("glMagic") 97 | 98 | # Format string for snapshot images 99 | num_zeros = int(math.ceil(math.log10(args.snapshots))) 100 | format_str = "{{0}}{{1:0{0}d}}.{{2}}".format(num_zeros) 101 | 102 | display = lambda: render(args, window, format_str) 103 | glutDisplayFunc(display) 104 | 105 | # Set up OpenGL 106 | glClearColor(0, 0, 0, 0) 107 | glMatrixMode(GL_PROJECTION); 108 | glLoadIdentity(); 109 | glMatrixMode(GL_MODELVIEW); 110 | glLoadIdentity(); 111 | 112 | # Make it pretty to look at 113 | glClearDepth(1.0) 114 | glShadeModel(GL_SMOOTH) 115 | glMatrixMode(GL_PROJECTION) 116 | glEnable(GL_LIGHTING) 117 | glEnable(GL_LIGHT0) 118 | glMaterialfv(GL_FRONT, GL_AMBIENT, (1, 1, 0, 0)) 119 | glLight(GL_LIGHT0, GL_POSITION, (1, 1, 1, 0)) 120 | 121 | glEnable(GL_DEPTH_TEST) 122 | glDepthFunc(GL_LEQUAL) 123 | glDepthRange(0, 5) 124 | 125 | glutMainLoop() 126 | -------------------------------------------------------------------------------- /magic-shark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synesthesiam/magicpy/4275577e05070137e5affce45b1c6974197264fc/magic-shark.png -------------------------------------------------------------------------------- /magic-teapot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synesthesiam/magicpy/4275577e05070137e5affce45b1c6974197264fc/magic-teapot.png -------------------------------------------------------------------------------- /magicpy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2012 Michael Hansen (mihansen@indiana.edu) 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import numpy, argparse 24 | from PIL import Image 25 | 26 | def gen_pattern(width, height): 27 | return numpy.random.randint(0, 256, (width, height)) 28 | 29 | if __name__ == '__main__': 30 | 31 | parser = argparse.ArgumentParser(description = "Autostereogram (MagicEye) generator") 32 | parser.add_argument("depthmap", type=str, 33 | help = "Path to grayscale depth-map (white = close)") 34 | 35 | parser.add_argument("-o", "--output", type=str, default="output.png", 36 | help="Path to write output image") 37 | 38 | parser.add_argument("-p", "--pattern-div", type=int, default=8, 39 | help = "Width of generated pattern (width n means 1/n of depth-map width)") 40 | 41 | parser.add_argument("-i", "--invert", action="store_true", help = "Invert depthmap (white = far)") 42 | 43 | args = parser.parse_args() 44 | invert = -1 if args.invert else 1 45 | 46 | depth_map = Image.open(args.depthmap).convert("RGB") 47 | depth_data = depth_map.load() 48 | 49 | out_img = Image.new("L", depth_map.size) 50 | out_data = out_img.load() 51 | 52 | pattern_width = depth_map.size[0] / args.pattern_div 53 | pattern = gen_pattern(pattern_width, depth_map.size[1]) 54 | 55 | # Create stereogram 56 | for x in xrange(0, depth_map.size[0]): 57 | for y in xrange(0, depth_map.size[1]): 58 | 59 | if x < pattern_width: 60 | out_data[x, y] = pattern[x, y] # Use generated pattern 61 | else: 62 | shift = depth_data[x, y][0] / args.pattern_div # 255 is closest 63 | out_data[x, y] = out_data[x - pattern_width + (shift * invert), y] 64 | 65 | out_img.save(args.output) 66 | 67 | -------------------------------------------------------------------------------- /make_movie.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | PREFIX=snapshot_ 3 | EXT=png 4 | PROCS=4 5 | 6 | # Use glmagic.py to make all of the depth maps 7 | echo "Generating depth maps..." 8 | python glmagic.py -p $PREFIX -e $EXT 9 | 10 | # Convert each depth map into a stereogram with magicpy.py (in parallel with xargs) 11 | echo "Converting depth maps to stereograms ($PROCS threads)..." 12 | ls -1 $PREFIX*.$EXT | xargs -I {} -n1 -P$PROCS python magicpy.py {} -o magic-{} 13 | 14 | # Use ImageMagick to roll up all of the stereograms into an animated GIF 15 | echo "Generating animated GIF..." 16 | convert -delay 20 -loop 0 magic-$PREFIX*.$EXT movie.gif 17 | 18 | # Get rid of the depth maps and stereogram frames 19 | echo "Deleting frames" 20 | rm $PREFIX*.$EXT 21 | rm magic-$PREFIX*.$EXT 22 | -------------------------------------------------------------------------------- /sample/pattern1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synesthesiam/magicpy/4275577e05070137e5affce45b1c6974197264fc/sample/pattern1.gif -------------------------------------------------------------------------------- /sample/pattern2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synesthesiam/magicpy/4275577e05070137e5affce45b1c6974197264fc/sample/pattern2.gif -------------------------------------------------------------------------------- /sample/pattern3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synesthesiam/magicpy/4275577e05070137e5affce45b1c6974197264fc/sample/pattern3.gif -------------------------------------------------------------------------------- /sample/pattern4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synesthesiam/magicpy/4275577e05070137e5affce45b1c6974197264fc/sample/pattern4.gif -------------------------------------------------------------------------------- /shark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synesthesiam/magicpy/4275577e05070137e5affce45b1c6974197264fc/shark.png -------------------------------------------------------------------------------- /teapot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synesthesiam/magicpy/4275577e05070137e5affce45b1c6974197264fc/teapot.gif -------------------------------------------------------------------------------- /teapot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synesthesiam/magicpy/4275577e05070137e5affce45b1c6974197264fc/teapot.png --------------------------------------------------------------------------------