File indexing completed on 2024-04-28 04:52:23

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 "waveformgenerator.h"
0009 
0010 #include <cmath>
0011 
0012 #include <QDebug>
0013 #include <QElapsedTimer>
0014 #include <QImage>
0015 #include <QPainter>
0016 #include <QSize>
0017 #include <vector>
0018 
0019 #define CHOP255(a) int((255) < (a) ? (255) : (a))
0020 
0021 WaveformGenerator::WaveformGenerator() = default;
0022 
0023 WaveformGenerator::~WaveformGenerator() = default;
0024 
0025 QImage WaveformGenerator::calculateWaveform(const QSize &waveformSize, const QImage &image, WaveformGenerator::PaintMode paintMode, bool drawAxis, ITURec rec,
0026                                             uint accelFactor)
0027 {
0028     Q_ASSERT(accelFactor >= 1);
0029 
0030     // QTime time;
0031     // time.start();
0032 
0033     QImage wave(waveformSize, QImage::Format_ARGB32);
0034 
0035     if (waveformSize.width() <= 0 || waveformSize.height() <= 0 || image.width() <= 0 || image.height() <= 0) {
0036         return QImage();
0037     }
0038 
0039     // Fill with transparent color
0040     wave.fill(qRgba(0, 0, 0, 0));
0041 
0042     const uint ww = uint(waveformSize.width());
0043     const uint wh = uint(waveformSize.height());
0044     const uint iw = uint(image.width());
0045     const auto totalPixels = image.width() * image.height();
0046 
0047     std::vector<std::vector<uint>> waveValues(size_t(waveformSize.width()), std::vector<uint>(size_t(waveformSize.height()), 0));
0048 
0049     // Number of input pixels that will fall on one scope pixel.
0050     // Must be a float because the acceleration factor can be high, leading to <1 expected px per px.
0051     const float pixelDepth = float(totalPixels / accelFactor) / (ww * wh);
0052     const float gain = 255.f / (8 * pixelDepth);
0053     // qCDebug(KDENLIVE_LOG) << "Pixel depth: expected " << pixelDepth << "; Gain: using " << gain << " (acceleration: " << accelFactor << "x)";
0054 
0055     // Subtract 1 from sizes because we start counting from 0.
0056     // Not doing it would result in attempts to paint outside of the image.
0057     const float hPrediv = (wh - 1) / 255.f;
0058     const float wPrediv = (ww - 1) / float(iw - 1);
0059 
0060     for (int i = 0; i < totalPixels; i += accelFactor) {
0061         const int x = i % image.width();
0062         const QRgb pixel = image.pixel(x, i / image.width());
0063 
0064         float dY, dx, dy;
0065 
0066         if (rec == ITURec::Rec_601) {
0067             // CIE 601 Luminance
0068             dY = REC_601_R * qRed(pixel) + REC_601_G * qGreen(pixel) + REC_601_B * qBlue(pixel);
0069         } else {
0070             // CIE 709 Luminance
0071             dY = REC_709_R * qRed(pixel) + REC_709_G * qGreen(pixel) + REC_709_B * qBlue(pixel);
0072         }
0073         // dY is on [0,255] now.
0074 
0075         dy = dY * hPrediv;
0076         dx = x * wPrediv;
0077         waveValues[size_t(dx)][size_t(dy)]++;
0078     }
0079 
0080     switch (paintMode) {
0081     case PaintMode_Green:
0082         for (int i = 0; i < waveformSize.width(); ++i) {
0083             for (int j = 0; j < waveformSize.height(); ++j) {
0084                 // Logarithmic scale. Needs fine tuning by hand, but looks great.
0085                 wave.setPixel(i, waveformSize.height() - j - 1,
0086                               qRgba(CHOP255(52 * logf(0.1f * gain * float(waveValues[size_t(i)][size_t(j)]))),
0087                                     CHOP255(52 * logf(gain * float(waveValues[size_t(i)][size_t(j)]))),
0088                                     CHOP255(52 * logf(.25f * gain * float(waveValues[size_t(i)][size_t(j)]))),
0089                                     CHOP255(64 * logf(gain * float(waveValues[size_t(i)][size_t(j)])))));
0090             }
0091         }
0092         break;
0093     case PaintMode_Yellow:
0094         for (int i = 0; i < waveformSize.width(); ++i) {
0095             for (int j = 0; j < waveformSize.height(); ++j) {
0096                 wave.setPixel(i, waveformSize.height() - j - 1, qRgba(255, 242, 0, CHOP255(gain * float(waveValues[size_t(i)][size_t(j)]))));
0097             }
0098         }
0099         break;
0100     default:
0101         for (int i = 0; i < waveformSize.width(); ++i) {
0102             for (int j = 0; j < waveformSize.height(); ++j) {
0103                 wave.setPixel(i, waveformSize.height() - j - 1, qRgba(255, 255, 255, CHOP255(2.f * gain * float(waveValues[size_t(i)][size_t(j)]))));
0104             }
0105         }
0106         break;
0107     }
0108 
0109     if (drawAxis) {
0110         QPainter davinci;
0111         bool ok = davinci.begin(&wave);
0112         if (!ok) {
0113             qDebug() << "Could not initialise QPainter for Waveform.";
0114             return wave;
0115         }
0116 
0117         QRgb opx;
0118         davinci.setPen(qRgba(150, 255, 200, 32));
0119         davinci.setCompositionMode(QPainter::CompositionMode_Overlay);
0120         for (int i = 0; i <= 10; ++i) {
0121             int dy = int(i / 10.f * (wh - 1));
0122             for (int x = 0; x < int(ww); ++x) {
0123                 opx = wave.pixel(x, dy);
0124                 wave.setPixel(x, dy, qRgba(CHOP255(150 + qRed(opx)), 255, CHOP255(200 + qBlue(opx)), CHOP255(32 + qAlpha(opx))));
0125             }
0126         }
0127     }
0128 
0129     // uint diff = time.elapsed();
0130     // Q_EMIT signalCalculationFinished(wave, diff);
0131 
0132     return wave;
0133 }
0134 #undef CHOP255