Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
authorBenoit Bolsee <benoit.bolsee@online.be>2010-02-22 01:20:00 +0300
committerBenoit Bolsee <benoit.bolsee@online.be>2010-02-22 01:20:00 +0300
commit37b9c9fe4dc6a4adea419ce6b20b28dd60e24794 (patch)
treeec184b994766f3e5b3c7c757d37898a5cda9432b /source
parent115fc34dd382c76cf74c062db5741bcbb52fe988 (diff)
VideoTexture: improvements to image data access API.
- Use BGL buffer instead of string for image data. - Add buffer interface to image source. - Allow customization of pixel format. - Add valid property to check if the image data is available. The image property of all Image source objects will now return a BGL 'buffer' object. Previously it was returning a string, which was not working at all with Python 3.1. The BGL buffer type allows sequence access to bytes and is directly usable in BGL OpenGL wrapper functions. The buffer is formated as a 1 dimensional array of bytes with 4 bytes per pixel in RGBA order. BGL buffers will also be accepted in the ImageBuff load() and plot() functions. It is possible to customize the pixel format by using the VideoTexture.imageToArray(image, mode) function: the first argument is a Image source object, the second optional argument is a format string using the R, G, B, A, 0 and 1 characters. For example "BGR" means that each pixel will be 3 bytes, corresponding to the Blue, Green and Red channel in that order. Use 0 for a fixed hex 00 value, 1 for hex FF. The default mode is "RGBA". All Image source objects now support the buffer interface which allows to create memoryview objects for direct access to the image internal buffer without memory copy. The buffer format is one dimensional array of bytes with 4 bytes per pixel in RGBA order. The buffer is writable, which allows custom modifications of the image data. v = memoryview(source) A bug in the Python 3.1 buffer API will cause a crash if the memoryview object cannot be created. Therefore, you must always check first that an image data is available before creating a memoryview object. Use the new valid attribute for that: if source.valid: v = memoryview(source) ... Note: the BGL buffer object itself does not yet support the buffer interface. Note: the valid attribute makes sense only if you use image source in conjunction with texture object like this: # refresh texture but keep image data in memory texture.refresh(False) if texture.source.valid: v = memoryview(texture.source) # process image ... # invalidate image for next texture refresh texture.source.refresh() Limitation: While memoryview objects exist, the image cannot be resized. Resizing occurs with ImageViewport objects when the viewport size is changed or with ImageFFmpeg when a new image is reloaded for example. Any attempt to resize will cause a runtime error. Delete the memoryview objects is you want to resize an image source object.
Diffstat (limited to 'source')
-rw-r--r--source/blender/python/generic/BGL.c65
-rw-r--r--source/blender/python/generic/BGL.h37
-rw-r--r--source/gameengine/VideoTexture/CMakeLists.txt1
-rw-r--r--source/gameengine/VideoTexture/Exception.cpp2
-rw-r--r--source/gameengine/VideoTexture/Exception.h2
-rw-r--r--source/gameengine/VideoTexture/ImageBase.cpp170
-rw-r--r--source/gameengine/VideoTexture/ImageBase.h15
-rw-r--r--source/gameengine/VideoTexture/ImageBuff.cpp102
-rw-r--r--source/gameengine/VideoTexture/ImageMix.cpp3
-rw-r--r--source/gameengine/VideoTexture/ImageRender.cpp6
-rw-r--r--source/gameengine/VideoTexture/ImageViewport.cpp26
-rw-r--r--source/gameengine/VideoTexture/Makefile1
-rw-r--r--source/gameengine/VideoTexture/SConscript2
-rw-r--r--source/gameengine/VideoTexture/Texture.cpp2
-rw-r--r--source/gameengine/VideoTexture/VideoFFmpeg.cpp14
-rw-r--r--source/gameengine/VideoTexture/VideoFFmpeg.h3
-rw-r--r--source/gameengine/VideoTexture/blendVideoTex.cpp16
17 files changed, 365 insertions, 102 deletions
diff --git a/source/blender/python/generic/BGL.c b/source/blender/python/generic/BGL.c
index 2318549f2ec..340ebbe3dcd 100644
--- a/source/blender/python/generic/BGL.c
+++ b/source/blender/python/generic/BGL.c
@@ -36,9 +36,6 @@
#include <GL/glew.h>
#include "MEM_guardedalloc.h"
-static int type_size( int type );
-static Buffer *make_buffer( int type, int ndimensions, int *dimensions );
-
static char Method_Buffer_doc[] =
"(type, dimensions, [template]) - Create a new Buffer object\n\n\
(type) - The format to store data in\n\
@@ -82,7 +79,7 @@ static PyObject *Buffer_dimensions( PyObject * self );
static PyObject *Buffer_getattr( PyObject * self, char *name );
static PyObject *Buffer_repr( PyObject * self );
-PyTypeObject buffer_Type = {
+PyTypeObject BGL_bufferType = {
PyVarObject_HEAD_INIT(NULL, 0)
"buffer", /*tp_name */
sizeof( Buffer ), /*tp_basicsize */
@@ -120,7 +117,7 @@ static PyObject *Method_##funcname (PyObject *self, PyObject *args) {\
/* #endif */
/********/
-static int type_size(int type)
+int BGL_typeSize(int type)
{
switch (type) {
case GL_BYTE:
@@ -137,7 +134,7 @@ static int type_size(int type)
return -1;
}
-static Buffer *make_buffer(int type, int ndimensions, int *dimensions)
+Buffer *BGL_MakeBuffer(int type, int ndimensions, int *dimensions, void *initbuffer)
{
Buffer *buffer;
void *buf= NULL;
@@ -147,39 +144,49 @@ static Buffer *make_buffer(int type, int ndimensions, int *dimensions)
for (i=0; i<ndimensions; i++)
length*= dimensions[i];
- size= type_size(type);
+ size= BGL_typeSize(type);
buf= MEM_mallocN(length*size, "Buffer buffer");
-
- buffer= (Buffer *) PyObject_NEW(Buffer, &buffer_Type);
+
+ buffer= (Buffer *) PyObject_NEW(Buffer, &BGL_bufferType);
buffer->parent= NULL;
buffer->ndimensions= ndimensions;
- buffer->dimensions= dimensions;
+ buffer->dimensions= MEM_mallocN(ndimensions*sizeof(int), "Buffer dimensions");
+ memcpy(buffer->dimensions, dimensions, ndimensions*sizeof(int));
buffer->type= type;
buffer->buf.asvoid= buf;
- for (i= 0; i<length; i++) {
- if (type==GL_BYTE)
- buffer->buf.asbyte[i]= 0;
- else if (type==GL_SHORT)
- buffer->buf.asshort[i]= 0;
- else if (type==GL_INT)
- buffer->buf.asint[i]= 0;
- else if (type==GL_FLOAT)
- buffer->buf.asfloat[i]= 0.0f;
- else if (type==GL_DOUBLE)
- buffer->buf.asdouble[i]= 0.0;
+ if (initbuffer) {
+ memcpy(buffer->buf.asvoid, initbuffer, length*size);
+ } else {
+ memset(buffer->buf.asvoid, 0, length*size);
+ /*
+ for (i= 0; i<length; i++) {
+ if (type==GL_BYTE)
+ buffer->buf.asbyte[i]= 0;
+ else if (type==GL_SHORT)
+ buffer->buf.asshort[i]= 0;
+ else if (type==GL_INT)
+ buffer->buf.asint[i]= 0;
+ else if (type==GL_FLOAT)
+ buffer->buf.asfloat[i]= 0.0f;
+ else if (type==GL_DOUBLE)
+ buffer->buf.asdouble[i]= 0.0;
+ }
+ */
}
return buffer;
}
+#define MAX_DIMENSIONS 256
static PyObject *Method_Buffer (PyObject *self, PyObject *args)
{
PyObject *length_ob= NULL, *template= NULL;
Buffer *buffer;
+ int dimensions[MAX_DIMENSIONS];
int i, type;
- int *dimensions = 0, ndimensions = 0;
+ int ndimensions = 0;
if (!PyArg_ParseTuple(args, "iO|O", &type, &length_ob, &template)) {
PyErr_SetString(PyExc_AttributeError, "expected an int and one or two PyObjects");
@@ -192,11 +199,13 @@ static PyObject *Method_Buffer (PyObject *self, PyObject *args)
if (PyNumber_Check(length_ob)) {
ndimensions= 1;
- dimensions= MEM_mallocN(ndimensions*sizeof(int), "Buffer dimensions");
dimensions[0]= PyLong_AsLong(length_ob);
} else if (PySequence_Check(length_ob)) {
ndimensions= PySequence_Length(length_ob);
- dimensions= MEM_mallocN(ndimensions*sizeof(int), "Buffer dimensions");
+ if (ndimensions > MAX_DIMENSIONS) {
+ PyErr_SetString(PyExc_AttributeError, "too many dimensions, max is 256");
+ return NULL;
+ }
for (i=0; i<ndimensions; i++) {
PyObject *ob= PySequence_GetItem(length_ob, i);
@@ -206,7 +215,7 @@ static PyObject *Method_Buffer (PyObject *self, PyObject *args)
}
}
- buffer= make_buffer(type, ndimensions, dimensions);
+ buffer= BGL_MakeBuffer(type, ndimensions, dimensions, NULL);
if (template && ndimensions) {
if (Buffer_ass_slice((PyObject *) buffer, 0, dimensions[0], template)) {
Py_DECREF(buffer);
@@ -250,9 +259,9 @@ static PyObject *Buffer_item(PyObject *self, int i)
for (j=1; j<buf->ndimensions; j++) {
length*= buf->dimensions[j];
}
- size= type_size(buf->type);
+ size= BGL_typeSize(buf->type);
- newbuf= (Buffer *) PyObject_NEW(Buffer, &buffer_Type);
+ newbuf= (Buffer *) PyObject_NEW(Buffer, &BGL_bufferType);
Py_INCREF(self);
newbuf->parent= self;
@@ -1104,7 +1113,7 @@ PyObject *BGL_Init(void)
PyDict_SetItemString(PySys_GetObject("modules"), BGL_module_def.m_name, mod);
dict= PyModule_GetDict(mod);
- if( PyType_Ready( &buffer_Type) < 0)
+ if( PyType_Ready( &BGL_bufferType) < 0)
return NULL; /* should never happen */
#define EXPP_ADDCONST(x) PyDict_SetItemString(dict, #x, item=PyLong_FromLong((int)x)); Py_DECREF(item)
diff --git a/source/blender/python/generic/BGL.h b/source/blender/python/generic/BGL.h
index 32076f4fba8..89bade930ce 100644
--- a/source/blender/python/generic/BGL.h
+++ b/source/blender/python/generic/BGL.h
@@ -44,9 +44,16 @@
PyObject *BGL_Init(void);
+/*@ Create a buffer object */
+/*@ dimensions is an array of ndimensions integers representing the size of each dimension */
+/*@ initbuffer if not NULL holds a contiguous buffer with the correct format from which the buffer will be initialized */
+struct _Buffer *BGL_MakeBuffer( int type, int ndimensions, int *dimensions, void *initbuffer );
+/*@ Return the size of buffer element, type must be one of GL_BYTE, GL_SHORT, GL_INT, GL_FLOAT or GL_DOUBLE */
+/*@ returns -1 otherwise */
+int BGL_typeSize( int type );
+
/*@ Buffer Object */
/*@ For Python access to OpenGL functions requiring a pointer. */
-
typedef struct _Buffer {
PyObject_VAR_HEAD
PyObject * parent;
@@ -66,6 +73,8 @@ typedef struct _Buffer {
} buf;
} Buffer;
+/*@ The type object */
+extern PyTypeObject BGL_bufferType;
/*@ By golly George! It looks like fancy pants macro time!!! */
@@ -93,7 +102,7 @@ typedef struct _Buffer {
#define buffer_str "O!"
#define buffer_var(number) (bgl_buffer##number)->buf.asvoid
-#define buffer_ref(number) &buffer_Type, &bgl_buffer##number
+#define buffer_ref(number) &BGL_bufferType, &bgl_buffer##number
#define buffer_def(number) Buffer *bgl_buffer##number
/* GL Pointer fields, handled by buffer type */
@@ -101,62 +110,62 @@ typedef struct _Buffer {
#define GLbooleanP_str "O!"
#define GLbooleanP_var(number) (bgl_buffer##number)->buf.asvoid
-#define GLbooleanP_ref(number) &buffer_Type, &bgl_buffer##number
+#define GLbooleanP_ref(number) &BGL_bufferType, &bgl_buffer##number
#define GLbooleanP_def(number) Buffer *bgl_buffer##number
#define GLbyteP_str "O!"
#define GLbyteP_var(number) (bgl_buffer##number)->buf.asvoid
-#define GLbyteP_ref(number) &buffer_Type, &bgl_buffer##number
+#define GLbyteP_ref(number) &BGL_bufferType, &bgl_buffer##number
#define GLbyteP_def(number) Buffer *bgl_buffer##number
#define GLubyteP_str "O!"
#define GLubyteP_var(number) (bgl_buffer##number)->buf.asvoid
-#define GLubyteP_ref(number) &buffer_Type, &bgl_buffer##number
+#define GLubyteP_ref(number) &BGL_bufferType, &bgl_buffer##number
#define GLubyteP_def(number) Buffer *bgl_buffer##number
#define GLintP_str "O!"
#define GLintP_var(number) (bgl_buffer##number)->buf.asvoid
-#define GLintP_ref(number) &buffer_Type, &bgl_buffer##number
+#define GLintP_ref(number) &BGL_bufferType, &bgl_buffer##number
#define GLintP_def(number) Buffer *bgl_buffer##number
#define GLuintP_str "O!"
#define GLuintP_var(number) (bgl_buffer##number)->buf.asvoid
-#define GLuintP_ref(number) &buffer_Type, &bgl_buffer##number
+#define GLuintP_ref(number) &BGL_bufferType, &bgl_buffer##number
#define GLuintP_def(number) Buffer *bgl_buffer##number
#define GLshortP_str "O!"
#define GLshortP_var(number) (bgl_buffer##number)->buf.asvoid
-#define GLshortP_ref(number) &buffer_Type, &bgl_buffer##number
+#define GLshortP_ref(number) &BGL_bufferType, &bgl_buffer##number
#define GLshortP_def(number) Buffer *bgl_buffer##number
#define GLushortP_str "O!"
#define GLushortP_var(number) (bgl_buffer##number)->buf.asvoid
-#define GLushortP_ref(number) &buffer_Type, &bgl_buffer##number
+#define GLushortP_ref(number) &BGL_bufferType, &bgl_buffer##number
#define GLushortP_def(number) Buffer *bgl_buffer##number
#define GLfloatP_str "O!"
#define GLfloatP_var(number) (bgl_buffer##number)->buf.asvoid
-#define GLfloatP_ref(number) &buffer_Type, &bgl_buffer##number
+#define GLfloatP_ref(number) &BGL_bufferType, &bgl_buffer##number
#define GLfloatP_def(number) Buffer *bgl_buffer##number
#define GLdoubleP_str "O!"
#define GLdoubleP_var(number) (bgl_buffer##number)->buf.asvoid
-#define GLdoubleP_ref(number) &buffer_Type, &bgl_buffer##number
+#define GLdoubleP_ref(number) &BGL_bufferType, &bgl_buffer##number
#define GLdoubleP_def(number) Buffer *bgl_buffer##number
#define GLclampfP_str "O!"
#define GLclampfP_var(number) (bgl_buffer##number)->buf.asvoid
-#define GLclampfP_ref(number) &buffer_Type, &bgl_buffer##number
+#define GLclampfP_ref(number) &BGL_bufferType, &bgl_buffer##number
#define GLclampfP_def(number) Buffer *bgl_buffer##number
#define GLvoidP_str "O!"
#define GLvoidP_var(number) (bgl_buffer##number)->buf.asvoid
-#define GLvoidP_ref(number) &buffer_Type, &bgl_buffer##number
+#define GLvoidP_ref(number) &BGL_bufferType, &bgl_buffer##number
#define GLvoidP_def(number) Buffer *bgl_buffer##number
#define buffer_str "O!"
#define buffer_var(number) (bgl_buffer##number)->buf.asvoid
-#define buffer_ref(number) &buffer_Type, &bgl_buffer##number
+#define buffer_ref(number) &BGL_bufferType, &bgl_buffer##number
#define buffer_def(number) Buffer *bgl_buffer##number
/*@The standard GL typedefs are used as prototypes, we can't
diff --git a/source/gameengine/VideoTexture/CMakeLists.txt b/source/gameengine/VideoTexture/CMakeLists.txt
index 1268d887eee..eaee00753ff 100644
--- a/source/gameengine/VideoTexture/CMakeLists.txt
+++ b/source/gameengine/VideoTexture/CMakeLists.txt
@@ -42,6 +42,7 @@ SET(INC
../../../source/blender/editors/include
../../../source/blender/imbuf
../../../source/blender/python
+ ../../../source/blender/python/generic
../../../source/blender/gpu
../../../source/kernel/gen_system
../../../intern/string
diff --git a/source/gameengine/VideoTexture/Exception.cpp b/source/gameengine/VideoTexture/Exception.cpp
index 8b4808ffbe9..124c8ae27d8 100644
--- a/source/gameengine/VideoTexture/Exception.cpp
+++ b/source/gameengine/VideoTexture/Exception.cpp
@@ -202,6 +202,8 @@ void registerAllExceptions(void)
errNFoundDesc.registerDesc();
MaterialNotAvailDesc.registerDesc();
ImageSizesNotMatchDesc.registerDesc();
+ ImageHasExportsDesc.registerDesc();
+ InvalidColorChannelDesc.registerDesc();
SceneInvalidDesc.registerDesc();
CameraInvalidDesc.registerDesc();
ObserverInvalidDesc.registerDesc();
diff --git a/source/gameengine/VideoTexture/Exception.h b/source/gameengine/VideoTexture/Exception.h
index 4a57a7f20e4..74dc444c0a9 100644
--- a/source/gameengine/VideoTexture/Exception.h
+++ b/source/gameengine/VideoTexture/Exception.h
@@ -200,6 +200,8 @@ protected:
extern ExpDesc MaterialNotAvailDesc;
extern ExpDesc ImageSizesNotMatchDesc;
+extern ExpDesc ImageHasExportsDesc;
+extern ExpDesc InvalidColorChannelDesc;
extern ExpDesc SceneInvalidDesc;
extern ExpDesc CameraInvalidDesc;
extern ExpDesc ObserverInvalidDesc;
diff --git a/source/gameengine/VideoTexture/ImageBase.cpp b/source/gameengine/VideoTexture/ImageBase.cpp
index 1c5425c932c..908aa5ddd5e 100644
--- a/source/gameengine/VideoTexture/ImageBase.cpp
+++ b/source/gameengine/VideoTexture/ImageBase.cpp
@@ -21,6 +21,10 @@ http://www.gnu.org/copyleft/lesser.txt.
*/
#include "ImageBase.h"
+extern "C" {
+#include "BGL.h"
+}
+#include "GL/glew.h"
#include <vector>
#include <string.h>
@@ -32,7 +36,9 @@ http://www.gnu.org/copyleft/lesser.txt.
#include "Exception.h"
-
+#if (defined(WIN32) || defined(WIN64)) && !defined(FREE_WINDOWS)
+#define strcasecmp _stricmp
+#endif
// ImageBase class implementation
@@ -42,6 +48,7 @@ m_avail(false), m_scale(false), m_scaleChange(false), m_flip(false),
m_staticSources(staticSrc), m_pyfilter(NULL)
{
m_size[0] = m_size[1] = 0;
+ m_exports = 0;
}
@@ -161,6 +168,11 @@ void ImageBase::setFilter (PyFilter * filt)
m_pyfilter = filt;
}
+ExceptionID ImageHasExports;
+ExceptionID InvalidColorChannel;
+
+ExpDesc ImageHasExportsDesc (ImageHasExports, "Image has exported buffers, cannot resize");
+ExpDesc InvalidColorChannelDesc (InvalidColorChannel, "Invalid or too many color channels specified. At most 4 values within R, G, B, A, 0, 1");
// initialize image data
void ImageBase::init (short width, short height)
@@ -175,6 +187,9 @@ void ImageBase::init (short width, short height)
// if sizes differ
if (width != m_size[0] || height != m_size[1])
{
+ if (m_exports > 0)
+ THRWEXCP(ImageHasExports,S_OK);
+
// new buffer size
unsigned int newSize = width * height;
// if new buffer is larger than previous
@@ -352,6 +367,12 @@ void Image_dealloc (PyImage * self)
// release object attributes
if (self->m_image != NULL)
{
+ if (self->m_image->m_exports > 0)
+ {
+ PyErr_SetString(PyExc_SystemError,
+ "deallocated Image object has exported buffers");
+ PyErr_Print();
+ }
// if release requires deleting of object, do it
if (self->m_image->release())
delete self->m_image;
@@ -360,17 +381,88 @@ void Image_dealloc (PyImage * self)
}
// get image data
-PyObject * Image_getImage (PyImage * self, void * closure)
+PyObject * Image_getImage (PyImage * self, char * mode)
{
try
{
- // get image
unsigned int * image = self->m_image->getImage();
- return Py_BuildValue("s#", image, self->m_image->getBuffSize());
+ if (image)
+ {
+ // build BGL buffer
+ int dimensions = self->m_image->getBuffSize();
+ Buffer * buffer;
+ if (mode == NULL || !strcasecmp(mode, "RGBA"))
+ {
+ buffer = BGL_MakeBuffer( GL_BYTE, 1, &dimensions, image);
+ }
+ else
+ {
+ int i, c, ncolor, pixels;
+ int offset[4];
+ unsigned char *s, *d;
+ // scan the mode to get the channels requested, no more than 4
+ for (i=ncolor=0; mode[i] != 0 && ncolor < 4; i++)
+ {
+ switch (toupper(mode[i]))
+ {
+ case 'R':
+ offset[ncolor++] = 0;
+ break;
+ case 'G':
+ offset[ncolor++] = 1;
+ break;
+ case 'B':
+ offset[ncolor++] = 2;
+ break;
+ case 'A':
+ offset[ncolor++] = 3;
+ break;
+ case '0':
+ offset[ncolor++] = -1;
+ break;
+ case '1':
+ offset[ncolor++] = -2;
+ break;
+ // if you add more color code, change the switch further down
+ default:
+ THRWEXCP(InvalidColorChannel,S_OK);
+ }
+ }
+ if (mode[i] != 0) {
+ THRWEXCP(InvalidColorChannel,S_OK);
+ }
+ // first get the number of pixels
+ pixels = dimensions / 4;
+ // multiple by the number of channels, each is one byte
+ dimensions = pixels * ncolor;
+ // get an empty buffer
+ buffer = BGL_MakeBuffer( GL_BYTE, 1, &dimensions, NULL);
+ // and fill it
+ for (i=0, d=(unsigned char*)buffer->buf.asbyte, s=(unsigned char*)image;
+ i<pixels;
+ ++i, d+=ncolor, s+=4)
+ {
+ for (c=0; c<ncolor; c++)
+ {
+ switch (offset[c])
+ {
+ case 0: d[c] = s[0]; break;
+ case 1: d[c] = s[1]; break;
+ case 2: d[c] = s[2]; break;
+ case 3: d[c] = s[3]; break;
+ case -1: d[c] = 0; break;
+ case -2: d[c] = 0xFF; break;
+ }
+ }
+ }
+ }
+ return (PyObject*)buffer;
+ }
}
catch (Exception & exp)
{
exp.report();
+ return NULL;
}
Py_RETURN_NONE;
}
@@ -533,3 +625,73 @@ int Image_setFilter (PyImage * self, PyObject * value, void * closure)
// return success
return 0;
}
+PyObject * Image_valid(PyImage * self, void * closure)
+{
+ if (self->m_image->isImageAvailable())
+ {
+ Py_RETURN_TRUE;
+ }
+ else
+ {
+ Py_RETURN_FALSE;
+ }
+}
+
+int Image_getbuffer(PyImage *self, Py_buffer *view, int flags)
+{
+ unsigned int * image;
+ int ret;
+
+ try
+ {
+ // can throw in case of resize
+ image = self->m_image->getImage();
+ }
+ catch (Exception & exp)
+ {
+ // cannot return -1, this creates a crash in Python, for now we will just return an empty buffer
+ //exp.report();
+ //return -1;
+ goto error;
+ }
+
+ if (!image)
+ {
+ // same remark, see above
+ //PyErr_SetString(PyExc_BufferError, "Image buffer is not available");
+ //return -1;
+ goto error;
+ }
+ if (view == NULL)
+ {
+ self->m_image->m_exports++;
+ return 0;
+ }
+ ret = PyBuffer_FillInfo(view, (PyObject*)self, image, self->m_image->getBuffSize(), 0, flags);
+ if (ret >= 0)
+ self->m_image->m_exports++;
+ return ret;
+
+error:
+ // Return a empty buffer to avoid a crash in Python 3.1
+ // The bug is fixed in Python SVN 77916, as soon as the python revision used by Blender is
+ // updated, you can simply return -1 and set the error
+ static char* buf = "";
+ ret = PyBuffer_FillInfo(view, (PyObject*)self, buf, 0, 0, flags);
+ if (ret >= 0)
+ self->m_image->m_exports++;
+ return ret;
+
+}
+
+void Image_releaseBuffer(PyImage *self, Py_buffer *buffer)
+{
+ self->m_image->m_exports--;
+}
+
+PyBufferProcs imageBufferProcs =
+{
+ (getbufferproc)Image_getbuffer,
+ (releasebufferproc)Image_releaseBuffer
+};
+
diff --git a/source/gameengine/VideoTexture/ImageBase.h b/source/gameengine/VideoTexture/ImageBase.h
index 70b1929b91d..43a56290bee 100644
--- a/source/gameengine/VideoTexture/ImageBase.h
+++ b/source/gameengine/VideoTexture/ImageBase.h
@@ -53,6 +53,9 @@ public:
/// release contained objects, if returns true, object should be deleted
virtual bool release (void);
+ /// is an image available
+ bool isImageAvailable(void)
+ { return m_avail; }
/// get image
unsigned int * getImage (unsigned int texId = 0, double timestamp=-1.0);
/// get image size
@@ -85,6 +88,9 @@ public:
/// calculate size (nearest power of 2)
static short calcSize (short size);
+ /// number of buffer pointing to m_image, public because not handled by this class
+ int m_exports;
+
protected:
/// image buffer
unsigned int * m_image;
@@ -295,8 +301,6 @@ private:
ImageSource (void) {}
};
-
-
// list of python image types
extern PyTypeList pyImageTypes;
@@ -320,7 +324,7 @@ PyObject * Image_allocNew (PyTypeObject * type, PyObject * args, PyObject * kwds
void Image_dealloc (PyImage * self);
// get image data
-PyObject * Image_getImage (PyImage * self, void * closure);
+PyObject * Image_getImage (PyImage * self, char * mode);
// get image size
PyObject * Image_getSize (PyImage * self, void * closure);
// refresh image - invalidate current content
@@ -344,6 +348,9 @@ PyObject * Image_setSource (PyImage * self, PyObject * args);
PyObject * Image_getFilter (PyImage * self, void * closure);
// set pixel filter object
int Image_setFilter (PyImage * self, PyObject * value, void * closure);
-
+// check if a buffer can be extracted
+PyObject * Image_valid(PyImage * self, void * closure);
+// for buffer access to PyImage objects
+extern PyBufferProcs imageBufferProcs;
#endif
diff --git a/source/gameengine/VideoTexture/ImageBuff.cpp b/source/gameengine/VideoTexture/ImageBuff.cpp
index eccac9d9f89..474a34b2f43 100644
--- a/source/gameengine/VideoTexture/ImageBuff.cpp
+++ b/source/gameengine/VideoTexture/ImageBuff.cpp
@@ -26,7 +26,7 @@ http://www.gnu.org/copyleft/lesser.txt.
#include <structmember.h>
#include "ImageBuff.h"
-
+#include "Exception.h"
#include "ImageBase.h"
#include "FilterSource.h"
@@ -34,6 +34,7 @@ http://www.gnu.org/copyleft/lesser.txt.
extern "C" {
#include "IMB_imbuf_types.h"
#include "IMB_imbuf.h"
+#include "BGL.h"
};
// default filter
@@ -137,7 +138,7 @@ static bool testPyBuffer(Py_buffer* buffer, int width, int height, unsigned int
}
if (buffer->len != width*height*pixsize)
{
- PyErr_SetString(PyExc_ValueError, "Buffer hasn't correct size");
+ PyErr_SetString(PyExc_ValueError, "Buffer hasn't the correct size");
return false;
}
// multi dimension are ok as long as there is no hole in the memory
@@ -160,43 +161,91 @@ static bool testPyBuffer(Py_buffer* buffer, int width, int height, unsigned int
return true;
}
+static bool testBGLBuffer(Buffer* buffer, int width, int height, unsigned int pixsize)
+{
+ unsigned int size = BGL_typeSize(buffer->type);
+ for (int i=0; i<buffer->ndimensions; i++)
+ {
+ size *= buffer->dimensions[i];
+ }
+ if (size != width*height*pixsize)
+ {
+ PyErr_SetString(PyExc_ValueError, "Buffer hasn't the correct size");
+ return false;
+ }
+ return true;
+}
+
+
// load image
static PyObject * load (PyImage * self, PyObject * args)
{
// parameters: string image buffer, its size, width, height
Py_buffer buffer;
+ Buffer *bglBuffer;
short width;
short height;
+ unsigned int pixSize;
+
+ // calc proper buffer size
+ // use pixel size from filter
+ if (self->m_image->getFilter() != NULL)
+ pixSize = self->m_image->getFilter()->m_filter->firstPixelSize();
+ else
+ pixSize = defFilter.firstPixelSize();
+
// parse parameters
if (!PyArg_ParseTuple(args, "s*hh:load", &buffer, &width, &height))
{
- // report error
- return NULL;
+ PyErr_Clear();
+ // check if it is BGL buffer
+ if (!PyArg_ParseTuple(args, "O!hh:load", &BGL_bufferType, &bglBuffer, &width, &height))
+ {
+ // report error
+ return NULL;
+ }
+ else
+ {
+ if (testBGLBuffer(bglBuffer, width, height, pixSize))
+ {
+ try
+ {
+ // if correct, load image
+ getImageBuff(self)->load((unsigned char*)bglBuffer->buf.asvoid, width, height);
+ }
+ catch (Exception & exp)
+ {
+ exp.report();
+ }
+ }
+ }
}
- // else check buffer size
else
{
- // calc proper buffer size
- unsigned int pixSize;
- // use pixel size from filter
- if (self->m_image->getFilter() != NULL)
- pixSize = self->m_image->getFilter()->m_filter->firstPixelSize();
- else
- pixSize = defFilter.firstPixelSize();
// check if buffer size is correct
if (testPyBuffer(&buffer, width, height, pixSize))
{
- // if correct, load image
- getImageBuff(self)->load((unsigned char*)buffer.buf, width, height);
+ try
+ {
+ // if correct, load image
+ getImageBuff(self)->load((unsigned char*)buffer.buf, width, height);
+ }
+ catch (Exception & exp)
+ {
+ exp.report();
+ }
}
PyBuffer_Release(&buffer);
}
+ if (PyErr_Occurred())
+ return NULL;
Py_RETURN_NONE;
}
static PyObject * plot (PyImage * self, PyObject * args)
{
PyImage * other;
+ Buffer* bglBuffer;
Py_buffer buffer;
//unsigned char * buff;
//unsigned int buffSize;
@@ -214,17 +263,31 @@ static PyObject * plot (PyImage * self, PyObject * args)
getImageBuff(self)->plot((unsigned char*)buffer.buf, width, height, x, y, mode);
}
PyBuffer_Release(&buffer);
+ if (PyErr_Occurred())
+ return NULL;
Py_RETURN_NONE;
}
PyErr_Clear();
// try the other format
- if (!PyArg_ParseTuple(args, "O!hh|h:plot", &ImageBuffType, &other, &x, &y, &mode))
+ if (PyArg_ParseTuple(args, "O!hh|h:plot", &ImageBuffType, &other, &x, &y, &mode))
{
- PyErr_SetString(PyExc_TypeError, "Expecting ImageBuff or string,width,height as first arguments, postion x, y and mode and last arguments");
+ getImageBuff(self)->plot(getImageBuff(other), x, y, mode);
+ Py_RETURN_NONE;
+ }
+ PyErr_Clear();
+ // try the last format (BGL buffer)
+ if (!PyArg_ParseTuple(args, "O!hhhh|h:plot", &BGL_bufferType, &bglBuffer, &width, &height, &x, &y, &mode))
+ {
+ PyErr_SetString(PyExc_TypeError, "Expecting ImageBuff or Py buffer or BGL buffer as first argument; width, height next; postion x, y and mode as last arguments");
return NULL;
}
- getImageBuff(self)->plot(getImageBuff(other), x, y, mode);
- Py_RETURN_NONE;
+ if (testBGLBuffer(bglBuffer, width, height, 4))
+ {
+ getImageBuff(self)->plot((unsigned char*)bglBuffer->buf.asvoid, width, height, x, y, mode);
+ }
+ if (PyErr_Occurred)
+ return NULL;
+ Py_RETURN_NONE;
}
// methods structure
@@ -237,6 +300,7 @@ static PyMethodDef imageBuffMethods[] =
// attributes structure
static PyGetSetDef imageBuffGetSets[] =
{ // attributes from ImageBase class
+ {(char*)"valid", (getter)Image_valid, NULL, (char*)"bool to tell if an image is available", NULL},
{(char*)"image", (getter)Image_getImage, NULL, (char*)"image data", NULL},
{(char*)"size", (getter)Image_getSize, NULL, (char*)"image size", NULL},
{(char*)"scale", (getter)Image_getScale, (setter)Image_setScale, (char*)"fast scale of image (near neighbour)", NULL},
@@ -267,7 +331,7 @@ PyTypeObject ImageBuffType =
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
- 0, /*tp_as_buffer*/
+ &imageBufferProcs, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
"Image source from image buffer", /* tp_doc */
0, /* tp_traverse */
diff --git a/source/gameengine/VideoTexture/ImageMix.cpp b/source/gameengine/VideoTexture/ImageMix.cpp
index a4a61e7f55a..7b304dda3ce 100644
--- a/source/gameengine/VideoTexture/ImageMix.cpp
+++ b/source/gameengine/VideoTexture/ImageMix.cpp
@@ -154,6 +154,7 @@ static PyMethodDef imageMixMethods[] =
// attributes structure
static PyGetSetDef imageMixGetSets[] =
{ // attributes from ImageBase class
+ {(char*)"valid", (getter)Image_valid, NULL, (char*)"bool to tell if an image is available", NULL},
{(char*)"image", (getter)Image_getImage, NULL, (char*)"image data", NULL},
{(char*)"size", (getter)Image_getSize, NULL, (char*)"image size", NULL},
{(char*)"scale", (getter)Image_getScale, (setter)Image_setScale, (char*)"fast scale of image (near neighbour)", NULL},
@@ -184,7 +185,7 @@ PyTypeObject ImageMixType =
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
- 0, /*tp_as_buffer*/
+ &imageBufferProcs, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
"Image mixer", /* tp_doc */
0, /* tp_traverse */
diff --git a/source/gameengine/VideoTexture/ImageRender.cpp b/source/gameengine/VideoTexture/ImageRender.cpp
index 0a01ce11a0e..d654bc14114 100644
--- a/source/gameengine/VideoTexture/ImageRender.cpp
+++ b/source/gameengine/VideoTexture/ImageRender.cpp
@@ -365,6 +365,7 @@ static PyGetSetDef imageRenderGetSets[] =
{(char*)"alpha", (getter)ImageViewport_getAlpha, (setter)ImageViewport_setAlpha, (char*)"use alpha in texture", NULL},
{(char*)"whole", (getter)ImageViewport_getWhole, (setter)ImageViewport_setWhole, (char*)"use whole viewport to render", NULL},
// attributes from ImageBase class
+ {(char*)"valid", (getter)Image_valid, NULL, (char*)"bool to tell if an image is available", NULL},
{(char*)"image", (getter)Image_getImage, NULL, (char*)"image data", NULL},
{(char*)"size", (getter)Image_getSize, NULL, (char*)"image size", NULL},
{(char*)"scale", (getter)Image_getScale, (setter)Image_setScale, (char*)"fast scale of image (near neighbour)", NULL},
@@ -395,7 +396,7 @@ PyTypeObject ImageRenderType =
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
- 0, /*tp_as_buffer*/
+ &imageBufferProcs, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
"Image source from render", /* tp_doc */
0, /* tp_traverse */
@@ -526,6 +527,7 @@ static PyGetSetDef imageMirrorGetSets[] =
{(char*)"alpha", (getter)ImageViewport_getAlpha, (setter)ImageViewport_setAlpha, (char*)"use alpha in texture", NULL},
{(char*)"whole", (getter)ImageViewport_getWhole, (setter)ImageViewport_setWhole, (char*)"use whole viewport to render", NULL},
// attributes from ImageBase class
+ {(char*)"valid", (getter)Image_valid, NULL, (char*)"bool to tell if an image is available", NULL},
{(char*)"image", (getter)Image_getImage, NULL, (char*)"image data", NULL},
{(char*)"size", (getter)Image_getSize, NULL, (char*)"image size", NULL},
{(char*)"scale", (getter)Image_getScale, (setter)Image_setScale, (char*)"fast scale of image (near neighbour)", NULL},
@@ -729,7 +731,7 @@ PyTypeObject ImageMirrorType =
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
- 0, /*tp_as_buffer*/
+ &imageBufferProcs, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
"Image source from mirror", /* tp_doc */
0, /* tp_traverse */
diff --git a/source/gameengine/VideoTexture/ImageViewport.cpp b/source/gameengine/VideoTexture/ImageViewport.cpp
index c39173a96f9..5a4e8af1b0c 100644
--- a/source/gameengine/VideoTexture/ImageViewport.cpp
+++ b/source/gameengine/VideoTexture/ImageViewport.cpp
@@ -177,8 +177,16 @@ int ImageViewport_setWhole (PyImage * self, PyObject * value, void * closure)
PyErr_SetString(PyExc_TypeError, "The value must be a bool");
return -1;
}
- // set whole
- if (self->m_image != NULL) getImageViewport(self)->setWhole(value == Py_True);
+ try
+ {
+ // set whole, can throw in case of resize and buffer exports
+ if (self->m_image != NULL) getImageViewport(self)->setWhole(value == Py_True);
+ }
+ catch (Exception & exp)
+ {
+ exp.report();
+ return -1;
+ }
// success
return 0;
}
@@ -257,7 +265,16 @@ int ImageViewport_setCaptureSize (PyImage * self, PyObject * value, void * closu
short(PyLong_AsSsize_t(PySequence_Fast_GET_ITEM(value, 0))),
short(PyLong_AsSsize_t(PySequence_Fast_GET_ITEM(value, 1)))
};
- getImageViewport(self)->setCaptureSize(size);
+ try
+ {
+ // can throw in case of resize and buffer exports
+ getImageViewport(self)->setCaptureSize(size);
+ }
+ catch (Exception & exp)
+ {
+ exp.report();
+ return -1;
+ }
// success
return 0;
}
@@ -277,6 +294,7 @@ static PyGetSetDef imageViewportGetSets[] =
{(char*)"capsize", (getter)ImageViewport_getCaptureSize, (setter)ImageViewport_setCaptureSize, (char*)"size of viewport area being captured", NULL},
{(char*)"alpha", (getter)ImageViewport_getAlpha, (setter)ImageViewport_setAlpha, (char*)"use alpha in texture", NULL},
// attributes from ImageBase class
+ {(char*)"valid", (getter)Image_valid, NULL, (char*)"bool to tell if an image is available", NULL},
{(char*)"image", (getter)Image_getImage, NULL, (char*)"image data", NULL},
{(char*)"size", (getter)Image_getSize, NULL, (char*)"image size", NULL},
{(char*)"scale", (getter)Image_getScale, (setter)Image_setScale, (char*)"fast scale of image (near neighbour)", NULL},
@@ -307,7 +325,7 @@ PyTypeObject ImageViewportType =
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
- 0, /*tp_as_buffer*/
+ &imageBufferProcs, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
"Image source from viewport", /* tp_doc */
0, /* tp_traverse */
diff --git a/source/gameengine/VideoTexture/Makefile b/source/gameengine/VideoTexture/Makefile
index 90457df720f..1cb147860cd 100644
--- a/source/gameengine/VideoTexture/Makefile
+++ b/source/gameengine/VideoTexture/Makefile
@@ -41,6 +41,7 @@ CPPFLAGS += -I$(NAN_GLEW)/include
CPPFLAGS += -I$(OPENGL_HEADERS)
CPPFLAGS += -I$(NAN_PYTHON)/include/python$(NAN_PYTHON_VERSION)
CPPFLAGS += -I../../blender/python
+CPPFLAGS += -I../../blender/python/generic
CPPFLAGS += -I$(NAN_STRING)/include
CPPFLAGS += -I$(NAN_MOTO)/include
CPPFLAGS += -I../Rasterizer/RAS_OpenGLRasterizer
diff --git a/source/gameengine/VideoTexture/SConscript b/source/gameengine/VideoTexture/SConscript
index 59c311d5240..70c7dfc6d3a 100644
--- a/source/gameengine/VideoTexture/SConscript
+++ b/source/gameengine/VideoTexture/SConscript
@@ -10,7 +10,7 @@ incs += ' #source/gameengine/GameLogic #source/gameengine/SceneGraph #source/gam
incs += ' #source/gameengine/Rasterizer/RAS_OpenGLRasterizer'
incs += ' #source/gameengine/BlenderRoutines'
incs += ' #source/blender/editors/include #source/blender/blenlib #source/blender/blenkernel'
-incs += ' #source/blender/makesdna #source/blender/imbuf #source/blender/python'
+incs += ' #source/blender/makesdna #source/blender/imbuf #source/blender/python #source/blender/python/generic'
incs += ' #source/blender/gpu #source/kernel/gen_system #intern/string #intern/moto/include'
incs += ' #intern/guardedalloc #extern/glew/include'
diff --git a/source/gameengine/VideoTexture/Texture.cpp b/source/gameengine/VideoTexture/Texture.cpp
index f59b92409f5..b8ed38c435d 100644
--- a/source/gameengine/VideoTexture/Texture.cpp
+++ b/source/gameengine/VideoTexture/Texture.cpp
@@ -455,7 +455,7 @@ PyTypeObject TextureType =
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
- 0, /*tp_as_buffer*/
+ &imageBufferProcs, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
"Texture objects", /* tp_doc */
0, /* tp_traverse */
diff --git a/source/gameengine/VideoTexture/VideoFFmpeg.cpp b/source/gameengine/VideoTexture/VideoFFmpeg.cpp
index 4c87b1764d6..c33cb9a671d 100644
--- a/source/gameengine/VideoTexture/VideoFFmpeg.cpp
+++ b/source/gameengine/VideoTexture/VideoFFmpeg.cpp
@@ -723,14 +723,8 @@ void VideoFFmpeg::setFrameRate (float rate)
// image calculation
-void VideoFFmpeg::calcImage (unsigned int texId, double ts)
-{
- loadFrame(ts);
-}
-
-
// load frame from video
-void VideoFFmpeg::loadFrame (double ts)
+void VideoFFmpeg::calcImage (unsigned int texId, double ts)
{
if (m_status == SourcePlaying)
{
@@ -1162,6 +1156,7 @@ static PyGetSetDef videoGetSets[] =
{(char*)"repeat", (getter)Video_getRepeat, (setter)Video_setRepeat, (char*)"repeat count, -1 for infinite repeat", NULL},
{(char*)"framerate", (getter)Video_getFrameRate, (setter)Video_setFrameRate, (char*)"frame rate", NULL},
// attributes from ImageBase class
+ {(char*)"valid", (getter)Image_valid, NULL, (char*)"bool to tell if an image is available", NULL},
{(char*)"image", (getter)Image_getImage, NULL, (char*)"image data", NULL},
{(char*)"size", (getter)Image_getSize, NULL, (char*)"image size", NULL},
{(char*)"scale", (getter)Image_getScale, (setter)Image_setScale, (char*)"fast scale of image (near neighbour)", NULL},
@@ -1193,7 +1188,7 @@ PyTypeObject VideoFFmpegType =
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
- 0, /*tp_as_buffer*/
+ &imageBufferProcs, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
"FFmpeg video source", /* tp_doc */
0, /* tp_traverse */
@@ -1282,6 +1277,7 @@ static PyGetSetDef imageGetSets[] =
{ // methods from VideoBase class
{(char*)"status", (getter)Video_getStatus, NULL, (char*)"video status", NULL},
// attributes from ImageBase class
+ {(char*)"valid", (getter)Image_valid, NULL, (char*)"bool to tell if an image is available", NULL},
{(char*)"image", (getter)Image_getImage, NULL, (char*)"image data", NULL},
{(char*)"size", (getter)Image_getSize, NULL, (char*)"image size", NULL},
{(char*)"scale", (getter)Image_getScale, (setter)Image_setScale, (char*)"fast scale of image (near neighbour)", NULL},
@@ -1311,7 +1307,7 @@ PyTypeObject ImageFFmpegType =
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
- 0, /*tp_as_buffer*/
+ &imageBufferProcs, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
"FFmpeg image source", /* tp_doc */
0, /* tp_traverse */
diff --git a/source/gameengine/VideoTexture/VideoFFmpeg.h b/source/gameengine/VideoTexture/VideoFFmpeg.h
index 355c58c496b..b9bf69039c7 100644
--- a/source/gameengine/VideoTexture/VideoFFmpeg.h
+++ b/source/gameengine/VideoTexture/VideoFFmpeg.h
@@ -159,9 +159,6 @@ protected:
/// image calculation
virtual void calcImage (unsigned int texId, double ts);
- /// load frame from video
- void loadFrame (double ts);
-
/// set actual position
void setPositions (void);
diff --git a/source/gameengine/VideoTexture/blendVideoTex.cpp b/source/gameengine/VideoTexture/blendVideoTex.cpp
index 998d63506b0..01e783edc10 100644
--- a/source/gameengine/VideoTexture/blendVideoTex.cpp
+++ b/source/gameengine/VideoTexture/blendVideoTex.cpp
@@ -86,7 +86,8 @@ static PyObject * imageToArray (PyObject * self, PyObject *args)
{
// parameter is Image object
PyObject * pyImg;
- if (!PyArg_ParseTuple(args, "O:imageToArray", &pyImg) || !pyImageTypes.in(pyImg->ob_type))
+ char *mode = NULL;
+ if (!PyArg_ParseTuple(args, "O|s:imageToArray", &pyImg, &mode) || !pyImageTypes.in(pyImg->ob_type))
{
// if object is incorect, report error
PyErr_SetString(PyExc_TypeError, "VideoTexture.imageToArray(image): The value must be a image source object");
@@ -94,16 +95,7 @@ static PyObject * imageToArray (PyObject * self, PyObject *args)
}
// get image structure
PyImage * img = reinterpret_cast<PyImage*>(pyImg);
- // create array object
- unsigned int * imgBuff = img->m_image->getImage();
- // if image is available, convert it to array
- if (imgBuff != NULL)
- // Nasty problem here: the image buffer is an array of integers
- // in the processor endian format. The user must take care of that in the script.
- // Need to find an elegant solution to this problem
- return Py_BuildValue("s#", imgBuff, img->m_image->getBuffSize());
- // otherwise return None
- Py_RETURN_NONE;
+ return Image_getImage(img, mode);
}
@@ -113,7 +105,7 @@ static PyMethodDef moduleMethods[] =
{"materialID", getMaterialID, METH_VARARGS, "Gets object's Blender Material ID"},
{"getLastError", getLastError, METH_NOARGS, "Gets last error description"},
{"setLogFile", setLogFile, METH_VARARGS, "Sets log file name"},
- {"imageToArray", imageToArray, METH_VARARGS, "get array from image source"},
+ {"imageToArray", imageToArray, METH_VARARGS, "get buffer from image source, color channels are selectable"},
{NULL} /* Sentinel */
};