forked from bartvdbraak/blender
769 lines
17 KiB
C++
769 lines
17 KiB
C++
/*
|
|
* ***** BEGIN GPL LICENSE BLOCK *****
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* Copyright (c) 2007 The Zdeno Ash Miklas
|
|
*
|
|
* This source file is part of VideoTexture library
|
|
*
|
|
* Contributor(s):
|
|
*
|
|
* ***** END GPL LICENSE BLOCK *****
|
|
*/
|
|
|
|
/** \file gameengine/VideoTexture/ImageBase.cpp
|
|
* \ingroup bgevideotex
|
|
*/
|
|
|
|
#include "ImageBase.h"
|
|
extern "C" {
|
|
#include "bgl.h"
|
|
}
|
|
#include "GL/glew.h"
|
|
|
|
#include <vector>
|
|
#include <string.h>
|
|
|
|
#include "PyObjectPlus.h"
|
|
#include <structmember.h>
|
|
|
|
#include "FilterBase.h"
|
|
|
|
#include "Exception.h"
|
|
|
|
#if (defined(WIN32) || defined(WIN64)) && !defined(FREE_WINDOWS)
|
|
#define strcasecmp _stricmp
|
|
#endif
|
|
|
|
// ImageBase class implementation
|
|
|
|
// constructor
|
|
ImageBase::ImageBase (bool staticSrc) : m_image(NULL), m_imgSize(0),
|
|
m_avail(false), m_scale(false), m_scaleChange(false), m_flip(false),
|
|
m_zbuff(false),
|
|
m_depth(false),
|
|
m_staticSources(staticSrc), m_pyfilter(NULL)
|
|
{
|
|
m_size[0] = m_size[1] = 0;
|
|
m_exports = 0;
|
|
}
|
|
|
|
|
|
// destructor
|
|
ImageBase::~ImageBase (void)
|
|
{
|
|
// release image
|
|
if (m_image)
|
|
delete [] m_image;
|
|
}
|
|
|
|
|
|
// release python objects
|
|
bool ImageBase::release (void)
|
|
{
|
|
// iterate sources
|
|
for (ImageSourceList::iterator it = m_sources.begin(); it != m_sources.end(); ++it)
|
|
{
|
|
// release source object
|
|
delete *it;
|
|
*it = NULL;
|
|
}
|
|
// release filter object
|
|
Py_XDECREF(m_pyfilter);
|
|
m_pyfilter = NULL;
|
|
return true;
|
|
}
|
|
|
|
|
|
// get image
|
|
unsigned int * ImageBase::getImage (unsigned int texId, double ts)
|
|
{
|
|
// if image is not available
|
|
if (!m_avail)
|
|
{
|
|
// if there are any sources
|
|
if (!m_sources.empty())
|
|
{
|
|
// get images from sources
|
|
for (ImageSourceList::iterator it = m_sources.begin(); it != m_sources.end(); ++it)
|
|
// get source image
|
|
(*it)->getImage(ts);
|
|
// init image
|
|
init(m_sources[0]->getSize()[0], m_sources[0]->getSize()[1]);
|
|
}
|
|
// calculate new image
|
|
calcImage(texId, ts);
|
|
}
|
|
// if image is available, return it, otherwise NULL
|
|
return m_avail ? m_image : NULL;
|
|
}
|
|
|
|
|
|
// refresh image source
|
|
void ImageBase::refresh (void)
|
|
{
|
|
// invalidate this image
|
|
m_avail = false;
|
|
// refresh all sources
|
|
for (ImageSourceList::iterator it = m_sources.begin(); it != m_sources.end(); ++it)
|
|
(*it)->refresh();
|
|
}
|
|
|
|
|
|
// get source object
|
|
PyImage * ImageBase::getSource (const char *id)
|
|
{
|
|
// find source
|
|
ImageSourceList::iterator src = findSource(id);
|
|
// return it, if found
|
|
return src != m_sources.end() ? (*src)->getSource() : NULL;
|
|
}
|
|
|
|
|
|
// set source object
|
|
bool ImageBase::setSource (const char *id, PyImage *source)
|
|
{
|
|
// find source
|
|
ImageSourceList::iterator src = findSource(id);
|
|
// check source loop
|
|
if (source != NULL && source->m_image->loopDetect(this))
|
|
return false;
|
|
// if found, set new object
|
|
if (src != m_sources.end())
|
|
// if new object is not empty or sources are static
|
|
if (source != NULL || m_staticSources)
|
|
// replace previous source
|
|
(*src)->setSource(source);
|
|
// otherwise delete source
|
|
else
|
|
m_sources.erase(src);
|
|
// if source is not found and adding is allowed
|
|
else
|
|
if (!m_staticSources)
|
|
{
|
|
// create new source
|
|
ImageSource * newSrc = newSource(id);
|
|
newSrc->setSource(source);
|
|
// if source was created, add it to source list
|
|
if (newSrc != NULL) m_sources.push_back(newSrc);
|
|
}
|
|
// otherwise source wasn't set
|
|
else
|
|
return false;
|
|
// source was set
|
|
return true;
|
|
}
|
|
|
|
|
|
// set pixel filter
|
|
void ImageBase::setFilter (PyFilter * filt)
|
|
{
|
|
// reference new filter
|
|
if (filt != NULL) Py_INCREF(filt);
|
|
// release previous filter
|
|
Py_XDECREF(m_pyfilter);
|
|
// set new filter
|
|
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)
|
|
{
|
|
// if image has to be scaled
|
|
if (m_scale)
|
|
{
|
|
// recalc sizes of image
|
|
width = calcSize(width);
|
|
height = calcSize(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
|
|
if (newSize > m_imgSize)
|
|
{
|
|
// set new buffer size
|
|
m_imgSize = newSize;
|
|
// release previous and create new buffer
|
|
if (m_image)
|
|
delete [] m_image;
|
|
m_image = new unsigned int[m_imgSize];
|
|
}
|
|
// new image size
|
|
m_size[0] = width;
|
|
m_size[1] = height;
|
|
// scale was processed
|
|
m_scaleChange = false;
|
|
}
|
|
}
|
|
|
|
|
|
// find source
|
|
ImageSourceList::iterator ImageBase::findSource (const char *id)
|
|
{
|
|
// iterate sources
|
|
ImageSourceList::iterator it;
|
|
for (it = m_sources.begin(); it != m_sources.end(); ++it)
|
|
// if id matches, return iterator
|
|
if ((*it)->is(id)) return it;
|
|
// source not found
|
|
return it;
|
|
}
|
|
|
|
|
|
// check sources sizes
|
|
bool ImageBase::checkSourceSizes (void)
|
|
{
|
|
// reference size
|
|
short * refSize = NULL;
|
|
// iterate sources
|
|
for (ImageSourceList::iterator it = m_sources.begin(); it != m_sources.end(); ++it)
|
|
{
|
|
// get size of current source
|
|
short * curSize = (*it)->getSize();
|
|
// if size is available and is not empty
|
|
if (curSize[0] != 0 && curSize[1] != 0) {
|
|
// if reference size is not set
|
|
if (refSize == NULL) {
|
|
// set current size as reference
|
|
refSize = curSize;
|
|
// otherwise check with current size
|
|
}
|
|
else if (curSize[0] != refSize[0] || curSize[1] != refSize[1]) {
|
|
// if they don't match, report it
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
// all sizes match
|
|
return true;
|
|
}
|
|
|
|
|
|
// compute nearest power of 2 value
|
|
short ImageBase::calcSize (short size)
|
|
{
|
|
// while there is more than 1 bit in size value
|
|
while ((size & (size - 1)) != 0)
|
|
// clear last bit
|
|
size = size & (size - 1);
|
|
// return result
|
|
return size;
|
|
}
|
|
|
|
|
|
// perform loop detection
|
|
bool ImageBase::loopDetect (ImageBase * img)
|
|
{
|
|
// if this object is the same as parameter, loop is detected
|
|
if (this == img) return true;
|
|
// check all sources
|
|
for (ImageSourceList::iterator it = m_sources.begin(); it != m_sources.end(); ++it)
|
|
// if source detected loop, return this result
|
|
if ((*it)->getSource() != NULL && (*it)->getSource()->m_image->loopDetect(img))
|
|
return true;
|
|
// no loop detected
|
|
return false;
|
|
}
|
|
|
|
|
|
// ImageSource class implementation
|
|
|
|
// constructor
|
|
ImageSource::ImageSource (const char *id) : m_source(NULL), m_image(NULL)
|
|
{
|
|
// copy id
|
|
int idx;
|
|
for (idx = 0; id[idx] != '\0' && idx < SourceIdSize - 1; ++idx)
|
|
m_id[idx] = id[idx];
|
|
m_id[idx] = '\0';
|
|
}
|
|
|
|
// destructor
|
|
ImageSource::~ImageSource (void)
|
|
{
|
|
// release source
|
|
setSource(NULL);
|
|
}
|
|
|
|
|
|
// compare id
|
|
bool ImageSource::is (const char *id)
|
|
{
|
|
for (char *myId = m_id; *myId != '\0'; ++myId, ++id)
|
|
if (*myId != *id) return false;
|
|
return *id == '\0';
|
|
}
|
|
|
|
|
|
// set source object
|
|
void ImageSource::setSource (PyImage *source)
|
|
{
|
|
// reference new source
|
|
if (source != NULL) Py_INCREF(source);
|
|
// release previous source
|
|
Py_XDECREF(m_source);
|
|
// set new source
|
|
m_source = source;
|
|
}
|
|
|
|
|
|
// get image from source
|
|
unsigned int * ImageSource::getImage (double ts)
|
|
{
|
|
// if source is available
|
|
if (m_source != NULL)
|
|
// get image from source
|
|
m_image = m_source->m_image->getImage(0, ts);
|
|
// otherwise reset buffer
|
|
else
|
|
m_image = NULL;
|
|
// return image
|
|
return m_image;
|
|
}
|
|
|
|
|
|
// refresh source
|
|
void ImageSource::refresh (void)
|
|
{
|
|
// if source is available, refresh it
|
|
if (m_source != NULL) m_source->m_image->refresh();
|
|
}
|
|
|
|
|
|
|
|
// list of image types
|
|
PyTypeList pyImageTypes;
|
|
|
|
|
|
|
|
// functions for python interface
|
|
|
|
// object allocation
|
|
PyObject *Image_allocNew(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
|
{
|
|
// allocate object
|
|
PyImage *self = reinterpret_cast<PyImage*>(type->tp_alloc(type, 0));
|
|
// initialize object structure
|
|
self->m_image = NULL;
|
|
// return allocated object
|
|
return reinterpret_cast<PyObject*>(self);
|
|
}
|
|
|
|
// object deallocation
|
|
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;
|
|
self->m_image = NULL;
|
|
}
|
|
}
|
|
|
|
// get image data
|
|
PyObject *Image_getImage(PyImage *self, char *mode)
|
|
{
|
|
try
|
|
{
|
|
unsigned int * image = self->m_image->getImage();
|
|
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 if (!strcasecmp(mode, "F"))
|
|
{
|
|
// this mode returns the image as an array of float.
|
|
// This makes sense ONLY for the depth buffer:
|
|
// source = VideoTexture.ImageViewport()
|
|
// source.depth = True
|
|
// depth = VideoTexture.imageToArray(source, 'F')
|
|
|
|
// adapt dimension from byte to float
|
|
dimensions /= sizeof(float);
|
|
buffer = BGL_MakeBuffer( GL_FLOAT, 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;
|
|
}
|
|
|
|
// get image size
|
|
PyObject *Image_getSize (PyImage *self, void *closure)
|
|
{
|
|
return Py_BuildValue("(hh)", self->m_image->getSize()[0],
|
|
self->m_image->getSize()[1]);
|
|
}
|
|
|
|
// refresh image
|
|
PyObject *Image_refresh (PyImage *self)
|
|
{
|
|
self->m_image->refresh();
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
// get scale
|
|
PyObject *Image_getScale (PyImage *self, void *closure)
|
|
{
|
|
if (self->m_image != NULL && self->m_image->getScale()) Py_RETURN_TRUE;
|
|
else Py_RETURN_FALSE;
|
|
}
|
|
|
|
// set scale
|
|
int Image_setScale(PyImage *self, PyObject *value, void *closure)
|
|
{
|
|
// check parameter, report failure
|
|
if (value == NULL || !PyBool_Check(value))
|
|
{
|
|
PyErr_SetString(PyExc_TypeError, "The value must be a bool");
|
|
return -1;
|
|
}
|
|
// set scale
|
|
if (self->m_image != NULL) self->m_image->setScale(value == Py_True);
|
|
// success
|
|
return 0;
|
|
}
|
|
|
|
// get flip
|
|
PyObject *Image_getFlip (PyImage *self, void *closure)
|
|
{
|
|
if (self->m_image != NULL && self->m_image->getFlip()) Py_RETURN_TRUE;
|
|
else Py_RETURN_FALSE;
|
|
}
|
|
|
|
// set flip
|
|
int Image_setFlip(PyImage *self, PyObject *value, void *closure)
|
|
{
|
|
// check parameter, report failure
|
|
if (value == NULL || !PyBool_Check(value))
|
|
{
|
|
PyErr_SetString(PyExc_TypeError, "The value must be a bool");
|
|
return -1;
|
|
}
|
|
// set scale
|
|
if (self->m_image != NULL) self->m_image->setFlip(value == Py_True);
|
|
// success
|
|
return 0;
|
|
}
|
|
|
|
// get zbuff
|
|
PyObject *Image_getZbuff(PyImage *self, void *closure)
|
|
{
|
|
if (self->m_image != NULL && self->m_image->getZbuff()) Py_RETURN_TRUE;
|
|
else Py_RETURN_FALSE;
|
|
}
|
|
|
|
// set zbuff
|
|
int Image_setZbuff(PyImage *self, PyObject *value, void *closure)
|
|
{
|
|
// check parameter, report failure
|
|
if (value == NULL || !PyBool_Check(value))
|
|
{
|
|
PyErr_SetString(PyExc_TypeError, "The value must be a bool");
|
|
return -1;
|
|
}
|
|
// set scale
|
|
if (self->m_image != NULL) self->m_image->setZbuff(value == Py_True);
|
|
// success
|
|
return 0;
|
|
}
|
|
|
|
// get depth
|
|
PyObject *Image_getDepth(PyImage *self, void *closure)
|
|
{
|
|
if (self->m_image != NULL && self->m_image->getDepth()) Py_RETURN_TRUE;
|
|
else Py_RETURN_FALSE;
|
|
}
|
|
|
|
// set depth
|
|
int Image_setDepth(PyImage *self, PyObject *value, void *closure)
|
|
{
|
|
// check parameter, report failure
|
|
if (value == NULL || !PyBool_Check(value))
|
|
{
|
|
PyErr_SetString(PyExc_TypeError, "The value must be a bool");
|
|
return -1;
|
|
}
|
|
// set scale
|
|
if (self->m_image != NULL) self->m_image->setDepth(value == Py_True);
|
|
// success
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
// get filter source object
|
|
PyObject *Image_getSource(PyImage *self, PyObject *args)
|
|
{
|
|
// get arguments
|
|
char *id;
|
|
if (!PyArg_ParseTuple(args, "s:getSource", &id))
|
|
return NULL;
|
|
if (self->m_image != NULL)
|
|
{
|
|
// get source object
|
|
PyObject *src = reinterpret_cast<PyObject*>(self->m_image->getSource(id));
|
|
// if source is available
|
|
if (src != NULL)
|
|
{
|
|
// return source
|
|
Py_INCREF(src);
|
|
return src;
|
|
}
|
|
}
|
|
// source was not found
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
|
|
// set filter source object
|
|
PyObject *Image_setSource(PyImage *self, PyObject *args)
|
|
{
|
|
// get arguments
|
|
char *id;
|
|
PyObject *obj;
|
|
if (!PyArg_ParseTuple(args, "sO:setSource", &id, &obj))
|
|
return NULL;
|
|
if (self->m_image != NULL)
|
|
{
|
|
// check type of object
|
|
if (pyImageTypes.in(Py_TYPE(obj)))
|
|
{
|
|
// convert to image struct
|
|
PyImage * img = reinterpret_cast<PyImage*>(obj);
|
|
// set source
|
|
if (!self->m_image->setSource(id, img))
|
|
{
|
|
// if not set, retport error
|
|
PyErr_SetString(PyExc_RuntimeError, "Invalid source or id");
|
|
return NULL;
|
|
}
|
|
}
|
|
// else report error
|
|
else
|
|
{
|
|
PyErr_SetString(PyExc_RuntimeError, "Invalid type of object");
|
|
return NULL;
|
|
}
|
|
}
|
|
// return none
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
|
|
// get pixel filter object
|
|
PyObject *Image_getFilter(PyImage *self, void *closure)
|
|
{
|
|
// if image object is available
|
|
if (self->m_image != NULL)
|
|
{
|
|
// pixel filter object
|
|
PyObject *filt = reinterpret_cast<PyObject*>(self->m_image->getFilter());
|
|
// if filter is present
|
|
if (filt != NULL)
|
|
{
|
|
// return it
|
|
Py_INCREF(filt);
|
|
return filt;
|
|
}
|
|
}
|
|
// otherwise return none
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
|
|
// set pixel filter object
|
|
int Image_setFilter(PyImage *self, PyObject *value, void *closure)
|
|
{
|
|
// if image object is available
|
|
if (self->m_image != NULL)
|
|
{
|
|
// check new value
|
|
if (value == NULL || !pyFilterTypes.in(Py_TYPE(value)))
|
|
{
|
|
// report value error
|
|
PyErr_SetString(PyExc_TypeError, "Invalid type of value");
|
|
return -1;
|
|
}
|
|
// set new value
|
|
self->m_image->setFilter(reinterpret_cast<PyFilter*>(value));
|
|
}
|
|
// return success
|
|
return 0;
|
|
}
|
|
PyObject *Image_valid(PyImage *self, void *closure)
|
|
{
|
|
if (self->m_image->isImageAvailable())
|
|
{
|
|
Py_RETURN_TRUE;
|
|
}
|
|
else
|
|
{
|
|
Py_RETURN_FALSE;
|
|
}
|
|
}
|
|
|
|
static 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 = (char *)"";
|
|
ret = PyBuffer_FillInfo(view, (PyObject *)self, buf, 0, 0, flags);
|
|
if (ret >= 0)
|
|
self->m_image->m_exports++;
|
|
return ret;
|
|
|
|
}
|
|
|
|
static void Image_releaseBuffer(PyImage *self, Py_buffer *buffer)
|
|
{
|
|
self->m_image->m_exports--;
|
|
}
|
|
|
|
PyBufferProcs imageBufferProcs =
|
|
{
|
|
(getbufferproc)Image_getbuffer,
|
|
(releasebufferproc)Image_releaseBuffer
|
|
};
|
|
|