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