summaryrefslogtreecommitdiffstats
path: root/src/client/vnc/vncwindow.cpp
diff options
context:
space:
mode:
authorSimon Rettberg2018-07-20 18:21:41 +0200
committerSimon Rettberg2018-07-20 18:21:41 +0200
commit7b57706df76a675592c026264d3a2028ed4b47b5 (patch)
tree3db5146b718e9cfb29e6d61461e36a3b587eb2b8 /src/client/vnc/vncwindow.cpp
parentCMake: Add config fields for compiler flags (diff)
downloadpvs2-7b57706df76a675592c026264d3a2028ed4b47b5.tar.gz
pvs2-7b57706df76a675592c026264d3a2028ed4b47b5.tar.xz
pvs2-7b57706df76a675592c026264d3a2028ed4b47b5.zip
[client] Rewrite thread sync for VNC yet again
Move processing of image (scaling) to GUI thread. Get rid of second (scaled) image buffer. Instead, whenever we redraw parts of the VNC viewer, the according image parts will be copied and scaled from the buffer the vncclient thread is using. The buffer is wrapped in a QImage and handed over using a QSharedPointer, so reinitializing the buffer on the fly should yield no problems.
Diffstat (limited to 'src/client/vnc/vncwindow.cpp')
-rw-r--r--src/client/vnc/vncwindow.cpp125
1 files changed, 100 insertions, 25 deletions
diff --git a/src/client/vnc/vncwindow.cpp b/src/client/vnc/vncwindow.cpp
index f5a9337..79352ae 100644
--- a/src/client/vnc/vncwindow.cpp
+++ b/src/client/vnc/vncwindow.cpp
@@ -21,9 +21,23 @@
#include <QTimer>
+/**
+ * Calc greatest common divisor.
+ *
+ * @param a one number
+ * @param b another number
+ * @return greatest common divisor of a and b
+ */
+static int gcd(int a, int b)
+{
+ if (b == 0)
+ return a;
+ return gcd(b, a % b);
+}
+
VncWindow::VncWindow(QWidget *parent) :
- QWidget(parent), _vncWorker(NULL), _viewOnly(true), _multiScreen(false), _clientId(0), _redrawTimer(0), _tcpTimeoutTimer(0),
- _image(new QImage)
+ QWidget(parent), _srcStepX(1), _srcStepY(1), _dstStepX(1), _dstStepY(1),
+ _vncWorker(NULL), _viewOnly(true), _multiScreen(false), _clientId(0), _redrawTimer(0), _tcpTimeoutTimer(0)
{
QTimer *upper = new QTimer(this);
connect(upper, SIGNAL(timeout()), this, SLOT(timer_moveToTop()));
@@ -74,7 +88,6 @@ void VncWindow::deleteVncThread()
/**
* Draws given part of the current VNC frame buffer to the window.
- * Gets the image from the _vncWorker thread in an unsafe way. :(
*
* @param x X offset of the region to draw
* @param y Y offset of the region to draw
@@ -85,11 +98,75 @@ void VncWindow::draw(const int x, const int y, const int w, const int h)
{
if (_vncWorker == NULL)
return;
+ QSharedPointer<QImage> buffer = _vncWorker->getFrameBuffer();
+ if (buffer.isNull())
+ return;
+ this->calcScaling(buffer.data());
+
QPainter painter(this);
- painter.drawImage(x, y, *_image, x, y, w, h);
+ if (_dstStepX > 1 || _dstStepY > 1) {
+ // Scaling is required as vnc server and client are using different resolutions
+ // Calc section offsets first
+ const int startX = x / _dstStepX;
+ const int startY = y / _dstStepY;
+ const int endX = (x + w - 1) / _dstStepX + 1;
+ const int endY = (y + h - 1) / _dstStepY + 1;
+ // Now pixel offsets for source
+ const int dstX = startX * _dstStepX;
+ const int dstY = startY * _dstStepY;
+ const int dstW = endX * _dstStepX - dstX;
+ const int dstH = endY * _dstStepY - dstY;
+ // Pixel offsets for destination
+ const int srcX = startX * _srcStepX;
+ const int srcY = startY * _srcStepY;
+ const int srcW = endX * _srcStepX - srcX;
+ const int srcH = endY * _srcStepY - srcY;
+ // Rescale
+ QImage scaled(
+ buffer->copy(srcX, srcY, srcW, srcH).scaled(dstW, dstH, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
+ painter.drawImage(dstX, dstY, scaled, 0, 0, dstW, dstH);
+ } else {
+ // Same resolution, nothing to do
+ painter.drawImage(x, y, *buffer, x, y, w, h);
+ }
//painter.drawRect(x,y,w,h); // for debugging updated area
}
+/**
+ * When using image scaling, calc matching pixel borders for both
+ * resolutions to prevent artifacts through bilinear scaling.
+ * If you simply round to the nearest number when scaling, you would
+ * slightly stretch or shrink the image area, which leads to strange
+ * sub-pixel-movements of parts of the image when being updated.
+ * The values calculated here are used to determine a larger area around
+ * the area that should actually be updated, that will not cause any
+ * artifacts when scaling down/up.
+ *
+ * @return whether scaling factors changed
+ */
+bool VncWindow::calcScaling(const QImage *remote)
+{
+ if (this->size() == _desiredSize &&
+ (remote == NULL || remote->size() == _remoteSize))
+ return false;
+ const QSize mySize = this->size();
+ const QSize remoteSize = remote == NULL ? _remoteSize : remote->size();
+ if (mySize.isEmpty())
+ return false;
+ if (remoteSize.isEmpty())
+ return false;
+ _remoteSize = remoteSize;
+ _desiredSize = mySize;
+ const int gcdX = gcd(_desiredSize.width(), _remoteSize.width());
+ const int gcdY = gcd(_desiredSize.height(), _remoteSize.height());
+ _srcStepX = _remoteSize.width() / gcdX;
+ _srcStepY = _remoteSize.height() / gcdY;
+ _dstStepX = _desiredSize.width() / gcdX;
+ _dstStepY = _desiredSize.height() / gcdY;
+ qDebug() << "Scaling updated to" << _remoteSize << "->" << _desiredSize;
+ return true;
+}
+
////////////////////////////////////////////////////////////////////////////////
// Public
@@ -154,8 +231,7 @@ void VncWindow::open(const QString& host, int port, const QString& passwd, bool
showNormal();
}
- this->repaint();
- _vncWorker->setTargetBuffer(_image);
+ this->update();
_tcpTimeoutTimer = startTimer(10000);
_vncWorker->start(QThread::LowPriority);
@@ -183,8 +259,9 @@ void VncWindow::closeEvent(QCloseEvent * /* e */ )
/**
* Triggered by imageUpdate signal from the _vncWorker thread telling
* that the given region of the image changed. Simply repaints the
- * given area of the window. Coordinates have already been scaled
- * accordingly.
+ * given area of the window.
+ * Since these are client coordinates we might have to do
+ * some scaling.
*
* @param x X offset of the region to update
* @param y Y offset of the region to update
@@ -193,7 +270,21 @@ void VncWindow::closeEvent(QCloseEvent * /* e */ )
*/
void VncWindow::onUpdateImage(const int x, const int y, const int w, const int h)
{
- this->repaint(x, y, w, h);
+ if (_vncWorker == NULL)
+ return;
+ if (!this->calcScaling(_vncWorker->getFrameBuffer().data()) && !_remoteSize.isEmpty()) {
+ if (_srcStepX > 1 || _srcStepY > 1) {
+ this->update((x * _desiredSize.width()) / _remoteSize.width() - 2,
+ (y * _desiredSize.height()) / _remoteSize.height() - 2,
+ (w * _desiredSize.width()) / _remoteSize.width() + 4,
+ (h * _desiredSize.height()) / _remoteSize.height() + 4);
+ } else {
+ this->update(x, y, w, h);
+ }
+ } else {
+ // Changed, redraw everything
+ this->update();
+ }
}
/**
@@ -290,22 +381,6 @@ void VncWindow::paintEvent(QPaintEvent *event)
}
/**
- * Called by Qt if the window is being resized.
- *
- * @param event the resize event
- */
-void VncWindow::resizeEvent(QResizeEvent* event)
-{
- const QSize size = event->size();
- if (size != _image->size()) {
- _image = QSharedPointer<QImage>(new QImage(size, QImage::Format_RGB32));
- }
- if (_vncWorker != NULL) {
- _vncWorker->setTargetBuffer(_image);
- }
-}
-
-/**
* Called when user releases a pressed key and the window has focus
*/
void VncWindow::keyReleaseEvent(QKeyEvent* event)