File indexing completed on 2024-12-01 04:29:20
0001 /* 0002 SPDX-FileCopyrightText: 2010 Simon Andreas Eugster <simon.eu@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "colortools.h" 0008 0009 #include <QColor> 0010 #include <QDebug> 0011 #include <cmath> 0012 0013 //#define DEBUG_CT 0014 #ifdef DEBUG_CT 0015 #include "kdenlive_debug.h" 0016 #endif 0017 0018 namespace { 0019 double preventOverflow(double value) 0020 { 0021 return value < 0 ? 0 : value > 255 ? 255 : value; 0022 } 0023 } // namespace 0024 0025 ColorTools::ColorTools(QObject *parent) 0026 : QObject(parent) 0027 { 0028 } 0029 0030 QImage ColorTools::yuvColorWheel(const QSize &size, int Y, float scaling, bool modifiedVersion, bool circleOnly) 0031 { 0032 QImage wheel(size, QImage::Format_ARGB32); 0033 if (size.width() == 0 || size.height() == 0) { 0034 qCritical() << "ERROR: Size of the color wheel must not be 0!"; 0035 return wheel; 0036 } 0037 if (circleOnly) { 0038 wheel.fill(qRgba(0, 0, 0, 0)); 0039 } 0040 0041 double dr, dg, db, dv; 0042 double ru, rv, rr; 0043 const int w = size.width(); 0044 const int h = size.height(); 0045 const float w2 = (float)w / 2; 0046 const float h2 = (float)h / 2; 0047 0048 for (int u = 0; u < w; ++u) { 0049 // Transform u from {0,...,w} to [-1,1] 0050 double du = (double)2 * u / (w - 1) - 1; 0051 du = scaling * du; 0052 0053 for (int v = 0; v < h; ++v) { 0054 dv = (double)2 * v / (h - 1) - 1; 0055 dv = scaling * dv; 0056 0057 if (circleOnly) { 0058 // Ellipsis equation: x²/a² + y²/b² = 1 0059 // Here: x=ru, y=rv, a=w/2, b=h/2, 1=rr 0060 // For rr > 1, the point lies outside. Don't draw it. 0061 ru = u - double(w2); 0062 rv = v - double(h2); 0063 rr = ru * ru / (w2 * w2) + rv * rv / (h2 * h2); 0064 if (rr > 1) { 0065 continue; 0066 } 0067 } 0068 0069 // Calculate the RGB values from YUV 0070 dr = Y + 290.8 * dv; 0071 dg = Y - 100.6 * du - 148 * dv; 0072 db = Y + 517.2 * du; 0073 0074 if (modifiedVersion) { 0075 // Scale the RGB values down, or up, to max 255 0076 const double dmax = 255 / std::max({fabs(dr), fabs(dg), fabs(db)}); 0077 0078 dr *= dmax; 0079 dg *= dmax; 0080 db *= dmax; 0081 } 0082 0083 // Avoid overflows (which would generate intersecting patterns). 0084 // Note that not all possible (y,u,v) values with u,v \in [-1,1] 0085 // have a correct RGB representation, therefore some RGB values 0086 // may exceed {0,...,255}. 0087 dr = preventOverflow(dr); 0088 dg = preventOverflow(dg); 0089 db = preventOverflow(db); 0090 0091 wheel.setPixel(u, (h - v - 1), qRgba(dr, dg, db, 255)); 0092 } 0093 } 0094 0095 Q_EMIT signalYuvWheelCalculationFinished(); 0096 return wheel; 0097 } 0098 0099 QImage ColorTools::yuvVerticalPlane(const QSize &size, int angle, float scaling) 0100 { 0101 QImage plane(size, QImage::Format_ARGB32); 0102 if (size.width() == 0 || size.height() == 0) { 0103 qCritical() << "ERROR: Size of the color plane must not be 0!"; 0104 return plane; 0105 } 0106 0107 double dr, dg, db, Y; 0108 const int w = size.width(); 0109 const int h = size.height(); 0110 const double uscaling = scaling * cos(M_PI * angle / 180); 0111 const double vscaling = scaling * sin(M_PI * angle / 180); 0112 0113 for (int uv = 0; uv < w; ++uv) { 0114 double du = uscaling * ((double)2 * uv / w - 1); //(double)? 0115 double dv = vscaling * ((double)2 * uv / w - 1); 0116 0117 for (int y = 0; y < h; ++y) { 0118 Y = (double)255 * y / h; 0119 0120 // See yuv2rgb, yuvColorWheel 0121 dr = preventOverflow(Y + 290.8 * dv); 0122 dg = preventOverflow(Y - 100.6 * du - 148 * dv); 0123 db = preventOverflow(Y + 517.2 * du); 0124 0125 plane.setPixel(uv, (h - y - 1), qRgba(dr, dg, db, 255)); 0126 } 0127 } 0128 0129 return plane; 0130 } 0131 0132 QImage ColorTools::rgbCurvePlane(const QSize &size, const ColorTools::ColorsRGB &color, float scaling, const QRgb &background) 0133 { 0134 Q_ASSERT(scaling > 0 && scaling <= 1); 0135 0136 QImage plane(size, QImage::Format_ARGB32); 0137 if (size.width() == 0 || size.height() == 0) { 0138 qCritical() << "ERROR: Size of the color plane must not be 0!"; 0139 return plane; 0140 } 0141 0142 const int w = size.width(); 0143 const int h = size.height(); 0144 0145 double dcol; 0146 double dx, dy; 0147 0148 for (int x = 0; x < w; ++x) { 0149 double dval = (double)255 * x / (w - 1); 0150 0151 for (int y = 0; y < h; ++y) { 0152 dy = (double)y / (h - 1); 0153 dx = (double)x / (w - 1); 0154 0155 if (1 - scaling < 0.0001) { 0156 dcol = (double)255 * dy; 0157 } else { 0158 dcol = (double)255 * (dy - (dy - dx) * (1 - scaling)); 0159 } 0160 0161 if (color == ColorTools::ColorsRGB::R) { 0162 plane.setPixel(x, (h - y - 1), qRgb(dcol, dval, dval)); 0163 } else if (color == ColorTools::ColorsRGB::G) { 0164 plane.setPixel(x, (h - y - 1), qRgb(dval, dcol, dval)); 0165 } else if (color == ColorTools::ColorsRGB::B) { 0166 plane.setPixel(x, (h - y - 1), qRgb(dval, dval, dcol)); 0167 } else if (color == ColorTools::ColorsRGB::A) { 0168 plane.setPixel(x, (h - y - 1), qRgb(dcol / 255. * qRed(background), dcol / 255. * qGreen(background), dcol / 255. * qBlue(background))); 0169 } else { 0170 plane.setPixel(x, (h - y - 1), qRgb(dcol, dcol, dcol)); 0171 } 0172 } 0173 } 0174 return plane; 0175 } 0176 0177 QImage ColorTools::rgbCurveLine(const QSize &size, const ColorTools::ColorsRGB &color, const QRgb &background) 0178 { 0179 0180 QImage plane(size, QImage::Format_ARGB32); 0181 if (size.width() == 0 || size.height() == 0) { 0182 qCritical() << "ERROR: Size of the color line must not be 0!"; 0183 return plane; 0184 } 0185 0186 const int w = size.width(); 0187 const int h = size.height(); 0188 0189 double dcol; 0190 double dy; 0191 0192 for (int x = 0; x < w; ++x) { 0193 0194 for (int y = 0; y < h; ++y) { 0195 dy = (double)y / (h - 1); 0196 0197 dcol = (double)255 * dy; 0198 0199 if (color == ColorTools::ColorsRGB::R) { 0200 plane.setPixel(x, (h - y - 1), qRgb(dcol, 0, 0)); 0201 } else if (color == ColorTools::ColorsRGB::G) { 0202 plane.setPixel(x, (h - y - 1), qRgb(0, dcol, 0)); 0203 } else if (color == ColorTools::ColorsRGB::B) { 0204 plane.setPixel(x, (h - y - 1), qRgb(0, 0, dcol)); 0205 } else if (color == ColorTools::ColorsRGB::A) { 0206 plane.setPixel(x, (h - y - 1), qRgb(dcol / 255. * qRed(background), dcol / 255. * qGreen(background), dcol / 255. * qBlue(background))); 0207 } else { 0208 plane.setPixel(x, (h - y - 1), qRgb(dcol, dcol, dcol)); 0209 } 0210 } 0211 } 0212 return plane; 0213 } 0214 0215 QImage ColorTools::yPbPrColorWheel(const QSize &size, int Y, float scaling, bool circleOnly) 0216 { 0217 0218 QImage wheel(size, QImage::Format_ARGB32); 0219 if (size.width() == 0 || size.height() == 0) { 0220 qCritical() << "ERROR: Size of the color wheel must not be 0!"; 0221 return wheel; 0222 } 0223 if (circleOnly) { 0224 wheel.fill(qRgba(0, 0, 0, 0)); 0225 } 0226 0227 double dr, dg, db, dpR; 0228 double rB, rR, rr; 0229 const int w = size.width(); 0230 const int h = size.height(); 0231 const float w2 = (float)w / 2; 0232 const float h2 = (float)h / 2; 0233 0234 for (int b = 0; b < w; ++b) { 0235 // Transform pB from {0,...,w} to [-0.5,0.5] 0236 double dpB = (double)b / (w - 1) - .5; 0237 dpB = scaling * dpB; 0238 0239 for (int r = 0; r < h; ++r) { 0240 dpR = (double)r / (h - 1) - .5; 0241 dpR = scaling * dpR; 0242 0243 if (circleOnly) { 0244 // see yuvColorWheel 0245 rB = b - double(w2); 0246 rR = r - double(h2); 0247 rr = rB * rB / (w2 * w2) + rR * rR / (h2 * h2); 0248 if (rr > 1) { 0249 continue; 0250 } 0251 } 0252 0253 // Calculate the RGB values from YPbPr 0254 dr = preventOverflow(Y + 357.5 * dpR); 0255 dg = preventOverflow(Y - 87.75 * dpB - 182.1 * dpR); 0256 db = preventOverflow(Y + 451.86 * dpB); 0257 0258 wheel.setPixel(b, (h - r - 1), qRgba(dr, dg, db, 255)); 0259 } 0260 } 0261 0262 return wheel; 0263 } 0264 0265 QImage ColorTools::hsvHueShiftPlane(const QSize &size, int S, int V, int MIN, int MAX) 0266 { 0267 Q_ASSERT(size.width() > 0); 0268 Q_ASSERT(size.height() > 0); 0269 Q_ASSERT(MAX > MIN); 0270 0271 QImage plane(size, QImage::Format_ARGB32); 0272 0273 #ifdef DEBUG_CT 0274 qCDebug(KDENLIVE_LOG) << "Requested: Saturation " << S << ", Value " << V; 0275 QColor colTest(-1, 256, 257); 0276 qCDebug(KDENLIVE_LOG) << "-1 mapped to " << colTest.red() << ", 256 to " << colTest.green() << ", 257 to " << colTest.blue(); 0277 #endif 0278 0279 QColor col(0, 0, 0); 0280 0281 const int hueValues = MAX - MIN; 0282 0283 float huediff; 0284 int newhue; 0285 for (int x = 0; x < size.width(); ++x) { 0286 float hue = x / (size.width() - 1.0) * 359; 0287 for (int y = 0; y < size.height(); ++y) { 0288 huediff = (1.0f - y / (size.height() - 1.0)) * hueValues + MIN; 0289 // qCDebug(KDENLIVE_LOG) << "hue: " << hue << ", huediff: " << huediff; 0290 0291 newhue = hue + huediff + 360; // Avoid negative numbers. Rest (>360) will be mapped correctly. 0292 0293 col.setHsv(newhue, S, V); 0294 plane.setPixel(x, y, col.rgba()); 0295 } 0296 } 0297 0298 return plane; 0299 } 0300 0301 QImage ColorTools::hsvCurvePlane(const QSize &size, const QColor &baseColor, const ComponentsHSV &xVariant, const ComponentsHSV &yVariant, bool shear, 0302 const float offsetY) 0303 { 0304 Q_ASSERT(size.width() > 0); 0305 Q_ASSERT(size.height() > 0); 0306 0307 /*int xMax, yMax; 0308 0309 switch(xVariant) { 0310 case COM_H: 0311 xMax = 360; 0312 break; 0313 case COM_S: 0314 case COM_V: 0315 xMax = 256; 0316 break; 0317 } 0318 0319 switch (yVariant) { 0320 case COM_H: 0321 yMax = 360; 0322 break; 0323 case COM_S: 0324 case COM_V: 0325 yMax = 256; 0326 break; 0327 }*/ 0328 0329 QImage plane(size, QImage::Format_ARGB32); 0330 0331 QColor col(0, 0, 0); 0332 0333 float hue, sat, val; 0334 hue = baseColor.hueF(); 0335 sat = baseColor.saturationF(); 0336 val = baseColor.valueF(); 0337 0338 for (int x = 0; x < size.width(); ++x) { 0339 switch (xVariant) { 0340 case COM_H: 0341 hue = x / (size.width() - 1.0); 0342 break; 0343 case COM_S: 0344 sat = x / (size.width() - 1.0); 0345 break; 0346 case COM_V: 0347 val = x / (size.width() - 1.0); 0348 break; 0349 } 0350 for (int y = 0; y < size.height(); ++y) { 0351 switch (yVariant) { 0352 case COM_H: 0353 hue = 1.0 - y / (size.height() - 1.0); 0354 break; 0355 case COM_S: 0356 sat = 1.0 - y / (size.height() - 1.0); 0357 break; 0358 case COM_V: 0359 val = 1.0 - y / (size.height() - 1.0); 0360 break; 0361 } 0362 0363 col.setHsvF(hue, sat, val); 0364 0365 if (!shear) { 0366 plane.setPixel(x, y, col.rgba()); 0367 } else { 0368 plane.setPixel(x, (2 * size.height() + y - x * size.width() / size.height() - int(offsetY) * size.height()) % size.height(), col.rgba()); 0369 } 0370 } 0371 } 0372 0373 return plane; 0374 }