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 &paradeSize, 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(&parade);
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