Merge branch 'add_x11'

Former-commit-id: d80efce3aa0413e4f63e9171dab438d03db9021c
This commit is contained in:
poljvd 2014-12-17 19:09:19 +01:00
commit f6ce35813a
34 changed files with 2243 additions and 1511 deletions

View File

@ -19,6 +19,9 @@ message(STATUS "ENABLE_WS2812BPWM = " ${ENABLE_WS2812BPWM})
option(ENABLE_V4L2 "Enable the V4L2 grabber" ON)
message(STATUS "ENABLE_V4L2 = " ${ENABLE_V4L2})
option(ENABLE_X11 "Enable the X11 grabber" ON)
message(STATUS "ENABLE_X11 = " ${ENABLE_X11})
option(ENABLE_TINKERFORGE "Enable the TINKERFORGE device" ON)
message(STATUS "ENABLE_TINKERFORGE = " ${ENABLE_TINKERFORGE})
@ -54,8 +57,8 @@ include_directories(${CMAKE_SOURCE_DIR}/include)
# Prefer static linking over dynamic
#set(CMAKE_FIND_LIBRARY_SUFFIXES ".a;.so")
#set(CMAKE_BUILD_TYPE "Debug")
set(CMAKE_BUILD_TYPE "Release")
set(CMAKE_BUILD_TYPE "Debug")
#set(CMAKE_BUILD_TYPE "Release")
# enable C++11
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -Wall")

View File

@ -17,6 +17,9 @@
#include "getoptpp.h"
#include <stdexcept>
#include <cassert>
#include <cstdio>
#include <string>
#include <sys/ioctl.h>
using namespace std;
@ -116,15 +119,32 @@ void OptionsParser::usage() const {
cerr << "Parameters: " << endl;
int totalWidth = 80;
int usageWidth = 33;
// read total width from the terminal
struct winsize w;
if (ioctl(0, TIOCGWINSZ, &w) == 0)
{
if (w.ws_col > totalWidth)
totalWidth = w.ws_col;
}
std::list<Parameter*>::const_iterator i;
for(i = parameters.parameters.begin();
i != parameters.parameters.end(); i++)
{
cerr.width(33);
cerr.width(usageWidth);
cerr << std::left << " " + (*i)->usageLine();
cerr.width(40);
cerr << std::left << (*i)->description() << endl;
std::string description = (*i)->description();
while (int(description.length()) > (totalWidth - usageWidth))
{
size_t pos = description.find_last_of(' ', totalWidth - usageWidth);
cerr << description.substr(0, pos) << std::endl << std::string(usageWidth - 1, ' ');
description = description.substr(pos);
}
cerr << description << endl;
}
}

View File

@ -11,11 +11,12 @@
// util includes
#include <utils/Image.h>
#include <utils/ColorRgb.h>
#include <utils/PixelFormat.h>
#include <utils/VideoMode.h>
#include <utils/ImageResampler.h>
// grabber includes
#include <grabber/VideoStandard.h>
#include <grabber/PixelFormat.h>
/// Capture class for V4L2 devices
///
@ -108,22 +109,17 @@ private:
PixelFormat _pixelFormat;
int _width;
int _height;
int _lineLength;
int _frameByteSize;
int _cropLeft;
int _cropRight;
int _cropTop;
int _cropBottom;
int _frameDecimation;
int _horizontalPixelDecimation;
int _verticalPixelDecimation;
int _noSignalCounterThreshold;
ColorRgb _noSignalThresholdColor;
VideoMode _mode3D;
int _currentFrame;
int _noSignalCounter;
QSocketNotifier * _streamNotifier;
ImageResampler _imageResampler;
};

View File

@ -0,0 +1,39 @@
// Hyperion-utils includes
#include <utils/Image.h>
#include <utils/ColorRgb.h>
#include <utils/ImageResampler.h>
// X11 includes
#include <X11/Xlib.h>
class X11Grabber
{
public:
X11Grabber(int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation);
virtual ~X11Grabber();
int open();
Image<ColorRgb> & grab();
private:
ImageResampler _imageResampler;
int _cropLeft;
int _cropRight;
int _cropTop;
int _cropBottom;
/// Reference to the X11 display (nullptr if not opened)
Display * _x11Display;
unsigned _screenWidth;
unsigned _screenHeight;
Image<ColorRgb> _image;
int updateScreenDimensions();
};

View File

@ -0,0 +1,102 @@
#pragma once
// stl includes
#include <string>
// Qt includes
#include <QColor>
#include <QImage>
#include <QTcpSocket>
#include <QMap>
// hyperion util
#include <utils/Image.h>
#include <utils/ColorRgb.h>
// jsoncpp includes
#include <message.pb.h>
///
/// Connection class to setup an connection to the hyperion server and execute commands
///
class ProtoConnection
{
public:
///
/// Constructor
///
/// @param address The address of the Hyperion server (for example "192.168.0.32:19444)
///
ProtoConnection(const std::string & address);
///
/// Destructor
///
~ProtoConnection();
/// Do not read reply messages from Hyperion if set to true
void setSkipReply(bool skip);
///
/// Set all leds to the specified color
///
/// @param color The color
/// @param priority The priority
/// @param duration The duration in milliseconds
///
void setColor(const ColorRgb & color, int priority, int duration = 1);
///
/// Set the leds according to the given image (assume the image is stretched to the display size)
///
/// @param image The image
/// @param priority The priority
/// @param duration The duration in milliseconds
///
void setImage(const Image<ColorRgb> & image, int priority, int duration = -1);
///
/// Clear the given priority channel
///
/// @param priority The priority
///
void clear(int priority);
///
/// Clear all priority channels
///
void clearAll();
private:
/// Try to connect to the Hyperion host
void connectToHost();
///
/// Send a command message and receive its reply
///
/// @param message The message to send
///
void sendMessage(const proto::HyperionRequest & message);
///
/// Parse a reply message
///
/// @param reply The received reply
///
/// @return true if the reply indicates success
///
bool parseReply(const proto::HyperionReply & reply);
private:
/// The TCP-Socket with the connection to the server
QTcpSocket _socket;
/// Host address
QString _host;
/// Host port
uint16_t _port;
/// Skip receiving reply messages from Hyperion if set
bool _skipReply;
};

View File

@ -0,0 +1,34 @@
// Qt includes
#include <QObject>
// hyperion includes
#include <utils/Image.h>
#include <utils/ColorRgb.h>
// hyperion proto includes
#include "protoserver/ProtoConnection.h"
/// This class handles callbacks from the V4L2 grabber
class ProtoConnectionWrapper : public QObject
{
Q_OBJECT
public:
ProtoConnectionWrapper(const std::string & address, int priority, int duration_ms, bool skipProtoReply);
virtual ~ProtoConnectionWrapper();
public slots:
/// Handle a single image
/// @param image The image to process
void receiveImage(const Image<ColorRgb> & image);
private:
/// Priority for calls to Hyperion
const int _priority;
/// Duration for color calls to Hyperion
const int _duration_ms;
/// Hyperion proto connection object
ProtoConnection _connection;
};

View File

@ -96,11 +96,6 @@ public:
return _height;
}
uint8_t alpha(const unsigned pixel) const
{
return (_pixels + pixel)->red;
}
uint8_t red(const unsigned pixel) const
{
return (_pixels + pixel)->red;

View File

@ -0,0 +1,40 @@
#pragma once
#include <utils/VideoMode.h>
#include <utils/PixelFormat.h>
#include <utils/Image.h>
#include <utils/ColorRgb.h>
class ImageResampler
{
public:
ImageResampler();
~ImageResampler();
void setHorizontalPixelDecimation(int decimator);
void setVerticalPixelDecimation(int decimator);
void setCropping(int cropLeft,
int cropRight,
int cropTop,
int cropBottom);
void set3D(VideoMode mode);
void processImage(const uint8_t * data, int width, int height, int lineLength, PixelFormat pixelFormat,
Image<ColorRgb> & outputImage) const;
private:
static inline uint8_t clamp(int x);
static void yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t & r, uint8_t & g, uint8_t & b);
private:
int _horizontalDecimation;
int _verticalDecimation;
int _cropLeft;
int _cropRight;
int _cropTop;
int _cropBottom;
VideoMode _videoMode;
};

View File

@ -10,6 +10,7 @@ enum PixelFormat {
PIXELFORMAT_YUYV,
PIXELFORMAT_UYVY,
PIXELFORMAT_RGB32,
PIXELFORMAT_BGR32,
PIXELFORMAT_NO_CHANGE
};
@ -30,6 +31,10 @@ inline PixelFormat parsePixelFormat(std::string pixelFormat)
{
return PIXELFORMAT_RGB32;
}
else if (pixelFormat == "bgr32")
{
return PIXELFORMAT_BGR32;
}
// return the default NO_CHANGE
return PIXELFORMAT_NO_CHANGE;

View File

@ -1,4 +1,3 @@
if (ENABLE_DISPMANX)
add_subdirectory(dispmanx)
endif (ENABLE_DISPMANX)
@ -6,3 +5,7 @@ endif (ENABLE_DISPMANX)
if (ENABLE_V4L2)
add_subdirectory(v4l2)
endif (ENABLE_V4L2)
if (ENABLE_X11)
add_subdirectory(x11)
endif()

View File

@ -9,7 +9,6 @@ SET(V4L2_QT_HEADERS
SET(V4L2_HEADERS
${CURRENT_HEADER_DIR}/VideoStandard.h
${CURRENT_HEADER_DIR}/PixelFormat.h
)
SET(V4L2_SOURCES

View File

@ -18,24 +18,6 @@
#define CLEAR(x) memset(&(x), 0, sizeof(x))
static inline uint8_t clamp(int x)
{
return (x<0) ? 0 : ((x>255) ? 255 : uint8_t(x));
}
static void yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t & r, uint8_t & g, uint8_t & b)
{
// see: http://en.wikipedia.org/wiki/YUV#Y.27UV444_to_RGB888_conversion
int c = y - 16;
int d = u - 128;
int e = v - 128;
r = clamp((298 * c + 409 * e + 128) >> 8);
g = clamp((298 * c - 100 * d - 208 * e + 128) >> 8);
b = clamp((298 * c + 516 * d + 128) >> 8);
}
V4L2Grabber::V4L2Grabber(const std::string & device,
int input,
VideoStandard videoStandard,
@ -52,21 +34,19 @@ V4L2Grabber::V4L2Grabber(const std::string & device,
_pixelFormat(pixelFormat),
_width(width),
_height(height),
_lineLength(-1),
_frameByteSize(-1),
_cropLeft(0),
_cropRight(0),
_cropTop(0),
_cropBottom(0),
_frameDecimation(std::max(1, frameDecimation)),
_horizontalPixelDecimation(std::max(1, horizontalPixelDecimation)),
_verticalPixelDecimation(std::max(1, verticalPixelDecimation)),
_noSignalCounterThreshold(50),
_noSignalThresholdColor(ColorRgb{0,0,0}),
_mode3D(VIDEO_2D),
_currentFrame(0),
_noSignalCounter(0),
_streamNotifier(nullptr)
_streamNotifier(nullptr),
_imageResampler()
{
_imageResampler.setHorizontalPixelDecimation(std::max(1, horizontalPixelDecimation));
_imageResampler.setVerticalPixelDecimation(std::max(1, verticalPixelDecimation));
open_device();
init_device(videoStandard, input);
}
@ -81,15 +61,12 @@ V4L2Grabber::~V4L2Grabber()
void V4L2Grabber::setCropping(int cropLeft, int cropRight, int cropTop, int cropBottom)
{
_cropLeft = cropLeft;
_cropRight = cropRight;
_cropTop = cropTop;
_cropBottom = cropBottom;
_imageResampler.setCropping(cropLeft, cropRight, cropTop, cropBottom);
}
void V4L2Grabber::set3D(VideoMode mode)
{
_mode3D = mode;
_imageResampler.set3D(mode);
}
void V4L2Grabber::setSignalThreshold(double redSignalThreshold, double greenSignalThreshold, double blueSignalThreshold, int noSignalCounterThreshold)
@ -414,6 +391,9 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input)
}
}
// set the line length
_lineLength = fmt.fmt.pix.bytesperline;
// set the settings
if (-1 == xioctl(VIDIOC_S_FMT, &fmt))
{
@ -688,66 +668,8 @@ bool V4L2Grabber::process_image(const void *p, int size)
void V4L2Grabber::process_image(const uint8_t * data)
{
int width = _width;
int height = _height;
switch (_mode3D)
{
case VIDEO_3DSBS:
width = _width/2;
break;
case VIDEO_3DTAB:
height = _height/2;
break;
default:
break;
}
// create output structure
int outputWidth = (width - _cropLeft - _cropRight + _horizontalPixelDecimation/2) / _horizontalPixelDecimation;
int outputHeight = (height - _cropTop - _cropBottom + _verticalPixelDecimation/2) / _verticalPixelDecimation;
Image<ColorRgb> image(outputWidth, outputHeight);
for (int ySource = _cropTop + _verticalPixelDecimation/2, yDest = 0; ySource < height - _cropBottom; ySource += _verticalPixelDecimation, ++yDest)
{
for (int xSource = _cropLeft + _horizontalPixelDecimation/2, xDest = 0; xSource < width - _cropRight; xSource += _horizontalPixelDecimation, ++xDest)
{
ColorRgb & rgb = image(xDest, yDest);
switch (_pixelFormat)
{
case PIXELFORMAT_UYVY:
{
int index = (_width * ySource + xSource) * 2;
uint8_t y = data[index+1];
uint8_t u = (xSource%2 == 0) ? data[index ] : data[index-2];
uint8_t v = (xSource%2 == 0) ? data[index+2] : data[index ];
yuv2rgb(y, u, v, rgb.red, rgb.green, rgb.blue);
}
break;
case PIXELFORMAT_YUYV:
{
int index = (_width * ySource + xSource) * 2;
uint8_t y = data[index];
uint8_t u = (xSource%2 == 0) ? data[index+1] : data[index-1];
uint8_t v = (xSource%2 == 0) ? data[index+3] : data[index+1];
yuv2rgb(y, u, v, rgb.red, rgb.green, rgb.blue);
}
break;
case PIXELFORMAT_RGB32:
{
int index = (_width * ySource + xSource) * 4;
rgb.red = data[index ];
rgb.green = data[index+1];
rgb.blue = data[index+2];
}
break;
default:
// this should not be possible
break;
}
}
}
Image<ColorRgb> image(0, 0);
_imageResampler.processImage(data, _width, _height, _lineLength, _pixelFormat, image);
// check signal (only in center of the resulting image, because some grabbers have noise values along the borders)
bool noSignal = true;

View File

@ -0,0 +1,37 @@
# Define the current source locations
SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/grabber)
SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/grabber/x11)
# Find X11
find_package(X11 REQUIRED)
include_directories(
${QT_INCLUDES}
${X11_INCLUDES}
)
SET(X11_QT_HEADERS
${CURRENT_HEADER_DIR}/X11Grabber.h
)
SET(X11_HEADERS
${CURRENT_HEADER_DIR}/X11Grabber.h
)
SET(X11_SOURCES
${CURRENT_SOURCE_DIR}/X11Grabber.cpp
)
QT4_WRAP_CPP(X11_HEADERS_MOC ${X11_QT_HEADERS})
add_library(x11-grabber
${X11_HEADERS}
${X11_SOURCES}
${X11_QT_HEADERS}
${X11_HEADERS_MOC}
)
target_link_libraries(x11-grabber
hyperion
${QT_LIBRARIES}
)

View File

@ -0,0 +1,98 @@
// STL includes
#include <iostream>
#include <cstdint>
// X11 includes
#include <X11/Xutil.h>
// X11Grabber includes
#include <grabber/X11Grabber.h>
X11Grabber::X11Grabber(int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation) :
_imageResampler(),
_cropLeft(cropLeft),
_cropRight(cropRight),
_cropTop(cropTop),
_cropBottom(cropBottom),
_x11Display(nullptr),
_screenWidth(0),
_screenHeight(0),
_image(0,0)
{
_imageResampler.setHorizontalPixelDecimation(horizontalPixelDecimation);
_imageResampler.setVerticalPixelDecimation(verticalPixelDecimation);
_imageResampler.setCropping(0, 0, 0, 0); // cropping is performed by XGetImage
}
X11Grabber::~X11Grabber()
{
if (_x11Display != nullptr)
{
XCloseDisplay(_x11Display);
}
}
int X11Grabber::open()
{
const char * display_name = nullptr;
_x11Display = XOpenDisplay(display_name);
if (_x11Display == nullptr)
{
std::cerr << "Failed to open the default X11-display" << std::endl;
return -1;
}
return 0;
}
Image<ColorRgb> & X11Grabber::grab()
{
if (_x11Display == nullptr)
{
open();
}
updateScreenDimensions();
const unsigned croppedWidth = _screenWidth - _cropLeft - _cropRight;
const unsigned croppedHeight = _screenHeight - _cropTop - _cropBottom;
// Capture the current screen
XImage * xImage = XGetImage(_x11Display, DefaultRootWindow(_x11Display), _cropLeft, _cropTop, croppedWidth, croppedHeight, AllPlanes, ZPixmap);
if (xImage == nullptr)
{
std::cerr << "Grab failed" << std::endl;
return _image;
}
_imageResampler.processImage(reinterpret_cast<const uint8_t *>(xImage->data), xImage->width, xImage->height, xImage->bytes_per_line, PIXELFORMAT_BGR32, _image);
// Cleanup allocated resources of the X11 grab
XDestroyImage(xImage);
return _image;
}
int X11Grabber::updateScreenDimensions()
{
XWindowAttributes window_attributes_return;
const Status status = XGetWindowAttributes(_x11Display, DefaultRootWindow(_x11Display), &window_attributes_return);
if (status == 0)
{
std::cerr << "Failed to obtain window attributes" << std::endl;
return -1;
}
if (_screenWidth == unsigned(window_attributes_return.width) && _screenHeight == unsigned(window_attributes_return.height))
{
// No update required
return 0;
}
std::cout << "Update of screen resolution: [" << _screenWidth << "x" << _screenHeight <<"] => ";
_screenWidth = window_attributes_return.width;
_screenHeight = window_attributes_return.height;
std::cout << "[" << _screenWidth << "x" << _screenHeight <<"]" << std::endl;
return 0;
}

View File

@ -10,7 +10,9 @@ include_directories(
# Group the headers that go through the MOC compiler
set(ProtoServer_QT_HEADERS
${CURRENT_HEADER_DIR}/ProtoServer.h
${CURRENT_HEADER_DIR}/ProtoConnection.h
${CURRENT_SOURCE_DIR}/ProtoClientConnection.h
${CURRENT_HEADER_DIR}/ProtoConnectionWrapper.h
)
set(ProtoServer_HEADERS
@ -19,6 +21,8 @@ set(ProtoServer_HEADERS
set(ProtoServer_SOURCES
${CURRENT_SOURCE_DIR}/ProtoServer.cpp
${CURRENT_SOURCE_DIR}/ProtoClientConnection.cpp
${CURRENT_SOURCE_DIR}/ProtoConnection.cpp
${CURRENT_SOURCE_DIR}/ProtoConnectionWrapper.cpp
)
set(ProtoServer_PROTOS

View File

@ -0,0 +1,188 @@
// stl includes
#include <stdexcept>
// Qt includes
#include <QRgb>
// protoserver includes
#include "protoserver/ProtoConnection.h"
ProtoConnection::ProtoConnection(const std::string & a) :
_socket(),
_skipReply(false)
{
QString address(a.c_str());
QStringList parts = address.split(":");
if (parts.size() != 2)
{
throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(address).toStdString());
}
_host = parts[0];
bool ok;
_port = parts[1].toUShort(&ok);
if (!ok)
{
throw std::runtime_error(QString("Wrong address: Unable to parse the port number (%1)").arg(parts[1]).toStdString());
}
// try to connect to host
std::cout << "Connecting to Hyperion: " << _host.toStdString() << ":" << _port << std::endl;
connectToHost();
}
ProtoConnection::~ProtoConnection()
{
_socket.close();
}
void ProtoConnection::setSkipReply(bool skip)
{
_skipReply = skip;
}
void ProtoConnection::setColor(const ColorRgb & color, int priority, int duration)
{
proto::HyperionRequest request;
request.set_command(proto::HyperionRequest::COLOR);
proto::ColorRequest * colorRequest = request.MutableExtension(proto::ColorRequest::colorRequest);
colorRequest->set_rgbcolor((color.red << 16) | (color.green << 8) | color.blue);
colorRequest->set_priority(priority);
colorRequest->set_duration(duration);
// send command message
sendMessage(request);
}
void ProtoConnection::setImage(const Image<ColorRgb> &image, int priority, int duration)
{
proto::HyperionRequest request;
request.set_command(proto::HyperionRequest::IMAGE);
proto::ImageRequest * imageRequest = request.MutableExtension(proto::ImageRequest::imageRequest);
imageRequest->set_imagedata(image.memptr(), image.width() * image.height() * 3);
imageRequest->set_imagewidth(image.width());
imageRequest->set_imageheight(image.height());
imageRequest->set_priority(priority);
imageRequest->set_duration(duration);
// send command message
sendMessage(request);
}
void ProtoConnection::clear(int priority)
{
proto::HyperionRequest request;
request.set_command(proto::HyperionRequest::CLEAR);
proto::ClearRequest * clearRequest = request.MutableExtension(proto::ClearRequest::clearRequest);
clearRequest->set_priority(priority);
// send command message
sendMessage(request);
}
void ProtoConnection::clearAll()
{
proto::HyperionRequest request;
request.set_command(proto::HyperionRequest::CLEARALL);
// send command message
sendMessage(request);
}
void ProtoConnection::connectToHost()
{
_socket.connectToHost(_host, _port);
if (_socket.waitForConnected()) {
std::cout << "Connected to Hyperion host" << std::endl;
}
}
void ProtoConnection::sendMessage(const proto::HyperionRequest &message)
{
if (_socket.state() == QAbstractSocket::UnconnectedState)
{
std::cout << "Currently disconnected: trying to connect to host" << std::endl;
connectToHost();
}
if (_socket.state() != QAbstractSocket::ConnectedState)
{
return;
}
// We only get here if we are connected
// serialize message (FastWriter already appends a newline)
std::string serializedMessage = message.SerializeAsString();
int length = serializedMessage.size();
const uint8_t header[] = {
uint8_t((length >> 24) & 0xFF),
uint8_t((length >> 16) & 0xFF),
uint8_t((length >> 8) & 0xFF),
uint8_t((length ) & 0xFF)};
// write message
int count = 0;
count += _socket.write(reinterpret_cast<const char *>(header), 4);
count += _socket.write(reinterpret_cast<const char *>(serializedMessage.data()), length);
if (!_socket.waitForBytesWritten())
{
std::cerr << "Error while writing data to host" << std::endl;
return;
}
if (!_skipReply)
{
// read reply data
QByteArray serializedReply;
length = -1;
while (length < 0 && serializedReply.size() < length+4)
{
// receive reply
if (!_socket.waitForReadyRead())
{
std::cerr << "Error while reading data from host" << std::endl;
return;
}
serializedReply += _socket.readAll();
if (length < 0 && serializedReply.size() >= 4)
{
// read the message size
length =
((serializedReply[0]<<24) & 0xFF000000) |
((serializedReply[1]<<16) & 0x00FF0000) |
((serializedReply[2]<< 8) & 0x0000FF00) |
((serializedReply[3] ) & 0x000000FF);
}
}
// parse reply data
proto::HyperionReply reply;
reply.ParseFromArray(serializedReply.constData()+4, length);
// parse reply message
parseReply(reply);
}
}
bool ProtoConnection::parseReply(const proto::HyperionReply &reply)
{
bool success = false;
if (!reply.success())
{
if (reply.has_error())
{
throw std::runtime_error("Error: " + reply.error());
}
else
{
throw std::runtime_error("Error: No error info");
}
}
return success;
}

View File

@ -0,0 +1,19 @@
// protoserver includes
#include "protoserver/ProtoConnectionWrapper.h"
ProtoConnectionWrapper::ProtoConnectionWrapper(const std::string & address, int priority, int duration_ms, bool skipProtoReply) :
_priority(priority),
_duration_ms(duration_ms),
_connection(address)
{
_connection.setSkipReply(skipProtoReply);
}
ProtoConnectionWrapper::~ProtoConnectionWrapper()
{
}
void ProtoConnectionWrapper::receiveImage(const Image<ColorRgb> & image)
{
_connection.setImage(image, _priority, _duration_ms);
}

View File

@ -13,6 +13,12 @@ add_library(hyperion-utils
${CURRENT_HEADER_DIR}/Image.h
${CURRENT_HEADER_DIR}/Sleep.h
${CURRENT_HEADER_DIR}/PixelFormat.h
${CURRENT_HEADER_DIR}/VideoMode.h
${CURRENT_HEADER_DIR}/ImageResampler.h
${CURRENT_SOURCE_DIR}/ImageResampler.cpp
${CURRENT_HEADER_DIR}/HsvTransform.h
${CURRENT_SOURCE_DIR}/HsvTransform.cpp
${CURRENT_HEADER_DIR}/RgbChannelTransform.h

View File

@ -0,0 +1,133 @@
#include "utils/ImageResampler.h"
ImageResampler::ImageResampler() :
_horizontalDecimation(1),
_verticalDecimation(1),
_cropLeft(0),
_cropRight(0),
_cropTop(0),
_cropBottom(0),
_videoMode(VIDEO_2D)
{
}
ImageResampler::~ImageResampler()
{
}
void ImageResampler::setHorizontalPixelDecimation(int decimator)
{
_horizontalDecimation = decimator;
}
void ImageResampler::setVerticalPixelDecimation(int decimator)
{
_verticalDecimation = decimator;
}
void ImageResampler::setCropping(int cropLeft, int cropRight, int cropTop, int cropBottom)
{
_cropLeft = cropLeft;
_cropRight = cropRight;
_cropTop = cropTop;
_cropBottom = cropBottom;
}
void ImageResampler::set3D(VideoMode mode)
{
_videoMode = mode;
}
void ImageResampler::processImage(const uint8_t * data, int width, int height, int lineLength, PixelFormat pixelFormat, Image<ColorRgb> &outputImage) const
{
int cropLeft = _cropLeft;
int cropRight = _cropRight;
int cropTop = _cropTop;
int cropBottom = _cropBottom;
// handle 3D mode
switch (_videoMode)
{
case VIDEO_3DSBS:
cropRight = width/2;
break;
case VIDEO_3DTAB:
cropBottom = height/2;
break;
default:
break;
}
// calculate the output size
int outputWidth = (width - cropLeft - cropRight - _horizontalDecimation/2 + _horizontalDecimation - 1) / _horizontalDecimation;
int outputHeight = (height - cropTop - cropBottom - _verticalDecimation/2 + _verticalDecimation - 1) / _verticalDecimation;
outputImage.resize(outputWidth, outputHeight);
for (int yDest = 0, ySource = cropTop + _verticalDecimation/2; yDest < outputHeight; ySource += _verticalDecimation, ++yDest)
{
for (int xDest = 0, xSource = cropLeft + _horizontalDecimation/2; xDest < outputWidth; xSource += _horizontalDecimation, ++xDest)
{
ColorRgb & rgb = outputImage(xDest, yDest);
switch (pixelFormat)
{
case PIXELFORMAT_UYVY:
{
int index = lineLength * ySource + xSource * 2;
uint8_t y = data[index+1];
uint8_t u = ((xSource&1) == 0) ? data[index ] : data[index-2];
uint8_t v = ((xSource&1) == 0) ? data[index+2] : data[index ];
yuv2rgb(y, u, v, rgb.red, rgb.green, rgb.blue);
}
break;
case PIXELFORMAT_YUYV:
{
int index = lineLength * ySource + xSource * 2;
uint8_t y = data[index];
uint8_t u = ((xSource&1) == 0) ? data[index+1] : data[index-1];
uint8_t v = ((xSource&1) == 0) ? data[index+3] : data[index+1];
yuv2rgb(y, u, v, rgb.red, rgb.green, rgb.blue);
}
break;
case PIXELFORMAT_RGB32:
{
int index = lineLength * ySource + xSource * 4;
rgb.red = data[index ];
rgb.green = data[index+1];
rgb.blue = data[index+2];
}
break;
case PIXELFORMAT_BGR32:
{
int index = lineLength * ySource + xSource * 4;
rgb.blue = data[index ];
rgb.green = data[index+1];
rgb.red = data[index+2];
}
break;
case PIXELFORMAT_NO_CHANGE:
std::cerr << "Invalid pixel format given" << std::endl;
break;
}
}
}
}
uint8_t ImageResampler::clamp(int x)
{
return (x<0) ? 0 : ((x>255) ? 255 : uint8_t(x));
}
void ImageResampler::yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t &r, uint8_t &g, uint8_t &b)
{
// see: http://en.wikipedia.org/wiki/YUV#Y.27UV444_to_RGB888_conversion
int c = y - 16;
int d = u - 128;
int e = v - 128;
r = clamp((298 * c + 409 * e + 128) >> 8);
g = clamp((298 * c - 100 * d - 208 * e + 128) >> 8);
b = clamp((298 * c + 516 * d + 128) >> 8);
}

View File

@ -1,5 +1,12 @@
add_subdirectory(hyperiond)
add_subdirectory(hyperion-remote)
if (ENABLE_V4L2)
# Add the 'Video 4 Linux' grabber if it is enabled
if(ENABLE_V4L2)
add_subdirectory(hyperion-v4l2)
endif (ENABLE_V4L2)
endif(ENABLE_V4L2)
# Add the X11 grabber if it is enabled
if(ENABLE_X11)
add_subdirectory(hyperion-x11)
endif(ENABLE_X11)

View File

@ -2,44 +2,29 @@ cmake_minimum_required(VERSION 2.8)
project(hyperion-v4l2)
# add protocol buffers
find_package(Protobuf REQUIRED)
# find Qt4
find_package(Qt4 REQUIRED QtCore QtGui QtNetwork)
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_BINARY_DIR}/../../libsrc/protoserver
${PROTOBUF_INCLUDE_DIRS}
${QT_INCLUDES}
)
set(Hyperion_V4L2_QT_HEADERS
ImageHandler.h
ScreenshotHandler.h
)
set(Hyperion_V4L2_HEADERS
VideoStandardParameter.h
PixelFormatParameter.h
ProtoConnection.h
)
set(Hyperion_V4L2_SOURCES
hyperion-v4l2.cpp
ProtoConnection.cpp
ImageHandler.cpp
ScreenshotHandler.cpp
)
set(Hyperion_V4L2_PROTOS
${CMAKE_CURRENT_SOURCE_DIR}/../../libsrc/protoserver/message.proto
)
protobuf_generate_cpp(Hyperion_V4L2_PROTO_SRCS Hyperion_V4L2_PROTO_HDRS
${Hyperion_V4L2_PROTOS}
)
QT4_WRAP_CPP(Hyperion_V4L2_MOC_SOURCES ${Hyperion_V4L2_QT_HEADERS})
add_executable(hyperion-v4l2
@ -47,8 +32,6 @@ add_executable(hyperion-v4l2
${Hyperion_V4L2_SOURCES}
${Hyperion_V4L2_QT_HEADERS}
${Hyperion_V4L2_MOC_SOURCES}
${Hyperion_V4L2_PROTO_SRCS}
${Hyperion_V4L2_PROTO_HDRS}
)
target_link_libraries(hyperion-v4l2
@ -56,7 +39,7 @@ target_link_libraries(hyperion-v4l2
getoptPlusPlus
blackborder
hyperion-utils
${PROTOBUF_LIBRARIES}
protoserver
pthread
${QT_LIBRARIES}
)

View File

@ -1,18 +0,0 @@
// hyperion-v4l2 includes
#include "ImageHandler.h"
ImageHandler::ImageHandler(const std::string & address, int priority, bool skipProtoReply) :
_priority(priority),
_connection(address)
{
_connection.setSkipReply(skipProtoReply);
}
ImageHandler::~ImageHandler()
{
}
void ImageHandler::receiveImage(const Image<ColorRgb> & image)
{
_connection.setImage(image, _priority, 1000);
}

View File

@ -1,31 +0,0 @@
// Qt includes
#include <QObject>
// hyperion includes
#include <utils/Image.h>
#include <utils/ColorRgb.h>
// hyperion v4l2 includes
#include "ProtoConnection.h"
/// This class handles callbacks from the V4L2 grabber
class ImageHandler : public QObject
{
Q_OBJECT
public:
ImageHandler(const std::string & address, int priority, bool skipProtoReply);
virtual ~ImageHandler();
public slots:
/// Handle a single image
/// @param image The image to process
void receiveImage(const Image<ColorRgb> & image);
private:
/// Priority for calls to Hyperion
const int _priority;
/// Hyperion proto connection object
ProtoConnection _connection;
};

View File

@ -2,7 +2,7 @@
#include <getoptPlusPlus/getoptpp.h>
// grabber includes
#include <grabber/PixelFormat.h>
#include <utils/PixelFormat.h>
using namespace vlofgren;

View File

@ -1,188 +0,0 @@
// stl includes
#include <stdexcept>
// Qt includes
#include <QRgb>
// hyperion-v4l2 includes
#include "ProtoConnection.h"
ProtoConnection::ProtoConnection(const std::string & a) :
_socket(),
_skipReply(false)
{
QString address(a.c_str());
QStringList parts = address.split(":");
if (parts.size() != 2)
{
throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(address).toStdString());
}
_host = parts[0];
bool ok;
_port = parts[1].toUShort(&ok);
if (!ok)
{
throw std::runtime_error(QString("Wrong address: Unable to parse the port number (%1)").arg(parts[1]).toStdString());
}
// try to connect to host
std::cout << "Connecting to Hyperion: " << _host.toStdString() << ":" << _port << std::endl;
connectToHost();
}
ProtoConnection::~ProtoConnection()
{
_socket.close();
}
void ProtoConnection::setSkipReply(bool skip)
{
_skipReply = skip;
}
void ProtoConnection::setColor(const ColorRgb & color, int priority, int duration)
{
proto::HyperionRequest request;
request.set_command(proto::HyperionRequest::COLOR);
proto::ColorRequest * colorRequest = request.MutableExtension(proto::ColorRequest::colorRequest);
colorRequest->set_rgbcolor((color.red << 16) | (color.green << 8) | color.blue);
colorRequest->set_priority(priority);
colorRequest->set_duration(duration);
// send command message
sendMessage(request);
}
void ProtoConnection::setImage(const Image<ColorRgb> &image, int priority, int duration)
{
proto::HyperionRequest request;
request.set_command(proto::HyperionRequest::IMAGE);
proto::ImageRequest * imageRequest = request.MutableExtension(proto::ImageRequest::imageRequest);
imageRequest->set_imagedata(image.memptr(), image.width() * image.height() * 3);
imageRequest->set_imagewidth(image.width());
imageRequest->set_imageheight(image.height());
imageRequest->set_priority(priority);
imageRequest->set_duration(duration);
// send command message
sendMessage(request);
}
void ProtoConnection::clear(int priority)
{
proto::HyperionRequest request;
request.set_command(proto::HyperionRequest::CLEAR);
proto::ClearRequest * clearRequest = request.MutableExtension(proto::ClearRequest::clearRequest);
clearRequest->set_priority(priority);
// send command message
sendMessage(request);
}
void ProtoConnection::clearAll()
{
proto::HyperionRequest request;
request.set_command(proto::HyperionRequest::CLEARALL);
// send command message
sendMessage(request);
}
void ProtoConnection::connectToHost()
{
_socket.connectToHost(_host, _port);
if (_socket.waitForConnected()) {
std::cout << "Connected to Hyperion host" << std::endl;
}
}
void ProtoConnection::sendMessage(const proto::HyperionRequest &message)
{
if (_socket.state() == QAbstractSocket::UnconnectedState)
{
std::cout << "Currently disconnected: trying to connect to host" << std::endl;
connectToHost();
}
if (_socket.state() != QAbstractSocket::ConnectedState)
{
return;
}
// We only get here if we are connected
// serialize message (FastWriter already appends a newline)
std::string serializedMessage = message.SerializeAsString();
int length = serializedMessage.size();
const uint8_t header[] = {
uint8_t((length >> 24) & 0xFF),
uint8_t((length >> 16) & 0xFF),
uint8_t((length >> 8) & 0xFF),
uint8_t((length ) & 0xFF)};
// write message
int count = 0;
count += _socket.write(reinterpret_cast<const char *>(header), 4);
count += _socket.write(reinterpret_cast<const char *>(serializedMessage.data()), length);
if (!_socket.waitForBytesWritten())
{
std::cerr << "Error while writing data to host" << std::endl;
return;
}
if (!_skipReply)
{
// read reply data
QByteArray serializedReply;
length = -1;
while (length < 0 && serializedReply.size() < length+4)
{
// receive reply
if (!_socket.waitForReadyRead())
{
std::cerr << "Error while reading data from host" << std::endl;
return;
}
serializedReply += _socket.readAll();
if (length < 0 && serializedReply.size() >= 4)
{
// read the message size
length =
((serializedReply[0]<<24) & 0xFF000000) |
((serializedReply[1]<<16) & 0x00FF0000) |
((serializedReply[2]<< 8) & 0x0000FF00) |
((serializedReply[3] ) & 0x000000FF);
}
}
// parse reply data
proto::HyperionReply reply;
reply.ParseFromArray(serializedReply.constData()+4, length);
// parse reply message
parseReply(reply);
}
}
bool ProtoConnection::parseReply(const proto::HyperionReply &reply)
{
bool success = false;
if (!reply.success())
{
if (reply.has_error())
{
throw std::runtime_error("Error: " + reply.error());
}
else
{
throw std::runtime_error("Error: No error info");
}
}
return success;
}

View File

@ -1,102 +0,0 @@
#pragma once
// stl includes
#include <string>
// Qt includes
#include <QColor>
#include <QImage>
#include <QTcpSocket>
#include <QMap>
// hyperion util
#include <utils/Image.h>
#include <utils/ColorRgb.h>
// jsoncpp includes
#include <message.pb.h>
///
/// Connection class to setup an connection to the hyperion server and execute commands
///
class ProtoConnection
{
public:
///
/// Constructor
///
/// @param address The address of the Hyperion server (for example "192.168.0.32:19444)
///
ProtoConnection(const std::string & address);
///
/// Destructor
///
~ProtoConnection();
/// Do not read reply messages from Hyperion if set to true
void setSkipReply(bool skip);
///
/// Set all leds to the specified color
///
/// @param color The color
/// @param priority The priority
/// @param duration The duration in milliseconds
///
void setColor(const ColorRgb & color, int priority, int duration = 1);
///
/// Set the leds according to the given image (assume the image is stretched to the display size)
///
/// @param image The image
/// @param priority The priority
/// @param duration The duration in milliseconds
///
void setImage(const Image<ColorRgb> & image, int priority, int duration = -1);
///
/// Clear the given priority channel
///
/// @param priority The priority
///
void clear(int priority);
///
/// Clear all priority channels
///
void clearAll();
private:
/// Try to connect to the Hyperion host
void connectToHost();
///
/// Send a command message and receive its reply
///
/// @param message The message to send
///
void sendMessage(const proto::HyperionRequest & message);
///
/// Parse a reply message
///
/// @param reply The received reply
///
/// @return true if the reply indicates success
///
bool parseReply(const proto::HyperionReply & reply);
private:
/// The TCP-Socket with the connection to the server
QTcpSocket _socket;
/// Host address
QString _host;
/// Host port
uint16_t _port;
/// Skip receiving reply messages from Hyperion if set
bool _skipReply;
};

View File

@ -15,11 +15,13 @@
// grabber includes
#include "grabber/V4L2Grabber.h"
// proto includes
#include "protoserver/ProtoConnection.h"
#include "protoserver/ProtoConnectionWrapper.h"
// hyperion-v4l2 includes
#include "ProtoConnection.h"
#include "VideoStandardParameter.h"
#include "PixelFormatParameter.h"
#include "ImageHandler.h"
#include "ScreenshotHandler.h"
using namespace vlofgren;
@ -100,6 +102,7 @@ int main(int argc, char** argv)
return 0;
}
// cropping values if not defined
if (!argCropLeft.isSet()) argCropLeft.setDefault(argCropWidth.getValue());
if (!argCropRight.isSet()) argCropRight.setDefault(argCropWidth.getValue());
if (!argCropTop.isSet()) argCropTop.setDefault(argCropHeight.getValue());
@ -152,7 +155,7 @@ int main(int argc, char** argv)
}
else
{
ImageHandler handler(argAddress.getValue(), argPriority.getValue(), argSkipReply.isSet());
ProtoConnectionWrapper handler(argAddress.getValue(), argPriority.getValue(), 1000, argSkipReply.isSet());
QObject::connect(&grabber, SIGNAL(newFrame(Image<ColorRgb>)), &handler, SLOT(receiveImage(Image<ColorRgb>)));
grabber.start();
QCoreApplication::exec();

View File

@ -0,0 +1,51 @@
# Configure minimum CMAKE version
cmake_minimum_required(VERSION 2.8)
# Set the project name
project(hyperion-x11)
# find Qt4
find_package(Qt4 REQUIRED QtCore QtGui QtNetwork)
# Find X11
find_package(X11 REQUIRED)
include_directories(
${CMAKE_CURRENT_BINARY_DIR}/../../libsrc/protoserver
${QT_INCLUDES}
${X11_INCLUDES}
)
set(Hyperion_X11_QT_HEADERS
X11Wrapper.h)
set(Hyperion_X11_HEADERS
)
set(Hyperion_X11_SOURCES
hyperion-x11.cpp
X11Wrapper.cpp
)
QT4_WRAP_CPP(Hyperion_X11_HEADERS_MOC ${Hyperion_X11_QT_HEADERS})
add_executable(hyperion-x11
${Hyperion_X11_HEADERS}
${Hyperion_X11_SOURCES}
${Hyperion_X11_HEADERS_MOC}
)
target_link_libraries(hyperion-x11
getoptPlusPlus
blackborder
hyperion-utils
protoserver
x11-grabber
${X11_LIBRARIES}
pthread
)
qt4_use_modules(hyperion-x11
Core
Gui
Network)

View File

@ -0,0 +1,36 @@
// Hyperion-X11 includes
#include "X11Wrapper.h"
X11Wrapper::X11Wrapper(int grabInterval, int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation) :
_timer(this),
_grabber(cropLeft, cropRight, cropTop, cropBottom, horizontalPixelDecimation, verticalPixelDecimation)
{
_timer.setSingleShot(false);
_timer.setInterval(grabInterval);
// Connect capturing to the timeout signal of the timer
connect(&_timer, SIGNAL(timeout()), this, SLOT(capture()));
}
const Image<ColorRgb> & X11Wrapper::getScreenshot()
{
const Image<ColorRgb> & screenshot = _grabber.grab();
return screenshot;
}
void X11Wrapper::start()
{
_timer.start();
}
void X11Wrapper::stop()
{
_timer.stop();
}
void X11Wrapper::capture()
{
const Image<ColorRgb> & screenshot = _grabber.grab();
emit sig_screenshot(screenshot);
}

View File

@ -0,0 +1,39 @@
// QT includes
#include <QTimer>
// Hyperion-X11 includes
#include <grabber/X11Grabber.h>
class X11Wrapper : public QObject
{
Q_OBJECT
public:
X11Wrapper(int grabInterval, int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation);
const Image<ColorRgb> & getScreenshot();
///
/// Starts the timed capturing of screenshots
///
void start();
void stop();
signals:
void sig_screenshot(const Image<ColorRgb> & screenshot);
private slots:
///
/// Performs a single screenshot capture and publishes the capture screenshot on the screenshot
/// signal.
///
void capture();
private:
/// The QT timer to generate capture-publish events
QTimer _timer;
/// The grabber for creating screenshots
X11Grabber _grabber;
};

View File

@ -0,0 +1,110 @@
// QT includes
#include <QCoreApplication>
#include <QImage>
// getoptPlusPLus includes
#include <getoptPlusPlus/getoptpp.h>
#include "protoserver/ProtoConnectionWrapper.h"
#include "X11Wrapper.h"
using namespace vlofgren;
// save the image as screenshot
void saveScreenshot(const char * filename, const Image<ColorRgb> & image)
{
// store as PNG
QImage pngImage((const uint8_t *) image.memptr(), image.width(), image.height(), 3*image.width(), QImage::Format_RGB888);
pngImage.save(filename);
}
int main(int argc, char ** argv)
{
QCoreApplication app(argc, argv);
try
{
// create the option parser and initialize all parameters
OptionsParser optionParser("X11 capture application for Hyperion");
ParameterSet & parameters = optionParser.getParameters();
IntParameter & argFps = parameters.add<IntParameter> ('f', "framerate", "Capture frame rate [default=10]");
IntParameter & argCropWidth = parameters.add<IntParameter> (0x0, "crop-width", "Number of pixels to crop from the left and right sides of the picture before decimation [default=0]");
IntParameter & argCropHeight = parameters.add<IntParameter> (0x0, "crop-height", "Number of pixels to crop from the top and the bottom of the picture before decimation [default=0]");
IntParameter & argCropLeft = parameters.add<IntParameter> (0x0, "crop-left", "Number of pixels to crop from the left of the picture before decimation (overrides --crop-width)");
IntParameter & argCropRight = parameters.add<IntParameter> (0x0, "crop-right", "Number of pixels to crop from the right of the picture before decimation (overrides --crop-width)");
IntParameter & argCropTop = parameters.add<IntParameter> (0x0, "crop-top", "Number of pixels to crop from the top of the picture before decimation (overrides --crop-height)");
IntParameter & argCropBottom = parameters.add<IntParameter> (0x0, "crop-bottom", "Number of pixels to crop from the bottom of the picture before decimation (overrides --crop-height)");
IntParameter & argSizeDecimation = parameters.add<IntParameter> ('s', "size-decimator", "Decimation factor for the output size [default=8]");
SwitchParameter<> & argScreenshot = parameters.add<SwitchParameter<>> (0x0, "screenshot", "Take a single screenshot, save it to file and quit");
StringParameter & argAddress = parameters.add<StringParameter> ('a', "address", "Set the address of the hyperion server [default: 127.0.0.1:19445]");
IntParameter & argPriority = parameters.add<IntParameter> ('p', "priority", "Use the provided priority channel (the lower the number, the higher the priority) [default: 800]");
SwitchParameter<> & argSkipReply = parameters.add<SwitchParameter<>> (0x0, "skip-reply", "Do not receive and check reply messages from Hyperion");
SwitchParameter<> & argHelp = parameters.add<SwitchParameter<>> ('h', "help", "Show this help message and exit");
// set defaults
argFps.setDefault(10);
argCropWidth.setDefault(0);
argCropHeight.setDefault(0);
argSizeDecimation.setDefault(8);
argAddress.setDefault("127.0.0.1:19445");
argPriority.setDefault(800);
// parse all options
optionParser.parse(argc, const_cast<const char **>(argv));
// check if we need to display the usage. exit if we do.
if (argHelp.isSet())
{
optionParser.usage();
return 0;
}
// cropping values if not defined
if (!argCropLeft.isSet()) argCropLeft.setDefault(argCropWidth.getValue());
if (!argCropRight.isSet()) argCropRight.setDefault(argCropWidth.getValue());
if (!argCropTop.isSet()) argCropTop.setDefault(argCropHeight.getValue());
if (!argCropBottom.isSet()) argCropBottom.setDefault(argCropHeight.getValue());
// Create the X11 grabbing stuff
int grabInterval = 1000 / argFps.getValue();
X11Wrapper x11Wrapper(
grabInterval,
argCropLeft.getValue(),
argCropRight.getValue(),
argCropTop.getValue(),
argCropBottom.getValue(),
argSizeDecimation.getValue(), // horizontal decimation
argSizeDecimation.getValue()); // vertical decimation
if (argScreenshot.isSet())
{
// Capture a single screenshot and finish
const Image<ColorRgb> & screenshot = x11Wrapper.getScreenshot();
saveScreenshot("screenshot.png", screenshot);
}
else
{
// Create the Proto-connection with hyperiond
ProtoConnectionWrapper protoWrapper(argAddress.getValue(), argPriority.getValue(), 1000, argSkipReply.isSet());
// Connect the screen capturing to the proto processing
QObject::connect(&x11Wrapper, SIGNAL(sig_screenshot(const Image<ColorRgb> &)), &protoWrapper, SLOT(receiveImage(Image<ColorRgb>)));
// Start the capturing
x11Wrapper.start();
// Start the application
app.exec();
}
}
catch (const std::runtime_error & e)
{
// An error occured. Display error and quit
std::cerr << e.what() << std::endl;
return -1;
}
return 0;
}

View File

@ -52,3 +52,11 @@ target_link_libraries(test_qregexp
add_executable(test_qtscreenshot TestQtScreenshot.cpp)
target_link_libraries(test_qtscreenshot
${QT_LIBRARIES})
if(ENABLE_X11)
# Find X11
find_package(X11 REQUIRED)
add_executable(test_x11performance TestX11Performance.cpp)
target_link_libraries(test_x11performance ${X11_LIBRARIES} ${QT_LIBRARIES})
endif(ENABLE_X11)

View File

@ -6,15 +6,67 @@
#include <QApplication>
#include <QDesktopWidget>
#include <QPixmap>
#include <QFile>
#include <QRgb>
#include <QElapsedTimer>
// Utils includes
#include <utils/Image.h>
#include <utils/ColorRgb.h>
void createScreenshot(const int cropHorizontal, const int cropVertical, const int decimation, Image<ColorRgb> & image)
{
// Create the full size screenshot
const QRect screenSize = QApplication::desktop()->screenGeometry();
const int croppedWidth = screenSize.width() - 2*cropVertical;
const int croppedHeight = screenSize.height() - 2*cropHorizontal;
const QPixmap fullSizeScreenshot = QPixmap::grabWindow(QApplication::desktop()->winId(), cropVertical, cropHorizontal, croppedWidth, croppedHeight);
// Scale the screenshot to the required size
const int width = fullSizeScreenshot.width()/decimation;
const int height = fullSizeScreenshot.height()/decimation;
const QPixmap scaledScreenshot = fullSizeScreenshot.scaled(width, height, Qt::IgnoreAspectRatio, Qt::FastTransformation);
// Convert the QPixmap to QImage in order to get out RGB values
const QImage qImage = scaledScreenshot.toImage();
// Make sure that the output image has the right size
image.resize(width, height);
// Copy the data into the output image
for (int y=0; y<qImage.height(); ++y)
{
for (int x=0; x<qImage.width(); ++x)
{
// Get the pixel at [x;y] (format int32 #AARRGGBB)
const QRgb inPixel = qImage.pixel(x,y);
// Copy the color channels into the output pixel
ColorRgb & outPixel = image(x,y);
outPixel.red = (inPixel & 0x00ff0000) >> 16;
outPixel.green = (inPixel & 0x0000ff00) >> 8;
outPixel.blue = (inPixel & 0x000000ff);
}
}
}
int main(int argc, char** argv)
{
int decimation = 10;
QApplication app(argc, argv);
QElapsedTimer timer;
QPixmap originalPixmap = QPixmap::grabWindow(QApplication::desktop()->winId());
Image<ColorRgb> screenshot(64,64);
std::cout << "Grabbed image: [" << originalPixmap.width() << "; " << originalPixmap.height() << "]" << std::endl;
int loopCnt = 100;
timer.start();
for (int i=0; i<loopCnt; ++i)
{
createScreenshot(0,0, decimation, screenshot);
}
std::cout << "Time required for single screenshot: " << timer.elapsed()/loopCnt << "ms" << std::endl;
return 0;
}

139
test/TestX11Performance.cpp Normal file
View File

@ -0,0 +1,139 @@
// X11 includes
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <QElapsedTimer>
#include <utils/Image.h>
#include <utils/ColorRgb.h>
void foo_1(int pixelDecimation)
{
int cropWidth = 0;
int cropHeight = 0;
Image<ColorRgb> image(64, 64);
/// Reference to the X11 display (nullptr if not opened)
Display * x11Display;
const char * display_name = nullptr;
x11Display = XOpenDisplay(display_name);
std::cout << "Opened display: " << x11Display << std::endl;
XWindowAttributes window_attributes_return;
XGetWindowAttributes(x11Display, DefaultRootWindow(x11Display), &window_attributes_return);
int screenWidth = window_attributes_return.width;
int screenHeight = window_attributes_return.height;
std::cout << "[" << screenWidth << "x" << screenHeight <<"]" << std::endl;
// Update the size of the buffer used to transfer the screenshot
int width = (screenWidth - 2 * cropWidth + pixelDecimation/2) / pixelDecimation;
int height = (screenHeight - 2 * cropHeight + pixelDecimation/2) / pixelDecimation;
image.resize(width, height);
const int croppedWidth = screenWidth - 2*cropWidth;
const int croppedHeight = screenHeight - 2*cropHeight;
QElapsedTimer timer;
timer.start();
XImage * xImage = XGetImage(x11Display, DefaultRootWindow(x11Display), cropWidth, cropHeight, croppedWidth, croppedHeight, AllPlanes, ZPixmap);
std::cout << "Captured image: " << xImage << std::endl;
// Copy the capture XImage to the local image (and apply required decimation)
ColorRgb * outputPtr = image.memptr();
for (int iY=(pixelDecimation/2); iY<croppedHeight; iY+=pixelDecimation)
{
for (int iX=(pixelDecimation/2); iX<croppedWidth; iX+=pixelDecimation)
{
// Extract the pixel from the X11-image
const uint32_t pixel = uint32_t(XGetPixel(xImage, iX, iY));
// Assign the color value
outputPtr->red = uint8_t((pixel >> 16) & 0xff);
outputPtr->green = uint8_t((pixel >> 8) & 0xff);
outputPtr->blue = uint8_t((pixel >> 0) & 0xff);
// Move to the next output pixel
++outputPtr;
}
}
// Cleanup allocated resources of the X11 grab
XDestroyImage(xImage);
std::cout << "Time required: " << timer.elapsed() << " ms" << std::endl;
XCloseDisplay(x11Display);
}
void foo_2(int pixelDecimation)
{
int cropWidth = 0;
int cropHeight = 0;
Image<ColorRgb> image(64, 64);
/// Reference to the X11 display (nullptr if not opened)
Display * x11Display;
const char * display_name = nullptr;
x11Display = XOpenDisplay(display_name);
XWindowAttributes window_attributes_return;
XGetWindowAttributes(x11Display, DefaultRootWindow(x11Display), &window_attributes_return);
int screenWidth = window_attributes_return.width;
int screenHeight = window_attributes_return.height;
std::cout << "[" << screenWidth << "x" << screenHeight <<"]" << std::endl;
// Update the size of the buffer used to transfer the screenshot
int width = (screenWidth - 2 * cropWidth + pixelDecimation/2) / pixelDecimation;
int height = (screenHeight - 2 * cropHeight + pixelDecimation/2) / pixelDecimation;
image.resize(width, height);
const int croppedWidth = screenWidth - 2*cropWidth;
const int croppedHeight = screenHeight - 2*cropHeight;
QElapsedTimer timer;
timer.start();
// Copy the capture XImage to the local image (and apply required decimation)
ColorRgb * outputPtr = image.memptr();
for (int iY=(pixelDecimation/2); iY<croppedHeight; iY+=pixelDecimation)
{
for (int iX=(pixelDecimation/2); iX<croppedWidth; iX+=pixelDecimation)
{
XImage * xImage = XGetImage(x11Display, DefaultRootWindow(x11Display), iX, iY, 1, 1, AllPlanes, ZPixmap);
// Extract the pixel from the X11-image
const uint32_t pixel = uint32_t(XGetPixel(xImage, 0, 0));
// Assign the color value
outputPtr->red = uint8_t((pixel >> 16) & 0xff);
outputPtr->green = uint8_t((pixel >> 8) & 0xff);
outputPtr->blue = uint8_t((pixel >> 0) & 0xff);
// Move to the next output pixel
++outputPtr;
// Cleanup allocated resources of the X11 grab
XDestroyImage(xImage);
}
}
std::cout << "Time required: " << timer.elapsed() << " ms" << std::endl;
XCloseDisplay(x11Display);
}
int main()
{
foo_1(10);
foo_2(10);
return 0;
}