Beating Heart Pattern

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:

  1. 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

  2. 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.

  3. 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.

1 Like