Repository: python-pillow/Pillow

Issue:
Use CFFI for the pixel access object
Hoisted from #67, post-close, with additional brain dumping. 

@fijal said:

> If you need to call, call it with cffi (C functions directly) instead of C API. That would be much better and should be reasonably fast on pypy. Pure python is obviously better (you avoid function calls), but cffi is definitely next good, if the image does not export _any_ way to get to the raw data. to be perfectly clear - I doubt there is _no_ way to access raw data one way or another. The _ideal_ way would be to get the raw data pointer and access it via cffi.

The crux that I see is that (IIRC) the data isn't necessarily in a continuous block. If it were, the raw data pointer would be easy. 

The backbrain is thinking that the solution might be related to checking the strides from #224. If the data's not continuous, we can fall back to slow access. 

If a cffi approach would work, we'd need to require cffi and libffi-dev, but it's really only necessary on pypy, and anyone doing this on pypy would likely be ok an additional dependency. 

Docs are here:  http://cffi.readthedocs.org/en/latest/index.html
It looks like this is what we'd need:

> ffi.buffer(cdata, [size]): return a buffer object that references the raw C data pointed to by the given ‘cdata’, of ‘size’ bytes. The ‘cdata’ must be a pointer or an array.

So, Assuming that I can get a pointer from something like PyLong_FromVoidPtr, this should give higher performance access to the pixel_access_object.

Reading and converting 16-bit pixels
```
>>> import PIL
>>> PIL.PILLOW_VERSION
'2.2.2'
>>> from PIL import Image
>>> im = Image.open('pil_pillow_16_bit_bug.tif')
>>> im
<PIL.TiffImagePlugin.TiffImageFile image mode=I;16B size=512x512 at 0x103125E60>
```

Note no 'S' in mode.
Then,

```
>>> im.getpixel((0,0))
-32768
```

Two problems: Other than using the extended SampleFormat tag (339, 0x0153), there is no way to store a signed value in a tiff file.  The value stored in this image at 0,0 is 32768, and this file has no SampleFormat tag, so only unsigned integers are possible. Instead the value reported is the two's complement of the actual value.
Second problem unrelated to tiff peculiarities, you have an unsigned image mode with a signed pixel value.
Moving on,

```
>>> im2 = im.convert('I')
>>> im2.getpixel((0,0))
32768
```

This is the correct value.
Even more surprising is when we convert this 32-bit, presumably unsigned image back to unsigned 16 bit:

```
>>> im3 = im2.convert('I;16')
>>> im3.getpixel((0,0))
32767
```

???. Even better, a pixel at 32,16 has a value of 32769:

```
>>> im2.getpixel((32,16))
32769
```

Correct in the 32-bit unsigned image (is it really unsigned, BTW?  didn't check), but in the 16-bit image converted from it, this pixel is still 32767!

```
>>> im3.getpixel((32,16))
32767
```

In fact, all of the pixels in the converted image are clipped to 32767.  In the image as-read, this value is -32767 (two's complement of 32769):

```
>>>> >>> im.getpixel((32,16))
-32767
```

Does the problem originate from the fact that we went over the signed 16-bit max even though we were supposed to be dealing with unsigned values?  Is the convert problem related to the tiff reader problem?

Sample file for the above: [pil_pillow_16_bit_bug.tif](https://drive.google.com/file/d/0B-x3kTkP-RShSjY1ZUVCZGNBMWc/edit?usp=sharing)



Discussion:
Cool :+1:

Is this a 2.2.0 or some other milestone target?

@wiredfool Will this happen for 2.3.0?

Maybe. I think I know what has to happen.

That image isn't coming up as a signed tiff for me. I'm seeing a SampleFormat of 1, not 2.   If you look at the tiff spec, http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf page 80, the SampleFormat tag can take one of 4 values: 

```
1 = unsigned integer data
2 = two’s complement signed integer data
3 = IEEE floating point data [IEEE]
4 = undefined data format
```

We do read the sample format tag and act on it -- the values are in PIL/TiffImagePlugin.py around line 140. If this had a sample format of 2, we'd be getting this line:  (MM, 1, 2, 1, (16,), ()): ("I;16BS", "I;16BS"), where currently we're getting:  (MM, 1, 1, 1, (16,), ()): ("I;16B", "I;16B"),

```
*** Summary ***
- compression: raw
- photometric_interpretation: 1
- planar_configuration: 1
- fill_order: 1
- size: (64, 64)
format key: ('MM', 1, 1, 1, (16,), ())
- raw mode: I;16B
- pil mode: I;16B
tiles:  [('raw', (0, 0, 64, 64), 3089, ('I;16B', 0, 1))]
```

Imagemagick agrees: 

```
(vpy27)erics:~/16bit$ identify -verbose pil_pillow_16_bit_bug.tif 
Image: pil_pillow_16_bit_bug.tif
  Format: TIFF (Tagged Image File Format)
  Class: DirectClass
  Geometry: 64x64+0+0
  Resolution: 72x72
  Print size: 0.888889x0.888889
  Units: PixelsPerInch
  Type: Grayscale
  Base type: Grayscale
  Endianess: MSB
  Colorspace: RGB
  Depth: 16-bit
  Channel depth:
    gray: 16-bit
  Channel statistics:
    Gray:
      min: 32768 (0.500008)
      max: 32771 (0.500053)
      mean: 32769.5 (0.500031)
      standard deviation: 0.949615 (1.44902e-05)
      kurtosis: 389103
      skewness: -3.10189
  Histogram:
       846: (32768,32768,32768) #800080008000 rgb(50.0008%,50.0008%,50.0008%)
       807: (32769,32769,32769) #800180018001 rgb(50.0023%,50.0023%,50.0023%)
      1954: (32770,32770,32770) #800280028002 rgb(50.0038%,50.0038%,50.0038%)
       489: (32771,32771,32771) #800380038003 rgb(50.0053%,50.0053%,50.0053%)
  Rendering intent: Undefined
  Interlace: None
  Background color: white
  Border color: rgb(223,223,223)
  Matte color: grey74
  Transparent color: black
  Compose: Over
  Page geometry: 64x64+0+0
  Dispose: Undefined
  Iterations: 0
  Compression: None
  Orientation: TopLeft
  Properties:
    comment: ImageJ=1.48k
cf=0
c0=-32768.0
c1=1.0
vunit=Gray Value
min=32768.0
max=32771.0

    date:create: 2013-12-19T08:36:22-08:00
    date:modify: 2013-12-19T08:36:22-08:00
    signature: b4b86b8ead068a453a9d05af84c2c460e918bade85b48b6b8bf930141ce6815b
    tiff:endian: msb
    tiff:photometric: min-is-black
    tiff:rows-per-strip: 64
  Artifacts:
    verbose: true
  Tainted: False
  Filesize: 11.3KBB
  Number pixels: 4.1KB
  Pixels per second: 410KB
  User time: 0.000u
  Elapsed time: 0:01.010
```

As for the rest of it, try checking after including PR #416 and #417.  There were some fixes for I;16 images and endianness issues there.  

Of course its not coming up as a signed tiff.  Its not a signed tiff - your ImageMagick dump, my tiffdump, tiffinfo, and ImageJ are all in complete agreement about that.  Its only a signed tiff when read by Image.open(), which is the whole problem and basis for this bug report.  The mode is reported correctly as unsigned, but the pixel values are **negative**!

As far as conversion goes, I would expect the same numerical values as I convert from unsigned 16-bit to unsigned 32-bit, and back to unsigned 16-bit (I;16B -> I -> I;16).  This should be true regardless of the numerical range because the first conversion is an up-cast, and the second conversion is a down-cast to the original numeric type.  The output above clearly shows that this is not the case.

So PR #416 and #417 are not part of the 2.2.2 release which looked to me happened after their merge into master?  From my looking at them (which I did before submitting this report, but maybe not in sufficient detail), they deal with 12-bit tiffs stored as 16-bit, where you wouldn't encounter numerical values high enough to run into signedness bugs.  If these were not merged into the 2.2.2 release, and as they are your PRs and you already have the test file in hand, can you quickly check what pixel values get reported for this file using your codebase?

Lastly, tiffdump does not report a SampleFormat tag for this file, so a SampleFormat tag value of 1 (unsigned) is only implied by its absence.  Or, did you read the SampleFormat tag from the file?  I always considered tiffdump the last word on what tags are present in a tiff file, but at this point I'm ready to consider any possibility.

For reference, I just tried with current master (presumably PR #416 and #417 are in there?), and the output is exactly as originally reported: negative pixel values with an unsigned image mode, clipping at the signed-16-bit-max (32767) when converting from I;16B->I->I;16.

Finally, in case your branch isn't merged with master at this repo, I installed pillow from your 12-bit-tiff branch like so:

```
pip uninstall pillow
pip install https://github.com/wiredfool/Pillow/archive/12-bit-tiff.zip
```

And the output is still exactly the same as above.  Are we there yet?

Sorry, I misread your report as the image not showing up as signed. I'll take a closer look. 


Resolving patch:
diff --git a/PIL/Image.py b/PIL/Image.py
--- a/PIL/Image.py
+++ b/PIL/Image.py
@@ -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):
     """
@@ -468,6 +475,7 @@ def __init__(self):
         self.info = {}
         self.category = NORMAL
         self.readonly = 0
+        self.pyaccess = None
 
     def _new(self, im):
         new = Image()
@@ -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):
@@ -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):
@@ -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):
@@ -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")
@@ -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):
@@ -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.
diff --git a/PIL/PyAccess.py b/PIL/PyAccess.py
new file mode 100644
--- /dev/null
+++ b/PIL/PyAccess.py
@@ -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)
+    
+


Test patch:
diff --git a/Tests/bench_cffi_access.py b/Tests/bench_cffi_access.py
new file mode 100644
--- /dev/null
+++ b/Tests/bench_cffi_access.py
@@ -0,0 +1,51 @@
+from tester import *
+
+# not running this test by default. No DOS against travis.
+
+from PIL import PyAccess
+from PIL import Image
+
+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)] = (x %256,y%256,0)
+
+def timer(func, label, *args):
+    iterations = 5000
+    starttime = time.time()
+    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()
+    im.load()
+    #im = Image.new( "RGB", (2000,2000), (1,3,2))
+    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)
+    
+    
+
+    
diff --git a/Tests/test_cffi.py b/Tests/test_cffi.py
new file mode 100644
--- /dev/null
+++ b/Tests/test_cffi.py
@@ -0,0 +1,100 @@
+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_basic()
+    get.test_signedness()
+
+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(access[(x,y)], caccess[(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('LA'))
+    _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)
+    
+    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):
+    """ 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(color, caccess[(x,y)])
+
+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
+    _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)
+    _test_set_access(im, 45000)
+    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)
diff --git a/Tests/test_image_getpixel.py b/Tests/test_image_getpixel.py
--- a/Tests/test_image_getpixel.py
+++ b/Tests/test_image_getpixel.py
@@ -2,6 +2,8 @@
 
 from PIL import Image
 
+Image.USE_CFFI_ACCESS=False
+
 def color(mode):
     bands = Image.getmodebands(mode)
     if bands == 1:
@@ -9,49 +11,39 @@ def color(mode):
     else:
         return tuple(range(1, bands+1))
 
-def test_pixel():
 
-    def pixel(mode):
-        c = color(mode)
-        im = Image.new(mode, (1, 1), None)
-        im.putpixel((0, 0), c)
-        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_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 check(mode, c=None):
+    if not c:
+        c = color(mode)
+        
+    #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))
+
+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_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)
+
+                
 
 
diff --git a/Tests/test_image_putpixel.py b/Tests/test_image_putpixel.py
--- a/Tests/test_image_putpixel.py
+++ b/Tests/test_image_putpixel.py
@@ -2,6 +2,8 @@
 
 from PIL import Image
 
+Image.USE_CFFI_ACCESS=False
+
 def test_sanity():
 
     im1 = lena()