I used the following Python program:
save this as 'picToPat.py'
import argparse, os
from PIL import Image
# Parse command line.
parser = argparse.ArgumentParser()
parser.add_argument("pictureFile", help="The picture to be converted")
args = parser.parse_args()
#################
numImages = 1
#################
# Generate pattern code
print("// Image helpers")
print("function numImages() { return _images.length; }")
print("function numFrames(image) { return _images[image][0]; }")
print("function numRows(image) { return _images[image][1]; }")
print("function numColumns(image) { return _images[image][2]; }")
print("// Pixel helpers")
print("function pixelOffset(image, frame, row, column) { return (frame*numRows(image)*numColumns(image)) + (row*numColumns(image)) + column + 3; }")
print("function putPixel(image, frame, row, column, value) { _images[image][pixelOffset(image,frame,row,column)] = value; }")
print("function getPixel(image, frame, row, column, wrap) { if (column >= numColumns(image,frame)) { if (!wrap) return 0; else column = column % numColumns(image,frame); } return _images[image][pixelOffset(image,frame,row,column)]; }")
print("function RGBA(r, g, b, a) { return (r << 8) + g + (b >> 8) + (a >> 16); }")
print("function _r(pixval) { return ((pixval >> 8) & 0xff) >> 8; }")
print("function _g(pixval) { return ((pixval) & 0xff) >> 8; }")
print("function _b(pixval) { return ((pixval << 8) & 0xff) >> 8; }")
print("function _a(pixval) { return ((pixval << 16) & 0xff) >> 8; }")
print("// Helpers to reduce the text size")
print("// Viewport storage")
print("var viewportOffsetX = 0, viewportOffsetY = 0, viewportWrap = true;")
print("// Rendering functions")
print("export function render2D(index, x, y) {")
print(" dimension = sqrt(pixelCount); row = trunc(x * dimension); col = trunc(y * dimension);")
print(" pixval = getPixel(0, 0, row + viewportOffsetY, col + viewportOffsetX, viewportWrap);")
print(" rgb(pow(_r(pixval),2), pow(_g(pixval),2), pow(_b(pixval),2));")
print("}")
print("// Animation storage")
print("export var elapsedTime = 0, loopTime = 50;")
print("export function beforeRender(delta) {")
print(" elapsedTime += delta; if (elapsedTime > loopTime) {")
print(" elapsedTime = 0;")
print(" // Scroll horizontally; wrap when we get to the end.")
print(" if (numColumns(0) > sqrt(pixelCount)) {")
print(" viewportOffsetX += 1; if (viewportOffsetX >= numColumns(0,0,0)) { viewportOffsetX = 0; }")
print(" }")
print(" }")
print("}")
print("")
print("")
# Allocate working storage
print("// Image storage")
#print("var imageCount = %u;" % numImages)
print("var _images = array(%u); // allocate image storage" % numImages)
#
# *****
# REPEAT THE FOLLOWING FOR EACH IMAGE FILE:
# *****
#
for iterImage in range(numImages):
# read image and add its bitmaps to the pattern
filename, extension = os.path.splitext(args.pictureFile)
with Image.open(args.pictureFile, mode='r').convert('RGB') as im:
pixels = im.load()
print("// Image #%u (%ux%u) generated from '%s'" % (iterImage, im.width, im.height, args.pictureFile))
# Process frames
imageFrames = 1
if getattr(im, "is_animated", False):
imageFrames = im.n_frames
print(" _images[%u] = array(3 + %u); _images[%u][0] = %u; _images[%u][1] = %u; _images[%u][2] = %u; // frames, rows, columns" % (iterImage, imageFrames*im.height*im.width, iterImage, imageFrames, iterImage, im.height, iterImage, im.width))
# Dump image frame(s)
for iterFrame in range(imageFrames):
print(" // Frame #%u" % iterFrame)
# Dump image pixels
for iterRow in range(im.height):
print(" // Row #%u: " % iterRow)
for iterCol in range(im.width):
_r, _g, _b = im.getpixel((iterCol, iterRow))
_a = 0
numPixelsPerTextRow = 4
if iterCol % numPixelsPerTextRow == 0:
print(' ', end='')
print('putPixel(%u,%u,%u,%u,RGBA(0x%02x,0x%02x,0x%02x,0x%02x)); ' % (iterImage, iterFrame, iterRow, iterCol, _r, _g, _b, _a), end='')
if iterCol % numPixelsPerTextRow == (numPixelsPerTextRow - 1):
print('')
# Finish this row.
print(" // end Row #%u" % iterRow)
# Finish this frame.
print(" // end Frame #%u" % iterFrame)
# Finish this image.
print(" // end Image #%u" % iterImage)
im.close()
…but:
-
the image library I used doesn’t like some GIFs so those I’d have to explode into their individual frames using gifsicle and paste the generated arrays together manually; and
-
a Pixelblaze can only hold around 10K array elements, so the combination of image size and frame count needs to be low enough to fit within that limit.
-
For larger images I’ve needed to drop the
packRGBA()
helper function and load the arrays with larger chunks, store RGBA as a 31-bit literal by dropping one bit of resolution, or use a 16-color palette table where the image arrays contain 4-bit palette indexes, but I haven’t built any general-purpose tools for those steps.