]> git.sesse.net Git - kdenlive/blob - src/vectorscope.cpp
* Vectorscope added
[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 <QPainter>
38 #include <QDebug>
39
40 #include "vectorscope.h"
41
42 const float SCALING = 1/.7; // See class docs
43 const float P75 = .75;
44 const QPointF YUV_R(-.147,  .615);
45 const QPointF YUV_G(-.289, -.515);
46 const QPointF YUV_B(.437, -.100);
47 const QPointF YUV_Cy(.147, -.615);
48 const QPointF YUV_Mg(.289,  .515);
49 const QPointF YUV_Yl(-.437,  .100);
50
51
52
53 Vectorscope::Vectorscope(Render *render, QWidget *parent) :
54     QWidget(parent),
55     m_render(render),
56     iPaintMode(GREEN),
57     scaling(1)
58 {
59     setupUi(this);
60     paintMode->insertItem(GREEN, i18n("Green"));
61     paintMode->insertItem(CHROMA, i18n("Chroma"));
62     paintMode->insertItem(ORIG, i18n("Original Color"));
63     connect(paintMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPaintModeChanged(int)));
64     connect(cbMagnify, SIGNAL(stateChanged(int)), this, SLOT(slotMagnifyChanged()));
65 }
66
67 Vectorscope::~Vectorscope()
68 {
69 }
70
71 void Vectorscope::mousePressEvent(QMouseEvent *)
72 {
73     this->update();
74 }
75
76 /**
77
78   Input point is on [-1,1]², 0 being at the center,
79   and positive directions are top/right.
80
81   Maps to a QRect «inside» which is using the
82   0 point in the top left corner. The coordinates of
83   the rect «inside» are relative to «parent». The
84   coordinates returned can be used in the parent.
85
86
87     parent v
88   +-------------------+
89   | inside v          |
90   | +-----------+     |
91   | |    +      |     |
92   | |  --0++    |     | < point
93   | |    -      |     |
94   | +-----------+     |
95   |                   |
96   +-------------------+
97
98  */
99 QPoint Vectorscope::mapToCanvas(QRect inside, QPointF point)
100 {
101     return QPoint(point.x()/2*inside.width()  + inside.width()/2  + inside.x(),
102                  -point.y()/2*inside.height() + inside.height()/2 + inside.y());
103 }
104
105 void Vectorscope::paintEvent(QPaintEvent *)
106 {
107     // Widget width/height
108     int ww = this->size().width();
109     int wh = this->size().height();
110
111     // Distance from top/left/right
112     int offset = 6;
113
114     // controlsArea contains the controls at the top;
115     // We want to paint below
116     QPoint topleft(offset, controlsArea->geometry().height()+offset);
117
118     // Circle Width: min of width and height
119     int cw = wh - topleft.y();
120     if (ww < cw) { cw = ww; }
121     cw -= 2*offset;
122     QRect scopeRect(topleft, QPoint(cw, cw) + topleft);
123
124
125     // Draw the vectorscope circle
126     QPainter davinci(this);
127     QPoint vinciPoint;
128     davinci.setRenderHint(QPainter::Antialiasing, true);
129     davinci.fillRect(0, 0, ww, wh, QColor(25,25,23));
130
131     davinci.setPen(QPen(QBrush(QColor(250,250,250)), 2, Qt::SolidLine));
132     davinci.drawEllipse(scopeRect);
133
134     // Draw RGB/CMY points with 100% chroma
135     vinciPoint = mapToCanvas(scopeRect, SCALING*YUV_R);
136     davinci.drawEllipse(vinciPoint, 4,4);
137     davinci.drawText(vinciPoint-QPoint(20, -10), "R");
138
139     vinciPoint = mapToCanvas(scopeRect, SCALING*YUV_G);
140     davinci.drawEllipse(vinciPoint, 4,4);
141     davinci.drawText(vinciPoint-QPoint(20, 0), "G");
142
143     vinciPoint = mapToCanvas(scopeRect, SCALING*YUV_B);
144     davinci.drawEllipse(vinciPoint, 4,4);
145     davinci.drawText(vinciPoint+QPoint(15, 10), "B");
146
147     vinciPoint = mapToCanvas(scopeRect, SCALING*YUV_Cy);
148     davinci.drawEllipse(vinciPoint, 4,4);
149     davinci.drawText(vinciPoint+QPoint(15, -5), "Cy");
150
151     vinciPoint = mapToCanvas(scopeRect, SCALING*YUV_Mg);
152     davinci.drawEllipse(vinciPoint, 4,4);
153     davinci.drawText(vinciPoint+QPoint(15, 10), "Mg");
154
155     vinciPoint = mapToCanvas(scopeRect, SCALING*YUV_Yl);
156     davinci.drawEllipse(vinciPoint, 4,4);
157     davinci.drawText(vinciPoint-QPoint(25, 0), "Yl");
158
159     // Draw RGB/CMY points with 75% chroma (for NTSC)
160     davinci.setPen(QPen(QBrush(QColor(250,250,250)), 1, Qt::SolidLine));
161     davinci.drawEllipse(mapToCanvas(scopeRect, QPointF(0,0)), 4,4);
162     davinci.drawEllipse(mapToCanvas(scopeRect, P75*SCALING*YUV_R), 3,3);
163     davinci.drawEllipse(mapToCanvas(scopeRect, P75*SCALING*YUV_G), 3,3);
164     davinci.drawEllipse(mapToCanvas(scopeRect, P75*SCALING*YUV_B), 3,3);
165     davinci.drawEllipse(mapToCanvas(scopeRect, P75*SCALING*YUV_Cy), 3,3);
166     davinci.drawEllipse(mapToCanvas(scopeRect, P75*SCALING*YUV_Mg), 3,3);
167     davinci.drawEllipse(mapToCanvas(scopeRect, P75*SCALING*YUV_Yl), 3,3);
168
169
170     // If no renderer available, there is nothing more to paint now.
171     if (m_render == 0) return;
172
173
174     // Prepare the vectorscope data
175     QImage scope(cw, cw, QImage::Format_ARGB32);
176     scope.fill(qRgba(0,0,0,0));
177
178     QImage img(m_render->extractFrame(m_render->seekFramePosition()));
179     uchar *bits = img.bits();
180
181     davinci.setCompositionMode(QPainter::CompositionMode_Plus);
182     davinci.setPen(QColor(144,255,100,50));
183     
184     int r,g,b;
185     double dy, dr, dg, db, dmax;
186     double y,u,v;
187     QPoint pt;
188     QRgb px;
189
190     for (int i = 0; i < img.byteCount(); i+= 4) {
191         QRgb *col = (QRgb *) bits;
192
193         r = qRed(*col);
194         g = qGreen(*col);
195         b = qBlue(*col);
196
197         y = (double) 0.001173 * r + 0.002302 * g + 0.0004471 * b;
198         u = (double) -0.0005781*r -0.001135*g +0.001713*b;
199         v = (double) 0.002411*r -0.002019*g -0.0003921*b;
200
201         pt = mapToCanvas(QRect(QPoint(0,0), scope.size()), QPointF(SCALING*scaling*u, SCALING*scaling*v));
202
203         if (!(pt.x() <= scopeRect.width() && pt.x() >= 0
204             && pt.y() <= scopeRect.height() && pt.y() >= 0)) {
205             // Point lies outside (because of scaling), don't plot it
206             continue;
207         }
208
209         // Draw the pixel using the chosen draw mode
210         switch (iPaintMode) {
211         case CHROMA:
212             dy = 200;
213             dr = dy + 290.8*v;
214             dg = dy - 100.6*u - 148*v;
215             db = dy + 517.2*u;
216
217             dmax = dr;
218             if (dg > dmax) dmax = dg;
219             if (db > dmax) dmax = db;
220             dmax = 255/dmax;
221
222             dr *= dmax;
223             dg *= dmax;
224             db *= dmax;
225
226             scope.setPixel(pt, qRgba(dr, dg, db, 255));
227             break;
228         case ORIG:
229             scope.setPixel(pt, *col);
230             break;
231         case GREEN:
232             px = scope.pixel(pt);
233             scope.setPixel(pt, qRgba(qRed(px)+(255-qRed(px))/40, 255, qBlue(px)+(255-qBlue(px))/40, qAlpha(px)+(255-qAlpha(px))/20));
234             break;
235         }
236
237         bits += 4;
238     }
239
240     davinci.drawImage(scopeRect.topLeft(), scope);
241
242 }
243
244 void Vectorscope::slotPaintModeChanged(int index)
245 {
246     iPaintMode = index;
247     this->update();
248 }
249
250 void Vectorscope::slotMagnifyChanged()
251 {
252     if (cbMagnify->isChecked()) {
253         scaling = 1.4;
254     } else {
255         scaling = 1;
256     }
257     this->update();
258 }