Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cffi pixelaccess #476

Merged
merged 26 commits into from
Jan 31, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
598d97d
Reorg+add test for #452
wiredfool Dec 20, 2013
77c36d6
Using uint* for pixel access in mode I;16 and I;32, fixes #452
wiredfool Dec 20, 2013
1dd80b2
reverted int32 changes
wiredfool Dec 31, 2013
5efe737
Cffi pixel access object, #248
wiredfool Jan 5, 2014
bb20f6c
basic benchmark shows ~order of magnitude speedup
wiredfool Jan 6, 2014
de53889
removed initial testing
wiredfool Jan 6, 2014
663f881
all original tests pass
wiredfool Jan 6, 2014
273a501
more extensive testing, matches original c code
wiredfool Jan 6, 2014
c98f731
Ensuring c-api access
wiredfool Jan 6, 2014
2921b5c
Getting travis into the testing loop
wiredfool Jan 6, 2014
d8a88a5
Passes tests on python 3.2
wiredfool Jan 6, 2014
32f4097
Fixes putalpha tests
wiredfool Jan 6, 2014
594276c
existing behaviour is to clamp to max, not bitmask
wiredfool Jan 6, 2014
4cacfe6
longer benchmark
wiredfool Jan 6, 2014
53ba63f
Raising proper ValueErrors
wiredfool Jan 7, 2014
c3812f4
clamping channels to 255
wiredfool Jan 7, 2014
8a69f6c
use cffi by default in pypy, not in c-python
wiredfool Jan 7, 2014
121b51a
trying to bust up the JIT a bit
wiredfool Jan 7, 2014
5dfadf6
LA, PA image modes
wiredfool Jan 7, 2014
84e2cf6
I;16 modes
wiredfool Jan 7, 2014
e87e033
Merge branch 'i16-pixelaccess' into cffi-pixelaccess
wiredfool Jan 7, 2014
b6ad79d
detailed tests for I;16 modes, merged from i16-pixelaccess branch
wiredfool Jan 7, 2014
bfdc599
F mode support
wiredfool Jan 9, 2014
79a7fdc
I32 modes, not that I can actually use the I;32L/B ones
wiredfool Jan 9, 2014
9e069bf
I mode is native endian
wiredfool Jan 9, 2014
288a563
merge from master
wiredfool Jan 31, 2014
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ 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
Expand Down
27 changes: 26 additions & 1 deletion PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ def __getattr__(self, id):
import collections
import numbers

# 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

def isImageType(t):
"""
Expand Down Expand Up @@ -468,6 +475,7 @@ def __init__(self):
self.info = {}
self.category = NORMAL
self.readonly = 0
self.pyaccess = None

def _new(self, im):
new = Image()
Expand All @@ -492,6 +500,7 @@ def _new(self, im):
def _copy(self):
self.load()
self.im = self.im.copy()
self.pyaccess = None
self.readonly = 0

def _dump(self, file=None, format=None):
Expand Down Expand Up @@ -645,6 +654,13 @@ def load(self):
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):
Expand Down Expand Up @@ -976,6 +992,8 @@ def getpixel(self, xy):
"""

self.load()
if self.pyaccess:
return self.pyaccess.getpixel(xy)
return self.im.getpixel(xy)

def getprojection(self):
Expand Down Expand Up @@ -1186,12 +1204,14 @@ def putalpha(self, alpha):
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")
Expand Down Expand Up @@ -1292,7 +1312,11 @@ def putpixel(self, xy, value):
self.load()
if self.readonly:
self._copy()

self.pyaccess = None
self.load()

if self.pyaccess:
return self.pyaccess.putpixel(xy,value)
return self.im.putpixel(xy, value)

def resize(self, size, resample=NEAREST):
Expand Down Expand Up @@ -1593,6 +1617,7 @@ def thumbnail(self, size, resample=NEAREST):
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.
Expand Down
297 changes: 297 additions & 0 deletions PIL/PyAccess.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
#
# 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
import sys

DEBUG = 0

defs = """
struct Pixel_RGBA {
unsigned char r,g,b,a;
};
struct Pixel_I16 {
unsigned char l,r;
};
"""
ffi = FFI()
ffi.cdef(defs)


class PyAccess(object):

def __init__(self, img, readonly = False):
vals = dict(img.im.unsafe_ptrs)
self.readonly = readonly
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._post_init()

def _post_init(): pass

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 ValueError('Attempt to putpixel a read only image')
(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.xsize and 0 <= y < self.ysize):
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)

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]
# tuple
pixel.r = min(color[0],255)
pixel.g = min(color[1],255)
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)

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]
# tuple
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):
""" 1, L, P, 8 bit images stored as uint8 """
def _post_init(self, *args, **kwargs):
self.pixels = self.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] = min(color,255)
except:
# 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

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 """
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,
'LA': _PyAccess32_2,
'PA': _PyAccess32_2,
'RGB': _PyAccess32_3,
'LAB': _PyAccess32_3,
'YCbCr': _PyAccess32_3,
'RGBA': _PyAccess32_4,
'RGBa': _PyAccess32_4,
'RGBX': _PyAccess32_4,
'CMYK': _PyAccess32_4,
'F': _PyAccessF,
'I': _PyAccessI32_N,
}

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;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;32L'] = _PyAccessI32_Swap
mode_map['I;32B'] = _PyAccessI32_N

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)


Loading