Merge branch 'master' into pr/1913
This commit is contained in:
@@ -18,13 +18,9 @@ set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
audio/audiomanager.cpp
|
||||
audio/audiomanager.h
|
||||
audio/audioprocessor.cpp
|
||||
audio/audioprocessor.h
|
||||
audio/audiovisualwaveform.cpp
|
||||
audio/audiovisualwaveform.h
|
||||
audio/packedprocessor.cpp
|
||||
audio/packedprocessor.h
|
||||
audio/planarprocessor.cpp
|
||||
audio/planarprocessor.h
|
||||
audio/tempoprocessor.cpp
|
||||
audio/tempoprocessor.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include "audio/packedprocessor.h"
|
||||
#include "config/config.h"
|
||||
|
||||
namespace olive {
|
||||
@@ -81,10 +80,11 @@ int InputCallback(const void *input, void *output, unsigned long frameCount, con
|
||||
return paContinue;
|
||||
}
|
||||
|
||||
void AudioManager::PushToOutput(const AudioParams ¶ms, const QByteArray &samples)
|
||||
bool AudioManager::PushToOutput(const AudioParams ¶ms, const QByteArray &samples, QString *error)
|
||||
{
|
||||
if (output_device_ == paNoDevice) {
|
||||
return;
|
||||
if (error) *error = tr("No output device is set");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (output_params_ != params || output_stream_ == nullptr) {
|
||||
@@ -94,7 +94,13 @@ void AudioManager::PushToOutput(const AudioParams ¶ms, const QByteArray &sam
|
||||
|
||||
PaStreamParameters p = GetPortAudioParams(params, output_device_);
|
||||
|
||||
Pa_OpenStream(&output_stream_, nullptr, &p, output_params_.sample_rate(), paFramesPerBufferUnspecified, paNoFlag, OutputCallback, output_buffer_);
|
||||
PaError r = Pa_OpenStream(&output_stream_, nullptr, &p, output_params_.sample_rate(), paFramesPerBufferUnspecified, paNoFlag, OutputCallback, output_buffer_);
|
||||
if (r != paNoError) {
|
||||
// Unhandled error
|
||||
//qCritical() << "Failed to open output stream:" << Pa_GetErrorText(r);
|
||||
if (error) *error = Pa_GetErrorText(r);
|
||||
return false;
|
||||
}
|
||||
|
||||
output_buffer_->set_bytes_per_frame(output_params_.samples_to_bytes(1));
|
||||
}
|
||||
@@ -104,6 +110,8 @@ void AudioManager::PushToOutput(const AudioParams ¶ms, const QByteArray &sam
|
||||
if (!Pa_IsStreamActive(output_stream_)) {
|
||||
Pa_StartStream(output_stream_);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioManager::ClearBufferedOutput()
|
||||
@@ -189,7 +197,7 @@ void AudioManager::HardReset()
|
||||
Pa_Initialize();
|
||||
}
|
||||
|
||||
bool AudioManager::StartRecording(const EncodingParams ¶ms)
|
||||
bool AudioManager::StartRecording(const EncodingParams ¶ms, QString *error_str)
|
||||
{
|
||||
if (input_device_ == paNoDevice) {
|
||||
return false;
|
||||
@@ -203,12 +211,19 @@ bool AudioManager::StartRecording(const EncodingParams ¶ms)
|
||||
|
||||
PaStreamParameters p = GetPortAudioParams(params.audio_params(), input_device_);
|
||||
|
||||
if (Pa_OpenStream(&input_stream_, &p, nullptr, params.audio_params().sample_rate(), paFramesPerBufferUnspecified, paNoFlag, InputCallback, input_encoder_) == paNoError) {
|
||||
if (Pa_StartStream(input_stream_) == paNoError) {
|
||||
PaError r = Pa_OpenStream(&input_stream_, &p, nullptr, params.audio_params().sample_rate(), paFramesPerBufferUnspecified, paNoFlag, InputCallback, input_encoder_);
|
||||
if (r == paNoError) {
|
||||
//const PaStreamInfo* info = Pa_GetStreamInfo(input_stream_);
|
||||
r = Pa_StartStream(input_stream_);
|
||||
if (r == paNoError) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (error_str) {
|
||||
*error_str = Pa_GetErrorText(r);
|
||||
}
|
||||
|
||||
StopRecording();
|
||||
return false;
|
||||
}
|
||||
@@ -235,7 +250,7 @@ PaDeviceIndex AudioManager::FindConfigDeviceByName(bool is_output_device)
|
||||
{
|
||||
QString entry = is_output_device ? QStringLiteral("AudioOutput") : QStringLiteral("AudioInput");
|
||||
|
||||
return FindDeviceByName(Config::Current()[entry].toString(), is_output_device);
|
||||
return FindDeviceByName(OLIVE_CONFIG_STR(entry).toString(), is_output_device);
|
||||
}
|
||||
|
||||
PaDeviceIndex AudioManager::FindDeviceByName(const QString &s, bool is_output_device)
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <portaudio.h>
|
||||
|
||||
#include "audiovisualwaveform.h"
|
||||
#include "audio/audioprocessor.h"
|
||||
#include "common/define.h"
|
||||
#include "codec/ffmpeg/ffmpegencoder.h"
|
||||
#include "render/audioparams.h"
|
||||
@@ -52,7 +53,7 @@ public:
|
||||
|
||||
void SetOutputNotifyInterval(int n);
|
||||
|
||||
void PushToOutput(const AudioParams ¶ms, const QByteArray& samples);
|
||||
bool PushToOutput(const AudioParams ¶ms, const QByteArray& samples, QString *error = nullptr);
|
||||
|
||||
void ClearBufferedOutput();
|
||||
|
||||
@@ -74,7 +75,7 @@ public:
|
||||
|
||||
void HardReset();
|
||||
|
||||
bool StartRecording(const EncodingParams ¶ms);
|
||||
bool StartRecording(const EncodingParams ¶ms, QString *error_str = nullptr);
|
||||
|
||||
void StopRecording();
|
||||
|
||||
@@ -86,6 +87,8 @@ public:
|
||||
signals:
|
||||
void OutputNotify();
|
||||
|
||||
void OutputParamsChanged();
|
||||
|
||||
private:
|
||||
AudioManager();
|
||||
|
||||
@@ -104,6 +107,7 @@ private:
|
||||
|
||||
PaDeviceIndex input_device_;
|
||||
PaStream *input_stream_;
|
||||
|
||||
FFmpegEncoder *input_encoder_;
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
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 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "audioprocessor.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavfilter/buffersrc.h>
|
||||
#include <libavfilter/buffersink.h>
|
||||
}
|
||||
|
||||
#include "common/ffmpegutils.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
AudioProcessor::AudioProcessor()
|
||||
{
|
||||
filter_graph_ = nullptr;
|
||||
in_frame_ = nullptr;
|
||||
out_frame_ = nullptr;
|
||||
}
|
||||
|
||||
AudioProcessor::~AudioProcessor()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
bool AudioProcessor::Open(const AudioParams &from, const AudioParams &to, double tempo)
|
||||
{
|
||||
if (filter_graph_) {
|
||||
qWarning() << "Tried to open a processor that was already open";
|
||||
return false;
|
||||
}
|
||||
|
||||
filter_graph_ = avfilter_graph_alloc();
|
||||
if (!filter_graph_) {
|
||||
qCritical() << "Failed to allocate filter graph";
|
||||
return false;
|
||||
}
|
||||
|
||||
from_fmt_ = FFmpegUtils::GetFFmpegSampleFormat(from.format());
|
||||
to_fmt_ = FFmpegUtils::GetFFmpegSampleFormat(to.format());
|
||||
|
||||
// Set up audio buffer args
|
||||
char filter_args[200];
|
||||
snprintf(filter_args, 200, "time_base=%d/%d:sample_rate=%d:sample_fmt=%d:channel_layout=0x%" PRIx64,
|
||||
1,
|
||||
from.sample_rate(),
|
||||
from.sample_rate(),
|
||||
from_fmt_,
|
||||
from.channel_layout());
|
||||
|
||||
int r;
|
||||
|
||||
// Create buffersrc (input)
|
||||
r = avfilter_graph_create_filter(&buffersrc_ctx_, avfilter_get_by_name("abuffer"), "in", filter_args, nullptr, filter_graph_);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to create buffersrc:" << r;
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store "previous" filter for linking
|
||||
AVFilterContext *previous_filter = buffersrc_ctx_;
|
||||
|
||||
// Create tempo
|
||||
bool create_tempo;
|
||||
if ((create_tempo = !qFuzzyCompare(tempo, 1.0))) {
|
||||
// Create audio tempo filters: FFmpeg's atempo can only be set between 0.5 and 2.0. If the requested speed is outside
|
||||
// those boundaries, we need to daisychain more than one together.
|
||||
double base = (tempo > 1.0) ? 2.0 : 0.5;
|
||||
double speed_log = log(tempo) / log(base);
|
||||
|
||||
// This is the number of how many 0.5 or 2.0 tempos we need to daisychain
|
||||
int whole = qFloor(speed_log);
|
||||
|
||||
// Set speed_log to the remainder
|
||||
speed_log -= whole;
|
||||
|
||||
for (int i=0;i<=whole;i++) {
|
||||
double filter_tempo = (i == whole) ? qPow(base, speed_log) : base;
|
||||
|
||||
if (qFuzzyCompare(filter_tempo, 1.0)) {
|
||||
// This filter would do nothing
|
||||
continue;
|
||||
}
|
||||
|
||||
previous_filter = CreateTempoFilter(filter_graph_,
|
||||
previous_filter,
|
||||
filter_tempo);
|
||||
|
||||
if (!previous_filter) {
|
||||
qCritical() << "Failed to create audio tempo filter";
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create conversion filter
|
||||
if (from.sample_rate() != to.sample_rate() || from.channel_layout() != to.channel_layout() || from.format() != to.format()
|
||||
|| (to.FormatIsPlanar() && create_tempo)) { // Tempo processor automatically converts to packed,
|
||||
// so if the desired output is planar, it'll need
|
||||
// to be converted
|
||||
snprintf(filter_args, 200, "sample_fmts=%s:sample_rates=%d:channel_layouts=0x%" PRIx64,
|
||||
av_get_sample_fmt_name(to_fmt_),
|
||||
to.sample_rate(),
|
||||
to.channel_layout());
|
||||
|
||||
AVFilterContext *c;
|
||||
r = avfilter_graph_create_filter(&c, avfilter_get_by_name("aformat"), "fmt", filter_args, nullptr, filter_graph_);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to create format conversion filter:" << r << filter_args;
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
r = avfilter_link(previous_filter, 0, c, 0);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to link filters:" << r;
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
previous_filter = c;
|
||||
}
|
||||
|
||||
// Create buffersink (output)
|
||||
r = avfilter_graph_create_filter(&buffersink_ctx_, avfilter_get_by_name("abuffersink"), "out", nullptr, nullptr, filter_graph_);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to create buffersink:" << r;
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
r = avfilter_link(previous_filter, 0, buffersink_ctx_, 0);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to link filters:" << r;
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
r = avfilter_graph_config(filter_graph_, nullptr);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to configure graph:" << r;
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
in_frame_ = av_frame_alloc();
|
||||
if (in_frame_) {
|
||||
in_frame_->sample_rate = from.sample_rate();
|
||||
in_frame_->format = from_fmt_;
|
||||
in_frame_->channel_layout = from.channel_layout();
|
||||
in_frame_->channels = from.channel_count();
|
||||
in_frame_->pts = 0;
|
||||
} else {
|
||||
qCritical() << "Failed to allocate input frame";
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
out_frame_ = av_frame_alloc();
|
||||
if (!out_frame_) {
|
||||
qCritical() << "Failed to allocate output frame";
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
from_ = from;
|
||||
to_ = to;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioProcessor::Close()
|
||||
{
|
||||
if (filter_graph_) {
|
||||
avfilter_graph_free(&filter_graph_);
|
||||
filter_graph_ = nullptr;
|
||||
buffersrc_ctx_ = nullptr;
|
||||
buffersink_ctx_ = nullptr;
|
||||
}
|
||||
|
||||
if (in_frame_) {
|
||||
av_frame_free(&in_frame_);
|
||||
in_frame_ = nullptr;
|
||||
}
|
||||
|
||||
if (out_frame_) {
|
||||
av_frame_free(&out_frame_);
|
||||
out_frame_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int AudioProcessor::Convert(float **in, int nb_in_samples, AudioProcessor::Buffer *output)
|
||||
{
|
||||
if (!IsOpen()) {
|
||||
qCritical() << "Tried to convert on closed processor";
|
||||
return -1;
|
||||
}
|
||||
|
||||
int r = 0;
|
||||
|
||||
if (in && nb_in_samples) {
|
||||
// Set frame parameters
|
||||
in_frame_->nb_samples = nb_in_samples;
|
||||
for (int i=0; i<from_.channel_count(); i++) {
|
||||
in_frame_->data[i] = reinterpret_cast<uint8_t*>(in[i]);
|
||||
in_frame_->linesize[i] = from_.samples_to_bytes(nb_in_samples);
|
||||
}
|
||||
|
||||
r = av_buffersrc_add_frame_flags(buffersrc_ctx_, in_frame_, AV_BUFFERSRC_FLAG_KEEP_REF);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to add frame to buffersrc:" << r;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
if (output) {
|
||||
int nb_channels = to_.channel_count();
|
||||
|
||||
if (to_.FormatIsPacked()) {
|
||||
nb_channels = 1;
|
||||
}
|
||||
|
||||
AudioProcessor::Buffer &result = *output;
|
||||
result.resize(nb_channels);
|
||||
|
||||
int byte_offset = 0;
|
||||
|
||||
while (true) {
|
||||
av_frame_unref(out_frame_);
|
||||
r = av_buffersink_get_frame(buffersink_ctx_, out_frame_);
|
||||
if (r < 0) {
|
||||
if (r == AVERROR(EAGAIN)) {
|
||||
r = 0;
|
||||
} else {
|
||||
// Handle unexpected error
|
||||
qCritical() << "Failed to pull from buffersink:" << r;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
int nb_bytes = out_frame_->nb_samples * to_.bytes_per_sample_per_channel();
|
||||
if (to_.FormatIsPacked()) {
|
||||
nb_bytes *= to_.channel_count();
|
||||
}
|
||||
|
||||
for (int i=0; i<nb_channels; i++) {
|
||||
result[i].resize(byte_offset + nb_bytes);
|
||||
memcpy(result[i].data() + byte_offset, out_frame_->data[i], nb_bytes);
|
||||
}
|
||||
byte_offset += nb_bytes;
|
||||
}
|
||||
av_frame_unref(out_frame_);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void AudioProcessor::Flush()
|
||||
{
|
||||
int r = av_buffersrc_add_frame_flags(buffersrc_ctx_, nullptr, AV_BUFFERSRC_FLAG_KEEP_REF);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to flush:" << r;
|
||||
}
|
||||
}
|
||||
|
||||
AVFilterContext *AudioProcessor::CreateTempoFilter(AVFilterGraph* graph, AVFilterContext* link, const double &tempo)
|
||||
{
|
||||
// Set up tempo param, which is taken as a C string
|
||||
char speed_param[20];
|
||||
snprintf(speed_param, 20, "%f", tempo);
|
||||
|
||||
AVFilterContext* tempo_ctx = nullptr;
|
||||
|
||||
if (avfilter_graph_create_filter(&tempo_ctx, avfilter_get_by_name("atempo"), "atempo", speed_param, nullptr, graph) >= 0
|
||||
&& avfilter_link(link, 0, tempo_ctx, 0) == 0) {
|
||||
return tempo_ctx;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,14 +18,8 @@
|
||||
|
||||
***/
|
||||
|
||||
#ifndef TEMPOPROCESSOR_H
|
||||
#define TEMPOPROCESSOR_H
|
||||
|
||||
#ifdef __MINGW32__
|
||||
#ifndef __USE_MINGW_ANSI_STDIO
|
||||
#define __USE_MINGW_ANSI_STDIO
|
||||
#endif
|
||||
#endif
|
||||
#ifndef AUDIOPROCESSOR_H
|
||||
#define AUDIOPROCESSOR_H
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
@@ -37,28 +31,28 @@ extern "C" {
|
||||
|
||||
namespace olive {
|
||||
|
||||
class TempoProcessor
|
||||
class AudioProcessor
|
||||
{
|
||||
public:
|
||||
TempoProcessor();
|
||||
AudioProcessor();
|
||||
|
||||
~TempoProcessor();
|
||||
~AudioProcessor();
|
||||
|
||||
DISABLE_COPY_MOVE(TempoProcessor)
|
||||
DISABLE_COPY_MOVE(AudioProcessor)
|
||||
|
||||
bool IsOpen() const;
|
||||
bool Open(const AudioParams &from, const AudioParams &to, double tempo = 1.0);
|
||||
|
||||
const double& GetSpeed() const;
|
||||
void Close();
|
||||
|
||||
bool Open(const AudioParams& params, const double &speed);
|
||||
bool IsOpen() const { return filter_graph_; }
|
||||
|
||||
void Push(const QByteArray &packed);
|
||||
using Buffer = QVector<QByteArray>;
|
||||
int Convert(float **in, int nb_in_samples, AudioProcessor::Buffer *output);
|
||||
|
||||
void Flush();
|
||||
|
||||
QByteArray Pull();
|
||||
|
||||
void Close();
|
||||
const AudioParams &from() const { return from_; }
|
||||
const AudioParams &to() const { return to_; }
|
||||
|
||||
private:
|
||||
static AVFilterContext* CreateTempoFilter(AVFilterGraph *graph, AVFilterContext *link, const double& tempo);
|
||||
@@ -69,17 +63,18 @@ private:
|
||||
|
||||
AVFilterContext* buffersink_ctx_;
|
||||
|
||||
AudioParams params_;
|
||||
AudioParams from_;
|
||||
AVSampleFormat from_fmt_;
|
||||
|
||||
int64_t timestamp_;
|
||||
AudioParams to_;
|
||||
AVSampleFormat to_fmt_;
|
||||
|
||||
double speed_;
|
||||
AVFrame *in_frame_;
|
||||
|
||||
bool open_;
|
||||
AVFrame *out_frame_;
|
||||
|
||||
bool flushed_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // TEMPOPROCESSOR_H
|
||||
#endif // AUDIOPROCESSOR_H
|
||||
@@ -40,10 +40,10 @@ AudioVisualWaveform::AudioVisualWaveform() :
|
||||
}
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::OverwriteSamplesFromBuffer(SampleBufferPtr samples, int sample_rate, const rational &start, double target_rate, Sample& data, int &start_index, int &samples_length)
|
||||
void AudioVisualWaveform::OverwriteSamplesFromBuffer(const SampleBuffer &samples, int sample_rate, const rational &start, double target_rate, Sample& data, int &start_index, int &samples_length)
|
||||
{
|
||||
start_index = time_to_samples(start, target_rate);
|
||||
samples_length = time_to_samples(static_cast<double>(samples->sample_count()) / static_cast<double>(sample_rate), target_rate);
|
||||
samples_length = time_to_samples(static_cast<double>(samples.sample_count()) / static_cast<double>(sample_rate), target_rate);
|
||||
|
||||
int end_index = start_index + samples_length;
|
||||
if (data.size() < end_index) {
|
||||
@@ -54,7 +54,7 @@ void AudioVisualWaveform::OverwriteSamplesFromBuffer(SampleBufferPtr samples, in
|
||||
|
||||
for (int i=0; i<samples_length; i+=channels_) {
|
||||
int src_start = qRound((double(i) * chunk_size)) / channels_;
|
||||
int src_end = qMin(qRound((double(i + channels_) * chunk_size)) / channels_, samples->sample_count());
|
||||
int src_end = qMin(qRound((double(i + channels_) * chunk_size)) / channels_, samples.sample_count());
|
||||
|
||||
Sample summary = SumSamples(samples,
|
||||
src_start,
|
||||
@@ -91,7 +91,7 @@ void AudioVisualWaveform::OverwriteSamplesFromMipmap(const AudioVisualWaveform::
|
||||
input_length = samples_length;
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::OverwriteSamples(SampleBufferPtr samples, int sample_rate, const rational &start)
|
||||
void AudioVisualWaveform::OverwriteSamples(const SampleBuffer &samples, int sample_rate, const rational &start)
|
||||
{
|
||||
if (!channels_) {
|
||||
qWarning() << "Failed to write samples - channel count is zero";
|
||||
@@ -125,7 +125,7 @@ void AudioVisualWaveform::OverwriteSamples(SampleBufferPtr samples, int sample_r
|
||||
current_mipmap->second);
|
||||
}
|
||||
|
||||
rational sample_length(samples->sample_count(), sample_rate);
|
||||
rational sample_length(samples.sample_count(), sample_rate);
|
||||
length_ = qMax(length_, start + sample_length);
|
||||
}
|
||||
|
||||
@@ -277,7 +277,7 @@ AudioVisualWaveform::Sample AudioVisualWaveform::GetSummaryFromTime(const ration
|
||||
return AudioVisualWaveform::Sample(channel_count(), {0, 0});
|
||||
}
|
||||
|
||||
void ExpandMinMaxChannel(float *a, int start, int length, float &min_val, float &max_val)
|
||||
void ExpandMinMaxChannel(const float *a, int start, int length, float &min_val, float &max_val)
|
||||
{
|
||||
#if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM)
|
||||
// SSE optimized
|
||||
@@ -321,13 +321,13 @@ void ExpandMinMaxChannel(float *a, int start, int length, float &min_val, float
|
||||
#endif
|
||||
}
|
||||
|
||||
AudioVisualWaveform::Sample AudioVisualWaveform::SumSamples(SampleBufferPtr samples, int start_index, int length)
|
||||
AudioVisualWaveform::Sample AudioVisualWaveform::SumSamples(const SampleBuffer &samples, int start_index, int length)
|
||||
{
|
||||
int channels = samples->audio_params().channel_count();
|
||||
int channels = samples.audio_params().channel_count();
|
||||
AudioVisualWaveform::Sample summed_samples(channels);
|
||||
|
||||
for (int channel=0; channel<samples->audio_params().channel_count(); channel++) {
|
||||
ExpandMinMaxChannel(samples->data(channel), start_index, length, summed_samples[channel].min, summed_samples[channel].max);
|
||||
for (int channel=0; channel<samples.audio_params().channel_count(); channel++) {
|
||||
ExpandMinMaxChannel(samples.data(channel), start_index, length, summed_samples[channel].min, summed_samples[channel].max);
|
||||
}
|
||||
|
||||
// for reference: this approximation is n x faster (and less accurate) for a n-tracks clip
|
||||
@@ -426,7 +426,7 @@ void AudioVisualWaveform::DrawWaveform(QPainter *painter, const QRect& rect, con
|
||||
int start = qMax(rect.x(), -top_left.x());
|
||||
int end = qMin(rect.right(), -top_left.x() + viewport.width());
|
||||
|
||||
bool rectified = Config::Current()[QStringLiteral("RectifiedWaveforms")].toBool();
|
||||
bool rectified = OLIVE_CONFIG("RectifiedWaveforms").toBool();
|
||||
|
||||
for (int i=start;i<end;i++) {
|
||||
sample_index = next_sample_index;
|
||||
|
||||
@@ -65,7 +65,7 @@ public:
|
||||
*
|
||||
* Starting at `start`, writes samples over anything in the buffer, expanding it if necessary.
|
||||
*/
|
||||
void OverwriteSamples(SampleBufferPtr samples, int sample_rate, const rational& start = 0);
|
||||
void OverwriteSamples(const SampleBuffer &samples, int sample_rate, const rational& start = 0);
|
||||
|
||||
/**
|
||||
* @brief Replaces sums at a certain range in this visual waveform
|
||||
@@ -98,7 +98,7 @@ public:
|
||||
|
||||
Sample GetSummaryFromTime(const rational& start, const rational& length) const;
|
||||
|
||||
static Sample SumSamples(SampleBufferPtr samples, int start_index, int length);
|
||||
static Sample SumSamples(const SampleBuffer &samples, int start_index, int length);
|
||||
|
||||
static Sample ReSumSamples(const SamplePerChannel *samples, int nb_samples, int nb_channels);
|
||||
|
||||
@@ -111,7 +111,7 @@ public:
|
||||
static const rational kMaximumSampleRate;
|
||||
|
||||
private:
|
||||
void OverwriteSamplesFromBuffer(SampleBufferPtr samples, int sample_rate, const rational& start, double target_rate, Sample &data, int &start_index, int &samples_length);
|
||||
void OverwriteSamplesFromBuffer(const SampleBuffer &samples, int sample_rate, const rational& start, double target_rate, Sample &data, int &start_index, int &samples_length);
|
||||
|
||||
void OverwriteSamplesFromMipmap(const Sample& input, double input_sample_rate, int &input_start, int &input_length, const rational& start, double output_rate, Sample &output_data);
|
||||
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
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 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "packedprocessor.h"
|
||||
|
||||
#include "common/ffmpegutils.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
PackedProcessor::PackedProcessor() :
|
||||
swr_ctx_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
PackedProcessor::~PackedProcessor()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
bool PackedProcessor::Open(const AudioParams ¶ms)
|
||||
{
|
||||
if (IsOpen()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
swr_ctx_ = swr_alloc_set_opts(nullptr,
|
||||
params.channel_layout(),
|
||||
FFmpegUtils::GetFFmpegSampleFormat(AudioParams::GetPackedEquivalent(params.format())),
|
||||
params.sample_rate(),
|
||||
params.channel_layout(),
|
||||
FFmpegUtils::GetFFmpegSampleFormat(params.format()),
|
||||
params.sample_rate(),
|
||||
0,
|
||||
nullptr);
|
||||
|
||||
if (!swr_ctx_) {
|
||||
qCritical() << "Failed to allocate resample context";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (swr_init(swr_ctx_) < 0) {
|
||||
qCritical() << "Failed to init resample context";
|
||||
swr_free(&swr_ctx_);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray PackedProcessor::Convert(SampleBufferPtr planar)
|
||||
{
|
||||
if (!IsOpen()) {
|
||||
qCritical() << "Tried to convert while closed";
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
int nb_samples = planar->sample_count();
|
||||
if (nb_samples == 0) {
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
QByteArray output(planar->audio_params().samples_to_bytes(nb_samples), Qt::Uninitialized);
|
||||
uint8_t *output_data = reinterpret_cast<uint8_t*>(output.data());
|
||||
|
||||
int ret = swr_convert(swr_ctx_, &output_data, nb_samples,
|
||||
const_cast<const uint8_t**>(reinterpret_cast<uint8_t**>(planar->to_raw_ptrs())),
|
||||
nb_samples);
|
||||
if (ret < 0) {
|
||||
char buf[200];
|
||||
av_strerror(ret, buf, 200);
|
||||
qDebug() << "Packed processor failed with error:" << buf << ret;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
void PackedProcessor::Close()
|
||||
{
|
||||
if (swr_ctx_) {
|
||||
swr_free(&swr_ctx_);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
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 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "planarprocessor.h"
|
||||
|
||||
#include "common/ffmpegutils.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
PlanarProcessor::PlanarProcessor() :
|
||||
swr_ctx_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
PlanarProcessor::~PlanarProcessor()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
bool PlanarProcessor::Open(const AudioParams ¶ms)
|
||||
{
|
||||
if (IsOpen()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
swr_ctx_ = swr_alloc_set_opts(nullptr,
|
||||
params.channel_layout(),
|
||||
FFmpegUtils::GetFFmpegSampleFormat(AudioParams::GetPlanarEquivalent(params.format())),
|
||||
params.sample_rate(),
|
||||
params.channel_layout(),
|
||||
FFmpegUtils::GetFFmpegSampleFormat(params.format()),
|
||||
params.sample_rate(),
|
||||
0,
|
||||
nullptr);
|
||||
|
||||
if (!swr_ctx_) {
|
||||
qCritical() << "Failed to allocate resample context";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (swr_init(swr_ctx_) < 0) {
|
||||
qCritical() << "Failed to init resample context";
|
||||
swr_free(&swr_ctx_);
|
||||
return false;
|
||||
}
|
||||
|
||||
params_ = params;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SampleBufferPtr PlanarProcessor::Convert(const QByteArray &packed)
|
||||
{
|
||||
if (!IsOpen()) {
|
||||
qCritical() << "Tried to convert while closed";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (packed.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int nb_samples_per_channel = params_.bytes_to_samples(packed.size());
|
||||
|
||||
SampleBufferPtr output = SampleBuffer::CreateAllocated(params_, nb_samples_per_channel);
|
||||
|
||||
const uint8_t *input = reinterpret_cast<const uint8_t*>(packed.constData());
|
||||
int ret = swr_convert(swr_ctx_,
|
||||
reinterpret_cast<uint8_t**>(output->to_raw_ptrs()), nb_samples_per_channel,
|
||||
&input, nb_samples_per_channel);
|
||||
if (ret < 0) {
|
||||
char buf[200];
|
||||
av_strerror(ret, buf, 200);
|
||||
qDebug() << "Planar processor failed with error:" << buf << ret;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
void PlanarProcessor::Close()
|
||||
{
|
||||
if (swr_ctx_) {
|
||||
swr_free(&swr_ctx_);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
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 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "tempoprocessor.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavfilter/buffersink.h>
|
||||
#include <libavfilter/buffersrc.h>
|
||||
#include <libavutil/opt.h>
|
||||
}
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "common/ffmpegutils.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
TempoProcessor::TempoProcessor() :
|
||||
filter_graph_(nullptr),
|
||||
buffersrc_ctx_(nullptr),
|
||||
buffersink_ctx_(nullptr),
|
||||
open_(false)
|
||||
{
|
||||
}
|
||||
|
||||
TempoProcessor::~TempoProcessor()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
bool TempoProcessor::IsOpen() const
|
||||
{
|
||||
return open_;
|
||||
}
|
||||
|
||||
const double &TempoProcessor::GetSpeed() const
|
||||
{
|
||||
return speed_;
|
||||
}
|
||||
|
||||
bool TempoProcessor::Open(const AudioParams ¶ms, const double& speed)
|
||||
{
|
||||
if (open_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
params_ = params;
|
||||
speed_ = speed;
|
||||
|
||||
// Create AVFilterGraph instance
|
||||
filter_graph_ = avfilter_graph_alloc();
|
||||
if (!filter_graph_) {
|
||||
qCritical() << "Failed to create AVFilterGraph";
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set up audio buffer args
|
||||
char filter_args[200];
|
||||
snprintf(filter_args, 200, "time_base=%d/%d:sample_rate=%d:sample_fmt=%d:channel_layout=0x%" PRIx64,
|
||||
1,
|
||||
params_.sample_rate(),
|
||||
params_.sample_rate(),
|
||||
FFmpegUtils::GetFFmpegSampleFormat(params_.format()),
|
||||
params.channel_layout());
|
||||
|
||||
// Create buffer and buffersink
|
||||
if (avfilter_graph_create_filter(&buffersrc_ctx_, avfilter_get_by_name("abuffer"), "in", filter_args, nullptr, filter_graph_) < 0) {
|
||||
qCritical() << "Failed to create audio buffer source";
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (avfilter_graph_create_filter(&buffersink_ctx_, avfilter_get_by_name("abuffersink"), "out", nullptr, nullptr, filter_graph_) < 0) {
|
||||
qCritical() << "Failed to create audio buffer sink";
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create audio tempo filters: FFmpeg's atempo can only be set between 0.5 and 2.0. If the requested speed is outside
|
||||
// those boundaries, we need to daisychain more than one together.
|
||||
double base = (speed_ > 1.0) ? 2.0 : 0.5;
|
||||
double speed_log = log(speed_) / log(base);
|
||||
|
||||
// This is the number of how many 0.5 or 2.0 tempos we need to daisychain
|
||||
int whole = qFloor(speed_log);
|
||||
|
||||
// Set speed_log to the remainder
|
||||
speed_log -= whole;
|
||||
|
||||
AVFilterContext* previous_filter = buffersrc_ctx_;
|
||||
|
||||
for (int i=0;i<=whole;i++) {
|
||||
double filter_tempo = (i == whole) ? qPow(base, speed_log) : base;
|
||||
|
||||
if (qFuzzyCompare(filter_tempo, 1.0)) {
|
||||
// This filter would do nothing
|
||||
continue;
|
||||
}
|
||||
|
||||
previous_filter = CreateTempoFilter(filter_graph_,
|
||||
previous_filter,
|
||||
filter_tempo);
|
||||
|
||||
if (!previous_filter) {
|
||||
qCritical() << "Failed to create audio tempo filter";
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Link the last filter to the buffersink
|
||||
if (avfilter_link(previous_filter, 0, buffersink_ctx_, 0) != 0) {
|
||||
qCritical() << "Failed to link final filter and buffer sink";
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Config graph
|
||||
if (avfilter_graph_config(filter_graph_, nullptr) < 0) {
|
||||
qCritical() << "Failed to configure filter graph";
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
timestamp_ = 0;
|
||||
|
||||
open_ = true;
|
||||
|
||||
flushed_ = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TempoProcessor::Push(const QByteArray &packed)
|
||||
{
|
||||
if (!IsOpen()) {
|
||||
qWarning() << "Tried to push to closed TempoProcessor";
|
||||
return;
|
||||
}
|
||||
|
||||
if (flushed_) {
|
||||
qWarning() << "Tried to push to flushed TempoProcessor";
|
||||
return;
|
||||
}
|
||||
|
||||
AVFrame* src_frame = av_frame_alloc();
|
||||
|
||||
if (!src_frame) {
|
||||
qCritical() << "Failed to allocate source frame";
|
||||
return;
|
||||
}
|
||||
|
||||
// Allocate a buffer for the number of samples we got
|
||||
src_frame->sample_rate = params_.sample_rate();
|
||||
src_frame->format = FFmpegUtils::GetFFmpegSampleFormat(params_.format());
|
||||
src_frame->channel_layout = params_.channel_layout();
|
||||
src_frame->nb_samples = params_.bytes_to_samples(packed.size());
|
||||
src_frame->pts = timestamp_;
|
||||
timestamp_ += src_frame->nb_samples;
|
||||
|
||||
if (av_frame_get_buffer(src_frame, 0) < 0) {
|
||||
qCritical() << "Failed to allocate buffer for source frame";
|
||||
av_frame_free(&src_frame);
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy buffer from data array to frame
|
||||
memcpy(src_frame->data[0], packed, packed.size());
|
||||
|
||||
int ret = av_buffersrc_add_frame_flags(buffersrc_ctx_, src_frame, AV_BUFFERSRC_FLAG_KEEP_REF);
|
||||
|
||||
if (ret < 0) {
|
||||
qCritical() << "Failed to feed buffer source" << ret;
|
||||
}
|
||||
|
||||
if (src_frame) {
|
||||
av_frame_free(&src_frame);
|
||||
}
|
||||
}
|
||||
|
||||
void TempoProcessor::Flush()
|
||||
{
|
||||
if (!flushed_) {
|
||||
int ret = av_buffersrc_add_frame_flags(buffersrc_ctx_, nullptr, AV_BUFFERSRC_FLAG_KEEP_REF);
|
||||
if (ret < 0) {
|
||||
qCritical() << "Failed to feed buffer source" << ret;
|
||||
}
|
||||
flushed_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray TempoProcessor::Pull()
|
||||
{
|
||||
QByteArray b;
|
||||
AVFrame *processed_frame = av_frame_alloc();
|
||||
|
||||
// Try to pull samples from the buffersink
|
||||
int ret = av_buffersink_get_frame(buffersink_ctx_, processed_frame);
|
||||
|
||||
if (ret < 0) {
|
||||
// We couldn't pull for some reason, if the error was EAGAIN, we just need to send more samples. Otherwise the
|
||||
// error might be fatal...
|
||||
if (ret != AVERROR(EAGAIN)) {
|
||||
qCritical() << "Failed to pull from buffersink" << ret;
|
||||
}
|
||||
|
||||
av_frame_free(&processed_frame);
|
||||
return b;
|
||||
}
|
||||
|
||||
b.resize(params_.samples_to_bytes(processed_frame->nb_samples));
|
||||
|
||||
// Copy the bytes
|
||||
memcpy(b.data(), processed_frame->data[0], b.size());
|
||||
|
||||
// If the index has reached the limit of this processed frame, we can dispose of the frame now
|
||||
av_frame_free(&processed_frame);
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
void TempoProcessor::Close()
|
||||
{
|
||||
open_ = false;
|
||||
|
||||
if (filter_graph_) {
|
||||
avfilter_graph_free(&filter_graph_);
|
||||
filter_graph_ = nullptr;
|
||||
}
|
||||
|
||||
buffersrc_ctx_ = nullptr;
|
||||
buffersink_ctx_ = nullptr;
|
||||
}
|
||||
|
||||
AVFilterContext *TempoProcessor::CreateTempoFilter(AVFilterGraph* graph, AVFilterContext* link, const double &tempo)
|
||||
{
|
||||
// Set up tempo param, which is taken as a C string
|
||||
char speed_param[20];
|
||||
snprintf(speed_param, 20, "%f", tempo);
|
||||
|
||||
AVFilterContext* tempo_ctx = nullptr;
|
||||
|
||||
if (avfilter_graph_create_filter(&tempo_ctx, avfilter_get_by_name("atempo"), "atempo", speed_param, nullptr, graph) >= 0
|
||||
&& avfilter_link(link, 0, tempo_ctx, 0) == 0) {
|
||||
return tempo_ctx;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -109,7 +109,7 @@ FramePtr Decoder::RetrieveVideo(const rational &timecode, const RetrieveVideoPar
|
||||
return RetrieveVideoInternal(timecode, divider, cancelled);
|
||||
}
|
||||
|
||||
Decoder::RetrieveAudioStatus Decoder::RetrieveAudio(SampleBufferPtr dest, const TimeRange &range, const AudioParams ¶ms, const QString& cache_path, Footage::LoopMode loop_mode, RenderMode::Mode mode)
|
||||
Decoder::RetrieveAudioStatus Decoder::RetrieveAudio(SampleBuffer &dest, const TimeRange &range, const AudioParams ¶ms, const QString& cache_path, Footage::LoopMode loop_mode, RenderMode::Mode mode)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
@@ -272,14 +272,14 @@ bool Decoder::ConformAudioInternal(const QVector<QString> &filenames, const Audi
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Decoder::RetrieveAudioFromConform(SampleBufferPtr sample_buffer, const QVector<QString> &conform_filenames, const TimeRange& range, Footage::LoopMode loop_mode, const AudioParams &input_params)
|
||||
bool Decoder::RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector<QString> &conform_filenames, const TimeRange& range, Footage::LoopMode loop_mode, const AudioParams &input_params)
|
||||
{
|
||||
PlanarFileDevice input;
|
||||
if (input.open(conform_filenames, QFile::ReadOnly)) {
|
||||
qint64 read_index = input_params.time_to_bytes(range.in()) / input_params.channel_count();
|
||||
qint64 write_index = 0;
|
||||
|
||||
const qint64 buffer_length_in_bytes = sample_buffer->sample_count() * input_params.bytes_per_sample_per_channel();
|
||||
const qint64 buffer_length_in_bytes = sample_buffer.sample_count() * input_params.bytes_per_sample_per_channel();
|
||||
|
||||
while (write_index < buffer_length_in_bytes) {
|
||||
if (loop_mode == Footage::kLoopModeLoop) {
|
||||
@@ -297,15 +297,15 @@ bool Decoder::RetrieveAudioFromConform(SampleBufferPtr sample_buffer, const QVec
|
||||
if (read_index < 0) {
|
||||
// Reading before 0, write silence here until audio data would actually start
|
||||
write_count = qMin(-read_index, buffer_length_in_bytes);
|
||||
sample_buffer->silence_bytes(write_index, write_index + write_count);
|
||||
sample_buffer.silence_bytes(write_index, write_index + write_count);
|
||||
} else if (read_index >= input.size()) {
|
||||
// Reading after data length, write silence until the end of the buffer
|
||||
write_count = buffer_length_in_bytes - write_index;
|
||||
sample_buffer->silence_bytes(write_index, write_index + write_count);
|
||||
sample_buffer.silence_bytes(write_index, write_index + write_count);
|
||||
} else {
|
||||
write_count = qMin(input.size() - read_index, buffer_length_in_bytes - write_index);
|
||||
input.seek(read_index);
|
||||
input.read(reinterpret_cast<char**>(sample_buffer->to_raw_ptrs()), write_count, write_index);
|
||||
input.read(reinterpret_cast<char**>(sample_buffer.to_raw_ptrs().data()), write_count, write_index);
|
||||
}
|
||||
|
||||
read_index += write_count;
|
||||
|
||||
+2
-2
@@ -201,7 +201,7 @@ public:
|
||||
*
|
||||
* This function is thread safe and can only run while the decoder is open. \see Open()
|
||||
*/
|
||||
RetrieveAudioStatus RetrieveAudio(SampleBufferPtr dest, const TimeRange& range, const AudioParams& params, const QString &cache_path, Footage::LoopMode loop_mode, RenderMode::Mode mode);
|
||||
RetrieveAudioStatus RetrieveAudio(SampleBuffer &dest, const TimeRange& range, const AudioParams& params, const QString &cache_path, Footage::LoopMode loop_mode, RenderMode::Mode mode);
|
||||
|
||||
/**
|
||||
* @brief Determine the last time this decoder instance was used in any way
|
||||
@@ -307,7 +307,7 @@ signals:
|
||||
private:
|
||||
void UpdateLastAccessed();
|
||||
|
||||
bool RetrieveAudioFromConform(SampleBufferPtr sample_buffer, const QVector<QString> &conform_filenames, const TimeRange &range, Footage::LoopMode loop_mode, const AudioParams ¶ms);
|
||||
bool RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector<QString> &conform_filenames, const TimeRange &range, Footage::LoopMode loop_mode, const AudioParams ¶ms);
|
||||
|
||||
CodecStream stream_;
|
||||
|
||||
|
||||
+1
-1
@@ -183,7 +183,7 @@ public slots:
|
||||
virtual bool Open() = 0;
|
||||
|
||||
virtual bool WriteFrame(olive::FramePtr frame, olive::rational time) = 0;
|
||||
virtual bool WriteAudio(olive::SampleBufferPtr audio) = 0;
|
||||
virtual bool WriteAudio(const olive::SampleBuffer &audio) = 0;
|
||||
virtual bool WriteSubtitle(const SubtitleBlock *sub_block) = 0;
|
||||
|
||||
virtual void Close() = 0;
|
||||
|
||||
@@ -515,7 +515,7 @@ bool FFmpegDecoder::ConformAudioInternal(const QVector<QString> &filenames, cons
|
||||
|
||||
// Resample audio to our destination parameters
|
||||
nb_samples = swr_convert(resampler,
|
||||
reinterpret_cast<uint8_t**>(data.to_raw_ptrs()),
|
||||
reinterpret_cast<uint8_t**>(data.to_raw_ptrs().data()),
|
||||
nb_samples,
|
||||
const_cast<const uint8_t**>(frame->data),
|
||||
frame->nb_samples);
|
||||
@@ -526,7 +526,7 @@ bool FFmpegDecoder::ConformAudioInternal(const QVector<QString> &filenames, cons
|
||||
nb_bytes_per_channel = params.samples_to_bytes(nb_samples) / nb_channels;
|
||||
|
||||
// Write to files
|
||||
wave_out.write(const_cast<const char**>(reinterpret_cast<char**>(data.to_raw_ptrs())), nb_bytes_per_channel);
|
||||
wave_out.write(const_cast<const char**>(reinterpret_cast<char**>(data.to_raw_ptrs().data())), nb_bytes_per_channel);
|
||||
}
|
||||
|
||||
// Free buffer
|
||||
|
||||
@@ -266,26 +266,26 @@ fail:
|
||||
return success;
|
||||
}
|
||||
|
||||
bool FFmpegEncoder::WriteAudio(SampleBufferPtr audio)
|
||||
bool FFmpegEncoder::WriteAudio(const SampleBuffer &audio)
|
||||
{
|
||||
bool result = true;
|
||||
|
||||
// Create input buffer
|
||||
int input_sample_count = 0;
|
||||
uint8_t** input_data = nullptr;
|
||||
if (audio) {
|
||||
input_sample_count = audio->sample_count();
|
||||
if (audio.is_allocated()) {
|
||||
input_sample_count = audio.sample_count();
|
||||
int input_linesize;
|
||||
|
||||
av_samples_alloc_array_and_samples(&input_data, &input_linesize, audio->audio_params().channel_count(),
|
||||
input_sample_count, FFmpegUtils::GetFFmpegSampleFormat(audio->audio_params().format()), 0);
|
||||
av_samples_alloc_array_and_samples(&input_data, &input_linesize, audio.audio_params().channel_count(),
|
||||
input_sample_count, FFmpegUtils::GetFFmpegSampleFormat(audio.audio_params().format()), 0);
|
||||
|
||||
for (int i=0; i<audio->audio_params().channel_count(); i++) {
|
||||
memcpy(input_data[i], audio->data(i), input_sample_count * audio->audio_params().bytes_per_sample_per_channel());
|
||||
for (int i=0; i<audio.audio_params().channel_count(); i++) {
|
||||
memcpy(input_data[i], audio.data(i), input_sample_count * audio.audio_params().bytes_per_sample_per_channel());
|
||||
}
|
||||
}
|
||||
|
||||
result = WriteAudioData(audio->audio_params(), const_cast<const uint8_t**>(input_data), input_sample_count);
|
||||
result = WriteAudioData(audio.audio_params().is_valid() ? audio.audio_params() : params().audio_params(), const_cast<const uint8_t**>(input_data), input_sample_count);
|
||||
|
||||
if (input_data) {
|
||||
av_freep(&input_data[0]);
|
||||
@@ -774,7 +774,7 @@ void FFmpegEncoder::FlushEncoders()
|
||||
}
|
||||
|
||||
if (audio_codec_ctx_) {
|
||||
WriteAudio(nullptr);
|
||||
WriteAudio(SampleBuffer());
|
||||
|
||||
FlushCodecCtx(audio_codec_ctx_, audio_stream_);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public:
|
||||
|
||||
virtual bool WriteFrame(olive::FramePtr frame, olive::rational time) override;
|
||||
|
||||
virtual bool WriteAudio(olive::SampleBufferPtr audio) override;
|
||||
virtual bool WriteAudio(const olive::SampleBuffer &audio) override;
|
||||
|
||||
bool WriteAudioData(const AudioParams &audio_params, const uint8_t **data, int input_sample_count);
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ bool OIIOEncoder::WriteFrame(FramePtr frame, rational time)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OIIOEncoder::WriteAudio(SampleBufferPtr audio)
|
||||
bool OIIOEncoder::WriteAudio(const SampleBuffer &audio)
|
||||
{
|
||||
// Do nothing
|
||||
return false;
|
||||
|
||||
@@ -35,7 +35,7 @@ public slots:
|
||||
virtual bool Open() override;
|
||||
|
||||
virtual bool WriteFrame(olive::FramePtr frame, olive::rational time) override;
|
||||
virtual bool WriteAudio(SampleBufferPtr audio) override;
|
||||
virtual bool WriteAudio(const SampleBuffer &audio) override;
|
||||
virtual bool WriteSubtitle(const SubtitleBlock *sub_block) override;
|
||||
|
||||
virtual void Close() override;
|
||||
|
||||
+28
-33
@@ -20,6 +20,8 @@
|
||||
|
||||
#include "samplebuffer.h"
|
||||
|
||||
#include "common/cpuoptimize.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
SampleBuffer::SampleBuffer() :
|
||||
@@ -27,25 +29,18 @@ SampleBuffer::SampleBuffer() :
|
||||
{
|
||||
}
|
||||
|
||||
SampleBufferPtr SampleBuffer::Create()
|
||||
SampleBuffer::SampleBuffer(const AudioParams &audio_params, const rational &length) :
|
||||
audio_params_(audio_params)
|
||||
{
|
||||
return std::make_shared<SampleBuffer>();
|
||||
sample_count_per_channel_ = audio_params_.time_to_samples(length);
|
||||
allocate();
|
||||
}
|
||||
|
||||
SampleBufferPtr SampleBuffer::CreateAllocated(const AudioParams &audio_params, const rational &length)
|
||||
SampleBuffer::SampleBuffer(const AudioParams &audio_params, int samples_per_channel) :
|
||||
audio_params_(audio_params),
|
||||
sample_count_per_channel_(samples_per_channel)
|
||||
{
|
||||
return CreateAllocated(audio_params, audio_params.time_to_samples(length));
|
||||
}
|
||||
|
||||
SampleBufferPtr SampleBuffer::CreateAllocated(const AudioParams &audio_params, int samples_per_channel)
|
||||
{
|
||||
SampleBufferPtr buffer = Create();
|
||||
|
||||
buffer->set_audio_params(audio_params);
|
||||
buffer->set_sample_count(samples_per_channel);
|
||||
buffer->allocate();
|
||||
|
||||
return buffer;
|
||||
allocate();
|
||||
}
|
||||
|
||||
const AudioParams &SampleBuffer::audio_params() const
|
||||
@@ -104,14 +99,11 @@ void SampleBuffer::allocate()
|
||||
for (int i=0; i<audio_params_.channel_count(); i++) {
|
||||
data_[i].resize(sample_count_per_channel_);
|
||||
}
|
||||
|
||||
update_raw();
|
||||
}
|
||||
|
||||
void SampleBuffer::destroy()
|
||||
{
|
||||
data_.clear();
|
||||
raw_ptrs_.clear();
|
||||
}
|
||||
|
||||
void SampleBuffer::reverse()
|
||||
@@ -157,29 +149,40 @@ void SampleBuffer::speed(double speed)
|
||||
}
|
||||
|
||||
data_ = output_data;
|
||||
update_raw();
|
||||
}
|
||||
|
||||
void SampleBuffer::transform_volume(float f)
|
||||
{
|
||||
for (int i=0;i<audio_params().channel_count();i++) {
|
||||
for (int j=0;j<sample_count_per_channel_;j++) {
|
||||
data_[i][j] *= f;
|
||||
}
|
||||
transform_volume_for_channel(i, f);
|
||||
}
|
||||
}
|
||||
|
||||
void SampleBuffer::transform_volume_for_channel(int channel, float volume)
|
||||
{
|
||||
for (int i=0;i<sample_count_per_channel_;i++) {
|
||||
data_[channel][i] *= volume;
|
||||
float *cdat = data_[channel].data();
|
||||
int unopt_start = 0;
|
||||
|
||||
#if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM)
|
||||
__m128 mult = _mm_load1_ps(&volume);
|
||||
unopt_start = (sample_count_per_channel_ / 4) * 4;
|
||||
for (int j=0; j<unopt_start; j+=4) {
|
||||
float *here = cdat + j;
|
||||
__m128 samples = _mm_loadu_ps(here);
|
||||
__m128 multiplied = _mm_mul_ps(samples, mult);
|
||||
_mm_storeu_ps(here, multiplied);
|
||||
}
|
||||
#endif
|
||||
|
||||
for (int j=unopt_start; j<sample_count_per_channel_; j++) {
|
||||
cdat[j] *= volume;
|
||||
}
|
||||
}
|
||||
|
||||
void SampleBuffer::transform_volume_for_sample(int sample_index, float volume)
|
||||
{
|
||||
for (int i=0;i<audio_params().channel_count();i++) {
|
||||
data_[i][sample_index] *= volume;
|
||||
transform_volume_for_sample_on_channel(sample_index, i, volume);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,12 +223,4 @@ void SampleBuffer::set(int channel, const float *data, int sample_offset, int sa
|
||||
memcpy(&data_[channel].data()[sample_offset], data, sizeof(float) * sample_length);
|
||||
}
|
||||
|
||||
void SampleBuffer::update_raw()
|
||||
{
|
||||
raw_ptrs_.resize(data_.size());
|
||||
for (int i=0; i<raw_ptrs_.size(); i++) {
|
||||
raw_ptrs_[i] = data_[i].data();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,9 +27,6 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
class SampleBuffer;
|
||||
using SampleBufferPtr = std::shared_ptr<SampleBuffer>;
|
||||
|
||||
/**
|
||||
* @brief A buffer of audio samples
|
||||
*
|
||||
@@ -42,12 +39,8 @@ class SampleBuffer
|
||||
{
|
||||
public:
|
||||
SampleBuffer();
|
||||
|
||||
static SampleBufferPtr Create();
|
||||
static SampleBufferPtr CreateAllocated(const AudioParams& audio_params, const rational& length);
|
||||
static SampleBufferPtr CreateAllocated(const AudioParams& audio_params, int samples_per_channel);
|
||||
|
||||
DISABLE_COPY_MOVE(SampleBuffer)
|
||||
SampleBuffer(const AudioParams& audio_params, const rational& length);
|
||||
SampleBuffer(const AudioParams& audio_params, int samples_per_channel);
|
||||
|
||||
const AudioParams& audio_params() const;
|
||||
void set_audio_params(const AudioParams& params);
|
||||
@@ -69,9 +62,13 @@ public:
|
||||
return data_.at(channel).constData();
|
||||
}
|
||||
|
||||
float **to_raw_ptrs()
|
||||
QVector<float *> to_raw_ptrs()
|
||||
{
|
||||
return raw_ptrs_.data();
|
||||
QVector<float *> r(data_.size());
|
||||
for (int i=0; i<r.size(); i++) {
|
||||
r[i] = data_[i].data();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
bool is_allocated() const;
|
||||
@@ -96,19 +93,16 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void update_raw();
|
||||
|
||||
AudioParams audio_params_;
|
||||
|
||||
int sample_count_per_channel_;
|
||||
|
||||
QVector< QVector<float> > data_;
|
||||
QVector<float*> raw_ptrs_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(olive::SampleBufferPtr)
|
||||
Q_DECLARE_METATYPE(olive::SampleBuffer)
|
||||
|
||||
#endif // SAMPLEBUFFER_H
|
||||
|
||||
@@ -79,4 +79,40 @@ QString QtUtils::GetFormattedDateTime(const QDateTime &dt)
|
||||
return dt.toString(Qt::TextDate);
|
||||
}
|
||||
|
||||
QStringList QtUtils::WordWrapString(const QString &s, const QFontMetrics &fm, int bounding_width)
|
||||
{
|
||||
QStringList list;
|
||||
|
||||
QStringList lines = s.split('\n');
|
||||
|
||||
// Iterate every line
|
||||
for (int i=0; i<lines.size(); i++) {
|
||||
QString this_line = lines.at(i);
|
||||
|
||||
while (this_line.size() > 1 && QFontMetricsWidth(fm, this_line) >= bounding_width) {
|
||||
for (int j=this_line.size()-1; j>=0; j--) {
|
||||
if (this_line.at(j).isSpace()) {
|
||||
QString chopped = this_line.left(j);
|
||||
if (QFontMetricsWidth(fm, chopped) < bounding_width) {
|
||||
list.append(chopped);
|
||||
|
||||
int k = j+1;
|
||||
while (k < this_line.size() && this_line.at(k).isSpace()) {
|
||||
k++;
|
||||
}
|
||||
this_line.remove(0, k);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this_line.isEmpty()) {
|
||||
list.append(this_line);
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -62,6 +62,8 @@ public:
|
||||
|
||||
static QString GetFormattedDateTime(const QDateTime &dt);
|
||||
|
||||
static QStringList WordWrapString(const QString &s, const QFontMetrics &fm, int bounding_width);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -121,6 +121,10 @@ void Config::SetDefaults()
|
||||
SetEntryInternal(QStringLiteral("AudioOutput"), NodeValue::kText, QString());
|
||||
SetEntryInternal(QStringLiteral("AudioInput"), NodeValue::kText, QString());
|
||||
|
||||
SetEntryInternal(QStringLiteral("AudioOutputSampleRate"), NodeValue::kInt, 48000);
|
||||
SetEntryInternal(QStringLiteral("AudioOutputChannelLayout"), NodeValue::kInt, AV_CH_LAYOUT_STEREO);
|
||||
SetEntryInternal(QStringLiteral("AudioOutputSampleFormat"), NodeValue::kInt, AudioParams::kFormatSigned16Packed);
|
||||
|
||||
SetEntryInternal(QStringLiteral("AudioRecordingFormat"), NodeValue::kInt, ExportFormat::kFormatWAV);
|
||||
SetEntryInternal(QStringLiteral("AudioRecordingCodec"), NodeValue::kInt, ExportCodec::kCodecPCM);
|
||||
SetEntryInternal(QStringLiteral("AudioRecordingSampleRate"), NodeValue::kInt, 48000);
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
namespace olive {
|
||||
|
||||
#define OLIVE_CONFIG(x) Config::Current()[QStringLiteral(x)]
|
||||
#define OLIVE_CONFIG_STR(x) Config::Current()[x]
|
||||
|
||||
class Config {
|
||||
public:
|
||||
|
||||
+7
-26
@@ -108,7 +108,7 @@ void Core::DeclareTypesForQt()
|
||||
qRegisterMetaType<NodeValueTable>();
|
||||
qRegisterMetaType<NodeValueDatabase>();
|
||||
qRegisterMetaType<FramePtr>();
|
||||
qRegisterMetaType<SampleBufferPtr>();
|
||||
qRegisterMetaType<SampleBuffer>();
|
||||
qRegisterMetaType<AudioParams>();
|
||||
qRegisterMetaType<NodeKeyframe::Type>();
|
||||
qRegisterMetaType<Decoder::RetrieveState>();
|
||||
@@ -794,7 +794,7 @@ void Core::StartGUI(bool full_screen)
|
||||
connect(this, &Core::ProjectClosed, main_window_, &MainWindow::ProjectClose);
|
||||
|
||||
// Start autorecovery timer using the config value as its interval
|
||||
SetAutorecoveryInterval(Config::Current()["AutorecoveryInterval"].toInt());
|
||||
SetAutorecoveryInterval(OLIVE_CONFIG("AutorecoveryInterval").toInt());
|
||||
connect(&autorecovery_timer_, &QTimer::timeout, this, &Core::SaveAutorecovery);
|
||||
autorecovery_timer_.start();
|
||||
|
||||
@@ -960,7 +960,7 @@ bool Core::RevertProjectInternal(Project *p, bool by_opening_existing)
|
||||
|
||||
void Core::SaveAutorecovery()
|
||||
{
|
||||
if (Config::Current()[QStringLiteral("AutorecoveryEnabled")].toBool()) {
|
||||
if (OLIVE_CONFIG("AutorecoveryEnabled").toBool()) {
|
||||
foreach (Project* p, open_projects_) {
|
||||
if (!p->has_autorecovery_been_saved()) {
|
||||
QDir project_autorecovery_dir(QDir(FileFunctions::GetAutoRecoveryRoot()).filePath(p->GetUuid().toString()));
|
||||
@@ -986,7 +986,7 @@ void Core::SaveAutorecovery()
|
||||
realname_file.close();
|
||||
}
|
||||
|
||||
int64_t max_recoveries_per_file = Config::Current()[QStringLiteral("AutorecoveryMaximum")].toLongLong();
|
||||
int64_t max_recoveries_per_file = OLIVE_CONFIG("AutorecoveryMaximum").toLongLong();
|
||||
|
||||
// Since we write an extra file, increment total allowed files by 1
|
||||
max_recoveries_per_file++;
|
||||
@@ -1075,12 +1075,12 @@ Folder *Core::GetSelectedFolderInActiveProject() const
|
||||
|
||||
Timecode::Display Core::GetTimecodeDisplay() const
|
||||
{
|
||||
return static_cast<Timecode::Display>(Config::Current()["TimecodeDisplay"].toInt());
|
||||
return static_cast<Timecode::Display>(OLIVE_CONFIG("TimecodeDisplay").toInt());
|
||||
}
|
||||
|
||||
void Core::SetTimecodeDisplay(Timecode::Display d)
|
||||
{
|
||||
Config::Current()["TimecodeDisplay"] = d;
|
||||
OLIVE_CONFIG("TimecodeDisplay") = d;
|
||||
|
||||
emit TimecodeDisplayChanged(d);
|
||||
}
|
||||
@@ -1202,7 +1202,7 @@ void Core::SetStartupLocale()
|
||||
}
|
||||
}
|
||||
|
||||
QString use_locale = Config::Current()[QStringLiteral("Language")].toString();
|
||||
QString use_locale = OLIVE_CONFIG("Language").toString();
|
||||
|
||||
if (use_locale.isEmpty()) {
|
||||
// No configured locale, auto-detect the system's locale
|
||||
@@ -1412,25 +1412,6 @@ int Core::CountFilesInFileList(const QFileInfoList &filenames)
|
||||
return file_count;
|
||||
}
|
||||
|
||||
QString GetRenderModePreferencePrefix(RenderMode::Mode mode, const QString &preference) {
|
||||
QString key;
|
||||
|
||||
key.append((mode == RenderMode::kOffline) ? QStringLiteral("Offline") : QStringLiteral("Online"));
|
||||
key.append(preference);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
QVariant Core::GetPreferenceForRenderMode(RenderMode::Mode mode, const QString &preference)
|
||||
{
|
||||
return Config::Current()[GetRenderModePreferencePrefix(mode, preference)];
|
||||
}
|
||||
|
||||
void Core::SetPreferenceForRenderMode(RenderMode::Mode mode, const QString &preference, const QVariant &value)
|
||||
{
|
||||
Config::Current()[GetRenderModePreferencePrefix(mode, preference)] = value;
|
||||
}
|
||||
|
||||
bool Core::LabelNodes(const QVector<Node *> &nodes, MultiUndoCommand *parent)
|
||||
{
|
||||
if (nodes.isEmpty()) {
|
||||
|
||||
@@ -247,9 +247,6 @@ public:
|
||||
*/
|
||||
static int CountFilesInFileList(const QFileInfoList &filenames);
|
||||
|
||||
static QVariant GetPreferenceForRenderMode(RenderMode::Mode mode, const QString& preference);
|
||||
static void SetPreferenceForRenderMode(RenderMode::Mode mode, const QString& preference, const QVariant& value);
|
||||
|
||||
/**
|
||||
* @brief Show a dialog to the user to rename a set of nodes
|
||||
*/
|
||||
|
||||
@@ -132,7 +132,7 @@ AboutDialog::AboutDialog(bool welcome_dialog, QWidget *parent) :
|
||||
void AboutDialog::accept()
|
||||
{
|
||||
if (dont_show_again_checkbox_ && dont_show_again_checkbox_->isChecked()) {
|
||||
Config::Current()[QStringLiteral("ShowWelcomeDialog")] = false;
|
||||
OLIVE_CONFIG("ShowWelcomeDialog") = false;
|
||||
}
|
||||
|
||||
QDialog::accept();
|
||||
|
||||
@@ -38,8 +38,8 @@ public:
|
||||
|
||||
int GetValue() const;
|
||||
|
||||
static const int kDefaultH264CRF = 23;
|
||||
static const int kDefaultH265CRF = 28;
|
||||
static const int kDefaultH264CRF = 18;
|
||||
static const int kDefaultH265CRF = 23;
|
||||
|
||||
private:
|
||||
static const int kMinimumCRF = 0;
|
||||
|
||||
@@ -208,7 +208,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
|
||||
video_tab_->height_slider()->SetDefaultValue(vp.height());
|
||||
video_tab_->SetSelectedFrameRate(vp.frame_rate());
|
||||
video_tab_->pixel_aspect_combobox()->SetPixelAspectRatio(vp.pixel_aspect_ratio());
|
||||
video_tab_->pixel_format_field()->SetPixelFormat(static_cast<VideoParams::Format>(Config::Current()[QStringLiteral("OnlinePixelFormat")].toInt()));
|
||||
video_tab_->pixel_format_field()->SetPixelFormat(static_cast<VideoParams::Format>(OLIVE_CONFIG("OnlinePixelFormat").toInt()));
|
||||
video_tab_->interlaced_combobox()->SetInterlaceMode(vp.interlacing());
|
||||
audio_tab_->sample_rate_combobox()->SetSampleRate(ap.sample_rate());
|
||||
audio_tab_->sample_format_combobox()->SetAttemptToRestoreFormat(false);
|
||||
|
||||
@@ -74,7 +74,7 @@ PreferencesAppearanceTab::PreferencesAppearanceTab()
|
||||
color_layout->addWidget(new QLabel(cat_name), i, 0);
|
||||
|
||||
ColorCodingComboBox* ccc = new ColorCodingComboBox();
|
||||
ccc->SetColor(Config::Current()[QStringLiteral("CatColor%1").arg(i)].toInt());
|
||||
ccc->SetColor(OLIVE_CONFIG_STR(QStringLiteral("CatColor%1").arg(i)).toInt());
|
||||
color_layout->addWidget(ccc, i, 1);
|
||||
color_btns_.append(ccc);
|
||||
}
|
||||
@@ -92,7 +92,7 @@ PreferencesAppearanceTab::PreferencesAppearanceTab()
|
||||
marker_layout->addWidget(new QLabel("Default Marker Color"), 0, 0);
|
||||
|
||||
marker_btn_ = new ColorCodingComboBox();
|
||||
marker_btn_->SetColor(Config::Current()[QStringLiteral("MarkerColor")].toInt());
|
||||
marker_btn_->SetColor(OLIVE_CONFIG("MarkerColor").toInt());
|
||||
marker_layout->addWidget(marker_btn_, 0, 1);
|
||||
|
||||
appearance_layout->addWidget(marker_group, row, 0, 1, 2);
|
||||
@@ -109,14 +109,14 @@ void PreferencesAppearanceTab::Accept(MultiUndoCommand *command)
|
||||
|
||||
if (style_path != StyleManager::GetStyle()) {
|
||||
StyleManager::SetStyle(style_path);
|
||||
Config::Current()[QStringLiteral("Style")] = style_path;
|
||||
OLIVE_CONFIG("Style") = style_path;
|
||||
}
|
||||
|
||||
for (int i=0; i<color_btns_.size(); i++) {
|
||||
Config::Current()[QStringLiteral("CatColor%1").arg(i)] = color_btns_.at(i)->GetSelectedColor();
|
||||
OLIVE_CONFIG_STR(QStringLiteral("CatColor%1").arg(i)) = color_btns_.at(i)->GetSelectedColor();
|
||||
}
|
||||
|
||||
Config::Current()[QStringLiteral("MarkerColor")] = marker_btn_->GetSelectedColor();
|
||||
OLIVE_CONFIG("MarkerColor") = marker_btn_->GetSelectedColor();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -69,6 +69,40 @@ PreferencesAudioTab::PreferencesAudioTab()
|
||||
|
||||
audio_output_devices_ = new QComboBox();
|
||||
output_layout->addWidget(audio_output_devices_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
{
|
||||
int output_row = 0;
|
||||
|
||||
QGroupBox *output_param_group = new QGroupBox(tr("Advanced"));
|
||||
output_layout->addWidget(output_param_group, row, 0, 1, 2);
|
||||
|
||||
QGridLayout *output_param_layout = new QGridLayout(output_param_group);
|
||||
|
||||
output_param_layout->addWidget(new QLabel(tr("Sample Rate:")), output_row, 0);
|
||||
|
||||
output_rate_combo_ = new SampleRateComboBox();
|
||||
output_rate_combo_->SetSampleRate(OLIVE_CONFIG("AudioOutputSampleRate").toInt());
|
||||
output_param_layout->addWidget(output_rate_combo_, output_row, 1);
|
||||
|
||||
output_row++;
|
||||
|
||||
output_param_layout->addWidget(new QLabel(tr("Channel Layout:")), output_row, 0);
|
||||
|
||||
output_ch_layout_combo_ = new ChannelLayoutComboBox();
|
||||
output_ch_layout_combo_->SetChannelLayout(OLIVE_CONFIG("AudioOutputChannelLayout").toULongLong());
|
||||
output_param_layout->addWidget(output_ch_layout_combo_, output_row, 1);
|
||||
|
||||
output_row++;
|
||||
|
||||
output_param_layout->addWidget(new QLabel(tr("Sample Format:")), output_row, 0);
|
||||
|
||||
output_fmt_combo_ = new SampleFormatComboBox();
|
||||
output_fmt_combo_->SetPackedFormats();
|
||||
output_fmt_combo_->SetSampleFormat(static_cast<AudioParams::Format>(OLIVE_CONFIG("AudioOutputSampleFormat").toInt()));
|
||||
output_param_layout->addWidget(output_fmt_combo_, output_row, 1);
|
||||
}
|
||||
}
|
||||
|
||||
row = 0;
|
||||
@@ -146,12 +180,18 @@ void PreferencesAudioTab::Accept(MultiUndoCommand *command)
|
||||
AudioManager::instance()->SetOutputDevice(output_device);
|
||||
AudioManager::instance()->SetInputDevice(input_device);
|
||||
|
||||
OLIVE_CONFIG("AudioOutputSampleRate") = output_rate_combo_->GetSampleRate();
|
||||
OLIVE_CONFIG("AudioOutputChannelLayout") = QVariant::fromValue(output_ch_layout_combo_->GetChannelLayout());
|
||||
OLIVE_CONFIG("AudioOutputSampleFormat") = output_fmt_combo_->GetSampleFormat();
|
||||
|
||||
OLIVE_CONFIG("AudioRecordingFormat") = record_format_combo_->GetFormat();
|
||||
OLIVE_CONFIG("AudioRecordingCodec") = record_options_->GetCodec();
|
||||
OLIVE_CONFIG("AudioRecordingSampleRate") = record_options_->sample_rate_combobox()->GetSampleRate();
|
||||
OLIVE_CONFIG("AudioRecordingChannelLayout") = QVariant::fromValue(record_options_->channel_layout_combobox()->GetChannelLayout());
|
||||
OLIVE_CONFIG("AudioRecordingBitRate") = QVariant::fromValue(record_options_->bit_rate_slider()->GetValue());
|
||||
OLIVE_CONFIG("AudioRecordingSampleFormat") = record_options_->sample_format_combobox()->GetSampleFormat();
|
||||
|
||||
emit AudioManager::instance()->OutputParamsChanged();
|
||||
}
|
||||
|
||||
void PreferencesAudioTab::RefreshBackends()
|
||||
|
||||
@@ -61,6 +61,10 @@ private:
|
||||
*/
|
||||
QPushButton* refresh_devices_btn_;
|
||||
|
||||
SampleRateComboBox *output_rate_combo_;
|
||||
ChannelLayoutComboBox *output_ch_layout_combo_;
|
||||
SampleFormatComboBox *output_fmt_combo_;
|
||||
|
||||
ExportFormatComboBox *record_format_combo_;
|
||||
|
||||
ExportAudioTab *record_options_;
|
||||
|
||||
@@ -108,10 +108,8 @@ void PreferencesBehaviorTab::Accept(MultiUndoCommand *command)
|
||||
{
|
||||
Q_UNUSED(command)
|
||||
|
||||
QMap<QTreeWidgetItem*, QString>::const_iterator iterator;
|
||||
|
||||
for (iterator=config_map_.begin();iterator!=config_map_.end();iterator++) {
|
||||
Config::Current()[iterator.value()] = (iterator.key()->checkState(0) == Qt::Checked);
|
||||
for (auto iterator=config_map_.begin();iterator!=config_map_.end();iterator++) {
|
||||
OLIVE_CONFIG_STR(iterator.value()) = (iterator.key()->checkState(0) == Qt::Checked);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +117,7 @@ QTreeWidgetItem* PreferencesBehaviorTab::AddItem(const QString &text, const QStr
|
||||
{
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem({text});
|
||||
item->setToolTip(0, tooltip);
|
||||
item->setCheckState(0, Config::Current()[config_key].toBool() ? Qt::Checked : Qt::Unchecked);
|
||||
item->setCheckState(0, OLIVE_CONFIG_STR(config_key).toBool() ? Qt::Checked : Qt::Unchecked);
|
||||
|
||||
config_map_.insert(item, config_key);
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ PreferencesDiskTab::PreferencesDiskTab()
|
||||
cache_ahead_slider_ = new FloatSlider();
|
||||
cache_ahead_slider_->SetFormat(tr("%1 seconds"));
|
||||
cache_ahead_slider_->SetMinimum(0);
|
||||
cache_ahead_slider_->SetValue(Config::Current()["DiskCacheAhead"].value<rational>().toDouble());
|
||||
cache_ahead_slider_->SetValue(OLIVE_CONFIG("DiskCacheAhead").value<rational>().toDouble());
|
||||
cache_behavior_layout->addWidget(cache_ahead_slider_, row, 1);
|
||||
|
||||
cache_behavior_layout->addWidget(new QLabel(tr("Cache Behind:")), row, 2);
|
||||
@@ -79,7 +79,7 @@ PreferencesDiskTab::PreferencesDiskTab()
|
||||
cache_behind_slider_ = new FloatSlider();
|
||||
cache_behind_slider_->SetMinimum(0);
|
||||
cache_behind_slider_->SetFormat(tr("%1 seconds"));
|
||||
cache_behind_slider_->SetValue(Config::Current()["DiskCacheBehind"].value<rational>().toDouble());
|
||||
cache_behind_slider_->SetValue(OLIVE_CONFIG("DiskCacheBehind").value<rational>().toDouble());
|
||||
cache_behavior_layout->addWidget(cache_behind_slider_, row, 3);
|
||||
|
||||
outer_layout->addStretch();
|
||||
@@ -115,8 +115,8 @@ void PreferencesDiskTab::Accept(MultiUndoCommand *command)
|
||||
default_disk_cache_folder_->SetPath(disk_cache_location_->text());
|
||||
}
|
||||
|
||||
Config::Current()["DiskCacheBehind"] = QVariant::fromValue(rational::fromDouble(cache_behind_slider_->GetValue()));
|
||||
Config::Current()["DiskCacheAhead"] = QVariant::fromValue(rational::fromDouble(cache_ahead_slider_->GetValue()));
|
||||
OLIVE_CONFIG("DiskCacheBehind") = QVariant::fromValue(rational::fromDouble(cache_behind_slider_->GetValue()));
|
||||
OLIVE_CONFIG("DiskCacheAhead") = QVariant::fromValue(rational::fromDouble(cache_ahead_slider_->GetValue()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ PreferencesGeneralTab::PreferencesGeneralTab()
|
||||
AddLanguage(l);
|
||||
}
|
||||
|
||||
QString current_language = Config::Current()[QStringLiteral("Language")].toString();
|
||||
QString current_language = OLIVE_CONFIG("Language").toString();
|
||||
if (current_language.isEmpty()) {
|
||||
// No configured language, use system language
|
||||
current_language = QLocale::system().name();
|
||||
@@ -86,7 +86,7 @@ PreferencesGeneralTab::PreferencesGeneralTab()
|
||||
autoscroll_method_->addItem(tr("None"), AutoScroll::kNone);
|
||||
autoscroll_method_->addItem(tr("Page Scrolling"), AutoScroll::kPage);
|
||||
autoscroll_method_->addItem(tr("Smooth Scrolling"), AutoScroll::kSmooth);
|
||||
autoscroll_method_->setCurrentIndex(Config::Current()["Autoscroll"].toInt());
|
||||
autoscroll_method_->setCurrentIndex(OLIVE_CONFIG("Autoscroll").toInt());
|
||||
timeline_layout->addWidget(autoscroll_method_, row, 1);
|
||||
|
||||
row++;
|
||||
@@ -94,7 +94,7 @@ PreferencesGeneralTab::PreferencesGeneralTab()
|
||||
timeline_layout->addWidget(new QLabel(tr("Rectified Waveforms:")), row, 0);
|
||||
|
||||
rectified_waveforms_ = new QCheckBox();
|
||||
rectified_waveforms_->setChecked(Config::Current()["RectifiedWaveforms"].toBool());
|
||||
rectified_waveforms_->setChecked(OLIVE_CONFIG("RectifiedWaveforms").toBool());
|
||||
timeline_layout->addWidget(rectified_waveforms_, row, 1);
|
||||
|
||||
row++;
|
||||
@@ -105,7 +105,7 @@ PreferencesGeneralTab::PreferencesGeneralTab()
|
||||
default_still_length_->SetMinimum(rational(100, 1000));
|
||||
default_still_length_->SetTimebase(rational(100, 1000));
|
||||
default_still_length_->SetFormat(tr("%1 seconds"));
|
||||
default_still_length_->SetValue(Config::Current()["DefaultStillLength"].value<rational>());
|
||||
default_still_length_->SetValue(OLIVE_CONFIG("DefaultStillLength").value<rational>());
|
||||
timeline_layout->addWidget(default_still_length_);
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ PreferencesGeneralTab::PreferencesGeneralTab()
|
||||
autorecovery_layout->addWidget(new QLabel(tr("Enable Auto-Recovery:")), row, 0);
|
||||
|
||||
autorecovery_enabled_ = new QCheckBox();
|
||||
autorecovery_enabled_->setChecked(Config::Current()[QStringLiteral("AutorecoveryEnabled")].toBool());
|
||||
autorecovery_enabled_->setChecked(OLIVE_CONFIG("AutorecoveryEnabled").toBool());
|
||||
autorecovery_layout->addWidget(autorecovery_enabled_, row, 1);
|
||||
|
||||
row++;
|
||||
@@ -130,7 +130,7 @@ PreferencesGeneralTab::PreferencesGeneralTab()
|
||||
autorecovery_interval_->SetMinimum(1);
|
||||
autorecovery_interval_->SetMaximum(60);
|
||||
autorecovery_interval_->SetFormat(QT_TRANSLATE_N_NOOP("olive::SliderBase", "%n minute(s)"), true);
|
||||
autorecovery_interval_->SetValue(Config::Current()[QStringLiteral("AutorecoveryInterval")].toLongLong());
|
||||
autorecovery_interval_->SetValue(OLIVE_CONFIG("AutorecoveryInterval").toLongLong());
|
||||
autorecovery_layout->addWidget(autorecovery_interval_, row, 1);
|
||||
|
||||
row++;
|
||||
@@ -140,7 +140,7 @@ PreferencesGeneralTab::PreferencesGeneralTab()
|
||||
autorecovery_maximum_ = new IntegerSlider();
|
||||
autorecovery_maximum_->SetMinimum(1);
|
||||
autorecovery_maximum_->SetMaximum(1000);
|
||||
autorecovery_maximum_->SetValue(Config::Current()[QStringLiteral("AutorecoveryMaximum")].toLongLong());
|
||||
autorecovery_maximum_->SetValue(OLIVE_CONFIG("AutorecoveryMaximum").toLongLong());
|
||||
autorecovery_layout->addWidget(autorecovery_maximum_, row, 1);
|
||||
|
||||
row++;
|
||||
@@ -157,11 +157,11 @@ void PreferencesGeneralTab::Accept(MultiUndoCommand *command)
|
||||
{
|
||||
Q_UNUSED(command)
|
||||
|
||||
Config::Current()[QStringLiteral("RectifiedWaveforms")] = rectified_waveforms_->isChecked();
|
||||
OLIVE_CONFIG("RectifiedWaveforms") = rectified_waveforms_->isChecked();
|
||||
|
||||
Config::Current()[QStringLiteral("Autoscroll")] = autoscroll_method_->currentData();
|
||||
OLIVE_CONFIG("Autoscroll") = autoscroll_method_->currentData();
|
||||
|
||||
Config::Current()[QStringLiteral("DefaultStillLength")] = QVariant::fromValue(default_still_length_->GetValue());
|
||||
OLIVE_CONFIG("DefaultStillLength") = QVariant::fromValue(default_still_length_->GetValue());
|
||||
|
||||
QString set_language = language_combobox_->currentData().toString();
|
||||
if (QLocale::system().name() == set_language) {
|
||||
@@ -170,14 +170,14 @@ void PreferencesGeneralTab::Accept(MultiUndoCommand *command)
|
||||
}
|
||||
|
||||
// If the language has changed, set it now
|
||||
if (Config::Current()[QStringLiteral("Language")].toString() != set_language) {
|
||||
Config::Current()[QStringLiteral("Language")] = set_language;
|
||||
if (OLIVE_CONFIG("Language").toString() != set_language) {
|
||||
OLIVE_CONFIG("Language") = set_language;
|
||||
Core::instance()->SetLanguage(set_language.isEmpty() ? QLocale::system().name() : set_language);
|
||||
}
|
||||
|
||||
Config::Current()[QStringLiteral("AutorecoveryEnabled")] = autorecovery_enabled_->isChecked();
|
||||
Config::Current()[QStringLiteral("AutorecoveryInterval")] = QVariant::fromValue(autorecovery_interval_->GetValue());
|
||||
Config::Current()[QStringLiteral("AutorecoveryMaximum")] = QVariant::fromValue(autorecovery_maximum_->GetValue());
|
||||
OLIVE_CONFIG("AutorecoveryEnabled") = autorecovery_enabled_->isChecked();
|
||||
OLIVE_CONFIG("AutorecoveryInterval") = QVariant::fromValue(autorecovery_interval_->GetValue());
|
||||
OLIVE_CONFIG("AutorecoveryMaximum") = QVariant::fromValue(autorecovery_maximum_->GetValue());
|
||||
Core::instance()->SetAutorecoveryInterval(autorecovery_interval_->GetValue());
|
||||
}
|
||||
|
||||
|
||||
@@ -148,14 +148,14 @@ void SequenceDialog::SetAsDefaultClicked()
|
||||
tr("Are you sure you want to set the current parameters as defaults?"),
|
||||
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
|
||||
// Maybe replace with Preset system
|
||||
Config::Current()[QStringLiteral("DefaultSequenceWidth")] = parameter_tab_->GetSelectedVideoWidth();
|
||||
Config::Current()[QStringLiteral("DefaultSequenceHeight")] = parameter_tab_->GetSelectedVideoHeight();
|
||||
Config::Current()[QStringLiteral("DefaultSequencePixelAspect")] = QVariant::fromValue(parameter_tab_->GetSelectedVideoPixelAspect());
|
||||
Config::Current()[QStringLiteral("DefaultSequenceFrameRate")] = QVariant::fromValue(parameter_tab_->GetSelectedVideoFrameRate().flipped());
|
||||
Config::Current()[QStringLiteral("DefaultSequenceInterlacing")] = parameter_tab_->GetSelectedVideoInterlacingMode();
|
||||
Config::Current()[QStringLiteral("DefaultSequenceAudioFrequency")] = parameter_tab_->GetSelectedAudioSampleRate();
|
||||
Config::Current()[QStringLiteral("DefaultSequenceAudioLayout")] = QVariant::fromValue(parameter_tab_->GetSelectedAudioChannelLayout());
|
||||
Config::Current()[QStringLiteral("DefaultSequenceAutoCache")] = QVariant::fromValue(parameter_tab_->GetSelectedPreviewAutoCache());
|
||||
OLIVE_CONFIG("DefaultSequenceWidth") = parameter_tab_->GetSelectedVideoWidth();
|
||||
OLIVE_CONFIG("DefaultSequenceHeight") = parameter_tab_->GetSelectedVideoHeight();
|
||||
OLIVE_CONFIG("DefaultSequencePixelAspect") = QVariant::fromValue(parameter_tab_->GetSelectedVideoPixelAspect());
|
||||
OLIVE_CONFIG("DefaultSequenceFrameRate") = QVariant::fromValue(parameter_tab_->GetSelectedVideoFrameRate().flipped());
|
||||
OLIVE_CONFIG("DefaultSequenceInterlacing") = parameter_tab_->GetSelectedVideoInterlacingMode();
|
||||
OLIVE_CONFIG("DefaultSequenceAudioFrequency") = parameter_tab_->GetSelectedAudioSampleRate();
|
||||
OLIVE_CONFIG("DefaultSequenceAudioLayout") = QVariant::fromValue(parameter_tab_->GetSelectedAudioChannelLayout());
|
||||
OLIVE_CONFIG("DefaultSequenceAutoCache") = QVariant::fromValue(parameter_tab_->GetSelectedPreviewAutoCache());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,8 +100,8 @@ QTreeWidgetItem* SequenceDialogPresetTab::CreateFolder(const QString &name)
|
||||
|
||||
QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &name, int width, int height, int divider)
|
||||
{
|
||||
const VideoParams::Format default_format = static_cast<VideoParams::Format>(Config::Current()["OfflinePixelFormat"].toInt());
|
||||
const bool default_autocache = Config::Current()[QStringLiteral("DefaultSequenceAutoCache")].toBool();
|
||||
const VideoParams::Format default_format = static_cast<VideoParams::Format>(OLIVE_CONFIG("OfflinePixelFormat").toInt());
|
||||
const bool default_autocache = OLIVE_CONFIG("DefaultSequenceAutoCache").toBool();
|
||||
QTreeWidgetItem* parent = CreateFolder(name);
|
||||
AddStandardItem(parent, std::make_shared<SequencePreset>(tr("%1 23.976 FPS").arg(name),
|
||||
width,
|
||||
@@ -163,8 +163,8 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &na
|
||||
|
||||
QTreeWidgetItem *SequenceDialogPresetTab::CreateSDPresetFolder(const QString &name, int width, int height, const rational& frame_rate, const rational &standard_par, const rational &wide_par, int divider)
|
||||
{
|
||||
const VideoParams::Format default_format = static_cast<VideoParams::Format>(Config::Current()["OfflinePixelFormat"].toInt());
|
||||
const bool default_autocache = Config::Current()[QStringLiteral("DefaultSequenceAutoCache")].toBool();
|
||||
const VideoParams::Format default_format = static_cast<VideoParams::Format>(OLIVE_CONFIG("OfflinePixelFormat").toInt());
|
||||
const bool default_autocache = OLIVE_CONFIG("DefaultSequenceAutoCache").toBool();
|
||||
QTreeWidgetItem* parent = CreateFolder(name);
|
||||
preset_tree_->addTopLevelItem(parent);
|
||||
AddStandardItem(parent, std::make_shared<SequencePreset>(tr("%1 Standard").arg(name),
|
||||
|
||||
+19
-21
@@ -42,11 +42,6 @@ PanNode::PanNode()
|
||||
SetEffectInput(kSamplesInput);
|
||||
}
|
||||
|
||||
Node *PanNode::copy() const
|
||||
{
|
||||
return new PanNode();
|
||||
}
|
||||
|
||||
QString PanNode::Name() const
|
||||
{
|
||||
return tr("Pan");
|
||||
@@ -72,45 +67,48 @@ void PanNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV
|
||||
Q_UNUSED(globals)
|
||||
|
||||
// Create a sample job
|
||||
SampleJob job(kSamplesInput, value);
|
||||
|
||||
if (job.HasSamples()) {
|
||||
bool push_job = false;
|
||||
SampleBuffer samples = value[kSamplesInput].toSamples();
|
||||
if (samples.is_allocated()) {
|
||||
bool pushed_job = false;
|
||||
|
||||
// This node is only compatible with stereo audio
|
||||
if (job.samples()->audio_params().channel_count() == 2) {
|
||||
if (samples.audio_params().channel_count() == 2) {
|
||||
// If the input is static, we can just do it now which will be faster
|
||||
if (IsInputStatic(kPanningInput)) {
|
||||
float pan_volume = job.GetValue(kPanningInput).data().toFloat();
|
||||
float pan_volume = value[kPanningInput].toDouble();
|
||||
if (!qIsNull(pan_volume)) {
|
||||
if (pan_volume > 0) {
|
||||
job.samples()->transform_volume_for_channel(0, 1.0f - pan_volume);
|
||||
samples.transform_volume_for_channel(0, 1.0f - pan_volume);
|
||||
} else {
|
||||
job.samples()->transform_volume_for_channel(1, 1.0f + pan_volume);
|
||||
samples.transform_volume_for_channel(1, 1.0f + pan_volume);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Requires job
|
||||
push_job = true;
|
||||
|
||||
pushed_job = true;
|
||||
table->Push(NodeValue::kSamples, SampleJob(kSamplesInput, value), this);
|
||||
}
|
||||
}
|
||||
|
||||
table->Push(NodeValue::kSamples, push_job ? QVariant::fromValue(job) : QVariant::fromValue(job.samples()), this);
|
||||
if (!pushed_job) {
|
||||
table->Push(value[kSamplesInput]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PanNode::ProcessSamples(const NodeValueRow &values, const SampleBufferPtr input, SampleBufferPtr output, int index) const
|
||||
void PanNode::ProcessSamples(const NodeValueRow &values, const SampleBuffer &input, SampleBuffer &output, int index) const
|
||||
{
|
||||
float pan_val = values[kPanningInput].data().toFloat();
|
||||
float pan_val = values[kPanningInput].toDouble();
|
||||
|
||||
for (int i=0;i<input->audio_params().channel_count();i++) {
|
||||
output->data(i)[index] = input->data(i)[index];
|
||||
for (int i=0;i<input.audio_params().channel_count();i++) {
|
||||
output.data(i)[index] = input.data(i)[index];
|
||||
}
|
||||
|
||||
if (pan_val > 0) {
|
||||
output->data(0)[index] *= (1.0F - pan_val);
|
||||
output.data(0)[index] *= (1.0F - pan_val);
|
||||
} else if (pan_val < 0) {
|
||||
output->data(1)[index] *= (1.0F - qAbs(pan_val));
|
||||
output.data(1)[index] *= (1.0F - qAbs(pan_val));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,9 +31,7 @@ class PanNode : public Node
|
||||
public:
|
||||
PanNode();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(PanNode)
|
||||
|
||||
virtual Node* copy() const override;
|
||||
NODE_DEFAULT_FUNCTIONS(PanNode)
|
||||
|
||||
virtual QString Name() const override;
|
||||
virtual QString id() const override;
|
||||
@@ -42,7 +40,7 @@ public:
|
||||
|
||||
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
|
||||
|
||||
virtual void ProcessSamples(const NodeValueRow &values, const SampleBufferPtr input, SampleBufferPtr output, int index) const override;
|
||||
virtual void ProcessSamples(const NodeValueRow &values, const SampleBuffer &input, SampleBuffer &output, int index) const override;
|
||||
|
||||
virtual void Retranslate() override;
|
||||
|
||||
|
||||
@@ -41,11 +41,6 @@ VolumeNode::VolumeNode()
|
||||
SetEffectInput(kSamplesInput);
|
||||
}
|
||||
|
||||
Node *VolumeNode::copy() const
|
||||
{
|
||||
return new VolumeNode();
|
||||
}
|
||||
|
||||
QString VolumeNode::Name() const
|
||||
{
|
||||
return tr("Volume");
|
||||
@@ -68,17 +63,30 @@ QString VolumeNode::Description() const
|
||||
|
||||
void VolumeNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||
{
|
||||
return ValueInternal(kOpMultiply,
|
||||
kPairSampleNumber,
|
||||
kSamplesInput,
|
||||
value[kSamplesInput],
|
||||
kVolumeInput,
|
||||
value[kVolumeInput],
|
||||
globals,
|
||||
table);
|
||||
Q_UNUSED(globals)
|
||||
|
||||
// Create a sample job
|
||||
SampleBuffer buffer = value[kSamplesInput].toSamples();
|
||||
|
||||
if (buffer.is_allocated()) {
|
||||
// If the input is static, we can just do it now which will be faster
|
||||
if (IsInputStatic(kVolumeInput)) {
|
||||
auto volume = value[kVolumeInput].toDouble();
|
||||
|
||||
if (!qFuzzyCompare(volume, 1.0)) {
|
||||
buffer.transform_volume(volume);
|
||||
}
|
||||
|
||||
table->Push(NodeValue::kSamples, QVariant::fromValue(buffer), this);
|
||||
} else {
|
||||
// Requires job
|
||||
SampleJob job(kSamplesInput, value);
|
||||
table->Push(NodeValue::kSamples, QVariant::fromValue(job), this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VolumeNode::ProcessSamples(const NodeValueRow &values, const SampleBufferPtr input, SampleBufferPtr output, int index) const
|
||||
void VolumeNode::ProcessSamples(const NodeValueRow &values, const SampleBuffer &input, SampleBuffer &output, int index) const
|
||||
{
|
||||
return ProcessSamplesInternal(values, kOpMultiply, kSamplesInput, kVolumeInput, input, output, index);
|
||||
}
|
||||
|
||||
@@ -31,9 +31,7 @@ class VolumeNode : public MathNodeBase
|
||||
public:
|
||||
VolumeNode();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(VolumeNode)
|
||||
|
||||
virtual Node* copy() const override;
|
||||
NODE_DEFAULT_FUNCTIONS(VolumeNode)
|
||||
|
||||
virtual QString Name() const override;
|
||||
virtual QString id() const override;
|
||||
@@ -42,7 +40,7 @@ public:
|
||||
|
||||
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
|
||||
|
||||
virtual void ProcessSamples(const NodeValueRow &values, const SampleBufferPtr input, SampleBufferPtr output, int index) const override;
|
||||
virtual void ProcessSamples(const NodeValueRow &values, const SampleBuffer &input, SampleBuffer &output, int index) const override;
|
||||
|
||||
virtual void Retranslate() override;
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ Block::Block() :
|
||||
SetInputProperty(kLengthInput, QStringLiteral("viewlock"), true);
|
||||
IgnoreHashingFrom(kLengthInput);
|
||||
|
||||
SetInputFlags(kEnabledInput, InputFlags(GetInputFlags(kEnabledInput) | kInputFlagNotConnectable | kInputFlagNotKeyframable));
|
||||
|
||||
SetFlags(kDontShowInParamView);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,6 @@ class Block : public Node
|
||||
public:
|
||||
Block();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(Block)
|
||||
|
||||
virtual QVector<CategoryID> Category() const override;
|
||||
|
||||
const rational& in() const
|
||||
|
||||
@@ -61,11 +61,6 @@ ClipBlock::ClipBlock() :
|
||||
SetEffectInput(kBufferIn);
|
||||
}
|
||||
|
||||
Node *ClipBlock::copy() const
|
||||
{
|
||||
return new ClipBlock();
|
||||
}
|
||||
|
||||
QString ClipBlock::Name() const
|
||||
{
|
||||
if (track()) {
|
||||
@@ -289,6 +284,7 @@ void ClipBlock::Retranslate()
|
||||
SetInputName(kMediaInInput, tr("Media In"));
|
||||
SetInputName(kSpeedInput, tr("Speed"));
|
||||
SetInputName(kReverseInput, tr("Reverse"));
|
||||
SetInputName(kMaintainAudioPitchInput, tr("Maintain Audio Pitch"));
|
||||
}
|
||||
|
||||
void ClipBlock::Hash(QCryptographicHash &hash, const NodeGlobals &globals, const VideoParams &video_params) const
|
||||
|
||||
@@ -37,9 +37,7 @@ class ClipBlock : public Block
|
||||
public:
|
||||
ClipBlock();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(ClipBlock)
|
||||
|
||||
virtual Node* copy() const override;
|
||||
NODE_DEFAULT_FUNCTIONS(ClipBlock)
|
||||
|
||||
virtual QString Name() const override;
|
||||
virtual QString id() const override;
|
||||
|
||||
@@ -26,11 +26,6 @@ GapBlock::GapBlock()
|
||||
{
|
||||
}
|
||||
|
||||
Node *GapBlock::copy() const
|
||||
{
|
||||
return new GapBlock();
|
||||
}
|
||||
|
||||
QString GapBlock::Name() const
|
||||
{
|
||||
return tr("Gap");
|
||||
|
||||
@@ -34,9 +34,7 @@ class GapBlock : public Block
|
||||
public:
|
||||
GapBlock();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(GapBlock)
|
||||
|
||||
virtual Node * copy() const override;
|
||||
NODE_DEFAULT_FUNCTIONS(GapBlock)
|
||||
|
||||
virtual QString Name() const override;
|
||||
virtual QString id() const override;
|
||||
|
||||
@@ -29,11 +29,16 @@ const QString SubtitleBlock::kTextIn = QStringLiteral("text_in");
|
||||
SubtitleBlock::SubtitleBlock()
|
||||
{
|
||||
AddInput(kTextIn, NodeValue::kText, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
|
||||
}
|
||||
|
||||
Node *SubtitleBlock::copy() const
|
||||
{
|
||||
return new SubtitleBlock();
|
||||
SetInputFlags(kBufferIn, InputFlags(GetInputFlags(kBufferIn) | kInputFlagHidden));
|
||||
SetInputFlags(kLengthInput, InputFlags(GetInputFlags(kLengthInput) | kInputFlagHidden));
|
||||
SetInputFlags(kMediaInInput, InputFlags(GetInputFlags(kMediaInInput) | kInputFlagHidden));
|
||||
SetInputFlags(kSpeedInput, InputFlags(GetInputFlags(kSpeedInput) | kInputFlagHidden));
|
||||
SetInputFlags(kReverseInput, InputFlags(GetInputFlags(kReverseInput) | kInputFlagHidden));
|
||||
SetInputFlags(kMaintainAudioPitchInput, InputFlags(GetInputFlags(kMaintainAudioPitchInput) | kInputFlagHidden));
|
||||
|
||||
// Undo block flag that hides in param view
|
||||
SetFlags(GetFlags() & ~kDontShowInParamView);
|
||||
}
|
||||
|
||||
QString SubtitleBlock::Name() const
|
||||
|
||||
@@ -31,9 +31,7 @@ class SubtitleBlock : public ClipBlock
|
||||
public:
|
||||
SubtitleBlock();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(SubtitleBlock)
|
||||
|
||||
virtual Node* copy() const override;
|
||||
NODE_DEFAULT_FUNCTIONS(SubtitleBlock)
|
||||
|
||||
virtual QString Name() const override;
|
||||
virtual QString id() const override;
|
||||
|
||||
@@ -24,12 +24,6 @@ namespace olive {
|
||||
|
||||
CrossDissolveTransition::CrossDissolveTransition()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Node *CrossDissolveTransition::copy() const
|
||||
{
|
||||
return new CrossDissolveTransition();
|
||||
}
|
||||
|
||||
QString CrossDissolveTransition::Name() const
|
||||
@@ -52,9 +46,9 @@ QString CrossDissolveTransition::Description() const
|
||||
return tr("Smoothly transition between two clips.");
|
||||
}
|
||||
|
||||
ShaderCode CrossDissolveTransition::GetShaderCode(const QString &shader_id) const
|
||||
ShaderCode CrossDissolveTransition::GetShaderCode(const ShaderRequest &request) const
|
||||
{
|
||||
Q_UNUSED(shader_id)
|
||||
Q_UNUSED(request)
|
||||
|
||||
return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/crossdissolve.frag"), QString());
|
||||
}
|
||||
@@ -66,27 +60,27 @@ void CrossDissolveTransition::ShaderJobEvent(const NodeValueRow &value, ShaderJo
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
|
||||
}
|
||||
|
||||
void CrossDissolveTransition::SampleJobEvent(SampleBufferPtr from_samples, SampleBufferPtr to_samples, SampleBufferPtr out_samples, double time_in) const
|
||||
void CrossDissolveTransition::SampleJobEvent(const SampleBuffer &from_samples, const SampleBuffer &to_samples, SampleBuffer &out_samples, double time_in) const
|
||||
{
|
||||
for (int i=0; i<out_samples->sample_count(); i++) {
|
||||
double this_sample_time = out_samples->audio_params().samples_to_time(i).toDouble() + time_in;
|
||||
for (int i=0; i<out_samples.sample_count(); i++) {
|
||||
double this_sample_time = out_samples.audio_params().samples_to_time(i).toDouble() + time_in;
|
||||
double progress = GetTotalProgress(this_sample_time);
|
||||
|
||||
for (int j=0; j<out_samples->audio_params().channel_count(); j++) {
|
||||
out_samples->data(j)[i] = 0;
|
||||
for (int j=0; j<out_samples.audio_params().channel_count(); j++) {
|
||||
out_samples.data(j)[i] = 0;
|
||||
|
||||
if (from_samples) {
|
||||
if (i < from_samples->sample_count()) {
|
||||
out_samples->data(j)[i] += from_samples->data(j)[i] * TransformCurve(1.0 - progress);
|
||||
if (from_samples.is_allocated()) {
|
||||
if (i < from_samples.sample_count()) {
|
||||
out_samples.data(j)[i] += from_samples.data(j)[i] * TransformCurve(1.0 - progress);
|
||||
}
|
||||
}
|
||||
|
||||
if (to_samples) {
|
||||
if (to_samples.is_allocated()) {
|
||||
// Offset input samples from the end
|
||||
int in_index = i - (out_samples->sample_count() - to_samples->sample_count());
|
||||
int in_index = i - (out_samples.sample_count() - to_samples.sample_count());
|
||||
|
||||
if (in_index >= 0) {
|
||||
out_samples->data(j)[i] += to_samples->data(j)[in_index] * TransformCurve(progress);
|
||||
out_samples.data(j)[i] += to_samples.data(j)[in_index] * TransformCurve(progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,7 @@ class CrossDissolveTransition : public TransitionBlock
|
||||
public:
|
||||
CrossDissolveTransition();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(CrossDissolveTransition)
|
||||
|
||||
virtual Node* copy() const override;
|
||||
NODE_DEFAULT_FUNCTIONS(CrossDissolveTransition)
|
||||
|
||||
virtual QString Name() const override;
|
||||
virtual QString id() const override;
|
||||
@@ -42,12 +40,12 @@ public:
|
||||
|
||||
//virtual void Retranslate() override;
|
||||
|
||||
virtual ShaderCode GetShaderCode(const QString& shader_id) const override;
|
||||
virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override;
|
||||
|
||||
protected:
|
||||
virtual void ShaderJobEvent(const NodeValueRow &value, ShaderJob& job) const override;
|
||||
|
||||
virtual void SampleJobEvent(SampleBufferPtr from_samples, SampleBufferPtr to_samples, SampleBufferPtr out_samples, double time_in) const override;
|
||||
virtual void SampleJobEvent(const SampleBuffer &from_samples, const SampleBuffer &to_samples, SampleBuffer &out_samples, double time_in) const override;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -29,11 +29,6 @@ DipToColorTransition::DipToColorTransition()
|
||||
AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(0, 0, 0)));
|
||||
}
|
||||
|
||||
Node *DipToColorTransition::copy() const
|
||||
{
|
||||
return new DipToColorTransition();
|
||||
}
|
||||
|
||||
QString DipToColorTransition::Name() const
|
||||
{
|
||||
return tr("Dip To Color");
|
||||
@@ -54,16 +49,16 @@ QString DipToColorTransition::Description() const
|
||||
return tr("Transition between clips by dipping to a color.");
|
||||
}
|
||||
|
||||
ShaderCode DipToColorTransition::GetShaderCode(const QString &shader_id) const
|
||||
ShaderCode DipToColorTransition::GetShaderCode(const ShaderRequest &request) const
|
||||
{
|
||||
Q_UNUSED(shader_id)
|
||||
Q_UNUSED(request)
|
||||
|
||||
return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/diptoblack.frag"), QString());
|
||||
}
|
||||
|
||||
void DipToColorTransition::ShaderJobEvent(const NodeValueRow &value, ShaderJob &job) const
|
||||
{
|
||||
job.InsertValue(kColorInput, value);
|
||||
job.Insert(kColorInput, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,16 +31,14 @@ class DipToColorTransition : public TransitionBlock
|
||||
public:
|
||||
DipToColorTransition();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(DipToColorTransition)
|
||||
|
||||
virtual Node* copy() const override;
|
||||
NODE_DEFAULT_FUNCTIONS(DipToColorTransition)
|
||||
|
||||
virtual QString Name() const override;
|
||||
virtual QString id() const override;
|
||||
virtual QVector<CategoryID> Category() const override;
|
||||
virtual QString Description() const override;
|
||||
|
||||
virtual ShaderCode GetShaderCode(const QString& shader_id) const override;
|
||||
virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override;
|
||||
|
||||
static const QString kColorInput;
|
||||
|
||||
|
||||
@@ -162,15 +162,15 @@ double TransitionBlock::GetInternalTransitionTime(const double &time) const
|
||||
void TransitionBlock::InsertTransitionTimes(AcceleratedJob *job, const double &time) const
|
||||
{
|
||||
// Provides total transition progress from 0.0 (start) - 1.0 (end)
|
||||
job->InsertValue(QStringLiteral("ove_tprog_all"),
|
||||
job->Insert(QStringLiteral("ove_tprog_all"),
|
||||
NodeValue(NodeValue::kFloat, GetTotalProgress(time), this));
|
||||
|
||||
// Provides progress of out section from 1.0 (start) - 0.0 (end)
|
||||
job->InsertValue(QStringLiteral("ove_tprog_out"),
|
||||
job->Insert(QStringLiteral("ove_tprog_out"),
|
||||
NodeValue(NodeValue::kFloat, GetOutProgress(time), this));
|
||||
|
||||
// Provides progress of in section from 0.0 (start) - 1.0 (end)
|
||||
job->InsertValue(QStringLiteral("ove_tprog_in"),
|
||||
job->Insert(QStringLiteral("ove_tprog_in"),
|
||||
NodeValue(NodeValue::kFloat, GetInProgress(time), this));
|
||||
}
|
||||
|
||||
@@ -188,14 +188,14 @@ void TransitionBlock::Value(const NodeValueRow &value, const NodeGlobals &global
|
||||
ShaderJob job;
|
||||
|
||||
if (out_buffer.type() != NodeValue::kNone) {
|
||||
job.InsertValue(kOutBlockInput, out_buffer);
|
||||
job.Insert(kOutBlockInput, out_buffer);
|
||||
}
|
||||
|
||||
if (in_buffer.type() != NodeValue::kNone) {
|
||||
job.InsertValue(kInBlockInput, in_buffer);
|
||||
job.Insert(kInBlockInput, in_buffer);
|
||||
}
|
||||
|
||||
job.InsertValue(kCurveInput, value);
|
||||
job.Insert(kCurveInput, value);
|
||||
|
||||
double time = globals.time().in().toDouble();
|
||||
InsertTransitionTimes(&job, time);
|
||||
@@ -206,25 +206,22 @@ void TransitionBlock::Value(const NodeValueRow &value, const NodeGlobals &global
|
||||
push_job = QVariant::fromValue(job);
|
||||
} else if (data_type == NodeValue::kSamples) {
|
||||
// This must be an audio transition
|
||||
SampleBufferPtr from_samples = out_buffer.data().value<SampleBufferPtr>();
|
||||
SampleBufferPtr to_samples = in_buffer.data().value<SampleBufferPtr>();
|
||||
SampleBuffer from_samples = out_buffer.toSamples();
|
||||
SampleBuffer to_samples = in_buffer.toSamples();
|
||||
|
||||
if (from_samples || to_samples) {
|
||||
if (from_samples.is_allocated() || to_samples.is_allocated()) {
|
||||
double time_in = globals.time().in().toDouble();
|
||||
double time_out = globals.time().out().toDouble();
|
||||
|
||||
const AudioParams& params = (from_samples) ? from_samples->audio_params() : to_samples->audio_params();
|
||||
const AudioParams& params = (from_samples.is_allocated()) ? from_samples.audio_params() : to_samples.audio_params();
|
||||
|
||||
SampleBufferPtr out_samples;
|
||||
SampleBuffer out_samples;
|
||||
|
||||
if (params.is_valid()) {
|
||||
int nb_samples = params.time_to_samples(time_out - time_in);
|
||||
|
||||
out_samples = SampleBuffer::CreateAllocated(params, nb_samples);
|
||||
out_samples = SampleBuffer(params, nb_samples);
|
||||
SampleJobEvent(from_samples, to_samples, out_samples, time_in);
|
||||
} else {
|
||||
// Create dummy sample buffer
|
||||
out_samples = SampleBuffer::Create();
|
||||
}
|
||||
|
||||
job_type = NodeValue::kSamples;
|
||||
@@ -251,20 +248,6 @@ void TransitionBlock::InvalidateCache(const TimeRange &range, const QString &fro
|
||||
super::InvalidateCache(r, from, element, options);
|
||||
}
|
||||
|
||||
void TransitionBlock::ShaderJobEvent(const NodeValueRow &value, ShaderJob &job) const
|
||||
{
|
||||
Q_UNUSED(value)
|
||||
Q_UNUSED(job)
|
||||
}
|
||||
|
||||
void TransitionBlock::SampleJobEvent(SampleBufferPtr from_samples, SampleBufferPtr to_samples, SampleBufferPtr out_samples, double time_in) const
|
||||
{
|
||||
Q_UNUSED(from_samples)
|
||||
Q_UNUSED(to_samples)
|
||||
Q_UNUSED(out_samples)
|
||||
Q_UNUSED(time_in)
|
||||
}
|
||||
|
||||
double TransitionBlock::TransformCurve(double linear) const
|
||||
{
|
||||
switch (static_cast<CurveType>(GetStandardValue(kCurveInput).toInt())) {
|
||||
|
||||
@@ -33,8 +33,6 @@ class TransitionBlock : public Block
|
||||
public:
|
||||
TransitionBlock();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(TransitionBlock)
|
||||
|
||||
virtual void Retranslate() override;
|
||||
|
||||
rational in_offset() const;
|
||||
@@ -75,9 +73,9 @@ public:
|
||||
static const QString kCenterInput;
|
||||
|
||||
protected:
|
||||
virtual void ShaderJobEvent(const NodeValueRow &value, ShaderJob& job) const;
|
||||
virtual void ShaderJobEvent(const NodeValueRow &value, ShaderJob& job) const {}
|
||||
|
||||
virtual void SampleJobEvent(SampleBufferPtr from_samples, SampleBufferPtr to_samples, SampleBufferPtr out_samples, double time_in) const;
|
||||
virtual void SampleJobEvent(const SampleBuffer &from_samples, const SampleBuffer &to_samples, SampleBuffer &out_samples, double time_in) const {}
|
||||
|
||||
double TransformCurve(double linear) const;
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_subdirectory(colormanager)
|
||||
add_subdirectory(displaytransform)
|
||||
add_subdirectory(ociobase)
|
||||
add_subdirectory(ociogradingtransformlinear)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
|
||||
@@ -273,6 +273,7 @@ void ColorManager::InputValueChangedEvent(const QString &input, int element)
|
||||
|
||||
try {
|
||||
SetConfig(OCIO::Config::CreateFromFile(GetConfigFilename().toUtf8()));
|
||||
emit ConfigChanged();
|
||||
} catch (OCIO::Exception&) {}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ class ColorManager : public Node
|
||||
public:
|
||||
ColorManager();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(ColorManager)
|
||||
|
||||
virtual QString Name() const override
|
||||
{
|
||||
return tr("Color Manager");
|
||||
@@ -58,11 +60,6 @@ public:
|
||||
return tr("Color management configuration for project.");
|
||||
}
|
||||
|
||||
virtual Node* copy() const override
|
||||
{
|
||||
return new ColorManager();
|
||||
}
|
||||
|
||||
OCIO::ConstConfigRcPtr GetConfig() const;
|
||||
|
||||
static OCIO::ConstConfigRcPtr CreateConfigFromFile(const QString& filename);
|
||||
@@ -124,6 +121,9 @@ public:
|
||||
|
||||
virtual void Retranslate() override;
|
||||
|
||||
signals:
|
||||
void ConfigChanged();
|
||||
|
||||
protected:
|
||||
virtual void InputValueChangedEvent(const QString &input, int element) override;
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2021 Olive Team
|
||||
#
|
||||
# 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/color/displaytransform/displaytransform.cpp
|
||||
node/color/displaytransform/displaytransform.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,144 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
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 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "displaytransform.h"
|
||||
|
||||
#include "node/color/colormanager/colormanager.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
const QString DisplayTransformNode::kDisplayInput = QStringLiteral("display_in");
|
||||
const QString DisplayTransformNode::kViewInput = QStringLiteral("view_in");
|
||||
const QString DisplayTransformNode::kDirectionInput = QStringLiteral("dir_in");
|
||||
|
||||
#define super OCIOBaseNode
|
||||
|
||||
DisplayTransformNode::DisplayTransformNode()
|
||||
{
|
||||
AddInput(kDisplayInput, NodeValue::kCombo, 0, InputFlags(kInputFlagNotKeyframable | kInputFlagNotConnectable));
|
||||
|
||||
AddInput(kViewInput, NodeValue::kCombo, 0, InputFlags(kInputFlagNotKeyframable | kInputFlagNotConnectable));
|
||||
|
||||
AddInput(kDirectionInput, NodeValue::kCombo, 0, InputFlags(kInputFlagNotKeyframable | kInputFlagNotConnectable));
|
||||
}
|
||||
|
||||
QString DisplayTransformNode::Name() const
|
||||
{
|
||||
return tr("Display Transform");
|
||||
}
|
||||
|
||||
QString DisplayTransformNode::id() const
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.displaytransform");
|
||||
}
|
||||
|
||||
QVector<Node::CategoryID> DisplayTransformNode::Category() const
|
||||
{
|
||||
return {kCategoryColor};
|
||||
}
|
||||
|
||||
QString DisplayTransformNode::Description() const
|
||||
{
|
||||
return tr("Converts an image to or from a display color space.");
|
||||
}
|
||||
|
||||
void DisplayTransformNode::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kTextureInput, tr("Input"));
|
||||
SetInputName(kDisplayInput, tr("Display"));
|
||||
SetInputName(kViewInput, tr("View"));
|
||||
SetInputName(kDirectionInput, tr("Direction"));
|
||||
SetComboBoxStrings(kDirectionInput, {tr("Forward"), tr("Inverse")});
|
||||
}
|
||||
|
||||
void DisplayTransformNode::InputValueChangedEvent(const QString &input, int element)
|
||||
{
|
||||
Q_UNUSED(element);
|
||||
if (input == kDisplayInput || input == kDirectionInput || input == kViewInput) {
|
||||
if (input == kDisplayInput) {
|
||||
UpdateViews();
|
||||
}
|
||||
GenerateProcessor();
|
||||
}
|
||||
}
|
||||
|
||||
QString DisplayTransformNode::GetDisplay() const
|
||||
{
|
||||
if (manager()) {
|
||||
int index = GetStandardValue(kDisplayInput).toInt();
|
||||
if (index < manager()->ListAvailableDisplays().size()) {
|
||||
return manager()->ListAvailableDisplays().at(index);
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString DisplayTransformNode::GetView() const
|
||||
{
|
||||
if (manager()) {
|
||||
QString display = GetDisplay();
|
||||
if (!display.isEmpty()) {
|
||||
int index = GetStandardValue(kViewInput).toInt();
|
||||
QStringList views = manager()->ListAvailableViews(display);
|
||||
if (index < views.size()) {
|
||||
return views.at(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
ColorProcessor::Direction DisplayTransformNode::GetDirection() const
|
||||
{
|
||||
return static_cast<ColorProcessor::Direction>(GetStandardValue(kDirectionInput).toInt());;
|
||||
}
|
||||
|
||||
void DisplayTransformNode::UpdateDisplays()
|
||||
{
|
||||
if (manager()) {
|
||||
SetComboBoxStrings(kDisplayInput, manager()->ListAvailableDisplays());
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayTransformNode::UpdateViews()
|
||||
{
|
||||
if (manager()) {
|
||||
SetComboBoxStrings(kViewInput, manager()->ListAvailableViews(GetDisplay()));
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayTransformNode::ConfigChanged()
|
||||
{
|
||||
UpdateDisplays();
|
||||
UpdateViews();
|
||||
GenerateProcessor();
|
||||
}
|
||||
|
||||
void DisplayTransformNode::GenerateProcessor()
|
||||
{
|
||||
if (manager()) {
|
||||
ColorTransform transform(GetDisplay(), GetView(), QString());
|
||||
set_processor(ColorProcessor::Create(manager(), manager()->GetReferenceColorSpace(), transform, GetDirection()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
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 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef DISPLAYTRANSFORMNODE_H
|
||||
#define DISPLAYTRANSFORMNODE_H
|
||||
|
||||
#include "node/color/ociobase/ociobase.h"
|
||||
#include "render/colorprocessor.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class DisplayTransformNode : public OCIOBaseNode
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
DisplayTransformNode();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(DisplayTransformNode)
|
||||
|
||||
virtual QString Name() const override;
|
||||
virtual QString id() const override;
|
||||
virtual QVector<CategoryID> Category() const override;
|
||||
virtual QString Description() const override;
|
||||
|
||||
virtual void Retranslate() override;
|
||||
virtual void InputValueChangedEvent(const QString &input, int element) override;
|
||||
|
||||
QString GetDisplay() const;
|
||||
QString GetView() const;
|
||||
ColorProcessor::Direction GetDirection() const;
|
||||
|
||||
static const QString kDisplayInput;
|
||||
static const QString kViewInput;
|
||||
static const QString kDirectionInput;
|
||||
|
||||
protected slots:
|
||||
virtual void ConfigChanged() override;
|
||||
|
||||
private:
|
||||
void GenerateProcessor();
|
||||
|
||||
void UpdateDisplays();
|
||||
|
||||
void UpdateViews();
|
||||
|
||||
};
|
||||
|
||||
} // olive
|
||||
|
||||
#endif // DISPLAYTRANSFORMNODE_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2021 Olive Team
|
||||
#
|
||||
# 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/color/ociobase/ociobase.cpp
|
||||
node/color/ociobase/ociobase.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,69 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
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 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "ociobase.h"
|
||||
|
||||
#include "node/color/colormanager/colormanager.h"
|
||||
#include "node/project/project.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
const QString OCIOBaseNode::kTextureInput = QStringLiteral("tex_in");
|
||||
|
||||
OCIOBaseNode::OCIOBaseNode() :
|
||||
manager_(nullptr),
|
||||
processor_(nullptr)
|
||||
{
|
||||
AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
|
||||
|
||||
SetEffectInput(kTextureInput);
|
||||
|
||||
connect(this, &Node::AddedToGraph, this, &OCIOBaseNode::ParentChanged);
|
||||
|
||||
SetFlags(kVideoEffect);
|
||||
}
|
||||
|
||||
void OCIOBaseNode::ParentChanged(NodeGraph *graph)
|
||||
{
|
||||
if (manager_) {
|
||||
disconnect(manager_, &ColorManager::ConfigChanged, this, &OCIOBaseNode::ConfigChanged);
|
||||
manager_ = nullptr;
|
||||
}
|
||||
|
||||
if (Project *p = dynamic_cast<Project*>(graph)) {
|
||||
manager_ = p->color_manager();
|
||||
connect(manager_, &ColorManager::ConfigChanged, this, &OCIOBaseNode::ConfigChanged);
|
||||
ConfigChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void OCIOBaseNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||
{
|
||||
if (value[kTextureInput].toTexture() && processor_) {
|
||||
ColorTransformJob job;
|
||||
|
||||
job.SetColorProcessor(processor_);
|
||||
job.SetInputTexture(value[kTextureInput].toTexture());
|
||||
|
||||
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,45 +18,43 @@
|
||||
|
||||
***/
|
||||
|
||||
#ifndef PLANARPROCESSOR_H
|
||||
#define PLANARPROCESSOR_H
|
||||
#ifndef OCIOBASENODE_H
|
||||
#define OCIOBASENODE_H
|
||||
|
||||
extern "C" {
|
||||
#include <libswresample/swresample.h>
|
||||
}
|
||||
|
||||
#include "codec/samplebuffer.h"
|
||||
#include "render/audioparams.h"
|
||||
#include "node/node.h"
|
||||
#include "render/job/colortransformjob.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class PlanarProcessor
|
||||
class OCIOBaseNode : public Node
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
PlanarProcessor();
|
||||
OCIOBaseNode();
|
||||
|
||||
~PlanarProcessor();
|
||||
virtual void Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const override;
|
||||
|
||||
DISABLE_COPY_MOVE(PlanarProcessor)
|
||||
static const QString kTextureInput;
|
||||
|
||||
bool Open(const AudioParams ¶ms);
|
||||
protected slots:
|
||||
virtual void ConfigChanged() = 0;
|
||||
|
||||
SampleBufferPtr Convert(const QByteArray &packed);
|
||||
protected:
|
||||
ColorManager *manager() const { return manager_; }
|
||||
|
||||
void Close();
|
||||
|
||||
bool IsOpen() const
|
||||
{
|
||||
return swr_ctx_;
|
||||
}
|
||||
ColorProcessorPtr processor() const { return processor_; }
|
||||
void set_processor(ColorProcessorPtr p) { processor_ = p; }
|
||||
|
||||
private:
|
||||
SwrContext *swr_ctx_;
|
||||
ColorManager *manager_;
|
||||
|
||||
AudioParams params_;
|
||||
ColorProcessorPtr processor_;
|
||||
|
||||
private slots:
|
||||
void ParentChanged(olive::NodeGraph *graph);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // PLANARPROCESSOR_H
|
||||
#endif // OCIOBASENODE_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2021 Olive Team
|
||||
#
|
||||
# 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/color/ociogradingtransformlinear/ociogradingtransformlinear.cpp
|
||||
node/color/ociogradingtransformlinear/ociogradingtransformlinear.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,218 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
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 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "ociogradingtransformlinear.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "common/ocioutils.h"
|
||||
#include "node/project/project.h"
|
||||
#include "render/colorprocessor.h"
|
||||
#include "widget/slider/floatslider.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
const QString OCIOGradingTransformLinearNode::kContrastInput = QStringLiteral("ocio_grading_primary_contrast");
|
||||
const QString OCIOGradingTransformLinearNode::kOffsetInput = QStringLiteral("ocio_grading_primary_offset");
|
||||
const QString OCIOGradingTransformLinearNode::kExposureInput = QStringLiteral("ocio_grading_primary_exposure");
|
||||
const QString OCIOGradingTransformLinearNode::kSaturationInput = QStringLiteral("ocio_grading_primary_saturation");
|
||||
const QString OCIOGradingTransformLinearNode::kPivotInput = QStringLiteral("ocio_grading_primary_pivot");
|
||||
const QString OCIOGradingTransformLinearNode::kClampBlackEnableInput = QStringLiteral("clamp_black_enable_in");
|
||||
const QString OCIOGradingTransformLinearNode::kClampBlackInput = QStringLiteral("ocio_grading_primary_clampBlack");
|
||||
const QString OCIOGradingTransformLinearNode::kClampWhiteEnableInput = QStringLiteral("clamp_white_enable_in");
|
||||
const QString OCIOGradingTransformLinearNode::kClampWhiteInput = QStringLiteral("ocio_grading_primary_clampWhite");
|
||||
|
||||
#define super OCIOBaseNode
|
||||
|
||||
OCIOGradingTransformLinearNode::OCIOGradingTransformLinearNode()
|
||||
{
|
||||
AddInput(kContrastInput, NodeValue::kVec4, QVector4D{1.0, 1.0, 1.0, 1.0});
|
||||
// Minimum based on OCIO::GradingPrimary::validate
|
||||
SetInputProperty(kContrastInput, QStringLiteral("min"), QVector4D{0.01f, 0.01f, 0.01f, 0.01f});
|
||||
SetInputProperty(kContrastInput, QStringLiteral("base"), 0.01);
|
||||
SetVec4InputColors(kContrastInput);
|
||||
|
||||
AddInput(kOffsetInput, NodeValue::kVec4, QVector4D{0.0, 0.0, 0.0, 0.0});
|
||||
SetInputProperty(kOffsetInput, QStringLiteral("base"), 0.01);
|
||||
SetVec4InputColors(kOffsetInput);
|
||||
|
||||
AddInput(kExposureInput, NodeValue::kVec4, QVector4D{0.0, 0.0, 0.0, 0.0});
|
||||
SetInputProperty(kExposureInput, QStringLiteral("base"), 0.01);
|
||||
SetVec4InputColors(kExposureInput);
|
||||
|
||||
AddInput(kSaturationInput, NodeValue::kFloat, 1.0);
|
||||
SetInputProperty(kSaturationInput, QStringLiteral("view"), FloatSlider::kPercentage);
|
||||
SetInputProperty(kSaturationInput, QStringLiteral("min"), 0.0);
|
||||
|
||||
AddInput(kPivotInput, NodeValue::kFloat, 0.18); // Default listed in OCIO::GradingPrimary
|
||||
SetInputProperty(kPivotInput, QStringLiteral("base"), 0.01);
|
||||
|
||||
AddInput(kClampBlackEnableInput, NodeValue::kBoolean, false);
|
||||
|
||||
AddInput(kClampBlackInput, NodeValue::kFloat, 0.0);
|
||||
SetInputProperty(kClampBlackInput, QStringLiteral("enabled"), GetStandardValue(kClampBlackEnableInput).toBool());
|
||||
SetInputProperty(kClampBlackInput, QStringLiteral("base"), 0.01);
|
||||
|
||||
AddInput(kClampWhiteEnableInput, NodeValue::kBoolean, false);
|
||||
|
||||
AddInput(kClampWhiteInput, NodeValue::kFloat, 1.0);
|
||||
SetInputProperty(kClampWhiteInput, QStringLiteral("enabled"), GetStandardValue(kClampWhiteEnableInput).toBool());
|
||||
SetInputProperty(kClampWhiteInput, QStringLiteral("base"), 0.01);
|
||||
|
||||
// FIXME: Temporarily disabled. This will break if "clamp black" is keyframed or connected to
|
||||
// something and there's currently no solution to remedy that. If there is in the future,
|
||||
// we can look into re-enabling this.
|
||||
//SetInputProperty(kClampWhiteInput, QStringLiteral("min"), GetStandardValue(kClampBlackInput).toDouble() + 0.000001);
|
||||
}
|
||||
|
||||
QString OCIOGradingTransformLinearNode::Name() const
|
||||
{
|
||||
return tr("OCIO Color Grading (Linear)");
|
||||
}
|
||||
|
||||
QString OCIOGradingTransformLinearNode::id() const
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.ociogradingtransformlinear");
|
||||
}
|
||||
|
||||
QVector<Node::CategoryID> OCIOGradingTransformLinearNode::Category() const
|
||||
{
|
||||
return {kCategoryColor};
|
||||
}
|
||||
|
||||
QString OCIOGradingTransformLinearNode::Description() const
|
||||
{
|
||||
return tr("Simple linear color grading using OpenColorIO.");
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLinearNode::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kTextureInput, tr("Input"));
|
||||
SetInputName(kContrastInput, tr("Contrast"));
|
||||
SetInputName(kOffsetInput, tr("Offset"));
|
||||
SetInputName(kExposureInput, tr("Exposure"));
|
||||
SetInputProperty(kExposureInput, QStringLiteral("tooltip"), tr("Exposure increments in stops."));
|
||||
SetInputName(kSaturationInput, tr("Saturation"));
|
||||
SetInputName(kPivotInput, tr("Pivot"));
|
||||
SetInputName(kClampBlackEnableInput, tr("Enable Black Clamp"));
|
||||
SetInputName(kClampBlackInput, tr("Black Clamp"));
|
||||
SetInputName(kClampWhiteEnableInput, tr("Enable White Clamp"));
|
||||
SetInputName(kClampWhiteInput, tr("White Clamp"));
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLinearNode::InputValueChangedEvent(const QString &input, int element)
|
||||
{
|
||||
Q_UNUSED(element);
|
||||
|
||||
if (input == kClampWhiteEnableInput) {
|
||||
SetInputProperty(kClampWhiteInput, QStringLiteral("enabled"), GetStandardValue(kClampWhiteEnableInput).toBool());
|
||||
} else if (input == kClampBlackEnableInput) {
|
||||
SetInputProperty(kClampBlackInput, QStringLiteral("enabled"), GetStandardValue(kClampBlackEnableInput).toBool());
|
||||
} else if (input == kClampBlackInput) {
|
||||
// Ensure the white clamp is always greater than the black clamp as per OCIO::GradingPrimary::validate
|
||||
// FIXME: Temporarily disabled. This will break if "clamp black" is keyframed or connected to
|
||||
// something and there's currently no solution to remedy that. If there is in the future,
|
||||
// we can look into re-enabling this.
|
||||
//SetInputProperty(kClampWhiteInput, QStringLiteral("min"), GetStandardValue(kClampBlackInput).toDouble() + 0.000001);
|
||||
}
|
||||
|
||||
GenerateProcessor();
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLinearNode::GenerateProcessor()
|
||||
{
|
||||
if (manager()) {
|
||||
OCIO::GradingPrimaryTransformRcPtr gp = OCIO::GradingPrimaryTransform::Create(OCIO::GRADING_LIN);
|
||||
gp->makeDynamic();
|
||||
gp->setDirection(OCIO::TransformDirection::TRANSFORM_DIR_FORWARD);
|
||||
|
||||
try {
|
||||
set_processor(ColorProcessor::Create(manager()->GetConfig()->getProcessor(gp)));
|
||||
} catch (const OCIO::Exception &e) {
|
||||
std::cerr << std::endl << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLinearNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||
{
|
||||
if (value[kTextureInput].toTexture() && processor()) {
|
||||
ColorTransformJob job;
|
||||
|
||||
job.SetColorProcessor(processor());
|
||||
job.SetInputTexture(value[kTextureInput].toTexture());
|
||||
|
||||
job.Insert(value);
|
||||
|
||||
const int MASTER_CHANNEL = 0;
|
||||
const int RED_CHANNEL = 1;
|
||||
const int GREEN_CHANNEL = 2;
|
||||
const int BLUE_CHANNEL = 3;
|
||||
|
||||
// Oddly, OCIO uses RGBMs when setting the GradingPrimary on the CPU, but uses vec3s on the GPU.
|
||||
// Even more oddly, the conversion from RGBM to vec3 does not appear to have a public API.
|
||||
// Therefore, this code has been duplicated from OCIO here:
|
||||
// https://github.com/AcademySoftwareFoundation/OpenColorIO/blob/3abbe5b20521169580fcfe3692aca81859859953/src/OpenColorIO/ops/gradingprimary/GradingPrimary.cpp#L157
|
||||
QVector4D offset = value[kOffsetInput].toVec4();
|
||||
offset[RED_CHANNEL] += offset[MASTER_CHANNEL];
|
||||
offset[GREEN_CHANNEL] += offset[MASTER_CHANNEL];
|
||||
offset[BLUE_CHANNEL] += offset[MASTER_CHANNEL];
|
||||
job.Insert(kOffsetInput, NodeValue(NodeValue::kVec3, QVector3D(offset[RED_CHANNEL], offset[GREEN_CHANNEL], offset[BLUE_CHANNEL])));
|
||||
|
||||
QVector4D exposure = value[kExposureInput].toVec4();
|
||||
exposure[RED_CHANNEL] = std::pow(2.0f, exposure[MASTER_CHANNEL] + exposure[RED_CHANNEL]);
|
||||
exposure[GREEN_CHANNEL] = std::pow(2.0f, exposure[MASTER_CHANNEL] + exposure[GREEN_CHANNEL]);
|
||||
exposure[BLUE_CHANNEL] = std::pow(2.0f, exposure[MASTER_CHANNEL] + exposure[BLUE_CHANNEL]);
|
||||
job.Insert(kExposureInput, NodeValue(NodeValue::kVec3, QVector3D(exposure[RED_CHANNEL], exposure[GREEN_CHANNEL], exposure[BLUE_CHANNEL])));
|
||||
|
||||
QVector4D contrast = value[kContrastInput].toVec4();
|
||||
contrast[RED_CHANNEL] *= contrast[MASTER_CHANNEL];
|
||||
contrast[GREEN_CHANNEL] *= contrast[MASTER_CHANNEL];
|
||||
contrast[BLUE_CHANNEL] *= contrast[MASTER_CHANNEL];
|
||||
job.Insert(kContrastInput, NodeValue(NodeValue::kVec3, QVector3D(contrast[RED_CHANNEL], contrast[GREEN_CHANNEL], contrast[BLUE_CHANNEL])));
|
||||
|
||||
if (!value[kClampBlackEnableInput].toBool()) {
|
||||
job.Insert(kClampBlackInput, NodeValue(NodeValue::kFloat, OCIO::GradingPrimary::NoClampBlack()));
|
||||
}
|
||||
|
||||
if (!value[kClampWhiteEnableInput].toBool()) {
|
||||
job.Insert(kClampWhiteInput, NodeValue(NodeValue::kFloat, OCIO::GradingPrimary::NoClampWhite()));
|
||||
}
|
||||
|
||||
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
|
||||
}
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLinearNode::ConfigChanged()
|
||||
{
|
||||
GenerateProcessor();
|
||||
}
|
||||
|
||||
void OCIOGradingTransformLinearNode::SetVec4InputColors(const QString &input)
|
||||
{
|
||||
SetInputProperty(input, QStringLiteral("color0"), QColor(192, 192, 192).name());
|
||||
SetInputProperty(input, QStringLiteral("color1"), QColor(255, 0, 0).name());
|
||||
SetInputProperty(input, QStringLiteral("color2"), QColor(0, 255, 0).name());
|
||||
SetInputProperty(input, QStringLiteral("color3"), QColor(0, 0, 255).name());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
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 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OCIOGRADINGTRANSFORMLINEARNODE_H
|
||||
#define OCIOGRADINGTRANSFORMLINEARNODE_H
|
||||
|
||||
#include "node/color/ociobase/ociobase.h"
|
||||
#include "render/colorprocessor.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class OCIOGradingTransformLinearNode : public OCIOBaseNode
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
OCIOGradingTransformLinearNode();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(OCIOGradingTransformLinearNode)
|
||||
|
||||
virtual QString Name() const override;
|
||||
virtual QString id() const override;
|
||||
virtual QVector<CategoryID> Category() const override;
|
||||
virtual QString Description() const override;
|
||||
|
||||
virtual void Retranslate() override;
|
||||
virtual void InputValueChangedEvent(const QString &input, int element) override;
|
||||
void GenerateProcessor();
|
||||
|
||||
virtual void Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const override;
|
||||
|
||||
static const QString kContrastInput;
|
||||
static const QString kOffsetInput;
|
||||
static const QString kExposureInput;
|
||||
static const QString kSaturationInput;
|
||||
static const QString kPivotInput;
|
||||
static const QString kClampBlackEnableInput;
|
||||
static const QString kClampBlackInput;
|
||||
static const QString kClampWhiteEnableInput;
|
||||
static const QString kClampWhiteInput;
|
||||
|
||||
protected slots:
|
||||
virtual void ConfigChanged() override;
|
||||
|
||||
private:
|
||||
void SetVec4InputColors(const QString &input);
|
||||
|
||||
};
|
||||
|
||||
} // olive
|
||||
|
||||
#endif
|
||||
@@ -17,6 +17,7 @@
|
||||
add_subdirectory(cornerpin)
|
||||
add_subdirectory(crop)
|
||||
add_subdirectory(flip)
|
||||
add_subdirectory(mask)
|
||||
add_subdirectory(transform)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
|
||||
@@ -70,9 +70,9 @@ void CornerPinDistortNode::Retranslate()
|
||||
void CornerPinDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||
{
|
||||
ShaderJob job;
|
||||
job.InsertValue(value);
|
||||
job.Insert(value);
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
|
||||
job.InsertValue(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this));
|
||||
job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this));
|
||||
|
||||
// Convert slider values to their pixel values and then convert to clip space (-1.0 ... 1.0) for overriding the
|
||||
// vertex coordinates.
|
||||
@@ -94,23 +94,23 @@ void CornerPinDistortNode::Value(const NodeValueRow &value, const NodeGlobals &g
|
||||
job.SetVertexCoordinates(adjusted_vertices);
|
||||
|
||||
// If no texture do nothing
|
||||
if (!job.GetValue(kTextureInput).data().isNull()) {
|
||||
if (job.Get(kTextureInput).toTexture()) {
|
||||
// In the special case that all sliders are in their default position just
|
||||
// push the texture.
|
||||
if (!(job.GetValue(kTopLeftInput).data().value<QVector2D>().isNull()
|
||||
&& job.GetValue(kTopRightInput).data().value<QVector2D>().isNull() &&
|
||||
job.GetValue(kBottomRightInput).data().value<QVector2D>().isNull() &&
|
||||
job.GetValue(kBottomLeftInput).data().value<QVector2D>().isNull())) {
|
||||
if (!(job.Get(kTopLeftInput).toVec2().isNull()
|
||||
&& job.Get(kTopRightInput).toVec2().isNull() &&
|
||||
job.Get(kBottomRightInput).toVec2().isNull() &&
|
||||
job.Get(kBottomLeftInput).toVec2().isNull())) {
|
||||
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
|
||||
} else {
|
||||
table->Push(NodeValue::kTexture, job.GetValue(kTextureInput).data(), this);
|
||||
table->Push(job.Get(kTextureInput));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShaderCode CornerPinDistortNode::GetShaderCode(const QString &shader_id) const
|
||||
ShaderCode CornerPinDistortNode::GetShaderCode(const ShaderRequest &request) const
|
||||
{
|
||||
Q_UNUSED(shader_id)
|
||||
Q_UNUSED(request)
|
||||
|
||||
return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/cornerpin.frag")),
|
||||
FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/cornerpin.vert")));
|
||||
@@ -119,25 +119,24 @@ ShaderCode CornerPinDistortNode::GetShaderCode(const QString &shader_id) const
|
||||
QPointF CornerPinDistortNode::ValueToPixel(int value, const NodeValueRow& row, const QVector2D &resolution) const
|
||||
{
|
||||
Q_ASSERT(value >= 0 && value <= 3);
|
||||
|
||||
QVector2D v;
|
||||
|
||||
switch (value) {
|
||||
case 0: // Top left
|
||||
return QPointF(row[kTopLeftInput].data().value<QVector2D>().x(),
|
||||
row[kTopLeftInput].data().value<QVector2D>().y());
|
||||
break;
|
||||
case 1: // Top right
|
||||
return QPointF(resolution.x() + row[kTopRightInput].data().value<QVector2D>().x(),
|
||||
row[kTopRightInput].data().value<QVector2D>().y());
|
||||
break;
|
||||
case 2: // Bottom right
|
||||
return QPointF(resolution.x() + row[kBottomRightInput].data().value<QVector2D>().x(),
|
||||
resolution.y() + row[kBottomRightInput].data().value<QVector2D>().y());
|
||||
break;
|
||||
case 3: //Bottom left
|
||||
return QPointF(row[kBottomLeftInput].data().value<QVector2D>().x(),
|
||||
row[kBottomLeftInput].data().value<QVector2D>().y() + resolution.y());
|
||||
break;
|
||||
default: // We should never get here
|
||||
return QPointF();
|
||||
case 0: // Top left
|
||||
v = row[kTopLeftInput].toVec2();
|
||||
return QPointF(v.x(), v.y());
|
||||
case 1: // Top right
|
||||
v = row[kTopRightInput].toVec2();
|
||||
return QPointF(resolution.x() + v.x(), v.y());
|
||||
case 2: // Bottom right
|
||||
v = row[kBottomRightInput].toVec2();
|
||||
return QPointF(resolution.x() + v.x(), resolution.y() + v.y());
|
||||
case 3: //Bottom left
|
||||
v = row[kBottomLeftInput].toVec2();
|
||||
return QPointF(v.x(), v.y() + resolution.y());
|
||||
default: // We should never get here
|
||||
return QPointF();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,12 +35,7 @@ class CornerPinDistortNode : public Node
|
||||
public:
|
||||
CornerPinDistortNode();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(CornerPinDistortNode)
|
||||
|
||||
virtual Node* copy() const override
|
||||
{
|
||||
return new CornerPinDistortNode();
|
||||
}
|
||||
NODE_DEFAULT_FUNCTIONS(CornerPinDistortNode)
|
||||
|
||||
virtual QString Name() const override
|
||||
{
|
||||
@@ -66,7 +61,7 @@ public:
|
||||
|
||||
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
|
||||
|
||||
virtual ShaderCode GetShaderCode(const QString &shader_id) const override;
|
||||
virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override;
|
||||
|
||||
virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override;
|
||||
|
||||
|
||||
@@ -78,25 +78,25 @@ void CropDistortNode::Retranslate()
|
||||
void CropDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||
{
|
||||
ShaderJob job;
|
||||
job.InsertValue(value);
|
||||
job.InsertValue(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this));
|
||||
job.Insert(value);
|
||||
job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this));
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
|
||||
|
||||
if (!job.GetValue(kTextureInput).data().isNull()) {
|
||||
if (!qIsNull(job.GetValue(kLeftInput).data().toDouble())
|
||||
|| !qIsNull(job.GetValue(kRightInput).data().toDouble())
|
||||
|| !qIsNull(job.GetValue(kTopInput).data().toDouble())
|
||||
|| !qIsNull(job.GetValue(kBottomInput).data().toDouble())) {
|
||||
if (job.Get(kTextureInput).toTexture()) {
|
||||
if (!qIsNull(job.Get(kLeftInput).toDouble())
|
||||
|| !qIsNull(job.Get(kRightInput).toDouble())
|
||||
|| !qIsNull(job.Get(kTopInput).toDouble())
|
||||
|| !qIsNull(job.Get(kBottomInput).toDouble())) {
|
||||
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
|
||||
} else {
|
||||
table->Push(NodeValue::kTexture, job.GetValue(kTextureInput).data(), this);
|
||||
table->Push(job.Get(kTextureInput));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShaderCode CropDistortNode::GetShaderCode(const QString &shader_id) const
|
||||
ShaderCode CropDistortNode::GetShaderCode(const ShaderRequest &request) const
|
||||
{
|
||||
Q_UNUSED(shader_id)
|
||||
Q_UNUSED(request)
|
||||
return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/crop.frag")));
|
||||
}
|
||||
|
||||
@@ -104,10 +104,10 @@ void CropDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGl
|
||||
{
|
||||
const QVector2D &resolution = globals.resolution();
|
||||
|
||||
double left_pt = resolution.x() * row[kLeftInput].data().toDouble();
|
||||
double top_pt = resolution.y() * row[kTopInput].data().toDouble();
|
||||
double right_pt = resolution.x() * (1.0 - row[kRightInput].data().toDouble());
|
||||
double bottom_pt = resolution.y() * (1.0 - row[kBottomInput].data().toDouble());
|
||||
double left_pt = resolution.x() * row[kLeftInput].toDouble();
|
||||
double top_pt = resolution.y() * row[kTopInput].toDouble();
|
||||
double right_pt = resolution.x() * (1.0 - row[kRightInput].toDouble());
|
||||
double bottom_pt = resolution.y() * (1.0 - row[kBottomInput].toDouble());
|
||||
double center_x_pt = mid(left_pt, right_pt);
|
||||
double center_y_pt = mid(top_pt, bottom_pt);
|
||||
|
||||
|
||||
@@ -36,12 +36,7 @@ class CropDistortNode : public Node
|
||||
public:
|
||||
CropDistortNode();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(CropDistortNode)
|
||||
|
||||
virtual Node* copy() const override
|
||||
{
|
||||
return new CropDistortNode();
|
||||
}
|
||||
NODE_DEFAULT_FUNCTIONS(CropDistortNode)
|
||||
|
||||
virtual QString Name() const override
|
||||
{
|
||||
@@ -67,7 +62,7 @@ public:
|
||||
|
||||
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
|
||||
|
||||
virtual ShaderCode GetShaderCode(const QString &shader_id) const override;
|
||||
virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override;
|
||||
|
||||
virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override;
|
||||
|
||||
|
||||
@@ -40,11 +40,6 @@ FlipDistortNode::FlipDistortNode()
|
||||
SetEffectInput(kTextureInput);
|
||||
}
|
||||
|
||||
Node* FlipDistortNode::copy() const
|
||||
{
|
||||
return new FlipDistortNode();
|
||||
}
|
||||
|
||||
QString FlipDistortNode::Name() const
|
||||
{
|
||||
return tr("Flip");
|
||||
@@ -74,9 +69,9 @@ void FlipDistortNode::Retranslate()
|
||||
SetInputName(kVerticalInput, tr("Vertical"));
|
||||
}
|
||||
|
||||
ShaderCode FlipDistortNode::GetShaderCode(const QString& shader_id) const
|
||||
ShaderCode FlipDistortNode::GetShaderCode(const ShaderRequest &request) const
|
||||
{
|
||||
Q_UNUSED(shader_id)
|
||||
Q_UNUSED(request)
|
||||
return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/flip.frag"));
|
||||
}
|
||||
|
||||
@@ -84,16 +79,16 @@ void FlipDistortNode::Value(const NodeValueRow &value, const NodeGlobals &global
|
||||
{
|
||||
ShaderJob job;
|
||||
|
||||
job.InsertValue(value);
|
||||
job.Insert(value);
|
||||
|
||||
// If there's no texture, no need to run an operation
|
||||
if (!job.GetValue(kTextureInput).data().isNull()) {
|
||||
if (job.Get(kTextureInput).toTexture()) {
|
||||
// Only run shader if at least one of flip or flop are selected
|
||||
if (job.GetValue(kHorizontalInput).data().toBool() || job.GetValue(kVerticalInput).data().toBool()) {
|
||||
if (job.Get(kHorizontalInput).toBool() || job.Get(kVerticalInput).toBool()) {
|
||||
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
|
||||
} else {
|
||||
// If we're not flipping or flopping just push the texture
|
||||
table->Push(job.GetValue(kTextureInput));
|
||||
table->Push(job.Get(kTextureInput));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,9 +31,7 @@ class FlipDistortNode : public Node
|
||||
public:
|
||||
FlipDistortNode();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(FlipDistortNode)
|
||||
|
||||
virtual Node* copy() const override;
|
||||
NODE_DEFAULT_FUNCTIONS(FlipDistortNode)
|
||||
|
||||
virtual QString Name() const override;
|
||||
virtual QString id() const override;
|
||||
@@ -42,7 +40,7 @@ public:
|
||||
|
||||
virtual void Retranslate() override;
|
||||
|
||||
virtual ShaderCode GetShaderCode(const QString &shader_id) const override;
|
||||
virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override;
|
||||
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
|
||||
|
||||
static const QString kTextureInput;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2021 Olive Team
|
||||
#
|
||||
# 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/distort/mask/mask.cpp
|
||||
node/distort/mask/mask.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,94 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
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 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "mask.h"
|
||||
|
||||
#include "node/filter/blur/blur.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
#define super PolygonGenerator
|
||||
|
||||
const QString MaskDistortNode::kFeatherInput = QStringLiteral("feather_in");
|
||||
|
||||
MaskDistortNode::MaskDistortNode()
|
||||
{
|
||||
// Mask should always be (1.0, 1.0, 1.0) for multiply to work correctly
|
||||
SetInputFlags(kColorInput, InputFlags(GetInputFlags(kColorInput) | kInputFlagHidden));
|
||||
|
||||
AddInput(kFeatherInput, NodeValue::kFloat, 0.0);
|
||||
SetInputProperty(kFeatherInput, QStringLiteral("min"), 0.0);
|
||||
}
|
||||
|
||||
ShaderCode MaskDistortNode::GetShaderCode(const ShaderRequest &request) const
|
||||
{
|
||||
if (request.id == QStringLiteral("mrg")) {
|
||||
return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/multiply.frag")));
|
||||
} else if (request.id == QStringLiteral("feather")) {
|
||||
return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/blur.frag")));
|
||||
} else {
|
||||
return ShaderCode();
|
||||
}
|
||||
}
|
||||
|
||||
void MaskDistortNode::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kBaseInput, tr("Texture"));
|
||||
SetInputName(kFeatherInput, tr("Feather"));
|
||||
}
|
||||
|
||||
void MaskDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||
{
|
||||
GenerateJob job = GetGenerateJob(value);
|
||||
|
||||
if (value[kBaseInput].toTexture()) {
|
||||
// Push as merge node
|
||||
ShaderJob merge;
|
||||
|
||||
merge.SetShaderID(QStringLiteral("mrg"));
|
||||
merge.Insert(QStringLiteral("tex_a"), value[kBaseInput]);
|
||||
|
||||
if (value[kFeatherInput].toDouble() > 0.0) {
|
||||
// Nest a blur shader in there too
|
||||
ShaderJob feather;
|
||||
|
||||
feather.SetShaderID(QStringLiteral("feather"));
|
||||
feather.Insert(BlurFilterNode::kTextureInput, NodeValue(NodeValue::kTexture, job, this));
|
||||
feather.Insert(BlurFilterNode::kMethodInput, NodeValue(NodeValue::kInt, int(BlurFilterNode::kGaussian), this));
|
||||
feather.Insert(BlurFilterNode::kHorizInput, NodeValue(NodeValue::kBoolean, true, this));
|
||||
feather.Insert(BlurFilterNode::kVertInput, NodeValue(NodeValue::kBoolean, true, this));
|
||||
feather.Insert(BlurFilterNode::kRepeatEdgePixelsInput, NodeValue(NodeValue::kBoolean, true, this));
|
||||
feather.Insert(BlurFilterNode::kRadiusInput, NodeValue(NodeValue::kFloat, value[kFeatherInput].toDouble(), this));
|
||||
feather.SetIterations(2, BlurFilterNode::kTextureInput);
|
||||
feather.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this));
|
||||
feather.SetAlphaChannelRequired(ShaderJob::kAlphaForceOn);
|
||||
|
||||
merge.Insert(QStringLiteral("tex_b"), NodeValue(NodeValue::kTexture, feather, this));
|
||||
} else {
|
||||
merge.Insert(QStringLiteral("tex_b"), NodeValue(NodeValue::kTexture, job, this));
|
||||
}
|
||||
|
||||
table->Push(NodeValue::kTexture, QVariant::fromValue(merge), this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
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 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef MASKDISTORTNODE_H
|
||||
#define MASKDISTORTNODE_H
|
||||
|
||||
#include "node/generator/polygon/polygon.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class MaskDistortNode : public PolygonGenerator
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MaskDistortNode();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(MaskDistortNode)
|
||||
|
||||
virtual QString Name() const override
|
||||
{
|
||||
return tr("Mask");
|
||||
}
|
||||
|
||||
virtual QString id() const override
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.mask");
|
||||
}
|
||||
|
||||
virtual QVector<CategoryID> Category() const override
|
||||
{
|
||||
return {kCategoryDistort};
|
||||
}
|
||||
|
||||
virtual QString Description() const override
|
||||
{
|
||||
return tr("Apply a polygonal mask.");
|
||||
}
|
||||
|
||||
virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override;
|
||||
|
||||
virtual void Retranslate() override;
|
||||
|
||||
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
|
||||
|
||||
static const QString kFeatherInput;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // MASKDISTORTNODE_H
|
||||
@@ -92,16 +92,16 @@ void TransformDistortNode::Value(const NodeValueRow &value, const NodeGlobals &g
|
||||
bool pushed_job = false;
|
||||
|
||||
// If we have a texture, generate a matrix and make it happen
|
||||
if (TexturePtr texture = texture_meta.data().value<TexturePtr>()) {
|
||||
if (TexturePtr texture = texture_meta.toTexture()) {
|
||||
// Adjust our matrix by the resolutions involved
|
||||
QMatrix4x4 real_matrix = GenerateAutoScaledMatrix(generated_matrix, value, globals, texture->params());
|
||||
|
||||
if (!real_matrix.isIdentity()) {
|
||||
// The matrix will transform things
|
||||
ShaderJob job;
|
||||
job.InsertValue(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(texture), this));
|
||||
job.InsertValue(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, real_matrix, this));
|
||||
job.SetInterpolation(QStringLiteral("ove_maintex"), static_cast<Texture::Interpolation>(value[kInterpolationInput].data().toInt()));
|
||||
job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(texture), this));
|
||||
job.Insert(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, real_matrix, this));
|
||||
job.SetInterpolation(QStringLiteral("ove_maintex"), static_cast<Texture::Interpolation>(value[kInterpolationInput].toInt()));
|
||||
|
||||
// FIXME: This should be optimized, we can use matrix math to determine if this operation will
|
||||
// end up with gaps in the screen that will require an alpha channel.
|
||||
@@ -119,9 +119,9 @@ void TransformDistortNode::Value(const NodeValueRow &value, const NodeGlobals &g
|
||||
}
|
||||
}
|
||||
|
||||
ShaderCode TransformDistortNode::GetShaderCode(const QString &shader_id) const
|
||||
ShaderCode TransformDistortNode::GetShaderCode(const ShaderRequest &request) const
|
||||
{
|
||||
Q_UNUSED(shader_id);
|
||||
Q_UNUSED(request);
|
||||
|
||||
// Returns default frag and vert shader
|
||||
return ShaderCode();
|
||||
@@ -140,7 +140,7 @@ void TransformDistortNode::Hash(QCryptographicHash &hash, const NodeGlobals &glo
|
||||
traverser.SetCacheVideoParams(video_params);
|
||||
|
||||
NodeValueRow db = traverser.GenerateRow(this, globals.time());
|
||||
TexturePtr tex = db[kTextureInput].data().value<TexturePtr>();
|
||||
TexturePtr tex = db[kTextureInput].toTexture();
|
||||
if (tex) {
|
||||
VideoParams tex_params = tex->params();
|
||||
QMatrix4x4 matrix = GenerateMatrix(db, true, false, false, false);
|
||||
@@ -167,13 +167,13 @@ void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, dou
|
||||
} else if (IsAScaleGizmo(gizmo)) {
|
||||
|
||||
// Dragging scale handle
|
||||
TexturePtr tex = row[kTextureInput].data().value<TexturePtr>();
|
||||
TexturePtr tex = row[kTextureInput].toTexture();
|
||||
if (!tex) {
|
||||
return;
|
||||
}
|
||||
|
||||
gizmo_scale_uniform_ = row[kUniformScaleInput].data().toBool();
|
||||
gizmo_anchor_pt_ = (row[kAnchorInput].data().value<QVector2D>() + gizmo->GetGlobals().resolution()/2).toPointF();
|
||||
gizmo_scale_uniform_ = row[kUniformScaleInput].toBool();
|
||||
gizmo_anchor_pt_ = (row[kAnchorInput].toVec2() + gizmo->GetGlobals().resolution()/2).toPointF();
|
||||
|
||||
if (gizmo == point_gizmo_[kGizmoScaleTopLeft] || gizmo == point_gizmo_[kGizmoScaleTopRight]
|
||||
|| gizmo == point_gizmo_[kGizmoScaleBottomLeft] || gizmo == point_gizmo_[kGizmoScaleBottomRight]) {
|
||||
@@ -187,7 +187,7 @@ void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, dou
|
||||
// Store texture size
|
||||
VideoParams texture_params = tex->params();
|
||||
QVector2D texture_sz(texture_params.square_pixel_width(), texture_params.height());
|
||||
gizmo_scale_anchor_ = row[kAnchorInput].data().value<QVector2D>() + texture_sz/2;
|
||||
gizmo_scale_anchor_ = row[kAnchorInput].toVec2() + texture_sz/2;
|
||||
|
||||
if (gizmo == point_gizmo_[kGizmoScaleTopRight]
|
||||
|| gizmo == point_gizmo_[kGizmoScaleBottomRight]
|
||||
@@ -208,7 +208,7 @@ void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, dou
|
||||
|
||||
} else if (gizmo == rotation_gizmo_) {
|
||||
|
||||
gizmo_anchor_pt_ = (row[kAnchorInput].data().value<QVector2D>() + gizmo->GetGlobals().resolution()/2).toPointF();
|
||||
gizmo_anchor_pt_ = (row[kAnchorInput].toVec2() + gizmo->GetGlobals().resolution()/2).toPointF();
|
||||
gizmo_start_angle_ = qAtan2(y - gizmo_anchor_pt_.y(), x - gizmo_anchor_pt_.x());
|
||||
gizmo_last_angle_ = gizmo_start_angle_;
|
||||
gizmo_last_alt_angle_ = qAtan2(x - gizmo_anchor_pt_.x(), y - gizmo_anchor_pt_.y());
|
||||
@@ -368,7 +368,7 @@ QMatrix4x4 TransformDistortNode::AdjustMatrixByResolutions(const QMatrix4x4 &mat
|
||||
|
||||
void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals)
|
||||
{
|
||||
TexturePtr tex = row[kTextureInput].data().value<TexturePtr>();
|
||||
TexturePtr tex = row[kTextureInput].toTexture();
|
||||
if (!tex) {
|
||||
return;
|
||||
}
|
||||
@@ -384,7 +384,7 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N
|
||||
QVector2D tex_offset = tex_params.offset();
|
||||
|
||||
// Retrieve autoscale value
|
||||
AutoScaleType autoscale = static_cast<AutoScaleType>(row[kAutoscaleInput].data().toInt());
|
||||
AutoScaleType autoscale = static_cast<AutoScaleType>(row[kAutoscaleInput].toInt());
|
||||
|
||||
// Fold values into a matrix for the rectangle
|
||||
QMatrix4x4 rectangle_matrix;
|
||||
@@ -441,7 +441,7 @@ QMatrix4x4 TransformDistortNode::GenerateAutoScaledMatrix(const QMatrix4x4& gene
|
||||
{
|
||||
const QVector2D &sequence_res = globals.resolution();
|
||||
QVector2D texture_res(texture_params.square_pixel_width(), texture_params.height());
|
||||
AutoScaleType autoscale = static_cast<AutoScaleType>(value[kAutoscaleInput].data().toInt());
|
||||
AutoScaleType autoscale = static_cast<AutoScaleType>(value[kAutoscaleInput].toInt());
|
||||
|
||||
return AdjustMatrixByResolutions(generated_matrix,
|
||||
sequence_res,
|
||||
|
||||
@@ -34,23 +34,13 @@ class TransformDistortNode : public MatrixGenerator
|
||||
public:
|
||||
TransformDistortNode();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(TransformDistortNode)
|
||||
|
||||
virtual Node* copy() const override
|
||||
{
|
||||
return new TransformDistortNode();
|
||||
}
|
||||
NODE_DEFAULT_FUNCTIONS(TransformDistortNode)
|
||||
|
||||
virtual QString Name() const override
|
||||
{
|
||||
return tr("Transform");
|
||||
}
|
||||
|
||||
virtual QString ShortName() const override
|
||||
{
|
||||
return Node::ShortName();
|
||||
}
|
||||
|
||||
virtual QString id() const override
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.transform");
|
||||
@@ -70,7 +60,7 @@ public:
|
||||
|
||||
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
|
||||
|
||||
virtual ShaderCode GetShaderCode(const QString& shader_id) const override;
|
||||
virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override;
|
||||
|
||||
enum AutoScaleType {
|
||||
kAutoScaleNone,
|
||||
|
||||
@@ -37,9 +37,9 @@ void OpacityEffect::Retranslate()
|
||||
SetInputName(kValueInput, tr("Opacity"));
|
||||
}
|
||||
|
||||
ShaderCode OpacityEffect::GetShaderCode(const QString &shader_id) const
|
||||
ShaderCode OpacityEffect::GetShaderCode(const ShaderRequest &request) const
|
||||
{
|
||||
Q_UNUSED(shader_id)
|
||||
Q_UNUSED(request)
|
||||
return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/opacity.frag"));
|
||||
}
|
||||
|
||||
@@ -47,16 +47,16 @@ void OpacityEffect::Value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
{
|
||||
ShaderJob job;
|
||||
|
||||
job.InsertValue(value);
|
||||
job.Insert(value);
|
||||
|
||||
// If there's no texture, no need to run an operation
|
||||
if (!job.GetValue(kTextureInput).data().isNull()) {
|
||||
if (!qFuzzyCompare(job.GetValue(kValueInput).data().toDouble(), 1.0)) {
|
||||
if (job.Get(kTextureInput).toTexture()) {
|
||||
if (!qFuzzyCompare(job.Get(kValueInput).toDouble(), 1.0)) {
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
|
||||
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
|
||||
} else {
|
||||
// 1.0 float is a no-op, so just push the texture
|
||||
table->Push(job.GetValue(kTextureInput));
|
||||
table->Push(job.Get(kTextureInput));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,7 @@ class OpacityEffect : public Node
|
||||
public:
|
||||
OpacityEffect();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(OpacityEffect)
|
||||
|
||||
NODE_COPY_FUNCTION(OpacityEffect)
|
||||
NODE_DEFAULT_FUNCTIONS(OpacityEffect)
|
||||
|
||||
virtual QString Name() const override
|
||||
{
|
||||
@@ -36,7 +34,7 @@ public:
|
||||
|
||||
virtual void Retranslate() override;
|
||||
|
||||
virtual ShaderCode GetShaderCode(const QString &shader_id) const override;
|
||||
virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override;
|
||||
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
|
||||
|
||||
static const QString kTextureInput;
|
||||
|
||||
+14
-7
@@ -29,9 +29,12 @@
|
||||
#include "block/subtitle/subtitle.h"
|
||||
#include "block/transition/crossdissolve/crossdissolvetransition.h"
|
||||
#include "block/transition/diptocolor/diptocolortransition.h"
|
||||
#include "color/displaytransform/displaytransform.h"
|
||||
#include "color/ociogradingtransformlinear/ociogradingtransformlinear.h"
|
||||
#include "distort/cornerpin/cornerpindistortnode.h"
|
||||
#include "distort/crop/cropdistortnode.h"
|
||||
#include "distort/flip/flipdistortnode.h"
|
||||
#include "distort/mask/mask.h"
|
||||
#include "distort/transform/transformdistortnode.h"
|
||||
#include "effect/opacity/opacityeffect.h"
|
||||
#include "generator/matrix/matrix.h"
|
||||
@@ -52,6 +55,7 @@
|
||||
#include "math/trigonometry/trigonometry.h"
|
||||
#include "keying/colordifferencekey/colordifferencekey.h"
|
||||
#include "keying/despill/despill.h"
|
||||
#include "keying/chromakey/chromakey.h"
|
||||
#include "output/track/track.h"
|
||||
#include "output/viewer/viewer.h"
|
||||
#include "project/folder/folder.h"
|
||||
@@ -61,8 +65,8 @@
|
||||
#include "time/timeremap/timeremap.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
QList<Node*> NodeFactory::library_;
|
||||
QVector<int> NodeFactory::hidden_;
|
||||
|
||||
void NodeFactory::Initialize()
|
||||
{
|
||||
@@ -74,10 +78,6 @@ void NodeFactory::Initialize()
|
||||
|
||||
library_.append(created_node);
|
||||
}
|
||||
|
||||
hidden_.append(kTextGeneratorV1);
|
||||
hidden_.append(kTextGeneratorV2);
|
||||
hidden_.append(kGroupNode);
|
||||
}
|
||||
|
||||
void NodeFactory::Destroy()
|
||||
@@ -103,8 +103,7 @@ Menu *NodeFactory::CreateMenu(QWidget* parent, bool create_none_item, Node::Cate
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hidden_.contains(i)) {
|
||||
// Skip this node
|
||||
if (n->GetFlags() & Node::kDontShowInCreateMenu) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -281,6 +280,14 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id)
|
||||
return new TimeOffsetNode();
|
||||
case kCornerPinDistort:
|
||||
return new CornerPinDistortNode();
|
||||
case kDisplayTransform:
|
||||
return new DisplayTransformNode();
|
||||
case kOCIOGradingTransformLinear:
|
||||
return new OCIOGradingTransformLinearNode();
|
||||
case kChromaKey:
|
||||
return new ChromaKeyNode();
|
||||
case kMaskDistort:
|
||||
return new MaskDistortNode();
|
||||
|
||||
case kInternalNodeCount:
|
||||
break;
|
||||
|
||||
+4
-2
@@ -70,6 +70,10 @@ public:
|
||||
kNoiseGenerator,
|
||||
kTimeOffsetNode,
|
||||
kCornerPinDistort,
|
||||
kDisplayTransform,
|
||||
kOCIOGradingTransformLinear,
|
||||
kChromaKey,
|
||||
kMaskDistort,
|
||||
|
||||
// Count value
|
||||
kInternalNodeCount
|
||||
@@ -96,8 +100,6 @@ public:
|
||||
private:
|
||||
static QList<Node*> library_;
|
||||
|
||||
static QVector<int> hidden_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
+114
-22
@@ -29,30 +29,50 @@ const QString BlurFilterNode::kHorizInput = QStringLiteral("horiz_in");
|
||||
const QString BlurFilterNode::kVertInput = QStringLiteral("vert_in");
|
||||
const QString BlurFilterNode::kRepeatEdgePixelsInput = QStringLiteral("repeat_edge_pixels_in");
|
||||
|
||||
const QString BlurFilterNode::kDirectionalDegreesInput = QStringLiteral("directional_degrees_in");
|
||||
|
||||
const QString BlurFilterNode::kRadialCenterInput = QStringLiteral("radial_center_in");
|
||||
|
||||
#define super Node
|
||||
|
||||
BlurFilterNode::BlurFilterNode()
|
||||
{
|
||||
AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
|
||||
|
||||
AddInput(kMethodInput, NodeValue::kCombo, 1); // Default to gaussian
|
||||
Method default_method = kGaussian;
|
||||
|
||||
AddInput(kMethodInput, NodeValue::kCombo, default_method, InputFlags(kInputFlagNotKeyframable | kInputFlagNotConnectable));
|
||||
|
||||
AddInput(kRadiusInput, NodeValue::kFloat, 10.0);
|
||||
SetInputProperty(kRadiusInput, QStringLiteral("min"), 0.0);
|
||||
|
||||
AddInput(kHorizInput, NodeValue::kBoolean, true);
|
||||
{
|
||||
// Box and gaussian only
|
||||
AddInput(kHorizInput, NodeValue::kBoolean, true);
|
||||
AddInput(kVertInput, NodeValue::kBoolean, true);
|
||||
}
|
||||
|
||||
AddInput(kVertInput, NodeValue::kBoolean, true);
|
||||
{
|
||||
// Directional only
|
||||
AddInput(kDirectionalDegreesInput, NodeValue::kFloat, 0.0);
|
||||
}
|
||||
|
||||
{
|
||||
// Radial only
|
||||
AddInput(kRadialCenterInput, NodeValue::kVec2, QVector2D(0, 0));
|
||||
}
|
||||
|
||||
UpdateInputs(default_method);
|
||||
|
||||
AddInput(kRepeatEdgePixelsInput, NodeValue::kBoolean, true);
|
||||
|
||||
SetFlags(kVideoEffect);
|
||||
SetEffectInput(kTextureInput);
|
||||
}
|
||||
|
||||
Node *BlurFilterNode::copy() const
|
||||
{
|
||||
return new BlurFilterNode();
|
||||
radial_center_gizmo_ = AddDraggableGizmo<PointGizmo>();
|
||||
radial_center_gizmo_->SetShape(PointGizmo::kAnchorPoint);
|
||||
radial_center_gizmo_->AddInput(NodeKeyframeTrackReference(NodeInput(this, kRadialCenterInput), 0));
|
||||
radial_center_gizmo_->AddInput(NodeKeyframeTrackReference(NodeInput(this, kRadialCenterInput), 1));
|
||||
}
|
||||
|
||||
QString BlurFilterNode::Name() const
|
||||
@@ -81,16 +101,19 @@ void BlurFilterNode::Retranslate()
|
||||
|
||||
SetInputName(kTextureInput, tr("Input"));
|
||||
SetInputName(kMethodInput, tr("Method"));
|
||||
SetComboBoxStrings(kMethodInput, { tr("Box"), tr("Gaussian") });
|
||||
SetComboBoxStrings(kMethodInput, { tr("Box"), tr("Gaussian"), tr("Directional"), tr("Radial") });
|
||||
SetInputName(kRadiusInput, tr("Radius"));
|
||||
SetInputName(kHorizInput, tr("Horizontal"));
|
||||
SetInputName(kVertInput, tr("Vertical"));
|
||||
SetInputName(kRepeatEdgePixelsInput, tr("Repeat Edge Pixels"));
|
||||
|
||||
SetInputName(kDirectionalDegreesInput, tr("Direction"));
|
||||
SetInputName(kRadialCenterInput, tr("Center"));
|
||||
}
|
||||
|
||||
ShaderCode BlurFilterNode::GetShaderCode(const QString &shader_id) const
|
||||
ShaderCode BlurFilterNode::GetShaderCode(const ShaderRequest &request) const
|
||||
{
|
||||
Q_UNUSED(shader_id)
|
||||
Q_UNUSED(request)
|
||||
return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/blur.frag"));
|
||||
}
|
||||
|
||||
@@ -98,34 +121,103 @@ void BlurFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globals
|
||||
{
|
||||
ShaderJob job;
|
||||
|
||||
job.InsertValue(value);
|
||||
job.InsertValue(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this));
|
||||
job.Insert(value);
|
||||
job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this));
|
||||
|
||||
Method method = static_cast<Method>(job.Get(kMethodInput).toInt());
|
||||
|
||||
// If there's no texture, no need to run an operation
|
||||
if (!job.GetValue(kTextureInput).data().isNull()) {
|
||||
if (job.Get(kTextureInput).toTexture()) {
|
||||
|
||||
// Check if radius > 0, and both "horiz" and/or "vert" are enabled
|
||||
if ((job.GetValue(kHorizInput).data().toBool() || job.GetValue(kVertInput).data().toBool())
|
||||
&& job.GetValue(kRadiusInput).data().toDouble() > 0.0) {
|
||||
bool can_push_job = true;
|
||||
|
||||
// Set iteration count to 2 if we're blurring both horizontally and vertically
|
||||
if (job.GetValue(kHorizInput).data().toBool() && job.GetValue(kVertInput).data().toBool()) {
|
||||
job.SetIterations(2, kTextureInput);
|
||||
// Check if radius is > 0
|
||||
if (job.Get(kRadiusInput).toDouble() > 0.0) {
|
||||
// Method-specific considerations
|
||||
switch (method) {
|
||||
case kBox:
|
||||
case kGaussian:
|
||||
{
|
||||
bool horiz = job.Get(kHorizInput).toBool();
|
||||
bool vert = job.Get(kVertInput).toBool();
|
||||
|
||||
if (!horiz && !vert) {
|
||||
// Disable job if horiz and vert are unchecked
|
||||
can_push_job = false;
|
||||
} else if (horiz && vert) {
|
||||
// Set iteration count to 2 if we're blurring both horizontally and vertically
|
||||
job.SetIterations(2, kTextureInput);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kDirectional:
|
||||
case kRadial:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
can_push_job = false;
|
||||
}
|
||||
|
||||
if (can_push_job) {
|
||||
// If we're not repeating pixels, expect an alpha channel to appear
|
||||
if (!job.GetValue(kRepeatEdgePixelsInput).data().toBool()) {
|
||||
if (!job.Get(kRepeatEdgePixelsInput).toBool()) {
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
|
||||
}
|
||||
|
||||
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
|
||||
|
||||
} else {
|
||||
// If we're not performing the blur job, just push the texture
|
||||
table->Push(job.GetValue(kTextureInput));
|
||||
table->Push(job.Get(kTextureInput));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void BlurFilterNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals)
|
||||
{
|
||||
if (row[kMethodInput].toInt() == kRadial) {
|
||||
const QVector2D &sequence_res = globals.resolution();
|
||||
QVector2D sequence_half_res = sequence_res * 0.5;
|
||||
|
||||
radial_center_gizmo_->SetVisible(true);
|
||||
radial_center_gizmo_->SetPoint(sequence_half_res.toPointF() + row[kRadialCenterInput].toVec2().toPointF());
|
||||
|
||||
SetInputProperty(kRadialCenterInput, QStringLiteral("offset"), sequence_half_res);
|
||||
} else{
|
||||
radial_center_gizmo_->SetVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
void BlurFilterNode::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers)
|
||||
{
|
||||
DraggableGizmo *gizmo = static_cast<DraggableGizmo*>(sender());
|
||||
|
||||
if (gizmo == radial_center_gizmo_) {
|
||||
|
||||
NodeInputDragger &x_drag = gizmo->GetDraggers()[0];
|
||||
NodeInputDragger &y_drag = gizmo->GetDraggers()[1];
|
||||
|
||||
x_drag.Drag(x_drag.GetStartValue().toDouble() + x);
|
||||
y_drag.Drag(y_drag.GetStartValue().toDouble() + y);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void BlurFilterNode::InputValueChangedEvent(const QString &input, int element)
|
||||
{
|
||||
if (input == kMethodInput) {
|
||||
UpdateInputs(GetMethod());
|
||||
}
|
||||
|
||||
super::InputValueChangedEvent(input, element);
|
||||
}
|
||||
|
||||
void BlurFilterNode::UpdateInputs(Method method)
|
||||
{
|
||||
SetInputFlags(kHorizInput, (method == kBox || method == kGaussian) ? InputFlags() : InputFlags(kInputFlagHidden));
|
||||
SetInputFlags(kVertInput, (method == kBox || method == kGaussian) ? InputFlags() : InputFlags(kInputFlagHidden));
|
||||
SetInputFlags(kDirectionalDegreesInput, (method == kDirectional) ? InputFlags() : InputFlags(kInputFlagHidden));
|
||||
SetInputFlags(kRadialCenterInput, (method == kRadial) ? InputFlags() : InputFlags(kInputFlagHidden));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#ifndef BLURFILTERNODE_H
|
||||
#define BLURFILTERNODE_H
|
||||
|
||||
#include "node/gizmo/point.h"
|
||||
#include "node/node.h"
|
||||
|
||||
namespace olive {
|
||||
@@ -31,9 +32,14 @@ class BlurFilterNode : public Node
|
||||
public:
|
||||
BlurFilterNode();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(BlurFilterNode)
|
||||
enum Method {
|
||||
kBox,
|
||||
kGaussian,
|
||||
kDirectional,
|
||||
kRadial
|
||||
};
|
||||
|
||||
virtual Node* copy() const override;
|
||||
NODE_DEFAULT_FUNCTIONS(BlurFilterNode)
|
||||
|
||||
virtual QString Name() const override;
|
||||
virtual QString id() const override;
|
||||
@@ -42,9 +48,16 @@ public:
|
||||
|
||||
virtual void Retranslate() override;
|
||||
|
||||
virtual ShaderCode GetShaderCode(const QString &shader_id) const override;
|
||||
virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override;
|
||||
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
|
||||
|
||||
Method GetMethod() const
|
||||
{
|
||||
return static_cast<Method>(GetStandardValue(kMethodInput).toInt());
|
||||
}
|
||||
|
||||
virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override;
|
||||
|
||||
static const QString kTextureInput;
|
||||
static const QString kMethodInput;
|
||||
static const QString kRadiusInput;
|
||||
@@ -52,6 +65,21 @@ public:
|
||||
static const QString kVertInput;
|
||||
static const QString kRepeatEdgePixelsInput;
|
||||
|
||||
static const QString kDirectionalDegreesInput;
|
||||
|
||||
static const QString kRadialCenterInput;
|
||||
|
||||
protected slots:
|
||||
virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override;
|
||||
|
||||
protected:
|
||||
virtual void InputValueChangedEvent(const QString& input, int element) override;
|
||||
|
||||
private:
|
||||
void UpdateInputs(Method method);
|
||||
|
||||
PointGizmo *radial_center_gizmo_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -55,27 +55,27 @@ void MosaicFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globa
|
||||
{
|
||||
ShaderJob job;
|
||||
|
||||
job.InsertValue(value);
|
||||
job.Insert(value);
|
||||
|
||||
// Mipmapping makes this look weird, so we just use bilinear for finding the color of each block
|
||||
job.SetInterpolation(kTextureInput, Texture::kLinear);
|
||||
|
||||
if (!job.GetValue(kTextureInput).data().isNull()) {
|
||||
TexturePtr texture = job.GetValue(kTextureInput).data().value<TexturePtr>();
|
||||
if (job.Get(kTextureInput).toTexture()) {
|
||||
TexturePtr texture = job.Get(kTextureInput).toTexture();
|
||||
|
||||
if (texture
|
||||
&& job.GetValue(kHorizInput).data().toInt() != texture->width()
|
||||
&& job.GetValue(kVertInput).data().toInt() != texture->height()) {
|
||||
&& job.Get(kHorizInput).toInt() != texture->width()
|
||||
&& job.Get(kVertInput).toInt() != texture->height()) {
|
||||
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
|
||||
} else {
|
||||
table->Push(job.GetValue(kTextureInput));
|
||||
table->Push(job.Get(kTextureInput));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShaderCode MosaicFilterNode::GetShaderCode(const QString &shader_id) const
|
||||
ShaderCode MosaicFilterNode::GetShaderCode(const ShaderRequest &request) const
|
||||
{
|
||||
Q_UNUSED(shader_id)
|
||||
Q_UNUSED(request)
|
||||
|
||||
return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/mosaic.frag"));
|
||||
}
|
||||
|
||||
@@ -31,12 +31,7 @@ class MosaicFilterNode : public Node
|
||||
public:
|
||||
MosaicFilterNode();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(MosaicFilterNode)
|
||||
|
||||
virtual Node* copy() const override
|
||||
{
|
||||
return new MosaicFilterNode();
|
||||
}
|
||||
NODE_DEFAULT_FUNCTIONS(MosaicFilterNode)
|
||||
|
||||
virtual QString Name() const override
|
||||
{
|
||||
@@ -61,7 +56,7 @@ public:
|
||||
virtual void Retranslate() override;
|
||||
|
||||
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
|
||||
virtual ShaderCode GetShaderCode(const QString &shader_id) const override;
|
||||
virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override;
|
||||
|
||||
static const QString kTextureInput;
|
||||
static const QString kHorizInput;
|
||||
|
||||
@@ -53,11 +53,6 @@ StrokeFilterNode::StrokeFilterNode()
|
||||
SetEffectInput(kTextureInput);
|
||||
}
|
||||
|
||||
Node *StrokeFilterNode::copy() const
|
||||
{
|
||||
return new StrokeFilterNode();
|
||||
}
|
||||
|
||||
QString StrokeFilterNode::Name() const
|
||||
{
|
||||
return tr("Stroke");
|
||||
@@ -93,22 +88,22 @@ void StrokeFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globa
|
||||
{
|
||||
ShaderJob job;
|
||||
|
||||
job.InsertValue(value);
|
||||
job.InsertValue(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this));
|
||||
job.Insert(value);
|
||||
job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this));
|
||||
|
||||
if (!job.GetValue(kTextureInput).data().isNull()) {
|
||||
if (job.GetValue(kRadiusInput).data().toDouble() > 0.0
|
||||
&& job.GetValue(kOpacityInput).data().toDouble() > 0.0) {
|
||||
if (job.Get(kTextureInput).toTexture()) {
|
||||
if (job.Get(kRadiusInput).toDouble() > 0.0
|
||||
&& job.Get(kOpacityInput).toDouble() > 0.0) {
|
||||
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
|
||||
} else {
|
||||
table->Push(job.GetValue(kTextureInput));
|
||||
table->Push(job.Get(kTextureInput));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShaderCode StrokeFilterNode::GetShaderCode(const QString &shader_id) const
|
||||
ShaderCode StrokeFilterNode::GetShaderCode(const ShaderRequest &request) const
|
||||
{
|
||||
Q_UNUSED(shader_id)
|
||||
Q_UNUSED(request)
|
||||
|
||||
return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/stroke.frag"));
|
||||
}
|
||||
|
||||
@@ -31,9 +31,7 @@ class StrokeFilterNode : public Node
|
||||
public:
|
||||
StrokeFilterNode();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(StrokeFilterNode)
|
||||
|
||||
virtual Node* copy() const override;
|
||||
NODE_DEFAULT_FUNCTIONS(StrokeFilterNode)
|
||||
|
||||
virtual QString Name() const override;
|
||||
virtual QString id() const override;
|
||||
@@ -43,7 +41,7 @@ public:
|
||||
virtual void Retranslate() override;
|
||||
|
||||
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
|
||||
virtual ShaderCode GetShaderCode(const QString &shader_id) const override;
|
||||
virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override;
|
||||
|
||||
static const QString kTextureInput;
|
||||
static const QString kColorInput;
|
||||
|
||||
@@ -44,18 +44,13 @@ MatrixGenerator::MatrixGenerator()
|
||||
AddInput(kScaleInput, NodeValue::kVec2, QVector2D(1.0f, 1.0f));
|
||||
SetInputProperty(kScaleInput, QStringLiteral("min"), QVector2D(0, 0));
|
||||
SetInputProperty(kScaleInput, QStringLiteral("view"), FloatSlider::kPercentage);
|
||||
SetInputProperty(kScaleInput, QStringLiteral("disabley"), true);
|
||||
SetInputProperty(kScaleInput, QStringLiteral("disable1"), true);
|
||||
|
||||
AddInput(kUniformScaleInput, NodeValue::kBoolean, true, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
|
||||
|
||||
AddInput(kAnchorInput, NodeValue::kVec2, QVector2D(0.0, 0.0));
|
||||
}
|
||||
|
||||
Node *MatrixGenerator::copy() const
|
||||
{
|
||||
return new MatrixGenerator();
|
||||
}
|
||||
|
||||
QString MatrixGenerator::Name() const
|
||||
{
|
||||
return tr("Orthographic Matrix");
|
||||
@@ -108,47 +103,47 @@ QMatrix4x4 MatrixGenerator::GenerateMatrix(const NodeValueRow &value, bool take,
|
||||
if (!ignore_anchor) {
|
||||
if (take) {
|
||||
// Take and store
|
||||
anchor = value[kAnchorInput].data().value<QVector2D>();
|
||||
anchor = value[kAnchorInput].toVec2();
|
||||
} else {
|
||||
// Get and store
|
||||
anchor = value[kAnchorInput].data().value<QVector2D>();
|
||||
anchor = value[kAnchorInput].toVec2();
|
||||
}
|
||||
} else if (take) {
|
||||
// Just take
|
||||
value[kAnchorInput].data().value<QVector2D>();
|
||||
value[kAnchorInput].toVec2();
|
||||
}
|
||||
|
||||
if (!ignore_scale) {
|
||||
if (take) {
|
||||
scale = value[kScaleInput].data().value<QVector2D>();
|
||||
scale = value[kScaleInput].toVec2();
|
||||
} else {
|
||||
scale = value[kScaleInput].data().value<QVector2D>();
|
||||
scale = value[kScaleInput].toVec2();
|
||||
}
|
||||
} else if (take) {
|
||||
value[kScaleInput].data().value<QVector2D>();
|
||||
value[kScaleInput].toVec2();
|
||||
}
|
||||
|
||||
if (!ignore_position) {
|
||||
if (take) {
|
||||
position = value[kPositionInput].data().value<QVector2D>();
|
||||
position = value[kPositionInput].toVec2();
|
||||
} else {
|
||||
position = value[kPositionInput].data().value<QVector2D>();
|
||||
position = value[kPositionInput].toVec2();
|
||||
}
|
||||
} else if (take) {
|
||||
value[kPositionInput].data().value<QVector2D>();
|
||||
value[kPositionInput].toVec2();
|
||||
}
|
||||
|
||||
if (take) {
|
||||
return GenerateMatrix(position,
|
||||
value[kRotationInput].data().toFloat(),
|
||||
value[kRotationInput].toDouble(),
|
||||
scale,
|
||||
value[kUniformScaleInput].data().toBool(),
|
||||
value[kUniformScaleInput].toBool(),
|
||||
anchor);
|
||||
} else {
|
||||
return GenerateMatrix(position,
|
||||
value[kRotationInput].data().toFloat(),
|
||||
value[kRotationInput].toDouble(),
|
||||
scale,
|
||||
value[kUniformScaleInput].data().toBool(),
|
||||
value[kUniformScaleInput].toBool(),
|
||||
anchor);
|
||||
|
||||
}
|
||||
@@ -188,7 +183,7 @@ void MatrixGenerator::InputValueChangedEvent(const QString &input, int element)
|
||||
Q_UNUSED(element)
|
||||
|
||||
if (input == kUniformScaleInput) {
|
||||
SetInputProperty(kScaleInput, QStringLiteral("disabley"), GetStandardValue(kUniformScaleInput).toBool());
|
||||
SetInputProperty(kScaleInput, QStringLiteral("disable1"), GetStandardValue(kUniformScaleInput).toBool());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,9 +34,7 @@ class MatrixGenerator : public Node
|
||||
public:
|
||||
MatrixGenerator();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(MatrixGenerator)
|
||||
|
||||
virtual Node* copy() const override;
|
||||
NODE_DEFAULT_FUNCTIONS(MatrixGenerator)
|
||||
|
||||
virtual QString Name() const override;
|
||||
virtual QString ShortName() const override;
|
||||
|
||||
@@ -44,11 +44,6 @@ NoiseGeneratorNode::NoiseGeneratorNode()
|
||||
SetFlags(kVideoEffect);
|
||||
}
|
||||
|
||||
Node* NoiseGeneratorNode::copy() const
|
||||
{
|
||||
return new NoiseGeneratorNode();
|
||||
}
|
||||
|
||||
QString NoiseGeneratorNode::Name() const
|
||||
{
|
||||
return tr("Noise");
|
||||
@@ -78,7 +73,7 @@ void NoiseGeneratorNode::Retranslate()
|
||||
SetInputName(kColorInput, tr("Color"));
|
||||
}
|
||||
|
||||
ShaderCode NoiseGeneratorNode::GetShaderCode(const QString& shader_id) const
|
||||
ShaderCode NoiseGeneratorNode::GetShaderCode(const ShaderRequest &request) const
|
||||
{
|
||||
return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/noise.frag"));
|
||||
}
|
||||
@@ -87,8 +82,8 @@ void NoiseGeneratorNode::Value(const NodeValueRow &value, const NodeGlobals &glo
|
||||
{
|
||||
ShaderJob job;
|
||||
|
||||
job.InsertValue(value);
|
||||
job.InsertValue(QStringLiteral("time_in"), NodeValue(NodeValue::kFloat, globals.time().in().toDouble(), this));
|
||||
job.Insert(value);
|
||||
job.Insert(QStringLiteral("time_in"), NodeValue(NodeValue::kFloat, globals.time().in().toDouble(), this));
|
||||
|
||||
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
|
||||
}
|
||||
|
||||
@@ -30,9 +30,7 @@ class NoiseGeneratorNode : public Node {
|
||||
public:
|
||||
NoiseGeneratorNode();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(NoiseGeneratorNode)
|
||||
|
||||
virtual Node *copy() const override;
|
||||
NODE_DEFAULT_FUNCTIONS(NoiseGeneratorNode)
|
||||
|
||||
virtual QString Name() const override;
|
||||
virtual QString id() const override;
|
||||
@@ -41,7 +39,7 @@ class NoiseGeneratorNode : public Node {
|
||||
|
||||
virtual void Retranslate() override;
|
||||
|
||||
virtual ShaderCode GetShaderCode(const QString &shader_id) const override;
|
||||
virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override;
|
||||
virtual void Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const override;
|
||||
|
||||
static const QString kBaseIn;
|
||||
|
||||
@@ -61,11 +61,6 @@ PolygonGenerator::PolygonGenerator()
|
||||
poly_gizmo_ = new PathGizmo(this);
|
||||
}
|
||||
|
||||
Node *PolygonGenerator::copy() const
|
||||
{
|
||||
return new PolygonGenerator();
|
||||
}
|
||||
|
||||
QString PolygonGenerator::Name() const
|
||||
{
|
||||
return tr("Polygon");
|
||||
@@ -94,14 +89,21 @@ void PolygonGenerator::Retranslate()
|
||||
SetInputName(kColorInput, tr("Color"));
|
||||
}
|
||||
|
||||
void PolygonGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||
GenerateJob PolygonGenerator::GetGenerateJob(const NodeValueRow &value) const
|
||||
{
|
||||
GenerateJob job;
|
||||
|
||||
job.InsertValue(value);
|
||||
job.Insert(value);
|
||||
job.SetRequestedFormat(VideoParams::kFormatFloat32);
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
void PolygonGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||
{
|
||||
GenerateJob job = GetGenerateJob(value);
|
||||
|
||||
PushMergableJob(value, QVariant::fromValue(job), table);
|
||||
}
|
||||
|
||||
@@ -114,7 +116,7 @@ void PolygonGenerator::GenerateFrame(FramePtr frame, const GenerateJob &job) con
|
||||
QImage img(frame->width(), frame->height(), QImage::Format_Grayscale8);
|
||||
img.fill(Qt::transparent);
|
||||
|
||||
QVector<NodeValue> points = job.GetValue(kPointsInput).data().value< QVector<NodeValue> >();
|
||||
QVector<NodeValue> points = job.Get(kPointsInput).value< QVector<NodeValue> >();
|
||||
|
||||
QPainterPath path = GeneratePath(points);
|
||||
|
||||
@@ -128,7 +130,7 @@ void PolygonGenerator::GenerateFrame(FramePtr frame, const GenerateJob &job) con
|
||||
p.drawPath(path);
|
||||
|
||||
// Transplant alpha channel to frame
|
||||
Color rgba = job.GetValue(kColorInput).data().value<Color>();
|
||||
Color rgba = job.Get(kColorInput).toColor();
|
||||
#if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM)
|
||||
__m128 sse_color = _mm_loadu_ps(rgba.data());
|
||||
#endif
|
||||
@@ -194,7 +196,7 @@ void PolygonGenerator::UpdateGizmoPositions(const NodeValueRow &row, const NodeG
|
||||
{
|
||||
QPointF half_res(globals.resolution_by_par().x()/2, globals.resolution_by_par().y()/2);
|
||||
|
||||
QVector<NodeValue> points = row[kPointsInput].data().value< QVector<NodeValue> >();
|
||||
QVector<NodeValue> points = row[kPointsInput].value< QVector<NodeValue> >();
|
||||
|
||||
int current_pos_sz = gizmo_position_handles_.size();
|
||||
|
||||
@@ -221,7 +223,7 @@ void PolygonGenerator::UpdateGizmoPositions(const NodeValueRow &row, const NodeG
|
||||
|
||||
if (!points.isEmpty()) {
|
||||
for (int i=0; i<points.size(); i++) {
|
||||
const Bezier &pt = points.at(i).data().value<Bezier>();
|
||||
const Bezier &pt = points.at(i).toBezier();
|
||||
|
||||
QPointF main = pt.ToPointF() + half_res;
|
||||
QPointF cp1 = main + pt.ControlPoint1ToPointF();
|
||||
@@ -265,14 +267,14 @@ QPainterPath PolygonGenerator::GeneratePath(const QVector<NodeValue> &points)
|
||||
QPainterPath path;
|
||||
|
||||
if (!points.isEmpty()) {
|
||||
const Bezier &first_pt = points.first().data().value<Bezier>();
|
||||
const Bezier &first_pt = points.first().toBezier();
|
||||
path.moveTo(first_pt.ToPointF());
|
||||
|
||||
for (int i=1; i<points.size(); i++) {
|
||||
AddPointToPath(&path, points.at(i-1).data().value<Bezier>(), points.at(i).data().value<Bezier>());
|
||||
AddPointToPath(&path, points.at(i-1).toBezier(), points.at(i).toBezier());
|
||||
}
|
||||
|
||||
AddPointToPath(&path, points.last().data().value<Bezier>(), first_pt);
|
||||
AddPointToPath(&path, points.last().toBezier(), first_pt);
|
||||
}
|
||||
|
||||
return path;
|
||||
|
||||
@@ -39,9 +39,7 @@ class PolygonGenerator : public GeneratorWithMerge
|
||||
public:
|
||||
PolygonGenerator();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(PolygonGenerator)
|
||||
|
||||
virtual Node* copy() const override;
|
||||
NODE_DEFAULT_FUNCTIONS(PolygonGenerator)
|
||||
|
||||
virtual QString Name() const override;
|
||||
virtual QString id() const override;
|
||||
@@ -59,6 +57,9 @@ public:
|
||||
static const QString kPointsInput;
|
||||
static const QString kColorInput;
|
||||
|
||||
protected:
|
||||
GenerateJob GetGenerateJob(const NodeValueRow &value) const;
|
||||
|
||||
protected slots:
|
||||
virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override;
|
||||
|
||||
|
||||
@@ -42,9 +42,9 @@ void GeneratorWithMerge::Retranslate()
|
||||
SetInputName(kBaseInput, tr("Base"));
|
||||
}
|
||||
|
||||
ShaderCode GeneratorWithMerge::GetShaderCode(const QString &shader_id) const
|
||||
ShaderCode GeneratorWithMerge::GetShaderCode(const ShaderRequest &request) const
|
||||
{
|
||||
if (shader_id == QStringLiteral("mrg")) {
|
||||
if (request.id == QStringLiteral("mrg")) {
|
||||
return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/alphaover.frag"));
|
||||
}
|
||||
|
||||
@@ -53,13 +53,13 @@ ShaderCode GeneratorWithMerge::GetShaderCode(const QString &shader_id) const
|
||||
|
||||
void GeneratorWithMerge::PushMergableJob(const NodeValueRow &value, const QVariant &job, NodeValueTable *table) const
|
||||
{
|
||||
if (!value[kBaseInput].data().isNull()) {
|
||||
if (value[kBaseInput].toTexture()) {
|
||||
// Push as merge node
|
||||
ShaderJob merge;
|
||||
|
||||
merge.SetShaderID(QStringLiteral("mrg"));
|
||||
merge.InsertValue(MergeNode::kBaseIn, value[kBaseInput]);
|
||||
merge.InsertValue(MergeNode::kBlendIn, NodeValue(NodeValue::kTexture, job, this));
|
||||
merge.Insert(MergeNode::kBaseIn, value[kBaseInput]);
|
||||
merge.Insert(MergeNode::kBlendIn, NodeValue(NodeValue::kTexture, job, this));
|
||||
|
||||
table->Push(NodeValue::kTexture, QVariant::fromValue(merge), this);
|
||||
} else {
|
||||
|
||||
@@ -31,11 +31,9 @@ class GeneratorWithMerge : public Node
|
||||
public:
|
||||
GeneratorWithMerge();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(GeneratorWithMerge)
|
||||
|
||||
virtual void Retranslate() override;
|
||||
|
||||
virtual ShaderCode GetShaderCode(const QString &shader_id) const override;
|
||||
virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override;
|
||||
|
||||
static const QString kBaseInput;
|
||||
|
||||
|
||||
@@ -61,12 +61,12 @@ void ShapeNode::Retranslate()
|
||||
SetComboBoxStrings(kTypeInput, {tr("Rectangle"), tr("Ellipse")});
|
||||
}
|
||||
|
||||
ShaderCode ShapeNode::GetShaderCode(const QString &shader_id) const
|
||||
ShaderCode ShapeNode::GetShaderCode(const ShaderRequest &request) const
|
||||
{
|
||||
if (shader_id == QStringLiteral("shape")) {
|
||||
if (request.id == QStringLiteral("shape")) {
|
||||
return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/shape.frag")));
|
||||
} else {
|
||||
return super::GetShaderCode(shader_id);
|
||||
return super::GetShaderCode(request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,8 +74,8 @@ void ShapeNode::Value(const NodeValueRow &value, const NodeGlobals &globals, Nod
|
||||
{
|
||||
ShaderJob job;
|
||||
|
||||
job.InsertValue(value);
|
||||
job.InsertValue(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this));
|
||||
job.Insert(value);
|
||||
job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this));
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
|
||||
job.SetShaderID(QStringLiteral("shape"));
|
||||
|
||||
|
||||
@@ -36,8 +36,7 @@ public:
|
||||
kEllipse
|
||||
};
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(ShapeNode)
|
||||
NODE_COPY_FUNCTION(ShapeNode)
|
||||
NODE_DEFAULT_FUNCTIONS(ShapeNode)
|
||||
|
||||
virtual QString Name() const override;
|
||||
virtual QString id() const override;
|
||||
@@ -46,7 +45,7 @@ public:
|
||||
|
||||
virtual void Retranslate() override;
|
||||
|
||||
virtual ShaderCode GetShaderCode(const QString& shader_id) const override;
|
||||
virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override;
|
||||
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
|
||||
|
||||
static QString kTypeInput;
|
||||
|
||||
@@ -79,8 +79,8 @@ void ShapeNodeBase::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlob
|
||||
QVector2D center_pt = globals.resolution() * 0.5;
|
||||
SetInputProperty(kPositionInput, QStringLiteral("offset"), center_pt);
|
||||
|
||||
QVector2D pos = row[kPositionInput].data().value<QVector2D>();
|
||||
QVector2D sz = row[kSizeInput].data().value<QVector2D>();
|
||||
QVector2D pos = row[kPositionInput].toVec2();
|
||||
QVector2D sz = row[kSizeInput].toVec2();
|
||||
QVector2D half_sz = sz * 0.5;
|
||||
|
||||
double left_pt = pos.x() + center_pt.x() - half_sz.x();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user