Lenard Lindstrom avatar Lenard Lindstrom committed da38206

Get pygame.PixelArray to handle PyBUF_* buffer import flags properly

Comments (0)

Files changed (2)

                            /* Parent pixel array: NULL if no parent */
 } PyPixelArray;
 
+static int array_is_contiguous(PyPixelArray *ap, char fortran);
+
 static PyPixelArray *_pxarray_new_internal(
     PyTypeObject *type, PyObject *surface, PyPixelArray *parent, Uint8 *pixels,
     Py_ssize_t dim0, Py_ssize_t dim1, Py_ssize_t stride0, Py_ssize_t stride1);
 #define SURFACE_EQUALS(x,y) \
     (((PyPixelArray *)x)->surface == ((PyPixelArray *)y)->surface)
 
+static int
+array_is_contiguous(PyPixelArray *ap, char fortran)
+{
+    int itemsize = PySurface_AsSurface(ap->surface)->format->BytesPerPixel;
+
+    if (ap->strides[0] == itemsize) {
+        if (ap->shape[1] == 0) {
+            return 1;
+        }
+        if ((fortran == 'F' || fortran == 'A') &&
+            (ap->strides[1] == ap->shape[0] * itemsize)) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
 #include "pixelarray_methods.c"
     
 /**
 {
     Py_ssize_t itemsize = 
         PySurface_AsSurface(self->surface)->format->BytesPerPixel;
+    int ndim = self->shape[1] ? 2 : 1;
+    Py_ssize_t *shape = 0;
+    Py_ssize_t *strides = 0;
+    Py_ssize_t len;
 
+    len = self->shape[0] * (ndim == 2 ? self->shape[1] : 1) * itemsize;
     view_p->obj = 0;
-    if ((flags & PyBUF_RECORDS_RO) != PyBUF_RECORDS_RO) {
+    if (PyBUF_HAS_FLAG(flags, PyBUF_C_CONTIGUOUS) &&
+        !array_is_contiguous(self, 'C')) {
         PyErr_SetString(PyExc_BufferError,
-                        "Only PyBUF_RECORDS(_RO) requests supported");
+                        "this pixel array is not C contiguous");
         return -1;
     }
-    switch (itemsize) {
+    if (PyBUF_HAS_FLAG(flags, PyBUF_F_CONTIGUOUS) &&
+        !array_is_contiguous(self, 'F')) {
+        PyErr_SetString(PyExc_BufferError,
+                        "this pixel array is not F contiguous");
+        return -1;
+    }
+    if (PyBUF_HAS_FLAG(flags, PyBUF_ANY_CONTIGUOUS) &&
+        !array_is_contiguous(self, 'A')) {
+        PyErr_SetString(PyExc_BufferError,
+                        "this pixel array is not contiguous");
+        return -1;
+    }
+    if (PyBUF_HAS_FLAG(flags, PyBUF_ND)) {
+        shape = self->shape;
+        if (PyBUF_HAS_FLAG(flags, PyBUF_STRIDES)) {
+            strides = self->strides;
+        }
+        else if (!array_is_contiguous(self, 'C')) {
+            PyErr_SetString(PyExc_BufferError,
+                            "this pixel array is not contiguous: need strides");
+            return -1;
+        }
+    }
+    else if (array_is_contiguous(self, 'F')) {
+        ndim = 0;
+    }
+    else {
+        PyErr_SetString(PyExc_BufferError,
+                        "this pixel array is not C contiguous: need strides");
+        return -1;
+    }
+    if (PyBUF_HAS_FLAG(flags, PyBUF_FORMAT)) {
+        switch (itemsize) {
 
-    case 1:
-        view_p->format = FormatUint8;
-        break;
-    case 2:
-        view_p->format = FormatUint16;
-        break;
-    case 3:
-        view_p->format = FormatUint24;
-        break;
-    case 4:
-        view_p->format = FormatUint32;
-        break;
-    default:
-        PyErr_Format(PyExc_SystemError,
-                     "Internal Pygame error at line %d in %s: "
-                     "unknown item size %d; please report",
-                     (int)__LINE__, __FILE__, (int)itemsize);
-        return -1;
+        case 1:
+            view_p->format = FormatUint8;
+            break;
+        case 2:
+            view_p->format = FormatUint16;
+            break;
+        case 3:
+            view_p->format = FormatUint24;
+            break;
+        case 4:
+            view_p->format = FormatUint32;
+            break;
+        default:
+            PyErr_Format(PyExc_SystemError,
+                         "Internal Pygame error at line %d in %s: "
+                         "unknown item size %d; please report",
+                         (int)__LINE__, __FILE__, (int)itemsize);
+            return -1;
+        }
+    }
+    else {
+        view_p->format = 0;
     }
     Py_INCREF(self);
     view_p->obj = (PyObject *)self;
     view_p->buf = self->pixels;
-    view_p->len = self->shape[0] * self->shape[1] * itemsize;
+    view_p->len = len;
     view_p->readonly = 0;
     view_p->itemsize = itemsize;
-    view_p->ndim = self->shape[1] ? 2 : 1;
-    view_p->shape = self->shape;
-    view_p->strides = self->strides;
+    view_p->ndim = ndim;
+    view_p->shape = shape;
+    view_p->strides = strides;
     view_p->suboffsets = 0;
     view_p->internal = 0;
     return 0;

test/pixelarray_test.py

 
 class PixelArrayNewBufferTest (unittest.TestCase, TestMixin):
     if pygame.HAVE_NEWBUF:
-        def test_newbuf (self):
-            self.NEWBUF_test_newbuf()
+        def test_newbuf_2D (self):
+            self.NEWBUF_test_newbuf_2D ()
+        def test_newbuf_1D (self):
+            self.NEWBUF_test_newbuf_1D ()
+        if is_pygame_pkg:
+            from pygame.tests.test_utils import buftools
+        else:
+            from test.test_utils import buftools
 
-    def NEWBUF_test_newbuf (self):
-        class PixelArrayNewbuf (pygame.PixelArray):
-            __array_struct__ = property (lambda self: None)
-            __array_interface__ = property (lambda self: None)
+    bitsize_to_format = {8: 'B', 16: '=H', 24: '3x', 32: '=I'}
 
-        typechar_of_bitsize = {8: 'u', 16: 'u', 24: 'V', 32: 'u'}
-        
-        s = pygame.Surface ((10, 2), 0, 32)
-        ar = PixelArrayNewbuf (s)
-        self.assertTrue (ar.__array_struct__ is None)
-        self.assertTrue (ar.__array_interface__ is None)
+    def NEWBUF_test_newbuf_2D (self):
+        buftools = self.buftools
+        BufferImporter = buftools.BufferImporter
 
         for bit_size in [8, 16, 24, 32]:
             s = pygame.Surface ((10, 2), 0, bit_size)
-            ar = PixelArrayNewbuf (s)
-            bp = pygame.bufferproxy.BufferProxy (ar)
-            ai = arrinter.ArrayInterface (bp)
-            itemsize = s.get_bytesize()
-            self.assertEqual (ai.nd, 2)
-            self.assertEqual (ai.itemsize, itemsize)
-            self.assertEqual (ai.typekind, typechar_of_bitsize[bit_size])
-            self.assertEqual ((ai.shape[0], ai.shape[1]), s.get_size())
-            self.assertEqual ((ai.strides[0], ai.strides[1]),
-                              (itemsize, s.get_pitch()))
-            self.assertEqual (ai.data, s._pixels_address)
-            self.assertTrue (ai.flags & arrinter.PAI_WRITEABLE)
+            ar = pygame.PixelArray (s)
+            format = self.bitsize_to_format[bit_size]
+            itemsize = ar.itemsize
+            shape = ar.shape
+            w, h = shape
+            strides = ar.strides
+            length = w * h * itemsize
+            imp = BufferImporter (ar, buftools.PyBUF_FULL)
+            self.assertTrue (imp.obj, ar)
+            self.assertEqual (imp.len, length)
+            self.assertEqual (imp.ndim, 2)
+            self.assertEqual (imp.itemsize, itemsize)
+            self.assertEqual (imp.format, format)
+            self.assertFalse (imp.readonly)
+            self.assertEqual (imp.shape, shape)
+            self.assertEqual (imp.strides, strides)
+            self.assertTrue (imp.suboffsets is None)
+            self.assertEqual (imp.buf, s._pixels_address)
+
+        s = pygame.Surface ((8, 16), 0, 32)
+        ar = pygame.PixelArray (s)
+        format = self.bitsize_to_format[s.get_bitsize ()]
+        itemsize = ar.itemsize
+        shape = ar.shape
+        w, h = shape
+        strides = ar.strides
+        length = w * h * itemsize
+        imp = BufferImporter (ar, buftools.PyBUF_SIMPLE)
+        self.assertTrue (imp.obj, ar)
+        self.assertEqual (imp.len, length)
+        self.assertEqual (imp.ndim, 0)
+        self.assertEqual (imp.itemsize, itemsize)
+        self.assertTrue (imp.format is None)
+        self.assertFalse (imp.readonly)
+        self.assertTrue (imp.shape is None)
+        self.assertTrue (imp.strides is None)
+        self.assertTrue (imp.suboffsets is None)
+        self.assertEqual (imp.buf, s._pixels_address)
+        imp = BufferImporter (ar, buftools.PyBUF_FORMAT)
+        self.assertEqual (imp.ndim, 0)
+        self.assertEqual (imp.format, format)
+        imp = BufferImporter (ar, buftools.PyBUF_WRITABLE)
+        self.assertEqual (imp.ndim, 0)
+        self.assertTrue (imp.format is None)
+        imp = BufferImporter (ar, buftools.PyBUF_F_CONTIGUOUS)
+        self.assertEqual (imp.ndim, 2)
+        self.assertTrue (imp.format is None)
+        self.assertEqual (imp.shape, shape)
+        self.assertEqual (imp.strides, strides)
+        imp = BufferImporter (ar, buftools.PyBUF_ANY_CONTIGUOUS)
+        self.assertEqual (imp.ndim, 2)
+        self.assertTrue (imp.format is None)
+        self.assertEqual (imp.shape, shape)
+        self.assertEqual (imp.strides, strides)
+        self.assertRaises (BufferError, BufferImporter, ar,
+                           buftools.PyBUF_C_CONTIGUOUS)
+        self.assertRaises (BufferError, BufferImporter, ar, buftools.PyBUF_ND)
+
+        ar_sliced = ar[:,::2]
+        format = self.bitsize_to_format[s.get_bitsize ()]
+        itemsize = ar_sliced.itemsize
+        shape = ar_sliced.shape
+        w, h = shape
+        strides = ar_sliced.strides
+        length = w * h * itemsize
+        imp = BufferImporter (ar_sliced, buftools.PyBUF_STRIDED)
+        self.assertEqual (imp.len, length)
+        self.assertEqual (imp.ndim, 2)
+        self.assertEqual (imp.itemsize, itemsize)
+        self.assertTrue (imp.format is None)
+        self.assertFalse (imp.readonly)
+        self.assertEqual (imp.shape, shape)
+        self.assertEqual (imp.strides, strides)
+        self.assertEqual (imp.buf, s._pixels_address)
+        self.assertRaises (BufferError, BufferImporter, ar_sliced,
+                           buftools.PyBUF_SIMPLE)
+        self.assertRaises (BufferError, BufferImporter, ar_sliced,
+                           buftools.PyBUF_ND)
+        self.assertRaises (BufferError, BufferImporter, ar_sliced,
+                           buftools.PyBUF_C_CONTIGUOUS)
+        self.assertRaises (BufferError, BufferImporter, ar_sliced,
+                           buftools.PyBUF_F_CONTIGUOUS)
+        self.assertRaises (BufferError, BufferImporter, ar_sliced,
+                           buftools.PyBUF_ANY_CONTIGUOUS)
+
+        ar_sliced = ar[::2,:]
+        format = self.bitsize_to_format[s.get_bitsize ()]
+        itemsize = ar_sliced.itemsize
+        shape = ar_sliced.shape
+        w, h = shape
+        strides = ar_sliced.strides
+        length = w * h * itemsize
+        imp = BufferImporter (ar_sliced, buftools.PyBUF_STRIDED)
+        self.assertEqual (imp.len, length)
+        self.assertEqual (imp.ndim, 2)
+        self.assertEqual (imp.itemsize, itemsize)
+        self.assertTrue (imp.format is None)
+        self.assertFalse (imp.readonly)
+        self.assertEqual (imp.shape, shape)
+        self.assertEqual (imp.strides, strides)
+        self.assertEqual (imp.buf, s._pixels_address)
+        self.assertRaises (BufferError, BufferImporter, ar_sliced,
+                           buftools.PyBUF_SIMPLE)
+        self.assertRaises (BufferError, BufferImporter, ar_sliced,
+                           buftools.PyBUF_ND)
+        self.assertRaises (BufferError, BufferImporter, ar_sliced,
+                           buftools.PyBUF_C_CONTIGUOUS)
+        self.assertRaises (BufferError, BufferImporter, ar_sliced,
+                           buftools.PyBUF_F_CONTIGUOUS)
+        self.assertRaises (BufferError, BufferImporter, ar_sliced,
+                           buftools.PyBUF_ANY_CONTIGUOUS)
+
+        s2 = s.subsurface ((2, 3, 5, 7))
+        ar = pygame.PixelArray (s2)
+        format = self.bitsize_to_format[s.get_bitsize ()]
+        itemsize = ar.itemsize
+        shape = ar.shape
+        w, h = shape
+        strides = ar.strides
+        length = w * h * itemsize
+        imp = BufferImporter (ar, buftools.PyBUF_STRIDES)
+        self.assertTrue (imp.obj, ar)
+        self.assertEqual (imp.len, length)
+        self.assertEqual (imp.ndim, 2)
+        self.assertEqual (imp.itemsize, itemsize)
+        self.assertTrue (imp.format is None)
+        self.assertFalse (imp.readonly)
+        self.assertEqual (imp.shape, shape)
+        self.assertEqual (imp.strides, strides)
+        self.assertTrue (imp.suboffsets is None)
+        self.assertEqual (imp.buf, s2._pixels_address)
+        self.assertRaises (BufferError, BufferImporter, ar,
+                           buftools.PyBUF_SIMPLE)
+        self.assertRaises (BufferError, BufferImporter, ar,
+                           buftools.PyBUF_FORMAT)
+        self.assertRaises (BufferError, BufferImporter, ar,
+                           buftools.PyBUF_WRITABLE)
+        self.assertRaises (BufferError, BufferImporter, ar,
+                           buftools.PyBUF_ND)
+        self.assertRaises (BufferError, BufferImporter, ar,
+                           buftools.PyBUF_C_CONTIGUOUS)
+        self.assertRaises (BufferError, BufferImporter, ar,
+                           buftools.PyBUF_F_CONTIGUOUS)
+        self.assertRaises (BufferError, BufferImporter, ar,
+                           buftools.PyBUF_ANY_CONTIGUOUS)
+
+    def NEWBUF_test_newbuf_1D (self):
+        buftools = self.buftools
+        BufferImporter = buftools.BufferImporter
+
+        s = pygame.Surface ((2, 16), 0, 32)
+        ar_2D = pygame.PixelArray (s)
+        x = 0
+        ar = ar_2D[x]
+        format = self.bitsize_to_format[s.get_bitsize ()]
+        itemsize = ar.itemsize
+        shape = ar.shape
+        h = shape[0]
+        strides = ar.strides
+        length = h * itemsize
+        buf = s._pixels_address + x * itemsize
+        imp = BufferImporter (ar, buftools.PyBUF_STRIDES)
+        self.assertTrue (imp.obj, ar)
+        self.assertEqual (imp.len, length)
+        self.assertEqual (imp.ndim, 1)
+        self.assertEqual (imp.itemsize, itemsize)
+        self.assertTrue (imp.format is None)
+        self.assertFalse (imp.readonly)
+        self.assertEqual (imp.shape, shape)
+        self.assertEqual (imp.strides, strides)
+        self.assertTrue (imp.suboffsets is None)
+        self.assertEqual (imp.buf, buf)
+        imp = BufferImporter (ar, buftools.PyBUF_FULL)
+        self.assertEqual (imp.ndim, 1)
+        self.assertEqual (imp.format, format)
+        self.assertRaises (BufferError, BufferImporter, ar,
+                           buftools.PyBUF_SIMPLE)
+        self.assertRaises (BufferError, BufferImporter, ar,
+                           buftools.PyBUF_FORMAT)
+        self.assertRaises (BufferError, BufferImporter, ar,
+                           buftools.PyBUF_WRITABLE)
+        self.assertRaises (BufferError, BufferImporter, ar,
+                           buftools.PyBUF_ND)
+        self.assertRaises (BufferError, BufferImporter, ar,
+                           buftools.PyBUF_C_CONTIGUOUS)
+        self.assertRaises (BufferError, BufferImporter, ar,
+                           buftools.PyBUF_F_CONTIGUOUS)
+        self.assertRaises (BufferError, BufferImporter, ar,
+                           buftools.PyBUF_ANY_CONTIGUOUS)
+        y = 10
+        ar = ar_2D[:,y]
+        shape = ar.shape
+        w = shape[0]
+        strides = ar.strides
+        length = w * itemsize
+        buf = s._pixels_address + y * s.get_pitch()
+        imp = BufferImporter (ar, buftools.PyBUF_FULL)
+        self.assertEqual (imp.len, length)
+        self.assertEqual (imp.ndim, 1)
+        self.assertEqual (imp.itemsize, itemsize)
+        self.assertEqual (imp.format, format)
+        self.assertFalse (imp.readonly)
+        self.assertEqual (imp.shape, shape)
+        self.assertEqual (imp.strides, strides)
+        self.assertEqual (imp.buf, buf)
+        self.assertTrue (imp.suboffsets is None)
+        imp = BufferImporter (ar, buftools.PyBUF_SIMPLE)
+        self.assertEqual (imp.len, length)
+        self.assertEqual (imp.ndim, 0)
+        self.assertEqual (imp.itemsize, itemsize)
+        self.assertTrue (imp.format is None)
+        self.assertFalse (imp.readonly)
+        self.assertTrue (imp.shape is None)
+        self.assertTrue (imp.strides is None)
+        imp = BufferImporter (ar, buftools.PyBUF_ND)
+        self.assertEqual (imp.len, length)
+        self.assertEqual (imp.ndim, 1)
+        self.assertEqual (imp.itemsize, itemsize)
+        self.assertTrue (imp.format is None)
+        self.assertFalse (imp.readonly)
+        self.assertEqual (imp.shape, shape)
+        self.assertTrue (imp.strides is None)
+        imp = BufferImporter (ar, buftools.PyBUF_C_CONTIGUOUS)
+        self.assertEqual (imp.ndim, 1)
+        imp = BufferImporter (ar, buftools.PyBUF_F_CONTIGUOUS)
+        self.assertEqual (imp.ndim, 1)
+        imp = BufferImporter (ar, buftools.PyBUF_ANY_CONTIGUOUS)
+        self.assertEqual (imp.ndim, 1)
 
 
 if __name__ == '__main__':
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.