AcidWarpy - Noah.org

AcidWarpy

From Noah.org

Jump to: navigation, search

Python is the new C

image:Aw_back.jpg

I originally wrote AcidWarp in C. To this day, 20 years later, people keep bugging me about AcidWarp!

I decided it would be fun to play around with the old functions so I decided to see if it was possible to resurrect the code to run on a modern machine. I decided to use Python since that is my language of choice in the 21st century.

This version requires PyGame. This is trivial to install on Ubuntu Linux (sudo aptitude install python-pygame) and Windows. It's probably easy on other platforms.

To run AcidWarpy you will need the main acidwarpy.py code below as well as the image plugins. You will need at least one plugin. Acidwarpy will scan the current directory for all plugins and automatically add them to the list of images that can be displayed.

Click here to download: acidwarpy.py

Click here to download: image1_plugin.py

Click here to download: image2_plugin.py

Click here to download: image3_plugin.py

Click here to download: image4_plugin.py

Click here to download: image5_plugin.py

Click here to download: image6_plugin.py

Click here to download: image7_plugin.py

#!/usr/bin/env python
 
"""
SYNOPSIS
 
    acidwarpy [-h] [-v,--verbose] [--version] [--full]
 
DESCRIPTION
 
    A port of AcidWarp to Python.
 
AUTHOR
 
    Noah Spurrier <noah@noah.org>
 
LICENSE
 
    This script is in the public domain, free from copyrights or restrictions.
 
VERSION
 
    $Id: acidwarpy.py 220 2008-02-20 19:55:50Z noah $
"""
 
try:
    import pygame, pygame.image
    from pygame.locals import *
except ImportError, e:
    raise ImportError (str(e) + """
 
A critical module was not found. This program requires "PyGame" to be
installed. See http://www.pygame.org/ for more information. On Ubuntu you can
type "sudo aptitude install python-pygame" to satisfy this requirement.""")
 
import os, sys, time, random, threading, traceback, optparse
from math import *
 
#from palette import palette_manager
 
class roller (threading.Thread):
 
    """This runs a function in a loop in a thread."""
 
    def __init__(self, interval, function, args=[], kwargs={}):
 
        """The interval parameter defines time between each call to the
        function. """
 
        threading.Thread.__init__(self)
        self.interval = interval
        self.function = function
        self.args = args
        self.kwargs = kwargs
        self.finished = threading.Event()
 
    def cancel(self):
 
        """Stop the roller."""
 
        self.finished.set()
 
    def run(self):
 
        while not self.finished.isSet():
            self.finished.wait(self.interval)
            self.function(*self.args, **self.kwargs)
 
def distance (x, y):
 
    """This returns the distance from (0,0) to (x,y)."""
 
    return sqrt(x*x + y*y)
 
# The ANGLE_UNIT is the number of angle units in a circle. 2PI for radians.
ANGLE_UNIT= 3.1415926535897932384626433832795 * 2.0
# 2*PI, PI/2, PI/4, etc.
ANGLE_UNIT_2   =             (ANGLE_UNIT*2.0)
ANGLE_UNIT_HALF   =          (ANGLE_UNIT/2.0)
ANGLE_UNIT_EIGHTH  =         (ANGLE_UNIT/8.0)
ANGLE_UNIT_QUART  =          (ANGLE_UNIT/4.0)
ANGLE_UNIT_THREE_QUARTERS =  (ANGLE_UNIT*3.0/4.0)
 
def angle (x, y):
 
    """Get and preserve quadrant info before we convert everything into
    quadrant one for processing."""
 
    quadrant = 1
    if (x*y) > 0:
        if (x < 0):
            quadrant = 3
            x = -x
            y = -y
    else:
        if (x < 0):
            quadrant = 2
            swap = -x
            x = y
            y = swap
        if (y < 0):
            quadrant = 4
            swap = -y
            y = x
            x = swap
 
    if (x < y):
        if (y != 0):
            angle = ANGLE_UNIT_QUART - atan (float(x)/float(y)) #Frame_Edge_Angle[FRAME_SIZE * x / y] - 1
        else:
            angle = ANGLE_UNIT_QUART
    else:
        if (x != 0):
            angle = atan(float(y)/float(x)) # Frame_Edge_Angle[FRAME_SIZE * y / x]
        else:
            angle = 0.0
 
    # Now put the angle in the proper quadrant
    if quadrant == 1:
        return angle
    elif quadrant == 2:
        return angle + ANGLE_UNIT_QUART
    elif quadrant == 3:
        return angle + ANGLE_UNIT_HALF
    elif quadrant == 4:
        return angle + ANGLE_UNIT_THREE_QUARTERS
 
    return -1;
 
def build_palette (step):
 
    """Build a palette -- a list with 256 RGB triplets."""
 
    loop = range(256)
    # First we create a 256-element array. it goes from 0, to 255, and back to 0
    ramp = [abs((x+step*3)%511-255) for x in loop]
    #using the previous ramp and some other crude math, we make some different
    #values for each R, G, and B color planes
    return [(ramp[x], ramp[(x+32)%256], (x+step)%256) for x in loop]
 
def make_image_from_plugin (surf, size, plugin_func):
 
    """This iterates over all the x,y coordinates on the given surface with the
    given size. For each x,y coordinate the plugin_function will be called. The
    current state is passed. This includes the current x,y coordinate as well
    as some extra useful information such as the angle from the current x,y
    point to the center of the surface. The plugin could calculate these values
    for itself, but these values are use so often that it's useful to
    recalculate them for all plugins. """
 
    center_x = size[0] / 2
    center_y = size[1] / 2
    for y in range(size[1]):
        for x in range (size[0]):
            dx = x - center_x;
            dy = y - center_y;
            d = distance(dx,dy)
            a = angle(dx,dy) + ANGLE_UNIT_QUART
            state = {"max_x":size[0], "max_y":size[1], "x":x, "y":y,
                     "center_x":center_x, "center_y":center_y,
                     "dx":dx, "dy":dy, "d":d, "a":a}
            color = plugin_func(state)
            color = color % 255
            pal_color = surf.get_palette_at(int(color))
            surf.set_at ((x,y), pal_color )
    return surf
 
def _get_func (plugin_filename):
 
    source = file(plugin_filename, "rb").read()
    c = compile (source, '', "exec")
    exec c in globals(), locals()
    aFunc = eval ("func", globals(), locals())
    assert callable(aFunc), u"%s is not callable." % fullFuncName
    return aFunc
 
def rotate_screen_palette (screen, palette, r_dir, g_dir, b_dir):
 
    palette.rotate_palette (r_dir, g_dir, b_dir)
    screen.set_palette (palette.get_array())
 
class palette_manager:
 
    RGBW=0
    WHITE=1
    WHITE_HALF=2
    PASTEL=3
    RGBW_LIGHTNING=4
    WHITE_LIGHTNING=5
    WHITE_HALF_LIGHTNING=6
    PASTEL_LIGHTNING=7
    ALL_BLACK=8
    ALL_WHITE=9
 
    def __init__ (self):
 
        self.palette_array = [(0,0,0)] * 256
 
    def init_type (self, pal_type):
 
        if pal_type == self.RGBW:
            self.set_rgbw ()
            return
        if pal_type == self.WHITE:
            self.set_white ()
            return
 
        if pal_type == self.WHITE_HALF:
            self.set_white_half ()
            return
 
        if pal_type == self.PASTEL:
            self.set_pastel ()
            return
 
        if pal_type == self.RGBW_LIGHTNING:
            self.set_rgbw ()
            self.add_sparkles_to_palette (9)
            return
 
        if pal_type == self.WHITE_LIGHTNING:
            self.set_white ()
            self.add_sparkles_to_palette (9)
            return
 
        if pal_type == self.WHITE_HALF_LIGHTNING:
            self.set_white_half ()
            self.add_sparkles_to_palette (9)
            return
 
        if pal_type == self.PASTEL_LIGHTNING:
            self.set_pastel ()
            self.add_sparkles_to_palette (9)
            return
 
        if pal_type == self.ALL_BLACK:
            self.set_all_black()
            return
 
        if pal_type == self.ALL_WHITE:
            self.set_all_white()
            return
 
        return
 
    def get_array (self):
 
        return self.palette_array
 
    def add_sparkles_to_palette (self, sparkle_amount):
 
        for index in range (1,256,4):
            self.palette_array[index] = (min(63, self.palette_array[index][0] + sparkle_amount),
                min(63, self.palette_array[index][1] + sparkle_amount),
                min(63, self.palette_array[index][2] + sparkle_amount) )
 
    def set_all_black (self):
 
        for index in range (0,256):
            self.palette_array[index] = (0,0,0)
 
    def set_all_white (self):
 
        for index in range (0,256):
            self.palette_array[index] = (255,255,255)
 
    def set_rgbw (self):
 
        for index in range (0, 32):
            self.palette_array[index] = (index * 2,0,0)
            self.palette_array[index +  64] = (0,index * 2,0)
            self.palette_array[index + 128] = (0,0,index * 2)
            self.palette_array[index + 192] = (index * 2,index * 2,index * 2)
        for index in range (32, 64):
            self.palette_array[index] = ((63 - index)*2,0,0)
            self.palette_array[index +  64] = (0,(63 - index)*2,0)
            self.palette_array[index + 128] = (0,0,(63 - index)*2)
            self.palette_array[index + 192] = ((63 - index)*2,(63 - index)*2,(63 - index)*2)
 
    def set_white (self):
 
        for index in range (0, 128):
            self.palette_array[index] = (index/2,index/2,index/2)
        for index in range (128, 256):
            self.palette_array[index] = ((255 - index)/2,(255 - index)/2,(255 - index)/2)
 
    def set_white_half (self):
 
        for index in range (0, 64):
            self.palette_array[index] = (index,index,index)
            self.palette_array[index + 64] = (63 - index,63 - index,63 - index)
        for index in range (128, 256):
            self.palette_array[index] = (0,0,0)
 
    def set_pastel (self):
 
        for index in range (0, 128):
            self.palette_array[index] = (128 + index,128 + index,128 + index)
            self.palette_array[index + 128] = (127 + (127 - index),127 + (127 - index),127 + (127 - index))
 
    def rotate_palette (self, r_step, g_step, b_step):
 
        pa = zip(*self.palette_array) # unzip
        pa = map(list,pa)
        self.palette_array = zip(*(tuple(self._rotate(pa[0],r_step)),tuple(self._rotate(pa[1],g_step)),tuple(self._rotate(pa[2],b_step))))
 
    def fade_to_black (self):
 
        pa = zip (*self.palette_array)
        pa = map(list,pa)
        all_black = 1
        for i in range (0, len(pa[0])):
            if pa[0][i] > 0:
                all_black = 0
                pa[0][i] = pa[0][i] - 1
            if pa[1][i] > 0:
                all_black = 0
                pa[1][i] = pa[1][i] - 1
            if pa[2][i] > 0:
                all_black = 0
                pa[2][i] = pa[2][i] - 1
        self.palette_array = zip(*(tuple(pa[0]),tuple(pa[1]),tuple(pa[2])) )
        return all_black
 
    def fade_to_other_palette (self, p):
 
        all_equal = 1
        for i in range (0, len(self.palette_array)):
            if self.palette_array[i] != p.palette_array[i]:
                all_equal = 0
                self.palette_array[i] = self._move_toward (self.palette_array[i],p.palette_array[i])
        return all_equal
 
    def _move_toward (self, tuple1, tuple2):
 
        if tuple1[0] < tuple2[0]:
            t0 = tuple1[0] + 1
        if tuple1[0] > tuple2[0]:
            t0 = tuple1[0] - 1
        if tuple1[1] < tuple2[1]:
            t1 = tuple1[1] + 1
        if tuple1[1] > tuple2[1]:
            t1 = tuple1[1] - 1
        if tuple1[2] < tuple2[2]:
            t2 = tuple1[2] + 1
        if tuple1[2] > tuple2[2]:
            t2 = tuple1[2] - 1
        return (t0,t1,t2)
 
    def _rotate (self, seq, n=0):
 
        if n == 0:
            return seq
        n = n % len(seq)
        return seq[n:] + seq[:n]
 
def main():
 
    global options, args
 
    # Load the functions -- these are stored in external python files.
    # This is a fancy import mechanism. This imports all files with names
    # that end in *plugin.py.
    plugins = []
    plugin_names = [x for x in os.listdir(".") if x[-9:]=='plugin.py']
    plugin_names.sort()
    plugin_names.reverse()
    for wrp in plugin_names:
        plugins.append (_get_func (wrp))
 
    pygame.init()
    pygame.mouse.set_visible(False)
    if options.full:
        screen = pygame.display.set_mode((640, 480), HWSURFACE|HWPALETTE|FULLSCREEN, 8)
    else:
        screen = pygame.display.set_mode((640, 480), 0, 8)
    pygame.display.set_caption ("AcidWarpy")
    p = palette_manager ()
    p.init_type(palette_manager.PASTEL)
    screen.set_palette(p.get_array())
    image = screen.convert()
 
    plugin_index = random.randrange (len(plugins))
    make_image_from_plugin (image, (640,480), plugins[plugin_index])
    screen.blit(image, (0,0))
    p.init_type(palette_manager.PASTEL)
    screen.set_palette(p.get_array())
 
#    r = roller (0.01, rotate_screen_palette, (screen, p, 1, 0, -1))
#    r.start()
#    screen.set_palette(p.get_array())
 
    plugin_index = random.randrange (len(plugins))
    start_time = time.time()
    showstoppers = (QUIT, KEYDOWN, MOUSEBUTTONDOWN)
    while pygame.event.poll().type not in showstoppers:
        time.sleep (0.01)
        p.rotate_palette (1,0,-1)
        screen.set_palette (p.get_array())
        if time.time() - start_time > 5:
            plugin_index = plugin_index + 1
            if plugin_index >= len(plugins):
                plugin_index = 0
            plugin_index = random.randrange (len(plugins))
            make_image_from_plugin (image, (640,480), plugins[plugin_index])
 
            while p.fade_to_black() != 1:
                screen.set_palette (p.get_array())
                time.sleep (0.01)
 
            p_target = palette_manager ()
            p_target.init_type(palette_manager.PASTEL)
 
            p.init_type(palette_manager.PASTEL)
            screen.set_palette(p.get_array())
            screen.blit(image, (0,0))
            p.init_type(palette_manager.ALL_BLACK)
            screen.set_palette(p.get_array())
            while p.fade_to_other_palette(p_target) != 1:
                screen.set_palette (p.get_array())
                time.sleep (0.01)
 
            start_time = time.time()
#    r.cancel()
    pygame.quit()
 
if __name__ == '__main__':
    try:
        start_time = time.time()
        parser = optparse.OptionParser(formatter=optparse.TitledHelpFormatter(), usage=globals()['__doc__'], version='$Id: acidwarpy.py 220 2008-02-20 19:55:50Z noah $')
        parser.add_option ('-v', '--verbose', action='store_true', default=False, help='verbose output')
        parser.add_option ('--full', action='store_true', default=False, help='Full screen mode')
        (options, args) = parser.parse_args()
        #if len(args) < 1:
        #    parser.error ('missing argument')
        if options.verbose: print time.asctime()
        main()
        if options.verbose: print time.asctime()
        if options.verbose: print 'TOTAL TIME IN MINUTES:',
        if options.verbose: print (time.time() - start_time) / 60.0
        sys.exit(0)
    except KeyboardInterrupt, e: # Ctrl-C
        raise e
    except SystemExit, e: # sys.exit()
        raise e
    except Exception, e:
        print 'ERROR, UNEXPECTED EXCEPTION'
        print str(e)
        traceback.print_exc()
        os._exit(1)
-->