Writing image pixel data to printer code I've been trying to take image pixel data and write it out into printer code and my results are rather slow.

Here is a simplified version of what I have so far (image is a PIL Image object of 1200 x 1800 pixels):

# ~36 seconds on beaglebone
f.write(pclhead)
pix = image.getdata()
for y in xrange(1800):
  row = '\xff'*72
  ### vvv slow code vvv ###
  for x in xrange(1200):
    (r,g,b) = pix[y*1200+x]
    row += chr(g)+chr(b)+chr(r)
  ### ^^^ slow code ^^^ ###
  row += '\xff'*72
  f.write(row)
f.write(pclfoot)


I know the loop can be optimized way better, but how?

My code is running on a beaglebone so speed is slower than you'd expect, but composing the complex images takes about 5 seconds. I wouldn't expect my printer code function (which just reorders the data) to take much longer than about 2 or 3 seconds. My first attempt (with getpixel) took 90 seconds. Now I have it down to 36 seconds.  Surely I can make this quite a bit faster yet.

For comparison, just so we can all see where the hold up is, this code runs in 0.1 secs (but, of course, is lacking the important data):

# ~0.1 seconds on beaglebone
f.write(pclhead)
pix = image.getdata()
for y in xrange(1800):
  row = '\xff'*72
  ### vvv substituted vvv ###
  row += '\xff'*3600
  ### ^^^ substituted ^^^ ###
  row += '\xff'*72
  f.write(row)
f.write(pclfoot)


I guess a simplified version of this problem is to rewrite something like the following:

[ (1,2,3), (1,2,3) ... 1200 times ]


into

[ 2, 3, 1, 2, 3, 1, etc... ]


but as a string

"\x02\x03\x01\x02\x03\x01 ... "
Start with storing the '\xff' * 72 string as a constant; Python strings are immutable, recreating that string each time is not necessary.

Next, use a list to collect all strings, then join at the end; this is cheaper than constant string concatenation.

Last, avoid attribute lookups in critical sections; assign any attribute lookups done more than once to a local name:

from operator import itemgetter

bgr = itemgetter(1,2,0)

pix = image.getdata()
rowstart = '\xff' * 72
f_write = f.write
empty_join = ''.join
for y in xrange(1800):
    row = [rowstart]
    r_extend = row.extend
    for x in xrange(1200):
        r_extend(map(chr, bgr(pix[y*1200+x])))
    r.append(rowstart)

    f_write(empty_join(row))


You can experiment with joining the whole row (including rowstart) or writing out rowstart values separately; the following version might be faster still depending on write speed versus list concatenation speed:

for y in xrange(1800):
    f_write(rowstart)
    row = []
    r_extend = row.extend
    for x in xrange(1200):
        r_extend(map(chr, bgr(pix[y*1200+x])))
    f_write(empty_join(row))
    f_write(rowstart)