blender/source/gameengine/VideoTexture/ImageViewport.cpp
Benoit Bolsee 6a51ba54cd VideoTexture: new ImageRender class for Render To Texture
The new class VideoTexture.ImageRender() is available to perform
render to texture in the GE.

Constructor:

  VideoTexture.ImageRender(scene,cam)
    cam  : camera object that will be used for the render.
           It must be an inactive camera.
    scene: reference to the scene that will be rendered.
           The camera must be part of that scene.
  Returns an object that can be used as a source of a VideoTexture.Texture object

Methods: none

Attributes:

  background: 
     4-tuple representing the background color of the rendering
     as RGBA color components, each component being an integer 
     between 0 and 255. 
     Default value = [0,0,255,255] (=saturated blue)
     Note: athough the alpha component can be specified, it is not
           supported at the moment, the alpha channel of the rendered
           texture will always be 255. You can however introduce an
           alpha channel by appending a FilterBlueScreen() filter, it
           will set the alpha to 0 (transparent) on all pixels that were
           not rendered.

  capsize:
     2-tuple representing the size of the render area as [x,y] number of pixels.
     Default value = largest rectangle with power of 2 dimensions that fits in the canvas 
     You may want to reduce the render area to increase performance. For example,
     a render area of [256,128] is probably sufficient to implement a car inner mirror.
     For best performance, use power of 2 dimensions and don't set any filter: this
     allows direct transfer between the GPU frame buffer and texture memory
     without going through the host.

  alpha: 
     Boolean indicating if the render alpha channel should be copied to the texture.
     Default value: False
     Experimental, do not use.

  whole:
     Boolean indicating if the entire canvas should be used for the rendering. 
     Default value: False
     Note: There is no reason to set this attribute to True: the rendering will
           in any case be scaled down to the largest rectangle with power of 2
           dimensions before transfering to the texture.

Attributes inherited from the ImageBase class:

  image : image binary data, read-only
  size  : [x,y] size of the texture, read-only
  scale : set to True for fast scale down in case the render area dimensions are not power of 2
  flip  : set to True for vertical flip. 
  filter: set a post-processing filter on the render.

Notes:

* Aspect Ratio
For consistent results in Blender and Blenderplayer, the same aspect ratio used
by Blender to draw the camera viewport (Scene(F10)->Format tab->Size X/Size Y) 
is also used during the rendering. You can control the portion of the scene that
will be rendered by "looking through the camera": the zone inside the outer dotted 
rectangle will be rendered to the texture.
In order to reproduce the scene without X/Y distortion, you must apply the texture
on an object or portion of object that has the same aspect ratio.

* Order of rendering
The rendereing is performed when you call the refresh() method of the parent 
Texture object. This happens outside the normal frame rendering and will have no 
effect on it.
However, if you want to use ImageViewport and ImageRender at the same time, be 
sure to refresh the viewport texture before the render texture because the latter
will destroy the frame buffer that is used by the former to update the texture.

* Scene status
The meshes are not updated during the render to texture: the rendered texture
is one frame late to the rendered frame with regards to mesh deformation.

* Example:

  cont = GameLogic.getCurrentController()
  # object that receives the texture
  obj = contr.getOwner()
  scene = GameLogic.getCurrentScene()
  # camera used for the render
  tvcam = scene.getObjectList()['OBtvcam']
  # assume obj has some faces UV assigned to tv.png
  matID = VideoTexture.materialID(obj, 'IMtv.png')
  GameLogic.tv = VideoTexture.Texture(obj, matID)
  GameLogic.tv.source = VideoTexture.ImageRender(scene,tvcam)
  GameLogic.tv.source.capsize = [256,256]
  # to render the texture, just call GameLogic.tv.refresh(True) on each frame.

You can download a demo game (with a video file) here:

  http://home.scarlet.be/~tsi46445/blender/VideoTextureDemo.zip

For those who have already downloaded the demo, you can just update the blend file:

  http://home.scarlet.be/~tsi46445/blender/VideoTextureDemo.blend
2008-11-26 17:47:42 +00:00

332 lines
10 KiB
C++

/* $Id$
-----------------------------------------------------------------------------
This source file is part of VideoTexture library
Copyright (c) 2007 The Zdeno Ash Miklas
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA, or go to
http://www.gnu.org/copyleft/lesser.txt.
-----------------------------------------------------------------------------
*/
// implementation
#include <PyObjectPlus.h>
#include <structmember.h>
#include <BIF_gl.h>
#include "Texture.h"
#include "ImageBase.h"
#include "FilterSource.h"
#include "ImageViewport.h"
// constructor
ImageViewport::ImageViewport (void) : m_alpha(false), m_texInit(false)
{
// get viewport rectangle
glGetIntegerv(GL_VIEWPORT, m_viewport);
// create buffer for viewport image
m_viewportImage = new BYTE [4 * getViewportSize()[0] * getViewportSize()[1]];
// set attributes
setWhole(false);
}
// destructor
ImageViewport::~ImageViewport (void)
{ delete m_viewportImage; }
// use whole viewport to capture image
void ImageViewport::setWhole (bool whole)
{
// set whole
m_whole = whole;
// set capture size to viewport size, if whole,
// otherwise place area in the middle of viewport
for (int idx = 0; idx < 2; ++idx)
{
// capture size
m_capSize[idx] = whole ? short(getViewportSize()[idx])
: calcSize(short(getViewportSize()[idx]));
// position
m_position[idx] = whole ? 0 : ((getViewportSize()[idx] - m_capSize[idx]) >> 1);
}
// init image
init(m_capSize[0], m_capSize[1]);
// set capture position
setPosition();
}
void ImageViewport::setCaptureSize (short * size)
{
m_whole = false;
if (size == NULL)
size = m_capSize;
for (int idx = 0; idx < 2; ++idx)
{
if (size[idx] < 1)
m_capSize[idx] = 1;
else if (size[idx] > getViewportSize()[idx])
m_capSize[idx] = short(getViewportSize()[idx]);
else
m_capSize[idx] = size[idx];
}
init(m_capSize[0], m_capSize[1]);
// set capture position
setPosition();
}
// set position of capture rectangle
void ImageViewport::setPosition (GLint * pos)
{
// if new position is not provided, use existing position
if (pos == NULL) pos = m_position;
// save position
for (int idx = 0; idx < 2; ++idx)
m_position[idx] = pos[idx] < 0 ? 0 : pos[idx] >= getViewportSize()[idx]
- m_capSize[idx] ? getViewportSize()[idx] - m_capSize[idx] : pos[idx];
// recalc up left corner
for (int idx = 0; idx < 2; ++idx)
m_upLeft[idx] = m_position[idx] + m_viewport[idx];
}
// capture image from viewport
void ImageViewport::calcImage (unsigned int texId)
{
// if scale was changed
if (m_scaleChange)
// reset image
init(m_capSize[0], m_capSize[1]);
// if texture wasn't initialized
if (!m_texInit)
{
// initialize it
loadTexture(texId, m_image, m_size);
m_texInit = true;
}
// if texture can be directly created
if (texId != 0 && m_pyfilter == NULL && m_capSize[0] == calcSize(m_capSize[0])
&& m_capSize[1] == calcSize(m_capSize[1]) && !m_flip)
{
// just copy current viewport to texture
glBindTexture(GL_TEXTURE_2D, texId);
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_upLeft[0], m_upLeft[1], (GLsizei)m_capSize[0], (GLsizei)m_capSize[1]);
// image is not available
m_avail = false;
}
// otherwise copy viewport to buffer, if image is not available
else if (!m_avail)
{
// get frame buffer data
if (m_alpha)
{
glReadPixels(m_upLeft[0], m_upLeft[1], (GLsizei)m_capSize[0], (GLsizei)m_capSize[1], GL_RGBA,
GL_UNSIGNED_BYTE, m_viewportImage);
// filter loaded data
FilterRGBA32 filt;
filterImage(filt, m_viewportImage, m_capSize);
}
else
{
glReadPixels(m_upLeft[0], m_upLeft[1], (GLsizei)m_capSize[0], (GLsizei)m_capSize[1], GL_RGB,
GL_UNSIGNED_BYTE, m_viewportImage);
// filter loaded data
FilterRGB24 filt;
filterImage(filt, m_viewportImage, m_capSize);
}
}
}
// cast Image pointer to ImageViewport
inline ImageViewport * getImageViewport (PyImage * self)
{ return static_cast<ImageViewport*>(self->m_image); }
// python methods
// get whole
PyObject * ImageViewport_getWhole (PyImage * self, void * closure)
{
if (self->m_image != NULL && getImageViewport(self)->getWhole()) Py_RETURN_TRUE;
else Py_RETURN_FALSE;
}
// set whole
int ImageViewport_setWhole (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 whole
if (self->m_image != NULL) getImageViewport(self)->setWhole(value == Py_True);
// success
return 0;
}
// get alpha
PyObject * ImageViewport_getAlpha (PyImage * self, void * closure)
{
if (self->m_image != NULL && getImageViewport(self)->getAlpha()) Py_RETURN_TRUE;
else Py_RETURN_FALSE;
}
// set whole
int ImageViewport_setAlpha (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 alpha
if (self->m_image != NULL) getImageViewport(self)->setAlpha(value == Py_True);
// success
return 0;
}
// get position
static PyObject * ImageViewport_getPosition (PyImage * self, void * closure)
{
return Py_BuildValue("(ii)", getImageViewport(self)->getPosition()[0],
getImageViewport(self)->getPosition()[1]);
}
// set position
static int ImageViewport_setPosition (PyImage * self, PyObject * value, void * closure)
{
// check validity of parameter
if (value == NULL || !PySequence_Check(value) || PySequence_Length(value) != 2
|| !PyInt_Check(PySequence_Fast_GET_ITEM(value, 0))
|| !PyInt_Check(PySequence_Fast_GET_ITEM(value, 1)))
{
PyErr_SetString(PyExc_TypeError, "The value must be a sequence of 2 ints");
return -1;
}
// set position
GLint pos [] = {
GLint(PyInt_AsLong(PySequence_Fast_GET_ITEM(value, 0))),
GLint(PyInt_AsLong(PySequence_Fast_GET_ITEM(value, 1)))
};
getImageViewport(self)->setPosition(pos);
// success
return 0;
}
// get capture size
PyObject * ImageViewport_getCaptureSize (PyImage * self, void * closure)
{
return Py_BuildValue("(ii)", getImageViewport(self)->getCaptureSize()[0],
getImageViewport(self)->getCaptureSize()[1]);
}
// set capture size
int ImageViewport_setCaptureSize (PyImage * self, PyObject * value, void * closure)
{
// check validity of parameter
if (value == NULL || !PySequence_Check(value) || PySequence_Length(value) != 2
|| !PyInt_Check(PySequence_Fast_GET_ITEM(value, 0))
|| !PyInt_Check(PySequence_Fast_GET_ITEM(value, 1)))
{
PyErr_SetString(PyExc_TypeError, "The value must be a sequence of 2 ints");
return -1;
}
// set capture size
short size [] = {
short(PyInt_AsLong(PySequence_Fast_GET_ITEM(value, 0))),
short(PyInt_AsLong(PySequence_Fast_GET_ITEM(value, 1)))
};
getImageViewport(self)->setCaptureSize(size);
// success
return 0;
}
// methods structure
static PyMethodDef imageViewportMethods[] =
{ // methods from ImageBase class
{"refresh", (PyCFunction)Image_refresh, METH_NOARGS, "Refresh image - invalidate its current content"},
{NULL}
};
// attributes structure
static PyGetSetDef imageViewportGetSets[] =
{
{(char*)"whole", (getter)ImageViewport_getWhole, (setter)ImageViewport_setWhole, (char*)"use whole viewport to capture", NULL},
{(char*)"position", (getter)ImageViewport_getPosition, (setter)ImageViewport_setPosition, (char*)"upper left corner of captured area", NULL},
{(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*)"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},
{(char*)"flip", (getter)Image_getFlip, (setter)Image_setFlip, (char*)"flip image vertically", NULL},
{(char*)"filter", (getter)Image_getFilter, (setter)Image_setFilter, (char*)"pixel filter", NULL},
{NULL}
};
// define python type
PyTypeObject ImageViewportType =
{
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"VideoTexture.ImageViewport", /*tp_name*/
sizeof(PyImage), /*tp_basicsize*/
0, /*tp_itemsize*/
(destructor)Image_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash */
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
"Image source from viewport", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
imageViewportMethods, /* tp_methods */
0, /* tp_members */
imageViewportGetSets, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)Image_init<ImageViewport>, /* tp_init */
0, /* tp_alloc */
Image_allocNew, /* tp_new */
};