mirror of https://github.com/H-uru/korman.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
610 lines
24 KiB
610 lines
24 KiB
/* This file is part of Korman. |
|
* |
|
* Korman 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 3 of the License, or |
|
* (at your option) any later version. |
|
* |
|
* Korman 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 Korman. If not, see <http://www.gnu.org/licenses/>. |
|
*/ |
|
|
|
#include "texture.h" |
|
#include "PyHSPlasma_private.h" |
|
|
|
#ifdef _WIN32 |
|
# define WIN32_LEAN_AND_MEAN |
|
# define NOMINMAX |
|
# include <windows.h> |
|
#endif // _WIN32 |
|
|
|
#include <cmath> |
|
#include <GL/gl.h> |
|
#include <PRP/Surface/plMipmap.h> |
|
|
|
#define TEXTARGET_TEXTURE_2D 0 |
|
|
|
// =============================================================================================== |
|
|
|
static inline void _ensure_copy_bytes(PyObject* parent, PyObject*& data) { |
|
// PyBytes objects are immutable and ought not to be changed once they are returned to Python |
|
// code. Therefore, this tests to see if the given bytes object is the same as one we're holding. |
|
// If so, a new copy is constructed seamlessly. |
|
if (parent == data) { |
|
Py_ssize_t size; |
|
char* buf; |
|
PyBytes_AsStringAndSize(parent, &buf, &size); |
|
data = PyBytes_FromStringAndSize(buf, size); |
|
Py_DECREF(parent); |
|
} |
|
} |
|
|
|
template<typename T> |
|
static T _ensure_power_of_two(T value) { |
|
return static_cast<T>(std::pow(2, std::floor(std::log2(value)))); |
|
} |
|
|
|
static void _flip_image(size_t width, size_t dataSize, uint8_t* data) { |
|
// OpenGL returns a flipped image, so we must reflip it. |
|
size_t row_stride = width * 4; |
|
uint8_t* sptr = data; |
|
uint8_t* eptr = data + (dataSize - row_stride); |
|
uint8_t* temp = new uint8_t[row_stride]; |
|
do { |
|
memcpy(temp, sptr, row_stride); |
|
memcpy(sptr, eptr, row_stride); |
|
memcpy(eptr, temp, row_stride); |
|
} while ((sptr += row_stride) < (eptr -= row_stride)); |
|
delete[] temp; |
|
} |
|
|
|
static inline bool _get_float(PyObject* source, const char* attr, float& result) { |
|
if (source) { |
|
PyObjectRef pyfloat = PyObject_GetAttrString(source, attr); |
|
if (pyfloat) { |
|
result = (float)PyFloat_AsDouble(pyfloat); |
|
return PyErr_Occurred() == NULL; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
static inline int _get_num_levels(size_t width, size_t height) { |
|
int num_levels = (int)std::floor(std::log2(std::max((float)width, (float)height))) + 1; |
|
|
|
// Major Workaround Ahoy |
|
// There is a bug in Cyan's level size algorithm that causes it to not allocate enough memory |
|
// for the color block in certain mipmaps. I personally have encountered an access violation on |
|
// 1x1 DXT5 mip levels -- the code only allocates an alpha block and not a color block. Paradox |
|
// reports that if any dimension is smaller than 4px in a mip level, OpenGL doesn't like Cyan generated |
|
// data. So, we're going to lop off the last two mip levels, which should be 1px and 2px as the smallest. |
|
// This bug is basically unfixable without crazy hacks because of the way Plasma reads in texture data. |
|
// "<Deledrius> I feel like any texture at a 1x1 level is essentially academic. I mean, JPEG/DXT |
|
// doesn't even compress that, and what is it? Just the average color of the whole |
|
// texture in a single pixel?" |
|
// :) |
|
return std::max(num_levels - 2, 2); |
|
} |
|
|
|
static void _scale_image(const uint8_t* srcBuf, const size_t srcW, const size_t srcH, |
|
uint8_t* dstBuf, const size_t dstW, const size_t dstH) { |
|
float scaleX = static_cast<float>(srcW) / static_cast<float>(dstW); |
|
float scaleY = static_cast<float>(srcH) / static_cast<float>(dstH); |
|
float filterW = std::max(scaleX, 1.f); |
|
float filterH = std::max(scaleY, 1.f); |
|
size_t srcRowspan = srcW * sizeof(uint32_t); |
|
size_t dstIdx = 0; |
|
|
|
for (size_t dstY = 0; dstY < dstH; ++dstY) { |
|
float srcY = dstY * scaleY; |
|
ssize_t srcY_start = std::max(static_cast<ssize_t>(srcY - filterH), |
|
static_cast<ssize_t>(0)); |
|
ssize_t srcY_end = std::min(static_cast<ssize_t>(srcY + filterH), |
|
static_cast<ssize_t>(srcH - 1)); |
|
|
|
float weightsY[16]; |
|
for (ssize_t i = srcY_start; i <= srcY_end && i - srcY_start < arrsize(weightsY); ++i) |
|
weightsY[i - srcY_start] = 1.f - std::abs((i - srcY) / filterH); |
|
|
|
for (size_t dstX = 0; dstX < dstW; ++dstX) { |
|
float srcX = dstX * scaleX; |
|
ssize_t srcX_start = std::max(static_cast<ssize_t>(srcX - filterW), |
|
static_cast<ssize_t>(0)); |
|
ssize_t srcX_end = std::min(static_cast<ssize_t>(srcX + filterW), |
|
static_cast<ssize_t>(srcW - 1)); |
|
|
|
float weightsX[16]; |
|
for (ssize_t i = srcX_start; i <= srcX_end && i - srcX_start < arrsize(weightsX); ++i) |
|
weightsX[i - srcX_start] = 1.f - std::abs((i - srcX) / filterW); |
|
|
|
float accum_color[] = { 0.f, 0.f, 0.f, 0.f }; |
|
float weight_total = 0.f; |
|
for (size_t i = srcY_start; i <= srcY_end; ++i) { |
|
float weightY; |
|
if (i - srcY_start < arrsize(weightsY)) |
|
weightY = weightsY[i - srcY_start]; |
|
else |
|
weightY = 1.f - std::abs((i - srcY) / filterH); |
|
|
|
if (weightY <= 0.f) |
|
continue; |
|
|
|
size_t srcIdx = ((i * srcRowspan) + (srcX_start * sizeof(uint32_t))); |
|
for (size_t j = srcX_start; j <= srcX_end; ++j, srcIdx += sizeof(uint32_t)) { |
|
float weightX; |
|
if (j - srcX_start < arrsize(weightsX)) |
|
weightX = weightsX[j - srcX_start]; |
|
else |
|
weightX = 1.f - std::abs((j - srcX) / filterW); |
|
float weight = weightX * weightY; |
|
|
|
if (weight > 0.f) { |
|
for (size_t k = 0; k < sizeof(uint32_t); ++k) |
|
accum_color[k] += (static_cast<float>(srcBuf[srcIdx+k]) / 255.f) * weight; |
|
weight_total += weight; |
|
} |
|
} |
|
} |
|
|
|
for (size_t k = 0; k < sizeof(uint32_t); ++k) |
|
accum_color[k] *= 1.f / weight_total; |
|
|
|
// Whew. |
|
for (size_t k = 0; k < sizeof(uint32_t); ++k) |
|
dstBuf[dstIdx+k] = static_cast<uint8_t>(accum_color[k] * 255.f); |
|
dstIdx += sizeof(uint32_t); |
|
} |
|
} |
|
} |
|
|
|
// =============================================================================================== |
|
|
|
PyObject* scale_image(PyObject*, PyObject* args, PyObject* kwargs) { |
|
static char* kwlist[] = { _pycs("buf"), _pycs("srcW"), _pycs("srcH"), |
|
_pycs("dstW"), _pycs("dstH"), NULL }; |
|
const uint8_t* srcBuf; |
|
int srcBufSz; |
|
uint32_t srcW, srcH, dstW, dstH; |
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "y#IIII", kwlist, &srcBuf, &srcBufSz, &srcW, &srcH, &dstW, &dstH)) { |
|
PyErr_SetString(PyExc_TypeError, "scale_image expects a bytes object, int, int, int int"); |
|
return NULL; |
|
} |
|
|
|
int expectedBufSz = srcW * srcH * sizeof(uint32_t); |
|
if (srcBufSz != expectedBufSz) { |
|
PyErr_Format(PyExc_ValueError, "buf size (%i bytes) incorrect (expected: %i bytes)", srcBufSz, expectedBufSz); |
|
return NULL; |
|
} |
|
|
|
PyObject* dst = PyBytes_FromStringAndSize(NULL, dstW * dstH * sizeof(uint32_t)); |
|
uint8_t* dstBuf = reinterpret_cast<uint8_t*>(PyBytes_AS_STRING(dst)); |
|
_scale_image(srcBuf, srcW, srcH, dstBuf, dstW, dstH); |
|
return dst; |
|
} |
|
|
|
// =============================================================================================== |
|
|
|
enum { |
|
TEX_DETAIL_ALPHA = 0, |
|
TEX_DETAIL_ADD = 1, |
|
TEX_DETAIL_MULTIPLY = 2, |
|
}; |
|
|
|
enum { |
|
kOpaque = 0, |
|
kOnOff = 1, |
|
kFull = 2, |
|
}; |
|
|
|
typedef struct { |
|
PyObject_HEAD |
|
PyObject* m_blenderImage; |
|
PyObject* m_textureKey; |
|
PyObject* m_imageData; |
|
GLint m_width; |
|
GLint m_height; |
|
bool m_bgra; |
|
bool m_imageInverted; |
|
} pyGLTexture; |
|
|
|
// =============================================================================================== |
|
|
|
static void pyGLTexture_dealloc(pyGLTexture* self) { |
|
Py_CLEAR(self->m_textureKey); |
|
Py_CLEAR(self->m_blenderImage); |
|
Py_CLEAR(self->m_imageData); |
|
Py_TYPE(self)->tp_free((PyObject*)self); |
|
} |
|
|
|
static PyObject* pyGLTexture_new(PyTypeObject* type, PyObject* args, PyObject* kwds) { |
|
pyGLTexture* self = (pyGLTexture*)type->tp_alloc(type, 0); |
|
self->m_blenderImage = NULL; |
|
self->m_textureKey = NULL; |
|
self->m_imageData = NULL; |
|
self->m_width = 0; |
|
self->m_height = 0; |
|
self->m_bgra = false; |
|
self->m_imageInverted = false; |
|
return (PyObject*)self; |
|
} |
|
|
|
static int pyGLTexture___init__(pyGLTexture* self, PyObject* args, PyObject* kwds) { |
|
static char* kwlist[] = { _pycs("texkey"), _pycs("image"), _pycs("bgra"), _pycs("fast"), NULL }; |
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OObb", kwlist, &self->m_textureKey, &self->m_blenderImage, |
|
&self->m_bgra, &self->m_imageInverted)) { |
|
PyErr_SetString(PyExc_TypeError, "expected a korman.exporter.material._Texture or a bpy.types.Image"); |
|
return -1; |
|
} |
|
if (!self->m_blenderImage && !self->m_textureKey) { |
|
PyErr_SetString(PyExc_TypeError, "expected a korman.exporter.material._Texture or a bpy.types.Image"); |
|
return -1; |
|
} |
|
|
|
Py_XINCREF(self->m_blenderImage); |
|
Py_XINCREF(self->m_textureKey); |
|
if (!self->m_blenderImage) { |
|
self->m_blenderImage = PyObject_GetAttrString(self->m_textureKey, "image"); |
|
} |
|
if (!self->m_blenderImage) { |
|
PyErr_SetString(PyExc_RuntimeError, "Could not fetch Blender Image"); |
|
return -1; |
|
} |
|
|
|
// Done! |
|
return 0; |
|
} |
|
|
|
static PyObject* pyGLTexture__enter__(pyGLTexture* self) { |
|
// Is the image already loaded? |
|
PyObjectRef bindcode = PyObject_GetAttrString(self->m_blenderImage, "bindcode"); |
|
|
|
// bindcode changed to a sequence in 2.77. We want the first element for a 2D texture. |
|
// Why did we make this change, exactly? |
|
if (PySequence_Check(bindcode)) { |
|
bindcode = PySequence_GetItem(bindcode, TEXTARGET_TEXTURE_2D); |
|
} |
|
|
|
// Now we should have a GLuint... |
|
if (!PyLong_Check(bindcode)) { |
|
PyErr_SetString(PyExc_TypeError, "Image bindcode isn't a long?"); |
|
return NULL; |
|
} |
|
|
|
GLint prevImage; |
|
glGetIntegerv(GL_TEXTURE_BINDING_2D, &prevImage); |
|
GLuint image_bindcode = PyLong_AsUnsignedLong(bindcode); |
|
bool ownit = image_bindcode == 0; |
|
|
|
// Load image into GL |
|
if (ownit) { |
|
PyObjectRef new_bind = PyObject_CallMethod(self->m_blenderImage, "gl_load", NULL); |
|
if (!new_bind) |
|
return NULL; |
|
|
|
if (!PyLong_Check(new_bind)) { |
|
PyErr_SetString(PyExc_TypeError, "gl_load() did not return a long"); |
|
return NULL; |
|
} |
|
ssize_t result = PyLong_AsSize_t(new_bind); |
|
if (result != GL_NO_ERROR) { |
|
PyErr_Format(PyExc_RuntimeError, "gl_load() error: %d", result); |
|
return NULL; |
|
} |
|
bindcode = PyObject_GetAttrString(self->m_blenderImage, "bindcode"); |
|
if (PySequence_Check(bindcode)) { |
|
bindcode = PySequence_GetItem(bindcode, TEXTARGET_TEXTURE_2D); |
|
} |
|
// Now we should have a GLuint... |
|
if (!PyLong_Check(bindcode)) { |
|
PyErr_SetString(PyExc_TypeError, "Image bindcode isn't a long?"); |
|
return NULL; |
|
} |
|
image_bindcode = PyLong_AsUnsignedLong(bindcode); |
|
} |
|
|
|
// Set image as current in GL |
|
bool changedState = prevImage != image_bindcode; |
|
if (changedState) |
|
glBindTexture(GL_TEXTURE_2D, image_bindcode); |
|
|
|
// Now we can load the image data... |
|
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &self->m_width); |
|
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &self->m_height); |
|
|
|
size_t bufsz = self->m_width * self->m_height * sizeof(uint32_t); |
|
self->m_imageData = PyBytes_FromStringAndSize(NULL, bufsz); |
|
char* imbuf = PyBytes_AS_STRING(self->m_imageData); |
|
GLint fmt = self->m_bgra ? GL_BGRA_EXT : GL_RGBA; |
|
glGetTexImage(GL_TEXTURE_2D, 0, fmt, GL_UNSIGNED_BYTE, reinterpret_cast<GLvoid*>(imbuf)); |
|
|
|
// OpenGL returns image data flipped upside down. We'll flip it to be correct, if requested. |
|
if (!self->m_imageInverted) |
|
_flip_image(self->m_width, bufsz, reinterpret_cast<uint8_t*>(imbuf)); |
|
|
|
// If we had to play with ourse^H^H^H^H^Hblender's image state, let's reset it |
|
if (changedState) |
|
glBindTexture(GL_TEXTURE_2D, prevImage); |
|
if (ownit) |
|
PyObjectRef result = PyObject_CallMethod(self->m_blenderImage, "gl_free", NULL); |
|
|
|
Py_INCREF(self); |
|
return (PyObject*)self; |
|
} |
|
|
|
static PyObject* pyGLTexture__exit__(pyGLTexture* self, PyObject*) { |
|
Py_CLEAR(self->m_imageData); |
|
Py_RETURN_NONE; |
|
} |
|
|
|
static int _generate_detail_alpha(pyGLTexture* self, GLint level, float* result) { |
|
float dropoff_start, dropoff_stop, detail_max, detail_min; |
|
if (!_get_float(self->m_textureKey, "detail_fade_start", dropoff_start)) |
|
return -1; |
|
if (!_get_float(self->m_textureKey, "detail_fade_stop", dropoff_stop)) |
|
return -1; |
|
if (!_get_float(self->m_textureKey, "detail_opacity_start", detail_max)) |
|
return -1; |
|
if (!_get_float(self->m_textureKey, "detail_opacity_stop", detail_min)) |
|
return -1; |
|
|
|
dropoff_start /= 100.f; |
|
dropoff_start *= _get_num_levels(self->m_width, self->m_height); |
|
dropoff_stop /= 100.f; |
|
dropoff_stop *= _get_num_levels(self->m_width, self->m_height); |
|
detail_max /= 100.f; |
|
detail_min /= 100.f; |
|
|
|
float alpha = (level - dropoff_start) * (detail_min - detail_max) / (dropoff_stop - dropoff_start) + detail_max; |
|
if (detail_min < detail_max) |
|
*result = std::min(detail_max, std::max(detail_min, alpha)); |
|
else |
|
*result = std::min(detail_min, std::max(detail_max, alpha)); |
|
return 0; |
|
} |
|
|
|
static int _generate_detail_map(pyGLTexture* self, uint8_t* buf, size_t bufsz, GLint level) { |
|
float alpha; |
|
if (_generate_detail_alpha(self, level, &alpha) != 0) |
|
return -1; |
|
PyObjectRef pydetail_blend; |
|
if (self->m_textureKey) |
|
pydetail_blend = PyObject_GetAttrString(self->m_textureKey, "detail_blend"); |
|
if (!pydetail_blend) |
|
return -1; |
|
|
|
size_t detail_blend = PyLong_AsSize_t(pydetail_blend); |
|
switch (detail_blend) { |
|
case TEX_DETAIL_ALPHA: { |
|
for (size_t i = 0; i < bufsz; i += 4) { |
|
buf[i+3] = (uint8_t)(((float)buf[i+3]) * alpha); |
|
} |
|
} |
|
break; |
|
case TEX_DETAIL_ADD: { |
|
for (size_t i = 0; i < bufsz; i += 4) { |
|
buf[i+0] = (uint8_t)(((float)buf[i+0]) * alpha); |
|
buf[i+1] = (uint8_t)(((float)buf[i+1]) * alpha); |
|
buf[i+2] = (uint8_t)(((float)buf[i+2]) * alpha); |
|
} |
|
} |
|
break; |
|
case TEX_DETAIL_MULTIPLY: { |
|
float invert_alpha = (1.f - alpha) * 255.f; |
|
for (size_t i = 0; i < bufsz; i += 4) { |
|
buf[i+3] = (uint8_t)((invert_alpha + (float)buf[i+3]) * alpha); |
|
} |
|
} |
|
break; |
|
default: |
|
return -1; |
|
} |
|
return 0; |
|
} |
|
|
|
static PyObject* pyGLTexture_get_level_data(pyGLTexture* self, PyObject* args, PyObject* kwargs) { |
|
static char* kwlist[] = { _pycs("level"), _pycs("calc_alpha"), _pycs("report"), |
|
_pycs("indent"), _pycs("fast"), NULL }; |
|
GLint level = 0; |
|
bool calc_alpha = false; |
|
PyObject* report = nullptr; |
|
int indent = 2; |
|
bool fast = false; |
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ibOib", kwlist, &level, &calc_alpha, &report, &indent, &fast)) { |
|
PyErr_SetString(PyExc_TypeError, "get_level_data expects an optional int, bool, obejct, int, bool"); |
|
return NULL; |
|
} |
|
|
|
// We only ever want to return POT images for use in Plasma |
|
auto eWidth = _ensure_power_of_two(self->m_width) >> level; |
|
auto eHeight = _ensure_power_of_two(self->m_height) >> level; |
|
bool is_og = eWidth == self->m_width && eHeight == self->m_height; |
|
size_t bufsz = eWidth * eHeight * sizeof(uint32_t); |
|
|
|
// Print out the debug message |
|
if (report && report != Py_None) { |
|
PyObjectRef msg_func = PyObject_GetAttrString(report, "msg"); |
|
PyObjectRef args = Py_BuildValue("siii", "Level #{}: {}x{}", level, eWidth, eHeight); |
|
PyObjectRef kwargs = Py_BuildValue("{s:i}", "indent", indent); |
|
PyObjectRef result = PyObject_Call(msg_func, args, kwargs); |
|
} |
|
|
|
PyObject* data; |
|
if (is_og) { |
|
Py_INCREF(self->m_imageData); |
|
data = self->m_imageData; |
|
} else { |
|
data = PyBytes_FromStringAndSize(NULL, bufsz); |
|
uint8_t* dstBuf = reinterpret_cast<uint8_t*>(PyBytes_AsString(data)); // AS_STRING :( |
|
uint8_t* srcBuf = reinterpret_cast<uint8_t*>(PyBytes_AsString(self->m_imageData)); |
|
_scale_image(srcBuf, self->m_width, self->m_height, dstBuf, eWidth, eHeight); |
|
} |
|
|
|
// Make sure the level data is not flipped upside down... |
|
if (self->m_imageInverted && !fast) { |
|
_ensure_copy_bytes(self->m_blenderImage, data); |
|
_flip_image(eWidth, bufsz, reinterpret_cast<uint8_t*>(PyBytes_AS_STRING(data))); |
|
} |
|
|
|
// Detail blend |
|
if (self->m_textureKey) { |
|
PyObjectRef is_detail_map = PyObject_GetAttrString(self->m_textureKey, "is_detail_map"); |
|
if (PyLong_AsLong(is_detail_map) != 0) { |
|
_ensure_copy_bytes(self->m_imageData, data); |
|
uint8_t* buf = reinterpret_cast<uint8_t*>(PyBytes_AS_STRING(data)); |
|
if (_generate_detail_map(self, buf, bufsz, level) != 0) { |
|
PyErr_SetString(PyExc_RuntimeError, "error while baking detail map"); |
|
Py_DECREF(data); |
|
return NULL; |
|
} |
|
} |
|
} |
|
|
|
if (calc_alpha) { |
|
_ensure_copy_bytes(self->m_imageData, data); |
|
char* buf = PyBytes_AS_STRING(data); |
|
for (size_t i = 0; i < bufsz; i += 4) |
|
buf[i + 3] = (buf[i + 0] + buf[i + 1] + buf[i + 2]) / 3; |
|
} |
|
|
|
return data; |
|
} |
|
|
|
static PyMethodDef pyGLTexture_Methods[] = { |
|
{ _pycs("__enter__"), (PyCFunction)pyGLTexture__enter__, METH_NOARGS, NULL }, |
|
{ _pycs("__exit__"), (PyCFunction)pyGLTexture__exit__, METH_VARARGS, NULL }, |
|
|
|
{ _pycs("get_level_data"), (PyCFunction)pyGLTexture_get_level_data, METH_KEYWORDS | METH_VARARGS, NULL }, |
|
{ NULL, NULL, 0, NULL } |
|
}; |
|
|
|
static PyObject* pyGLTexture_get_has_alpha(pyGLTexture* self, void*) { |
|
char* data = PyBytes_AsString(self->m_imageData); |
|
size_t bufsz = self->m_width * self->m_height * sizeof(uint32_t); |
|
bool transparency = false; |
|
|
|
uint32_t* datap = reinterpret_cast<uint32_t*>(data); |
|
uint32_t* endp = reinterpret_cast<uint32_t*>(data + bufsz); |
|
while (datap < endp) { |
|
uint8_t alpha = ((*datap & 0xFF000000) >> 24); |
|
if (alpha == 0x00) |
|
transparency = true; |
|
else if (alpha != 0xFF) |
|
return PyLong_FromLong(kFull); |
|
datap++; |
|
} |
|
return PyLong_FromLong(transparency ? kOnOff : kOpaque); |
|
} |
|
|
|
static PyObject* pyGLTexture_get_image_data(pyGLTexture* self, void*) { |
|
Py_XINCREF(self->m_imageData); |
|
return Py_BuildValue("iiO", self->m_width, self->m_height, self->m_imageData); |
|
} |
|
|
|
static int pyGLTexture_set_image_data(pyGLTexture* self, PyObject* value, void*) { |
|
PyObject* data; |
|
// Requesting a Bytes object "S" instead of a buffer "y#" so we can just increment the reference |
|
// count on a buffer that already exists, instead of doing a memcpy. |
|
if (!PyArg_ParseTuple(value, "iiS", &self->m_width, &self->m_height, &data)) { |
|
PyErr_SetString(PyExc_TypeError, "image_data should be a sequence of int, int, bytes"); |
|
return -1; |
|
} |
|
|
|
Py_XDECREF(self->m_imageData); |
|
Py_XINCREF(data); |
|
self->m_imageData = data; |
|
return 0; |
|
} |
|
|
|
static PyObject* pyGLTexture_get_num_levels(pyGLTexture* self, void*) { |
|
return PyLong_FromLong(_get_num_levels(self->m_width, self->m_height)); |
|
} |
|
|
|
static PyObject* pyGLTexture_get_size_npot(pyGLTexture* self, void*) { |
|
return Py_BuildValue("ii", self->m_width, self->m_height); |
|
} |
|
|
|
static PyObject* pyGLTexture_get_size_pot(pyGLTexture* self, void*) { |
|
size_t width = _ensure_power_of_two(self->m_width); |
|
size_t height = _ensure_power_of_two(self->m_height); |
|
return Py_BuildValue("ii", width, height); |
|
} |
|
|
|
static PyGetSetDef pyGLTexture_GetSet[] = { |
|
{ _pycs("has_alpha"), (getter)pyGLTexture_get_has_alpha, NULL, NULL, NULL }, |
|
{ _pycs("image_data"), (getter)pyGLTexture_get_image_data, (setter)pyGLTexture_set_image_data, NULL, NULL }, |
|
{ _pycs("num_levels"), (getter)pyGLTexture_get_num_levels, NULL, NULL, NULL }, |
|
{ _pycs("size_npot"), (getter)pyGLTexture_get_size_npot, NULL, NULL, NULL }, |
|
{ _pycs("size_pot"), (getter)pyGLTexture_get_size_pot, NULL, NULL, NULL }, |
|
{ NULL, NULL, NULL, NULL, NULL } |
|
}; |
|
|
|
PyTypeObject pyGLTexture_Type = { |
|
PyVarObject_HEAD_INIT(NULL, 0) |
|
"_korlib.GLTexture", /* tp_name */ |
|
sizeof(pyGLTexture), /* tp_basicsize */ |
|
0, /* tp_itemsize */ |
|
|
|
(destructor)pyGLTexture_dealloc, /* tp_dealloc */ |
|
NULL, /* tp_print */ |
|
NULL, /* tp_getattr */ |
|
NULL, /* tp_setattr */ |
|
NULL, /* tp_compare */ |
|
NULL, /* tp_repr */ |
|
NULL, /* tp_as_number */ |
|
NULL, /* tp_as_sequence */ |
|
NULL, /* tp_as_mapping */ |
|
NULL, /* tp_hash */ |
|
NULL, /* tp_call */ |
|
NULL, /* tp_str */ |
|
NULL, /* tp_getattro */ |
|
NULL, /* tp_setattro */ |
|
NULL, /* tp_as_buffer */ |
|
|
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ |
|
"GLTexture", /* tp_doc */ |
|
|
|
NULL, /* tp_traverse */ |
|
NULL, /* tp_clear */ |
|
NULL, /* tp_richcompare */ |
|
0, /* tp_weaklistoffset */ |
|
NULL, /* tp_iter */ |
|
NULL, /* tp_iternext */ |
|
|
|
pyGLTexture_Methods, /* tp_methods */ |
|
NULL, /* tp_members */ |
|
pyGLTexture_GetSet, /* tp_getset */ |
|
NULL, /* tp_base */ |
|
NULL, /* tp_dict */ |
|
NULL, /* tp_descr_get */ |
|
NULL, /* tp_descr_set */ |
|
0, /* tp_dictoffset */ |
|
|
|
(initproc)pyGLTexture___init__, /* tp_init */ |
|
NULL, /* tp_alloc */ |
|
pyGLTexture_new, /* tp_new */ |
|
NULL, /* tp_free */ |
|
NULL, /* tp_is_gc */ |
|
|
|
NULL, /* tp_bases */ |
|
NULL, /* tp_mro */ |
|
NULL, /* tp_cache */ |
|
NULL, /* tp_subclasses */ |
|
NULL, /* tp_weaklist */ |
|
|
|
NULL, /* tp_del */ |
|
0, /* tp_version_tag */ |
|
NULL, /* tp_finalize */ |
|
}; |
|
|
|
PyObject* Init_pyGLTexture_Type() { |
|
if (PyType_Ready(&pyGLTexture_Type) < 0) |
|
return NULL; |
|
|
|
Py_INCREF(&pyGLTexture_Type); |
|
return (PyObject*)&pyGLTexture_Type; |
|
}
|
|
|