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 "histogramgenerator.h"
0009 
0010 #include "klocalizedstring.h"
0011 #include <QDebug>
0012 #include <QImage>
0013 #include <QPainter>
0014 #include <algorithm>
0015 #include <cmath>
0016 
0017 HistogramGenerator::HistogramGenerator() = default;
0018 
0019 QImage HistogramGenerator::calculateHistogram(const QSize &paradeSize, const QImage &image, const int &components, ITURec rec, bool unscaled, bool logScale,
0020                                               uint accelFactor) const
0021 {
0022     if (paradeSize.height() <= 0 || paradeSize.width() <= 0 || image.width() <= 0 || image.height() <= 0) {
0023         return QImage();
0024     }
0025 
0026     bool drawY = (components & HistogramGenerator::ComponentY) != 0;
0027     bool drawR = (components & HistogramGenerator::ComponentR) != 0;
0028     bool drawG = (components & HistogramGenerator::ComponentG) != 0;
0029     bool drawB = (components & HistogramGenerator::ComponentB) != 0;
0030     bool drawSum = (components & HistogramGenerator::ComponentSum) != 0;
0031 
0032     int r[256], g[256], b[256], y[256], s[766];
0033     // Initialize the values to zero
0034     std::fill(r, r + 256, 0);
0035     std::fill(g, g + 256, 0);
0036     std::fill(b, b + 256, 0);
0037     std::fill(y, y + 256, 0);
0038     std::fill(s, s + 766, 0);
0039 
0040     const int ww = paradeSize.width();
0041     const int wh = paradeSize.height();
0042 
0043     // Read the stats from the input image
0044     for (int Y = 0; Y < image.height(); ++Y) {
0045         for (int X = 0; X < image.width(); X += accelFactor) {
0046 
0047             QRgb col = image.pixel(X, Y);
0048             r[qRed(col)]++;
0049             g[qGreen(col)]++;
0050             b[qBlue(col)]++;
0051 
0052             if (drawY) {
0053                 // Use if branch to avoid expensive multiplication if Y disabled
0054                 if (rec == ITURec::Rec_601) {
0055                     y[int(REC_601_R * qRed(col) + REC_601_G * qGreen(col) + REC_601_B * qBlue(col))]++;
0056                 } else {
0057                     y[int(REC_709_R * qRed(col) + REC_709_G * qGreen(col) + REC_709_B * qBlue(col))]++;
0058                 }
0059             }
0060 
0061             if (drawSum) {
0062                 // Use an if branch here because the sum takes more operations than rgb
0063                 s[qRed(col)]++;
0064                 s[qGreen(col)]++;
0065                 s[qBlue(col)]++;
0066             }
0067         }
0068     }
0069 
0070     const int nParts = (drawY ? 1 : 0) + (drawR ? 1 : 0) + (drawG ? 1 : 0) + (drawB ? 1 : 0) + (drawSum ? 1 : 0);
0071     if (nParts == 0) {
0072         // Nothing to draw
0073         return QImage();
0074     }
0075 
0076     // Distance for text
0077     const int d = 20;
0078 
0079     // Height of a single histogram box without text
0080     const int partH = (wh - nParts * d) / nParts;
0081 
0082     // Total number of bytes of the image
0083     const int byteCount = int(image.sizeInBytes());
0084 
0085     // Factor for scaling the measured value to the histogram.
0086     // This factor is used for linear scaling and does not depend
0087     // on the measured histogram values. Very large values,
0088     // e.g. in an image with a lot of white, are clipped.
0089     // Otherwise, the relatively low height of the histogram
0090     // would show all other values close to 0 when one bin is very high.
0091     float scaling = 0;
0092     int div = byteCount >> 7;
0093     if (div > 0) {
0094         scaling = partH / float(byteCount >> 7);
0095     }
0096     const int dist = 40;
0097 
0098     QImage histogram(paradeSize, QImage::Format_ARGB32);
0099     QPainter davinci;
0100     bool ok = davinci.begin(&histogram);
0101     if (!ok) {
0102         qDebug() << "Could not initialise QPainter for Histogram.";
0103         return histogram;
0104     }
0105     davinci.setPen(QColor(220, 220, 220, 255));
0106     histogram.fill(qRgba(0, 0, 0, 0));
0107 
0108     QColor neutralColor(220, 220, 210, 255);
0109     QColor redColor(255, 128, 0, 255);
0110     QColor greenColor(128, 255, 0, 255);
0111     QColor blueColor(0, 128, 255, 255);
0112 
0113     int wy = 0; // Drawing position
0114 
0115     if (drawY) {
0116         drawComponentFull(&davinci, y, scaling, QRect(0, wy, ww, partH + dist), neutralColor, dist, unscaled, logScale, 256);
0117         wy += partH + d;
0118     }
0119 
0120     if (drawSum) {
0121         drawComponentFull(&davinci, s, scaling / 3, QRect(0, wy, ww, partH + dist), neutralColor, dist, unscaled, logScale, 256);
0122         wy += partH + d;
0123     }
0124 
0125     if (drawR) {
0126         drawComponentFull(&davinci, r, scaling, QRect(0, wy, ww, partH + dist), redColor, dist, unscaled, logScale, 256);
0127         wy += partH + d;
0128     }
0129 
0130     if (drawG) {
0131         drawComponentFull(&davinci, g, scaling, QRect(0, wy, ww, partH + dist), greenColor, dist, unscaled, logScale, 256);
0132         wy += partH + d;
0133     }
0134 
0135     if (drawB) {
0136         drawComponentFull(&davinci, b, scaling, QRect(0, wy, ww, partH + dist), blueColor, dist, unscaled, logScale, 256);
0137     }
0138 
0139     return histogram;
0140 }
0141 
0142 QImage HistogramGenerator::drawComponent(const int *y, const QSize &size, const float &scaling, const QColor &color, bool unscaled, bool logScale, int max)
0143 {
0144     QImage component(max, size.height(), QImage::Format_ARGB32);
0145     component.fill(qRgba(0, 0, 0, 255));
0146     Q_ASSERT(scaling != INFINITY);
0147 
0148     const int partH = size.height();
0149 
0150     const int maxBinSize = *std::max_element(&y[0], &y[max - 1]);
0151     const float logScaling = float(size.height()) / log10f(float(maxBinSize + 1));
0152 
0153     for (int x = 0; x < max; ++x) {
0154 
0155         // Calculate the height of the curve at position x
0156         int partY;
0157         if (logScale) {
0158             partY = int(logScaling * log10f(float(y[x] + 1)));
0159         } else {
0160             partY = int(scaling * y[x]);
0161         }
0162 
0163         // Invert the y axis
0164         if (partY > partH - 1) {
0165             partY = partH - 1;
0166         }
0167         partY = partH - 1 - partY;
0168 
0169         for (int k = partH - 1; k >= partY; --k) {
0170             component.setPixel(x, k, color.rgba());
0171         }
0172     }
0173     if (unscaled && size.width() >= component.width()) {
0174         return component;
0175     }
0176     return component.scaled(size, Qt::IgnoreAspectRatio, Qt::FastTransformation);
0177 }
0178 
0179 void HistogramGenerator::drawComponentFull(QPainter *davinci, const int *y, const float &scaling, const QRect &rect, const QColor &color, int textSpace,
0180                                            bool unscaled, bool logScale, int max)
0181 {
0182     QImage component = drawComponent(y, rect.size() - QSize(0, textSpace), scaling, color, unscaled, logScale, max);
0183     davinci->drawImage(rect.topLeft(), component);
0184 
0185     int min = 0;
0186     for (int x = 0; x < max; ++x) {
0187         min = x;
0188         if (y[x] > 0) {
0189             break;
0190         }
0191     }
0192     int maxVal = max - 1;
0193     for (int x = max - 1; x >= 0; --x) {
0194         maxVal = x;
0195         if (y[x] > 0) {
0196             break;
0197         }
0198     }
0199 
0200     const int textY = rect.bottom() - textSpace + 15;
0201     const int dist = 40;
0202     const int cw = component.width();
0203 
0204     davinci->drawText(0, textY, i18n("min"));
0205     davinci->drawText(dist, textY, QString::number(min, 'f', 0));
0206 
0207     davinci->drawText(cw - dist - 30, textY, i18n("max"));
0208     davinci->drawText(cw - 30, textY, QString::number(maxVal, 'f', 0));
0209 }