File indexing completed on 2024-04-28 04:52:22
0001 /* 0002 SPDX-FileCopyrightText: 2010 Simon Andreas Eugster <simon.eu@gmail.com> 0003 This file is part of kdenlive. See www.kdenlive.org. 0004 0005 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0006 */ 0007 0008 #include "rgbparadegenerator.h" 0009 #include "klocalizedstring.h" 0010 #include <QColor> 0011 #include <QDebug> 0012 #include <QPainter> 0013 0014 #define CHOP255(a) ((255) < (a) ? (255) : int(a)) 0015 #define CHOP1255(a) ((a) < (1) ? (1) : ((a) > (255) ? (255) : (a))) 0016 0017 const QColor RGBParadeGenerator::colHighlight(255, 245, 235, 255); 0018 const QColor RGBParadeGenerator::colLight(200, 200, 200, 255); 0019 const QColor RGBParadeGenerator::colSoft(150, 150, 150, 255); 0020 0021 const uchar RGBParadeGenerator::distRight(40); 0022 const uchar RGBParadeGenerator::distBottom(40); 0023 0024 struct StructRGB 0025 { 0026 uint r; 0027 uint g; 0028 uint b; 0029 }; 0030 0031 RGBParadeGenerator::RGBParadeGenerator() = default; 0032 0033 QImage RGBParadeGenerator::calculateRGBParade(const QSize ¶deSize, const QImage &image, const RGBParadeGenerator::PaintMode paintMode, bool drawAxis, 0034 bool drawGradientRef, uint accelFactor) 0035 { 0036 Q_ASSERT(accelFactor >= 1); 0037 0038 if (paradeSize.width() <= 0 || paradeSize.height() <= 0 || image.width() <= 0 || image.height() <= 0) { 0039 return QImage(); 0040 } 0041 QImage parade(paradeSize, QImage::Format_ARGB32); 0042 parade.fill(Qt::transparent); 0043 0044 QPainter davinci; 0045 bool ok = davinci.begin(¶de); 0046 if (!ok) { 0047 qDebug() << "Could not initialise QPainter for RGB parade."; 0048 return parade; 0049 } 0050 0051 const uint ww = uint(paradeSize.width()); 0052 const uint wh = uint(paradeSize.height()); 0053 const uint iw = uint(image.width()); 0054 const uint ih = uint(image.height()); 0055 0056 const uchar offset = 10; 0057 const uint partW = (ww - 2 * offset - distRight) / 3; 0058 const uint partH = wh - distBottom; 0059 0060 // Statistics 0061 uchar minR = 255, minG = 255, minB = 255, maxR = 0, maxG = 0, maxB = 0; 0062 0063 // Number of input pixels that will fall on one scope pixel. 0064 // Must be a float because the acceleration factor can be high, leading to <1 expected px per px. 0065 const float pixelDepth = float((iw * ih) / accelFactor) / (partW * 255); 0066 const float gain = 255 / (8 * pixelDepth); 0067 // qCDebug(KDENLIVE_LOG) << "Pixel depth: expected " << pixelDepth << "; Gain: using " << gain << " (acceleration: " << accelFactor << "x)"; 0068 0069 QImage unscaled(int(ww) - distRight, 256, QImage::Format_ARGB32); 0070 unscaled.fill(qRgba(0, 0, 0, 0)); 0071 0072 const float wPrediv = float(partW - 1) / (iw - 1); 0073 0074 std::vector<std::vector<StructRGB>> paradeVals(partW, std::vector<StructRGB>(256, {0, 0, 0})); 0075 0076 const auto totalPixels = image.width() * image.height(); 0077 for (int i = 0; i < totalPixels; i += accelFactor) { 0078 const auto x = i % image.width(); 0079 const QRgb pixel = image.pixel(x, i / image.width()); 0080 auto r = uchar(qRed(pixel)); 0081 auto g = uchar(qGreen(pixel)); 0082 auto b = uchar(qBlue(pixel)); 0083 0084 double dx = x * double(wPrediv); 0085 0086 paradeVals[size_t(dx)][r].r++; 0087 paradeVals[size_t(dx)][g].g++; 0088 paradeVals[size_t(dx)][b].b++; 0089 0090 if (r < minR) { 0091 minR = r; 0092 } 0093 if (g < minG) { 0094 minG = g; 0095 } 0096 if (b < minB) { 0097 minB = b; 0098 } 0099 if (r > maxR) { 0100 maxR = r; 0101 } 0102 if (g > maxG) { 0103 maxG = g; 0104 } 0105 if (b > maxB) { 0106 maxB = b; 0107 } 0108 } 0109 0110 const int offset1 = int(partW + offset); 0111 const int offset2 = int(2 * partW + 2 * offset); 0112 switch (paintMode) { 0113 case PaintMode_RGB: 0114 for (int i = 0; i < int(partW); ++i) { 0115 for (int j = 0; j < 256; ++j) { 0116 unscaled.setPixel(i, j, qRgba(255, 10, 10, CHOP255(gain * float(paradeVals[size_t(i)][size_t(j)].r)))); 0117 unscaled.setPixel(i + offset1, j, qRgba(10, 255, 10, CHOP255(gain * float(paradeVals[size_t(i)][size_t(j)].g)))); 0118 unscaled.setPixel(i + offset2, j, qRgba(10, 10, 255, CHOP255(gain * float(paradeVals[size_t(i)][size_t(j)].b)))); 0119 } 0120 } 0121 break; 0122 default: 0123 for (int i = 0; i < int(partW); ++i) { 0124 for (int j = 0; j < 256; ++j) { 0125 unscaled.setPixel(i, j, qRgba(255, 255, 255, CHOP255(gain * float(paradeVals[size_t(i)][size_t(j)].r)))); 0126 unscaled.setPixel(i + offset1, j, qRgba(255, 255, 255, CHOP255(gain * float(paradeVals[size_t(i)][size_t(j)].g)))); 0127 unscaled.setPixel(i + offset2, j, qRgba(255, 255, 255, CHOP255(gain * float(paradeVals[size_t(i)][size_t(j)].b)))); 0128 } 0129 } 0130 break; 0131 } 0132 0133 // Scale the image to the target height. Scaling is not accomplished before because 0134 // there are only 255 different values which would lead to gaps if the height is not exactly 255. 0135 // Don't use bilinear transformation because the fast transformation meets the goal better. 0136 davinci.drawImage(0, 0, unscaled.mirrored(false, true).scaled(unscaled.width(), int(partH), Qt::IgnoreAspectRatio, Qt::FastTransformation)); 0137 0138 if (drawAxis) { 0139 QRgb opx; 0140 for (int i = 0; i <= 10; ++i) { 0141 int dy = i * int(partH - 1) / 10; 0142 for (int x = 0; x < int(ww - distRight); ++x) { 0143 opx = parade.pixel(x, dy); 0144 parade.setPixel(x, dy, qRgba(CHOP255(150 + qRed(opx)), 255, CHOP255(200 + qBlue(opx)), CHOP255(32 + qAlpha(opx)))); 0145 } 0146 } 0147 } 0148 0149 if (drawGradientRef) { 0150 davinci.setPen(colLight); 0151 davinci.drawLine(0, int(partH), int(partW), 0); 0152 davinci.drawLine(int(partW + offset), int(partH), int(2 * partW + offset), 0); 0153 davinci.drawLine(int(2 * partW + 2 * offset), int(partH), int(3 * partW + 2 * offset), 0); 0154 } 0155 0156 const int d = 50; 0157 0158 // Show numerical minimum 0159 if (minR == 0) { 0160 davinci.setPen(colHighlight); 0161 } else { 0162 davinci.setPen(colSoft); 0163 } 0164 davinci.drawText(0, int(wh), i18n("min: ")); 0165 if (minG == 0) { 0166 davinci.setPen(colHighlight); 0167 } else { 0168 davinci.setPen(colSoft); 0169 } 0170 davinci.drawText(int(partW + offset), int(wh), i18n("min: ")); 0171 if (minB == 0) { 0172 davinci.setPen(colHighlight); 0173 } else { 0174 davinci.setPen(colSoft); 0175 } 0176 davinci.drawText(int(2 * partW + 2 * offset), int(wh), i18n("min: ")); 0177 0178 // Show numerical maximum 0179 if (maxR == 255) { 0180 davinci.setPen(colHighlight); 0181 } else { 0182 davinci.setPen(colSoft); 0183 } 0184 davinci.drawText(0, int(wh - 20), i18n("max: ")); 0185 if (maxG == 255) { 0186 davinci.setPen(colHighlight); 0187 } else { 0188 davinci.setPen(colSoft); 0189 } 0190 davinci.drawText(int(partW + offset), int(wh) - 20, i18n("max: ")); 0191 if (maxB == 255) { 0192 davinci.setPen(colHighlight); 0193 } else { 0194 davinci.setPen(colSoft); 0195 } 0196 davinci.drawText(int(2 * partW + 2 * offset), int(wh - 20), i18n("max: ")); 0197 0198 davinci.setPen(colLight); 0199 davinci.drawText(d, int(wh), QString::number(minR, 'f', 0)); 0200 davinci.drawText(int(partW + offset + d), int(wh), QString::number(minG, 'f', 0)); 0201 davinci.drawText(int(2 * partW + 2 * offset + d), int(wh), QString::number(minB, 'f', 0)); 0202 0203 davinci.drawText(d, int(wh - 20), QString::number(maxR, 'f', 0)); 0204 davinci.drawText(int(partW + offset + d), int(wh) - 20, QString::number(maxG, 'f', 0)); 0205 davinci.drawText(int(2 * partW + 2 * offset + d), int(wh - 20), QString::number(maxB, 'f', 0)); 0206 0207 return parade; 0208 } 0209 0210 #undef CHOP255