1 /*
2 SPDX-FileCopyrightText: 2010 Simon Andreas Eugster <simon.eu@gmail.com>
3 This file is part of kdenlive. See www.kdenlive.org.
4
5 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
6 */
7
8 #include "colortools.h"
9
10 #include <QColor>
11 #include <cmath>
12
13 //#define DEBUG_CT
14 #ifdef DEBUG_CT
15 #include "kdenlive_debug.h"
16 #endif
17
18 namespace {
preventOverflow(double value)19 double preventOverflow(double value)
20 {
21 return value < 0 ? 0 : value > 255 ? 255 : value;
22 }
23 } // namespace
24
ColorTools(QObject * parent)25 ColorTools::ColorTools(QObject *parent)
26 : QObject(parent)
27 {
28 }
29
yuvColorWheel(const QSize & size,int Y,float scaling,bool modifiedVersion,bool circleOnly)30 QImage ColorTools::yuvColorWheel(const QSize &size, int Y, float scaling, bool modifiedVersion, bool circleOnly)
31 {
32 QImage wheel(size, QImage::Format_ARGB32);
33 if (size.width() == 0 || size.height() == 0) {
34 qCritical("ERROR: Size of the color wheel must not be 0!");
35 return wheel;
36 }
37 if (circleOnly) {
38 wheel.fill(qRgba(0, 0, 0, 0));
39 }
40
41 double dr, dg, db, dv;
42 double ru, rv, rr;
43 const int w = size.width();
44 const int h = size.height();
45 const float w2 = (float)w / 2;
46 const float h2 = (float)h / 2;
47
48 for (int u = 0; u < w; ++u) {
49 // Transform u from {0,...,w} to [-1,1]
50 double du = (double)2 * u / (w - 1) - 1;
51 du = scaling * du;
52
53 for (int v = 0; v < h; ++v) {
54 dv = (double)2 * v / (h - 1) - 1;
55 dv = scaling * dv;
56
57 if (circleOnly) {
58 // Ellipsis equation: x²/a² + y²/b² = 1
59 // Here: x=ru, y=rv, a=w/2, b=h/2, 1=rr
60 // For rr > 1, the point lies outside. Don't draw it.
61 ru = u - double(w2);
62 rv = v - double(h2);
63 rr = ru * ru / (w2 * w2) + rv * rv / (h2 * h2);
64 if (rr > 1) {
65 continue;
66 }
67 }
68
69 // Calculate the RGB values from YUV
70 dr = Y + 290.8 * dv;
71 dg = Y - 100.6 * du - 148 * dv;
72 db = Y + 517.2 * du;
73
74 if (modifiedVersion) {
75 // Scale the RGB values down, or up, to max 255
76 const double dmax = 255 / std::max({fabs(dr), fabs(dg), fabs(db)});
77
78 dr *= dmax;
79 dg *= dmax;
80 db *= dmax;
81 }
82
83 // Avoid overflows (which would generate intersecting patterns).
84 // Note that not all possible (y,u,v) values with u,v \in [-1,1]
85 // have a correct RGB representation, therefore some RGB values
86 // may exceed {0,...,255}.
87 dr = preventOverflow(dr);
88 dg = preventOverflow(dg);
89 db = preventOverflow(db);
90
91 wheel.setPixel(u, (h - v - 1), qRgba(dr, dg, db, 255));
92 }
93 }
94
95 emit signalYuvWheelCalculationFinished();
96 return wheel;
97 }
98
yuvVerticalPlane(const QSize & size,int angle,float scaling)99 QImage ColorTools::yuvVerticalPlane(const QSize &size, int angle, float scaling)
100 {
101 QImage plane(size, QImage::Format_ARGB32);
102 if (size.width() == 0 || size.height() == 0) {
103 qCritical("ERROR: Size of the color plane must not be 0!");
104 return plane;
105 }
106
107 double dr, dg, db, Y;
108 const int w = size.width();
109 const int h = size.height();
110 const double uscaling = scaling * cos(M_PI * angle / 180);
111 const double vscaling = scaling * sin(M_PI * angle / 180);
112
113 for (int uv = 0; uv < w; ++uv) {
114 double du = uscaling * ((double)2 * uv / w - 1); //(double)?
115 double dv = vscaling * ((double)2 * uv / w - 1);
116
117 for (int y = 0; y < h; ++y) {
118 Y = (double)255 * y / h;
119
120 // See yuv2rgb, yuvColorWheel
121 dr = preventOverflow(Y + 290.8 * dv);
122 dg = preventOverflow(Y - 100.6 * du - 148 * dv);
123 db = preventOverflow(Y + 517.2 * du);
124
125 plane.setPixel(uv, (h - y - 1), qRgba(dr, dg, db, 255));
126 }
127 }
128
129 return plane;
130 }
131
rgbCurvePlane(const QSize & size,const ColorTools::ColorsRGB & color,float scaling,const QRgb & background)132 QImage ColorTools::rgbCurvePlane(const QSize &size, const ColorTools::ColorsRGB &color, float scaling, const QRgb &background)
133 {
134 Q_ASSERT(scaling > 0 && scaling <= 1);
135
136 QImage plane(size, QImage::Format_ARGB32);
137 if (size.width() == 0 || size.height() == 0) {
138 qCritical("ERROR: Size of the color plane must not be 0!");
139 return plane;
140 }
141
142 const int w = size.width();
143 const int h = size.height();
144
145 double dcol;
146 double dx, dy;
147
148 for (int x = 0; x < w; ++x) {
149 double dval = (double)255 * x / (w - 1);
150
151 for (int y = 0; y < h; ++y) {
152 dy = (double)y / (h - 1);
153 dx = (double)x / (w - 1);
154
155 if (1 - scaling < 0.0001) {
156 dcol = (double)255 * dy;
157 } else {
158 dcol = (double)255 * (dy - (dy - dx) * (1 - scaling));
159 }
160
161 if (color == ColorTools::ColorsRGB::R) {
162 plane.setPixel(x, (h - y - 1), qRgb(dcol, dval, dval));
163 } else if (color == ColorTools::ColorsRGB::G) {
164 plane.setPixel(x, (h - y - 1), qRgb(dval, dcol, dval));
165 } else if (color == ColorTools::ColorsRGB::B) {
166 plane.setPixel(x, (h - y - 1), qRgb(dval, dval, dcol));
167 } else if (color == ColorTools::ColorsRGB::A) {
168 plane.setPixel(x, (h - y - 1), qRgb(dcol / 255. * qRed(background), dcol / 255. * qGreen(background), dcol / 255. * qBlue(background)));
169 } else {
170 plane.setPixel(x, (h - y - 1), qRgb(dcol, dcol, dcol));
171 }
172 }
173 }
174 return plane;
175 }
176
rgbCurveLine(const QSize & size,const ColorTools::ColorsRGB & color,const QRgb & background)177 QImage ColorTools::rgbCurveLine(const QSize &size, const ColorTools::ColorsRGB &color, const QRgb &background)
178 {
179
180 QImage plane(size, QImage::Format_ARGB32);
181 if (size.width() == 0 || size.height() == 0) {
182 qCritical("ERROR: Size of the color line must not be 0!");
183 return plane;
184 }
185
186 const int w = size.width();
187 const int h = size.height();
188
189 double dcol;
190 double dy;
191
192 for (int x = 0; x < w; ++x) {
193
194 for (int y = 0; y < h; ++y) {
195 dy = (double)y / (h - 1);
196
197 dcol = (double)255 * dy;
198
199 if (color == ColorTools::ColorsRGB::R) {
200 plane.setPixel(x, (h - y - 1), qRgb(dcol, 0, 0));
201 } else if (color == ColorTools::ColorsRGB::G) {
202 plane.setPixel(x, (h - y - 1), qRgb(0, dcol, 0));
203 } else if (color == ColorTools::ColorsRGB::B) {
204 plane.setPixel(x, (h - y - 1), qRgb(0, 0, dcol));
205 } else if (color == ColorTools::ColorsRGB::A) {
206 plane.setPixel(x, (h - y - 1), qRgb(dcol / 255. * qRed(background), dcol / 255. * qGreen(background), dcol / 255. * qBlue(background)));
207 } else {
208 plane.setPixel(x, (h - y - 1), qRgb(dcol, dcol, dcol));
209 }
210 }
211 }
212 return plane;
213 }
214
yPbPrColorWheel(const QSize & size,int Y,float scaling,bool circleOnly)215 QImage ColorTools::yPbPrColorWheel(const QSize &size, int Y, float scaling, bool circleOnly)
216 {
217
218 QImage wheel(size, QImage::Format_ARGB32);
219 if (size.width() == 0 || size.height() == 0) {
220 qCritical("ERROR: Size of the color wheel must not be 0!");
221 return wheel;
222 }
223 if (circleOnly) {
224 wheel.fill(qRgba(0, 0, 0, 0));
225 }
226
227 double dr, dg, db, dpR;
228 double rB, rR, rr;
229 const int w = size.width();
230 const int h = size.height();
231 const float w2 = (float)w / 2;
232 const float h2 = (float)h / 2;
233
234 for (int b = 0; b < w; ++b) {
235 // Transform pB from {0,...,w} to [-0.5,0.5]
236 double dpB = (double)b / (w - 1) - .5;
237 dpB = scaling * dpB;
238
239 for (int r = 0; r < h; ++r) {
240 dpR = (double)r / (h - 1) - .5;
241 dpR = scaling * dpR;
242
243 if (circleOnly) {
244 // see yuvColorWheel
245 rB = b - double(w2);
246 rR = r - double(h2);
247 rr = rB * rB / (w2 * w2) + rR * rR / (h2 * h2);
248 if (rr > 1) {
249 continue;
250 }
251 }
252
253 // Calculate the RGB values from YPbPr
254 dr = preventOverflow(Y + 357.5 * dpR);
255 dg = preventOverflow(Y - 87.75 * dpB - 182.1 * dpR);
256 db = preventOverflow(Y + 451.86 * dpB);
257
258 wheel.setPixel(b, (h - r - 1), qRgba(dr, dg, db, 255));
259 }
260 }
261
262 return wheel;
263 }
264
hsvHueShiftPlane(const QSize & size,int S,int V,int MIN,int MAX)265 QImage ColorTools::hsvHueShiftPlane(const QSize &size, int S, int V, int MIN, int MAX)
266 {
267 Q_ASSERT(size.width() > 0);
268 Q_ASSERT(size.height() > 0);
269 Q_ASSERT(MAX > MIN);
270
271 QImage plane(size, QImage::Format_ARGB32);
272
273 #ifdef DEBUG_CT
274 qCDebug(KDENLIVE_LOG) << "Requested: Saturation " << S << ", Value " << V;
275 QColor colTest(-1, 256, 257);
276 qCDebug(KDENLIVE_LOG) << "-1 mapped to " << colTest.red() << ", 256 to " << colTest.green() << ", 257 to " << colTest.blue();
277 #endif
278
279 QColor col(0, 0, 0);
280
281 const int hueValues = MAX - MIN;
282
283 float huediff;
284 int newhue;
285 for (int x = 0; x < size.width(); ++x) {
286 float hue = x / (size.width() - 1.0) * 359;
287 for (int y = 0; y < size.height(); ++y) {
288 huediff = (1.0f - y / (size.height() - 1.0)) * hueValues + MIN;
289 // qCDebug(KDENLIVE_LOG) << "hue: " << hue << ", huediff: " << huediff;
290
291 newhue = hue + huediff + 360; // Avoid negative numbers. Rest (>360) will be mapped correctly.
292
293 col.setHsv(newhue, S, V);
294 plane.setPixel(x, y, col.rgba());
295 }
296 }
297
298 return plane;
299 }
300
hsvCurvePlane(const QSize & size,const QColor & baseColor,const ComponentsHSV & xVariant,const ComponentsHSV & yVariant,bool shear,const float offsetY)301 QImage ColorTools::hsvCurvePlane(const QSize &size, const QColor &baseColor, const ComponentsHSV &xVariant, const ComponentsHSV &yVariant, bool shear,
302 const float offsetY)
303 {
304 Q_ASSERT(size.width() > 0);
305 Q_ASSERT(size.height() > 0);
306
307 /*int xMax, yMax;
308
309 switch(xVariant) {
310 case COM_H:
311 xMax = 360;
312 break;
313 case COM_S:
314 case COM_V:
315 xMax = 256;
316 break;
317 }
318
319 switch (yVariant) {
320 case COM_H:
321 yMax = 360;
322 break;
323 case COM_S:
324 case COM_V:
325 yMax = 256;
326 break;
327 }*/
328
329 QImage plane(size, QImage::Format_ARGB32);
330
331 QColor col(0, 0, 0);
332
333 float hue, sat, val;
334 hue = baseColor.hueF();
335 sat = baseColor.saturationF();
336 val = baseColor.valueF();
337
338 for (int x = 0; x < size.width(); ++x) {
339 switch (xVariant) {
340 case COM_H:
341 hue = x / (size.width() - 1.0);
342 break;
343 case COM_S:
344 sat = x / (size.width() - 1.0);
345 break;
346 case COM_V:
347 val = x / (size.width() - 1.0);
348 break;
349 }
350 for (int y = 0; y < size.height(); ++y) {
351 switch (yVariant) {
352 case COM_H:
353 hue = 1.0 - y / (size.height() - 1.0);
354 break;
355 case COM_S:
356 sat = 1.0 - y / (size.height() - 1.0);
357 break;
358 case COM_V:
359 val = 1.0 - y / (size.height() - 1.0);
360 break;
361 }
362
363 col.setHsvF(hue, sat, val);
364
365 if (!shear) {
366 plane.setPixel(x, y, col.rgba());
367 } else {
368 plane.setPixel(x, (2 * size.height() + y - x * size.width() / size.height() - int(offsetY) * size.height()) % size.height(), col.rgba());
369 }
370 }
371 }
372
373 return plane;
374 }
375