Browse Source

Initial import

jamesbowman 7 years ago
parent
commit
89cd6e5123

+ 10 - 0
MANIFEST

@@ -0,0 +1,10 @@
+# file GENERATED by distutils, do NOT edit
+setup.py
+gameduino2/__init__.py
+gameduino2/base.py
+gameduino2/convert.py
+gameduino2/prep.py
+gameduino2/psdparser.py
+gameduino2/registers.py
+gameduino2/remote.py
+scripts/gd2asset

+ 15 - 0
default_assets.h

@@ -0,0 +1,15 @@
+#define LOAD_ASSETS()  GD.safeload("xxx");
+#define FELIX_HANDLE 0
+#define FELIX_WIDTH 320
+#define FELIX_HEIGHT 200
+#define FELIX_CELLS 2
+#define HOBBY_OF_NIGHT_HANDLE 1
+#define HOBBY_OF_NIGHT_WIDTH 10
+#define HOBBY_OF_NIGHT_HEIGHT 16
+#define HOBBY_OF_NIGHT_CELLS 96
+#define 0 23832UL
+#define 0_LENGTH 3272
+#define 0_FREQ 8000
+#define ASSETS_END 27104UL
+static const shape_t FELIX_SHAPE = {0, 320, 200, 0};
+static const shape_t HOBBY_OF_NIGHT_SHAPE = {1, 10, 16, 0};

+ 1 - 0
gameduino2/.gitignore

@@ -0,0 +1 @@
+*.pyc

BIN
gameduino2/Alan_Turing_photo.jpg


+ 2 - 0
gameduino2/__init__.py

@@ -0,0 +1,2 @@
+from gameduino2.registers import *
+import prep

+ 256 - 0
gameduino2/base.py

@@ -0,0 +1,256 @@
+import struct
+
+def align4(s):
+    return s + chr(0) * (-len(s) & 3)
+
+def f16(v):
+    return int(round(65536 * v))
+
+class GD2:
+    def c4(self, i):
+        """Send a 32-bit value to the GD2."""
+        self.c(struct.pack("I", i))
+    def ac(self, s):
+        self.c(align4(s))
+
+    # The basic graphics instructions
+
+    def AlphaFunc(self, func,ref):
+        self.c4((9 << 24) | ((func & 7) << 8) | ((ref & 255) << 0))
+    def Begin(self, prim):
+        self.c4((31 << 24) | ((prim & 15) << 0))
+    def BitmapHandle(self, handle):
+        self.c4((5 << 24) | ((handle & 31) << 0))
+    def BitmapLayout(self, format,linestride,height):
+        self.c4((7 << 24) | ((format & 31) << 19) | ((linestride & 1023) << 9) | ((height & 511) << 0))
+    def BitmapSize(self, filter,wrapx,wrapy,width,height):
+        self.c4((8 << 24) | ((filter & 1) << 20) | ((wrapx & 1) << 19) | ((wrapy & 1) << 18) | ((width & 511) << 9) | ((height & 511) << 0))
+    def BitmapSource(self, addr):
+        self.c4((1 << 24) | ((addr & 1048575) << 0))
+    def BitmapTransformA(self, a):
+        self.c4((21 << 24) | ((a & 131071) << 0))
+    def BitmapTransformB(self, b):
+        self.c4((22 << 24) | ((b & 131071) << 0))
+    def BitmapTransformC(self, c):
+        self.c4((23 << 24) | ((c & 16777215) << 0))
+    def BitmapTransformD(self, d):
+        self.c4((24 << 24) | ((d & 131071) << 0))
+    def BitmapTransformE(self, e):
+        self.c4((25 << 24) | ((e & 131071) << 0))
+    def BitmapTransformF(self, f):
+        self.c4((26 << 24) | ((f & 16777215) << 0))
+    def BlendFunc(self, src,dst):
+        self.c4((11 << 24) | ((src & 7) << 3) | ((dst & 7) << 0))
+    def Call(self, dest):
+        self.c4((29 << 24) | ((dest & 65535) << 0))
+    def Cell(self, cell):
+        self.c4((6 << 24) | ((cell & 127) << 0))
+    def ClearColorA(self, alpha):
+        self.c4((15 << 24) | ((alpha & 255) << 0))
+    def ClearColorRGB(self, red,green,blue):
+        self.c4((2 << 24) | ((red & 255) << 16) | ((green & 255) << 8) | ((blue & 255) << 0))
+    def Clear(self, c = 1,s = 1,t = 1):
+        self.c4((38 << 24) | ((c & 1) << 2) | ((s & 1) << 1) | ((t & 1) << 0))
+    def ClearStencil(self, s):
+        self.c4((17 << 24) | ((s & 255) << 0))
+    def ClearTag(self, s):
+        self.c4((18 << 24) | ((s & 255) << 0))
+    def ColorA(self, alpha):
+        self.c4((16 << 24) | ((alpha & 255) << 0))
+    def ColorMask(self, r,g,b,a):
+        self.c4((32 << 24) | ((r & 1) << 3) | ((g & 1) << 2) | ((b & 1) << 1) | ((a & 1) << 0))
+    def ColorRGB(self, red,green,blue):
+        self.c4((4 << 24) | ((red & 255) << 16) | ((green & 255) << 8) | ((blue & 255) << 0))
+    def Display(self):
+        self.c4((0 << 24))
+    def End(self):
+        self.c4((33 << 24))
+    def Jump(self, dest):
+        self.c4((30 << 24) | ((dest & 65535) << 0))
+    def LineWidth(self, width):
+        self.c4((14 << 24) | ((width & 4095) << 0))
+    def Macro(self, m):
+        self.c4((37 << 24) | ((m & 1) << 0))
+    def PointSize(self, size):
+        self.c4((13 << 24) | ((size & 8191) << 0))
+    def RestoreContext(self):
+        self.c4((35 << 24))
+    def Return(self):
+        self.c4((36 << 24))
+    def SaveContext(self):
+        self.c4((34 << 24))
+    def ScissorSize(self, width,height):
+        self.c4((28 << 24) | ((width & 1023) << 10) | ((height & 1023) << 0))
+    def ScissorXY(self, x,y):
+        self.c4((27 << 24) | ((x & 511) << 9) | ((y & 511) << 0))
+    def StencilFunc(self, func,ref,mask):
+        self.c4((10 << 24) | ((func & 7) << 16) | ((ref & 255) << 8) | ((mask & 255) << 0))
+    def StencilMask(self, mask):
+        self.c4((19 << 24) | ((mask & 255) << 0))
+    def StencilOp(self, sfail,spass):
+        self.c4((12 << 24) | ((sfail & 7) << 3) | ((spass & 7) << 0))
+    def TagMask(self, mask):
+        self.c4((20 << 24) | ((mask & 1) << 0))
+    def Tag(self, s):
+        self.c4((3 << 24) | ((s & 255) << 0))
+    def Vertex2f(self, x, y):
+        x = int(16 * x)
+        y = int(16 * y)
+        self.c4((1 << 30) | ((x & 32767) << 15) | ((y & 32767) << 0))
+    def Vertex2ii(self, x,y,handle,cell):
+        self.c4((2 << 30) | ((x & 511) << 21) | ((y & 511) << 12) | ((handle & 31) << 7) | ((cell & 127) << 0))
+
+    # Higher-level graphics commands
+
+    def cmd_append(self, ptr, num):
+        self.c(struct.pack("III", 0xffffff1e, ptr, num))
+
+    def cmd_bgcolor(self, c):
+        self.c(struct.pack("II", 0xffffff09, c))
+
+    def cmd_bitmap_transform(self, x0, y0, x1, y1, x2, y2, tx0, ty0, tx1, ty1, tx2, ty2, result):
+        self.c(struct.pack("IiiiiiiiiiiiiH", 0xffffff21, x0, y0, x1, y1, x2, y2, tx0, ty0, tx1, ty1, tx2, ty2, result))
+
+    def cmd_button(self, x, y, w, h, font, options, s):
+        self.c(struct.pack("IhhhhhH", 0xffffff0d, x, y, w, h, font, options) + s + chr(0))
+
+    def cmd_calibrate(self, result):
+        self.c(struct.pack("II", 0xffffff15, result))
+
+    def cmd_clock(self, x, y, r, options, h, m, s, ms):
+        self.c(struct.pack("IhhhHHHHH", 0xffffff14, x, y, r, options, h, m, s, ms))
+
+    def cmd_coldstart(self):
+        self.c(struct.pack("I", 0xffffff32))
+
+    def cmd_dial(self, x, y, r, options, val):
+        self.c(struct.pack("IhhhHH", 0xffffff2d, x, y, r, options, val))
+
+    def cmd_dlstart(self):
+        self.c(struct.pack("I", 0xffffff00))
+
+    def cmd_fgcolor(self, c):
+        self.c(struct.pack("II", 0xffffff0a, c))
+
+    def cmd_gauge(self, x, y, r, options, major, minor, val, range):
+        self.c(struct.pack("IhhhHHHHH", 0xffffff13, x, y, r, options, major, minor, val, range))
+
+    def cmd_getmatrix(self, a, b, c, d, e, f):
+        self.c(struct.pack("Iiiiiii", 0xffffff33, a, b, c, d, e, f))
+
+    def cmd_getprops(self, ptr, w, h):
+        self.c(struct.pack("IIII", 0xffffff25, ptr, w, h))
+
+    def cmd_getptr(self, result):
+        self.c(struct.pack("II", 0xffffff23, result))
+
+    def cmd_gradcolor(self, c):
+        self.c(struct.pack("II", 0xffffff34, c))
+
+    def cmd_gradient(self, x0, y0, rgb0, x1, y1, rgb1):
+        self.c(struct.pack("IhhIhhI", 0xffffff0b, x0, y0, rgb0, x1, y1, rgb1))
+
+    def cmd_inflate(self, ptr):
+        self.c(struct.pack("II", 0xffffff22, ptr))
+
+    def cmd_interrupt(self, ms):
+        self.c(struct.pack("II", 0xffffff02, ms))
+
+    def cmd_keys(self, x, y, w, h, font, options, s):
+        self.c(struct.pack("IhhhhhH", 0xffffff0e, x, y, w, h, font, options) + s + chr(0))
+
+    def cmd_loadidentity(self):
+        self.c(struct.pack("I", 0xffffff26))
+
+    def cmd_loadimage(self, ptr, options):
+        self.c(struct.pack("III", 0xffffff24, ptr, options))
+
+    def cmd_logo(self):
+        self.c(struct.pack("I", 0xffffff31))
+
+    def cmd_memcpy(self, dest, src, num):
+        self.c(struct.pack("IIII", 0xffffff1d, dest, src, num))
+
+    def cmd_memcrc(self, ptr, num, result):
+        self.c(struct.pack("IIII", 0xffffff18, ptr, num, result))
+
+    def cmd_memset(self, ptr, value, num):
+        self.c(struct.pack("IIII", 0xffffff1b, ptr, value, num))
+
+    def cmd_memwrite(self, ptr, num):
+        self.c(struct.pack("III", 0xffffff1a, ptr, num))
+
+    def cmd_regwrite(self, ptr, val):
+        self.memwrite(ptr, 4)
+        self.c4(val)
+
+    def cmd_regwrite(self, ptr, val):
+        self.c(struct.pack("IIII", 0xffffff1a, ptr, 4, val))
+
+    def cmd_memzero(self, ptr, num):
+        self.c(struct.pack("III", 0xffffff1c, ptr, num))
+
+    def cmd_number(self, x, y, font, options, n):
+        self.c(struct.pack("IhhhHi", 0xffffff2e, x, y, font, options, n))
+
+    def cmd_progress(self, x, y, w, h, options, val, range):
+        self.c(struct.pack("IhhhhHHH", 0xffffff0f, x, y, w, h, options, val, range))
+
+    def cmd_regread(self, ptr, result):
+        self.c(struct.pack("III", 0xffffff19, ptr, result))
+
+    def cmd_rotate(self, a):
+        self.c(struct.pack("Ii", 0xffffff29, a))
+
+    def cmd_scale(self, sx, sy):
+        self.c(struct.pack("Iii", 0xffffff28, f16(sx), f16(sy)))
+
+    def cmd_screensaver(self):
+        self.c(struct.pack("I", 0xffffff2f))
+
+    def cmd_scrollbar(self, x, y, w, h, options, val, size, range):
+        self.c(struct.pack("IhhhhHHHH", 0xffffff11, x, y, w, h, options, val, size, range))
+
+    def cmd_setfont(self, font, ptr):
+        self.c(struct.pack("III", 0xffffff2b, font, ptr))
+
+    def cmd_setmatrix(self):
+        self.c(struct.pack("I", 0xffffff2a))
+
+    def cmd_sketch(self, x, y, w, h, ptr, format):
+        self.c(struct.pack("IhhHHIH", 0xffffff30, x, y, w, h, ptr, format))
+
+    def cmd_slider(self, x, y, w, h, options, val, range):
+        self.c(struct.pack("IhhhhHHH", 0xffffff10, x, y, w, h, options, val, range))
+
+    def cmd_snapshot(self, ptr):
+        self.c(struct.pack("II", 0xffffff1f, ptr))
+
+    def cmd_spinner(self, x, y, style, scale):
+        self.c(struct.pack("IhhHH", 0xffffff16, x, y, style, scale))
+
+    def cmd_stop(self):
+        self.c(struct.pack("I", 0xffffff17))
+
+    def cmd_swap(self):
+        self.c(struct.pack("I", 0xffffff01))
+
+    def cmd_text(self, x, y, font, options, s):
+        self.c(align4(struct.pack("IhhhH", 0xffffff0c, x, y, font, options) + s + chr(0)))
+
+    def cmd_toggle(self, x, y, w, font, options, state, s):
+        self.c(struct.pack("IhhhhHH", 0xffffff12, x, y, w, font, options, state) + s + chr(0))
+
+    def cmd_touch_transform(self, x0, y0, x1, y1, x2, y2, tx0, ty0, tx1, ty1, tx2, ty2, result):
+        self.c(struct.pack("IiiiiiiiiiiiiH", 0xffffff20, x0, y0, x1, y1, x2, y2, tx0, ty0, tx1, ty1, tx2, ty2, result))
+
+    def cmd_track(self, x, y, w, h, tag):
+        self.c(struct.pack("Ihhhhh", 0xffffff2c, x, y, w, h, tag))
+
+    def cmd_translate(self, tx, ty):
+        self.c(struct.pack("Iii", 0xffffff27, f16(tx), f16(ty)))
+
+    # Converting versions, *f
+
+    def cmd_translatef(self, tx, ty):
+        self.cmd_translate(int(65536 * tx), int(65536 * ty))

+ 83 - 0
gameduino2/convert.py

@@ -0,0 +1,83 @@
+import Image
+import array
+import random
+
+from gameduino2.registers import *
+
+def convert(im, dither = False, fmt = ARGB1555):
+    """ Convert PIL image to GD2 format, optionally dithering"""
+    im = im.convert({
+        ARGB1555 : "RGBA",
+        L1 : "L",
+        L4 : "L",
+        L8 : "L",
+        RGB332 : "RGB",
+        ARGB2 : "RGBA",
+        ARGB4 : "RGBA",
+        RGB565 : "RGB",
+        PALETTED : "RGB"}[fmt])
+    imdata = []
+    colorfmts = {
+        ARGB1555 : (1, 5, 5, 5),
+        RGB332 :   (0, 3, 3, 2),
+        ARGB2 :    (2, 2, 2, 2),
+        ARGB4 :    (4, 4, 4, 4),
+        RGB565 :   (0, 5, 6, 5)}
+    if dither:
+        rnd = random.Random()
+        rnd.seed(0)
+    if fmt in colorfmts:
+        im = im.convert("RGBA")
+        (asz,rsz,gsz,bsz) = colorfmts[fmt]
+        totalsz = sum((asz,rsz,gsz,bsz))
+        assert totalsz in (8,16)
+        for y in range(im.size[1]):
+            for x in range(im.size[0]):
+                (r,g,b,a) = im.getpixel((x,y))
+                if dither:
+                    a = min(255, a + rnd.randrange(256>>asz))
+                    r = min(255, r + rnd.randrange(256>>rsz))
+                    g = min(255, g + rnd.randrange(256>>gsz))
+                    b = min(255, b + rnd.randrange(256>>bsz))
+                binary = ((a >> (8-asz)) << (bsz+gsz+rsz)) | ((r >> (8-rsz)) << (gsz+bsz)) | ((g >> (8-gsz)) << (bsz)) | (b >> (8-bsz))
+                imdata.append(binary)
+        fmtchr = {8:'B',16:'H'}[totalsz]
+        data = array.array('B', array.array(fmtchr, imdata).tostring())
+    elif fmt == PALETTED:
+        im = im.convert("P", palette = Image.ADAPTIVE)
+        lut = im.resize((256, 1))
+        lut.putdata(range(256))
+        palstr = lut.convert("RGBA").tobytes()
+        rgba = zip(*(array.array('B', palstr[i::4]) for i in range(4)))
+        """
+        for i,(r,g,b,a) in enumerate(rgba):
+            self.memory[RAM_PAL + 4 * i + 0] = b
+            self.memory[RAM_PAL + 4 * i + 1] = g
+            self.memory[RAM_PAL + 4 * i + 2] = r
+            self.memory[RAM_PAL + 4 * i + 3] = a
+        """
+        data = array.array('B', im.tostring())
+        totalsz = 8
+    elif fmt == L8:
+        data = array.array('B', im.tostring())
+        totalsz = 8
+    elif fmt == L4:
+        b0 = im.tostring()[::2]
+        b1 = im.tostring()[1::2]
+        def to15(c):
+            if dither:
+                dc = min(255, ord(c) + rnd.randrange(16))
+            else:
+                dc = ord(c)
+            return int((15. * dc / 255))
+                
+        data = array.array('B', [(16 * to15(l) + to15(r)) for (l,r) in zip(b0, b1)])
+        totalsz = 4
+    elif fmt == L1:
+        if dither:
+            im = im.convert("1", dither=Image.FLOYDSTEINBERG)
+        else:
+            im = im.convert("1", dither=Image.NONE)
+        data = array.array('B', im.tostring())
+        totalsz = 1
+    return (im.size, data)

BIN
gameduino2/felix.png


+ 508 - 0
gameduino2/prep.py

@@ -0,0 +1,508 @@
+import os
+import sys
+import array
+import struct
+import zlib
+import textwrap
+import wave
+import audioop
+
+import Image
+import ImageFont
+import ImageDraw
+
+import gameduino2 as gd2
+import gameduino2.convert
+
+def stretch(im):
+    d = array.array('B', im.tostring())
+    # print min(d), max(d)
+    r = max(d) - min(d)
+    return im.point(lambda x: (x - min(d)) * 255 / r)
+
+def getalpha(im):
+    return im.split()[3]
+
+def tile(tw, th, im):
+    tiles = []
+    for y in range(0, im.size[1], th):
+        for x in range(0, im.size[0], tw):
+            tiles.append(im.crop((x, y, x + tw, y + th)))
+    o = Image.new(im.mode, (tw, th * len(tiles)))
+    for i,t in enumerate(tiles):
+        o.paste(t, (0, i * th))
+    return o
+
+def split(tw, th, im):
+    tiles = []
+    for y in range(0, im.size[1], th):
+        for x in range(0, im.size[0], tw):
+            tiles.append(im.crop((x, y, x + tw, y + th)))
+    return tiles
+
+def join(tiles):
+    (tw, th) = tiles[0].size
+    o = Image.new(tiles[0].mode, (tw, th * len(tiles)))
+    for i,t in enumerate(tiles):
+        o.paste(t, (0, i * th))
+    return o
+
+def setwidth(im, w):
+    e = Image.new(im.mode, (w, im.size[1]))
+    e.paste(im, (0, 0))
+    return e
+
+def even(im):
+    w = im.size[0]
+    if (w % 2) == 0:
+        return im
+    else:
+        return setwidth(im, w + 1)
+
+def extents(im):
+    """ find pixel extents of im, as a box """
+    w,h = im.size
+    cols = [set(im.crop((i, 0, i + 1, im.size[1])).tostring()) != set(chr(0)) for i in range(w)]
+    rows = [set(im.crop((0, i, im.size[0], i + 1)).tostring()) != set(chr(0)) for i in range(h)]
+    if not True in cols:
+        return (0, 0, 0, 0)
+    else:
+        x0 = cols.index(True)
+        y0 = rows.index(True)
+        while not cols[-1]:
+            cols.pop()
+        while not rows[-1]:
+            rows.pop()
+        return (x0, y0, len(cols), len(rows))
+
+def ul(x):
+    return str(x) + "UL"
+
+def preview(np, fmt, size, data):
+
+    def chan(x):
+        return Image.fromstring("L", size, (255 * x).astype(np.uint8))
+
+    if fmt == gd2.L1:
+        r = Image.fromstring("1", size, data)
+    elif fmt == gd2.L8:
+        r = Image.fromstring("L", size, data)
+    else:
+        d8 = np.array(data)
+        a = np.ones(size[0] * size[1])
+        (r, g, b) = (a, a, a)
+        if fmt == gd2.ARGB4:
+            d16 = np.array(array.array('H', data.tostring()))
+            a = (15 & (d16 >> 12)) / 15.
+            r = (15 & (d16 >> 8)) / 15.
+            g = (15 & (d16 >> 4)) / 15.
+            b = (15 & (d16 >> 0)) / 15.
+        elif fmt == gd2.RGB565:
+            d16 = np.array(array.array('H', data.tostring()))
+            r = (31 & (d16 >> 11)) / 31.
+            g = (63 & (d16 >> 5)) / 63.
+            b = (31 & (d16 >> 0)) / 31.
+        elif fmt == gd2.ARGB1555:
+            d16 = np.array(array.array('H', data.tostring()))
+            a = (1 & (d16 >> 15))
+            r = (31 & (d16 >> 10)) / 31.
+            g = (31 & (d16 >> 5)) / 31.
+            b = (31 & (d16 >> 0)) / 31.
+        elif fmt == gd2.ARGB2:
+            a = (3 & (d8 >> 6)) / 3.
+            r = (3 & (d8 >> 4)) / 3.
+            g = (3 & (d8 >> 2)) / 3.
+            b = (3 & (d8 >> 0)) / 3.
+        elif fmt == gd2.RGB332:
+            r = (7 & (d8 >> 5)) / 7.
+            g = (7 & (d8 >> 2)) / 7.
+            b = (3 & (d8 >> 0)) / 3.
+        elif fmt == gd2.L4:
+            hi = d8 >> 4
+            lo = d8 & 15
+            d4 = np.column_stack((hi, lo)).flatten()
+            r = d4 / 15.
+            g = r
+            b = r
+        o = Image.merge("RGB", [chan(c) for c in (r, g, b)])
+        bg = Image.new("RGB", size, (128, 128, 128))
+        r = Image.composite(o, bg, chan(a))
+
+    r = r.resize((r.size[0] * 5, r.size[1] * 5), Image.NEAREST)
+    return r
+
+import gameduino2.base
+
+class AssetBin(gameduino2.base.GD2):
+
+    asset_file = None
+    prefix = ""
+    previews = False
+
+    def __init__(self):
+        self.alldata = ""
+        self.commands = ""
+        self.defines = []
+        self.inits = []
+        self.handle = 0
+        self.np = None
+
+    def define(self, n, v):
+        self.defines.append((self.prefix + n, v))
+
+    def add(self, name, s):
+        if name:
+            self.defines.append((name, ul(len(self.alldata))))
+        self.alldata += s
+
+    def c(self, s):
+        self.commands += s
+
+    def addim(self, name, im, fmt, dither = False):
+        (_, imgdata) = gameduino2.convert.convert(im, dither, fmt = fmt)
+        self.add(name, imgdata.tostring())
+        (w, h) = im.size
+        if name:
+            self.defines.append(("%s_WIDTH" % name, w))
+            self.defines.append(("%s_HEIGHT" % name, h))
+
+    def align(self, n):
+        while (len(self.alldata) % n) != 0:
+            self.alldata += chr(0)
+
+    def load_handle(self, name, images, fmt,
+                    dither = False,
+                    filter = gd2.NEAREST,
+                    scale = 1,
+                    rotating = False):
+
+        if 15 <= self.handle:
+            print "Error: too many bitmap handles used, limit is 15"
+            sys.exit(1)
+
+        (w, h) = images[0].size
+
+        self.define("%s_HANDLE" % name, self.handle)
+        name = self.prefix + name
+        self.defines.append(("%s_WIDTH" % name, w))
+        self.defines.append(("%s_HEIGHT" % name, h))
+        self.defines.append(("%s_CELLS" % name, len(images)))
+
+        self.align(2)
+
+        self.BitmapHandle(self.handle);
+        self.BitmapSource(len(self.alldata));
+        if not rotating:
+            (vw, vh) = (scale * w, scale * h)
+            vsz = 0
+        else:
+            vsz = int(scale * max(w, h))
+            self.define("%s_SIZE" % name, vsz)
+            (vw, vh) = (vsz, vsz)
+        self.BitmapSize(filter, gd2.BORDER, gd2.BORDER, vw, vh);
+        self.inits.append("static const shape_t %s_SHAPE = {%d, %d, %d, %d};" % (name, self.handle, w, h, vsz))
+
+        # aw is aligned width
+        if fmt == gd2.L1:
+            aw = (w + 7) & ~7
+        elif fmt == gd2.L4:
+            aw = (w + 1) & ~1
+        else:
+            aw = w
+
+        # print self.name, name, (filter, gd2.BORDER, gd2.BORDER, vw, vh)
+        bpl = {
+            gd2.ARGB1555 : 2 * aw,
+            gd2.L1       : aw / 8,
+            gd2.L4       : aw / 2,
+            gd2.L8       : aw,
+            gd2.RGB332   : aw,
+            gd2.ARGB2    : aw,
+            gd2.ARGB4    : 2 * aw,
+            gd2.RGB565   : 2 * aw,
+            gd2.PALETTED : aw}[fmt]
+        self.BitmapLayout(fmt, bpl, h);
+
+        for i,im in enumerate(images):
+            if aw != w:
+                im = setwidth(im, aw)
+            if hasattr(im, "imgdata"):
+                imgdata = im.imgdata
+            else:
+                (_, imgdata) = gameduino2.convert.convert(im, dither, fmt = fmt)
+            """
+            if self.previews:
+                if not self.np:
+                    import numpy
+                    self.np = numpy
+                preview(self.np, fmt, im.size, imgdata).save("previews/%s-%s-%02d.png" % (self.name, name, i))
+            """
+            self.alldata += imgdata.tostring()
+
+        self.handle += 1
+
+    def load_font(self, name, ims, widths, fmt, **args):
+        trim0 = 0
+        while ims[trim0] is None:
+            trim0 += 1
+        p0 = len(self.alldata)
+        h = self.handle
+        tims = ims[trim0:]
+        self.load_handle(name, tims, fmt, **args)
+
+        self.align(4)
+        p1 = len(self.alldata)
+        # Compute memory required by one char
+        onechar = (p1 - p0) / len(tims)
+        # print name, 'font requires', (p1 - p0), 'bytes'
+        sz = ims[trim0].size
+        self.BitmapSource(p0 - (onechar * trim0));
+        dblock = array.array('B', widths).tostring() + struct.pack("<5i", fmt, 1, sz[0], sz[1], p0 - (onechar * trim0))
+        self.alldata += dblock
+        self.cmd_setfont(h, p1);
+
+    def load_ttf(self, name, ttfname, size, format):
+        font = ImageFont.truetype(ttfname, size)
+        sizes = [font.getsize(chr(c)) for c in range(32, 128)]
+        fw = max([w for (w, _) in sizes])
+        fh = max([h for (_, h) in sizes])
+        # print fw, fh
+        alle = {}
+        for i in range(1, 96):
+            im = Image.new("L", (fw+8, fh))
+            dr = ImageDraw.Draw(im)
+            dr.text((8,0), chr(32 + i), font=font, fill=255)
+            alle[i] = gd2.prep.extents(im)
+
+        fw = max([(x1 - x0) for (x0, y0, x1, y1) in alle.values()])
+        ims = ([None] * 32) + [Image.new("L", (fw, fh)) for i in range(32, 128)]
+        for i in range(33, 127):
+            dr = ImageDraw.Draw(ims[i])
+            (x0, y0, x1, y1) = alle[i - 32]
+            x = max(0, 8 - x0)
+            if x > 0:
+                sizes[i - 32] = (sizes[i - 32][0] - x, sizes[i - 32][1])
+            dr.text((x, 0), chr(i), font=font, fill=255)
+        # imgtools.view(im)
+        widths = ([0] * 32) + [w for (w, _) in sizes]
+        self.load_font(name, ims, widths, format)
+
+    """
+    def dxt1(self, imagefile):
+        import numpy
+
+        im = Image.open(imagefile).resize((480,272), Image.ANTIALIAS)
+        dxt = "%s.dxt" % imagefile
+        if not os.access(dxt, os.R_OK):
+            im.save("tmp.png")
+            assert os.system("squishpng tmp.png %s" % dxt) == 0
+
+        sz = (480 / 4, 272 / 4)
+
+        def rgb(cs):
+            r = (cs >> 11) << 3
+            g = 0xff & ((cs >> 5) << 2)
+            b = 0xff & (cs << 3)
+            return (r,g,b)
+        def rgbim(cs):
+            return Image.merge("RGB", [Image.fromarray(c.astype(numpy.uint8).reshape(*sz)) for c in rgb(cs)])
+        def morton1(x):
+            v = x & 0x55555555
+            v = (v | (v >> 1)) & 0x33333333;
+            v = (v | (v >> 2)) & 0x0F0F0F0F;
+            v = (v | (v >> 4)) & 0x00FF00FF;
+            v = (v | (v >> 8)) & 0x0000FFFF;
+            return v.astype(numpy.uint16)
+
+        h = open(dxt)
+        h.read(8)
+
+        c0s = []
+        c1s = []
+        bits = []
+        for i in range(sz[0] * sz[1]):
+            tile = h.read(8)
+            c0,c1,bit = struct.unpack("2HI", tile)
+            b0 = bit & 0x55555555
+            b1 = (bit >> 1) & 0x55555555
+            is0 = ~b1 & ~b0
+            is1 = ~b1 & b0
+            is2 = b1 & ~b0
+            is3 = b1 & b0
+            if c0<=c1:
+                if bit == 0xaaaaaaaa:
+                    # print c0<c1,hex(bit)
+                    r0,g0,b0 = rgb(c0)
+                    r1,g1,b1 = rgb(c1)
+                    r = (r0 + r1) / 2
+                    g = (g0 + g1) / 2
+                    b = (b0 + b1) / 2
+                    # r,g,b = (255,0,255)
+                    c0 = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3)
+                    c1 = c0
+                    bit = 0
+                else:
+                    if 0:
+                        for i in range(0, 32, 2):
+                            fld = (3 & (bit >> i))
+                            if 3 == fld:
+                                print c0, c1, hex(bit), i
+                                assert 0
+                    # Map as follows:
+                    # 0 -> 0
+                    # 1 -> 3
+                    # 2 -> 1
+                    bit = (is1 * 3) + (is2 * 1)
+                    assert is3 == 0
+            else:
+                # 0 -> 0
+                # 1 -> 3
+                # 2 -> 1
+                # 3 -> 2
+                bit = (is1 * 3) + (is2 * 1) + (is3 * 2)
+
+            if 0:
+                c0 = 63 << 5 # green
+                c1 = 31 << 11 # red
+                bit = 0x0f0f0f0f
+
+            c0s.append(c0)
+            c1s.append(c1)
+            bits.append(bit)
+        c0s = numpy.array(c0s, numpy.uint16)
+        c1s = numpy.array(c1s, numpy.uint16)
+        bits = numpy.array(bits, numpy.uint32)
+        bits0 = morton1(bits)
+        bits1 = morton1(bits >> 1)
+
+        if 1:
+            def im44(v):
+                im = Image.new("1", (4,4))
+                pix = im.load()
+                for i in range(4):
+                    for j in range(4):
+                        if v & (1 << (4 * i + j)):
+                            pix[j,i] = 255
+                return im
+            ims = [im44(i) for i in range(65536)]
+
+        b0im = Image.new("1", (480, 272))
+        b1im = Image.new("1", (480, 272))
+        xys = [(x,y) for y in range(0, 272, 4) for x in range(0, 480, 4)]
+        for i,(x,y) in enumerate(xys):
+            b0im.paste(ims[bits0[i]], (x, y))
+            b1im.paste(ims[bits1[i]], (x, y))
+
+        class MockImage:
+            def __init__(self, s, size):
+                self.imgdata = s
+                self.size = size
+
+        self.load_handle("BACKGROUND_COLOR", [MockImage(c0s, sz), MockImage(c1s, sz)], gd2.RGB565, scale = 4)
+        self.load_handle("BACKGROUND_BITS", [b0im, b1im], gd2.L1)
+    """
+
+    def load_sample(self, name, filename):
+        f = wave.open(filename, "rb")
+        if f.getnchannels() != 1:
+            print "Sorry - .wav file must be mono"
+            sys.exit(1)
+        if f.getsampwidth() != 2:
+            print "Sorry - .wav file must be 16-bit"
+            sys.exit(1)
+        freq = f.getframerate()
+        pcm16 = f.readframes(f.getnframes())
+        (adpcm, _) = audioop.lin2adpcm(pcm16, f.getsampwidth(), (0,0))
+        adpcm = adpcm[:len(adpcm) & ~7]
+        da = array.array('B', [((ord(c) >> 4) | ((15 & ord(c)) << 4)) for c in adpcm])
+        self.align(8)
+        self.add(name, da.tostring())
+        self.define(name + "_LENGTH", len(da))
+        self.define(name + "_FREQ", freq)
+
+    header = None
+    def make(self):
+        if self.header is None:
+            name = self.__class__.__name__.lower() + "_assets.h"
+        else:
+            name = self.header
+        self.name = name
+        self.addall()
+        if len(self.alldata) > 0x40000:
+            print "Error: The data (%d bytes) is larger the the GD2 RAM" % len(self.alldata)
+            sys.exit(1)
+        self.defines.append((self.prefix + "ASSETS_END", ul(len(self.alldata))))
+        self.cmd_inflate(0)
+        calldata = zlib.compress(self.alldata)
+        print 'Assets report'
+        print '-------------'
+        print 'Header file:    %s' % self.header
+        print 'GD2 RAM used:   %d' % len(self.alldata)
+        if not self.asset_file:
+            print 'Flash used:     %d' % len(calldata)
+        else:
+            print 'Output file:    %s' % self.asset_file
+            print 'File size:      %d' % len(calldata)
+
+        commandblock = self.commands + calldata
+
+        hh = open(name, "w")
+        if self.asset_file is None:
+            print >>hh, "static const PROGMEM uint8_t %s__assets[%d] = {" % (self.prefix, len(commandblock))
+            print >>hh, textwrap.fill(", ".join(["%d" % ord(c) for c in commandblock]))
+            print >>hh, "};"
+            print >>hh, "#define %sLOAD_ASSETS()  GD.copy(%s__assets, sizeof(%s__assets))" % (self.prefix, self.prefix, self.prefix)
+        else:
+            open(self.asset_file, "wb").write(commandblock)
+            print >>hh, '#define %sLOAD_ASSETS()  GD.safeload("%s");' % (self.prefix, self.asset_file)
+        for (nm,v) in self.defines:
+            print >>hh, "#define %s %s" % (nm, v)
+        for i in self.inits:
+            print >>hh, i
+        self.extras(hh)
+
+    def addall(self):
+        pass
+    def extras(self, hh):
+        pass
+
+class ForthAssetBin(AssetBin):
+    def make(self):
+        if self.header is None:
+            name = self.__class__.__name__.lower() + "_assets.fs"
+        else:
+            name = self.header
+        self.name = name
+        self.addall()
+        if len(self.alldata) > 0x40000:
+            print "Error: The data (%d bytes) is larger the the GD2 RAM" % len(self.alldata)
+            sys.exit(1)
+        self.defines.append((self.prefix + "ASSETS_END", ul(len(self.alldata))))
+        self.cmd_inflate(0)
+        calldata = zlib.compress(self.alldata)
+        print 'Assets report'
+        print '-------------'
+        print 'Header file:    %s' % self.header
+        print 'GD2 RAM used:   %d' % len(self.alldata)
+        if not self.asset_file:
+            print 'Flash used:     %d' % len(calldata)
+        else:
+            print 'Output file:    %s' % self.asset_file
+            print 'File size:      %d' % len(calldata)
+
+        commandblock = self.commands + calldata
+        commandblock += chr(0) * ((-len(commandblock)) & 3)
+        commandblock32 = array.array('I', commandblock)
+
+        hh = open(name, "w")
+        print >>hh, "base @"
+        print >>hh, "hex"
+        if self.asset_file is None:
+            print >>hh, textwrap.fill(" ".join(["%08x GD.c" % c for c in commandblock32]))
+        else:
+            open(self.asset_file, "wb").write(commandblock)
+        print >>hh, "decimal"
+        for (nm,v) in self.defines:
+            print >>hh, "%-8s constant %s" % (str(v).replace('UL', ''), nm)
+        print >>hh, "base !"
+        self.extras(hh)

+ 649 - 0
gameduino2/psdparser.py

@@ -0,0 +1,649 @@
+#!/usr/bin/env python
+
+# This file is part of psdparser.
+#
+# Psdparser is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Psdparser is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Psdparser.  If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+import sys
+from struct import unpack, calcsize
+from PIL import Image
+
+logger = logging.getLogger(__name__)
+
+"""
+Header mode field meanings
+"""
+CHANNEL_SUFFIXES = {
+    -2: 'layer mask',
+    -1: 'A',
+    0: 'R',
+    1: 'G',
+    2: 'B',
+    3: 'RGB',
+    4: 'CMYK', 5: 'HSL', 6: 'HSB',
+    9: 'Lab', 11: 'RGB',
+    12: 'Lab', 13: 'CMYK',
+}
+
+"""
+Resource id descriptions
+"""
+RESOURCE_DESCRIPTIONS = {
+    1000: 'PS2.0 mode data',
+    1001: 'Macintosh print record',
+    1003: 'PS2.0 indexed color table',
+    1005: 'ResolutionInfo',
+    1006: 'Names of the alpha channels',
+    1007: 'DisplayInfo',
+    1008: 'Caption',
+    1009: 'Border information',
+    1010: 'Background color',
+    1011: 'Print flags',
+    1012: 'Grayscale/multichannel halftoning info',
+    1013: 'Color halftoning info',
+    1014: 'Duotone halftoning info',
+    1015: 'Grayscale/multichannel transfer function',
+    1016: 'Color transfer functions',
+    1017: 'Duotone transfer functions',
+    1018: 'Duotone image info',
+    1019: 'B&W values for the dot range',
+    1021: 'EPS options',
+    1022: 'Quick Mask info',
+    1024: 'Layer state info',
+    1025: 'Working path',
+    1026: 'Layers group info',
+    1028: 'IPTC-NAA record (File Info)',
+    1029: 'Image mode for raw format files',
+    1030: 'JPEG quality',
+    1032: 'Grid and guides info',
+    1033: 'Thumbnail resource',
+    1034: 'Copyright flag',
+    1035: 'URL',
+    1036: 'Thumbnail resource',
+    1037: 'Global Angle',
+    1038: 'Color samplers resource',
+    1039: 'ICC Profile',
+    1040: 'Watermark',
+    1041: 'ICC Untagged',
+    1042: 'Effects visible',
+    1043: 'Spot Halftone',
+    1044: 'Document specific IDs',
+    1045: 'Unicode Alpha Names',
+    1046: 'Indexed Color Table Count',
+    1047: 'Transparent Index',
+    1049: 'Global Altitude',
+    1050: 'Slices',
+    1051: 'Workflow URL',
+    1052: 'Jump To XPEP',
+    1053: 'Alpha Identifiers',
+    1054: 'URL List',
+    1057: 'Version Info',
+    2999: 'Name of clipping path',
+    10000: 'Print flags info',
+}
+
+MODES = {
+    0:  'Bitmap',
+    1:  'GrayScale',
+    2:  'IndexedColor',
+    3:  'RGBColor',
+    4:  'CMYKColor',
+    5:  'HSLColor',
+    6:  'HSBColor',
+    7:  'Multichannel',
+    8:  'Duotone',
+    9:  'LabColor',
+    10: 'Gray16',
+    11: 'RGB48',
+    12: 'Lab48',
+    13: 'CMYK64',
+    14: 'DeepMultichannel',
+    15: 'Duotone16',
+}
+
+COMPRESSIONS = {
+    0: 'Raw',
+    1: 'RLE',
+    2: 'ZIP',
+    3: 'ZIPPrediction',
+}
+
+BLENDINGS = {
+    'norm': 'normal',
+    'dark': 'darken',
+    'mul ': 'multiply',
+    'lite': 'lighten',
+    'scrn': 'screen',
+    'over': 'overlay',
+    'sLit': 'soft-light',
+    'hLit': 'hard-light',
+    'lLit': 'linear-light',
+    'diff': 'difference',
+    'smud': 'exclusion',
+}
+
+PIL_BANDS = {
+    'R': 0,
+    'G': 1,
+    'B': 2,
+    'A': 3,
+    'L': 0,
+}
+
+def INDENT_OUTPUT(depth, msg):
+    return ''.join(['    ' for i in range(0, depth)]) + msg
+
+class PSDParser(object):
+
+    header = None
+    ressources = None
+    num_layers = 0
+    layers = None
+    images = None
+    merged_image = None
+
+    def __init__(self, filename):
+        self.filename = filename
+
+    def _pad2(self, i):
+        """same or next even"""
+        return (i + 1) / 2 * 2
+
+    def _pad4(self, i):
+        """same or next multiple of 4"""
+        return (i + 3) / 4 * 4
+
+    def _readf(self, format):
+        """read a strct from file structure according to format"""
+        return unpack(format, self.fd.read(calcsize(format)))
+
+    def _skip_block(self, desc, indent=0, new_line=False):
+        (n,) = self._readf('>L') # (n,) is a 1-tuple.
+        if n:
+            self.fd.seek(n, 1) # 1: relative
+
+        if new_line:
+            logger.debug('')
+        logger.debug(INDENT_OUTPUT(indent, 'Skipped %s with %s bytes' % (desc, n)))
+
+    def parse(self):
+        logger.debug("Opening '%s'" % self.filename)
+
+        self.fd = open(self.filename, 'rb')
+        try:
+            self.parse_header()
+            self.parse_image_resources()
+            self.parse_layers_and_masks()
+            self.parse_image_data()
+        finally:
+            self.fd.close()
+
+        logger.debug("")
+        logger.debug("DONE")
+
+    def parse_header(self):
+        logger.debug("")
+        logger.debug("# Header #")
+
+        self.header = {}
+
+        C_PSD_HEADER = ">4sH 6B HLLHH"
+        (
+            self.header['sig'],
+            self.header['version'],
+            self.header['r0'],
+            self.header['r1'],
+            self.header['r2'],
+            self.header['r3'],
+            self.header['r4'],
+            self.header['r5'],
+            self.header['channels'],
+            self.header['rows'],
+            self.header['cols'],
+            self.header['depth'],
+            self.header['mode']
+        ) = self._readf(C_PSD_HEADER)
+
+        self.size = [self.header['rows'], self.header['cols']]
+
+        if self.header['sig'] != "8BPS":
+            raise ValueError("Not a PSD signature: '%s'" % self.header['sig'])
+        if self.header['version'] != 1:
+            raise ValueError("Can not handle PSD version:%d" % self.header['version'])
+        self.header['modename'] = MODES[self.header['mode']] if 0 <= self.header['mode'] < 16 else "(%s)" % self.header['mode']
+
+        logger.debug(INDENT_OUTPUT(1, "channels:%(channels)d, rows:%(rows)d, cols:%(cols)d, depth:%(depth)d, mode:%(mode)d [%(modename)s]" % self.header))
+        logger.debug(INDENT_OUTPUT(1, "%s" % self.header))
+
+        # Remember position
+        self.header['colormodepos'] = self.fd.tell()
+        self._skip_block("color mode data", 1)
+
+    def parse_image_resources(self):
+        def parse_irb():
+            """return total bytes in block"""
+            r = {}
+            r['at'] = self.fd.tell()
+            (r['type'], r['id'], r['namelen']) = self._readf(">4s H B")
+            n = self._pad2(r['namelen'] + 1) - 1
+            (r['name'],) = self._readf(">%ds" % n)
+            r['name'] = r['name'][:-1] # skim off trailing 0byte
+            r['short'] = r['name'][:20]
+            (r['size'],) = self._readf(">L")
+            self.fd.seek(self._pad2(r['size']), 1) # 1: relative
+            r['rdesc'] = "[%s]" % RESOURCE_DESCRIPTIONS.get(r['id'], "?")
+            logger.debug(INDENT_OUTPUT(1, "Resource: %s" % r))
+            logger.debug(INDENT_OUTPUT(1, "0x%(at)06x| type:%(type)s, id:%(id)5d, size:0x%(size)04x %(rdesc)s '%(short)s'" % r))
+            self.ressources.append(r)
+            return 4 + 2 + self._pad2(1 + r['namelen']) + 4 + self._pad2(r['size'])
+
+        logger.debug("")
+        logger.debug("# Ressources #")
+        self.ressources = []
+        (n,) = self._readf(">L") # (n,) is a 1-tuple.
+        while n > 0:
+            n -= parse_irb()
+        if n != 0:
+            logger.debug("Image resources overran expected size by %d bytes" % (-n))
+
+    def parse_image(self, li, is_layer=True):
+        def parse_channel(li, idx, count, rows, cols, depth):
+            """params:
+            li -- layer info struct
+            idx -- channel number
+            count -- number of channels to process ???
+            rows, cols -- dimensions
+            depth -- bits
+            """
+            chlen = li['chlengths'][idx]
+            if chlen is not None  and  chlen < 2:
+                raise ValueError("Not enough channel data: %s" % chlen)
+            if li['chids'][idx] == -2:
+                rows, cols = li['mask']['rows'], li['mask']['cols']
+
+            rb = (cols * depth + 7) / 8 # round to next byte
+
+            # channel header
+            chpos = self.fd.tell()
+            (comp,) = self._readf(">H")
+
+            if chlen:
+                chlen -= 2
+
+            pos = self.fd.tell()
+
+            # If empty layer
+            if cols * rows == 0:
+                logger.debug(INDENT_OUTPUT(1, "Empty channel, skiping"))
+                return
+
+            if COMPRESSIONS.get(comp) == 'RLE':
+                logger.debug(INDENT_OUTPUT(1, "Handling RLE compressed data"))
+                rlecounts = 2 * count * rows
+                if chlen and chlen < rlecounts:
+                    raise ValueError("Channel too short for RLE row counts (need %d bytes, have %d bytes)" % (rlecounts,chlen))
+                pos += rlecounts # image data starts after RLE counts
+                rlecounts_data = self._readf(">%dH" % (count * rows))
+                for ch in range(count):
+                    rlelen_for_channel = sum(rlecounts_data[ch * rows:(ch + 1) * rows])
+                    data = self.fd.read(rlelen_for_channel)
+                    channel_name = CHANNEL_SUFFIXES[li['chids'][idx]]
+                    if li['channels'] == 2 and channel_name == 'B': channel_name = 'L'
+                    p = Image.fromstring("L", (cols, rows), data, "packbits", "L" )
+                    if is_layer:
+                        if channel_name in PIL_BANDS:
+                            self.images[li['idx']][PIL_BANDS[channel_name]] = p
+                    else:
+                        self.merged_image.append(p)
+
+            elif COMPRESSIONS.get(comp) == 'Raw':
+                logger.debug(INDENT_OUTPUT(1, "Handling Raw compressed data"))
+
+                for ch in range(count):
+                    data = self.fd.read(cols * rows)
+                    channel_name = CHANNEL_SUFFIXES[li['chids'][idx]]
+                    if li['channels'] == 2 and channel_name == 'B': channel_name = 'L'
+                    p = Image.fromstring("L", (cols, rows), data, "raw", "L")
+                    if is_layer:
+                        if channel_name in PIL_BANDS:
+                            self.images[li['idx']][PIL_BANDS[channel_name]] = p
+                    else:
+                        self.merged_image.append(p)
+
+            else:
+                # TODO: maybe just skip channel...:
+                #   f.seek(chlen, SEEK_CUR)
+                #   return
+                raise ValueError("Unsupported compression type: %s" % COMPRESSIONS.get(comp, comp))
+
+            if (chlen is not None) and (self.fd.tell() != chpos + 2 + chlen):
+                logger.debug("currentpos:%d should be:%d!" % (f.tell(), chpos + 2 + chlen))
+                self.fd.seek(chpos + 2 + chlen, 0) # 0: SEEK_SET
+
+            return
+
+        if not self.header:
+            self.parse_header()
+        if not self.ressources:
+            self._skip_block("image resources", new_line=True)
+            self.ressources = 'not parsed'
+
+        logger.debug("")
+        logger.debug("# Image: %s/%d #" % (li['name'], li['channels']))
+
+        # channels
+        if is_layer:
+            for ch in range(li['channels']):
+                parse_channel(li, ch, 1, li['rows'], li['cols'], self.header['depth'])
+        else:
+            parse_channel(li, 0, li['channels'], li['rows'], li['cols'], self.header['depth'])
+        return
+
+    def _read_descriptor(self):
+        # Descriptor
+        def _unicode_string():
+            len = self._readf(">L")[0]
+            result = u''
+            for count in range(len):
+                val = self._readf(">H")[0]
+                if val:
+                    result += unichr(val)
+            return result
+
+        def _string_or_key():
+            len = self._readf(">L")[0]
+            if not len:
+                len = 4
+            return self._readf(">%ds" % len)[0]
+
+        def _desc_TEXT():
+            return _unicode_string()
+
+        def _desc_enum():
+            return { 'typeID' : _string_or_key(),
+                     'enum' : _string_or_key(),
+                     }
+
+        def _desc_long():
+            return self._readf(">l")[0]
+
+        def _desc_bool():
+            return self._readf(">?")[0]
+
+        def _desc_doub():
+            return self._readf(">d")[0]
+
+        def _desc_tdta():
+            # Apparently it is pdf data?
+            # http://telegraphics.com.au/svn/psdparse
+            # descriptor.c pdf.c
+
+            len = self._readf(">L")[0]
+            pdf_data = self.fd.read(len)
+            return pdf_data
+
+        _desc_item_factory = {
+            'TEXT' : _desc_TEXT,
+            'enum' : _desc_enum,
+            'long' : _desc_long,
+            'bool' : _desc_bool,
+            'doub' : _desc_doub,
+            'tdta' : _desc_tdta,
+            }
+
+        class_id_name = _unicode_string()
+        class_id = _string_or_key()
+        logger.debug(INDENT_OUTPUT(4, u"name='%s' clsid='%s'" % (class_id_name, class_id)))
+
+        item_count = self._readf(">L")[0]
+        #logger.debug(INDENT_OUTPUT(4, "item_count=%d" % (item_count)))
+        items = {}
+        for item_index in range(item_count):
+            item_key = _string_or_key()
+            item_type = self._readf(">4s")[0]
+            if not item_type in _desc_item_factory:
+                logger.debug(INDENT_OUTPUT(4, "unknown descriptor item '%s', skipping ahead." % item_type))
+                break
+
+            items[item_key] = _desc_item_factory[item_type]()
+            #logger.debug(INDENT_OUTPUT(4, "item['%s']='%r'" % (item_key,items[item_key])))
+            #print items[item_key]
+        return items
+
+    def parse_layers_and_masks(self):
+
+        if not self.header:
+            self.parse_header()
+        if not self.ressources:
+            self._skip_block('image resources', new_line=True)
+            self.ressources = 'not parsed'
+
+        logger.debug("")
+        logger.debug("# Layers & Masks #")
+
+        self.layers = []
+        self.images = []
+        self.header['mergedalpha'] = False
+        (misclen,) = self._readf(">L")
+        if misclen:
+            miscstart = self.fd.tell()
+            # process layer info section
+            (layerlen,) = self._readf(">L")
+            if layerlen:
+                # layers structure
+                (self.num_layers,) = self._readf(">h")
+                if self.num_layers < 0:
+                    self.num_layers = -self.num_layers
+                    logger.debug(INDENT_OUTPUT(1, "First alpha is transparency for merged image"))
+                    self.header['mergedalpha'] = True
+                logger.debug(INDENT_OUTPUT(1, "Layer info for %d layers:" % self.num_layers))
+
+                if self.num_layers * (18 + 6 * self.header['channels']) > layerlen:
+                    raise ValueError("Unlikely number of %s layers for %s channels with %s layerlen. Giving up." % (self.num_layers, self.header['channels'], layerlen))
+
+                linfo = [] # collect header infos here
+
+                for i in range(self.num_layers):
+                    l = {}
+                    l['idx'] = i
+
+                    #
+                    # Layer Info
+                    #
+                    (l['top'], l['left'], l['bottom'], l['right'], l['channels']) = self._readf(">llllH")
+                    (l['rows'], l['cols']) = (l['bottom'] - l['top'], l['right'] - l['left'])
+                    logger.debug(INDENT_OUTPUT(1, "layer %(idx)d: (%(left)4d,%(top)4d,%(right)4d,%(bottom)4d), %(channels)d channels (%(cols)4d cols x %(rows)4d rows)" % l))
+
+                    # Sanity check
+                    if l['bottom'] < l['top'] or l['right'] < l['left'] or l['channels'] > 64:
+                        logger.debug(INDENT_OUTPUT(2, "Something's not right about that, trying to skip layer."))
+                        self.fd.seek(6 * l['channels'] + 12, 1) # 1: SEEK_CUR
+                        self._skip_block("layer info: extra data", 2)
+                        continue # next layer
+
+                    # Read channel infos
+                    l['chlengths'] = []
+                    l['chids']  = []
+                    # - 'hackish': addressing with -1 and -2 will wrap around to the two extra channels
+                    l['chindex'] = [ -1 ] * (l['channels'] + 2)
+                    for j in range(l['channels']):
+                        chid, chlen = self._readf(">hL")
+                        l['chids'].append(chid)
+                        l['chlengths'].append(chlen)
+                        logger.debug(INDENT_OUTPUT(3, "Channel %2d: id=%2d, %5d bytes" % (j, chid, chlen)))
+                        if -2 <= chid < l['channels']:
+                            # pythons negative list-indexs: [ 0, 1, 2, 3, ... -2, -1]
+                            l['chindex'][chid] = j
+                        else:
+                            logger.debug(INDENT_OUTPUT(3, "Unexpected channel id %d" % chid))
+                        l['chidstr'] = CHANNEL_SUFFIXES.get(chid, "?")
+
+                    # put channel info into connection
+                    linfo.append(l)
+
+                    #
+                    # Blend mode
+                    #
+                    bm = {}
+
+                    (bm['sig'], bm['key'], bm['opacity'], bm['clipping'], bm['flags'], bm['filler'],
+                     ) = self._readf(">4s4sBBBB")
+                    bm['opacp'] = (bm['opacity'] * 100 + 127) / 255
+                    bm['clipname'] = bm['clipping'] and "non-base" or "base"
+                    bm['blending'] = BLENDINGS.get(bm['key'])
+                    l['blend_mode'] = bm
+
+                    logger.debug(INDENT_OUTPUT(3, "Blending mode: sig=%(sig)s key=%(key)s opacity=%(opacity)d(%(opacp)d%%) clipping=%(clipping)d(%(clipname)s) flags=%(flags)x" % bm))
+
+                    # remember position for skipping unrecognized data
+                    (extralen,) = self._readf(">L")
+                    extrastart = self.fd.tell()
+
+                    #
+                    # Layer mask data
+                    #
+                    m = {}
+                    (m['size'],) = self._readf(">L")
+                    if m['size']:
+                        (m['top'], m['left'], m['bottom'], m['right'], m['default_color'], m['flags'],
+                         ) = self._readf(">llllBB")
+                        # skip remainder
+                        self.fd.seek(m['size'] - 18, 1) # 1: SEEK_CUR
+                        m['rows'], m['cols'] = m['bottom'] - m['top'], m['right'] - m['left']
+                    l['mask'] = m
+
+                    self._skip_block("layer blending ranges", 3)
+
+                    #
+                    # Layer name
+                    #
+                    name_start = self.fd.tell()
+                    (l['namelen'],) = self._readf(">B")
+                    addl_layer_data_start = name_start + self._pad4(l['namelen'] + 1)
+                    # - "-1": one byte traling 0byte. "-1": one byte garble.
+                    # (l['name'],) = readf(f, ">%ds" % (self._pad4(1+l['namelen'])-2))
+                    (l['name'],) = self._readf(">%ds" % (l['namelen']))
+
+                    logger.debug(INDENT_OUTPUT(3, "Name: '%s'" % l['name']))
+
+                    self.fd.seek(addl_layer_data_start, 0)
+
+
+                    #
+                    # Read add'l Layer Information
+                    #
+                    while self.fd.tell() < extrastart + extralen:
+                        (signature, key, size, ) = self._readf(">4s4sL") # (n,) is a 1-tuple.
+                        logger.debug(INDENT_OUTPUT(3, "Addl info: sig='%s' key='%s' size='%d'" %
+                                                    (signature, key, size)))
+                        next_addl_offset = self.fd.tell() + self._pad2(size)
+
+                        if key == 'luni':
+                            namelen = self._readf(">L")[0]
+                            l['name'] = u''
+                            for count in range(0, namelen):
+                                l['name'] += unichr(self._readf(">H")[0])
+
+                            logger.debug(INDENT_OUTPUT(4, u"Unicode Name: '%s'" % l['name']))
+                        elif key == 'TySh':
+                            version = self._readf(">H")[0]
+                            (xx, xy, yx, yy, tx, ty,) = self._readf(">dddddd") #transform
+                            text_version = self._readf(">H")[0]
+                            text_desc_version = self._readf(">L")[0]
+                            text_desc = self._read_descriptor()
+                            warp_version = self._readf(">H")[0]
+                            warp_desc_version = self._readf(">L")[0]
+                            warp_desc = self._read_descriptor()
+                            (left,top,right,bottom,) = self._readf(">llll")
+
+                            logger.debug(INDENT_OUTPUT(4, "ver=%d tver=%d dver=%d"
+                                          % (version, text_version, text_desc_version)))
+                            logger.debug(INDENT_OUTPUT(4, "%f %f %f %f %f %f" % (xx, xy, yx, yy, tx, ty,)))
+                            logger.debug(INDENT_OUTPUT(4, "l=%f t=%f r=%f b=%f"
+                                          % (left, top, right, bottom)))
+
+                            l['text_layer'] = {}
+                            l['text_layer']['text_desc'] = text_desc
+                            l['text_layer']['text_transform'] = (xx, xy, yx, yy, tx, ty,)
+                            l['text_layer']['left'] = left
+                            l['text_layer']['top'] = top
+                            l['text_layer']['right'] = right
+                            l['text_layer']['bottom'] = bottom
+
+                        self.fd.seek(next_addl_offset, 0)
+
+                    # Skip over any extra data
+                    self.fd.seek(extrastart + extralen, 0) # 0: SEEK_SET
+
+                    self.layers.append(l)
+
+                for i in range(self.num_layers):
+                    # Empty layer
+                    if linfo[i]['rows'] * linfo[i]['cols'] == 0:
+                        self.images.append(None)
+                        self.parse_image(linfo[i], is_layer=True)
+                        continue
+
+                    self.images.append([0, 0, 0, 0])
+                    self.parse_image(linfo[i], is_layer=True)
+                    if linfo[i]['channels'] == 2:
+                        l = self.images[i][0]
+                        a = self.images[i][3]
+                        self.images[i] = Image.merge('LA', [l, a])
+                    else:
+                        # is there an alpha channel?
+                        if type(self.images[i][3]) is int:
+                            self.images[i] = Image.merge('RGB', self.images[i][0:3])
+                        else:
+                            self.images[i] = Image.merge('RGBA', self.images[i])
+
+            else:
+                logger.debug(INDENT_OUTPUT(1, "Layer info section is empty"))
+
+            skip = miscstart + misclen - self.fd.tell()
+            if skip:
+                logger.debug("")
+                logger.debug("Skipped %d bytes at end of misc data?" % skip)
+                self.fd.seek(skip, 1) # 1: SEEK_CUR
+        else:
+            logger.debug(INDENT_OUTPUT(1, "Misc info section is empty"))
+
+    def parse_image_data(self):
+
+        if not self.header:
+            self.parse_header()
+        if not self.ressources:
+            self._skip_block("image resources", new_line=True)
+            self.ressources = 'not parsed'
+        if not self.layers:
+            self._skip_block("image layers", new_line=True)
+            self.layers = 'not parsed'
+
+        self.merged_image = []
+        li = {}
+        li['chids'] = range(self.header['channels'])
+        li['chlengths'] = [ None ] * self.header['channels'] # dummy data
+        (li['name'], li['channels'], li['rows'], li['cols']) = ('merged', self.header['channels'], self.header['rows'], self.header['cols'])
+        li['layernum'] = -1
+        self.parse_image(li, is_layer=False)
+        if li['channels'] == 1:
+            self.merged_image = self.merged_image[0]
+        elif li['channels'] == 3:
+            self.merged_image = Image.merge('RGB', self.merged_image)
+        elif li['channels'] >= 4 and self.header['mode'] == 3:
+            self.merged_image = Image.merge('RGBA', self.merged_image[:4])
+        else:
+            raise ValueError('Unsupported mode or number of channels')
+

+ 177 - 0
gameduino2/registers.py

@@ -0,0 +1,177 @@
+
+def RGB(r, g, b):
+    return (r << 16) | (g << 8) | b
+
+def DEGREES(n):
+    # Convert degrees to Furmans
+    return 65536 * n / 360
+
+NEVER                = 0
+LESS                 = 1
+LEQUAL               = 2
+GREATER              = 3
+GEQUAL               = 4
+EQUAL                = 5
+NOTEQUAL             = 6
+ALWAYS               = 7
+
+ARGB1555             = 0
+L1                   = 1
+L4                   = 2
+L8                   = 3
+RGB332               = 4
+ARGB2                = 5
+ARGB4                = 6
+RGB565               = 7
+PALETTED             = 8
+TEXT8X8              = 9
+TEXTVGA              = 10
+BARGRAPH             = 11
+
+NEAREST              = 0
+BILINEAR             = 1
+
+BORDER               = 0
+REPEAT               = 1
+
+KEEP                 = 1
+REPLACE              = 2
+INCR                 = 3
+DECR                 = 4
+INVERT               = 5
+
+DLSWAP_DONE          = 0
+DLSWAP_LINE          = 1
+DLSWAP_FRAME         = 2
+
+INT_SWAP             = 1
+INT_TOUCH            = 2
+INT_TAG              = 4
+INT_SOUND            = 8
+INT_PLAYBACK         = 16
+INT_CMDEMPTY         = 32
+INT_CMDFLAG          = 64
+INT_CONVCOMPLETE     = 128
+
+TOUCHMODE_OFF        = 0
+TOUCHMODE_ONESHOT    = 1
+TOUCHMODE_FRAME      = 2
+TOUCHMODE_CONTINUOUS = 3
+
+ZERO                 = 0
+ONE                  = 1
+SRC_ALPHA            = 2
+DST_ALPHA            = 3
+ONE_MINUS_SRC_ALPHA  = 4
+ONE_MINUS_DST_ALPHA  = 5
+
+BITMAPS              = 1
+POINTS               = 2
+LINES                = 3
+LINE_STRIP           = 4
+EDGE_STRIP_R         = 5
+EDGE_STRIP_L         = 6
+EDGE_STRIP_A         = 7
+EDGE_STRIP_B         = 8
+RECTS                = 9
+
+OPT_MONO             = 1
+OPT_NODL             = 2
+OPT_FLAT             = 256
+OPT_CENTERX          = 512
+OPT_CENTERY          = 1024
+OPT_CENTER           = (OPT_CENTERX | OPT_CENTERY)
+OPT_NOBACK           = 4096
+OPT_NOTICKS          = 8192
+OPT_NOHM             = 16384
+OPT_NOPOINTER        = 16384
+OPT_NOSECS           = 32768
+OPT_NOHANDS          = 49152
+OPT_RIGHTX           = 2048
+OPT_SIGNED           = 256
+
+LINEAR_SAMPLES       = 0
+ULAW_SAMPLES         = 1
+ADPCM_SAMPLES        = 2
+
+RAM_CMD              = 1081344
+RAM_DL               = 1048576
+RAM_PAL              = 1056768
+RAM_REG              = 1057792
+RAM_TOP              = 1064960
+REG_CLOCK            = 1057800
+REG_CMD_DL           = 1058028
+REG_CMD_READ         = 1058020
+REG_CMD_WRITE        = 1058024
+REG_CPURESET         = 1057820
+REG_CSPREAD          = 1057892
+REG_DITHER           = 1057884
+REG_DLSWAP           = 1057872
+REG_FRAMES           = 1057796
+REG_FREQUENCY        = 1057804
+REG_GPIO             = 1057936
+REG_GPIO_DIR         = 1057932
+REG_HCYCLE           = 1057832
+REG_HOFFSET          = 1057836
+REG_HSIZE            = 1057840
+REG_HSYNC0           = 1057844
+REG_HSYNC1           = 1057848
+REG_ID               = 1057792
+REG_INT_EN           = 1057948
+REG_INT_FLAGS        = 1057944
+REG_INT_MASK         = 1057952
+REG_J1_INT           = 1057940
+REG_MACRO_0          = 1057992
+REG_MACRO_1          = 1057996
+REG_OUTBITS          = 1057880
+REG_PCLK             = 1057900
+REG_PCLK_POL         = 1057896
+REG_PLAY             = 1057928
+REG_PLAYBACK_FORMAT  = 1057972
+REG_PLAYBACK_FREQ    = 1057968
+REG_PLAYBACK_LENGTH  = 1057960
+REG_PLAYBACK_LOOP    = 1057976
+REG_PLAYBACK_PLAY    = 1057980
+REG_PLAYBACK_READPTR = 1057964
+REG_PLAYBACK_START   = 1057956
+REG_PWM_DUTY         = 1057988
+REG_PWM_HZ           = 1057984
+REG_RENDERMODE       = 1057808
+REG_ROTATE           = 1057876
+REG_SNAPSHOT         = 1057816
+REG_SNAPY            = 1057812
+REG_SOUND            = 1057924
+REG_SWIZZLE          = 1057888
+REG_TAG              = 1057912
+REG_TAG_X            = 1057904
+REG_TAG_Y            = 1057908
+REG_TOUCH_ADC_MODE   = 1058036
+REG_TOUCH_CHARGE     = 1058040
+REG_TOUCH_DIRECT_XY  = 1058164
+REG_TOUCH_DIRECT_Z1Z2 = 1058168
+REG_TOUCH_MODE       = 1058032
+REG_TOUCH_OVERSAMPLE = 1058048
+REG_TOUCH_RAW_XY     = 1058056
+REG_TOUCH_RZ         = 1058060
+REG_TOUCH_RZTHRESH   = 1058052
+REG_TOUCH_SCREEN_XY  = 1058064
+REG_TOUCH_SETTLE     = 1058044
+REG_TOUCH_TAG        = 1058072
+REG_TOUCH_TAG_XY     = 1058068
+REG_TOUCH_TRANSFORM_A = 1058076
+REG_TOUCH_TRANSFORM_B = 1058080
+REG_TOUCH_TRANSFORM_C = 1058084
+REG_TOUCH_TRANSFORM_D = 1058088
+REG_TOUCH_TRANSFORM_E = 1058092
+REG_TOUCH_TRANSFORM_F = 1058096
+REG_TRACKER          = 1085440
+REG_VCYCLE           = 1057852
+REG_VOFFSET          = 1057856
+REG_VOL_PB           = 1057916
+REG_VOL_SOUND        = 1057920
+REG_VSIZE            = 1057860
+REG_VSYNC0           = 1057864
+REG_VSYNC1           = 1057868
+
+def VERTEX2II(x, y, handle, cell):
+    return ((2 << 30) | ((x & 511) << 21) | ((y & 511) << 12) | ((handle & 31) << 7) | ((cell & 127) << 0))

+ 146 - 0
gameduino2/remote.py

@@ -0,0 +1,146 @@
+import array
+import StringIO
+import zlib
+import Image
+import struct
+import time
+
+import convert
+
+def pad4(s):
+    while len(s) % 4:
+        s += chr(0)
+    return s
+
+import gameduino2.registers as reg
+import gameduino2.base
+
+class GD2Exception(Exception):
+    pass
+
+class GD2(gameduino2.base.GD2):
+
+    def __init__(self, transport):
+        self.ramptr = 0
+        self.wp = 0
+
+        self.cc = StringIO.StringIO()
+        self.transport = transport
+
+        self.transport.reset()
+
+        self.wr(reg.REG_GPIO, 0xff)
+
+        self.wr32(reg.REG_HCYCLE, 525)
+        self.wr32(reg.REG_HOFFSET, 43)
+        self.wr32(reg.REG_HSIZE, 480)
+        self.wr32(reg.REG_HSYNC0, 0)
+        self.wr32(reg.REG_HSYNC1, 41)
+        self.wr32(reg.REG_VCYCLE, 286)
+        self.wr32(reg.REG_VOFFSET, 12)
+        self.wr32(reg.REG_VSIZE, 272)
+        self.wr32(reg.REG_VSYNC0, 0)
+        self.wr32(reg.REG_VSYNC1, 10)
+        self.wr32(reg.REG_CSPREAD, 1)
+        self.wr32(reg.REG_DITHER, 1)
+        self.wr32(reg.REG_PCLK_POL, 1)
+
+        self.cmd_dlstart()
+        self.Clear(1,1,1)
+        self.Display()
+        self.cmd_swap()
+        self.cmd_regwrite(reg.REG_PCLK, 5)
+        self.finish()
+
+    def rdstr(self, a, n):
+        return self.transport.rdstr(a, n)
+
+    def wrstr(self, a, s):
+        return self.transport.wrstr(a, s)
+
+    def wr(self, a, v):
+        """ Write a single byte ``v`` to address ``a``. """
+        self.wrstr(a, chr(v))
+
+    def rd(self, a):
+        """ Read byte at address ``a`` """
+        return struct.unpack("<B", self.rdstr(a, 1))[0]
+
+    def rd16(self, a):
+        return struct.unpack("<H", self.rdstr(a, 2))[0]
+
+    def rd32(self, a):
+        return struct.unpack("<L", self.rdstr(a, 4))[0]
+
+    def wr16(self, a, v):
+        """ Write 16-bit value ``v`` at to address ``a`` """
+        self.wrstr(a, struct.pack("<H", v))
+
+    def wr32(self, a, v):
+        """ Write 32-bit value ``v`` at to address ``a`` """
+        self.wrstr(a, struct.pack("<L", v))
+
+    def command(self, cmd):
+        assert (len(cmd) % 4) == 0
+        while True:
+            rp = self.rd16(reg.REG_CMD_READ)
+            if rp & 3:
+                raise GD2Exception, "At address %04X" % self.rd32(reg.RAM_CMD)
+            fullness = (self.wp - rp) & 4095
+            available = 4096 - 4 - fullness
+            if min(1000, len(cmd)) <= available:
+                break
+        canwrite = min(available, len(cmd))
+        if canwrite != len(cmd):
+            self.command(cmd[:canwrite])
+            return self.command(cmd[canwrite:])
+        if (self.wp + len(cmd)) < 4096:
+            self.wrstr(reg.RAM_CMD + self.wp, cmd)
+            self.wp += len(cmd)
+        else:
+            rem = 4096 - self.wp
+            self.wrstr(reg.RAM_CMD + self.wp, cmd[:rem])
+            self.wrstr(reg.RAM_CMD, cmd[rem:])
+            self.wp = len(cmd) - rem
+        assert (self.wp % 4) == 0
+        assert 0 <= self.wp < 4096
+        self.wr16(reg.REG_CMD_WRITE, self.wp)
+
+    def finish(self):
+        c = self.cc.getvalue()
+        self.cc = StringIO.StringIO()
+        for i in range(0, len(c), 4096):
+            res = self.command(c[i:i+4096])
+        # self.v.waitidle()
+
+    def c(self, cmdstr):
+        self.cc.write(pad4(cmdstr))
+
+    def load_image(self, im, dither = False, fmt = reg.ARGB1555, sampling = reg.NEAREST, zoom = 1):
+        strides = {
+            reg.L1 :       lambda w: w / 8,
+            reg.L4 :       lambda w: w / 2,
+            reg.L8 :       lambda w: w,
+            reg.RGB332 :   lambda w: w,
+            reg.ARGB2 :    lambda w: w,
+            reg.PALETTED : lambda w: w,
+        }
+        if fmt == reg.L1:
+            i = Image.new(im.mode, ((im.size[0] + 7) & ~7, im.size[1]))
+            i.paste(im, (0,0))
+            im = i
+        stride = strides.get(fmt, lambda w: 2 * w)(im.size[0])
+        (_,da) = convert.convert(im, dither = dither, fmt = fmt)
+        self.ramptr = (self.ramptr + 1) & ~1
+        self.zload(self.ramptr, da.tostring())
+        self.BitmapSize(sampling, reg.BORDER, reg.BORDER, min(511, zoom * im.size[0]), min(511, zoom * im.size[1]))
+        self.BitmapSource(self.ramptr)
+        self.BitmapLayout(fmt, stride, im.size[1])
+        self.ramptr += len(da.tostring())
+
+    def zload(self, dst, data):
+        self.cmd_inflate(dst)
+        c = zlib.compress(data)
+        print len(data), len(c)
+        self.c(pad4(c))
+

+ 3 - 0
go

@@ -0,0 +1,3 @@
+sudo rm -rf /usr/local/lib/python2.7/dist-packages/gameduino2/*
+sudo python setup.py install
+gd2asset -f xxx testdata/felix.png,testdata/felix.png,format=L1 testdata/Hobby-of-night.ttf testdata/0.wav

+ 127 - 0
scripts/gd2asset

@@ -0,0 +1,127 @@
+#!/usr/bin/python
+
+import gameduino2 as gd2
+import os
+import array
+
+import Image
+import wave
+import audioop
+
+def cname(s):
+    """ make name s C-friendly """
+    for c in "-+.":
+        s = s.replace(c, "_")
+    return s.upper()
+
+class GD2Assets(gd2.prep.AssetBin):
+
+    def __init__(self, opts, args):
+        self.opts = opts
+        self.args = args
+        gd2.prep.AssetBin.__init__(self)
+        self.header = opts.get('-o', 'default_assets.h')
+
+        if '-f' in opts:
+            self.asset_file = opts['-f']
+
+        self.handlers = {
+            'png' : (self.image, "PNG image file (options: format)"),
+            'jpg' : (self.image, "JPEG image file (options: format)"),
+            'bmp' : (self.image, "BMP image file (options: format)"),
+            'gif' : (self.image, "GIF image file (options: format)"),
+            'ttf' : (self.ttf, "TrueType font file (options: format, size)"),
+            'wav' : (self.sample, "Audio sample, mono 16-bit (no options)"),
+        }
+
+    def parse_format(self, format):
+        formats = ('ARGB1555', 'L1', 'L4', 'L8', 'RGB332', 'ARGB2', 'ARGB4', 'RGB565')
+        if format not in formats:
+            print 'ERROR: unknown format "%s"' % format
+            print
+            print 'Formats are: %s' % " ".join(formats)
+            sys.exit(1)
+        return eval("gd2." + format)
+
+    def image(self, suffix, ff, format = 'ARGB4'):
+        name = cname(os.path.basename(ff[0])[:-1 - len(suffix)])
+        self.load_handle(name, [Image.open(f) for f in ff], self.parse_format(format))
+
+    def ttf(self, suffix, f, size = '12', format = 'L4'):
+        name = cname(os.path.basename(f[0])[:-1 - len(suffix)])
+        self.load_ttf(name, f[0], int(size), self.parse_format(format))
+
+    def sample(self, suffix, f):
+        name = os.path.basename(f[0])[:-1 - len(suffix)].upper()
+        f = wave.open(f[0], "rb")
+        if f.getnchannels() != 1:
+            print "Sorry - .wav file must be mono"
+            sys.exit(1)
+        if f.getsampwidth() != 2:
+            print "Sorry - .wav file must be 16-bit"
+            sys.exit(1)
+        freq = f.getframerate()
+        pcm16 = f.readframes(f.getnframes())
+        (adpcm, _) = audioop.lin2adpcm(pcm16, f.getsampwidth(), (0,0))
+        adpcm = adpcm[:len(adpcm) & ~7]
+        da = array.array('B', [((ord(c) >> 4) | ((15 & ord(c)) << 4)) for c in adpcm])
+        self.align(8)
+        self.add(name, da.tostring())
+        self.define(name + "_LENGTH", len(da))
+        self.define(name + "_FREQ", freq)
+
+    def error(self, suffix, f, **_):
+        print 'ERROR: cannot identify type of file "%s"' % f
+        print
+        print 'recognized file types are:'
+        for suffix,(_, doc) in sorted(self.handlers.items()):
+            print '  %s   %s' % (suffix, doc)
+        sys.exit(1)
+
+    def addall(self):
+        for a in self.args:
+            a = a.split(',')
+            f = []
+            vars = {}
+            for part in a:
+                if '=' in part:
+                    varval = part.split('=')
+                    if len(varval) != 2:
+                        print 'ERROR: syntax error in asset specification "%s"' % setting
+                        sys.exit(1)
+                    (var, val) = varval
+                    vars[var] = val
+                else:
+                    f.append(part)
+            suffix = f[0].split('.')[-1].lower()
+            (handler, _) = self.handlers.get(suffix, (self.error, ''))
+            handler(suffix, f, **vars)
+
+if __name__ == '__main__':
+    import sys, getopt
+    try:
+        optlist, args = getopt.getopt(sys.argv[1:], "o:f:")
+    except getopt.GetoptError:
+        print 'usage: gd2asset <options> <assets>'
+        print
+        print '  -o <name>   output header file'
+        print '  -f <name>   output asset file (default is header file)'
+        print
+        print 'If no output header file is given, then "default_assets.h" is used'
+        print
+        print 'Each asset is a filename, optionally followed by some var=val'
+        print 'assignments. For example:'
+        print '  pic1.png                 image, format ARGB4'
+        print '  pic2.jpg,format=L8       image, format L8'
+        print '  serif.ttf,size=16        font, 16 pixels high'
+        print
+        print 'The assets are compiled into flash, or if the "-f" option is given'
+        print 'into a file. In this case the file should be copied to the'
+        print 'microSD card.'
+        print 'In either case, calling LOAD_ASSETS() from the program loads all'
+        print 'assets.'
+
+        sys.exit(1)
+
+    optdict = dict(optlist)
+    GD2Assets(optdict, args).make()

+ 12 - 0
setup.py

@@ -0,0 +1,12 @@
+from distutils.core import setup
+setup(name='gameduino2',
+      version='0.1.4',
+      author='James Bowman',
+      author_email='jamesb@excamera.com',
+      url='http://gameduino.com',
+      description='Package of Gameduino 2 development tools',
+      long_description='Gameduino 2 (http://gameduino.com) is an Arduino video games adapter.  This package contains tools for developers: data preparation, remote control.',
+      license='GPL',
+      packages=['gameduino2'],
+      scripts=['scripts/gd2asset'],
+)

BIN
testdata/0.wav


BIN
testdata/Hobby-of-night.ttf


BIN
testdata/felix.png


+ 4 - 0
winsetup.py

@@ -0,0 +1,4 @@
+from distutils.core import setup
+import py2exe
+
+setup(console=['scripts/gd2asset'])