Commits

Lenard Lindstrom committed 50ef406

The Surface.get_view now properly exposes raw bytes as a buffer

For the '&' argument value, the buffer length is the size of the entire
surface buffer, including padding bytes. _view.BufferProxy also exports
the old buffer interface for Python 2.x.

  • Participants
  • Parent commits 71a533e

Comments (0)

Files changed (3)

 */
 
 /*
-  This module exports an object which provides an array interface to
-  another object's buffer. Both the C level array structure -
-  __array_struct__ - interface and Python level - __array_interface__ -
-  are exposed.
+  This module exports a proxy object that exposes another object's
+  data throught the Python buffer protocol or the array interface.
+  The new buffer protocol is available for Python 3.x. For Python 2.x
+  only the old protocol is implemented (for PyPy compatibility).
+  Both the C level array structure - __array_struct__ - interface and
+  Python level - __array_interface__ - are exposed.
  */
 
 #define NO_PYGAME_C_API
     Py_intptr_t imem[1];
 } CapsuleInterface;
 
+static int PgBufproxy_Trip(PyObject *);
 static int Pg_GetArrayInterface(PyObject *, PyObject **, PyArrayInterface **);
 static PyObject *Pg_ArrayStructAsDict(PyArrayInterface *);
 static PyObject *Pg_ViewAsDict(Py_buffer *);
     ((PgBufproxyObject *)obj)->after(obj);
 }
 
-static PyBufferProcs proxy_bufferprocs =
-    {proxy_getbuffer, proxy_releasebuffer};
+static PyBufferProcs proxy_bufferprocs = {
+    proxy_getbuffer,
+    proxy_releasebuffer
+};
+
+#else
+
+static Py_ssize_t
+proxy_getreadbuf(PgBufproxyObject *buffer, Py_ssize_t _index, const void **ptr)
+{
+    if (_index != 0) {
+        PyErr_SetString(PyExc_TypeError,
+                        "Accessing non-existent buffer segment");
+        return -1;
+    }
+
+    if (PgBufproxy_Trip((PyObject *)buffer)) {
+        return -1;
+    }
+    *ptr = buffer->view.buf;
+    return buffer->view.len;
+}
+
+static Py_ssize_t
+proxy_getwritebuf(PgBufproxyObject *buffer, Py_ssize_t _index, const void **ptr)
+{
+    if (buffer->view.readonly) {
+        PyErr_SetString(PyExc_TypeError,
+                        "Attempting to get write access to a readonly buffer");
+        return -1;
+    }
+
+    if (_index != 0) {
+        PyErr_SetString(PyExc_TypeError, 
+                        "Accessing non-existent array segment");
+        return -1;
+    }
+
+    if (PgBufproxy_Trip((PyObject *)buffer)) {
+        return -1;
+    }
+    *ptr = buffer->view.buf;
+    return buffer->view.len;
+}
+
+static Py_ssize_t
+proxy_getsegcount(PgBufproxyObject *buffer, Py_ssize_t *lenp)
+{
+    if (lenp) {
+        *lenp = buffer->view.len;
+    }
+    return 1;
+}
+
+static PyBufferProcs proxy_bufferprocs = {
+    (readbufferproc)proxy_getreadbuf,
+    (writebufferproc)proxy_getwritebuf,
+    (segcountproc)proxy_getsegcount,
+    0
+#if PY_VERSION_HEX >= 0x02060000
+    ,
+    0,
+    0
 #endif
+};
+
+#endif /* #if PY3 */
 
 static PyTypeObject PgBufproxy_Type =
 {
     0,                          /* tp_str */
     0,                          /* tp_getattro */
     0,                          /* tp_setattro */
-#if PY3
     &proxy_bufferprocs,         /* tp_as_buffer */
-#else
-    0,                          /* tp_as_buffer */
-#endif
     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,  /* tp_flags */
     "Object bufproxy as an array struct\n",
     0,                          /* tp_traverse */
 #   undef SURF_GET_VIEW_MAXDIM
     SurfViewKind view_kind = VIEWKIND_RAW;
     int ndim = maxdim;
+    Py_ssize_t len = 0;
     SDL_Surface *surface = PySurface_AsSurface (self);
     int pixelsize;
     Py_ssize_t itemsize = 0;
     case VIEWKIND_2D:
         ndim = 2;
         itemsize = pixelsize;
+        len = itemsize * shape[0] * shape[1];
         flags |= BUFPROXY_F_ORDER;
         if (strides[1] == shape[0] * itemsize) {
             flags |= BUFPROXY_CONTIGUOUS;
         ndim = 3;
         itemsize = 1;
         shape[2] = 3;
+        len = itemsize * shape[0] * shape[1] * shape[2];
         if (surface->format->Rmask == 0xff0000 &&
             surface->format->Gmask == 0x00ff00 &&
             surface->format->Bmask == 0x0000ff)   {
     case VIEWKIND_RAW:
         ndim = 0;
         itemsize = 1;
+        flags |= BUFPROXY_CONTIGUOUS; /* Assumes knowledgable consumers */
+        len = surface->pitch * surface->h;
         break;
     default:
         PyErr_Format (PyExc_SystemError,
         ndim = 2;
         pixelstep = pixelsize;
         itemsize = 1;
+        len = itemsize * shape[0] * shape[1];
         flags |= BUFPROXY_F_ORDER;
         switch (mask) {
 
                       (int)itemsize);
         return 0;
     }
-    switch (ndim) {
-
-    case 0:
-        view.len = pixelsize * shape[0] * shape[1];
-        break;
-    case 2:
-        view.len = itemsize * shape[0] * shape[1];
-        break;
-    case 3:
-        view.len = itemsize * shape[0] * shape[1] * shape[2];
-        break;
-    default:
-        /* Bug! Should not get here. */
-        PyErr_Format (PyExc_SystemError,
-                      "Pygame bug: Surface.get_buffer: "
-                      "unrecognized array dimension %d",
-                      (int)ndim);
-        return 0;
-    }
+    view.len = len;
     view.obj = self;
     Py_INCREF (self);
 

test/surface_test.py

 
 import gc
 import weakref
+import ctypes
 
 def intify(i):
     """If i is a long, cast to an int while preserving the bits"""
         self.assert_(isinstance(v, BufferProxy))
         v = s.get_view('b')
 
+        # Check backward compatibility.
         s = pygame.Surface((5, 7), 0, 24)
-        length = s.get_width() * s.get_height() * s.get_bytesize()
+        length = s.get_pitch() * s.get_height()
         v = s.get_view('&')
         self.assert_(isinstance(v, BufferProxy))
         self.assertEqual(v.length, length)
         v = s.get_view()
         self.assert_(isinstance(v, BufferProxy))
         self.assertEqual(v.length, length)
-        
 
-        # Check argument defaults.
+        # Check default argument ('&' for backward compatibility).
+        # The default may change in Pygame 1.9.3.
         s = pygame.Surface((5, 7), 0, 16)
+        length = s.get_pitch() * s.get_height()
         v = s.get_view()
         self.assert_(isinstance(v, BufferProxy))
+        # Simple buffer length check using ctype, which is portable,
+        # unlike buffer, and works ndim 0 views, unlike memoryview.
+        c_byte_Array = ctypes.c_byte * length
+        b = c_byte_Array.from_buffer(v)
+        c_byte_Array_plus_1 = ctypes.c_byte * (length + 1)
+        self.assertRaises(ValueError, c_byte_Array_plus_1.from_buffer, v)
 
         # Check keyword arguments.
         s = pygame.Surface((5, 7), 0, 24)