Close

Pillow tweaking, Ordered Dithering

A project log for PolaPi-Zero

Yet another Polaroid-like camera. Now with flavor! RapsberryPi Zero, Python, Memory-LCD, less wiring. It's all about monochrome.

muthMuth 05/07/2017 at 09:160 Comments

I was not too happy with a flickering effect during 'live-view'. Very visible on large gray area, where the black and white pixels should be alternate in a king of chess board pattern in order to create the gray illusion. With the error-diffusion method (Floyd-Steinberg), this alternating pattern will change randomly every frames. Maybe due to the LCD latency, flickering appears.

So a solution could consists to change from the pseudo random pattern of this Floyd dithering to the ordered method. The last one is less precise, but the fix Bayer matrix gives constant patterns.

The flickering is not clearly visible on this video, but the changed method gives a more smooth animation.

To reach this point, it was inconceivable to write the Ordered dithering algorithm in pure Python. I tried though, but far too slow as predicted. The solution was to directly implement that code in the compiled imaging Python library, Pillow. The following will be a summary of my Pillow compilation adventure...

Pillow, compiling Python lib

According their documentation, PIL (original and older version of the library) and Pillow cannot coexist. So first step is to uninstall them. Be sure to have the Python tools, and then I uninstall the previous version with PIP:

sudo apt-get install python-dev python-setuptools python-pip
sudo pip uninstall Pillow

Then I followed the Pillow building tutorial, and get and uncompress the sources:

wget https://pypi.python.org/packages/93/73/66854f63b1941aad9af18a1de59f9cf95ad1a87c801540222e332f6688d7/Pillow-4.1.1.tar.gz#md5=f2565954955c8d10f3b4f1f72f852bf7
tar -zxvf Pillow-4.1.1.tar.gz
Some external C library are needed:
sudo apt-get install libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev python-tk
And then I manage to get everything compiled and installed :
cd Pillow-4.1.1/
sudo python setup.py build
sudo python setup.py install
Inserting the Ordered dithering code

Now I used this very nice document of Lee Daniel Crocker to get a well written C code. It has to be inserted in /Pillow-4.1.1/libImaging/Convert.c :

static Imaging tobilevel(Imaging imOut, Imaging imIn, int dither) {
	int pattern[8][8] = {
    { 0, 32,  8, 40,  2, 34, 10, 42},   /* 8x8 Bayer ordered dithering  */
    {48, 16, 56, 24, 50, 18, 58, 26},   /* pattern.  Each input pixel   */
    {12, 44,  4, 36, 14, 46,  6, 38},   /* is scaled to the 0..63 range */
    {60, 28, 52, 20, 62, 30, 54, 22},   /* before looking in this table */
    { 3, 35, 11, 43,  1, 33,  9, 41},   /* to determine the action.     */
    {51, 19, 59, 27, 49, 17, 57, 25},
    {15, 47,  7, 39, 13, 45,  5, 37},
    {63, 31, 55, 23, 61, 29, 53, 21} };

//    [ ... ]
		
		if (dither == 1){
			int l;
			/* map each pixel to black or white, using ordered diffusion */
			ImagingSectionEnter(&cookie);
			for (y = 0; y < imIn->ysize; y++) {
				UINT8* in  = (UINT8*) imIn->image[y];
				UINT8* out = imOut->image8[y];

				for (x = 0; x < imIn->xsize; x++) {
					/* pick closest colour */
					l = CLIP(in[x] / 4);
					if (l > pattern[x & 7][y & 7]) {
						out[x] = 255; 
					} else {
						out[x] = 0; 
					}
				}
			}
			ImagingSectionLeave(&cookie);			
		
		} else {
\\ [ ... ]
The nice thing is this was foreseen in the Pillow development, as Dither variable could takes yet defines constants in /Pillow-4.1.1/PIL/Image.py :

# [...]
# dithers
NEAREST = NONE = 0
ORDERED = 1  # Not yet implemented
RASTERIZE = 2  # Not yet implemented
FLOYDSTEINBERG = 3  # default
# [...]
The Convert.c file I modified is probably not suitable to request a pull in the library, but I've put it in the PolaPi-Zero git repository.

I will soon update the SD card image with this last version.

Discussions