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 } };