Friday, July 30, 2010

OpenGL Intellivision Man

Not long ago I set out to turn an animated gif I found of the Intellivision Running Man into an OpenGL generated video clip. My goal was to shrink the images into tiny bitmaps, a.k.a. 8-bit, and then use the resulting bitmap data as positioning information within a 3D space. Another way of saying it: I wanted to render each pixel from the original images as a 3D block. Jump to the video to get a better idea about what I mean:

http://www.youtube.com/watch?v=E1rXt0l2pxY

Along the way I took a few turns. I never did reduce the image data to its smallest possible representation. I did reduce the image size for each image to 24x24 pixels and I converted the image data to grayscale. Using the handy PIL library, I was able to pull in the bitmap file data as a tuple of pixel values. I used each "pixel" to offset the drawing position in pyglet's on_draw handler.

The code I used to render the animation is a hack of an example found here:

http://code.google.com/p/pyglet-hene/source/browse/trunk/

This is a fairly ugly hack, meaning I didn't work to make the code beautiful. It's just the original code, chopped, altered and enhanced as needed to accomplish what I wanted to see rendered on the screen.

Though not provided here, to run this code an image directory needs to be provided named "data" containing a series of bitmaps. My directory contains this:


ls -ltr
total 32
-rw-r--r-- 1 dvenable dvenable 1654 2010-07-20 14:06 9.bmp
-rw-r--r-- 1 dvenable dvenable 1654 2010-07-20 14:06 8.bmp
-rw-r--r-- 1 dvenable dvenable 1654 2010-07-20 14:06 6.bmp
-rw-r--r-- 1 dvenable dvenable 1654 2010-07-20 14:06 5.bmp
-rw-r--r-- 1 dvenable dvenable 1654 2010-07-20 14:06 4.bmp
-rw-r--r-- 1 dvenable dvenable 1654 2010-07-20 14:06 3.bmp
-rw-r--r-- 1 dvenable dvenable 1654 2010-07-20 14:06 2.bmp
-rw-r--r-- 1 dvenable dvenable 1654 2010-07-20 14:06 1.bmp


If you are so inclined to try your own little experiment, find a favorite animated gif on the web, extract each frame, reduce size, convert to grayscale and drop your own images in a directory similar to the one I created here. Your animation result should be similar to what I've produced here.

And here's the complete source:


from pyglet.gl import *
import pyglet
from pyglet.window import *
from pyglet import image
import os
from PIL import Image
import glob
from math import sin, cos

window = pyglet.window.Window(width=640, height=480, resizable=True)

y=0.0
x=-10.0
z=10.0
xspeed = 0.5
yspeed = 0.0
lx=ly=0
lz=-2
angle=ratio=0.0

boxcol = [ [1.0, 0.0, 0.0], # bright: red
[1.0, 0.5, 0.0], # orange
[1.0, 1.0, 0.0], # yellow
[0.0, 1.0, 0.0], # green
[0.0, 1.0, 1.0], # blue
]

# Dark: red, orange, yellow, green ,blue
topcol =[ [0.6, 0.0, 0.0],
[0.6, 0.25, 0.0],
[0.6, 0.6, 0.0],
[0.0, 0.6 ,0.0],
[0.0, 0.6, 0.6]]



box = None # display list storage
top = None #display list storage

yloop = None # loop for y axis
xloop = None # loop for x axies

bmpdata = None
nextimg = 0
files = None

def load_image_data():
global bmpdata, bmpdatalen, files

files = glob.glob('data/*.bmp')
files.sort()
bmpdata = map(lambda x: Image.open(x).getdata(), files.__iter__())
bmpdatalen = len(bmpdata)


def build_lists():
global box, top
box = glGenLists(2)

glNewList(box, GL_COMPILE) # new compiled box display list

# draw the box without the top (it will be store in display list
# and will not appear on the screen)
glBegin(GL_QUADS)

# front face
glTexCoord2f(0.0, 0.0); glVertex3f(-1.0, -1.0, 1.0)
glTexCoord2f(1.0, 0.0); glVertex3f(1.0, -1.0, 1.0)
glTexCoord2f(1.0, 1.0); glVertex3f(1.0, 1.0, 1.0)
glTexCoord2f(0.0, 1.0); glVertex3f(-1.0, 1.0, 1.0)
# back face
glTexCoord2f(1.0, 0.0); glVertex3f(-1.0, -1.0, -1.0)
glTexCoord2f(1.0, 1.0); glVertex3f(-1.0, 1.0, -1.0)
glTexCoord2f(0.0, 1.0); glVertex3f(1.0, 1.0, -1.0)
glTexCoord2f(0.0, 0.0); glVertex3f(1.0, -1.0, -1.0)
# right face
glTexCoord2f(1.0, 0.0); glVertex3f(1.0, -1.0, -1.0)
glTexCoord2f(1.0, 1.0); glVertex3f(1.0, 1.0, -1.0)
glTexCoord2f(0.0, 1.0); glVertex3f(1.0, 1.0, 1.0)
glTexCoord2f(0.0, 0.0); glVertex3f(1.0, -1.0, 1.0)
# left face
glTexCoord2f(0.0, 0.0); glVertex3f(-1.0, -1.0, -1.0)
glTexCoord2f(1.0, 0.0); glVertex3f(-1.0, -1.0, 1.0)
glTexCoord2f(1.0, 1.0); glVertex3f(-1.0, 1.0, 1.0)
glTexCoord2f(0.0, 1.0); glVertex3f(-1.0, 1.0, -1.0)
glEnd()

glEndList() # Done building the list

top=box+1

glNewList(top, GL_COMPILE) # new compiled top display list
# Top face
glBegin(GL_QUADS)
glTexCoord2f(0.0, 1.0); glVertex3f(-1.0, 1.0, -1.0)
glTexCoord2f(0.0, 0.0); glVertex3f(-1.0, 1.0, 1.0)
glTexCoord2f(1.0, 0.0); glVertex3f(1.0, 1.0, 1.0)
glTexCoord2f(1.0, 1.0); glVertex3f(1.0, 1.0, -1.0)

glTexCoord2f(1.0, 1.0); glVertex3f(-1.0, -1.0, -1.0)
glTexCoord2f(0.0, 1.0); glVertex3f(1.0, -1.0, -1.0)
glTexCoord2f(0.0, 0.0); glVertex3f(1.0, -1.0, 1.0)
glTexCoord2f(1.0, 0.0); glVertex3f(-1.0, -1.0, 1.0)
glEnd()
glEndList()



def load_gl_textures():
# load bitmaps and convert to textures
global texture, texture_file, texture_surf
#texture_file = os.path.join('data', 'cube.bmp')
texture_file = files[nextimg]
texture_surf = image.load(texture_file)
texture = texture_surf.get_texture()
glBindTexture(GL_TEXTURE_2D, texture.id)

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)



def init():
"""
Pyglet oftentimes calls this setup()
"""
glEnable(GL_TEXTURE_2D)

load_image_data()
load_gl_textures()
build_lists()

glShadeModel(GL_SMOOTH) # Enables smooth shading
glClearColor(0.0, 0.0, 0.0, 0.0) #Black background

glClearDepth(1.0) # Depth buffer setup
glEnable(GL_DEPTH_TEST) # Enables depth testing
glDepthFunc(GL_LEQUAL) # The type of depth test to do

glEnable(GL_LIGHT0) # quick and dirty lighting

#glEnable(GL_LIGHTING) # enable lighting
glEnable(GL_COLOR_MATERIAL) # enable coloring

glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST) # Really nice perspective calculations



@window.event
def on_draw():
global nextimg, bmpdata, x, y, z, lx, ly, lz

# Here we do all the drawing
glClear(GL_COLOR_BUFFER_BIT |GL_DEPTH_BUFFER_BIT)

# Select the texture
load_gl_textures()
#glBindTexture(GL_TEXTURE_2D, texture.id)

xloop = 1
yloop = 1

mandata = bmpdata[nextimg]
for idx in range (0, len(mandata)):
if (idx+1) % 24 == 0:
yloop += 1
xloop = 1
else:
xloop += 1
if mandata[idx] < 100:

glLoadIdentity() # reset our view
gluLookAt(x,y,z, x+lx , y+ly, z+lz, 0.0, 1.0, 0.0)

glTranslatef( xloop*1.8 - 30 ,
28 - yloop*2.4 ,
-60.0)
glColor3f(*boxcol[xloop % 4]) # select a box color
glCallList(box) # draw the box

glColor3f(*topcol[1])
glCallList(top) # draw the top


return pyglet.event.EVENT_HANDLED

def moveMeFlat(direction):
global x, z, y, lx, lz, ly
x = x - direction*(lx)*0.75;
y = y + direction*(ly)*0.5;
z = z + direction*(lz)*0.5;

def orientMe(ang):
global lx, lz
lx = sin(ang)
lz = -cos(ang)


def update(dt):
global z, angle
angle +=0.005
orientMe(angle)
moveMeFlat(0.5)

def update2(dt):
global nextimg
if nextimg < bmpdatalen-1:
nextimg += 1
else:
nextimg = 0


pyglet.clock.schedule_interval(update2, .1)
pyglet.clock.schedule(update)

@window.event
def on_resize(width, height):
if height==0:
height = 1
glViewport(0, 0, width, height)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()

# Calculate the aspect ratio of the window
gluPerspective(45.0, 1.0*width/height, 0.1, 100.0)

glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
return pyglet.event.EVENT_HANDLED

init()

pyglet.app.run()

11 comments:

ToryO_Vis建銘 said...
This comment has been removed by a blog administrator.
劉林育雅嘉韋 said...
This comment has been removed by a blog administrator.
李吳俊其彥柏 said...
This comment has been removed by a blog administrator.
凱v胡倫 said...
This comment has been removed by a blog administrator.
國彭男國彭男 said...
This comment has been removed by a blog administrator.
誠陳侑 said...
This comment has been removed by a blog administrator.
智柏林婉林亞 said...
This comment has been removed by a blog administrator.
靜蔡蔡蔡蔡怡 said...
This comment has been removed by a blog administrator.
瑰潼 said...
This comment has been removed by a blog administrator.
洪勳劉耀德劉耀德華 said...
This comment has been removed by a blog administrator.
骆雨康 said...
This comment has been removed by a blog administrator.