Commit 11646947 authored by Simon Eugster's avatar Simon Eugster
Browse files

Vectorscope changes:

* Magnification = Gain can now be changed with a slider in 0.1 steps up to 4x
* Vectorscope generator extracted to separate (re-usable) class
* Inherits from AbstractScopeWidget, which means
  - Lots of duplicate code removed
  - Realtime bug that led to crash fixed (I hope)
* Checks whether events have been connected successfully with Q_ASSERT
* Delete members in the destructor
* .ui file: Palette removed, unnecessary grid layout removed as well
AbstractScopeWidget changes:
* Processing HUD and Background as well now.
* Re-render when auto refresh is selected
* Don't recalculate if the scope is hidden behind another widget
* Slot added to force the update of a certain layer
* Update scope when risen to top of a widget stack
* Realtime acceleration factor can now be calculated by the implementing widget
* Acceleration factor is protected instead of private now so that they can be accessed by other render tasks (hud displays scope factor e.g.)
WaveformGenerator changes:
* Using subclassing instead of direct #includes in the .h file («good style» :P)

svn path=/trunk/kdenlive/; revision=4611
parent d51a2ad7
......@@ -202,6 +202,7 @@ set(kdenlive_SRCS
rebuildgroupcommand.cpp
waveform.cpp
colorcorrection/waveformgenerator.cpp
colorcorrection/vectorscopegenerator.cpp
testwidget.cpp
razorgroupcommand.cpp
)
......
......@@ -31,12 +31,12 @@ AbstractScopeWidget::AbstractScopeWidget(Monitor *projMonitor, Monitor *clipMoni
m_projMonitor(projMonitor),
m_clipMonitor(clipMonitor),
offset(5),
m_semaphoreHUD(1),
m_semaphoreScope(1),
m_semaphoreBackground(1),
m_accelFactorHUD(1),
m_accelFactorScope(1),
m_accelFactorBackground(1),
m_semaphoreHUD(1),
m_semaphoreScope(1),
m_semaphoreBackground(1),
initialDimensionUpdateDone(false)
{
......@@ -77,6 +77,7 @@ AbstractScopeWidget::AbstractScopeWidget(Monitor *projMonitor, Monitor *clipMoni
b &= connect(this, SIGNAL(signalScopeRenderingFinished(uint,uint)), this, SLOT(slotScopeRenderingFinished(uint,uint)));
b &= connect(this, SIGNAL(signalBackgroundRenderingFinished(uint,uint)), this, SLOT(slotBackgroundRenderingFinished(uint,uint)));
b &= connect(m_aRealtime, SIGNAL(toggled(bool)), this, SLOT(slotResetRealtimeFactor(bool)));
b &= connect(m_aAutoRefresh, SIGNAL(toggled(bool)), this, SLOT(slotAutoRefreshToggled(bool)));
Q_ASSERT(b);
}
......@@ -85,12 +86,34 @@ AbstractScopeWidget::~AbstractScopeWidget()
{
delete m_menu;
delete m_aAutoRefresh;
delete m_aRealtime;
}
void AbstractScopeWidget::prodHUDThread() {}
void AbstractScopeWidget::prodHUDThread()
{
if (this->visibleRegion().isEmpty()) {
qDebug() << "Scope " << widgetName() << " is not visible. Not calculating HUD.";
}
if (m_semaphoreHUD.tryAcquire(1)) {
Q_ASSERT(!m_threadHUD.isRunning());
m_newHUDFrames.fetchAndStoreRelaxed(0);
m_newHUDUpdates.fetchAndStoreRelaxed(0);
m_threadHUD = QtConcurrent::run(this, &AbstractScopeWidget::renderHUD, m_accelFactorHUD);
qDebug() << "HUD thread started in " << widgetName();
} else {
qDebug() << "HUD semaphore locked, not prodding in " << widgetName() << ". Thread running: " << m_threadHUD.isRunning();
}
}
void AbstractScopeWidget::prodScopeThread()
{
// Only start a new thread if the scope is actually visible
// and not hidden by another widget on the stack.
if (this->visibleRegion().isEmpty()) {
qDebug() << "Scope " << widgetName() << " is not visible. Not calculating scope.";
}
// Try to acquire the semaphore. This must only succeed if m_threadScope is not running
// anymore. Therefore the semaphore must NOT be released before m_threadScope ends.
// If acquiring the semaphore fails, the thread is still running.
......@@ -99,15 +122,62 @@ void AbstractScopeWidget::prodScopeThread()
m_newScopeFrames.fetchAndStoreRelaxed(0);
m_newScopeUpdates.fetchAndStoreRelaxed(0);
// See http://doc.qt.nokia.com/latest/qtconcurrentrun.html#run about
// running member functions in a thread
m_threadScope = QtConcurrent::run(this, &AbstractScopeWidget::renderScope, m_accelFactorScope);
qDebug() << "Scope thread started in " << widgetName();
} else {
qDebug() << "Scope semaphore locked, not prodding in " << widgetName() << ". Thread running: " << m_threadScope.isRunning();
}
}
void AbstractScopeWidget::prodBackgroundThread()
{
if (this->visibleRegion().isEmpty()) {
qDebug() << "Scope " << widgetName() << " is not visible. Not calculating background.";
}
if (m_semaphoreBackground.tryAcquire(1)) {
Q_ASSERT(!m_threadBackground.isRunning());
m_newBackgroundFrames.fetchAndStoreRelaxed(0);
m_newBackgroundUpdates.fetchAndStoreRelaxed(0);
m_threadBackground = QtConcurrent::run(this, &AbstractScopeWidget::renderBackground, m_accelFactorBackground);
qDebug() << "Background thread started in " << widgetName();
} else {
qDebug() << "Background semaphore locked, not prodding in " << widgetName() << ". Thread running: " << m_threadBackground.isRunning();
}
}
void AbstractScopeWidget::forceUpdate()
{
m_newHUDUpdates.fetchAndAddRelaxed(1);
m_newScopeUpdates.fetchAndAddRelaxed(1);
m_newBackgroundUpdates.fetchAndAddRelaxed(1);
prodHUDThread();
prodScopeThread();
prodBackgroundThread();
}
void AbstractScopeWidget::forceUpdateHUD()
{
m_newHUDUpdates.fetchAndAddRelaxed(1);
prodHUDThread();
}
void AbstractScopeWidget::forceUpdateScope()
{
m_newScopeUpdates.fetchAndAddRelaxed(1);
prodScopeThread();
}
void AbstractScopeWidget::forceUpdateBackground()
{
m_newBackgroundUpdates.fetchAndAddRelaxed(1);
prodBackgroundThread();
void AbstractScopeWidget::prodBackgroundThread() {}
}
///// Events /////
......@@ -125,17 +195,19 @@ void AbstractScopeWidget::resizeEvent(QResizeEvent *event)
// Update the dimension of the available rect for painting
m_scopeRect = scopeRect();
m_newHUDUpdates.fetchAndAddRelaxed(1);
m_newScopeUpdates.fetchAndAddRelaxed(1);
m_newBackgroundUpdates.fetchAndAddRelaxed(1);
prodHUDThread();
prodScopeThread();
prodBackgroundThread();
forceUpdate();
QWidget::resizeEvent(event);
}
void AbstractScopeWidget::raise()
{
// Widget has been brought to the top of a widget stack,
// layers need to be updated.
QWidget::raise();
forceUpdate();
}
void AbstractScopeWidget::paintEvent(QPaintEvent *)
{
// qDebug() << "Painting now on scope " << widgetName();
......@@ -152,7 +224,6 @@ void AbstractScopeWidget::paintEvent(QPaintEvent *)
davinci.drawImage(scopeRect().topLeft(), m_imgBackground);
davinci.drawImage(scopeRect().topLeft(), m_imgScope);
davinci.drawImage(scopeRect().topLeft(), m_imgHUD);
davinci.fillRect(scopeRect(), QBrush(QColor(200, 100, 0, 16)));
}
void AbstractScopeWidget::customContextMenuRequested(const QPoint &pos)
......@@ -160,12 +231,39 @@ void AbstractScopeWidget::customContextMenuRequested(const QPoint &pos)
m_menu->exec(this->mapToGlobal(pos));
}
uint AbstractScopeWidget::calculateAccelFactorHUD(uint oldMseconds, uint) { return ceil((float)oldMseconds*REALTIME_FPS/1000 ); }
uint AbstractScopeWidget::calculateAccelFactorScope(uint oldMseconds, uint) { return ceil((float)oldMseconds*REALTIME_FPS/1000 ); }
uint AbstractScopeWidget::calculateAccelFactorBackground(uint oldMseconds, uint) { return ceil((float)oldMseconds*REALTIME_FPS/1000 ); }
///// Slots /////
void AbstractScopeWidget::slotHUDRenderingFinished(uint, uint) {}
void AbstractScopeWidget::slotHUDRenderingFinished(uint mseconds, uint oldFactor)
{
qDebug() << "HUD rendering has finished, waiting for termination in " << widgetName();
m_threadHUD.waitForFinished();
m_imgHUD = m_threadHUD.result();
m_semaphoreHUD.release(1);
this->update();
int accel;
if (m_aRealtime->isChecked()) {
accel = calculateAccelFactorHUD(mseconds, oldFactor);
if (m_accelFactorHUD < 1) {
accel = 1;
}
m_accelFactorHUD = accel;
}
if ( (m_newHUDFrames > 0 && m_aAutoRefresh->isChecked()) || m_newHUDUpdates > 0) {
qDebug() << "Trying to start a new HUD thread for " << widgetName()
<< ". New frames/updates: " << m_newHUDFrames << "/" << m_newHUDUpdates;
prodHUDThread();;
}
}
void AbstractScopeWidget::slotScopeRenderingFinished(uint mseconds, uint)
void AbstractScopeWidget::slotScopeRenderingFinished(uint mseconds, uint oldFactor)
{
// The signal can be received before the thread has really finished. So we
// need to wait until it has really finished before starting a new thread.
......@@ -181,9 +279,9 @@ void AbstractScopeWidget::slotScopeRenderingFinished(uint mseconds, uint)
// Calculate the acceleration factor hint to get «realtime» updates.
int accel;
if (m_aRealtime->isChecked()) {
accel = ceil((float)mseconds*REALTIME_FPS/1000 );
accel = calculateAccelFactorScope(mseconds, oldFactor);
if (m_accelFactorScope < 1) {
// If mseconds is 0.
// If mseconds happens to be 0.
accel = 1;
}
// Don't directly calculate with m_accelFactorScope as we are dealing with concurrency.
......@@ -193,13 +291,36 @@ void AbstractScopeWidget::slotScopeRenderingFinished(uint mseconds, uint)
}
if ( (m_newScopeFrames > 0 && m_aAutoRefresh->isChecked()) || m_newScopeUpdates > 0) {
qDebug() << "Trying to start a new thread for " << widgetName()
qDebug() << "Trying to start a new scope thread for " << widgetName()
<< ". New frames/updates: " << m_newScopeFrames << "/" << m_newScopeUpdates;
prodScopeThread();
}
}
void AbstractScopeWidget::slotBackgroundRenderingFinished(uint, uint) {}
void AbstractScopeWidget::slotBackgroundRenderingFinished(uint mseconds, uint oldFactor)
{
qDebug() << "Background rendering has finished, waiting for termination in " << widgetName();
m_threadBackground.waitForFinished();
m_imgBackground = m_threadBackground.result();
m_semaphoreBackground.release(1);
this->update();
int accel;
if (m_aRealtime->isChecked()) {
accel = calculateAccelFactorBackground(mseconds, oldFactor);
if (m_accelFactorBackground < 1) {
accel = 1;
}
m_accelFactorBackground = accel;
}
if ( (m_newBackgroundFrames > 0 && m_aAutoRefresh->isChecked()) || m_newBackgroundUpdates > 0) {
qDebug() << "Trying to start a new background thread for " << widgetName()
<< ". New frames/updates: " << m_newBackgroundFrames << "/" << m_newBackgroundUpdates;
prodBackgroundThread();;
}
}
void AbstractScopeWidget::slotActiveMonitorChanged(bool isClipMonitor)
{
......@@ -247,3 +368,11 @@ void AbstractScopeWidget::slotResetRealtimeFactor(bool realtimeChecked)
m_accelFactorBackground = 1;
}
}
void AbstractScopeWidget::slotAutoRefreshToggled(bool autoRefresh)
{
// TODO only if depends on input
if (autoRefresh) {
forceUpdate();
}
}
......@@ -68,6 +68,8 @@ public:
virtual ~AbstractScopeWidget(); // Must be virtual because of inheritance, to avoid memory leaks
QPalette m_scopePalette;
///// Unimplemented /////
virtual QString widgetName() const = 0;
protected:
......@@ -103,10 +105,17 @@ protected:
QImage m_imgScope;
QImage m_imgBackground;
/** The acceleration factors can be accessed also by other renderer tasks,
e.g. to display the scope's acceleration factor in the HUD renderer. */
int m_accelFactorHUD;
int m_accelFactorScope;
int m_accelFactorBackground;
///// Unimplemented Methods /////
/** Where on the widget we can paint in */
/** Where on the widget we can paint in.
May also update other variables that depend on the widget's size. */
virtual QRect scopeRect() = 0;
/** @brief HUD renderer. Must emit signalHUDRenderingFinished(). @see renderScope */
......@@ -127,12 +136,28 @@ protected:
/** @see isHUDDependingOnInput() */
virtual bool isBackgroundDependingOnInput() const = 0;
///// Methods /////
///// Can be reimplemented /////
/** Calculates the acceleration factor to be used by the render thread.
This method can be refined in the subclass if required. */
virtual uint calculateAccelFactorHUD(uint oldMseconds, uint oldFactor);
virtual uint calculateAccelFactorScope(uint oldMseconds, uint oldFactor);
virtual uint calculateAccelFactorBackground(uint oldMseconds, uint oldFactor);
///// Reimplemented /////
void mouseReleaseEvent(QMouseEvent *);
void paintEvent(QPaintEvent *);
void raise();
void resizeEvent(QResizeEvent *);
protected slots:
/** Forces an update of all layers. */
void forceUpdate();
void forceUpdateHUD();
void forceUpdateScope();
void forceUpdateBackground();
void slotAutoRefreshToggled(bool);
signals:
/** mseconds represent the time taken for the calculation,
accelerationFactor the acceleration factor that has been used. */
......@@ -165,10 +190,6 @@ private:
QFuture<QImage> m_threadScope;
QFuture<QImage> m_threadBackground;
int m_accelFactorHUD;
int m_accelFactorScope;
int m_accelFactorBackground;
bool initialDimensionUpdateDone;
void prodHUDThread();
void prodScopeThread();
......
/***************************************************************************
* 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. *
***************************************************************************/
/**
Vectorscope.
The basis matrix for converting RGB to YUV is:
mRgb2Yuv = r =
0.29900 0.58700 0.11400 1.00000
-0.14741 -0.28939 0.43680 x 0.00000
0.61478 -0.51480 -0.09998 0.00000
The resulting YUV value is then drawn on the circle
using U and V as coordinate values.
The maximum length of such an UV vector is reached
for the colors Red and Cyan: 0.632.
To make optimal use of space in the circle, this value
can be used for scaling.
As we are dealing with RGB values in a range of {0,...,255}
and the conversion values are made for [0,1], we already
divide the conversion values by 255 previously, e.g. in
GNU octave.
See also:
http://de.wikipedia.org/wiki/Vektorskop
http://www.elektroniktutor.de/techno/vektskop.html
*/
#include <math.h>
#include <QDebug>
#include <QImage>
#include "vectorscopegenerator.h"
// The maximum distance from the center for any RGB color is 0.63, so
// no need to make the circle bigger than required.
const float SCALING = 1/.7;
const float VectorscopeGenerator::scaling = 1/.7;
/**
Input point is on [-1,1]², 0 being at the center,
and positive directions are →top/→right.
Maps to the coordinates used in QImages with the 0 point
at the top left corner.
-1 +1
+1+-----------+
| + |
| --0++ |
| - |
-1+-----------+
vvv
mapped to
v
0 x
0+------+
|0++ |
|- |
|- |
y+------+
With y:
1. Scale from [-1,1] to [0,1] with y01 := (y+1)/2
2. Invert (Orientation of the y axis changes) with y10 := 1-y01
3. Scale from [1,0] to [height-1,0] with yy := (height-1) * y10
x does not need to be inverted.
*/
QPoint VectorscopeGenerator::mapToCircle(const QSize &targetSize, const QPointF &point) const
{
return QPoint( (targetSize.width() -1) * (point.x()+1)/2,
(targetSize.height()-1) * (1 - (point.y()+1)/2) );
}
QImage VectorscopeGenerator::calculateVectorscope(const QSize &vectorscopeSize, const QImage &image, const float &gain,
const VectorscopeGenerator::PaintMode &paintMode, const bool &drawAxis,
const uint &accelFactor) const
{
// Prepare the vectorscope data
const int cw = (vectorscopeSize.width() < vectorscopeSize.height()) ? vectorscopeSize.width() : vectorscopeSize.height();
QImage scope = QImage(cw, cw, QImage::Format_ARGB32);
scope.fill(qRgba(0,0,0,0));
const uchar *bits = image.bits();
int r,g,b;
double dy, dr, dg, db, dmax;
double y,u,v;
QPoint pt;
QRgb px;
const int stepsize = 4*accelFactor;
// Just an average for the number of image pixels per scope pixel.
// NOTE: byteCount() has to be replaced by (img.bytesPerLine()*img.height()) for Qt 4.5 to compile, see: http://doc.trolltech.org/4.6/qimage.html#bytesPerLine
double avgPxPerPx = (double) 4*(image.bytesPerLine()*image.height())/scope.size().width()/scope.size().height()/accelFactor;
qDebug() << "Expecting " << avgPxPerPx << " pixels per pixel.";
for (int i = 0; i < (image.bytesPerLine()*image.height()); i+= stepsize) {
QRgb *col = (QRgb *) bits;
r = qRed(*col);
g = qGreen(*col);
b = qBlue(*col);
y = (double) 0.001173 * r +0.002302 * g +0.0004471* b;
u = (double) -0.0005781* r -0.001135 * g +0.001713 * b;
v = (double) 0.002411 * r -0.002019 * g -0.0003921* b;
pt = mapToCircle(vectorscopeSize, QPointF(SCALING*gain*u, SCALING*gain*v));
if (!(pt.x() <= vectorscopeSize.width() && pt.x() >= 0
&& pt.y() <= vectorscopeSize.height() && pt.y() >= 0)) {
// Point lies outside (because of scaling), don't plot it
} else {
// Draw the pixel using the chosen draw mode.
switch (paintMode) {
case PaintMode_YUV:
// see yuvColorWheel
dy = 128; // Default Y value. Lower = darker.
// Calculate the RGB values from YUV
dr = dy + 290.8*v;
dg = dy - 100.6*u - 148*v;
db = dy + 517.2*u;
if (dr < 0) dr = 0;
if (dg < 0) dg = 0;
if (db < 0) db = 0;
if (dr > 255) dr = 255;
if (dg > 255) dg = 255;
if (db > 255) db = 255;
scope.setPixel(pt, qRgba(dr, dg, db, 255));
break;
case PaintMode_Chroma:
dy = 200; // Default Y value. Lower = darker.
// Calculate the RGB values from YUV
dr = dy + 290.8*v;
dg = dy - 100.6*u - 148*v;
db = dy + 517.2*u;
// Scale the RGB values back to max 255
dmax = dr;
if (dg > dmax) dmax = dg;
if (db > dmax) dmax = db;
dmax = 255/dmax;
dr *= dmax;
dg *= dmax;
db *= dmax;
scope.setPixel(pt, qRgba(dr, dg, db, 255));
break;
case PaintMode_Original:
scope.setPixel(pt, *col);
break;
case PaintMode_Green:
px = scope.pixel(pt);
scope.setPixel(pt, qRgba(qRed(px)+(255-qRed(px))/(3*avgPxPerPx), qGreen(px)+20*(255-qGreen(px))/(avgPxPerPx),
qBlue(px)+(255-qBlue(px))/(avgPxPerPx), qAlpha(px)+(255-qAlpha(px))/(avgPxPerPx)));
break;
case PaintMode_Green2:
px = scope.pixel(pt);
scope.setPixel(pt, qRgba(qRed(px)+ceil((255-(float)qRed(px))/(4*avgPxPerPx)), 255,
qBlue(px)+ceil((255-(float)qBlue(px))/(avgPxPerPx)), qAlpha(px)+ceil((255-(float)qAlpha(px))/(avgPxPerPx))));
break;
case PaintMode_Black:
px = scope.pixel(pt);
scope.setPixel(pt, qRgba(0,0,0, qAlpha(px)+(255-qAlpha(px))/20));
break;
}
}
bits += stepsize;
}
return scope;
}
/***************************************************************************
* 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 VECTORSCOPEGENERATOR_H
#define VECTORSCOPEGENERATOR_H
#include <QObject>
class QImage;
class QPoint;
class QPointF;
class QSize;
class VectorscopeGenerator : public QObject
{
Q_OBJECT
public:
enum PaintMode { PaintMode_Green, PaintMode_Green2, PaintMode_Original, PaintMode_Chroma, PaintMode_YUV, PaintMode_Black };
QImage calculateVectorscope(const QSize &vectorscopeSize, const QImage &image, const float &gain,
const VectorscopeGenerator::PaintMode &paintMode,
const bool&, const uint &accelFactor = 1) const;
QPoint mapToCircle(const QSize &targetSize, const QPointF &point) const;
static const float scaling;
signals:
void signalCalculationFinished(QImage image, const uint &ms);
};
#endif // VECTORSCOPEGENERATOR_H
......@@ -8,10 +8,12 @@
* (at your option) any later version. *
***************************************************************************/
#include <QDebug>
#include <QTime>
#include <QColor>
#include <QDebug>
#include <QImage>
#include <QPainter>
#include <QSize>
#include <QTime>
#include "waveformgenerator.h"
......
......@@ -12,8 +12,8 @@
#define WAVEFORMGENERATOR_H
#include <QObject>
#include <QImage>
#include <QSize>
class QImage;
class QSize;
class WaveformGenerator : public QObject
{
......
This diff is collapsed.
......@@ -12,37 +12,42 @@
#define VECTORSCOPE_H
#include <QtCore>
#include "renderer.h"