Skip to content

Instantly share code, notes, and snippets.

@sjlongland
Last active December 20, 2015 22:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sjlongland/6fd8974ef95202d35f5e to your computer and use it in GitHub Desktop.
Save sjlongland/6fd8974ef95202d35f5e to your computer and use it in GitHub Desktop.
import gd
import argparse
from sys import stdout
parser = argparse.ArgumentParser(
description='Extract bits from the average colour of each tile',
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('--image', help='Image file to parse')
parser.add_argument('--invert', help='Invert the bits',
action='store_const', default=False, const=True)
parser.add_argument('--binary', help='Write binary',
action='store_const', default=False, const=True)
parser.add_argument('--bits', help='Number of bits per grouping',
type=int, default=8)
parser.add_argument('--output', help='Output file')
parser.add_argument('--tile-w', type=int, default=12,
help='Width of FEZ tile')
parser.add_argument('--tile-h', type=int, default=12,
help='Height of FEZ tile')
parser.add_argument('--offset-x', type=int, default=1,
help='X co-ordinate of first tile')
parser.add_argument('--offset-y', type=int, default=1,
help='Y co-ordinate of first tile')
parser.add_argument('--direction', default='rd',
help='''Order of traversal, one of:
- rd ("right then down": Start top-left, scan to bottom-right,
horizontally)
- ld ("left then down": Start top-right, scan to bottom-left,
horizontally)
- ru ("right then up": Start bottom-left, scan to top-right,
horizontally)
- lu ("left then up": Start bottom-right, scan to top-left,
horizontally)
- dr ("down then right": Start top-left, scan to bottom-right,
vertically)
- dl ("down then left": Start top-right, scan to bottom-left,
vertically)
- ur ("up then right": Start bottom-left, scan to top-right,
vertically)
- ul ("up then left": Start bottom-right, scan to top-left,
vertically)''')
args = parser.parse_args()
img = gd.image(args.image)
if args.output is not None:
out = file(args.output,'w')
else:
out = stdout
# Compute number of tiles
(p_width, p_height) = img.size()
width = (p_width - args.offset_x) // args.tile_w
height = (p_height - args.offset_y) // args.tile_h
tile_pos = lambda x, y : (((x*args.tile_w) + args.offset_x), \
((y*args.tile_h) + args.offset_y))
# Compute the number of pixels in a tile
tile_num = args.tile_w * args.tile_h
# Bit threshold
threshold = tile_num // 2
# Enumerate a list of co-ordinates to read.
if args.direction[0] in ('d','u'):
v_dir = args.direction[0]
h_dir = args.direction[1]
hv_dir = 'v'
elif args.direction[0] in ('l','r'):
h_dir = args.direction[0]
v_dir = args.direction[1]
hv_dir = 'h'
else:
raise ValueError('Invalid direction %r' % args.direction)
x_posn = range(0, width)
y_posn = range(0, height)
if h_dir == 'l':
x_posn.reverse()
if v_dir == 'u':
y_posn.reverse()
if hv_dir == 'v':
coordinates = [tile_pos(x,y) for x in x_posn for y in y_posn]
else:
coordinates = [tile_pos(x,y) for y in y_posn for x in x_posn]
# Inversion logic
if args.invert:
read_bit = lambda p : not bool(p)
else:
read_bit = lambda p : bool(p)
# Average bit of tile
def avg_bit(c):
(tx, ty) = c
colour = 0
for py in range(ty, ty + args.tile_h):
for px in range(tx, tx + args.tile_w):
if read_bit(img.getPixel((px,py))):
colour += 1
return colour > threshold
# Traverse the image, collect bits.
bits = args.bits
byte = 0
for c in coordinates:
bit = avg_bit(c)
if args.binary:
byte <<= 1
if bit:
byte |= 1
else:
if bit:
out.write('1')
else:
out.write('0')
bits -= 1
if bits <= 0:
bits = args.bits
if args.binary:
out.write(chr(byte))
byte = 0
else:
out.write('\n')
if bits != args.bits:
if args.binary:
out.write(chr(byte))
else:
out.write('\n')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment