Commits from @MartB and more ...

- Commit: 1d9165f403
- New default QT capture implementation
- UploadHandler added to Effects Configurator to allow uploading GIF files
- Docker compile script and instruction
- Travis Fix
This commit is contained in:
Paulchen-Panther
2019-01-06 19:49:56 +01:00
parent 7352ff4d42
commit 2dca1c93e6
57 changed files with 1134 additions and 341 deletions

View File

@@ -24,6 +24,10 @@
{
"type" : "object",
"required" : true
},
"imageData" : {
"type" : "string",
"required" : false
}
},
"additionalProperties": false

View File

@@ -44,6 +44,10 @@
"pythonScript" : {
"type" : "string",
"required" : false
},
"imageData" : {
"type" : "string",
"required" : false
}
},
"additionalProperties": false

View File

@@ -12,9 +12,6 @@
#include <QImage>
#include <QBuffer>
#include <QByteArray>
// #include <QFileInfo>
// #include <QDir>
// #include <QIODevice>
#include <QDateTime>
// hyperion includes
@@ -180,7 +177,7 @@ void JsonAPI::handleImageCommand(const QJsonObject& message, const QString& comm
sendSuccessReply(command, tan);
}
void JsonAPI::handleEffectCommand(const QJsonObject& message, const QString& command, const int tan)
void JsonAPI::handleEffectCommand(const QJsonObject &message, const QString &command, const int tan)
{
emit forwardJsonMessage(message);
@@ -191,16 +188,12 @@ void JsonAPI::handleEffectCommand(const QJsonObject& message, const QString& com
QString origin = message["origin"].toString() + "@"+_peerAddress;
const QJsonObject & effect = message["effect"].toObject();
const QString & effectName = effect["name"].toString();
const QString & data = message["imageData"].toString("").toUtf8();
// set output
if (effect.contains("args"))
{
_hyperion->setEffect(effectName, effect["args"].toObject(), priority, duration, pythonScript, origin);
}
else
{
_hyperion->setEffect(effectName, priority, duration, origin);
}
(effect.contains("args"))
? _hyperion->setEffect(effectName, effect["args"].toObject(), priority, duration, pythonScript, origin, data)
: _hyperion->setEffect(effectName, priority, duration, origin);
// send reply
sendSuccessReply(command, tan);

View File

@@ -25,7 +25,7 @@
//impl
PyThreadState* mainThreadState;
Effect::Effect(Hyperion* hyperion, int priority, int timeout, const QString & script, const QString & name, const QJsonObject & args)
Effect::Effect(Hyperion *hyperion, int priority, int timeout, const QString &script, const QString &name, const QJsonObject &args, const QString &imageData)
: QThread()
, _hyperion(hyperion)
, _priority(priority)
@@ -33,6 +33,7 @@ Effect::Effect(Hyperion* hyperion, int priority, int timeout, const QString & sc
, _script(script)
, _name(name)
, _args(args)
, _imageData(imageData)
, _endTime(-1)
, _colors()
, _imageSize(hyperion->getLedGridSize())

View File

@@ -136,14 +136,14 @@ int EffectEngine::runEffect(const QString &effectName, int priority, int timeout
return runEffect(effectName, QJsonObject(), priority, timeout, "", origin);
}
int EffectEngine::runEffect(const QString &effectName, const QJsonObject &args, int priority, int timeout, const QString &pythonScript, const QString &origin, unsigned smoothCfg)
int EffectEngine::runEffect(const QString &effectName, const QJsonObject &args, int priority, int timeout, const QString &pythonScript, const QString &origin, unsigned smoothCfg, const QString &imageData)
{
Info( _log, "run effect %s on channel %d", QSTRING_CSTR(effectName), priority);
if (pythonScript.isEmpty())
{
const EffectDefinition * effectDefinition = nullptr;
for (const EffectDefinition & e : _availableEffects)
const EffectDefinition *effectDefinition = nullptr;
for (const EffectDefinition &e : _availableEffects)
{
if (e.name == effectName)
{
@@ -160,16 +160,16 @@ int EffectEngine::runEffect(const QString &effectName, const QJsonObject &args,
return runEffectScript(effectDefinition->script, effectName, (args.isEmpty() ? effectDefinition->args : args), priority, timeout, origin, effectDefinition->smoothCfg);
}
return runEffectScript(pythonScript, effectName, args, priority, timeout, origin, smoothCfg);
return runEffectScript(pythonScript, effectName, args, priority, timeout, origin, smoothCfg, imageData);
}
int EffectEngine::runEffectScript(const QString &script, const QString &name, const QJsonObject &args, int priority, int timeout, const QString & origin, unsigned smoothCfg)
int EffectEngine::runEffectScript(const QString &script, const QString &name, const QJsonObject &args, int priority, int timeout, const QString &origin, unsigned smoothCfg, const QString &imageData)
{
// clear current effect on the channel
channelCleared(priority);
// create the effect
Effect * effect = new Effect(_hyperion, priority, timeout, script, name, args);
Effect *effect = new Effect(_hyperion, priority, timeout, script, name, args, imageData);
connect(effect, &Effect::setInput, _hyperion, &Hyperion::setInput, Qt::QueuedConnection);
connect(effect, &Effect::setInputImage, _hyperion, &Hyperion::setInputImage, Qt::QueuedConnection);
connect(effect, &QThread::finished, this, &EffectEngine::effectFinished);

View File

@@ -8,6 +8,7 @@
#include <QFileInfo>
#include <QDir>
#include <QMap>
#include <QByteArray>
// createEffect helper
struct find_schema: std::unary_function<EffectSchema, bool>
@@ -69,7 +70,15 @@ const bool EffectFileHandler::deleteEffect(const QString& effectName, QString& r
{
if (effectConfigurationFile.exists())
{
if ( (it->script == ":/effects/gif.py") && !it->args.value("image").toString("").isEmpty())
{
QFileInfo effectImageFile(effectConfigurationFile.absolutePath() + "/" + it->args.value("image").toString());
if (effectImageFile.exists())
QFile::remove(effectImageFile.absoluteFilePath());
}
bool result = QFile::remove(effectConfigurationFile.absoluteFilePath());
if (result)
{
updateEffects();
@@ -141,6 +150,17 @@ const bool EffectFileHandler::saveEffect(const QJsonObject& message, QString& re
newFileName.setFile(f);
}
//TODO check if filename exist
if (!message["imageData"].toString("").isEmpty() && !message["args"].toObject().value("image").toString("").isEmpty())
{
QFileInfo imageFileName(effectArray[0].toString().replace("$ROOT",_rootPath) + "/" + message["args"].toObject().value("image").toString());
if(!FileUtils::writeFile(imageFileName.absoluteFilePath(), QByteArray::fromBase64(message["imageData"].toString("").toUtf8()), _log))
{
resultMsg = "Error while saving image file '" + message["args"].toObject().value("image").toString() + ", please check the Hyperion Log";
return false;
}
}
if(!JsonUtils::write(newFileName.absoluteFilePath(), effectJson, _log))
{
resultMsg = "Error while saving effect, please check the Hyperion Log";

View File

@@ -11,6 +11,7 @@
#include <QJsonArray>
#include <QDateTime>
#include <QImageReader>
#include <QBuffer>
// create the hyperion module
struct PyModuleDef EffectModule::moduleDef = {
@@ -257,25 +258,42 @@ PyObject* EffectModule::wrapSetImage(PyObject *self, PyObject *args)
PyObject* EffectModule::wrapGetImage(PyObject *self, PyObject *args)
{
Q_INIT_RESOURCE(EffectEngine);
Effect *effect = getEffect();
char *source;
if(!PyArg_ParseTuple(args, "s", &source))
QString file;
QBuffer buffer;
QImageReader reader;
if (effect->_imageData.isEmpty())
{
PyErr_SetString(PyExc_TypeError, "String required");
return NULL;
Q_INIT_RESOURCE(EffectEngine);
char *source;
if(!PyArg_ParseTuple(args, "s", &source))
{
PyErr_SetString(PyExc_TypeError, "String required");
return NULL;
}
file = QString::fromUtf8(source);
if (file.mid(0, 1) == ":")
file = ":/effects/"+file.mid(1);
reader.setDecideFormatFromContent(true);
reader.setFileName(file);
}
else
{
buffer.setData(QByteArray::fromBase64(effect->_imageData.toUtf8()));
buffer.open(QBuffer::ReadOnly);
reader.setDecideFormatFromContent(true);
reader.setDevice(&buffer);
}
QString file = QString::fromUtf8(source);
if (file.mid(0, 1) == ":")
file = ":/effects/"+file.mid(1);
QImageReader reader(file);
if (reader.canRead())
{
PyObject* result = PyList_New(reader.imageCount());
PyObject *result = PyList_New(reader.imageCount());
for (int i = 0; i < reader.imageCount(); ++i)
{
@@ -290,7 +308,7 @@ PyObject* EffectModule::wrapGetImage(PyObject *self, PyObject *args)
QByteArray binaryImage;
for (int i = 0; i<height; ++i)
{
const QRgb * scanline = reinterpret_cast<const QRgb *>(qimage.scanLine(i));
const QRgb *scanline = reinterpret_cast<const QRgb *>(qimage.scanLine(i));
for (int j = 0; j< width; ++j)
{
binaryImage.append((char) qRed(scanline[j]));

View File

@@ -21,3 +21,7 @@ endif (ENABLE_V4L2)
if (ENABLE_X11)
add_subdirectory(x11)
endif()
if (ENABLE_QT)
add_subdirectory(qt)
endif()

View File

@@ -0,0 +1,16 @@
# Define the current source locations
SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/grabber)
SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/grabber/qt)
find_package(Qt5Widgets REQUIRED)
include_directories( ${X11_INCLUDES} )
FILE ( GLOB QT_GRAB_SOURCES "${CURRENT_HEADER_DIR}/Qt*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" )
add_library(qt-grabber ${QT_GRAB_SOURCES} )
target_link_libraries(qt-grabber
hyperion
Qt5::Widgets
)

View File

@@ -0,0 +1,199 @@
// proj
#include <grabber/QtGrabber.h>
// qt
#include <QPixmap>
#include <QWindow>
#include <QGuiApplication>
#include <QWidget>
#include <QScreen>
QtGrabber::QtGrabber(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation, int display)
: Grabber("QTGRABBER", 0, 0, cropLeft, cropRight, cropTop, cropBottom)
, _display(unsigned(display))
, _pixelDecimation(pixelDecimation)
, _screenWidth(0)
, _screenHeight(0)
, _src_x(0)
, _src_y(0)
, _src_x_max(0)
, _src_y_max(0)
, _screen(nullptr)
{
_useImageResampler = false;
// init
setupDisplay();
}
QtGrabber::~QtGrabber()
{
freeResources();
}
void QtGrabber::freeResources()
{
// cleanup
if (_screen != nullptr)
{
delete _screen;
_screen = nullptr;
}
}
const bool QtGrabber::setupDisplay()
{
// cleanup last screen
freeResources();
QScreen* primary = QGuiApplication::primaryScreen();
QList<QScreen *> screens = QGuiApplication::screens();
// inject main screen at 0, if not nullptr
if(primary != nullptr)
{
screens.prepend(primary);
// remove last main screen if twice in list
if(screens.lastIndexOf(primary) > 0)
screens.removeAt(screens.lastIndexOf(primary));
}
if(screens.isEmpty())
{
Error(_log, "No displays found to capture from!");
return false;
}
Info(_log,"Available Displays:");
int index = 0;
for(auto screen : screens)
{
const QRect geo = screen->geometry();
Info(_log,"Display %d: Name:%s Geometry: (L,T,R,B) %d,%d,%d,%d Depth:%dbit", index, QSTRING_CSTR(screen->name()), geo.left(), geo.top() ,geo.right(), geo.bottom(), screen->depth());
index++;
}
// be sure the index is available
if(_display > unsigned(screens.size()-1))
{
Info(_log, "The requested display index '%d' is not available, falling back to display 0", _display);
_display = 0;
}
// init the requested display
_screen = screens.at(_display);
connect(_screen, &QScreen::geometryChanged, this, &QtGrabber::geometryChanged);
updateScreenDimensions(true);
Info(_log,"Initialized display %d", _display);
return true;
}
void QtGrabber::geometryChanged(const QRect &geo)
{
Info(_log, "The current display changed geometry to (L,T,R,B) %d,%d,%d,%d", geo.left(), geo.top() ,geo.right(), geo.bottom());
updateScreenDimensions(true);
}
int QtGrabber::grabFrame(Image<ColorRgb> & image)
{
if(_screen == nullptr)
{
// reinit, this will disable capture on failure
setEnabled(setupDisplay());
return -1;
}
QPixmap originalPixmap = _screen->grabWindow(0, _src_x, _src_y, _src_x_max, _src_y_max);
QPixmap resizedPixmap = originalPixmap.scaled(_width,_height);
QImage img = resizedPixmap.toImage().convertToFormat( QImage::Format_RGB888);
memcpy(image.memptr(), img.bits(),_width*_height*3);
return 0;
}
int QtGrabber::updateScreenDimensions(const bool& force)
{
if(!_screen)
return -1;
const QRect& geo = _screen->geometry();
if (!force && _screenWidth == unsigned(geo.right()) && _screenHeight == unsigned(geo.bottom()))
{
// No update required
return 0;
}
Info(_log, "Update of screen resolution: [%dx%d] to [%dx%d]", _screenWidth, _screenHeight, geo.right(), geo.bottom());
_screenWidth = geo.right() - geo.left();
_screenHeight = geo.bottom() - geo.top();
int width=0, height=0;
// Image scaling is performed by Qt
width = (_screenWidth > unsigned(_cropLeft + _cropRight))
? ((_screenWidth - _cropLeft - _cropRight) / _pixelDecimation)
: (_screenWidth / _pixelDecimation);
height = (_screenHeight > unsigned(_cropTop + _cropBottom))
? ((_screenHeight - _cropTop - _cropBottom) / _pixelDecimation)
: (_screenHeight / _pixelDecimation);
// calculate final image dimensions and adjust top/left cropping in 3D modes
switch (_videoMode)
{
case VIDEO_3DSBS:
_width = width /2;
_height = height;
_src_x = _cropLeft / 2;
_src_y = _cropTop;
_src_x_max = (_screenWidth / 2) - _cropRight;
_src_y_max = _screenHeight - _cropBottom;
break;
case VIDEO_3DTAB:
_width = width;
_height = height / 2;
_src_x = _cropLeft;
_src_y = _cropTop / 2;
_src_x_max = _screenWidth - _cropRight;
_src_y_max = (_screenHeight / 2) - _cropBottom;
break;
case VIDEO_2D:
default:
_width = width;
_height = height;
_src_x = _cropLeft;
_src_y = _cropTop;
_src_x_max = _screenWidth - _cropRight;
_src_y_max = _screenHeight - _cropBottom;
break;
}
Info(_log, "Update output image resolution to [%dx%d]", _width, _height);
return 1;
}
void QtGrabber::setVideoMode(VideoMode mode)
{
Grabber::setVideoMode(mode);
updateScreenDimensions(true);
}
void QtGrabber::setPixelDecimation(int pixelDecimation)
{
_pixelDecimation = pixelDecimation;
}
void QtGrabber::setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom)
{
Grabber::setCropping(cropLeft, cropRight, cropTop, cropBottom);
updateScreenDimensions(true);
}
void QtGrabber::setDisplayIndex(int index)
{
if(_display != unsigned(index))
{
_display = unsigned(index);
setupDisplay();
}
}

View File

@@ -0,0 +1,11 @@
#include <grabber/QtWrapper.h>
QtWrapper::QtWrapper(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation, int display, const unsigned updateRate_Hz)
: GrabberWrapper("Qt", &_grabber, 0, 0, updateRate_Hz)
, _grabber(cropLeft, cropRight, cropTop, cropBottom, pixelDecimation, display)
{}
void QtWrapper::action()
{
transferFrame(_grabber);
}

View File

@@ -77,6 +77,10 @@ QStringList GrabberWrapper::availableGrabbers()
grabbers << "x11";
#endif
#ifdef ENABLE_QT
grabbers << "qt";
#endif
return grabbers;
}

View File

@@ -529,9 +529,9 @@ int Hyperion::setEffect(const QString &effectName, int priority, int timeout, co
return _effectEngine->runEffect(effectName, priority, timeout, origin);
}
int Hyperion::setEffect(const QString &effectName, const QJsonObject &args, int priority, int timeout, const QString & pythonScript, const QString & origin)
int Hyperion::setEffect(const QString &effectName, const QJsonObject &args, int priority, int timeout, const QString &pythonScript, const QString &origin, const QString &imageData)
{
return _effectEngine->runEffect(effectName, args, priority, timeout, pythonScript, origin);
return _effectEngine->runEffect(effectName, args, priority, timeout, pythonScript, origin, 0, imageData);
}
void Hyperion::setLedMappingType(const int& mappingType)

View File

@@ -47,19 +47,24 @@ ImageToLedsMap::ImageToLedsMap(
minX_idx = qMin(minX_idx, xOffset + actualWidth - 1);
if (minX_idx == maxX_idx)
{
maxX_idx = minX_idx + 1;
maxX_idx++;
}
minY_idx = qMin(minY_idx, yOffset + actualHeight - 1);
if (minY_idx == maxY_idx)
{
maxY_idx = minY_idx + 1;
maxY_idx++;
}
// Add all the indices in the above defined rectangle to the indices for this led
const auto maxYLedCount = qMin(maxY_idx, yOffset+actualHeight);
const auto maxXLedCount = qMin(maxX_idx, xOffset+actualWidth);
std::vector<unsigned> ledColors;
for (unsigned y = minY_idx; y<maxY_idx && y<(yOffset+actualHeight); ++y)
ledColors.reserve(maxXLedCount*maxYLedCount);
for (unsigned y = minY_idx; y < maxYLedCount; ++y)
{
for (unsigned x = minX_idx; x<maxX_idx && x<(xOffset+actualWidth); ++x)
for (unsigned x = minX_idx; x < maxXLedCount; ++x)
{
ledColors.push_back(y*width + x);
}

View File

@@ -7,7 +7,7 @@
{
"type" : "string",
"title" : "edt_conf_fg_type_title",
"enum" : ["auto","dispmanx","amlogic","x11","framebuffer"],
"enum" : ["auto","dispmanx","amlogic","x11","framebuffer","qt"],
"default" : "auto",
"propertyOrder" : 2
},

View File

@@ -144,8 +144,8 @@ void RgbTransform::transform(uint8_t & red, uint8_t & green, uint8_t & blue)
{
// apply gamma
red = _mappingR[red];
green = _mappingR[green];
blue = _mappingR[blue];
green = _mappingG[green];
blue = _mappingB[blue];
// apply brightnesss
int rgbSum = red+green+blue;

View File

@@ -23,6 +23,8 @@ QtHttpClientWrapper::QtHttpClientWrapper (QTcpSocket * sock, QtHttpServer * pare
, m_sockClient (sock)
, m_currentRequest (Q_NULLPTR)
, m_serverHandle (parent)
, m_websocketClient(nullptr)
, m_webJsonRpc (nullptr)
{
connect (m_sockClient, &QTcpSocket::readyRead, this, &QtHttpClientWrapper::onClientDataReceived);
}

View File

@@ -50,8 +50,8 @@ private:
QTcpSocket * m_sockClient;
QtHttpRequest * m_currentRequest;
QtHttpServer * m_serverHandle;
WebSocketClient * m_websocketClient = nullptr;
WebJsonRpc * m_webJsonRpc = nullptr;
WebSocketClient * m_websocketClient;
WebJsonRpc * m_webJsonRpc;
};
#endif // QTHTTPCLIENTWRAPPER_H