[cairo-commit] cairo-demo/12or13 12.png, NONE, 1.1 12or13.py, NONE, 1.1

Behdad Esfahbod commit at pdx.freedesktop.org
Sun Jun 3 12:49:50 PDT 2007


Committed by: behdad

Update of /cvs/cairo/cairo-demo/12or13
In directory kemper:/tmp/cvs-serv18751/12or13

Added Files:
	12.png 12or13.py 
Log Message:
Add 12or13


--- NEW FILE: 12.png ---
(This appears to be a binary file; contents omitted.)

--- NEW FILE: 12or13.py ---
#!/usr/bin/python

# Written by Behdad Esfahbod, 2006
# http://mces.blogspot.com/2006/09/puzzle-12-or-13.html
# http://mces.blogspot.com/2006/09/solution-12.html
# Not copyrighted, use as you wish

import sys
import math
import cairo
import pygtk
pygtk.require('2.0')
import gtk
import gtk.gdk
import pango
import gobject

def clamp(x, lo=0., hi=1.):
	return max (lo, min (x, hi))

def interpolate(x, x0, x1):
	return x0 * (1. - x) + x1 * (0. + x)

class Puzzle(gtk.Widget):
    def __init__(self, file, num = 12, width_mult = -1, div_mult = -1, top_mult = 0.5, start_mult = 0.0):
        gtk.Widget.__init__ (self)

	self.surface = cairo.ImageSurface.create_from_png (file)
	self.num = num
	self.set_multipliers (width_mult, div_mult, top_mult, start_mult)

    def set_multipliers(self, width_mult = None, div_mult = None, top_mult = None, start_mult = None):
	if (width_mult is not None):
	    if (width_mult < 0):
		width_mult = float (self.num + 1) / self.num
	    self.width_mult = width_mult
	if (div_mult is not None):
	    if (div_mult < 0):
		div_mult = float (self.num - 1) / self.num
	    self.div_mult = clamp (div_mult)
	if (top_mult is not None):
	    self.top_mult = clamp (top_mult)
	if (start_mult is not None):
	    self.start_mult = start_mult % 1

	self.surface_width  = self.surface.get_width  ()
	self.surface_height = self.surface.get_height ()

	self.man_width = int (math.ceil (float (self.surface_width) / self.num))
	self.man_half_width = int (round (float (self.surface_width) / (self.num * 2)))

	self.man_height = self.surface_height
	self.man_top_height = int (round (self.surface_height * self.top_mult))
	self.man_bottom_height = self.man_height - self.man_top_height

	self.effective_width  = (self.surface_width - self.man_width) * self.width_mult
	self.effective_width  = max (1, int (round (self.effective_width)))

	self.width  = self.effective_width + self.man_width
	self.height = self.man_top_height + self.man_height

	self.x_offset = self.man_half_width
	self.y_offset = 2 * self.man_top_height

	self.div_width = (self.effective_width * self.div_mult)
	self.backward_advance = - self.div_width
	self.forward_advance  = self.effective_width + self.backward_advance

	self.__cache_bodies()

    def do_size_request(self, requisition):
	requisition.width  = self.width
	requisition.height = self.height

    def do_size_allocate(self, allocation):
	gtk.Widget.do_size_allocate (self, allocation)

	top_mult= div_mult = start_mult = width_mult = None

	width_mult = float (allocation.width   - self.man_width) / (self.surface_width - self.man_width)
	width_mult = max (0, width_mult)

	top_mult = float (allocation.height - self.surface_height) / self.surface_height

	self.set_multipliers(width_mult = width_mult, top_mult = top_mult, div_mult = div_mult, start_mult = start_mult)

    def __cache_bodies(self):
	self.men = []
	xf = self.effective_width * self.start_mult
	xi = int (round (xf))
	for i in range (self.num):

		surface = self.surface.create_similar (cairo.CONTENT_COLOR_ALPHA, self.man_width, self.man_top_height)
		cr = cairo.Context (surface)
		cr.set_source_surface (self.surface, - i * self.man_width, 0)
		cr.paint ()
		surface.set_device_offset (self.man_half_width, self.man_top_height)
		top = surface

		surface = self.surface.create_similar (cairo.CONTENT_COLOR_ALPHA, self.man_width, self.man_bottom_height)
		cr = cairo.Context (surface)
		cr.set_source_surface (self.surface, - i * self.man_width, - self.man_top_height)
		cr.paint ()
		surface.set_device_offset (self.man_half_width, 0)
		bottom = surface

		x0 = xi
		xf = (xf + self.forward_advance) % self.effective_width
		xi = int (round (xf))
		x1 = xi

		class Man:
		    def __init__(self, top, bottom, x0, x1):
			self.top = top
			self.bottom = bottom
			self.x = [x0, x1]

		self.men.append (Man (top, bottom, x0, x1))
	self.men.reverse ()

    def do_realize(self):
        self.set_flags(self.flags() | gtk.REALIZED)

        self.window = gtk.gdk.Window(
            self.get_parent_window(),
            width=self.allocation.width,
            height=self.allocation.height,
            window_type=gtk.gdk.WINDOW_CHILD,
            wclass=gtk.gdk.INPUT_OUTPUT,
            event_mask=self.get_events() | gtk.gdk.EXPOSURE_MASK)

        self.window.set_user_data(self)

        self.style.attach(self.window)

        self.style.set_background(self.window, gtk.STATE_NORMAL)
        self.window.move_resize(*self.allocation)

    def do_unrealize(self):
        self.window.destroy()

    def do_expose_event(self, event):
        cr = self.window.cairo_create()
        cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
        cr.clip()

        self.draw(cr)

        return False

    def draw(self, cr):

    	cr.set_source_rgb (1, 1, 1)
	cr.paint ()

	cr.translate (self.x_offset, self.y_offset)

	if self.decorate:
	    cr.set_line_width (1)
	    cr.set_line_cap (cairo.LINE_CAP_BUTT)

	    cr.set_source_rgba (1., 0., 0., .8)

	    # horiz top line
	    cr.move_to (-self.x_offset, -self.man_top_height + .5)
	    cr.rel_line_to (self.width, 0)
	    cr.move_to (-self.x_offset, +self.man_bottom_height - .5)
	    cr.rel_line_to (self.width, 0)
	    # vert left line
	    cr.move_to (.5, -self.y_offset)
	    cr.rel_line_to (0, self.height)
	    # vert right line
	    cr.move_to (self.effective_width - .5, -self.y_offset)
	    cr.rel_line_to (0, self.height)

	    cr.stroke ()

	    cr.set_source_rgba (0., 0., 1., .8)
	    # horiz baseline
	    cr.move_to (-self.x_offset, .5)
	    cr.rel_line_to (self.width, 0)
	    cr.stroke ()

	    # vert div line
	    cr.set_source_rgba (0., 0., 1., interpolate (0+self.x_cycle, .9, .2))
	    cr.move_to (self.div_width, -self.y_offset)
	    cr.rel_line_to (0, self.height)
	    cr.stroke ()

	    # vert 'after' div line
	    cr.set_source_rgba (0., 0., 1., interpolate (1-self.x_cycle, .9, .2))
	    cr.move_to (self.forward_advance, -self.y_offset)
	    cr.rel_line_to (0, self.height)
	    cr.stroke ()

	    cr.set_font_size (self.man_top_height)
	    font_extents = cr.font_extents ()
	    font_height = font_extents[0] + font_extents[1]
	    y = -self.man_top_height - font_extents[1] - (self.man_top_height - font_height) / 2.
	    # num
	    cr.set_source_rgba (1., .6, 0., interpolate (0+self.cycle, .5, 0))
	    text = str (self.num)
	    extents = cr.text_extents (text)
	    cr.move_to ((self.effective_width - extents[2]) / 2. - extents[0], y)
	    cr.show_text (text)
	    # num+1
	    cr.set_source_rgba (1., .6, 0., interpolate (1-self.cycle, .5, 0))
	    text = str (self.num+1)
	    extents = cr.text_extents (text)
	    cr.move_to ((self.effective_width - extents[2]) / 2. - extents[0], y)
	    cr.show_text (text)

	for man in self.men:
	    cr.set_source_surface (man.bottom, man.x[0], 0)
	    cr.paint ()
	    cr.set_source_surface (man.top, * self.animate_pos (man))
	    cr.paint ()

	if self.decorate > 1:
	    cr.set_line_width (1)
	    cr.set_line_cap (cairo.LINE_CAP_ROUND)
	    cr.set_source_rgba (0., 1., 0., .8)
	    dy = float (self.man_height) / self.num

	    y = -self.man_top_height + dy * .5
	    cr.new_path ()
	    for man in self.men:
		cr.line_to (man.x[0], y)
		y += dy
	    cr.stroke ()

	    cr.set_line_width (5)
	    y = -self.man_top_height + dy * .5
	    cr.set_source_rgba (0., 1., 0., .8)
	    for man in self.men:
		cr.move_to (man.x[0], y)
		cr.rel_line_to (0, 0)
		cr.stroke ()
		y += dy

    def animate_pos (self, man):
	x = interpolate (self.x_cycle, man.x[0], man.x[1])
	if man.x[0] < man.x[1]:
	    y = 0
	else:
	    y = self.y_cycle * -self.man_top_height
	return (x,y)

    def tick (self):
	self.busy = True

	prev_phase = self.phase
	self.phase += 2*math.pi * self.speed / self.frame_rate

	if (self.phase - prev_phase) * (self.phase%(math.pi) - prev_phase%(math.pi)) < 0:
	    self.phase -= self.phase%(math.pi)
	    self.delay_tick ()
	
	self.phase %= 2*math.pi

	self.cycle = 1. - abs ((self.phase % (2*math.pi) - math.pi) / math.pi)
	c = math.cos (self.phase)
	self.x_cycle = clamp(-math.cos (self.phase), -.5, +.5) + .5
	self.y_cycle = abs (math.sin (self.phase)) ** 2

	self.window.invalidate_rect (gtk.gdk.Rectangle (0, 0, self.width, self.height), True)

	return True

    def delay_tick (self, d = -1):

	if d < 0:
	    d = 1. / self.speed

	if self.timeout_handler_id:
	    gobject.source_remove (self.timeout_handler_id)
	
	self.busy = False
	
	def delay (f):
	    self.timeout_handler_id = gobject.timeout_add (1000 / self.frame_rate, f)
	    return False

	if self.next_delay < 0:
	    self.next_delay = d

	self.timeout_handler_id = gobject.timeout_add (int (self.next_delay * 1000), delay, self.tick)
	self.next_delay = -1
	self.__animate = True

	return False

    def set_animation (self, animate):
	if self.__animate == animate:
	    return

	if self.__animate:
	    gobject.source_remove (self.timeout_handler_id)
	    self.timeout_handler_id = None
	    self.__animate = False
	if animate:
	    self.delay_tick (0)

    def __key_press_event(self, widget, event):
        if event.string == 'q':
            gtk.main_quit()
        elif event.string == ' ':
	    if not self.busy:
		self.delay_tick (0)
	    else:
		self.next_delay = 0

        elif event.string == 'b':
	    self.decorate = (self.decorate + 1) % 3

        elif event.string == 'a':
	    self.speed = min (1.0, self.speed * (2**+.25))
        elif event.string == 'z':
	    self.speed = max (0.1, self.speed * (2**-.25))

        elif event.string == 'l':
	    self.set_multipliers (start_mult = self.start_mult + .01)
        elif event.string == 'j':
	    self.set_multipliers (start_mult = self.start_mult - .01)

        elif event.string == 'i':
	    self.set_multipliers (div_mult = self.div_mult + .03 / self.num)
        elif event.string == 'k':
	    self.set_multipliers (div_mult = self.div_mult - .03 / self.num)

	else:
	    if event.string:
		print "Unhandled key", `event.string`
	    return
	self.window.invalidate_rect (gtk.gdk.Rectangle (0, 0, self.width, self.height), True)

    def run(self):
	self.decorate = 0
	self.cycle = 0.
	self.x_cycle = 0.
	self.y_cycle = 0.
	self.phase = 0.
	self.speed = .3
	self.frame_rate = 25

	self.next_delay = -1
 	self.__animate = False
	self.timeout_handler_id = 0

	self.delay_tick ()

        window = gtk.Window()
	window.props.allow_shrink = True
        window.add(self)
        window.connect("destroy", gtk.main_quit)
        window.connect("key-press-event", self.__key_press_event)
        window.show_all()

        gtk.main()

gobject.type_register(Puzzle)

def main():
    file = '12.png'
    count = 12

    print """How many?

 Press 'b' for annotations,
 space for transition (or just wait),
       'a' and 'z' to change speed,
       'j' and 'l' to change start offset,
       'k' and 'i' to change advance (aka move division line),
resize the window to change width and/or waist line,
   and 'q' to quit"""

    puzzle = Puzzle (file, count, width_mult = 0.688, div_mult = 0.388)
    puzzle.run()

if __name__ == "__main__":
    main()



More information about the cairo-commit mailing list