]> git.sesse.net Git - kdenlive/blob - src/colorcorrection/vectorscopegenerator.cpp
Histogram: Fixed max display
[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     if (vectorscopeSize.width() <= 0 || vectorscopeSize.height() <= 0 || image.width() <= 0 || image.height() <= 0) {
94         // Invalid size
95         return QImage();
96     }
97
98     // Prepare the vectorscope data
99     const int cw = (vectorscopeSize.width() < vectorscopeSize.height()) ? vectorscopeSize.width() : vectorscopeSize.height();
100     QImage scope = QImage(cw, cw, QImage::Format_ARGB32);
101     scope.fill(qRgba(0,0,0,0));
102
103     const uchar *bits = image.bits();
104
105     int r,g,b;
106     double dy, dr, dg, db, dmax;
107     double y,u,v;
108     QPoint pt;
109     QRgb px;
110
111     const int stepsize = 4*accelFactor;
112
113     // Just an average for the number of image pixels per scope pixel.
114     // 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
115     double avgPxPerPx = (double) 4*(image.bytesPerLine()*image.height())/scope.size().width()/scope.size().height()/accelFactor;
116     qDebug() << "Expecting " << avgPxPerPx << " pixels per pixel.";
117
118     for (int i = 0; i < (image.bytesPerLine()*image.height()); i+= stepsize) {
119         QRgb *col = (QRgb *) bits;
120
121         r = qRed(*col);
122         g = qGreen(*col);
123         b = qBlue(*col);
124
125         y = (double)  0.001173 * r +0.002302 * g +0.0004471* b;
126         u = (double) -0.0005781* r -0.001135 * g +0.001713 * b;
127         v = (double)  0.002411 * r -0.002019 * g -0.0003921* b;
128
129         pt = mapToCircle(vectorscopeSize, QPointF(SCALING*gain*u, SCALING*gain*v));
130
131         if (pt.x() >= scope.width() || pt.x() < 0
132             || pt.y() >= scope.height() || pt.y() < 0) {
133             // Point lies outside (because of scaling), don't plot it
134
135         } else {
136
137             // Draw the pixel using the chosen draw mode.
138             switch (paintMode) {
139             case PaintMode_YUV:
140                 // see yuvColorWheel
141                 dy = 128; // Default Y value. Lower = darker.
142
143                 // Calculate the RGB values from YUV
144                 dr = dy + 290.8*v;
145                 dg = dy - 100.6*u - 148*v;
146                 db = dy + 517.2*u;
147
148                 if (dr < 0) dr = 0;
149                 if (dg < 0) dg = 0;
150                 if (db < 0) db = 0;
151                 if (dr > 255) dr = 255;
152                 if (dg > 255) dg = 255;
153                 if (db > 255) db = 255;
154
155                 scope.setPixel(pt, qRgba(dr, dg, db, 255));
156                 break;
157
158             case PaintMode_Chroma:
159                 dy = 200; // Default Y value. Lower = darker.
160
161                 // Calculate the RGB values from YUV
162                 dr = dy + 290.8*v;
163                 dg = dy - 100.6*u - 148*v;
164                 db = dy + 517.2*u;
165
166                 // Scale the RGB values back to max 255
167                 dmax = dr;
168                 if (dg > dmax) dmax = dg;
169                 if (db > dmax) dmax = db;
170                 dmax = 255/dmax;
171
172                 dr *= dmax;
173                 dg *= dmax;
174                 db *= dmax;
175
176                 scope.setPixel(pt, qRgba(dr, dg, db, 255));
177                 break;
178             case PaintMode_Original:
179                 scope.setPixel(pt, *col);
180                 break;
181             case PaintMode_Green:
182                 px = scope.pixel(pt);
183                 scope.setPixel(pt, qRgba(qRed(px)+(255-qRed(px))/(3*avgPxPerPx), qGreen(px)+20*(255-qGreen(px))/(avgPxPerPx),
184                                          qBlue(px)+(255-qBlue(px))/(avgPxPerPx), qAlpha(px)+(255-qAlpha(px))/(avgPxPerPx)));
185                 break;
186             case PaintMode_Green2:
187                 px = scope.pixel(pt);
188                 scope.setPixel(pt, qRgba(qRed(px)+ceil((255-(float)qRed(px))/(4*avgPxPerPx)), 255,
189                                          qBlue(px)+ceil((255-(float)qBlue(px))/(avgPxPerPx)), qAlpha(px)+ceil((255-(float)qAlpha(px))/(avgPxPerPx))));
190                 break;
191             case PaintMode_Black:
192                 px = scope.pixel(pt);
193                 scope.setPixel(pt, qRgba(0,0,0, qAlpha(px)+(255-qAlpha(px))/20));
194                 break;
195             }
196         }
197
198         bits += stepsize;
199     }
200     return scope;
201 }