prep.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. import os
  2. import sys
  3. import array
  4. import struct
  5. import zlib
  6. import textwrap
  7. import wave
  8. import audioop
  9. import Image
  10. import ImageFont
  11. import ImageDraw
  12. import gameduino2 as gd2
  13. import gameduino2.convert
  14. from gameduino2.imbytes import imbytes
  15. def stretch(im):
  16. d = imbytes(im)
  17. # print min(d), max(d)
  18. r = max(d) - min(d)
  19. return im.point(lambda x: (x - min(d)) * 255 / r)
  20. def getalpha(im):
  21. return im.split()[3]
  22. def tile(tw, th, im):
  23. tiles = []
  24. for y in range(0, im.size[1], th):
  25. for x in range(0, im.size[0], tw):
  26. tiles.append(im.crop((x, y, x + tw, y + th)))
  27. o = Image.new(im.mode, (tw, th * len(tiles)))
  28. for i,t in enumerate(tiles):
  29. o.paste(t, (0, i * th))
  30. return o
  31. def split(tw, th, im):
  32. tiles = []
  33. for y in range(0, im.size[1], th):
  34. for x in range(0, im.size[0], tw):
  35. tiles.append(im.crop((x, y, x + tw, y + th)))
  36. return tiles
  37. def join(tiles):
  38. (tw, th) = tiles[0].size
  39. o = Image.new(tiles[0].mode, (tw, th * len(tiles)))
  40. for i,t in enumerate(tiles):
  41. o.paste(t, (0, i * th))
  42. return o
  43. def setwidth(im, w):
  44. e = Image.new(im.mode, (w, im.size[1]))
  45. e.paste(im, (0, 0))
  46. return e
  47. def even(im):
  48. w = im.size[0]
  49. if (w % 2) == 0:
  50. return im
  51. else:
  52. return setwidth(im, w + 1)
  53. def extents(im):
  54. """ find pixel extents of im, as a box """
  55. w,h = im.size
  56. cols = [set(imbytes(im.crop((i, 0, i + 1, im.size[1])))) != set([0]) for i in range(w)]
  57. rows = [set(imbytes(im.crop((0, i, im.size[0], i + 1)))) != set([0]) for i in range(h)]
  58. if not True in cols:
  59. return (0, 0, 0, 0)
  60. else:
  61. x0 = cols.index(True)
  62. y0 = rows.index(True)
  63. while not cols[-1]:
  64. cols.pop()
  65. while not rows[-1]:
  66. rows.pop()
  67. return (x0, y0, len(cols), len(rows))
  68. def ul(x):
  69. return str(x) + "UL"
  70. def preview(np, fmt, size, data):
  71. def chan(x):
  72. return Image.fromstring("L", size, (255 * x).astype(np.uint8))
  73. if fmt == gd2.L1:
  74. r = Image.fromstring("1", size, data)
  75. elif fmt == gd2.L8:
  76. r = Image.fromstring("L", size, data)
  77. else:
  78. d8 = np.array(data)
  79. a = np.ones(size[0] * size[1])
  80. (r, g, b) = (a, a, a)
  81. if fmt == gd2.ARGB4:
  82. d16 = np.array(array.array('H', data.tostring()))
  83. a = (15 & (d16 >> 12)) / 15.
  84. r = (15 & (d16 >> 8)) / 15.
  85. g = (15 & (d16 >> 4)) / 15.
  86. b = (15 & (d16 >> 0)) / 15.
  87. elif fmt == gd2.RGB565:
  88. d16 = np.array(array.array('H', data.tostring()))
  89. r = (31 & (d16 >> 11)) / 31.
  90. g = (63 & (d16 >> 5)) / 63.
  91. b = (31 & (d16 >> 0)) / 31.
  92. elif fmt == gd2.ARGB1555:
  93. d16 = np.array(array.array('H', data.tostring()))
  94. a = (1 & (d16 >> 15))
  95. r = (31 & (d16 >> 10)) / 31.
  96. g = (31 & (d16 >> 5)) / 31.
  97. b = (31 & (d16 >> 0)) / 31.
  98. elif fmt == gd2.ARGB2:
  99. a = (3 & (d8 >> 6)) / 3.
  100. r = (3 & (d8 >> 4)) / 3.
  101. g = (3 & (d8 >> 2)) / 3.
  102. b = (3 & (d8 >> 0)) / 3.
  103. elif fmt == gd2.RGB332:
  104. r = (7 & (d8 >> 5)) / 7.
  105. g = (7 & (d8 >> 2)) / 7.
  106. b = (3 & (d8 >> 0)) / 3.
  107. elif fmt == gd2.L4:
  108. hi = d8 >> 4
  109. lo = d8 & 15
  110. d4 = np.column_stack((hi, lo)).flatten()
  111. r = d4 / 15.
  112. g = r
  113. b = r
  114. o = Image.merge("RGB", [chan(c) for c in (r, g, b)])
  115. bg = Image.new("RGB", size, (128, 128, 128))
  116. r = Image.composite(o, bg, chan(a))
  117. r = r.resize((r.size[0] * 5, r.size[1] * 5), Image.NEAREST)
  118. return r
  119. import gameduino2.base
  120. class AssetBin(gameduino2.base.GD2):
  121. asset_file = None
  122. prefix = ""
  123. previews = False
  124. def __init__(self):
  125. self.alldata = ""
  126. self.commands = ""
  127. self.defines = []
  128. self.inits = []
  129. self.handle = 0
  130. self.np = None
  131. self.bitmaps = []
  132. # Set defaults for FT800. target_810() modifies these
  133. self.device = 'GD2'
  134. self.maxram = 256 * 1024
  135. self.maxhandles = 15
  136. def target_810(self):
  137. self.device = 'GD3'
  138. self.maxram = 1024 * 1024
  139. self.maxhandles = 32
  140. def define(self, n, v):
  141. self.defines.append((self.prefix + n, v))
  142. def add(self, name, s):
  143. if name:
  144. self.defines.append((name, ul(len(self.alldata))))
  145. self.alldata += s
  146. def c(self, s):
  147. self.commands += s
  148. def addim(self, name, im, fmt, dither = False):
  149. (_, imgdata) = gameduino2.convert.convert(im, dither, fmt = fmt)
  150. self.add(name, imgdata.tostring())
  151. (w, h) = im.size
  152. if name:
  153. self.defines.append(("%s_WIDTH" % name, w))
  154. self.defines.append(("%s_HEIGHT" % name, h))
  155. def align(self, n):
  156. while (len(self.alldata) % n) != 0:
  157. self.alldata += chr(0)
  158. def load_handle(self, name, images, fmt,
  159. dither = False,
  160. filter = gd2.NEAREST,
  161. scale = 1,
  162. rotating = False):
  163. if self.maxhandles <= self.handle:
  164. print "Error: too many bitmap handles used, limit is %d" % self.maxhandles
  165. sys.exit(1)
  166. (w, h) = images[0].size
  167. self.define("%s_HANDLE" % name, self.handle)
  168. name = self.prefix + name
  169. self.defines.append(("%s_WIDTH" % name, w))
  170. self.defines.append(("%s_HEIGHT" % name, h))
  171. self.defines.append(("%s_CELLS" % name, len(images)))
  172. self.align(2)
  173. self.bitmaps.append((name.lower(), w, h, w / 2, h / 2, len(self.alldata), fmt, self.handle))
  174. self.BitmapHandle(self.handle);
  175. self.BitmapSource(len(self.alldata));
  176. if not rotating:
  177. (vw, vh) = (scale * w, scale * h)
  178. vsz = 0
  179. else:
  180. vsz = int(scale * max(w, h))
  181. self.define("%s_SIZE" % name, vsz)
  182. (vw, vh) = (vsz, vsz)
  183. self.BitmapSize(filter, gd2.BORDER, gd2.BORDER, vw, vh);
  184. self.inits.append("static const shape_t %s_SHAPE = {%d, %d, %d, %d};" % (name, self.handle, w, h, vsz))
  185. # aw is aligned width
  186. # For L1, L2, L4 formats the width must be a whole number of bytes
  187. if fmt == gd2.L1:
  188. aw = (w + 7) & ~7
  189. elif fmt == gd2.L2:
  190. aw = (w + 3) & ~3
  191. elif fmt == gd2.L4:
  192. aw = (w + 1) & ~1
  193. else:
  194. aw = w
  195. bpl = {
  196. gd2.ARGB1555 : 2 * aw,
  197. gd2.L1 : aw / 8,
  198. gd2.L2 : aw / 4,
  199. gd2.L4 : aw / 2,
  200. gd2.L8 : aw,
  201. gd2.RGB332 : aw,
  202. gd2.ARGB2 : aw,
  203. gd2.ARGB4 : 2 * aw,
  204. gd2.RGB565 : 2 * aw,
  205. gd2.PALETTED : aw}[fmt]
  206. if fmt == gd2.L2:
  207. print w, aw, bpl
  208. self.BitmapLayout(fmt, bpl, h);
  209. for i,im in enumerate(images):
  210. if aw != w:
  211. im = setwidth(im, aw)
  212. if hasattr(im, "imgdata"):
  213. imgdata = im.imgdata
  214. else:
  215. (_, imgdata) = gameduino2.convert.convert(im, dither, fmt = fmt)
  216. """
  217. if self.previews:
  218. if not self.np:
  219. import numpy
  220. self.np = numpy
  221. preview(self.np, fmt, im.size, imgdata).save("previews/%s-%s-%02d.png" % (self.name, name, i))
  222. """
  223. self.alldata += imgdata.tostring()
  224. self.handle += 1
  225. def load_font(self, name, ims, widths, fmt, **args):
  226. trim0 = 0
  227. while ims[trim0] is None:
  228. trim0 += 1
  229. p0 = len(self.alldata)
  230. h = self.handle
  231. tims = ims[trim0:]
  232. self.load_handle(name, tims, fmt, **args)
  233. self.align(4)
  234. p1 = len(self.alldata)
  235. # Compute memory required by one char
  236. onechar = (p1 - p0) / len(tims)
  237. # print name, 'font requires', (p1 - p0), 'bytes'
  238. sz = ims[trim0].size
  239. self.BitmapSource(p0 - (onechar * trim0));
  240. widths = [max(0, w) for w in widths]
  241. dblock = array.array('B', widths).tostring() + struct.pack("<5i", fmt, 1, sz[0], sz[1], p0 - (onechar * trim0))
  242. self.alldata += dblock
  243. self.cmd_setfont(h, p1);
  244. def load_ttf(self, name, ttfname, size, format):
  245. font = ImageFont.truetype(ttfname, size)
  246. sizes = [font.getsize(chr(c)) for c in range(32, 128)]
  247. fw = max([w for (w, _) in sizes])
  248. fh = max([h for (_, h) in sizes])
  249. # print fw, fh
  250. alle = {}
  251. for i in range(1, 96):
  252. im = Image.new("L", (fw+8, fh))
  253. dr = ImageDraw.Draw(im)
  254. dr.text((8,0), chr(32 + i), font=font, fill=255)
  255. alle[i] = gd2.prep.extents(im)
  256. fw = max([(x1 - x0) for (x0, y0, x1, y1) in alle.values()])
  257. ims = ([None] * 32) + [Image.new("L", (fw, fh)) for i in range(32, 128)]
  258. for i in range(33, 127):
  259. dr = ImageDraw.Draw(ims[i])
  260. (x0, y0, x1, y1) = alle[i - 32]
  261. x = max(0, 8 - x0)
  262. if x > 0:
  263. sizes[i - 32] = (sizes[i - 32][0] - x, sizes[i - 32][1])
  264. dr.text((x, 0), chr(i), font=font, fill=255)
  265. # imgtools.view(im)
  266. widths = ([0] * 32) + [w for (w, _) in sizes]
  267. self.load_font(name, ims, widths, format)
  268. """
  269. def dxt1(self, imagefile):
  270. import numpy
  271. im = Image.open(imagefile).resize((480,272), Image.ANTIALIAS)
  272. dxt = "%s.dxt" % imagefile
  273. if not os.access(dxt, os.R_OK):
  274. im.save("tmp.png")
  275. assert os.system("squishpng tmp.png %s" % dxt) == 0
  276. sz = (480 / 4, 272 / 4)
  277. def rgb(cs):
  278. r = (cs >> 11) << 3
  279. g = 0xff & ((cs >> 5) << 2)
  280. b = 0xff & (cs << 3)
  281. return (r,g,b)
  282. def rgbim(cs):
  283. return Image.merge("RGB", [Image.fromarray(c.astype(numpy.uint8).reshape(*sz)) for c in rgb(cs)])
  284. def morton1(x):
  285. v = x & 0x55555555
  286. v = (v | (v >> 1)) & 0x33333333;
  287. v = (v | (v >> 2)) & 0x0F0F0F0F;
  288. v = (v | (v >> 4)) & 0x00FF00FF;
  289. v = (v | (v >> 8)) & 0x0000FFFF;
  290. return v.astype(numpy.uint16)
  291. h = open(dxt)
  292. h.read(8)
  293. c0s = []
  294. c1s = []
  295. bits = []
  296. for i in range(sz[0] * sz[1]):
  297. tile = h.read(8)
  298. c0,c1,bit = struct.unpack("2HI", tile)
  299. b0 = bit & 0x55555555
  300. b1 = (bit >> 1) & 0x55555555
  301. is0 = ~b1 & ~b0
  302. is1 = ~b1 & b0
  303. is2 = b1 & ~b0
  304. is3 = b1 & b0
  305. if c0<=c1:
  306. if bit == 0xaaaaaaaa:
  307. # print c0<c1,hex(bit)
  308. r0,g0,b0 = rgb(c0)
  309. r1,g1,b1 = rgb(c1)
  310. r = (r0 + r1) / 2
  311. g = (g0 + g1) / 2
  312. b = (b0 + b1) / 2
  313. # r,g,b = (255,0,255)
  314. c0 = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3)
  315. c1 = c0
  316. bit = 0
  317. else:
  318. if 0:
  319. for i in range(0, 32, 2):
  320. fld = (3 & (bit >> i))
  321. if 3 == fld:
  322. print c0, c1, hex(bit), i
  323. assert 0
  324. # Map as follows:
  325. # 0 -> 0
  326. # 1 -> 3
  327. # 2 -> 1
  328. bit = (is1 * 3) + (is2 * 1)
  329. assert is3 == 0
  330. else:
  331. # 0 -> 0
  332. # 1 -> 3
  333. # 2 -> 1
  334. # 3 -> 2
  335. bit = (is1 * 3) + (is2 * 1) + (is3 * 2)
  336. if 0:
  337. c0 = 63 << 5 # green
  338. c1 = 31 << 11 # red
  339. bit = 0x0f0f0f0f
  340. c0s.append(c0)
  341. c1s.append(c1)
  342. bits.append(bit)
  343. c0s = numpy.array(c0s, numpy.uint16)
  344. c1s = numpy.array(c1s, numpy.uint16)
  345. bits = numpy.array(bits, numpy.uint32)
  346. bits0 = morton1(bits)
  347. bits1 = morton1(bits >> 1)
  348. if 1:
  349. def im44(v):
  350. im = Image.new("1", (4,4))
  351. pix = im.load()
  352. for i in range(4):
  353. for j in range(4):
  354. if v & (1 << (4 * i + j)):
  355. pix[j,i] = 255
  356. return im
  357. ims = [im44(i) for i in range(65536)]
  358. b0im = Image.new("1", (480, 272))
  359. b1im = Image.new("1", (480, 272))
  360. xys = [(x,y) for y in range(0, 272, 4) for x in range(0, 480, 4)]
  361. for i,(x,y) in enumerate(xys):
  362. b0im.paste(ims[bits0[i]], (x, y))
  363. b1im.paste(ims[bits1[i]], (x, y))
  364. class MockImage:
  365. def __init__(self, s, size):
  366. self.imgdata = s
  367. self.size = size
  368. self.load_handle("BACKGROUND_COLOR", [MockImage(c0s, sz), MockImage(c1s, sz)], gd2.RGB565, scale = 4)
  369. self.load_handle("BACKGROUND_BITS", [b0im, b1im], gd2.L1)
  370. """
  371. def load_sample(self, name, filename):
  372. f = wave.open(filename, "rb")
  373. if f.getnchannels() != 1:
  374. print "Sorry - .wav file must be mono"
  375. sys.exit(1)
  376. if f.getsampwidth() != 2:
  377. print "Sorry - .wav file must be 16-bit"
  378. sys.exit(1)
  379. freq = f.getframerate()
  380. pcm16 = f.readframes(f.getnframes())
  381. (adpcm, _) = audioop.lin2adpcm(pcm16, f.getsampwidth(), (0,0))
  382. adpcm = adpcm[:len(adpcm) & ~7]
  383. da = array.array('B', [((ord(c) >> 4) | ((15 & ord(c)) << 4)) for c in adpcm])
  384. self.align(8)
  385. self.add(name, da.tostring())
  386. self.define(name + "_LENGTH", len(da))
  387. self.define(name + "_FREQ", freq)
  388. header = None
  389. def make(self):
  390. if self.header is None:
  391. name = self.__class__.__name__.lower() + "_assets.h"
  392. else:
  393. name = self.header
  394. self.name = name
  395. self.addall()
  396. if len(self.alldata) > self.maxram:
  397. print "Error: The data (%d bytes) is larger the the %s RAM (%d)" % (len(self.alldata), self.device, self.maxram)
  398. sys.exit(1)
  399. self.defines.append((self.prefix + "ASSETS_END", ul(len(self.alldata))))
  400. self.cmd_inflate(0)
  401. calldata = zlib.compress(self.alldata, 9)
  402. print 'Assets report'
  403. print '-------------'
  404. print 'Header file: %s' % self.header
  405. print '%s RAM used: %d' % (self.device, len(self.alldata))
  406. if not self.asset_file:
  407. print 'Flash used: %d' % len(calldata)
  408. else:
  409. print 'Output file: %s' % self.asset_file
  410. print 'File size: %d' % len(calldata)
  411. commandblock = self.commands + calldata
  412. hh = open(name, "w")
  413. for (nm,v) in self.defines:
  414. print >>hh, "#define %s %s" % (nm, v)
  415. p = self.prefix
  416. if self.asset_file is None:
  417. print >>hh, "static const PROGMEM uint8_t %s__assets[%d] = {" % (p, len(commandblock))
  418. print >>hh, textwrap.fill(", ".join(["%d" % ord(c) for c in commandblock]))
  419. print >>hh, "};"
  420. print >>hh, "#define %sLOAD_ASSETS() (GD.copy(%s__assets, sizeof(%s__assets)), GD.loadptr = %sASSETS_END)" % (p, p, p, p)
  421. else:
  422. open(self.asset_file, "wb").write(commandblock)
  423. print >>hh, '#define %sLOAD_ASSETS() (GD.safeload("%s"), GD.loadptr = %sASSETS_END)' % (p, self.asset_file, p)
  424. print >>hh
  425. for i in self.inits:
  426. print >>hh, i
  427. self.dump_bitmaps(hh)
  428. self.extras(hh)
  429. def dump_bitmaps(self, hh):
  430. hh.write("struct {\n")
  431. hh.write("".join([" Bitmap %s;\n" % bm[0] for bm in self.bitmaps]))
  432. hh.write("} bitmaps = {\n")
  433. fmt = " /* %16s */ {{%3d, %3d}, {%3d, %3d}, %#8xUL, %2d, %2d}"
  434. hh.write(",\n".join([fmt % bm for bm in self.bitmaps]))
  435. hh.write("\n};\n")
  436. def addall(self):
  437. pass
  438. def extras(self, hh):
  439. pass
  440. class ForthAssetBin(AssetBin):
  441. def make(self):
  442. if self.header is None:
  443. name = self.__class__.__name__.lower() + "_assets.fs"
  444. else:
  445. name = self.header
  446. self.name = name
  447. self.addall()
  448. if len(self.alldata) > self.maxram:
  449. print "Error: The data (%d bytes) is larger the the GD2 RAM" % len(self.alldata)
  450. sys.exit(1)
  451. self.defines.append((self.prefix + "ASSETS_END", ul(len(self.alldata))))
  452. self.cmd_inflate(0)
  453. calldata = zlib.compress(self.alldata)
  454. print 'Assets report'
  455. print '-------------'
  456. print 'Header file: %s' % self.header
  457. print 'GD2 RAM used: %d' % len(self.alldata)
  458. if not self.asset_file:
  459. print 'Flash used: %d' % len(calldata)
  460. else:
  461. print 'Output file: %s' % self.asset_file
  462. print 'File size: %d' % len(calldata)
  463. commandblock = self.commands + calldata
  464. commandblock += chr(0) * ((-len(commandblock)) & 3)
  465. commandblock32 = array.array('I', commandblock)
  466. hh = open(name, "w")
  467. print >>hh, "base @"
  468. print >>hh, "hex"
  469. if self.asset_file is None:
  470. print >>hh, textwrap.fill(" ".join(["%08x GD.c" % c for c in commandblock32]))
  471. else:
  472. open(self.asset_file, "wb").write(commandblock)
  473. print >>hh, "decimal"
  474. for (nm,v) in self.defines:
  475. print >>hh, "%-8s constant %s" % (str(v).replace('UL', ''), nm)
  476. print >>hh, "base !"
  477. self.extras(hh)