Commit dfbaa896 authored by Simon Eugster's avatar Simon Eugster

Color scopes now using the common abstract scope as well. --> Common functions, more re-usable

svn path=/trunk/kdenlive/; revision=5130
parent 6cc358a0
......@@ -220,14 +220,14 @@ set(kdenlive_SRCS
tracksconfigdialog.cpp
configtrackscommand.cpp
abstractscopewidget.cpp
abstractgfxscopewidget.cpp
audioscopes/abstractaudioscopewidget.cpp
audioscopes/audiospectrum.cpp
rebuildgroupcommand.cpp
histogram.cpp
rgbparade.cpp
vectorscope.cpp
waveform.cpp
colorscopes/abstractgfxscopewidget.cpp
colorscopes/histogram.cpp
colorscopes/rgbparade.cpp
colorscopes/vectorscope.cpp
colorscopes/waveform.cpp
colorplaneexport.cpp
colortools.cpp
colorcorrection/histogramgenerator.cpp
......
/***************************************************************************
* 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 "qtconcurrentrun.h"
#include "abstractgfxscopewidget.h"
#include "renderer.h"
#include "monitor.h"
#include <QFuture>
#include <QColor>
#include <QMenu>
#include <QMouseEvent>
#include <QPainter>
const int REALTIME_FPS = 30;
const QColor light(250, 238, 226, 255);
const QColor dark(40, 40, 39, 255);
const QColor dark2(25, 25, 23, 255);
const QPen AbstractGfxScopeWidget::penThick(QBrush(QColor(250, 250, 250)), 2, Qt::SolidLine);
const QPen AbstractGfxScopeWidget::penThin(QBrush(QColor(250, 250, 250)), 1, Qt::SolidLine);
const QPen AbstractGfxScopeWidget::penLight(QBrush(QColor(200, 200, 250, 150)), 1, Qt::SolidLine);
const QPen AbstractGfxScopeWidget::penLightDots(QBrush(QColor(200, 200, 250, 150)), 1, Qt::DotLine);
const QPen AbstractGfxScopeWidget::penDark(QBrush(QColor(0, 0, 20, 250)), 1, Qt::SolidLine);
const QPen AbstractGfxScopeWidget::penDarkDots(QBrush(QColor(0, 0, 20, 250)), 1, Qt::DotLine);
AbstractGfxScopeWidget::AbstractGfxScopeWidget(Monitor *projMonitor, Monitor *clipMonitor, bool trackMouse, QWidget *parent) :
QWidget(parent),
m_projMonitor(projMonitor),
m_clipMonitor(clipMonitor),
m_mousePos(0, 0),
m_mouseWithinWidget(false),
offset(5),
m_accelFactorHUD(1),
m_accelFactorScope(1),
m_accelFactorBackground(1),
m_semaphoreHUD(1),
m_semaphoreScope(1),
m_semaphoreBackground(1),
initialDimensionUpdateDone(false),
m_requestForcedUpdate(false)
{
m_scopePalette = QPalette();
m_scopePalette.setBrush(QPalette::Window, QBrush(dark2));
m_scopePalette.setBrush(QPalette::Base, QBrush(dark));
m_scopePalette.setBrush(QPalette::Button, QBrush(dark));
m_scopePalette.setBrush(QPalette::Text, QBrush(light));
m_scopePalette.setBrush(QPalette::WindowText, QBrush(light));
m_scopePalette.setBrush(QPalette::ButtonText, QBrush(light));
this->setPalette(m_scopePalette);
this->setAutoFillBackground(true);
m_aAutoRefresh = new QAction(i18n("Auto Refresh"), this);
m_aAutoRefresh->setCheckable(true);
m_aRealtime = new QAction(i18n("Realtime (with precision loss)"), this);
m_aRealtime->setCheckable(true);
m_menu = new QMenu(this);
m_menu->setPalette(m_scopePalette);
m_menu->addAction(m_aAutoRefresh);
m_menu->addAction(m_aRealtime);
this->setContextMenuPolicy(Qt::CustomContextMenu);
m_activeRender = (m_clipMonitor->isActive()) ? m_clipMonitor->render : m_projMonitor->render;
bool b = true;
b &= connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(customContextMenuRequested(QPoint)));
//b &= connect(m_activeRender, SIGNAL(rendererPosition(int)), this, SLOT(slotRenderZoneUpdated()));
b &= connect(m_activeRender, SIGNAL(frameUpdated(QImage)), this, SLOT(slotRenderZoneUpdated(QImage)));
b &= connect(this, SIGNAL(signalHUDRenderingFinished(uint, uint)), this, SLOT(slotHUDRenderingFinished(uint, uint)));
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);
// Enable mouse tracking if desired.
// Causes the mouseMoved signal to be emitted when the mouse moves inside the
// widget, even when no mouse button is pressed.
this->setMouseTracking(trackMouse);
}
AbstractGfxScopeWidget::~AbstractGfxScopeWidget()
{
writeConfig();
delete m_menu;
delete m_aAutoRefresh;
delete m_aRealtime;
}
void AbstractGfxScopeWidget::init()
{
m_widgetName = widgetName();
readConfig();
}
void AbstractGfxScopeWidget::readConfig()
{
KSharedConfigPtr config = KGlobal::config();
KConfigGroup scopeConfig(config, configName());
m_aAutoRefresh->setChecked(scopeConfig.readEntry("autoRefresh", true));
m_aRealtime->setChecked(scopeConfig.readEntry("realtime", false));
scopeConfig.sync();
}
void AbstractGfxScopeWidget::writeConfig()
{
KSharedConfigPtr config = KGlobal::config();
KConfigGroup scopeConfig(config, configName());
scopeConfig.writeEntry("autoRefresh", m_aAutoRefresh->isChecked());
scopeConfig.writeEntry("realtime", m_aRealtime->isChecked());
scopeConfig.sync();
}
QString AbstractGfxScopeWidget::configName()
{
return "Scope_" + m_widgetName;
}
void AbstractGfxScopeWidget::prodHUDThread()
{
if (this->visibleRegion().isEmpty()) {
// qDebug() << "Scope " << m_widgetName << " is not visible. Not calculating HUD.";
} else {
if (m_semaphoreHUD.tryAcquire(1)) {
Q_ASSERT(!m_threadHUD.isRunning());
m_newHUDFrames.fetchAndStoreRelaxed(0);
m_newHUDUpdates.fetchAndStoreRelaxed(0);
m_threadHUD = QtConcurrent::run(this, &AbstractGfxScopeWidget::renderHUD, m_accelFactorHUD);
// qDebug() << "HUD thread started in " << m_widgetName;
} else {
// qDebug() << "HUD semaphore locked, not prodding in " << m_widgetName << ". Thread running: " << m_threadHUD.isRunning();
}
}
}
void AbstractGfxScopeWidget::prodScopeThread()
{
// Only start a new thread if the scope is actually visible
// and not hidden by another widget on the stack and if user want the scope to update.
if (this->visibleRegion().isEmpty() || (!m_aAutoRefresh->isChecked() && !m_requestForcedUpdate)) {
// qDebug() << "Scope " << m_widgetName << " is not visible. Not calculating scope.";
} else {
// 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.
if (m_semaphoreScope.tryAcquire(1)) {
Q_ASSERT(!m_threadScope.isRunning());
m_newScopeFrames.fetchAndStoreRelaxed(0);
m_newScopeUpdates.fetchAndStoreRelaxed(0);
Q_ASSERT(m_accelFactorScope > 0);
// See http://doc.qt.nokia.com/latest/qtconcurrentrun.html#run about
// running member functions in a thread
m_threadScope = QtConcurrent::run(this, &AbstractGfxScopeWidget::renderScope, m_accelFactorScope, m_scopeImage);
m_requestForcedUpdate = false;
// qDebug() << "Scope thread started in " << m_widgetName;
} else {
// qDebug() << "Scope semaphore locked, not prodding in " << m_widgetName << ". Thread running: " << m_threadScope.isRunning();
}
}
}
void AbstractGfxScopeWidget::prodBackgroundThread()
{
if (this->visibleRegion().isEmpty()) {
// qDebug() << "Scope " << m_widgetName << " is not visible. Not calculating background.";
} else {
if (m_semaphoreBackground.tryAcquire(1)) {
Q_ASSERT(!m_threadBackground.isRunning());
m_newBackgroundFrames.fetchAndStoreRelaxed(0);
m_newBackgroundUpdates.fetchAndStoreRelaxed(0);
m_threadBackground = QtConcurrent::run(this, &AbstractGfxScopeWidget::renderBackground, m_accelFactorBackground);
// qDebug() << "Background thread started in " << m_widgetName;
} else {
// qDebug() << "Background semaphore locked, not prodding in " << m_widgetName << ". Thread running: " << m_threadBackground.isRunning();
}
}
}
void AbstractGfxScopeWidget::forceUpdate(bool doUpdate)
{
// qDebug() << "Force update called in " << widgetName() << ". Arg: " << doUpdate;
if (!doUpdate) {
return;
}
m_requestForcedUpdate = true;
m_newHUDUpdates.fetchAndAddRelaxed(1);
m_newScopeUpdates.fetchAndAddRelaxed(1);
m_newBackgroundUpdates.fetchAndAddRelaxed(1);
prodHUDThread();
prodScopeThread();
prodBackgroundThread();
}
void AbstractGfxScopeWidget::forceUpdateHUD()
{
m_newHUDUpdates.fetchAndAddRelaxed(1);
prodHUDThread();
}
void AbstractGfxScopeWidget::forceUpdateScope()
{
m_newScopeUpdates.fetchAndAddRelaxed(1);
m_requestForcedUpdate = true;
prodScopeThread();
}
void AbstractGfxScopeWidget::forceUpdateBackground()
{
m_newBackgroundUpdates.fetchAndAddRelaxed(1);
prodBackgroundThread();
}
///// Events /////
void AbstractGfxScopeWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (!m_aAutoRefresh->isChecked()) {
m_requestForcedUpdate = true;
m_activeRender->sendFrameUpdate();
}
prodHUDThread();
prodScopeThread();
prodBackgroundThread();
QWidget::mouseReleaseEvent(event);
}
void AbstractGfxScopeWidget::resizeEvent(QResizeEvent *event)
{
// Update the dimension of the available rect for painting
m_scopeRect = scopeRect();
forceUpdate();
QWidget::resizeEvent(event);
}
void AbstractGfxScopeWidget::showEvent(QShowEvent *event)
{
QWidget::showEvent(event);
m_scopeRect = scopeRect();
}
void AbstractGfxScopeWidget::paintEvent(QPaintEvent *)
{
QPainter davinci(this);
davinci.drawImage(m_scopeRect.topLeft(), m_imgBackground);
davinci.drawImage(m_scopeRect.topLeft(), m_imgScope);
davinci.drawImage(m_scopeRect.topLeft(), m_imgHUD);
}
void AbstractGfxScopeWidget::mouseMoveEvent(QMouseEvent *event)
{
m_mousePos = event->pos();
m_mouseWithinWidget = true;
emit signalMousePositionChanged();
}
void AbstractGfxScopeWidget::leaveEvent(QEvent *)
{
m_mouseWithinWidget = false;
emit signalMousePositionChanged();
}
void AbstractGfxScopeWidget::customContextMenuRequested(const QPoint &pos)
{
m_menu->exec(this->mapToGlobal(pos));
}
uint AbstractGfxScopeWidget::calculateAccelFactorHUD(uint oldMseconds, uint)
{
return ceil((float)oldMseconds*REALTIME_FPS / 1000);
}
uint AbstractGfxScopeWidget::calculateAccelFactorScope(uint oldMseconds, uint)
{
return ceil((float)oldMseconds*REALTIME_FPS / 1000);
}
uint AbstractGfxScopeWidget::calculateAccelFactorBackground(uint oldMseconds, uint)
{
return ceil((float)oldMseconds*REALTIME_FPS / 1000);
}
///// Slots /////
void AbstractGfxScopeWidget::slotHUDRenderingFinished(uint mseconds, uint oldFactor)
{
// qDebug() << "HUD rendering has finished, waiting for termination in " << m_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 " << m_widgetName
// << ". New frames/updates: " << m_newHUDFrames << "/" << m_newHUDUpdates;
prodHUDThread();;
}
}
void AbstractGfxScopeWidget::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.
// qDebug() << "Scope rendering has finished, waiting for termination in " << m_widgetName;
m_threadScope.waitForFinished();
m_imgScope = m_threadScope.result();
// The scope thread has finished. Now we can release the semaphore, allowing a new thread.
// See prodScopeThread where the semaphore is acquired again.
m_semaphoreScope.release(1);
this->update();
// Calculate the acceleration factor hint to get «realtime» updates.
int accel;
if (m_aRealtime->isChecked()) {
accel = calculateAccelFactorScope(mseconds, oldFactor);
if (accel < 1) {
// If mseconds happens to be 0.
accel = 1;
}
// Don't directly calculate with m_accelFactorScope as we are dealing with concurrency.
// If m_accelFactorScope is set to 0 at the wrong moment, who knows what might happen
// then :) Therefore use a local variable.
m_accelFactorScope = accel;
}
if ((m_newScopeFrames > 0 && m_aAutoRefresh->isChecked()) || m_newScopeUpdates > 0) {
// qDebug() << "Trying to start a new scope thread for " << m_widgetName
// << ". New frames/updates: " << m_newScopeFrames << "/" << m_newScopeUpdates;
prodScopeThread();
}
}
void AbstractGfxScopeWidget::slotBackgroundRenderingFinished(uint mseconds, uint oldFactor)
{
// qDebug() << "Background rendering has finished, waiting for termination in " << m_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 " << m_widgetName
// << ". New frames/updates: " << m_newBackgroundFrames << "/" << m_newBackgroundUpdates;
prodBackgroundThread();;
}
}
void AbstractGfxScopeWidget::slotActiveMonitorChanged(bool isClipMonitor)
{
// qDebug() << "Active monitor has changed in " << m_widgetName << ". Is the clip monitor active now? " << isClipMonitor;
bool b = m_activeRender->disconnect(this);
Q_ASSERT(b);
m_activeRender = (isClipMonitor) ? m_clipMonitor->render : m_projMonitor->render;
//b &= connect(m_activeRender, SIGNAL(rendererPosition(int)), this, SLOT(slotRenderZoneUpdated()));
b &= connect(m_activeRender, SIGNAL(frameUpdated(QImage)), this, SLOT(slotRenderZoneUpdated(QImage)));
Q_ASSERT(b);
// Update the scope for the new monitor.
prodHUDThread();
prodScopeThread();
prodBackgroundThread();
}
void AbstractGfxScopeWidget::slotRenderZoneUpdated()
{
m_newHUDFrames.fetchAndAddRelaxed(1);
m_newScopeFrames.fetchAndAddRelaxed(1);
m_newBackgroundFrames.fetchAndAddRelaxed(1);
// qDebug() << "Monitor incoming. New frames total HUD/Scope/Background: " << m_newHUDFrames
// << "/" << m_newScopeFrames << "/" << m_newBackgroundFrames;
if (this->visibleRegion().isEmpty()) {
// qDebug() << "Scope of widget " << m_widgetName << " is not at the top, not rendering.";
} else {
if (m_aAutoRefresh->isChecked()) {
prodHUDThread();
prodScopeThread();
prodBackgroundThread();
}
}
}
void AbstractGfxScopeWidget::slotRenderZoneUpdated(QImage frame)
{
m_scopeImage = frame;
slotRenderZoneUpdated();
}
void AbstractGfxScopeWidget::slotResetRealtimeFactor(bool realtimeChecked)
{
if (!realtimeChecked) {
m_accelFactorHUD = 1;
m_accelFactorScope = 1;
m_accelFactorBackground = 1;
}
}
bool AbstractGfxScopeWidget::autoRefreshEnabled()
{
return m_aAutoRefresh->isChecked();
}
void AbstractGfxScopeWidget::slotAutoRefreshToggled(bool autoRefresh)
{
if (isVisible()) emit requestAutoRefresh(autoRefresh);
// TODO only if depends on input
if (autoRefresh) {
//forceUpdate();
m_requestForcedUpdate = true;
m_activeRender->sendFrameUpdate();
}
}
/***************************************************************************
* 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. *
***************************************************************************/
/**
This abstract widget is a proof that abstract things sometimes *are* useful.
The widget expects three layers which
* Will be painted on top of each other on each update
* Are rendered in a separate thread so that the UI is not blocked
* Are rendered only if necessary (e.g., if a layer does not depend
on input images, it will not be re-rendered for incoming frames)
The layer order is as follows:
_____________________
/ \
/ HUD Layer \
/ \
---------------------------
_____________________
/ \
/ Scope Layer \
/ \
---------------------------
_____________________
/ \
/ Background Layer \
/ \
---------------------------
Colors of Scope Widgets are defined in here (and thus don't need to be
re-defined in the implementation of the widget's .ui file).
The custom context menu already contains entries, like for enabling auto-
refresh. It can certainly be extended in the implementation of the widget.
Note: Widgets deriving from this class should connect slotActiveMonitorChanged
to the appropriate signal.
If you intend to write an own widget inheriting from this one, please read
the comments on the unimplemented methods carefully. They are not only here
for optical amusement, but also contain important information.
*/
#ifndef ABSTRACTGFXSCOPEWIDGET_H
#define ABSTRACTGFXSCOPEWIDGET_H
#include <QtCore>
#include <QWidget>
class QMenu;
class Monitor;
class Render;
class AbstractGfxScopeWidget : public QWidget
{
Q_OBJECT
public:
AbstractGfxScopeWidget(Monitor *projMonitor, Monitor *clipMonitor, bool trackMouse = false, QWidget *parent = 0);
virtual ~AbstractGfxScopeWidget(); // Must be virtual because of inheritance, to avoid memory leaks
QPalette m_scopePalette;
/** Initializes widget settings (reads configuration).
Has to be called in the implementing object. */
void init();
/** Does this scope have auto-refresh enabled */
bool autoRefreshEnabled();
///// Unimplemented /////
virtual QString widgetName() const = 0;
///// Variables /////
static const QPen penThick;
static const QPen penThin;
static const QPen penLight;
static const QPen penLightDots;
static const QPen penDark;
static const QPen penDarkDots;
protected:
///// Variables /////
Monitor *m_projMonitor;
Monitor *m_clipMonitor;
Render *m_activeRender;
/** The context menu. Feel free to add new entries in your implementation. */
QMenu *m_menu;
/** Enables auto refreshing of the scope.
This is when a new frame is shown on the active monitor.
Resize events always force a recalculation. */
QAction *m_aAutoRefresh;
/** Realtime rendering. Should be disabled if it is not supported.
Use the accelerationFactor variable passed to the render functions as a hint of
how many times faster the scope should be calculated. */
QAction *m_aRealtime;
/** The mouse position; Updated when the mouse enters the widget
AND mouse tracking has been enabled. */
QPoint m_mousePos;
/** Knows whether the mouse currently lies within the widget or not.
Can e.g. be used for drawing a HUD only when the mouse is in the widget. */
bool m_mouseWithinWidget;
/** Offset from the widget's borders */
const uchar offset;
/** The rect on the widget we're painting in.
Can be used by the implementing widget, e.g. in the render methods.
Is updated when necessary (size changes). */
QRect m_scopeRect;
/** Images storing the calculated layers. Will be used on repaint events. */
QImage m_imgHUD;
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. */