]> git.sesse.net Git - kdenlive/blob - src/vectorscope.cpp
f873ac4a10173daddc5db4ed8f8d23cd7b730417
[kdenlive] / src / vectorscope.cpp
1 /***************************************************************************
2  *   Copyright (C) 2010 by Simon Andreas Eugster (simon.eu@gmail.com)      *
3  *                                                                         *
4  *   This program is free software; you can redistribute it and/or modify  *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  ***************************************************************************/
9
10 /**
11
12   Vectorscope.
13
14   The basis matrix for converting RGB to YUV is:
15
16 mRgb2Yuv =                       r =
17
18    0.29900   0.58700   0.11400     1.00000
19   -0.14741  -0.28939   0.43680  x  0.00000
20    0.61478  -0.51480  -0.09998     0.00000
21
22   The resulting YUV value is then drawn on the circle
23   using U and V as coordinate values.
24
25   The maximum length of such an UV vector is reached
26   for the colors Red and Cyan: 0.632.
27   To make optimal use of space in the circle, this value
28   can be used for scaling.
29
30   As we are dealing with RGB values in a range of {0,...,255}
31   and the conversion values are made for [0,1], we already
32   divide the conversion values by 255 previously, e.g. in
33   GNU octave.
34  */
35
36 #include <QColor>
37 #include <QMouseEvent>
38 #include <QPainter>
39 #include <QDebug>
40
41 #include "vectorscope.h"
42
43 const float SCALING = 1/.7; // See class docs
44 const float P75 = .75;
45 const QPointF YUV_R(-.147,  .615);
46 const QPointF YUV_G(-.289, -.515);
47 const QPointF YUV_B(.437, -.100);
48 const QPointF YUV_Cy(.147, -.615);
49 const QPointF YUV_Mg(.289,  .515);
50 const QPointF YUV_Yl(-.437,  .100);
51
52 const QPen penThick(QBrush(QColor(250,250,250)), 2, Qt::SolidLine);
53 const QPen penThin(QBrush(QColor(250,250,250)), 1, Qt::SolidLine);
54 const QPen penLight(QBrush(QColor(144,255,100,50)), 1, Qt::SolidLine);
55
56
57 Vectorscope::Vectorscope(Render *projRender, Render *clipRender, QWidget *parent) :
58     QWidget(parent),
59     m_projRender(projRender),
60     m_clipRender(clipRender),
61     m_activeRender(clipRender),
62     iPaintMode(GREEN),
63     scaling(1),
64     circleOnly(false)
65 {
66     setupUi(this);
67
68     paintMode->insertItem(GREEN, i18n("Green"));
69     paintMode->insertItem(CHROMA, i18n("Chroma"));
70     paintMode->insertItem(ORIG, i18n("Original Color"));
71     cbAutoRefresh->setEnabled(false);
72
73     connect(paintMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPaintModeChanged(int)));
74     connect(cbMagnify, SIGNAL(stateChanged(int)), this, SLOT(slotMagnifyChanged()));
75
76     this->setMouseTracking(true);
77 }
78
79 Vectorscope::~Vectorscope()
80 {
81 }
82
83 /**
84
85   Input point is on [-1,1]², 0 being at the center,
86   and positive directions are top/right.
87
88   Maps to a QRect «inside» which is using the
89   0 point in the top left corner. The coordinates of
90   the rect «inside» are relative to «parent». The
91   coordinates returned can be used in the parent.
92
93
94     parent v
95   +-------------------+
96   | inside v          |
97   | +-----------+     |
98   | |    +      |     |
99   | |  --0++    |     | < point
100   | |    -      |     |
101   | +-----------+     |
102   |                   |
103   +-------------------+
104
105  */
106 QPoint Vectorscope::mapToCanvas(QRect inside, QPointF point)
107 {
108     return QPoint(point.x()/2*inside.width()  + inside.width()/2  + inside.x(),
109                  -point.y()/2*inside.height() + inside.height()/2 + inside.y());
110 }
111
112 void Vectorscope::paintEvent(QPaintEvent *)
113 {
114     // Widget width/height
115     int ww = this->size().width();
116     int wh = this->size().height();
117
118     // Distance from top/left/right
119     int offset = 6;
120
121     // controlsArea contains the controls at the top;
122     // We want to paint below
123     QPoint topleft(offset, controlsArea->geometry().height()+offset);
124
125     // Circle Width: min of width and height
126     int cw = wh - topleft.y();
127     if (ww < cw) { cw = ww; }
128     cw -= 2*offset;
129     QRect scopeRect(topleft, QPoint(cw, cw) + topleft);
130
131
132     // Draw the vectorscope circle
133     QPainter davinci(this);
134     QPoint vinciPoint;
135     QPoint centerPoint = mapToCanvas(scopeRect, QPointF(0,0));
136     davinci.setRenderHint(QPainter::Antialiasing, true);
137     davinci.fillRect(0, 0, ww, wh, QColor(25,25,23));
138
139     davinci.setPen(penThick);
140     davinci.drawEllipse(scopeRect);
141
142     // Draw RGB/CMY points with 100% chroma
143     vinciPoint = mapToCanvas(scopeRect, SCALING*YUV_R);
144     davinci.drawEllipse(vinciPoint, 4,4);
145     davinci.drawText(vinciPoint-QPoint(20, -10), "R");
146
147     vinciPoint = mapToCanvas(scopeRect, SCALING*YUV_G);
148     davinci.drawEllipse(vinciPoint, 4,4);
149     davinci.drawText(vinciPoint-QPoint(20, 0), "G");
150
151     vinciPoint = mapToCanvas(scopeRect, SCALING*YUV_B);
152     davinci.drawEllipse(vinciPoint, 4,4);
153     davinci.drawText(vinciPoint+QPoint(15, 10), "B");
154
155     vinciPoint = mapToCanvas(scopeRect, SCALING*YUV_Cy);
156     davinci.drawEllipse(vinciPoint, 4,4);
157     davinci.drawText(vinciPoint+QPoint(15, -5), "Cy");
158
159     vinciPoint = mapToCanvas(scopeRect, SCALING*YUV_Mg);
160     davinci.drawEllipse(vinciPoint, 4,4);
161     davinci.drawText(vinciPoint+QPoint(15, 10), "Mg");
162
163     vinciPoint = mapToCanvas(scopeRect, SCALING*YUV_Yl);
164     davinci.drawEllipse(vinciPoint, 4,4);
165     davinci.drawText(vinciPoint-QPoint(25, 0), "Yl");
166
167     // Draw RGB/CMY points with 75% chroma (for NTSC)
168     davinci.setPen(penThin);
169     davinci.drawEllipse(centerPoint, 4,4);
170     davinci.drawEllipse(mapToCanvas(scopeRect, P75*SCALING*YUV_R), 3,3);
171     davinci.drawEllipse(mapToCanvas(scopeRect, P75*SCALING*YUV_G), 3,3);
172     davinci.drawEllipse(mapToCanvas(scopeRect, P75*SCALING*YUV_B), 3,3);
173     davinci.drawEllipse(mapToCanvas(scopeRect, P75*SCALING*YUV_Cy), 3,3);
174     davinci.drawEllipse(mapToCanvas(scopeRect, P75*SCALING*YUV_Mg), 3,3);
175     davinci.drawEllipse(mapToCanvas(scopeRect, P75*SCALING*YUV_Yl), 3,3);
176
177
178     // If no renderer available, there is nothing more to paint now.
179     if (m_activeRender == 0) return;
180
181
182     // Prepare the vectorscope data
183     QImage scope(cw, cw, QImage::Format_ARGB32);
184     scope.fill(qRgba(0,0,0,0));
185
186     QImage img(m_activeRender->extractFrame(m_activeRender->seekFramePosition()));
187     uchar *bits = img.bits();
188
189     //davinci.setCompositionMode(QPainter::CompositionMode_Plus);
190     davinci.setPen(penLight);
191     
192     if (!circleOnly) {
193         int r,g,b;
194         double dy, dr, dg, db, dmax;
195         double y,u,v;
196         QPoint pt;
197         QRgb px;
198
199         for (int i = 0; i < img.byteCount(); i+= 4) {
200             QRgb *col = (QRgb *) bits;
201
202             r = qRed(*col);
203             g = qGreen(*col);
204             b = qBlue(*col);
205
206             y = (double) 0.001173 * r + 0.002302 * g + 0.0004471 * b;
207             u = (double) -0.0005781*r -0.001135*g +0.001713*b;
208             v = (double) 0.002411*r -0.002019*g -0.0003921*b;
209
210             pt = mapToCanvas(QRect(QPoint(0,0), scope.size()), QPointF(SCALING*scaling*u, SCALING*scaling*v));
211
212             if (!(pt.x() <= scopeRect.width() && pt.x() >= 0
213                 && pt.y() <= scopeRect.height() && pt.y() >= 0)) {
214                 // Point lies outside (because of scaling), don't plot it
215                 continue;
216             }
217
218             // Draw the pixel using the chosen draw mode
219             switch (iPaintMode) {
220             case CHROMA:
221                 dy = 200;
222                 dr = dy + 290.8*v;
223                 dg = dy - 100.6*u - 148*v;
224                 db = dy + 517.2*u;
225
226                 dmax = dr;
227                 if (dg > dmax) dmax = dg;
228                 if (db > dmax) dmax = db;
229                 dmax = 255/dmax;
230
231                 dr *= dmax;
232                 dg *= dmax;
233                 db *= dmax;
234
235                 scope.setPixel(pt, qRgba(dr, dg, db, 255));
236                 break;
237             case ORIG:
238                 scope.setPixel(pt, *col);
239                 break;
240             case GREEN:
241                 px = scope.pixel(pt);
242                 scope.setPixel(pt, qRgba(qRed(px)+(255-qRed(px))/40, 255, qBlue(px)+(255-qBlue(px))/40, qAlpha(px)+(255-qAlpha(px))/20));
243                 break;
244             }
245
246             bits += 4;
247         }
248
249         davinci.drawImage(scopeRect.topLeft(), scope);
250         m_scope = scope;
251     } else {
252         // Mouse moved: Draw a circle over the (already calculated) scope
253         davinci.drawImage(scopeRect.topLeft(), m_scope);
254
255         int dx = centerPoint.x()-mousePos.x();
256         int dy = centerPoint.y()-mousePos.y();
257
258         QPoint reference = mapToCanvas(scopeRect, QPointF(1,0));
259
260         int r = sqrt(dx*dx + dy*dy);
261         float percent = (float) 100*r/SCALING/(reference.x() - centerPoint.x());
262
263         qDebug() << "dx: " << dx << " dy: " << dy << " r: " << r;
264         davinci.drawEllipse(centerPoint, r,r);
265         davinci.setPen(penThin);
266         davinci.drawText(scopeRect.bottomRight()-QPoint(40,0), QVariant((int)percent).toString().append(" %"));
267
268         circleOnly = false;
269     }
270
271 }
272
273 void Vectorscope::slotPaintModeChanged(int index)
274 {
275     iPaintMode = index;
276     this->update();
277 }
278
279 void Vectorscope::slotMagnifyChanged()
280 {
281     if (cbMagnify->isChecked()) {
282         scaling = 1.4;
283     } else {
284         scaling = 1;
285     }
286     this->update();
287 }
288
289 void Vectorscope::slotActiveMonitorChanged(bool isClipMonitor)
290 {
291     qDebug() << "Active monitor changed. Is clip? " << isClipMonitor;
292     if (isClipMonitor) {
293         m_activeRender = m_clipRender;
294     } else {
295         m_activeRender = m_projRender;
296     }
297 }
298
299 void Vectorscope::mousePressEvent(QMouseEvent *)
300 {
301     // Update the scope on mouse press
302     this->update();
303 }
304
305 void Vectorscope::mouseMoveEvent(QMouseEvent *event)
306 {
307     // Draw a circle around the center,
308     // showing percentage number of the radius length
309
310     circleOnly = true;
311     mousePos = event->pos();
312     qDebug() << "event: " << mousePos.x() << "/" << mousePos.y();
313     this->update();
314 }