Commit 89078cf2 authored by Simon Eugster's avatar Simon Eugster Committed by Jean-Baptiste Mardelle

Small refactoring, extract CIE factors

See kde/kdenlive#655
parent 41c82802
......@@ -6,7 +6,6 @@
* Dependencies MLT etc.
* MVC
* GUI elements and their counterpart
* [kdenlivesettings.kcfg](../src/kdenlivesettings.kcfg)
## Architectural Overview
......
......@@ -42,7 +42,7 @@ sudo apt install libkf5archive-dev libkf5bookmarks-dev libkf5coreaddons-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:
......
# 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
<entry name="logscale" type="Bool">
<label>Use logarithmic scale</label>
<default>true</default>
</entry>
```
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
set(kdenlive_SRCS
${kdenlive_SRCS}
scopes/colorscopes/colorconstants.h
scopes/colorscopes/abstractgfxscopewidget.cpp
scopes/colorscopes/colorplaneexport.cpp
scopes/colorscopes/histogram.cpp
......
#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
......@@ -15,6 +15,7 @@
#include "klocalizedstring.h"
#include <KConfigGroup>
#include <KSharedConfig>
#include <QtWidgets/QButtonGroup>
Histogram::Histogram(QWidget *parent)
: AbstractGfxScopeWidget(false, parent)
......@@ -39,6 +40,10 @@ Histogram::Histogram(QWidget *parent)
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);
......@@ -47,6 +52,7 @@ Histogram::Histogram(QWidget *parent)
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();
......@@ -76,6 +82,7 @@ void Histogram::readConfig()
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()
......@@ -88,6 +95,7 @@ void Histogram::writeConfig()
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();
}
......@@ -131,9 +139,9 @@ QImage Histogram::renderGfxScope(uint accelFactor, const QImage &qimage)
(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;
......
......@@ -9,6 +9,7 @@
***************************************************************************/
#include "histogramgenerator.h"
#include "colorconstants.h"
#include "klocalizedstring.h"
#include <QImage>
......@@ -18,7 +19,8 @@
HistogramGenerator::HistogramGenerator() = default;
QImage HistogramGenerator::calculateHistogram(const QSize &paradeSize, const QImage &image, const int &components, HistogramGenerator::Rec rec, bool unscaled,
QImage HistogramGenerator::calculateHistogram(const QSize &paradeSize, 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) {
......@@ -39,27 +41,27 @@ QImage HistogramGenerator::calculateHistogram(const QSize &paradeSize, const QIm
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)]++;
......@@ -75,53 +77,68 @@ QImage HistogramGenerator::calculateHistogram(const QSize &paradeSize, const QIm
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));
......@@ -129,9 +146,18 @@ QImage HistogramGenerator::drawComponent(const int *y, const QSize &size, const
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) {
......@@ -150,9 +176,9 @@ QImage HistogramGenerator::drawComponent(const int *y, const QSize &size, const
}
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;
......
......@@ -12,6 +12,7 @@
#define HISTOGRAMGENERATOR_H
#include <QObject>
#include "colorconstants.h"
class QColor;
class QImage;
......@@ -25,21 +26,36 @@ class HistogramGenerator : public QObject
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 &paradeSize, 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 &paradeSize, 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 };
};
......
......@@ -181,7 +181,7 @@ QImage Waveform::renderGfxScope(uint accelFactor, const QImage &qimage)
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);
......
......@@ -9,6 +9,7 @@
***************************************************************************/
#include "waveformgenerator.h"
#include "colorconstants.h"
#include <cmath>
......@@ -25,7 +26,7 @@ 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);
......@@ -70,12 +71,12 @@ QImage WaveformGenerator::calculateWaveform(const QSize &waveformSize, const QIm
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.
......
......@@ -12,6 +12,8 @@
#define WAVEFORMGENERATOR_H
#include <QObject>
#include "colorconstants.h"
class QImage;
class QSize;
......@@ -21,13 +23,12 @@ class WaveformGenerator : public QObject
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
......@@ -11,69 +11,143 @@
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QCheckBox" name="cbY">
<property name="toolTip">
<string>Luma value</string>
</property>
<property name="whatsThis">
<string/>
</property>
<property name="text">
<string>Y</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QCheckBox" name="cbR">
<property name="text">
<string>R</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QCheckBox" name="cbG">
<property name="text">
<string>G</string>
</property>
</widget>
</item>
<item row="0" column="5">
<widget class="QCheckBox" name="cbB">
<property name="text">
<string>B</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="5">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0">
<widget class="QLabel" name="lblComponents">
<property name="text">
<string>Components</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QCheckBox" name="cbS">
<property name="toolTip">
<string>RGB summed up</string>
</property>
<property name="text">
<string>Sum</string>
</property>
</widget>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="lblComponents">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Components</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cbS">
<property name="toolTip">
<string>RGB summed up</string>
</property>
<property name="text">
<string>Sum</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cbY">
<property name="toolTip">
<string>Luma value</string>
</property>
<property name="whatsThis">
<string/>
</property>
<property name="text">
<string>Y</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cbR">
<property name="text">
<string>R</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cbG">
<property name="text">
<string>G</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cbB">
<property name="text">
<string>B</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="lblScale">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Scale</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rbLinear">
<property name="text">
<string>Linear</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rbLogarithmic">
<property name="text">
<string>Logarithmic</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>