Skip to content

Instantly share code, notes, and snippets.

@PM2Ring
Created July 14, 2017 17:30
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PM2Ring/b09216123cca86e9b9cf889bfd3c5cec to your computer and use it in GitHub Desktop.
Save PM2Ring/b09216123cca86e9b9cf889bfd3c5cec to your computer and use it in GitHub Desktop.
Demonstrate the PIL .fromarray bitmap bug
#! /usr/bin/env python3
''' Demonstrate the PIL .fromarray bitmap bug
PIL's Image.fromarray function has a bug with mode '1' images
See
https://stackoverflow.com/questions/2761645/error-converting-pil-bw-images-to-numpy-arrays
Written by PM 2Ring 2017.06.26
'''
import tkinter as tk
import numpy as np
from PIL import Image, ImageTk
class Viewer:
''' View a list of PIL Images in separate windows '''
def __init__(self, imglist):
self.root = tk.Tk()
tk.Label(self.root, text='Main Window').pack()
for title, img in imglist:
self.show(img, title)
self.root.mainloop()
def show(self, img, title):
win = tk.Toplevel(self.root)
win.title(title)
win.photo = ImageTk.PhotoImage(img)
tk.Label(win, image=win.photo).pack()
tk.Label(win, text=title).pack()
# Some test patterns
gliders = '''\
.o....o.
..o..o..
ooo..ooo
........
ooo..ooo
..o..o..
.o....o.
'''
diamond = '''\
...o....
..o.o...
.o...o..
o.....o.
.o...o..
..o.o...
...o....
'''
def decode_pattern(s):
''' Convert a bitmap pattern from string format to a 2D list of ints '''
rows = s.splitlines()
width = max(map(len, rows))
return [[int(c not in ' .') for c in row.ljust(width)] for row in rows]
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Various ways to convert a 2D Numpy binary array to a bitmap image
# The standard work-around: first convert to greyscale
def img_grey(data):
return Image.fromarray(data * 255, mode='L').convert('1')
# Use .frombytes instead of .fromarray.
# This is >2x faster than img_grey
def img_frombytes(data):
size = data.shape[::-1]
databytes = np.packbits(data, axis=1)
return Image.frombytes(mode='1', size=size, data=databytes)
# The broken one
def img_fromarray_bad(data):
databytes = np.packbits(data, axis=1)
return Image.fromarray(databytes, mode='1')
# This works... if the scale's a multiple of 8
# Otherwise, there's vertical distortion due to lost pixels
def img_fromarray_ok(data):
data = np.tile(data, (8, 8))
data = data.repeat(scale, axis=1).repeat(scale // 8, axis=0)
databytes = np.packbits(data, axis=1)
return Image.fromarray(databytes, mode='1')
# Another attempt that works if the scale % 8 == 0.
# This one gets the size correct when scale % 8 != 0
def img_fromarray_alt(data):
data = np.tile(data, (8, 8))
data = data[::8, ::1]
data = np.packbits(data, axis=1)
return Image.fromarray(data, mode='1')
# Also correct when scale % 8 == 0, and has the correct size otherwise.
# But it gets very wonky for odd scales.
def img_fromarray_grey(data):
data *= 255
data = np.tile(data, (8, 8))
data = data[::8, ::8]
return Image.fromarray(data, mode='1')
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
pattern = diamond
# Convert the pattern to a 2D list...
bits = decode_pattern(pattern)
#print(*map(str, bits), sep=',\n')
# ... and a Numpy array
arr = np.array(bits, dtype=np.uint8)
#print(arr)
scale = 12
# Scale the image bits
big_arr = arr.repeat(scale, axis=1).repeat(scale, axis=0)
imglist = [
('grey', img_grey(big_arr)),
('frombytes', img_frombytes(big_arr)),
('fromarray_ok', img_fromarray_ok(arr)),
('fromarray_alt', img_fromarray_alt(big_arr)),
('fromarray_grey', img_fromarray_grey(big_arr)),
('fromarray_bad', img_fromarray_bad(big_arr)),
]
print('Sizes')
for title, img in imglist:
print('{:15}: {}'.format(title, img.size))
Viewer(imglist)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment