diff --git a/dev-docs/architecture.md b/dev-docs/architecture.md index 8a0013c9d..447bc2c00 100644 --- a/dev-docs/architecture.md +++ b/dev-docs/architecture.md @@ -1,36 +1,35 @@ # Architecture *This document describes Kdenlive’s architecture top-down.* * Top-down architecture * Dependencies MLT etc. * MVC * GUI elements and their counterpart -* [kdenlivesettings.kcfg](../src/kdenlivesettings.kcfg) ## Architectural Overview Kdenlive uses a number of libraries. The most important library is MLT which is responsible for the core video editing functionality: the process of applying effects to clips which are organised in tracks and timeline(s). Kdenlive provides the user interface for this functionality. Kdenlive and MLT use a number of resources like frei0r for video effects. ``` ┌────────┐ │Kdenlive├─────┐ └────┬───┘ │ Render projects│ │Configure Decode audio/video│ │effect settings │ │ ┌────┴───┐ │ │ MLT ├─────┤ └────────┘ │Audio/Video Effects ├───────┬──────┬─────────┐ ┌──┴───┐┌──┴───┐┌─┴─┐┌──────┴──────┐ │frei0r││LADSPA││SoX││libsamplerate│ └──────┘└──────┘└───┘└─────────────┘ ``` diff --git a/dev-docs/build.md b/dev-docs/build.md index 0417b26d2..67282ea5d 100644 --- a/dev-docs/build.md +++ b/dev-docs/build.md @@ -1,165 +1,165 @@ # Building *This page describes how to build Kdenlive with its dependencies.* ## Building Kdenlive Building is done in 3 steps: 1. Install the development dependencies and remove existing installations 2. Clone the repositories 3. Build and install the projects ### Get the development dependencies Kdenlive usually requires the latest versions of MLT. MLT depends on frei0r, and on Ubuntu frei0r needs to be built as well as there is no `frei0r-plugins-dev` package. First, install the requirements. For Ubuntu 20.04 and 19.10, the required packages are: ```bash # Basic packages sudo apt install git build-essential cmake cmake-extras pkg-config # frei0r sudo apt install libopencv-dev libcairo-dev libgavl-dev # MLT sudo apt install libavformat-dev libsdl2-dev libswscale-dev libavfilter-dev \ libavdevice-dev libgdk-pixbuf2.0-dev libpango1.0-dev libexif-dev \ libfftw3-dev libebur128-dev librubberband-dev librtaudio-dev \ libvidstab-dev libxml2-dev qt5-default libqt5svg5-dev # Kdenlive sudo apt install libkf5archive-dev libkf5bookmarks-dev libkf5coreaddons-dev \ libkf5config-dev libkf5configwidgets-dev libkf5dbusaddons-dev \ libkf5kio-dev libkf5widgetsaddons-dev libkf5notifyconfig-dev \ libkf5newstuff-dev libkf5xmlgui-dev libkf5declarative-dev \ libkf5notifications-dev libkf5guiaddons-dev libkf5textwidgets-dev \ libkf5purpose-dev libkf5iconthemes-dev kdoctools-dev libkf5crash-dev \ libkf5filemetadata-dev kio kinit qtdeclarative5-dev libqt5svg5-dev \ qml-module-qtquick-controls qtmultimedia5-dev qtquickcontrols2-5-dev \ - appstream gettext libv4l-dev breeze + appstream gettext libv4l-dev libqt5webkit5-dev librttr-dev breeze ffmpeg ``` Most development packages will already be installed with the following command: ```bash # Ubuntu # Enable deb-src entries /etc/apt/sources beforehand! sudo apt-get build-dep mlt kdenlive # OpenSuse zypper build-dep # Fedora # Install builddep beforehand dnf builddep ``` **Important:** If you are going to install the projects to your `/usr` after building them, make sure to remove existing packages of Kdenlive, MLT, and frei0r beforehand! ```bash apt purge kdenlive frei0r-plugins libmlt++3 ``` ### Clone frei0r, MLT, and Kdenlive ```bash git clone https://github.com/dyne/frei0r.git git clone https://github.com/mltframework/mlt.git git clone https://invent.kde.org/kde/kdenlive.git ``` ### Build it! Now you can build and install the projects. As Kdenlive depends on MLT which depends on frei0r, build and install them in reverse order. Note that `make install` is required for Kdenlive, otherwise the effects will not be installed and cannot be used. For frei0r, MLT, and Kdenlive (in that order), run the following steps inside their directory to build, compile, and install it. ```bash # Create a build directory – the build files remain in here mkdir build cd build # Check dependencies and configure the build process cmake .. -DCMAKE_INSTALL_PREFIX=/usr # Compile make # Install the compiled files sudo make install ``` #### Building on Ubuntu 20.04 Ubuntu 20.04 provides OpenCV 4.2 which is not supported by the frei0r plugins yet. Run CMake with ```bash cmake .. -DWITHOUT_OPENCV=true -DCMAKE_INSTALL_PREFIX=/usr ``` ### Install Kdenlive to a local path As alternative to installing Kdenlive system wide in `/usr`, it can also be installed in a custom directory, for example `~/.local`, in order to still have a global sytem version for comparison. In that case, the `prefix.sh` script in the build directory has to be executed prior to running Kdenlive from the installation directory; it sets some environment variables. ```bash # Configure with a different install directory cmake .. -DCMAKE_INSTALL_PREFIX=~/.local # Compile and install (sudo is not required for ~/.local) make make install # Load environment variables chmod u+x prefix.sh ./prefix.sh # Run Kdenlive ~/.local/bin/kdenlive ``` ### Building in Docker For checking if the dependencies are still up-to-date, it is possible to run all the above commands inside a Docker container with a fresh Ubuntu, for example. Note that Kdenlive cannot be run from inside the Docker container as it is a GUI application and Docker is console-only. ```bash # Spin up a Docker container # The --rm flag removes the container after it is stopped. docker run -it --rm ubuntu:19.10 # Now install the dependencies etc. # Note that you are root in the container, and sudo neither exists nor works. apt install … # When you are done, exit exit ``` ## Translating Kdenlive TODO diff --git a/dev-docs/coding.md b/dev-docs/coding.md new file mode 100644 index 000000000..ca7ca5398 --- /dev/null +++ b/dev-docs/coding.md @@ -0,0 +1,30 @@ +# Coding and Resources + +* [All Qt5 classes][qt5c] +* [MLT introduction][mlt-intro]. + +## Configuration + +Named settings are stored in [`kdenlivesettings.kcfg`][sett]. To add a new +setting with default value, add an entry in the settings file, for example: + +```xml + + + true + +``` + +The setting can then be read and written as follows: + +```cpp +// Read +bool logScale = KdenliveSettings::logscale(); + +// Write +KdenliveSettings::setLogscale(true); +``` + +[sett]: ../src/kdenlivesettings.kcfg +[mlt-intro]: https://www.mltframework.org/docs/framework/ +[qt5c]: https://doc.qt.io/qt-5/classes.html diff --git a/src/scopes/colorscopes/CMakeLists.txt b/src/scopes/colorscopes/CMakeLists.txt index ecda42204..afd65cee3 100644 --- a/src/scopes/colorscopes/CMakeLists.txt +++ b/src/scopes/colorscopes/CMakeLists.txt @@ -1,14 +1,15 @@ set(kdenlive_SRCS ${kdenlive_SRCS} + scopes/colorscopes/colorconstants.h scopes/colorscopes/abstractgfxscopewidget.cpp scopes/colorscopes/colorplaneexport.cpp scopes/colorscopes/histogram.cpp scopes/colorscopes/histogramgenerator.cpp scopes/colorscopes/rgbparade.cpp scopes/colorscopes/rgbparadegenerator.cpp scopes/colorscopes/vectorscope.cpp scopes/colorscopes/vectorscopegenerator.cpp scopes/colorscopes/waveform.cpp scopes/colorscopes/waveformgenerator.cpp PARENT_SCOPE ) diff --git a/src/scopes/colorscopes/colorconstants.h b/src/scopes/colorscopes/colorconstants.h new file mode 100644 index 000000000..12f5368c6 --- /dev/null +++ b/src/scopes/colorscopes/colorconstants.h @@ -0,0 +1,22 @@ +#ifndef KDENLIVE_COLORCONSTANTS_H +#define KDENLIVE_COLORCONSTANTS_H + +/** + * ITU-R Recommendation for luminance calculation. + * See http://www.poynton.com/ColorFAQ.html for details. + */ +enum class ITURec { + Rec_601, Rec_709 +}; + +// CIE 601 luminance factors +constexpr float REC_601_R = .299; +constexpr float REC_601_G = .587; +constexpr float REC_601_B = .114; + +// CIE 709 luminance factors +constexpr float REC_709_R = .2125; +constexpr float REC_709_G = .7154; +constexpr float REC_709_B = .0721; + +#endif //KDENLIVE_COLORCONSTANTS_H diff --git a/src/scopes/colorscopes/histogram.cpp b/src/scopes/colorscopes/histogram.cpp index 71492171b..3d9fa434e 100644 --- a/src/scopes/colorscopes/histogram.cpp +++ b/src/scopes/colorscopes/histogram.cpp @@ -1,145 +1,153 @@ /*************************************************************************** * Copyright (C) 2010 by Simon Andreas Eugster (simon.eu@gmail.com) * * This file is part of kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #include "histogram.h" #include "histogramgenerator.h" #include #include "klocalizedstring.h" #include #include +#include Histogram::Histogram(QWidget *parent) : AbstractGfxScopeWidget(false, parent) { m_ui = new Ui::Histogram_UI(); m_ui->setupUi(this); m_aUnscaled = new QAction(i18n("Unscaled"), this); m_aUnscaled->setCheckable(true); m_aRec601 = new QAction(i18n("Rec. 601"), this); m_aRec601->setCheckable(true); m_aRec709 = new QAction(i18n("Rec. 709"), this); m_aRec709->setCheckable(true); m_agRec = new QActionGroup(this); m_agRec->addAction(m_aRec601); m_agRec->addAction(m_aRec709); m_menu->addSeparator(); m_menu->addAction(m_aUnscaled); m_menu->addSeparator()->setText(i18n("Luma mode")); m_menu->addAction(m_aRec601); m_menu->addAction(m_aRec709); + QButtonGroup scaleGroup; + scaleGroup.addButton(m_ui->rbLinear); + scaleGroup.addButton(m_ui->rbLogarithmic); + connect(m_ui->cbY, &QAbstractButton::toggled, this, &AbstractScopeWidget::forceUpdateScope); connect(m_ui->cbS, &QAbstractButton::toggled, this, &AbstractScopeWidget::forceUpdateScope); connect(m_ui->cbR, &QAbstractButton::toggled, this, &AbstractScopeWidget::forceUpdateScope); connect(m_ui->cbG, &QAbstractButton::toggled, this, &AbstractScopeWidget::forceUpdateScope); connect(m_ui->cbB, &QAbstractButton::toggled, this, &AbstractScopeWidget::forceUpdateScope); connect(m_aUnscaled, &QAction::toggled, this, &Histogram::forceUpdateScope); connect(m_aRec601, &QAction::toggled, this, &Histogram::forceUpdateScope); connect(m_aRec709, &QAction::toggled, this, &Histogram::forceUpdateScope); + connect(m_ui->rbLogarithmic, &QAbstractButton::toggled, this, &Histogram::forceUpdateScope); init(); m_histogramGenerator = new HistogramGenerator(); } Histogram::~Histogram() { writeConfig(); delete m_histogramGenerator; delete m_ui; delete m_aUnscaled; delete m_aRec601; delete m_aRec709; delete m_agRec; } void Histogram::readConfig() { AbstractGfxScopeWidget::readConfig(); KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup scopeConfig(config, configName()); m_ui->cbY->setChecked(scopeConfig.readEntry("yEnabled", true)); m_ui->cbS->setChecked(scopeConfig.readEntry("sEnabled", false)); m_ui->cbR->setChecked(scopeConfig.readEntry("rEnabled", true)); m_ui->cbG->setChecked(scopeConfig.readEntry("gEnabled", true)); m_ui->cbB->setChecked(scopeConfig.readEntry("bEnabled", true)); m_aRec601->setChecked(scopeConfig.readEntry("rec601", false)); m_aRec709->setChecked(!m_aRec601->isChecked()); + m_ui->rbLogarithmic->setChecked(scopeConfig.readEntry("logScale", false)); } void Histogram::writeConfig() { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup scopeConfig(config, configName()); scopeConfig.writeEntry("yEnabled", m_ui->cbY->isChecked()); scopeConfig.writeEntry("sEnabled", m_ui->cbS->isChecked()); scopeConfig.writeEntry("rEnabled", m_ui->cbR->isChecked()); scopeConfig.writeEntry("gEnabled", m_ui->cbG->isChecked()); scopeConfig.writeEntry("bEnabled", m_ui->cbB->isChecked()); scopeConfig.writeEntry("rec601", m_aRec601->isChecked()); + scopeConfig.writeEntry("logScale", m_ui->rbLogarithmic->isChecked()); scopeConfig.sync(); } QString Histogram::widgetName() const { return QStringLiteral("Histogram"); } bool Histogram::isHUDDependingOnInput() const { return false; } bool Histogram::isScopeDependingOnInput() const { return true; } bool Histogram::isBackgroundDependingOnInput() const { return false; } QRect Histogram::scopeRect() { // qCDebug(KDENLIVE_LOG) << "According to the spacer, the top left point is " << m_ui->verticalSpacer->geometry().x() << '/' << // m_ui->verticalSpacer->geometry().y(); QPoint topleft(offset, offset + m_ui->verticalSpacer->geometry().y()); return QRect(topleft, this->rect().size() - QSize(topleft.x() + offset, topleft.y() + offset)); } QImage Histogram::renderHUD(uint) { emit signalHUDRenderingFinished(0, 1); return QImage(); } QImage Histogram::renderGfxScope(uint accelFactor, const QImage &qimage) { QElapsedTimer timer; timer.start(); const int componentFlags = (m_ui->cbY->isChecked() ? 1 : 0) * HistogramGenerator::ComponentY | (m_ui->cbS->isChecked() ? 1 : 0) * HistogramGenerator::ComponentSum | (m_ui->cbR->isChecked() ? 1 : 0) * HistogramGenerator::ComponentR | (m_ui->cbG->isChecked() ? 1 : 0) * HistogramGenerator::ComponentG | (m_ui->cbB->isChecked() ? 1 : 0) * HistogramGenerator::ComponentB; - HistogramGenerator::Rec rec = m_aRec601->isChecked() ? HistogramGenerator::Rec_601 : HistogramGenerator::Rec_709; + ITURec rec = m_aRec601->isChecked() ? ITURec::Rec_601 : ITURec::Rec_709; - QImage histogram = m_histogramGenerator->calculateHistogram(m_scopeRect.size(), qimage, componentFlags, rec, m_aUnscaled->isChecked(), accelFactor); + QImage histogram = m_histogramGenerator->calculateHistogram(m_scopeRect.size(), qimage, componentFlags, rec, m_aUnscaled->isChecked(), m_ui->rbLogarithmic->isChecked(), accelFactor); emit signalScopeRenderingFinished(uint(timer.elapsed()), accelFactor); return histogram; } QImage Histogram::renderBackground(uint) { emit signalBackgroundRenderingFinished(0, 1); return QImage(); } diff --git a/src/scopes/colorscopes/histogramgenerator.cpp b/src/scopes/colorscopes/histogramgenerator.cpp index 1251a5a33..0340304c0 100644 --- a/src/scopes/colorscopes/histogramgenerator.cpp +++ b/src/scopes/colorscopes/histogramgenerator.cpp @@ -1,182 +1,208 @@ /*************************************************************************** * Copyright (C) 2010 by Simon Andreas Eugster (simon.eu@gmail.com) * * This file is part of kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #include "histogramgenerator.h" +#include "colorconstants.h" #include "klocalizedstring.h" #include #include #include #include HistogramGenerator::HistogramGenerator() = default; -QImage HistogramGenerator::calculateHistogram(const QSize ¶deSize, const QImage &image, const int &components, HistogramGenerator::Rec rec, bool unscaled, +QImage HistogramGenerator::calculateHistogram(const QSize ¶deSize, const QImage &image, const int &components, + ITURec rec, bool unscaled, bool logScale, uint accelFactor) const { if (paradeSize.height() <= 0 || paradeSize.width() <= 0 || image.width() <= 0 || image.height() <= 0) { return QImage(); } bool drawY = (components & HistogramGenerator::ComponentY) != 0; bool drawR = (components & HistogramGenerator::ComponentR) != 0; bool drawG = (components & HistogramGenerator::ComponentG) != 0; bool drawB = (components & HistogramGenerator::ComponentB) != 0; bool drawSum = (components & HistogramGenerator::ComponentSum) != 0; int r[256], g[256], b[256], y[256], s[766]; // Initialize the values to zero std::fill(r, r + 256, 0); std::fill(g, g + 256, 0); std::fill(b, b + 256, 0); std::fill(y, y + 256, 0); std::fill(s, s + 766, 0); - const uint iw = (uint)image.bytesPerLine(); - const uint ih = (uint)image.height(); const uint ww = (uint)paradeSize.width(); const uint wh = (uint)paradeSize.height(); - const uint byteCount = iw * ih; // Read the stats from the input image for (int Y = 0; Y < image.height(); ++Y) { for (int X = 0; X < image.width(); X += (int)accelFactor) { + QRgb col = image.pixel(X, Y); r[qRed(col)]++; g[qGreen(col)]++; b[qBlue(col)]++; + if (drawY) { // Use if branch to avoid expensive multiplication if Y disabled - if (rec == HistogramGenerator::Rec_601) { - y[(int)floor(.299 * qRed(col) + .587 * qGreen(col) + .114 * qBlue(col))]++; + if (rec == ITURec::Rec_601) { + y[(int)floor(REC_601_R * (float) qRed(col) + REC_601_G * (float) qGreen(col) + REC_601_B * (float) qBlue(col))]++; } else { - y[(int)floor(.2125 * qRed(col) + .7154 * qGreen(col) + .0721 * qBlue(col))]++; + y[(int)floor(REC_709_R * (float) qRed(col) + REC_709_G * (float) qGreen(col) + REC_709_B * (float) qBlue(col))]++; } } + if (drawSum) { // Use an if branch here because the sum takes more operations than rgb s[qRed(col)]++; s[qGreen(col)]++; s[qBlue(col)]++; } } } const int nParts = (drawY ? 1 : 0) + (drawR ? 1 : 0) + (drawG ? 1 : 0) + (drawB ? 1 : 0) + (drawSum ? 1 : 0); if (nParts == 0) { // Nothing to draw return QImage(); } - const int d = 20; // Distance for text + // Distance for text + const int d = 20; + + // Height of a single histogram box without text const int partH = int((int)wh - nParts * d) / nParts; + + // Total number of bytes of the image + const uint byteCount = (uint) image.sizeInBytes(); + + // Factor for scaling the measured value to the histogram. + // This factor is used for linear scaling and does not depend + // on the measured histogram values. Very large values, + // e.g. in an image with a lot of white, are clipped. + // Otherwise, the relatively low height of the histogram + // would show all other values close to 0 when one bin is very high. float scaling = 0; int div = (int)byteCount >> 7; if (div > 0) { - scaling = (float)partH / float((int)byteCount >> 7); + scaling = (float)partH / float(byteCount >> 7); } const int dist = 40; - int wy = 0; // Drawing position - QImage histogram(paradeSize, QImage::Format_ARGB32); QPainter davinci(&histogram); davinci.setPen(QColor(220, 220, 220, 255)); histogram.fill(qRgba(0, 0, 0, 0)); - if (drawY) { - drawComponentFull(&davinci, y, scaling, QRect(0, wy, (int)ww, partH + dist), QColor(220, 220, 210, 255), dist, unscaled, 256); + QColor neutralColor(220, 220, 210, 255); + QColor redColor(255, 128, 0, 255); + QColor greenColor(128, 255, 0, 255); + QColor blueColor(0, 128, 255, 255); + int wy = 0; // Drawing position + + if (drawY) { + drawComponentFull(&davinci, y, scaling, QRect(0, wy, (int)ww, partH + dist), neutralColor, dist, unscaled, logScale, 256); wy += partH + d; } if (drawSum) { - drawComponentFull(&davinci, s, scaling / 3, QRect(0, wy, (int)ww, partH + dist), QColor(220, 220, 210, 255), dist, unscaled, 256); - + drawComponentFull(&davinci, s, scaling / 3, QRect(0, wy, (int)ww, partH + dist), neutralColor, dist, unscaled, logScale, 256); wy += partH + d; } if (drawR) { - drawComponentFull(&davinci, r, scaling, QRect(0, wy, (int)ww, partH + dist), QColor(255, 128, 0, 255), dist, unscaled, 256); - + drawComponentFull(&davinci, r, scaling, QRect(0, wy, (int)ww, partH + dist), redColor, dist, unscaled, logScale, 256); wy += partH + d; } if (drawG) { - drawComponentFull(&davinci, g, scaling, QRect(0, wy, (int)ww, partH + dist), QColor(128, 255, 0, 255), dist, unscaled, 256); + drawComponentFull(&davinci, g, scaling, QRect(0, wy, (int)ww, partH + dist), greenColor, dist, unscaled, logScale, 256); wy += partH + d; } if (drawB) { - drawComponentFull(&davinci, b, scaling, QRect(0, wy, (int)ww, partH + dist), QColor(0, 128, 255, 255), dist, unscaled, 256); + drawComponentFull(&davinci, b, scaling, QRect(0, wy, (int)ww, partH + dist), blueColor, dist, unscaled, logScale, 256); } return histogram; } -QImage HistogramGenerator::drawComponent(const int *y, const QSize &size, const float &scaling, const QColor &color, bool unscaled, uint max) const +QImage HistogramGenerator::drawComponent(const int *y, const QSize &size, const float &scaling, const QColor &color, bool unscaled, bool logScale, uint max) { QImage component((int)max, size.height(), QImage::Format_ARGB32); component.fill(qRgba(0, 0, 0, 255)); Q_ASSERT(scaling != INFINITY); const int partH = size.height(); + const int maxBinSize = *std::max_element(&y[0], &y[max - 1]); + const float logScaling = float(size.height()) / log10f(float(maxBinSize + 1)); + for (uint x = 0; x < max; ++x) { + // Calculate the height of the curve at position x - int partY = int(scaling * (float)y[x]); + int partY; + if (logScale) { + partY = int(logScaling * log10f(float(y[x] + 1))); + } else { + partY = int(scaling * (float)y[x]); + } // Invert the y axis if (partY > partH - 1) { partY = partH - 1; } partY = partH - 1 - partY; for (int k = partH - 1; k >= partY; --k) { component.setPixel((int)x, k, color.rgba()); } } if (unscaled && size.width() >= component.width()) { return component; } return component.scaled(size, Qt::IgnoreAspectRatio, Qt::FastTransformation); } void HistogramGenerator::drawComponentFull(QPainter *davinci, const int *y, const float &scaling, const QRect &rect, const QColor &color, int textSpace, - bool unscaled, uint max) const + bool unscaled, bool logScale, uint max) { - QImage component = drawComponent(y, rect.size() - QSize(0, textSpace), scaling, color, unscaled, max); + QImage component = drawComponent(y, rect.size() - QSize(0, textSpace), scaling, color, unscaled, logScale, max); davinci->drawImage(rect.topLeft(), component); uint min = 0; for (uint x = 0; x < max; ++x) { min = x; if (y[x] > 0) { break; } } int maxVal = (int)max - 1; for (int x = (int)max - 1; x >= 0; --x) { maxVal = x; if (y[x] > 0) { break; } } const int textY = rect.bottom() - textSpace + 15; const int dist = 40; const int cw = component.width(); davinci->drawText(0, textY, i18n("min")); davinci->drawText(dist, textY, QString::number(min, 'f', 0)); davinci->drawText(cw - dist - 30, textY, i18n("max")); davinci->drawText(cw - 30, textY, QString::number(maxVal, 'f', 0)); } diff --git a/src/scopes/colorscopes/histogramgenerator.h b/src/scopes/colorscopes/histogramgenerator.h index a3ce0459b..f2904d077 100644 --- a/src/scopes/colorscopes/histogramgenerator.h +++ b/src/scopes/colorscopes/histogramgenerator.h @@ -1,47 +1,63 @@ /*************************************************************************** * Copyright (C) 2010 by Simon Andreas Eugster (simon.eu@gmail.com) * * This file is part of kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #ifndef HISTOGRAMGENERATOR_H #define HISTOGRAMGENERATOR_H #include +#include "colorconstants.h" class QColor; class QImage; class QPainter; class QRect; class QSize; class HistogramGenerator : public QObject { Q_OBJECT public: explicit HistogramGenerator(); - /** Recommendation to use. - See http://www.poynton.com/ColorFAQ.html for details. */ - enum Rec { Rec_601, Rec_709 }; - /** - Calculates a histogram display from the input image. - components are OR-ed HistogramGenerator::Components flags and decide with components (Y, R, G, B) to paint. - unscaled = true leaves the width at 256 if the widget is wider (to avoid scaling). */ - QImage calculateHistogram(const QSize ¶deSize, const QImage &image, const int &components, const HistogramGenerator::Rec rec, bool unscaled, + * Calculates a histogram display from the input image. + * @param paradeSize + * @param image + * @param components OR-ed HistogramGenerator::Components flags and decide with components (Y, R, G, B) to paint. + * @param rec + * @param unscaled unscaled = true leaves the width at 256 if the widget is wider (to avoid scaling). + * @param logScale Use a logarithmic instead of linear scale. + * @param accelFactor + * @return + */ + QImage calculateHistogram(const QSize ¶deSize, const QImage &image, const int &components, const ITURec rec, bool unscaled, + bool logScale, uint accelFactor = 1) const; - QImage drawComponent(const int *y, const QSize &size, const float &scaling, const QColor &color, bool unscaled, uint max) const; - - void drawComponentFull(QPainter *davinci, const int *y, const float &scaling, const QRect &rect, const QColor &color, int textSpace, bool unscaled, - uint max) const; + /** + * Draws the histogram of a single component. + * + * @param y Bins containing the number of samples per value + * @param size Desired box size of the histogram + * @param scaling Use this scaling factor to scale the y values to the box height + * @param color Color to use for drawing + * @param unscaled Do not scale the width but take the number of bins instead (usually 256) + * @param logScale Use logarithmic scale instead of linear + * @param max Number of bins, usually 256 + */ + static QImage drawComponent(const int *y, const QSize &size, const float &scaling, const QColor &color, bool unscaled, bool logScale, uint max) ; + + static void drawComponentFull(QPainter *davinci, const int *y, const float &scaling, const QRect &rect, const QColor &color, int textSpace, + bool unscaled, bool logScale, uint max) ; enum Components { ComponentY = 1 << 0, ComponentR = 1 << 1, ComponentG = 1 << 2, ComponentB = 1 << 3, ComponentSum = 1 << 4 }; }; #endif // HISTOGRAMGENERATOR_H diff --git a/src/scopes/colorscopes/waveform.cpp b/src/scopes/colorscopes/waveform.cpp index daf2f941b..342ac3c28 100644 --- a/src/scopes/colorscopes/waveform.cpp +++ b/src/scopes/colorscopes/waveform.cpp @@ -1,196 +1,196 @@ /*************************************************************************** * Copyright (C) 2010 by Simon Andreas Eugster (simon.eu@gmail.com) * * This file is part of kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #include "waveform.h" #include "waveformgenerator.h" // For reading out the project resolution #include "core.h" #include "profiles/profilemodel.hpp" #include "klocalizedstring.h" #include #include #include #include #include const QSize Waveform::m_textWidth(35, 0); const int Waveform::m_paddingBottom(20); Waveform::Waveform(QWidget *parent) : AbstractGfxScopeWidget(true, parent) { m_ui = new Ui::Waveform_UI(); m_ui->setupUi(this); m_ui->paintMode->addItem(i18n("Yellow"), QVariant(WaveformGenerator::PaintMode_Yellow)); m_ui->paintMode->addItem(i18n("White"), QVariant(WaveformGenerator::PaintMode_White)); m_ui->paintMode->addItem(i18n("Green"), QVariant(WaveformGenerator::PaintMode_Green)); m_aRec601 = new QAction(i18n("Rec. 601"), this); m_aRec601->setCheckable(true); m_aRec709 = new QAction(i18n("Rec. 709"), this); m_aRec709->setCheckable(true); m_agRec = new QActionGroup(this); m_agRec->addAction(m_aRec601); m_agRec->addAction(m_aRec709); m_menu->addSeparator()->setText(i18n("Luma mode")); m_menu->addAction(m_aRec601); m_menu->addAction(m_aRec709); connect(m_ui->paintMode, SIGNAL(currentIndexChanged(int)), this, SLOT(forceUpdateScope())); connect(this, &Waveform::signalMousePositionChanged, this, &Waveform::forceUpdateHUD); connect(m_aRec601, &QAction::toggled, this, &Waveform::forceUpdateScope); connect(m_aRec709, &QAction::toggled, this, &Waveform::forceUpdateScope); init(); m_waveformGenerator = new WaveformGenerator(); } Waveform::~Waveform() { writeConfig(); delete m_waveformGenerator; delete m_aRec601; delete m_aRec709; delete m_agRec; delete m_ui; } void Waveform::readConfig() { AbstractGfxScopeWidget::readConfig(); KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup scopeConfig(config, configName()); m_ui->paintMode->setCurrentIndex(scopeConfig.readEntry("paintmode", 0)); m_aRec601->setChecked(scopeConfig.readEntry("rec601", false)); m_aRec709->setChecked(!m_aRec601->isChecked()); } void Waveform::writeConfig() { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup scopeConfig(config, configName()); scopeConfig.writeEntry("paintmode", m_ui->paintMode->currentIndex()); scopeConfig.writeEntry("rec601", m_aRec601->isChecked()); scopeConfig.sync(); } QRect Waveform::scopeRect() { // Distance from top/left/right int border = 6; QPoint topleft(border, m_ui->verticalSpacer->geometry().y() + border); return QRect(topleft, this->size() - QSize(border + topleft.x(), border + topleft.y())); } ///// Implemented methods ///// QString Waveform::widgetName() const { return QStringLiteral("Waveform"); } bool Waveform::isHUDDependingOnInput() const { return false; } bool Waveform::isScopeDependingOnInput() const { return true; } bool Waveform::isBackgroundDependingOnInput() const { return false; } QImage Waveform::renderHUD(uint) { QImage hud(m_scopeRect.size(), QImage::Format_ARGB32); hud.fill(qRgba(0, 0, 0, 0)); QPainter davinci(&hud); davinci.setPen(penLight); // qCDebug(KDENLIVE_LOG) << values.value("width"); const int rightX = scopeRect().width() - m_textWidth.width() + 3; const int x = m_mousePos.x() - scopeRect().x(); const int y = m_mousePos.y() - scopeRect().y(); if (scopeRect().height() > 0 && m_mouseWithinWidget) { int val = int(255. * (1. - (float)y / (float)scopeRect().height())); if (val >= 0 && val <= 255) { // Draw a horizontal line through the current mouse position // and show the value of the waveform there davinci.drawLine(0, y, scopeRect().size().width() - m_textWidth.width(), y); // Make the value stick to the line unless it is at the top/bottom of the scope int valY = y + 5; const int top = 30; const int bottom = 20; if (valY < top) { valY = top; } else if (valY > scopeRect().height() - bottom) { valY = scopeRect().height() - bottom; } davinci.drawText(rightX, valY, QVariant(val).toString()); } if (scopeRect().width() > 0) { // Draw a vertical line and the x position of the source clip const int profileWidth = pCore->getCurrentProfile()->width(); const int clipX = int((float)x / float(scopeRect().width() - m_textWidth.width() - 1) * float(profileWidth - 1)); if (clipX >= 0 && clipX <= profileWidth) { int valX = x - 15; if (valX < 0) { valX = 0; } if (valX > scopeRect().width() - 55 - m_textWidth.width()) { valX = scopeRect().width() - 55 - m_textWidth.width(); } davinci.drawLine(x, y, x, scopeRect().height() - m_paddingBottom); davinci.drawText(valX, scopeRect().height() - 5, QVariant(clipX).toString() + QStringLiteral(" px")); } } } davinci.drawText(rightX, scopeRect().height() - m_paddingBottom, QStringLiteral("0")); davinci.drawText(rightX, 10, QStringLiteral("255")); emit signalHUDRenderingFinished(0, 1); return hud; } QImage Waveform::renderGfxScope(uint accelFactor, const QImage &qimage) { QElapsedTimer timer; timer.start(); const int paintmode = m_ui->paintMode->itemData(m_ui->paintMode->currentIndex()).toInt(); - WaveformGenerator::Rec rec = m_aRec601->isChecked() ? WaveformGenerator::Rec_601 : WaveformGenerator::Rec_709; + ITURec rec = m_aRec601->isChecked() ? ITURec::Rec_601 : ITURec::Rec_709; QImage wave = m_waveformGenerator->calculateWaveform(scopeRect().size() - m_textWidth - QSize(0, m_paddingBottom), qimage, (WaveformGenerator::PaintMode)paintmode, true, rec, accelFactor); emit signalScopeRenderingFinished((uint)timer.elapsed(), 1); return wave; } QImage Waveform::renderBackground(uint) { emit signalBackgroundRenderingFinished(0, 1); return QImage(); } diff --git a/src/scopes/colorscopes/waveformgenerator.cpp b/src/scopes/colorscopes/waveformgenerator.cpp index 65573f517..853506476 100644 --- a/src/scopes/colorscopes/waveformgenerator.cpp +++ b/src/scopes/colorscopes/waveformgenerator.cpp @@ -1,145 +1,146 @@ /*************************************************************************** * Copyright (C) 2010 by Simon Andreas Eugster (simon.eu@gmail.com) * * This file is part of kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #include "waveformgenerator.h" +#include "colorconstants.h" #include #include #include #include #include #include #define CHOP255(a) ((255) < (a) ? (255) : (a)) WaveformGenerator::WaveformGenerator() = default; WaveformGenerator::~WaveformGenerator() = default; QImage WaveformGenerator::calculateWaveform(const QSize &waveformSize, const QImage &image, WaveformGenerator::PaintMode paintMode, bool drawAxis, - WaveformGenerator::Rec rec, uint accelFactor) + ITURec rec, uint accelFactor) { Q_ASSERT(accelFactor >= 1); // QTime time; // time.start(); QImage wave(waveformSize, QImage::Format_ARGB32); if (waveformSize.width() <= 0 || waveformSize.height() <= 0 || image.width() <= 0 || image.height() <= 0) { return QImage(); } // Fill with transparent color wave.fill(qRgba(0, 0, 0, 0)); const uint ww = (uint)waveformSize.width(); const uint wh = (uint)waveformSize.height(); const uint iw = (uint)image.bytesPerLine(); const uint ih = (uint)image.height(); const uint byteCount = iw * ih; std::vector> waveValues((size_t)waveformSize.width(), std::vector((size_t)waveformSize.height(), 0)); // Number of input pixels that will fall on one scope pixel. // Must be a float because the acceleration factor can be high, leading to <1 expected px per px. const float pixelDepth = (float)((byteCount >> 2) / accelFactor) / float(ww * wh); const float gain = 255. / (8. * pixelDepth); // qCDebug(KDENLIVE_LOG) << "Pixel depth: expected " << pixelDepth << "; Gain: using " << gain << " (acceleration: " << accelFactor << "x)"; // Subtract 1 from sizes because we start counting from 0. // Not doing it would result in attempts to paint outside of the image. const float hPrediv = (float)(wh - 1) / 255.; const float wPrediv = (float)(ww - 1) / float(iw - 1); const uchar *bits = image.bits(); const int bpp = image.depth() / 8; for (uint i = 0, x = 0; i < byteCount; i += (uint)bpp) { Q_ASSERT(bits < image.bits() + byteCount); double dY, dx, dy; auto *col = (const QRgb *)bits; - if (rec == WaveformGenerator::Rec_601) { + if (rec == ITURec::Rec_601) { // CIE 601 Luminance - dY = .299 * qRed(*col) + .587 * qGreen(*col) + .114 * qBlue(*col); + dY = REC_601_R * float(qRed(*col)) + REC_601_G * float(qGreen(*col)) + REC_601_B * float(qBlue(*col)); } else { // CIE 709 Luminance - dY = .2125 * qRed(*col) + .7154 * qGreen(*col) + .0721 * qBlue(*col); + dY = REC_709_R * float(qRed(*col)) + REC_709_G * float(qGreen(*col)) + REC_709_B * float(qBlue(*col)); } // dY is on [0,255] now. dy = dY * hPrediv; dx = (float)x * wPrediv; waveValues[(size_t)dx][(size_t)dy]++; bits += bpp; x += (uint)bpp; if (x > iw) { x -= iw; if (accelFactor > 1) { bits += bpp * (int)iw * ((int)accelFactor - 1); i += (uint)bpp * iw * (accelFactor - 1); } } } switch (paintMode) { case PaintMode_Green: for (int i = 0; i < waveformSize.width(); ++i) { for (int j = 0; j < waveformSize.height(); ++j) { // Logarithmic scale. Needs fine tuning by hand, but looks great. wave.setPixel(i, waveformSize.height() - j - 1, qRgba(CHOP255(52 * log(0.1 * gain * (float)waveValues[(size_t)i][(size_t)j])), CHOP255(52 * std::log(gain * (float)waveValues[(size_t)i][(size_t)j])), CHOP255(52 * log(.25 * gain * (float)waveValues[(size_t)i][(size_t)j])), CHOP255(64 * std::log(gain * (float)waveValues[(size_t)i][(size_t)j])))); } } break; case PaintMode_Yellow: for (int i = 0; i < waveformSize.width(); ++i) { for (int j = 0; j < waveformSize.height(); ++j) { wave.setPixel(i, waveformSize.height() - j - 1, qRgba(255, 242, 0, CHOP255(gain * (float)waveValues[(size_t)i][(size_t)j]))); } } break; default: for (int i = 0; i < waveformSize.width(); ++i) { for (int j = 0; j < waveformSize.height(); ++j) { wave.setPixel(i, waveformSize.height() - j - 1, qRgba(255, 255, 255, CHOP255(2. * gain * (float)waveValues[(size_t)i][(size_t)j]))); } } break; } if (drawAxis) { QPainter davinci(&wave); QRgb opx; davinci.setPen(qRgba(150, 255, 200, 32)); davinci.setCompositionMode(QPainter::CompositionMode_Overlay); for (int i = 0; i <= 10; ++i) { float dy = (float)i / 10. * ((int)wh - 1); for (int x = 0; x < (int)ww; ++x) { opx = wave.pixel(x, dy); wave.setPixel(x, dy, qRgba(CHOP255(150 + qRed(opx)), 255, CHOP255(200 + qBlue(opx)), CHOP255(32 + qAlpha(opx)))); } } } // uint diff = time.elapsed(); // emit signalCalculationFinished(wave, diff); return wave; } #undef CHOP255 diff --git a/src/scopes/colorscopes/waveformgenerator.h b/src/scopes/colorscopes/waveformgenerator.h index 46cbb3246..90df7fb9f 100644 --- a/src/scopes/colorscopes/waveformgenerator.h +++ b/src/scopes/colorscopes/waveformgenerator.h @@ -1,33 +1,34 @@ /*************************************************************************** * Copyright (C) 2010 by Simon Andreas Eugster (simon.eu@gmail.com) * * This file is part of kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #ifndef WAVEFORMGENERATOR_H #define WAVEFORMGENERATOR_H #include +#include "colorconstants.h" + class QImage; class QSize; class WaveformGenerator : public QObject { Q_OBJECT public: enum PaintMode { PaintMode_Green, PaintMode_Yellow, PaintMode_White }; - enum Rec { Rec_601, Rec_709 }; WaveformGenerator(); ~WaveformGenerator() override; QImage calculateWaveform(const QSize &waveformSize, const QImage &image, WaveformGenerator::PaintMode paintMode, bool drawAxis, - const WaveformGenerator::Rec rec, uint accelFactor = 1); + const ITURec rec, uint accelFactor = 1); }; #endif // WAVEFORMGENERATOR_H diff --git a/src/ui/histogram_ui.ui b/src/ui/histogram_ui.ui index 1aebcd0d6..b102f90ca 100644 --- a/src/ui/histogram_ui.ui +++ b/src/ui/histogram_ui.ui @@ -1,82 +1,156 @@ Histogram_UI 0 0 396 296 - - - - Luma value - - - - - - Y - - - - - - - R - - - - - - - G - - - - - - - B - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - Components - - - - - - - RGB summed up - - - Sum - - + + + + + + + + 75 + true + + + + Components + + + + + + + RGB summed up + + + Sum + + + + + + + Luma value + + + + + + Y + + + + + + + R + + + + + + + G + + + + + + + B + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 75 + true + + + + Scale + + + + + + + Linear + + + true + + + + + + + Logarithmic + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + +