From 598d97daffe27d1fdc20a79dbac7ff3fa353cfc1 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 19 Dec 2013 21:38:31 -0800 Subject: [PATCH 01/24] Reorg+add test for #452 --- Tests/test_image_getpixel.py | 72 +++++++++++++++++------------------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/Tests/test_image_getpixel.py b/Tests/test_image_getpixel.py index 6c5e8b084..104b67097 100644 --- a/Tests/test_image_getpixel.py +++ b/Tests/test_image_getpixel.py @@ -9,49 +9,43 @@ def color(mode): else: return tuple(range(1, bands+1)) -def test_pixel(): - def pixel(mode): + +def check(mode, c=None): + if not c: c = color(mode) - im = Image.new(mode, (1, 1), None) - im.putpixel((0, 0), c) - return im.getpixel((0, 0)) + + #check putpixel + im = Image.new(mode, (1, 1), None) + im.putpixel((0, 0), c) + assert_equal(im.getpixel((0, 0)), c, + "put/getpixel roundtrip failed for mode %s, color %s" % + (mode, c)) + + # check inital color + im = Image.new(mode, (1, 1), c) + assert_equal(im.getpixel((0, 0)), c, + "initial color failed for mode %s, color %s " % + (mode, color)) - assert_equal(pixel("1"), 1) - assert_equal(pixel("L"), 1) - assert_equal(pixel("LA"), (1, 2)) - assert_equal(pixel("I"), 1) - assert_equal(pixel("I;16"), 1) - assert_equal(pixel("I;16B"), 1) - assert_equal(pixel("F"), 1.0) - assert_equal(pixel("P"), 1) - assert_equal(pixel("PA"), (1, 2)) - assert_equal(pixel("RGB"), (1, 2, 3)) - assert_equal(pixel("RGBA"), (1, 2, 3, 4)) - assert_equal(pixel("RGBX"), (1, 2, 3, 4)) - assert_equal(pixel("CMYK"), (1, 2, 3, 4)) - assert_equal(pixel("YCbCr"), (1, 2, 3)) +def test_basic(): + for mode in ("1", "L", "LA", "I", "I;16", "I;16B", "F", + "P", "PA", "RGB", "RGBA", "RGBX", "CMYK","YCbCr"): + check(mode) -def test_image(): - - def pixel(mode): - im = Image.new(mode, (1, 1), color(mode)) - return im.getpixel((0, 0)) - - assert_equal(pixel("1"), 1) - assert_equal(pixel("L"), 1) - assert_equal(pixel("LA"), (1, 2)) - assert_equal(pixel("I"), 1) - assert_equal(pixel("I;16"), 1) - assert_equal(pixel("I;16B"), 1) - assert_equal(pixel("F"), 1.0) - assert_equal(pixel("P"), 1) - assert_equal(pixel("PA"), (1, 2)) - assert_equal(pixel("RGB"), (1, 2, 3)) - assert_equal(pixel("RGBA"), (1, 2, 3, 4)) - assert_equal(pixel("RGBX"), (1, 2, 3, 4)) - assert_equal(pixel("CMYK"), (1, 2, 3, 4)) - assert_equal(pixel("YCbCr"), (1, 2, 3)) +def test_signedness(): + # see https://github.com/python-imaging/Pillow/issues/452 + # pixelaccess is using signed int* instead of uint* + for mode in ("I;16", "I;16B"): + check(mode, 2**15-1) + check(mode, 2**15) + check(mode, 2**15+1) + check(mode, 2**16-1) + check("I", 2**31-1) + check("I", 2**31) + check("I", 2**31+1) + check("I", 2**32-1) + From 77c36d6edcf9c24280e978fffb899a29e04f0dd0 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 19 Dec 2013 21:39:18 -0800 Subject: [PATCH 02/24] Using uint* for pixel access in mode I;16 and I;32, fixes #452 --- _imaging.c | 8 ++++---- libImaging/Access.c | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/_imaging.c b/_imaging.c index dcb063081..0cce74ce0 100644 --- a/_imaging.c +++ b/_imaging.c @@ -463,14 +463,14 @@ getpixel(Imaging im, ImagingAccess access, int x, int y) { union { UINT8 b[4]; - INT16 h; - INT32 i; + UINT16 h; + UINT32 i; FLOAT32 f; } pixel; if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) { - PyErr_SetString(PyExc_IndexError, outside_image); - return NULL; + PyErr_SetString(PyExc_IndexError, outside_image); + return NULL; } access->get_pixel(im, x, y, &pixel); diff --git a/libImaging/Access.c b/libImaging/Access.c index 70eb1af4c..82a4d5297 100644 --- a/libImaging/Access.c +++ b/libImaging/Access.c @@ -94,11 +94,11 @@ static void get_pixel_16L(Imaging im, int x, int y, void* color) { UINT8* in = (UINT8*) &im->image[y][x+x]; - INT16* out = color; + UINT16* out = color; #ifdef WORDS_BIGENDIAN out[0] = in[0] + (in[1]<<8); #else - out[0] = *(INT16*) in; + out[0] = *(UINT16*) in; #endif } @@ -106,9 +106,9 @@ static void get_pixel_16B(Imaging im, int x, int y, void* color) { UINT8* in = (UINT8*) &im->image[y][x+x]; - INT16* out = color; + UINT16* out = color; #ifdef WORDS_BIGENDIAN - out[0] = *(INT16*) in; + out[0] = *(UINT16*) in; #else out[0] = in[1] + (in[0]<<8); #endif @@ -125,11 +125,11 @@ static void get_pixel_32L(Imaging im, int x, int y, void* color) { UINT8* in = (UINT8*) &im->image[y][x*4]; - INT32* out = color; + UINT32* out = color; #ifdef WORDS_BIGENDIAN out[0] = in[0] + (in[1]<<8) + (in[2]<<16) + (in[3]<<24); #else - out[0] = *(INT32*) in; + out[0] = *(UINT32*) in; #endif } @@ -137,9 +137,9 @@ static void get_pixel_32B(Imaging im, int x, int y, void* color) { UINT8* in = (UINT8*) &im->image[y][x*4]; - INT32* out = color; + UINT32* out = color; #ifdef WORDS_BIGENDIAN - out[0] = *(INT32*) in; + out[0] = *(UINT32*) in; #else out[0] = in[3] + (in[2]<<8) + (in[1]<<16) + (in[0]<<24); #endif From 1dd80b26250206b8d5f00e91b3e24bda2570b745 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 30 Dec 2013 21:00:32 -0800 Subject: [PATCH 03/24] reverted int32 changes --- Tests/test_image_getpixel.py | 4 ---- _imaging.c | 2 +- libImaging/Access.c | 8 ++++---- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/Tests/test_image_getpixel.py b/Tests/test_image_getpixel.py index 104b67097..0acfd6698 100644 --- a/Tests/test_image_getpixel.py +++ b/Tests/test_image_getpixel.py @@ -42,10 +42,6 @@ def test_signedness(): check(mode, 2**15+1) check(mode, 2**16-1) - check("I", 2**31-1) - check("I", 2**31) - check("I", 2**31+1) - check("I", 2**32-1) diff --git a/_imaging.c b/_imaging.c index 0cce74ce0..3511de4cf 100644 --- a/_imaging.c +++ b/_imaging.c @@ -464,7 +464,7 @@ getpixel(Imaging im, ImagingAccess access, int x, int y) union { UINT8 b[4]; UINT16 h; - UINT32 i; + INT32 i; FLOAT32 f; } pixel; diff --git a/libImaging/Access.c b/libImaging/Access.c index 82a4d5297..62c97f3a3 100644 --- a/libImaging/Access.c +++ b/libImaging/Access.c @@ -125,11 +125,11 @@ static void get_pixel_32L(Imaging im, int x, int y, void* color) { UINT8* in = (UINT8*) &im->image[y][x*4]; - UINT32* out = color; + INT32* out = color; #ifdef WORDS_BIGENDIAN out[0] = in[0] + (in[1]<<8) + (in[2]<<16) + (in[3]<<24); #else - out[0] = *(UINT32*) in; + out[0] = *(INT32*) in; #endif } @@ -137,9 +137,9 @@ static void get_pixel_32B(Imaging im, int x, int y, void* color) { UINT8* in = (UINT8*) &im->image[y][x*4]; - UINT32* out = color; + INT32* out = color; #ifdef WORDS_BIGENDIAN - out[0] = *(UINT32*) in; + out[0] = *(INT32*) in; #else out[0] = in[3] + (in[2]<<8) + (in[1]<<16) + (in[0]<<24); #endif From 5efe737f6f8a10a40cef4bb0a251c100101c9762 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 5 Jan 2014 10:41:25 -0800 Subject: [PATCH 04/24] Cffi pixel access object, #248 --- PIL/Image.py | 25 +++++- PIL/PyAccess.py | 191 +++++++++++++++++++++++++++++++++++++++++++++ Tests/test_cffi.py | 48 ++++++++++++ _imaging.c | 23 ++++++ 4 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 PIL/PyAccess.py create mode 100644 Tests/test_cffi.py diff --git a/PIL/Image.py b/PIL/Image.py index 87631d6f7..8c5844c64 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -100,6 +100,17 @@ import os, sys import collections import numbers +USE_CFFI_ACCESS = True +try: + import cffi + HAS_CFFI=True +except: + HAS_CFFI=False + +#if HAS_CFFI: +# from PIL import PyAccess + + def isImageType(t): """ @@ -468,6 +479,7 @@ class Image: self.info = {} self.category = NORMAL self.readonly = 0 + self.pyaccess = None def _new(self, im): new = Image() @@ -645,6 +657,13 @@ class Image: self.palette.mode = "RGBA" if self.im: + if HAS_CFFI and USE_CFFI_ACCESS: + if self.pyaccess: + return self.pyaccess + from PIL import PyAccess + self.pyaccess = PyAccess.new(self, self.readonly) + if self.pyaccess: + return self.pyaccess return self.im.pixel_access(self.readonly) def verify(self): @@ -974,6 +993,8 @@ class Image: """ self.load() + if self.pyaccess: + return self.pyaccess.getpixel(xy) return self.im.getpixel(xy) def getprojection(self): @@ -1290,7 +1311,9 @@ class Image: self.load() if self.readonly: self._copy() - + + if self.pyaccess: # undone , what about the readonly? Fix premerge + return self.pyaccess.putpixel(xy,value) return self.im.putpixel(xy, value) def resize(self, size, resample=NEAREST): diff --git a/PIL/PyAccess.py b/PIL/PyAccess.py new file mode 100644 index 000000000..95abbf238 --- /dev/null +++ b/PIL/PyAccess.py @@ -0,0 +1,191 @@ +# +# The Python Imaging Library +# Pillow fork +# +# Python implementation of the PixelAccess Object +# +# Copyright (c) 1997-2009 by Secret Labs AB. All rights reserved. +# Copyright (c) 1995-2009 by Fredrik Lundh. +# Copyright (c) 2013 Eric Soroos +# +# See the README file for information on usage and redistribution +# + +# Notes: +# +# * Implements the pixel access object following Access. +# * Does not implement the line functions, as they don't appear to be used +# * Taking only the tuple form, which is used from python. +# * Fill.c uses the integer form, but it's still going to use the old Access.c implementation. +# + +from __future__ import print_function + +from cffi import FFI + +DEBUG = 0 + +defs = """ +struct ImagingMemoryInstance { + + /* Format */ + char mode[7]; /* Band names ("1", "L", "P", "RGB", "RGBA", "CMYK", "YCbCr", "BGR;xy") */ + int type; /* Data type (IMAGING_TYPE_*) */ + int depth; /* Depth (ignored in this version) */ + int bands; /* Number of bands (1, 2, 3, or 4) */ + int xsize; /* Image dimension. */ + int ysize; + + /* Colour palette (for "P" images only) */ + void *palette; + + /* Data pointers */ + unsigned char **image8; /* Set for 8-bit images (pixelsize=1). */ + int **image32; /* Set for 32-bit images (pixelsize=4). */ + + /* Internals */ + char **image; /* Actual raster data. */ + char *block; /* Set if data is allocated in a single block. */ + + int pixelsize; /* Size of a pixel, in bytes (1, 2 or 4) */ + int linesize; /* Size of a line, in bytes (xsize * pixelsize) */ + + /* Virtual methods */ + void (*destroy)(int im); /*keeping this for compatibility */ +}; + +struct Pixel_RGBA { + unsigned char r,g,b,a; +}; + +""" +ffi = FFI() +ffi.cdef(defs) + + +class PyAccess(object): + + def __init__(self, img, readonly = False): + vals = dict(img.im.unsafe_ptrs) + self.readonly = readonly + for att in ['palette', 'image8', 'image32','image', 'block', 'destroy']: + vals[att] = ffi.cast("void *", vals[att]) + if DEBUG: + print (vals) + self.im = ffi.new("struct ImagingMemoryInstance *", vals) + + def __setitem__(self, xy, color): + """ + Modifies the pixel at x,y. The color is given as a single + numerical value for single band images, and a tuple for + multi-band images + + :param xy: The pixel coordinate, given as (x, y). + :param value: The pixel value. + """ + if self.readonly: raise Exception('ValueError') # undone, ImagingError_ValueError + (x,y) = self.check_xy(xy) + return self.set_pixel(x,y,color) + + def __getitem__(self, xy): + """ + Returns the pixel at x,y. The pixel is returned as a single + value for single band images or a tuple for multiple band + images + + :param xy: The pixel coordinate, given as (x, y). + """ + + (x,y) = self.check_xy(xy) + return self.get_pixel(x,y) + + putpixel = __setitem__ + getpixel = __getitem__ + + def check_xy(self, xy): + (x,y) = xy + if not (0 <= x < self.im.xsize and 0 <= y < self.im.ysize): + raise Exception('ValueError- pixel location out of range') #undone + return (x,y) + + +class _PyAccess32_3(PyAccess): + def __init__(self, *args, **kwargs): + PyAccess.__init__(self, *args, **kwargs) + self.pixels = ffi.cast("struct Pixel_RGBA **", self.im.image32) + + def get_pixel(self, x,y): + pixel = self.pixels[y][x] + return (pixel.r, pixel.g, pixel.b) + + def set_pixel(self, x,y, color): + pixel = self.pixels[y][x] + try: + # tuple + pixel.r, pixel.g, pixel.b = color + except: + # int, as a char[4] + pixel.r = color >> 24 + pixel.g = (color >> 16) & 0xFF + pixel.b = (color >> 8) & 0xFF + +class _PyAccess32_4(PyAccess): + def __init__(self, *args, **kwargs): + PyAccess.__init__(self, *args, **kwargs) + self.pixels = ffi.cast("struct Pixel_RGBA **", self.im.image32) + + def get_pixel(self, x,y): + pixel = self.pixels[y][x] + return (pixel.r, pixel.g, pixel.b, pixel.a) + + def set_pixel(self, x,y, color): + pixel = self.pixels[y][x] + try: + # tuple + pixel.r, pixel.g, pixel.b, pixel.a = color + except: + # int, as a char[4] + pixel.r = color >> 24 + pixel.g = (color >> 16) & 0xFF + pixel.b = (color >> 8) & 0xFF + pixel.a = color & 0xFF + +class _PyAccess8(PyAccess): + def __init__(self, *args, **kwargs): + PyAccess.__init__(self, *args, **kwargs) + self.pixels = self.im.image8 + + def get_pixel(self, x,y): + return self.pixels[y][x] + + def set_pixel(self, x,y, color): + try: + # integer + self.pixels[y][x] = color & 0xFF + except: + # tuple + self.pixels[y][x] = color[0] & 0xFF + + +mode_map = {'1': _PyAccess8, + 'L': _PyAccess8, + 'P': _PyAccess8, + 'RGB': _PyAccess32_3, + 'LAB': _PyAccess32_3, + 'YCbCr': _PyAccess32_3, + 'RGBA': _PyAccess32_4, + 'RGBa': _PyAccess32_4, + 'RGBX': _PyAccess32_4, + 'CMYK': _PyAccess32_4, + } + +def new(img, readonly=False): + + access_type = mode_map.get(img.mode, None) + if not access_type: + if DEBUG: print ("PyAccess Not Implemented: %s" % img.mode) + return None + if DEBUG: print ("New PyAccess: %s" % img.mode) + return access_type(img, readonly) + + diff --git a/Tests/test_cffi.py b/Tests/test_cffi.py new file mode 100644 index 000000000..c3883d22f --- /dev/null +++ b/Tests/test_cffi.py @@ -0,0 +1,48 @@ +from tester import * + +from PIL import Image, PyAccess + +import test_image_putpixel as put +import test_image_getpixel as get + + + +try: + import cffi +except: + skip() + +Image.USE_CFFI_ACCESS = True + +def test_put(): + put.test_sanity() + +def test_get(): + get.test_pixel() + get.test_image() + + +def xtest_direct(): + im = Image.open('lena.png') + caccess = im.im.pixel_access(false) + access = PyAccess.new(im) + + print (caccess[(0,0)]) + print (access[(0,0)]) + + print (access.im.depth) + print (access.im.image32[0][0]) + print (im.getpixel((0,0))) + print (access.get_pixel(0,0)) + access.set_pixel(0,0,(1,2,3)) + print (im.getpixel((0,0))) + print (access.get_pixel(0,0)) + + access.set_pixel(0,0,(1,2,3)) + print (im.getpixel((0,0))) + print (access.get_pixel(0,0)) + + p_int = (5 << 24) + (4<<16) + (3 <<8) + access.set_pixel(0,0,p_int) + print (im.getpixel((0,0))) + print (access.get_pixel(0,0)) diff --git a/_imaging.c b/_imaging.c index c62f0257d..13466f00a 100644 --- a/_imaging.c +++ b/_imaging.c @@ -3072,12 +3072,35 @@ _getattr_ptr(ImagingObject* self, void* closure) #endif } +static PyObject* +_getattr_unsafe_ptrs(ImagingObject* self, void* closure) +{ + return Py_BuildValue("(ss)(si)(si)(si)(si)(si)(sn)(sn)(sn)(sn)(sn)(si)(si)(sn)", + "mode", self->image->mode, + "type", self->image->type, + "depth", self->image->depth, + "bands", self->image->bands, + "xsize", self->image->xsize, + "ysize", self->image->ysize, + "palette", self->image->palette, + "image8", self->image->image8, + "image32", self->image->image32, + "image", self->image->image, + "block", self->image->block, + "pixelsize", self->image->pixelsize, + "linesize", self->image->linesize, + "destroy", self->image->destroy + ); +}; + + static struct PyGetSetDef getsetters[] = { { "mode", (getter) _getattr_mode }, { "size", (getter) _getattr_size }, { "bands", (getter) _getattr_bands }, { "id", (getter) _getattr_id }, { "ptr", (getter) _getattr_ptr }, + { "unsafe_ptrs", (getter) _getattr_unsafe_ptrs }, { NULL } }; From bb20f6ca831bfaf41d00f1c864c282f4acd649f4 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 5 Jan 2014 16:45:01 -0800 Subject: [PATCH 05/24] basic benchmark shows ~order of magnitude speedup --- Tests/bench_cffi_access.py | 43 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 Tests/bench_cffi_access.py diff --git a/Tests/bench_cffi_access.py b/Tests/bench_cffi_access.py new file mode 100644 index 000000000..21d491818 --- /dev/null +++ b/Tests/bench_cffi_access.py @@ -0,0 +1,43 @@ +from tester import * + +# not running this test by default. No DOS against travis. + +from PIL import PyAccess + +import time + +def iterate_get(size, access): + (w,h) = size + for x in range(w): + for y in range(h): + access[(x,y)] + +def iterate_set(size, access): + (w,h) = size + for x in range(w): + for y in range(h): + access[(x,y)] = access[(x,y)] + +def timer(func, label, *args): + starttime = time.time() + func(*args) + endtime = time.time() + print ("%s: %.4f s" %(label, endtime-starttime)) + +def test_direct(): + im = lena() + im.load() + caccess = im.im.pixel_access(False) + access = PyAccess.new(im, False) + + assert_equal(caccess[(0,0)], access[(0,0)]) + + print ("Size: %sx%s" % im.size) + timer(iterate_get, 'PyAccess - get', im.size, access) + timer(iterate_set, 'PyAccess - set', im.size, access) + timer(iterate_get, 'C-api - get', im.size, caccess) + timer(iterate_set, 'C-api - set', im.size, caccess) + + + + From de538897115e0e3948caf4c42ac41e1f1876a173 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 5 Jan 2014 20:50:05 -0800 Subject: [PATCH 06/24] removed initial testing --- Tests/test_cffi.py | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/Tests/test_cffi.py b/Tests/test_cffi.py index c3883d22f..f4fc8760f 100644 --- a/Tests/test_cffi.py +++ b/Tests/test_cffi.py @@ -22,27 +22,3 @@ def test_get(): get.test_image() -def xtest_direct(): - im = Image.open('lena.png') - caccess = im.im.pixel_access(false) - access = PyAccess.new(im) - - print (caccess[(0,0)]) - print (access[(0,0)]) - - print (access.im.depth) - print (access.im.image32[0][0]) - print (im.getpixel((0,0))) - print (access.get_pixel(0,0)) - access.set_pixel(0,0,(1,2,3)) - print (im.getpixel((0,0))) - print (access.get_pixel(0,0)) - - access.set_pixel(0,0,(1,2,3)) - print (im.getpixel((0,0))) - print (access.get_pixel(0,0)) - - p_int = (5 << 24) + (4<<16) + (3 <<8) - access.set_pixel(0,0,p_int) - print (im.getpixel((0,0))) - print (access.get_pixel(0,0)) From 663f881b212d290604e4c259256616b26c1be09e Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 5 Jan 2014 21:06:13 -0800 Subject: [PATCH 07/24] all original tests pass --- PIL/Image.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/PIL/Image.py b/PIL/Image.py index 8c5844c64..2e353dd47 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1311,8 +1311,10 @@ class Image: self.load() if self.readonly: self._copy() + self.pyaccess = None + self.load() - if self.pyaccess: # undone , what about the readonly? Fix premerge + if self.pyaccess: return self.pyaccess.putpixel(xy,value) return self.im.putpixel(xy, value) From 273a5014d2b3782819d7eaae144bfe80496f5125 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 5 Jan 2014 21:18:53 -0800 Subject: [PATCH 08/24] more extensive testing, matches original c code --- Tests/test_cffi.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Tests/test_cffi.py b/Tests/test_cffi.py index f4fc8760f..1cb1e694c 100644 --- a/Tests/test_cffi.py +++ b/Tests/test_cffi.py @@ -21,4 +21,44 @@ def test_get(): get.test_pixel() get.test_image() +def _test_get_access(im): + """ Do we get the same thing as the old pixel access """ + """ Using private interfaces, forcing a capi access and a pyaccess for the same image """ + caccess = im.im.pixel_access(False) + access = PyAccess.new(im, False) + + w,h = im.size + for x in range(0,w,10): + for y in range(0,h,10): + assert_equal(caccess[(x,y)], access[(x,y)]) + +def test_get_vs_c(): + _test_get_access(lena('RGB')) + _test_get_access(lena('RGBA')) + _test_get_access(lena('L')) + _test_get_access(lena('1')) + _test_get_access(lena('P')) + + +def _test_set_access(im, color): + """ Are we writing the correct bits into the image? """ + + """ Using private interfaces, forcing a capi access and a pyaccess for the same image """ + caccess = im.im.pixel_access(False) + access = PyAccess.new(im, False) + + w,h = im.size + for x in range(0,w,10): + for y in range(0,h,10): + access[(x,y)] = color + assert_equal(caccess[(x,y)], color) + +def test_set_vs_c(): + _test_set_access(lena('RGB'), (255, 128,0) ) + _test_set_access(lena('RGBA'), (255, 192, 128, 0)) + _test_set_access(lena('L'), 128) + _test_set_access(lena('1'), 255) + _test_set_access(lena('P') , 128) + + From c98f731f7e720a609d8f30d5a50888ec05869721 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 5 Jan 2014 21:36:13 -0800 Subject: [PATCH 09/24] Ensuring c-api access --- Tests/test_image_getpixel.py | 2 ++ Tests/test_image_putpixel.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Tests/test_image_getpixel.py b/Tests/test_image_getpixel.py index 6c5e8b084..ffa6a9c52 100644 --- a/Tests/test_image_getpixel.py +++ b/Tests/test_image_getpixel.py @@ -2,6 +2,8 @@ from tester import * from PIL import Image +Image.USE_CFFI_ACCESS=False + def color(mode): bands = Image.getmodebands(mode) if bands == 1: diff --git a/Tests/test_image_putpixel.py b/Tests/test_image_putpixel.py index 2b60bbd97..5f19237cb 100644 --- a/Tests/test_image_putpixel.py +++ b/Tests/test_image_putpixel.py @@ -2,6 +2,8 @@ from tester import * from PIL import Image +Image.USE_CFFI_ACCESS=False + def test_sanity(): im1 = lena() From 2921b5c5e5145a26320faf8e3bd6058f3f9ec1b6 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 5 Jan 2014 21:36:29 -0800 Subject: [PATCH 10/24] Getting travis into the testing loop --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1eecc9c13..d7c3dcd27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,9 @@ python: - 3.2 - 3.3 -install: "sudo apt-get -qq install libfreetype6-dev liblcms2-dev libwebp-dev python-qt4 ghostscript"" +install: + - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev libwebp-dev python-qt4 ghostscript libffi-dev" + - "pip install cffi" script: - python setup.py clean From d8a88a53902c85418d50751ee5cda84ba1705090 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 5 Jan 2014 22:00:09 -0800 Subject: [PATCH 11/24] Passes tests on python 3.2 Reorganized to remove ImagingMemoryInstance struct, only saving the three pointers that we need (image, image8, image32) and the x/ysize ints. --- PIL/PyAccess.py | 79 +++++++++++++------------------------------------ 1 file changed, 21 insertions(+), 58 deletions(-) diff --git a/PIL/PyAccess.py b/PIL/PyAccess.py index 95abbf238..8ccff11ed 100644 --- a/PIL/PyAccess.py +++ b/PIL/PyAccess.py @@ -26,34 +26,6 @@ from cffi import FFI DEBUG = 0 defs = """ -struct ImagingMemoryInstance { - - /* Format */ - char mode[7]; /* Band names ("1", "L", "P", "RGB", "RGBA", "CMYK", "YCbCr", "BGR;xy") */ - int type; /* Data type (IMAGING_TYPE_*) */ - int depth; /* Depth (ignored in this version) */ - int bands; /* Number of bands (1, 2, 3, or 4) */ - int xsize; /* Image dimension. */ - int ysize; - - /* Colour palette (for "P" images only) */ - void *palette; - - /* Data pointers */ - unsigned char **image8; /* Set for 8-bit images (pixelsize=1). */ - int **image32; /* Set for 32-bit images (pixelsize=4). */ - - /* Internals */ - char **image; /* Actual raster data. */ - char *block; /* Set if data is allocated in a single block. */ - - int pixelsize; /* Size of a pixel, in bytes (1, 2 or 4) */ - int linesize; /* Size of a line, in bytes (xsize * pixelsize) */ - - /* Virtual methods */ - void (*destroy)(int im); /*keeping this for compatibility */ -}; - struct Pixel_RGBA { unsigned char r,g,b,a; }; @@ -68,12 +40,18 @@ class PyAccess(object): def __init__(self, img, readonly = False): vals = dict(img.im.unsafe_ptrs) self.readonly = readonly - for att in ['palette', 'image8', 'image32','image', 'block', 'destroy']: - vals[att] = ffi.cast("void *", vals[att]) + self.image8 = ffi.cast('unsigned char **', vals['image8']) + self.image32 = ffi.cast('int **', vals['image32']) + self.image = ffi.cast('unsigned char **', vals['image']) + self.xsize = vals['xsize'] + self.ysize = vals['ysize'] + if DEBUG: print (vals) - self.im = ffi.new("struct ImagingMemoryInstance *", vals) + self._post_init() + def _post_init(): pass + def __setitem__(self, xy, color): """ Modifies the pixel at x,y. The color is given as a single @@ -104,15 +82,14 @@ class PyAccess(object): def check_xy(self, xy): (x,y) = xy - if not (0 <= x < self.im.xsize and 0 <= y < self.im.ysize): + if not (0 <= x < self.xsize and 0 <= y < self.ysize): raise Exception('ValueError- pixel location out of range') #undone return (x,y) class _PyAccess32_3(PyAccess): - def __init__(self, *args, **kwargs): - PyAccess.__init__(self, *args, **kwargs) - self.pixels = ffi.cast("struct Pixel_RGBA **", self.im.image32) + def _post_init(self, *args, **kwargs): + self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32) def get_pixel(self, x,y): pixel = self.pixels[y][x] @@ -120,19 +97,13 @@ class _PyAccess32_3(PyAccess): def set_pixel(self, x,y, color): pixel = self.pixels[y][x] - try: - # tuple - pixel.r, pixel.g, pixel.b = color - except: - # int, as a char[4] - pixel.r = color >> 24 - pixel.g = (color >> 16) & 0xFF - pixel.b = (color >> 8) & 0xFF + # tuple + pixel.r, pixel.g, pixel.b = color + class _PyAccess32_4(PyAccess): - def __init__(self, *args, **kwargs): - PyAccess.__init__(self, *args, **kwargs) - self.pixels = ffi.cast("struct Pixel_RGBA **", self.im.image32) + def _post_init(self, *args, **kwargs): + self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32) def get_pixel(self, x,y): pixel = self.pixels[y][x] @@ -140,20 +111,12 @@ class _PyAccess32_4(PyAccess): def set_pixel(self, x,y, color): pixel = self.pixels[y][x] - try: - # tuple - pixel.r, pixel.g, pixel.b, pixel.a = color - except: - # int, as a char[4] - pixel.r = color >> 24 - pixel.g = (color >> 16) & 0xFF - pixel.b = (color >> 8) & 0xFF - pixel.a = color & 0xFF + # tuple + pixel.r, pixel.g, pixel.b, pixel.a = color class _PyAccess8(PyAccess): - def __init__(self, *args, **kwargs): - PyAccess.__init__(self, *args, **kwargs) - self.pixels = self.im.image8 + def _post_init(self, *args, **kwargs): + self.pixels = self.image8 def get_pixel(self, x,y): return self.pixels[y][x] From 32f4097d1e132eb02689a553c89a2bc893dda256 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 5 Jan 2014 22:18:42 -0800 Subject: [PATCH 12/24] Fixes putalpha tests Make sure we clear the pyaccess object each time we reset the self.im object --- PIL/Image.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/PIL/Image.py b/PIL/Image.py index 2e353dd47..254b5b9c4 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -504,6 +504,7 @@ class Image: def _copy(self): self.load() self.im = self.im.copy() + self.pyaccess = None self.readonly = 0 def _dump(self, file=None, format=None): @@ -1205,12 +1206,14 @@ class Image: mode = getmodebase(self.mode) + "A" try: self.im.setmode(mode) + self.pyaccess = None except (AttributeError, ValueError): # do things the hard way im = self.im.convert(mode) if im.mode not in ("LA", "RGBA"): raise ValueError # sanity check self.im = im + self.pyaccess = None self.mode = self.im.mode except (KeyError, ValueError): raise ValueError("illegal image mode") @@ -1616,6 +1619,7 @@ class Image: self.size = size self.readonly = 0 + self.pyaccess = None # FIXME: the different tranform methods need further explanation # instead of bloating the method docs, add a separate chapter. From 594276c45e7ec4e78f29d6ff61a958bb079aa42a Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 5 Jan 2014 22:20:07 -0800 Subject: [PATCH 13/24] existing behaviour is to clamp to max, not bitmask --- PIL/PyAccess.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/PIL/PyAccess.py b/PIL/PyAccess.py index 8ccff11ed..5f56e2bca 100644 --- a/PIL/PyAccess.py +++ b/PIL/PyAccess.py @@ -112,6 +112,7 @@ class _PyAccess32_4(PyAccess): def set_pixel(self, x,y, color): pixel = self.pixels[y][x] # tuple + #undone clamp? pixel.r, pixel.g, pixel.b, pixel.a = color class _PyAccess8(PyAccess): @@ -124,10 +125,10 @@ class _PyAccess8(PyAccess): def set_pixel(self, x,y, color): try: # integer - self.pixels[y][x] = color & 0xFF + self.pixels[y][x] = min(color,255) except: # tuple - self.pixels[y][x] = color[0] & 0xFF + self.pixels[y][x] = min(color[0],255) mode_map = {'1': _PyAccess8, From 4cacfe6b118fdcf2b0bfb5e185c40dc9c62fe25f Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 6 Jan 2014 08:55:06 -0800 Subject: [PATCH 14/24] longer benchmark --- Tests/bench_cffi_access.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Tests/bench_cffi_access.py b/Tests/bench_cffi_access.py index 21d491818..140cedd61 100644 --- a/Tests/bench_cffi_access.py +++ b/Tests/bench_cffi_access.py @@ -19,10 +19,16 @@ def iterate_set(size, access): access[(x,y)] = access[(x,y)] def timer(func, label, *args): + iterations = 1000 starttime = time.time() - func(*args) - endtime = time.time() - print ("%s: %.4f s" %(label, endtime-starttime)) + for x in range(iterations): + func(*args) + if time.time()-starttime > 10: + print ("%s: breaking at %s iterations, %.6f per iteration"%(label, x+1, (time.time()-starttime)/(x+1.0))) + break + if x == iterations-1: + endtime = time.time() + print ("%s: %.4f s %.6f per iteration" %(label, endtime-starttime, (endtime-starttime)/(x+1.0))) def test_direct(): im = lena() From 53ba63fcd5fb58899084703ea2297191647eca7b Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 6 Jan 2014 21:19:58 -0800 Subject: [PATCH 15/24] Raising proper ValueErrors --- PIL/PyAccess.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/PIL/PyAccess.py b/PIL/PyAccess.py index 5f56e2bca..1c1294203 100644 --- a/PIL/PyAccess.py +++ b/PIL/PyAccess.py @@ -61,7 +61,7 @@ class PyAccess(object): :param xy: The pixel coordinate, given as (x, y). :param value: The pixel value. """ - if self.readonly: raise Exception('ValueError') # undone, ImagingError_ValueError + if self.readonly: raise ValueError('Attempt to putpixel a read only image') (x,y) = self.check_xy(xy) return self.set_pixel(x,y,color) @@ -83,8 +83,8 @@ class PyAccess(object): def check_xy(self, xy): (x,y) = xy if not (0 <= x < self.xsize and 0 <= y < self.ysize): - raise Exception('ValueError- pixel location out of range') #undone - return (x,y) + raise ValueError('pixel location out of range') + return xy class _PyAccess32_3(PyAccess): From c3812f46b24f0162c1417979e68431213517a1fd Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 6 Jan 2014 21:20:19 -0800 Subject: [PATCH 16/24] clamping channels to 255 --- PIL/PyAccess.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/PIL/PyAccess.py b/PIL/PyAccess.py index 1c1294203..e3fdc9ec7 100644 --- a/PIL/PyAccess.py +++ b/PIL/PyAccess.py @@ -98,8 +98,9 @@ class _PyAccess32_3(PyAccess): def set_pixel(self, x,y, color): pixel = self.pixels[y][x] # tuple - pixel.r, pixel.g, pixel.b = color - + pixel.r = min(color[0],255) + pixel.g = min(color[1],255) + pixel.b = min(color[2],255) class _PyAccess32_4(PyAccess): def _post_init(self, *args, **kwargs): @@ -112,8 +113,11 @@ class _PyAccess32_4(PyAccess): def set_pixel(self, x,y, color): pixel = self.pixels[y][x] # tuple - #undone clamp? - pixel.r, pixel.g, pixel.b, pixel.a = color + pixel.r = min(color[0],255) + pixel.g = min(color[1],255) + pixel.b = min(color[2],255) + pixel.a = min(color[3],255) + class _PyAccess8(PyAccess): def _post_init(self, *args, **kwargs): From 8a69f6caa0ae564812a6c1b23c8fa4457f7ad5c5 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 6 Jan 2014 22:08:14 -0800 Subject: [PATCH 17/24] use cffi by default in pypy, not in c-python --- PIL/Image.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 254b5b9c4..25673c4f0 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -100,18 +100,14 @@ import os, sys import collections import numbers -USE_CFFI_ACCESS = True +# works everywhere, win for pypy, not cpython +USE_CFFI_ACCESS = hasattr(sys, 'pypy_version_info') try: import cffi HAS_CFFI=True except: HAS_CFFI=False -#if HAS_CFFI: -# from PIL import PyAccess - - - def isImageType(t): """ Checks if an object is an image object. From 121b51aebc6da52fb1473a4ca379beea0e51265c Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 6 Jan 2014 22:09:00 -0800 Subject: [PATCH 18/24] trying to bust up the JIT a bit --- Tests/bench_cffi_access.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Tests/bench_cffi_access.py b/Tests/bench_cffi_access.py index 140cedd61..8f8ef937a 100644 --- a/Tests/bench_cffi_access.py +++ b/Tests/bench_cffi_access.py @@ -3,6 +3,7 @@ from tester import * # not running this test by default. No DOS against travis. from PIL import PyAccess +from PIL import Image import time @@ -16,10 +17,10 @@ def iterate_set(size, access): (w,h) = size for x in range(w): for y in range(h): - access[(x,y)] = access[(x,y)] + access[(x,y)] = (x %256,y%256,0) def timer(func, label, *args): - iterations = 1000 + iterations = 5000 starttime = time.time() for x in range(iterations): func(*args) @@ -33,6 +34,7 @@ def timer(func, label, *args): def test_direct(): im = lena() im.load() + #im = Image.new( "RGB", (2000,2000), (1,3,2)) caccess = im.im.pixel_access(False) access = PyAccess.new(im, False) From 5dfadf623bc90dc43cffacdafc291a955228a869 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 6 Jan 2014 22:51:31 -0800 Subject: [PATCH 19/24] LA, PA image modes --- PIL/PyAccess.py | 20 ++++++++++++++++++++ Tests/test_cffi.py | 4 ++++ 2 files changed, 24 insertions(+) diff --git a/PIL/PyAccess.py b/PIL/PyAccess.py index e3fdc9ec7..40b717c4a 100644 --- a/PIL/PyAccess.py +++ b/PIL/PyAccess.py @@ -86,8 +86,24 @@ class PyAccess(object): raise ValueError('pixel location out of range') return xy +class _PyAccess32_2(PyAccess): + """ PA, LA, stored in first and last bytes of a 32 bit word """ + def _post_init(self, *args, **kwargs): + self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32) + + def get_pixel(self, x,y): + pixel = self.pixels[y][x] + return (pixel.r, pixel.a) + def set_pixel(self, x,y, color): + pixel = self.pixels[y][x] + # tuple + pixel.r = min(color[0],255) + pixel.a = min(color[1],255) + class _PyAccess32_3(PyAccess): + """ RGB and friends, stored in the first three bytes of a 32 bit word """ + def _post_init(self, *args, **kwargs): self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32) @@ -103,6 +119,7 @@ class _PyAccess32_3(PyAccess): pixel.b = min(color[2],255) class _PyAccess32_4(PyAccess): + """ RGBA etc, all 4 bytes of a 32 bit word """ def _post_init(self, *args, **kwargs): self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32) @@ -120,6 +137,7 @@ class _PyAccess32_4(PyAccess): class _PyAccess8(PyAccess): + """ 1, L, P, 8 bit images stored as uint8 """ def _post_init(self, *args, **kwargs): self.pixels = self.image8 @@ -138,6 +156,8 @@ class _PyAccess8(PyAccess): mode_map = {'1': _PyAccess8, 'L': _PyAccess8, 'P': _PyAccess8, + 'LA': _PyAccess32_2, + 'PA': _PyAccess32_2, 'RGB': _PyAccess32_3, 'LAB': _PyAccess32_3, 'YCbCr': _PyAccess32_3, diff --git a/Tests/test_cffi.py b/Tests/test_cffi.py index 1cb1e694c..4af0456ca 100644 --- a/Tests/test_cffi.py +++ b/Tests/test_cffi.py @@ -37,8 +37,10 @@ def test_get_vs_c(): _test_get_access(lena('RGB')) _test_get_access(lena('RGBA')) _test_get_access(lena('L')) + _test_get_access(lena('LA')) _test_get_access(lena('1')) _test_get_access(lena('P')) + #_test_get_access(lena('PA')) # PA -- how do I make a PA image??? def _test_set_access(im, color): @@ -58,7 +60,9 @@ def test_set_vs_c(): _test_set_access(lena('RGB'), (255, 128,0) ) _test_set_access(lena('RGBA'), (255, 192, 128, 0)) _test_set_access(lena('L'), 128) + _test_set_access(lena('LA'), (128,128)) _test_set_access(lena('1'), 255) _test_set_access(lena('P') , 128) + ##_test_set_access(i, (128,128)) #PA -- undone how to make From 84e2cf6eda35ea5cad5e6f56cfd77281d03989ae Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 6 Jan 2014 23:15:00 -0800 Subject: [PATCH 20/24] I;16 modes --- PIL/PyAccess.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/PIL/PyAccess.py b/PIL/PyAccess.py index 40b717c4a..f75fb312d 100644 --- a/PIL/PyAccess.py +++ b/PIL/PyAccess.py @@ -22,6 +22,7 @@ from __future__ import print_function from cffi import FFI +import sys DEBUG = 0 @@ -29,7 +30,9 @@ defs = """ struct Pixel_RGBA { unsigned char r,g,b,a; }; - +struct Pixel_I16 { + unsigned char l,r; +}; """ ffi = FFI() ffi.cdef(defs) @@ -152,6 +155,59 @@ class _PyAccess8(PyAccess): # tuple self.pixels[y][x] = min(color[0],255) +class _PyAccessI16_N(PyAccess): + """ I;16 access, native bitendian without conversion """ + def _post_init(self, *args, **kwargs): + self.pixels = ffi.cast('unsigned short **', self.image) + + def get_pixel(self, x,y): + return self.pixels[y][x] + + def set_pixel(self, x,y, color): + try: + # integer + self.pixels[y][x] = min(color, 65535) + except: + # tuple + self.pixels[y][x] = min(color[0], 65535) + +class _PyAccessI16_L(PyAccess): + """ I;16L access, with conversion """ + def _post_init(self, *args, **kwargs): + self.pixels = ffi.cast('struct Pixel_I16 **', self.image) + + def get_pixel(self, x,y): + pixel = self.pixels[y][x] + return pixel.l + pixel.r * 256 + + def set_pixel(self, x,y, color): + pixel = self.pixels[y][x] + try: + color = min(color, 65535) + except: + color = min(color[0], 65535) + + pixel.l = color & 0xFF + pixel.r = color >> 8 + +class _PyAccessI16_B(PyAccess): + """ I;16B access, with conversion """ + def _post_init(self, *args, **kwargs): + self.pixels = ffi.cast('struct Pixel_I16 **', self.image) + + def get_pixel(self, x,y): + pixel = self.pixels[y][x] + return pixel.l *256 + pixel.r + + def set_pixel(self, x,y, color): + pixel = self.pixels[y][x] + try: + color = min(color, 65535) + except: + color = min(color[0], 65535) + + pixel.l = color >> 8 + pixel.r = color & 0xFF mode_map = {'1': _PyAccess8, 'L': _PyAccess8, @@ -167,6 +223,16 @@ mode_map = {'1': _PyAccess8, 'CMYK': _PyAccess32_4, } +if sys.byteorder == 'little': + mode_map['I;16'] = _PyAccessI16_N + mode_map['I;16L'] = _PyAccessI16_N + mode_map['I;16B'] = _PyAccessI16_B +else: + mode_map['I;16'] = _PyAccessI16_L + mode_map['I;16L'] = _PyAccessI16_L + mode_map['I;16B'] = _PyAccessI16_N + + def new(img, readonly=False): access_type = mode_map.get(img.mode, None) From b6ad79d9f902088a3bdb2f5d799d98f87bea135e Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 6 Jan 2014 23:25:41 -0800 Subject: [PATCH 21/24] detailed tests for I;16 modes, merged from i16-pixelaccess branch --- Tests/test_cffi.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Tests/test_cffi.py b/Tests/test_cffi.py index 4af0456ca..87214475b 100644 --- a/Tests/test_cffi.py +++ b/Tests/test_cffi.py @@ -18,8 +18,8 @@ def test_put(): put.test_sanity() def test_get(): - get.test_pixel() - get.test_image() + get.test_basic() + get.test_signedness() def _test_get_access(im): """ Do we get the same thing as the old pixel access """ @@ -31,7 +31,7 @@ def _test_get_access(im): w,h = im.size for x in range(0,w,10): for y in range(0,h,10): - assert_equal(caccess[(x,y)], access[(x,y)]) + assert_equal(access[(x,y)], caccess[(x,y)]) def test_get_vs_c(): _test_get_access(lena('RGB')) @@ -42,6 +42,12 @@ def test_get_vs_c(): _test_get_access(lena('P')) #_test_get_access(lena('PA')) # PA -- how do I make a PA image??? + im = Image.new('I;16', (10,10), 40000) + _test_get_access(im) + im = Image.new('I;16L', (10,10), 40000) + _test_get_access(im) + im = Image.new('I;16B', (10,10), 40000) + _test_get_access(im) def _test_set_access(im, color): """ Are we writing the correct bits into the image? """ @@ -54,7 +60,7 @@ def _test_set_access(im, color): for x in range(0,w,10): for y in range(0,h,10): access[(x,y)] = color - assert_equal(caccess[(x,y)], color) + assert_equal(color, caccess[(x,y)]) def test_set_vs_c(): _test_set_access(lena('RGB'), (255, 128,0) ) @@ -65,4 +71,10 @@ def test_set_vs_c(): _test_set_access(lena('P') , 128) ##_test_set_access(i, (128,128)) #PA -- undone how to make + im = Image.new('I;16', (10,10), 40000) + _test_set_access(im, 45000) + im = Image.new('I;16L', (10,10), 40000) + _test_set_access(im, 45000) + im = Image.new('I;16B', (10,10), 40000) + _test_set_access(im, 45000) From bfdc599c289c046e11f708ce286b68560ecc17c0 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 8 Jan 2014 20:23:20 -0800 Subject: [PATCH 22/24] F mode support --- PIL/PyAccess.py | 19 +++++++++++++++++++ Tests/test_cffi.py | 8 ++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/PIL/PyAccess.py b/PIL/PyAccess.py index f75fb312d..cd862d4f0 100644 --- a/PIL/PyAccess.py +++ b/PIL/PyAccess.py @@ -209,6 +209,24 @@ class _PyAccessI16_B(PyAccess): pixel.l = color >> 8 pixel.r = color & 0xFF + +class _PyAccessF(PyAccess): + """ 32 bit float access """ + def _post_init(self, *args, **kwargs): + self.pixels = ffi.cast('float **', self.image32) + + def get_pixel(self, x,y): + return self.pixels[y][x] + + def set_pixel(self, x,y, color): + try: + # not a tuple + self.pixels[y][x] = color + except: + # tuple + self.pixels[y][x] = color[0] + + mode_map = {'1': _PyAccess8, 'L': _PyAccess8, 'P': _PyAccess8, @@ -221,6 +239,7 @@ mode_map = {'1': _PyAccess8, 'RGBa': _PyAccess32_4, 'RGBX': _PyAccess32_4, 'CMYK': _PyAccess32_4, + 'F': _PyAccessF, } if sys.byteorder == 'little': diff --git a/Tests/test_cffi.py b/Tests/test_cffi.py index 87214475b..1cf991701 100644 --- a/Tests/test_cffi.py +++ b/Tests/test_cffi.py @@ -41,13 +41,16 @@ def test_get_vs_c(): _test_get_access(lena('1')) _test_get_access(lena('P')) #_test_get_access(lena('PA')) # PA -- how do I make a PA image??? - + _test_get_access(lena('F')) + im = Image.new('I;16', (10,10), 40000) _test_get_access(im) im = Image.new('I;16L', (10,10), 40000) _test_get_access(im) im = Image.new('I;16B', (10,10), 40000) _test_get_access(im) + + def _test_set_access(im, color): """ Are we writing the correct bits into the image? """ @@ -70,7 +73,8 @@ def test_set_vs_c(): _test_set_access(lena('1'), 255) _test_set_access(lena('P') , 128) ##_test_set_access(i, (128,128)) #PA -- undone how to make - + _test_set_access(lena('F'), 1024.0) + im = Image.new('I;16', (10,10), 40000) _test_set_access(im, 45000) im = Image.new('I;16L', (10,10), 40000) From 79a7fdc1002cde5383996b147fd1fa2993f962ca Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 8 Jan 2014 21:18:38 -0800 Subject: [PATCH 23/24] I32 modes, not that I can actually use the I;32L/B ones --- PIL/PyAccess.py | 36 +++++++++++++++++++++++++++++++++++- Tests/test_cffi.py | 16 ++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/PIL/PyAccess.py b/PIL/PyAccess.py index cd862d4f0..792a62f76 100644 --- a/PIL/PyAccess.py +++ b/PIL/PyAccess.py @@ -209,6 +209,33 @@ class _PyAccessI16_B(PyAccess): pixel.l = color >> 8 pixel.r = color & 0xFF +class _PyAccessI32_N(PyAccess): + """ Signed Int32 access, native endian """ + def _post_init(self, *args, **kwargs): + self.pixels = self.image32 + + def get_pixel(self, x,y): + return self.pixels[y][x] + + def set_pixel(self, x,y, color): + self.pixels[y][x] = color + +class _PyAccessI32_Swap(PyAccess): + """ I;32L/B access, with byteswapping conversion """ + def _post_init(self, *args, **kwargs): + self.pixels = self.image32 + + def reverse(self, i): + orig = ffi.new('int *', i) + chars = ffi.cast('unsigned char *', orig) + chars[0],chars[1],chars[2],chars[3] = chars[3], chars[2],chars[1],chars[0] + return ffi.cast('int *', chars)[0] + + def get_pixel(self, x,y): + return self.reverse(self.pixels[y][x]) + + def set_pixel(self, x,y, color): + self.pixels[y][x] = self.reverse(color) class _PyAccessF(PyAccess): """ 32 bit float access """ @@ -246,11 +273,18 @@ if sys.byteorder == 'little': mode_map['I;16'] = _PyAccessI16_N mode_map['I;16L'] = _PyAccessI16_N mode_map['I;16B'] = _PyAccessI16_B + + mode_map['I'] = _PyAccessI32_N + mode_map['I;32L'] = _PyAccessI32_N + mode_map['I;32B'] = _PyAccessI32_Swap else: mode_map['I;16'] = _PyAccessI16_L mode_map['I;16L'] = _PyAccessI16_L mode_map['I;16B'] = _PyAccessI16_N - + + mode_map['I'] = _PyAccessI32_Swap + mode_map['I;32L'] = _PyAccessI32_Swap + mode_map['I;32B'] = _PyAccessI32_N def new(img, readonly=False): diff --git a/Tests/test_cffi.py b/Tests/test_cffi.py index 1cf991701..4065a9e53 100644 --- a/Tests/test_cffi.py +++ b/Tests/test_cffi.py @@ -50,6 +50,15 @@ def test_get_vs_c(): im = Image.new('I;16B', (10,10), 40000) _test_get_access(im) + im = Image.new('I', (10,10), 40000) + _test_get_access(im) + # These don't actually appear to be modes that I can actually make, + # as unpack sets them directly into the I mode. + #im = Image.new('I;32L', (10,10), -2**10) + #_test_get_access(im) + #im = Image.new('I;32B', (10,10), 2**10) + #_test_get_access(im) + def _test_set_access(im, color): @@ -82,3 +91,10 @@ def test_set_vs_c(): im = Image.new('I;16B', (10,10), 40000) _test_set_access(im, 45000) + + im = Image.new('I', (10,10), 40000) + _test_set_access(im, 45000) +# im = Image.new('I;32L', (10,10), -(2**10)) +# _test_set_access(im, -(2**13)+1) + #im = Image.new('I;32B', (10,10), 2**10) + #_test_set_access(im, 2**13-1) From 9e069bfa542b505d019ddb6c38a32a2fc1435eb2 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Thu, 9 Jan 2014 05:44:18 +0000 Subject: [PATCH 24/24] I mode is native endian --- PIL/PyAccess.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/PIL/PyAccess.py b/PIL/PyAccess.py index 792a62f76..f76beb820 100644 --- a/PIL/PyAccess.py +++ b/PIL/PyAccess.py @@ -267,6 +267,7 @@ mode_map = {'1': _PyAccess8, 'RGBX': _PyAccess32_4, 'CMYK': _PyAccess32_4, 'F': _PyAccessF, + 'I': _PyAccessI32_N, } if sys.byteorder == 'little': @@ -274,15 +275,13 @@ if sys.byteorder == 'little': mode_map['I;16L'] = _PyAccessI16_N mode_map['I;16B'] = _PyAccessI16_B - mode_map['I'] = _PyAccessI32_N mode_map['I;32L'] = _PyAccessI32_N mode_map['I;32B'] = _PyAccessI32_Swap else: mode_map['I;16'] = _PyAccessI16_L mode_map['I;16L'] = _PyAccessI16_L mode_map['I;16B'] = _PyAccessI16_N - - mode_map['I'] = _PyAccessI32_Swap + mode_map['I;32L'] = _PyAccessI32_Swap mode_map['I;32B'] = _PyAccessI32_N