]> git.sesse.net Git - kdenlive/blob - src/colorcorrection/vectorscopegenerator.cpp
ad6164978f39e0df6c13abbab2b8611b029b83c3
[kdenlive] / src / colorcorrection / vectorscopegenerator.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 <math.h>
43 #include <QDebug>
44 #include <QImage>
45 #include "vectorscopegenerator.h"
46
47 // The maximum distance from the center for any RGB color is 0.63, so
48 // no need to make the circle bigger than required.
49 const float SCALING = 1/.7;
50
51 const float VectorscopeGenerator::scaling = 1/.7;
52
53 /**
54   Input point is on [-1,1]², 0 being at the center,
55   and positive directions are →top/→right.
56
57   Maps to the coordinates used in QImages with the 0 point
58   at the top left corner.
59
60  -1          +1
61 +1+-----------+
62   |    +      |
63   |  --0++    |
64   |    -      |
65 -1+-----------+
66      vvv
67    mapped to
68       v
69   0      x
70  0+------+
71   |0++   |
72   |-     |
73   |-     |
74  y+------+
75
76   With y:
77   1. Scale from [-1,1] to [0,1] with                    y01 := (y+1)/2
78   2. Invert (Orientation of the y axis changes) with    y10 := 1-y01
79   3. Scale from [1,0] to [height-1,0] with              yy  := (height-1) * y10
80   x does not need to be inverted.
81
82  */
83 QPoint VectorscopeGenerator::mapToCircle(const QSize &targetSize, const QPointF &point) const
84 {
85     return QPoint( (targetSize.width() -1) *      (point.x()+1)/2,
86                    (targetSize.height()-1) * (1 - (point.y()+1)/2) );
87 }
88
89 QImage VectorscopeGenerator::calculateVectorscope(const QSize &vectorscopeSize, const QImage &image, const float &gain,
90                                                   const VectorscopeGenerator::PaintMode &paintMode, const bool&,
91                                                   const uint &accelFactor) const
92 {
93     // Prepare the vectorscope data
94     const int cw = (vectorscopeSize.width() < vectorscopeSize.height()) ? vectorscopeSize.width() : vectorscopeSize.height();
95     QImage scope = QImage(cw, cw, QImage::Format_ARGB32);
96     scope.fill(qRgba(0,0,0,0));
97
98     const uchar *bits = image.bits();
99
100     int r,g,b;
101     double dy, dr, dg, db, dmax;
102     double y,u,v;
103     QPoint pt;
104     QRgb px;
105
106     const int stepsize = 4*accelFactor;
107
108     // Just an average for the number of image pixels per scope pixel.
109     // 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
110     double avgPxPerPx = (double) 4*(image.bytesPerLine()*image.height())/scope.size().width()/scope.size().height()/accelFactor;
111     qDebug() << "Expecting " << avgPxPerPx << " pixels per pixel.";
112
113     for (int i = 0; i < (image.bytesPerLine()*image.height()); i+= stepsize) {
114         QRgb *col = (QRgb *) bits;
115
116         r = qRed(*col);
117         g = qGreen(*col);
118         b = qBlue(*col);
119
120         y = (double)  0.001173 * r +0.002302 * g +0.0004471* b;
121         u = (double) -0.0005781* r -0.001135 * g +0.001713 * b;
122         v = (double)  0.002411 * r -0.002019 * g -0.0003921* b;
123
124         pt = mapToCircle(vectorscopeSize, QPointF(SCALING*gain*u, SCALING*gain*v));
125
126         if (pt.x() >= scope.width() || pt.x() < 0
127             || pt.y() >= scope.height() || pt.y() < 0) {
128             // Point lies outside (because of scaling), don't plot it
129
130         } else {
131
132             // Draw the pixel using the chosen draw mode.
133             switch (paintMode) {
134             case PaintMode_YUV:
135                 // see yuvColorWheel
136                 dy = 128; // Default Y value. Lower = darker.
137
138                 // Calculate the RGB values from YUV
139                 dr = dy + 290.8*v;
140                 dg = dy - 100.6*u - 148*v;
141                 db = dy + 517.2*u;
142
143                 if (dr < 0) dr = 0;
144                 if (dg < 0) dg = 0;
145                 if (db < 0) db = 0;
146                 if (dr > 255) dr = 255;
147                 if (dg > 255) dg = 255;
148                 if (db > 255) db = 255;
149
150                 scope.setPixel(pt, qRgba(dr, dg, db, 255));
151                 break;
152
153             case PaintMode_Chroma:
154                 dy = 200; // Default Y value. Lower = darker.
155
156                 // Calculate the RGB values from YUV
157                 dr = dy + 290.8*v;
158                 dg = dy - 100.6*u - 148*v;
159                 db = dy + 517.2*u;
160
161                 // Scale the RGB values back to max 255
162                 dmax = dr;
163                 if (dg > dmax) dmax = dg;
164                 if (db > dmax) dmax = db;
165                 dmax = 255/dmax;
166
167                 dr *= dmax;
168                 dg *= dmax;
169                 db *= dmax;
170
171                 scope.setPixel(pt, qRgba(dr, dg, db, 255));
172                 break;
173             case PaintMode_Original:
174                 scope.setPixel(pt, *col);
175                 break;
176             case PaintMode_Green:
177                 px = scope.pixel(pt);
178                 scope.setPixel(pt, qRgba(qRed(px)+(255-qRed(px))/(3*avgPxPerPx), qGreen(px)+20*(255-qGreen(px))/(avgPxPerPx),
179                                          qBlue(px)+(255-qBlue(px))/(avgPxPerPx), qAlpha(px)+(255-qAlpha(px))/(avgPxPerPx)));
180                 break;
181             case PaintMode_Green2:
182                 px = scope.pixel(pt);
183                 scope.setPixel(pt, qRgba(qRed(px)+ceil((255-(float)qRed(px))/(4*avgPxPerPx)), 255,
184                                          qBlue(px)+ceil((255-(float)qBlue(px))/(avgPxPerPx)), qAlpha(px)+ceil((255-(float)qAlpha(px))/(avgPxPerPx))));
185                 break;
186             case PaintMode_Black:
187                 px = scope.pixel(pt);
188                 scope.setPixel(pt, qRgba(0,0,0, qAlpha(px)+(255-qAlpha(px))/20));
189                 break;
190             }
191         }
192
193         bits += stepsize;
194     }
195     return scope;
196 }