summaryrefslogtreecommitdiffstats
path: root/src/client/vnc/vncwindow.cpp
diff options
context:
space:
mode:
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)