extend effect engine with qt image effects (#249)

* - effects now can use qt image effects
- rainbow swirtl is now suitable for any led layout - including matrix

* fix rainbow effect

* effect: add radialGradient

* fix some js errors

* optimize code

* try fix travis test not working as expected

* fix default config files

* fix config
This commit is contained in:
redPanther
2016-09-21 22:01:50 +02:00
committed by GitHub
parent 9340a9a7c8
commit f88cd3a230
17 changed files with 373 additions and 91 deletions

View File

@@ -8,16 +8,27 @@
// Qt includes
#include <QDateTime>
#include <QFile>
#include <Qt>
#include <QLinearGradient>
#include <QConicalGradient>
#include <QRadialGradient>
#include <QRect>
// effect engin eincludes
#include "Effect.h"
#include <utils/Logger.h>
#include <hyperion/Hyperion.h>
// Python method table
PyMethodDef Effect::effectMethods[] = {
{"setColor", Effect::wrapSetColor, METH_VARARGS, "Set a new color for the leds."},
{"setImage", Effect::wrapSetImage, METH_VARARGS, "Set a new image to process and determine new led colors."},
{"abort", Effect::wrapAbort, METH_NOARGS, "Check if the effect should abort execution."},
{"setColor", Effect::wrapSetColor, METH_VARARGS, "Set a new color for the leds."},
{"setImage", Effect::wrapSetImage, METH_VARARGS, "Set a new image to process and determine new led colors."},
{"abort", Effect::wrapAbort, METH_NOARGS, "Check if the effect should abort execution."},
{"imageShow", Effect::wrapImageShow, METH_NOARGS, "set current effect image to hyperion core."},
{"imageCanonicalGradient", Effect::wrapImageCanonicalGradient, METH_VARARGS, ""},
{"imageRadialGradient" , Effect::wrapImageRadialGradient, METH_VARARGS, ""},
// {"imageSetPixel",Effect::wrapImageShow, METH_VARARGS, "set pixel color of image"},
// {"imageGetPixel",Effect::wrapImageShow, METH_VARARGS, "get pixel color of image"},
{NULL, NULL, 0, NULL}
};
@@ -69,6 +80,12 @@ Effect::Effect(PyThreadState * mainThreadState, int priority, int timeout, const
// disable the black border detector for effects
_imageProcessor->enableBlackBorderDetector(false);
// init effect image for image based effects, size is based on led layout
_imageSize = Hyperion::getInstance()->getLedGridSize();
_image = new QImage(_imageSize, QImage::Format_ARGB32_Premultiplied);
_image->fill(Qt::black);
_painter = new QPainter(_image);
// connect the finished signal
connect(this, SIGNAL(finished()), this, SLOT(effectFinished()));
@@ -77,6 +94,8 @@ Effect::Effect(PyThreadState * mainThreadState, int priority, int timeout, const
Effect::~Effect()
{
delete _painter;
delete _image;
}
void Effect::run()
@@ -96,6 +115,12 @@ void Effect::run()
// add ledCount variable to the interpreter
PyObject_SetAttrString(module, "ledCount", Py_BuildValue("i", _imageProcessor->getLedCount()));
// add imageWidth variable to the interpreter
PyObject_SetAttrString(module, "imageWidth", Py_BuildValue("i", _imageSize.width()));
// add imageHeight variable to the interpreter
PyObject_SetAttrString(module, "imageHeight", Py_BuildValue("i", _imageSize.height()));
// add a args variable to the interpreter
PyObject_SetAttrString(module, "args", json2python(_args));
@@ -357,6 +382,207 @@ PyObject* Effect::wrapAbort(PyObject *self, PyObject *)
return Py_BuildValue("i", effect->_abortRequested ? 1 : 0);
}
PyObject* Effect::wrapImageShow(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// determine the timeout
int timeout = effect->_timeout;
if (timeout > 0)
{
timeout = effect->_endTime - QDateTime::currentMSecsSinceEpoch();
// we are done if the time has passed
if (timeout <= 0)
{
return Py_BuildValue("");
}
}
int width = effect->_imageSize.width();
int height = effect->_imageSize.height();
QImage * qimage = effect->_image;
Image<ColorRgb> image(width, height);
QByteArray binaryImage;
for (int i = 0; i <qimage->height(); ++i)
{
const QRgb * scanline = reinterpret_cast<const QRgb *>(qimage->scanLine(i));
for (int j = 0; j < qimage->width(); ++j)
{
binaryImage.append((char) qRed(scanline[j]));
binaryImage.append((char) qGreen(scanline[j]));
binaryImage.append((char) qBlue(scanline[j]));
}
}
memcpy(image.memptr(), binaryImage.data(), binaryImage.size());
effect->_imageProcessor->process(image, effect->_colors);
effect->setColors(effect->_priority, effect->_colors, timeout, false);
return Py_BuildValue("");
}
PyObject* Effect::wrapImageCanonicalGradient(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
int argCount = PyTuple_Size(args);
PyObject * bytearray = nullptr;
int centerX, centerY, angle;
int startX = 0;
int startY = 0;
int width = effect->_imageSize.width();
int height = effect->_imageSize.height();
bool argsOK = false;
if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiiO", &startX, &startY, &width, &height, &centerX, &centerY, &angle, &bytearray) )
{
argsOK = true;
}
if ( argCount == 4 && PyArg_ParseTuple(args, "iiiO", &centerX, &centerY, &angle, &bytearray) )
{
argsOK = true;
}
angle = std::max(std::min(angle,360),0);
if (argsOK)
{
if (PyByteArray_Check(bytearray))
{
int length = PyByteArray_Size(bytearray);
if (length % 4 == 0)
{
QPainter * painter = effect->_painter;
QRect myQRect(startX,startY,width,height);
QConicalGradient gradient(QPoint(centerX,centerY), angle );
char * data = PyByteArray_AS_STRING(bytearray);
for (int idx=0; idx<length; idx+=4)
{
gradient.setColorAt(
((uint8_t)data[idx])/255.0,
QColor(
(uint8_t)(data[idx+1]),
(uint8_t)(data[idx+2]),
(uint8_t)(data[idx+3])
));
}
gradient.setSpread(QGradient::RepeatSpread);
painter->fillRect(myQRect, gradient);
return Py_BuildValue("");
}
else
{
PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should multiple of 4");
return nullptr;
}
}
else
{
PyErr_SetString(PyExc_RuntimeError, "Argument 8 is not a bytearray");
return nullptr;
}
}
else
{
return nullptr;
}
}
PyObject* Effect::wrapImageRadialGradient(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
int argCount = PyTuple_Size(args);
PyObject * bytearray = nullptr;
int centerX, centerY, radius, focalX, focalY, focalRadius;
int startX = 0;
int startY = 0;
int width = effect->_imageSize.width();
int height = effect->_imageSize.height();
bool argsOK = false;
if ( argCount == 11 && PyArg_ParseTuple(args, "iiiiiiiiiiO", &startX, &startY, &width, &height, &centerX, &centerY, &radius, &focalX, &focalY, &focalRadius, &bytearray) )
{
argsOK = true;
}
if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiiO", &startX, &startY, &width, &height, &centerX, &centerY, &radius, &bytearray) )
{
argsOK = true;
focalX = centerX;
focalY = centerY;
focalRadius = radius;
}
if ( argCount == 7 && PyArg_ParseTuple(args, "iiiiiiO", &centerX, &centerY, &radius, &focalX, &focalY, &focalRadius, &bytearray) )
{
argsOK = true;
}
if ( argCount == 4 && PyArg_ParseTuple(args, "iiiO", &centerX, &centerY, &radius, &bytearray) )
{
argsOK = true;
focalX = centerX;
focalY = centerY;
focalRadius = radius;
}
if (argsOK)
{
if (PyByteArray_Check(bytearray))
{
int length = PyByteArray_Size(bytearray);
if (length % 4 == 0)
{
QPainter * painter = effect->_painter;
QRect myQRect(startX,startY,width,height);
QRadialGradient gradient(QPoint(centerX,centerY), std::max(radius,0) );
char * data = PyByteArray_AS_STRING(bytearray);
for (int idx=0; idx<length; idx+=4)
{
gradient.setColorAt(
((uint8_t)data[idx])/255.0,
QColor(
(uint8_t)(data[idx+1]),
(uint8_t)(data[idx+2]),
(uint8_t)(data[idx+3])
));
}
//gradient.setSpread(QGradient::ReflectSpread);
painter->fillRect(myQRect, gradient);
return Py_BuildValue("");
}
else
{
PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should multiple of 4");
return nullptr;
}
}
else
{
PyErr_SetString(PyExc_RuntimeError, "Last argument is not a bytearray");
return nullptr;
}
}
else
{
return nullptr;
}
}
Effect * Effect::getEffect()
{
// extract the module from the runtime