File indexing completed on 2024-04-28 08:44:54

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 }