]> git.sesse.net Git - kdenlive/blob - src/vectorscope.cpp
Vectorscope change: Show angle on mouse hover
[kdenlive] / src / vectorscope.cpp
1 /***************************************************************************
2  *   Copyright (C) 2010 by Simon Andreas Eugster (simon.eu@gmail.com)      *
3  *   This file is part of kdenlive. See www.kdenlive.org.                  *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  ***************************************************************************/
10
11 /**
12
13   Vectorscope.
14
15   The basis matrix for converting RGB to YUV is:
16
17 mRgb2Yuv =                       r =
18
19    0.29900   0.58700   0.11400     1.00000
20   -0.14741  -0.28939   0.43680  x  0.00000
21    0.61478  -0.51480  -0.09998     0.00000
22
23   The resulting YUV value is then drawn on the circle
24   using U and V as coordinate values.
25
26   The maximum length of such an UV vector is reached
27   for the colors Red and Cyan: 0.632.
28   To make optimal use of space in the circle, this value
29   can be used for scaling.
30
31   As we are dealing with RGB values in a range of {0,...,255}
32   and the conversion values are made for [0,1], we already
33   divide the conversion values by 255 previously, e.g. in
34   GNU octave.
35
36   See also:
37     http://de.wikipedia.org/wiki/Vektorskop
38     http://www.elektroniktutor.de/techno/vektskop.html
39
40  */
41
42 #include <QColor>
43 #include <QMouseEvent>
44 #include <QPainter>
45 #include <QDebug>
46 #include <QAction>
47
48 #include <qtconcurrentrun.h>
49 #include <QThread>
50 #include <QTime>
51
52 #include "vectorscope.h"
53
54 const float SCALING = 1/.7; // See class docs
55 const float P75 = .75;
56 const unsigned char DEFAULT_Y = 255;
57 const unsigned int REALTIME_FPS = 15; // in fps.
58
59 const QPointF YUV_R(-.147,  .615);
60 const QPointF YUV_G(-.289, -.515);
61 const QPointF YUV_B(.437, -.100);
62 const QPointF YUV_Cy(.147, -.615);
63 const QPointF YUV_Mg(.289,  .515);
64 const QPointF YUV_Yl(-.437,  .100);
65
66 const QPen penThick(QBrush(QColor(250,250,250)), 2, Qt::SolidLine);
67 const QPen penThin(QBrush(QColor(250,250,250)), 1, Qt::SolidLine);
68 const QPen penLight(QBrush(QColor(200,200,250,150)), 1, Qt::SolidLine);
69 const QPen penDark(QBrush(QColor(0,0,20,250)), 1, Qt::SolidLine);
70
71
72 Vectorscope::Vectorscope(Monitor *projMonitor, Monitor *clipMonitor, QWidget *parent) :
73     QWidget(parent),
74     m_projMonitor(projMonitor),
75     m_clipMonitor(clipMonitor),
76     m_activeRender(clipMonitor->render),
77     m_scaling(1),
78     m_skipPixels(1),
79     circleEnabled(false),
80     initialDimensionUpdateDone(false),
81     semaphore(1)
82 {
83     setupUi(this);
84
85     m_colorTools = new ColorTools();
86     m_colorPlaneExport = new ColorPlaneExport(this);
87
88     paintMode->addItem(i18n("Green"), QVariant(PAINT_GREEN));
89     paintMode->addItem(i18n("Green 2"), QVariant(PAINT_GREEN2));
90     paintMode->addItem(i18n("Black"), QVariant(PAINT_BLACK));
91     paintMode->addItem(i18n("Modified YUV (Chroma)"), QVariant(PAINT_CHROMA));
92     paintMode->addItem(i18n("YUV"), QVariant(PAINT_YUV));
93     paintMode->addItem(i18n("Original Color"), QVariant(PAINT_ORIG));
94
95     backgroundMode->addItem(i18n("None"), QVariant(BG_NONE));
96     backgroundMode->addItem(i18n("YUV"), QVariant(BG_YUV));
97     backgroundMode->addItem(i18n("Modified YUV (Chroma)"), QVariant(BG_CHROMA));
98
99     cbAutoRefresh->setChecked(true);
100
101     connect(backgroundMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotBackgroundChanged()));
102     connect(cbMagnify, SIGNAL(stateChanged(int)), this, SLOT(slotMagnifyChanged()));
103     connect(this, SIGNAL(signalScopeCalculationFinished(uint,uint)), this, SLOT(slotScopeCalculationFinished(uint,uint)));
104     connect(m_colorTools, SIGNAL(signalWheelCalculationFinished()), this, SLOT(slotWheelCalculationFinished()));
105     connect(paintMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateScope()));
106     connect(cbAutoRefresh, SIGNAL(stateChanged(int)), this, SLOT(slotUpdateScope()));
107
108     newFrames.fetchAndStoreRelaxed(0);
109     newChanges.fetchAndStoreRelaxed(0);
110     newWheelChanges.fetchAndStoreRelaxed(0);
111
112
113     ///// Build context menu /////
114     setContextMenuPolicy(Qt::ActionsContextMenu);
115
116     m_aExportBackground = new QAction(i18n("Export background"), this);
117     addAction(m_aExportBackground);
118     connect(m_aExportBackground, SIGNAL(triggered()), this, SLOT(slotExportBackground()));
119
120     m_a75PBox = new QAction(i18n("75% box"), this);
121     m_a75PBox->setCheckable(true);
122     m_a75PBox->setChecked(false);
123     addAction(m_a75PBox);
124     connect(m_a75PBox, SIGNAL(changed()), this, SLOT(update()));
125
126     m_aAxisEnabled = new QAction(i18n("Draw axis"), this);
127     m_aAxisEnabled->setCheckable(true);
128     m_aAxisEnabled->setChecked(false);
129     addAction(m_aAxisEnabled);
130     connect(m_aAxisEnabled, SIGNAL(changed()), this, SLOT(update()));
131
132     m_aRealtime = new QAction(i18n("Realtime (with precision loss)"), this);
133     m_aRealtime->setCheckable(true);
134     m_aRealtime->setChecked(false);
135     addAction(m_aRealtime);
136
137
138     this->setMouseTracking(true);
139     updateDimensions();
140     prodWheelThread();
141 }
142
143 Vectorscope::~Vectorscope()
144 {
145     delete m_colorPlaneExport;
146 }
147
148 /**
149
150   Input point is on [-1,1]², 0 being at the center,
151   and positive directions are top/right.
152
153   Maps to a QRect «inside» which is using the
154   0 point in the top left corner. The coordinates of
155   the rect «inside» are relative to «parent». The
156   coordinates returned can be used in the parent.
157
158
159     parent v
160   +-------------------+
161   | inside v          |
162   | +-----------+     |
163   | |    +      |     |
164   | |  --0++    |     | < point
165   | |    -      |     |
166   | +-----------+     |
167   |                   |
168   +-------------------+
169
170  */
171 QPoint Vectorscope::mapToCanvas(QRect inside, QPointF point)
172 {
173     return QPoint(point.x()/2*inside.width()  + inside.width()/2  + inside.x(),
174                  -point.y()/2*inside.height() + inside.height()/2 + inside.y());
175 }
176
177 bool Vectorscope::prodCalcThread()
178 {
179     bool ok = false;
180     if (m_scopeCalcThread.isRunning()) {
181         qDebug() << "Calc thread still running.";
182         ok = false;
183     } else {
184
185         // Acquire the semaphore. Don't release it anymore until the QFuture m_scopeCalcThread
186         // tells us that the thread has finished.
187         ok = semaphore.tryAcquire(1);
188         if (ok) {
189             qDebug() << "Calc thread not running anymore, finished: " << m_scopeCalcThread.isFinished() << ", Starting new thread";
190
191             // See http://doc.qt.nokia.com/latest/qtconcurrentrun.html#run about
192             // running member functions in a thread
193             m_scopeCalcThread = QtConcurrent::run(this, &Vectorscope::calculateScope);
194
195             newFrames.fetchAndStoreRelease(0); // Reset number of new frames, as we just got the newest
196             newChanges.fetchAndStoreRelease(0); // Do the same with the external changes counter
197         } else {
198             qDebug() << "Could not acquire semaphore. Deadlock avoided? Not starting new thread.";
199         }
200     }
201     return ok;
202 }
203
204 bool Vectorscope::prodWheelThread()
205 {
206     if (m_wheelCalcThread.isRunning()) {
207         qDebug() << "Wheel thread still running.";
208         return false;
209     } else {
210         switch (backgroundMode->itemData(backgroundMode->currentIndex()).toInt()) {
211         case BG_NONE:
212             qDebug() << "No background.";
213             m_wheel = QImage();
214             this->update();
215             break;
216         case BG_YUV:
217             qDebug() << "YUV background.";
218             m_wheelCalcThread = QtConcurrent::run(m_colorTools, &ColorTools::yuvColorWheel, m_scopeRect.size(), (unsigned char) 128, 1/SCALING, false, true);
219             break;
220         case BG_CHROMA:
221             m_wheelCalcThread = QtConcurrent::run(m_colorTools, &ColorTools::yuvColorWheel, m_scopeRect.size(), (unsigned char) 255, 1/SCALING, true, true);
222             break;
223         }
224         newWheelChanges.fetchAndStoreRelaxed(0);
225         return true;
226     }
227 }
228
229
230
231 void Vectorscope::calculateScope()
232 {
233     qDebug() << "..Scope rendering starts now.";
234     QTime start = QTime::currentTime();
235     unsigned int skipPixels = 1;
236     if (m_aRealtime->isChecked()) {
237         skipPixels = m_skipPixels;
238     }
239     const int stepsize = 4*skipPixels;
240
241     // Prepare the vectorscope data
242     QImage scope(cw, cw, QImage::Format_ARGB32);
243     scope.fill(qRgba(0,0,0,0));
244
245     QImage img(m_activeRender->extractFrame(m_activeRender->seekFramePosition()));
246     const uchar *bits = img.bits();
247
248     int r,g,b;
249     double dy, dr, dg, db, dmax;
250     double y,u,v;
251     QPoint pt;
252     QRgb px;
253
254     // Just an average for the number of image pixels per scope pixel.
255     double avgPxPerPx = (double) 4*img.byteCount()/scope.size().width()/scope.size().height()/skipPixels;
256     qDebug() << "Expecting " << avgPxPerPx << " pixels per pixel.";
257
258     const QRect scopeRect(QPoint(0,0), scope.size());
259
260     for (int i = 0; i < img.byteCount(); i+= stepsize) {
261         QRgb *col = (QRgb *) bits;
262
263         r = qRed(*col);
264         g = qGreen(*col);
265         b = qBlue(*col);
266
267         y = (double)  0.001173 * r +0.002302 * g +0.0004471* b;
268         u = (double) -0.0005781* r -0.001135 * g +0.001713 * b;
269         v = (double)  0.002411 * r -0.002019 * g -0.0003921* b;
270
271         pt = mapToCanvas(scopeRect, QPointF(SCALING*m_scaling*u, SCALING*m_scaling*v));
272
273         if (!(pt.x() <= scopeRect.width() && pt.x() >= 0
274             && pt.y() <= scopeRect.height() && pt.y() >= 0)) {
275             // Point lies outside (because of scaling), don't plot it
276
277         } else {
278
279             // Draw the pixel using the chosen draw mode.
280             switch (paintMode->itemData(paintMode->currentIndex()).toInt()) {
281             case PAINT_YUV:
282                 // see yuvColorWheel
283                 dy = 128; // Default Y value. Lower = darker.
284
285                 // Calculate the RGB values from YUV
286                 dr = dy + 290.8*v;
287                 dg = dy - 100.6*u - 148*v;
288                 db = dy + 517.2*u;
289
290                 if (dr < 0) dr = 0;
291                 if (dg < 0) dg = 0;
292                 if (db < 0) db = 0;
293                 if (dr > 255) dr = 255;
294                 if (dg > 255) dg = 255;
295                 if (db > 255) db = 255;
296
297                 scope.setPixel(pt, qRgba(dr, dg, db, 255));
298                 break;
299
300             case PAINT_CHROMA:
301                 dy = 200; // Default Y value. Lower = darker.
302
303                 // Calculate the RGB values from YUV
304                 dr = dy + 290.8*v;
305                 dg = dy - 100.6*u - 148*v;
306                 db = dy + 517.2*u;
307
308                 // Scale the RGB values back to max 255
309                 dmax = dr;
310                 if (dg > dmax) dmax = dg;
311                 if (db > dmax) dmax = db;
312                 dmax = 255/dmax;
313
314                 dr *= dmax;
315                 dg *= dmax;
316                 db *= dmax;
317
318                 scope.setPixel(pt, qRgba(dr, dg, db, 255));
319                 break;
320             case PAINT_ORIG:
321                 scope.setPixel(pt, *col);
322                 break;
323             case PAINT_GREEN:
324                 px = scope.pixel(pt);
325                 scope.setPixel(pt, qRgba(qRed(px)+(255-qRed(px))/(3*avgPxPerPx), qGreen(px)+20*(255-qGreen(px))/(avgPxPerPx),
326                                          qBlue(px)+(255-qBlue(px))/(avgPxPerPx), qAlpha(px)+(255-qAlpha(px))/(avgPxPerPx)));
327                 break;
328             case PAINT_GREEN2:
329                 px = scope.pixel(pt);
330                 scope.setPixel(pt, qRgba(qRed(px)+ceil((255-(float)qRed(px))/(4*avgPxPerPx)), 255,
331                                          qBlue(px)+ceil((255-(float)qBlue(px))/(avgPxPerPx)), qAlpha(px)+ceil((255-(float)qAlpha(px))/(avgPxPerPx))));
332                 break;
333             case PAINT_BLACK:
334                 px = scope.pixel(pt);
335                 scope.setPixel(pt, qRgba(0,0,0, qAlpha(px)+(255-qAlpha(px))/20));
336                 break;
337             }
338         }
339
340         bits += stepsize;
341     }
342
343     m_scope = scope;
344
345     unsigned int mseconds = start.msecsTo(QTime::currentTime());
346     qDebug() << "Scope rendered in " << mseconds << " ms. Sending finished signal.";
347     emit signalScopeCalculationFinished(mseconds, skipPixels);
348     qDebug() << "xxScope: Signal finished sent.";
349 }
350
351 void Vectorscope::updateDimensions()
352 {
353     // Widget width/height
354     int ww = this->size().width();
355     int wh = this->size().height();
356
357     // Distance from top/left/right
358     int offset = 6;
359
360     // controlsArea contains the controls at the top;
361     // We want to paint below
362     QPoint topleft(offset, controlsArea->geometry().height()+offset);
363
364     // Circle Width: min of width and height
365     cw = wh - topleft.y();
366     if (ww < cw) { cw = ww; }
367     cw -= 2*offset;
368     m_scopeRect = QRect(topleft, QPoint(cw, cw) + topleft);
369
370     centerPoint = mapToCanvas(m_scopeRect, QPointF(0,0));
371     pR75 = mapToCanvas(m_scopeRect, P75*SCALING*YUV_R);
372     pG75 = mapToCanvas(m_scopeRect, P75*SCALING*YUV_G);
373     pB75 = mapToCanvas(m_scopeRect, P75*SCALING*YUV_B);
374     pCy75 = mapToCanvas(m_scopeRect, P75*SCALING*YUV_Cy);
375     pMg75 = mapToCanvas(m_scopeRect, P75*SCALING*YUV_Mg);
376     pYl75 = mapToCanvas(m_scopeRect, P75*SCALING*YUV_Yl);
377 }
378
379 void Vectorscope::paintEvent(QPaintEvent *)
380 {
381
382     if (!initialDimensionUpdateDone) {
383         // This is a workaround.
384         // When updating the dimensions in the constructor, the size
385         // of the control items at the top are simply ignored! So do
386         // it here instead.
387         updateDimensions();
388         initialDimensionUpdateDone = true;
389     }
390
391     // Draw the vectorscope circle
392     QPainter davinci(this);
393     QPoint vinciPoint;
394
395
396     davinci.setRenderHint(QPainter::Antialiasing, true);
397     davinci.fillRect(0, 0, this->size().width(), this->size().height(), QColor(25,25,23));
398
399     davinci.drawImage(m_scopeRect.topLeft(), m_wheel);
400
401     davinci.setPen(penThick);
402     davinci.drawEllipse(m_scopeRect);
403
404     // Draw RGB/CMY points with 100% chroma
405     vinciPoint = mapToCanvas(m_scopeRect, SCALING*YUV_R);
406     davinci.drawEllipse(vinciPoint, 4,4);
407     davinci.drawText(vinciPoint-QPoint(20, -10), "R");
408
409     vinciPoint = mapToCanvas(m_scopeRect, SCALING*YUV_G);
410     davinci.drawEllipse(vinciPoint, 4,4);
411     davinci.drawText(vinciPoint-QPoint(20, 0), "G");
412
413     vinciPoint = mapToCanvas(m_scopeRect, SCALING*YUV_B);
414     davinci.drawEllipse(vinciPoint, 4,4);
415     davinci.drawText(vinciPoint+QPoint(15, 10), "B");
416
417     vinciPoint = mapToCanvas(m_scopeRect, SCALING*YUV_Cy);
418     davinci.drawEllipse(vinciPoint, 4,4);
419     davinci.drawText(vinciPoint+QPoint(15, -5), "Cy");
420
421     vinciPoint = mapToCanvas(m_scopeRect, SCALING*YUV_Mg);
422     davinci.drawEllipse(vinciPoint, 4,4);
423     davinci.drawText(vinciPoint+QPoint(15, 10), "Mg");
424
425     vinciPoint = mapToCanvas(m_scopeRect, SCALING*YUV_Yl);
426     davinci.drawEllipse(vinciPoint, 4,4);
427     davinci.drawText(vinciPoint-QPoint(25, 0), "Yl");
428
429     switch (backgroundMode->itemData(backgroundMode->currentIndex()).toInt()) {
430     case BG_NONE:
431         davinci.setPen(penLight);
432         break;
433     default:
434         davinci.setPen(penDark);
435         break;
436     }
437
438     // Draw axis
439     if (m_aAxisEnabled->isChecked()) {
440         davinci.drawLine(mapToCanvas(m_scopeRect, QPointF(0,-.9)), mapToCanvas(m_scopeRect, QPointF(0,.9)));
441         davinci.drawLine(mapToCanvas(m_scopeRect, QPointF(-.9,0)), mapToCanvas(m_scopeRect, QPointF(.9,0)));
442     }
443
444     // Draw center point
445     switch (backgroundMode->itemData(backgroundMode->currentIndex()).toInt()) {
446     case BG_CHROMA:
447         davinci.setPen(penDark);
448         break;
449     default:
450         davinci.setPen(penThin);
451         break;
452     }
453     davinci.drawEllipse(centerPoint, 5,5);
454
455
456     // Draw 75% box
457     if (m_a75PBox->isChecked()) {
458         davinci.drawLine(pR75, pYl75);
459         davinci.drawLine(pYl75, pG75);
460         davinci.drawLine(pG75, pCy75);
461         davinci.drawLine(pCy75, pB75);
462         davinci.drawLine(pB75, pMg75);
463         davinci.drawLine(pMg75, pR75);
464     }
465
466     // Draw RGB/CMY points with 75% chroma (for NTSC)
467     davinci.setPen(penThin);
468     davinci.drawEllipse(pR75, 3,3);
469     davinci.drawEllipse(pG75, 3,3);
470     davinci.drawEllipse(pB75, 3,3);
471     davinci.drawEllipse(pCy75, 3,3);
472     davinci.drawEllipse(pMg75, 3,3);
473     davinci.drawEllipse(pYl75, 3,3);
474
475
476
477     // Draw the scope data (previously calculated in a separate thread)
478     davinci.drawImage(m_scopeRect.topLeft(), m_scope);
479
480
481     if (circleEnabled) {
482         // Mouse moved: Draw a circle over the scope
483
484         int dx = -centerPoint.x()+mousePos.x();
485         int dy =  centerPoint.y()-mousePos.y();
486
487         QPoint reference = mapToCanvas(m_scopeRect, QPointF(1,0));
488
489         float r = sqrt(dx*dx + dy*dy);
490         float percent = (float) 100*r/SCALING/m_scaling/(reference.x() - centerPoint.x());
491
492         switch (backgroundMode->itemData(backgroundMode->currentIndex()).toInt()) {
493         case BG_NONE:
494             davinci.setPen(penLight);
495             break;
496         default:
497             davinci.setPen(penDark);
498             break;
499         }
500         davinci.drawEllipse(centerPoint, (int)r, (int)r);
501         davinci.setPen(penThin);
502         davinci.drawText(m_scopeRect.bottomRight()-QPoint(40,0), i18n("%1 \%", QString::number(percent, 'f', 0)));
503         
504         float angle = copysign(acos(dx/r)*180/M_PI, dy);
505         davinci.drawText(m_scopeRect.bottomLeft()+QPoint(10,0), i18n("%1°", QString::number(angle, 'f', 1)));
506
507         circleEnabled = false;
508     }
509
510     // Draw realtime factor (number of skipped pixels)
511     if (m_aRealtime->isChecked()) {
512         davinci.setPen(penThin);
513         davinci.drawText(m_scopeRect.bottomRight()-QPoint(40,15), QVariant(m_skipPixels).toString().append("x"));
514     }
515
516 }
517
518
519
520
521 ///// Slots /////
522
523 void Vectorscope::slotMagnifyChanged()
524 {
525     if (cbMagnify->isChecked()) {
526         m_scaling = 1.4;
527     } else {
528         m_scaling = 1;
529     }
530     prodCalcThread();
531 }
532
533 void Vectorscope::slotActiveMonitorChanged(bool isClipMonitor)
534 {
535     if (isClipMonitor) {
536         m_activeRender = m_clipMonitor->render;
537         disconnect(this, SLOT(slotRenderZoneUpdated()));
538         connect(m_activeRender, SIGNAL(rendererPosition(int)), this, SLOT(slotRenderZoneUpdated()));
539     } else {
540         m_activeRender = m_projMonitor->render;
541         disconnect(this, SLOT(slotRenderZoneUpdated()));
542         connect(m_activeRender, SIGNAL(rendererPosition(int)), this, SLOT(slotRenderZoneUpdated()));
543     }
544 }
545
546 void Vectorscope::slotRenderZoneUpdated()
547 {
548     qDebug() << "Monitor incoming. New frames total: " << newFrames;
549     // Monitor has shown a new frame
550     newFrames.fetchAndAddRelaxed(1);
551     if (cbAutoRefresh->isChecked()) {
552         prodCalcThread();
553     }
554 }
555
556 void Vectorscope::slotScopeCalculationFinished(unsigned int mseconds, unsigned int skipPixels)
557 {
558     qDebug() << "Received finished signal.";
559     if (!m_scopeCalcThread.isFinished()) {
560         // Wait for the thread to finish. Otherwise the scope might not get updated
561         // as prodCalcThread may see it still running.
562         QTime start = QTime::currentTime();
563         qDebug() << "Scope renderer has not finished yet although finished signal received, waiting ...";
564         m_scopeCalcThread.waitForFinished();
565         qDebug() << "Waiting for finish is over. Waited for " << start.msecsTo(QTime::currentTime()) << " ms";
566     }
567     semaphore.release();
568
569     this->update();
570     qDebug() << "Scope updated.";
571
572     if (m_aRealtime->isChecked()) {
573         m_skipPixels = ceil((float)REALTIME_FPS*mseconds*skipPixels/1000);
574         Q_ASSERT(m_skipPixels >= 1);
575         qDebug() << "Realtime checked. Switching from " << skipPixels << " to " << m_skipPixels;
576
577     } else {
578         qDebug() << "No realtime.";
579     }
580
581     // If auto-refresh is enabled and new frames are available,
582     // just start the next calculation.
583     if (newFrames > 0 && cbAutoRefresh->isChecked()) {
584         qDebug() << "Found more frames in the queue (prodding now): " << newFrames;
585         prodCalcThread();
586     } else if (newChanges > 0) {
587         qDebug() << newChanges << " changes (e.g. resize) in the meantime.";
588         prodCalcThread();
589     } else {
590         qDebug() << newFrames << " new frames, " << newChanges << " new changes. Not updating.";
591     }
592 }
593
594 void Vectorscope::slotWheelCalculationFinished()
595 {
596     if (!m_wheelCalcThread.isFinished()) {
597         QTime start = QTime::currentTime();
598         qDebug() << "Wheel calc has not finished yet, waiting ...";
599         m_wheelCalcThread.waitForFinished();
600         qDebug() << "Done. Waited for " << start.msecsTo(QTime::currentTime()) << " ms";
601     }
602
603     qDebug() << "Wheel calculated. Updating.";
604     qDebug() << m_wheelCalcThread.resultCount() << " results from the Wheel thread.";
605     if (m_wheelCalcThread.resultCount() > 0) {
606         m_wheel = m_wheelCalcThread.resultAt(0);
607     }
608     this->update();
609     if (newWheelChanges > 0) {
610         prodWheelThread();
611     }
612 }
613
614 void Vectorscope::slotUpdateScope()
615 {
616     prodCalcThread();
617 }
618
619 void Vectorscope::slotUpdateWheel()
620 {
621     prodWheelThread();
622 }
623
624 void Vectorscope::slotExportBackground()
625 {
626     qDebug() << "Exporting background";
627     m_colorPlaneExport->show();
628
629 }
630
631 void Vectorscope::slotBackgroundChanged()
632 {
633     // Background changed, switch to a suitable color mode now
634     int index;
635     switch (backgroundMode->itemData(backgroundMode->currentIndex()).toInt()) {
636     case BG_YUV:
637         index = paintMode->findData(QVariant(PAINT_BLACK));
638         if (index >= 0) {
639             paintMode->setCurrentIndex(index);
640         }
641         break;
642
643     case BG_NONE:
644         if (paintMode->itemData(paintMode->currentIndex()).toInt() == PAINT_BLACK) {
645             index = paintMode->findData(QVariant(PAINT_GREEN));
646             paintMode->setCurrentIndex(index);
647         }
648         break;
649     }
650     newWheelChanges.fetchAndAddAcquire(1);
651     prodWheelThread();
652 }
653
654
655
656 ///// Events /////
657
658 void Vectorscope::mousePressEvent(QMouseEvent *)
659 {
660     // Update the scope on mouse press
661     prodCalcThread();
662 }
663
664 void Vectorscope::mouseMoveEvent(QMouseEvent *event)
665 {
666     // Draw a circle around the center,
667     // showing percentage number of the radius length
668
669     circleEnabled = true;
670     mousePos = event->pos();
671     this->update();
672 }
673
674 void Vectorscope::resizeEvent(QResizeEvent *event)
675 {
676     qDebug() << "Resized.";
677     updateDimensions();
678     newChanges.fetchAndAddAcquire(1);
679     prodCalcThread();
680     prodWheelThread();
681     QWidget::resizeEvent(event);
682 }