/* * ***** 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 #include #include "EXP_PyObjectPlus.h" #include #include "FilterBase.h" #include "Exception.h" #if (defined(WIN32) || defined(WIN64)) #define strcasecmp _stricmp #endif // ImageBase class implementation ExceptionID ImageHasExports; ExceptionID InvalidColorChannel; ExceptionID InvalidImageMode; 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"); ExpDesc InvalidImageModeDesc(InvalidImageMode, "Invalid image mode, only RGBA and BGRA are supported"); // 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; } bool ImageBase::loadImage(unsigned int *buffer, unsigned int size, unsigned int format, double ts) { unsigned int *d, *s, v, len; if (getImage(0, ts) != NULL && size >= getBuffSize()) { switch (format) { case GL_RGBA: memcpy(buffer, m_image, getBuffSize()); break; case GL_BGRA: len = (unsigned int)m_size[0] * m_size[1]; for (s=m_image, d=buffer; len; len--) { v = *s++; *d++ = VT_SWAPBR(v); } break; default: THRWEXCP(InvalidImageMode,S_OK); } return true; } return false; } // 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; } void ImageBase::swapImageBR() { unsigned int size, v, *s; if (m_avail) { size = 1 * m_size[0] * m_size[1]; for (s=m_image; size; size--) { v = *s; *s++ = VT_SWAPBR(v); } } } // 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(type->tp_alloc(type, 0)); // initialize object structure self->m_image = NULL; // return allocated object return reinterpret_cast(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; cm_image->getSize()[0], self->m_image->getSize()[1]); } // refresh image PyObject *Image_refresh (PyImage *self, PyObject *args) { Py_buffer buffer; bool done = true; char *mode = NULL; double ts = -1.0; unsigned int format; memset(&buffer, 0, sizeof(buffer)); if (PyArg_ParseTuple(args, "|s*sd:refresh", &buffer, &mode, &ts)) { if (buffer.buf) { // a target buffer is provided, verify its format if (buffer.readonly) { PyErr_SetString(PyExc_TypeError, "Buffers passed in argument must be writable"); } else if (!PyBuffer_IsContiguous(&buffer, 'C')) { PyErr_SetString(PyExc_TypeError, "Buffers passed in argument must be contiguous in memory"); } else if (((intptr_t)buffer.buf & 3) != 0) { PyErr_SetString(PyExc_TypeError, "Buffers passed in argument must be aligned to 4 bytes boundary"); } else { // ready to get the image into our buffer try { if (mode == NULL || !strcmp(mode, "RGBA")) format = GL_RGBA; else if (!strcmp(mode, "BGRA")) format = GL_BGRA; else THRWEXCP(InvalidImageMode,S_OK); done = self->m_image->loadImage((unsigned int *)buffer.buf, buffer.len, format, ts); } catch (Exception & exp) { exp.report(); } } PyBuffer_Release(&buffer); if (PyErr_Occurred()) { return NULL; } } } else { return NULL; } self->m_image->refresh(); if (done) Py_RETURN_TRUE; Py_RETURN_FALSE; } // 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(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(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(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(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) { exp.report(); return -1; } if (!image) { PyErr_SetString(PyExc_BufferError, "Image buffer is not available"); return -1; } 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; } static void Image_releaseBuffer(PyImage *self, Py_buffer *buffer) { self->m_image->m_exports--; } PyBufferProcs imageBufferProcs = { (getbufferproc)Image_getbuffer, (releasebufferproc)Image_releaseBuffer };