Merge branch 'master' into pr/1717

This commit is contained in:
itsmattkc
2022-05-01 10:58:44 -07:00
1583 changed files with 89018 additions and 321451 deletions
+82 -27
View File
@@ -50,7 +50,7 @@ jobs:
${{ matrix.cmake-gen }}>
runs-on: ubuntu-latest
container:
image: olivevideoeditor/ci-olive:2021.4
image: olivevideoeditor/ci-olive:2022.1
steps:
- name: Checkout Source Code
@@ -285,11 +285,20 @@ jobs:
os-arch: x86_64
os: macos-10.15
cmake-gen: Ninja
min-deploy: 10.13
- build-type: RelWithDebInfo
compiler-name: Clang LLVM
os-name: macOS
os-arch: arm64
os: macos-11.0
cmake-gen: Ninja
min-deploy: 11.0
env:
DEP_LOCATION: /opt/olive-editor
name: |
${{ matrix.os-name }}
<${{ matrix.compiler-name }},
<${{ matrix.os-arch }},
${{ matrix.compiler-name }},
${{ matrix.build-type }},
${{ matrix.cmake-gen }}>
runs-on: ${{ matrix.os }}
@@ -314,15 +323,16 @@ jobs:
shell: bash
working-directory: ${{ runner.workspace }}
run: |
$DOWNLOAD_TOOL https://github.com/olive-editor/dependencies/releases/download/continuous/olive-dep-mac-x86_64.tar.gz
sudo tar xzf olive-dep-mac-x86_64.tar.gz -C /
$DOWNLOAD_TOOL https://github.com/olive-editor/dependencies/releases/download/continuous/olive-dep-mac-${{ matrix.os-arch }}.tar.gz
sudo tar xzf olive-dep-mac-${{ matrix.os-arch }}.tar.gz -C /
- name: Acquire Google Crashpad
shell: bash
working-directory: ${{ runner.workspace }}
run: |
$DOWNLOAD_TOOL https://github.com/olive-editor/crashpad/releases/download/continuous/crashpad-mac-x86_64.tar.gz
sudo tar xzf crashpad-mac-x86_64.tar.gz -C /
$DOWNLOAD_TOOL https://github.com/olive-editor/crashpad/releases/download/continuous/crashpad-mac-${{ matrix.os-arch }}.tar.gz
sudo tar xzf crashpad-mac-${{ matrix.os-arch }}.tar.gz -C /
if: matrix.os-arch == 'x86_64' # TEMPORARY: Crashpad isn't compiling on ARM64 yet for some reason
- name: Generate Patreon List
env:
@@ -341,7 +351,8 @@ jobs:
brew install ninja
PATH=$DEP_LOCATION:$DEP_LOCATION/bin:$DEP_LOCATION/include:$DEP_LOCATION/lib:$DEP_LOCATION/crashpad:$PATH \
cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 -G "${{ matrix.cmake-gen }}"
-DCMAKE_OSX_DEPLOYMENT_TARGET=${{ matrix.min-deploy }} -G "${{ matrix.cmake-gen }}" \
-DCMAKE_OSX_ARCHITECTURES="${{ matrix.os-arch }}"
- name: Build
working-directory: ${{ runner.workspace }}/build
@@ -354,34 +365,78 @@ jobs:
shell: bash
run: |
ctest -C ${{ matrix.build-type }} -V
if: matrix.os-arch == 'x86_64' # ARM64 tests naturally won't be able to run on x86_64 runners
- name: Create Package
working-directory: ${{ runner.workspace }}/build
env:
BUNDLE_NAME: "Olive.app"
shell: bash
run: |
# Use macdeployqt and macdeployqt to bundle dependencies
BUNDLE_NAME="Olive.app"
brew install dylibbundler
if [ "${{ matrix.os-arch }}" == "x86_64" ]
then
DYLIBBUNDLER_EXTRA_ARGS="-x $BUNDLE_NAME/Contents/MacOS/olive-crashhandler"
fi
mv app/$BUNDLE_NAME .
$DEP_LOCATION/bin/macdeployqt $BUNDLE_NAME -executable=$BUNDLE_NAME/Contents/MacOS/olive-crashhandler
$DOWNLOAD_TOOL https://github.com/arl/macdeployqtfix/raw/master/macdeployqtfix.py
python2 macdeployqtfix.py $BUNDLE_NAME/Contents/MacOS/Olive $VCPKG_ROOT/installed/x64-osx
mkdir $BUNDLE_NAME/Contents/Frameworks
dylibbundler -b -ns -x "$BUNDLE_NAME/Contents/MacOS/Olive" -s "$DEP_LOCATION/lib" -d "$BUNDLE_NAME/Contents/Frameworks" -p "@executable_path/../Frameworks" $DYLIBBUNDLER_EXTRA_ARGS
# Manual corrections
for f in $BUNDLE_NAME/Contents/Frameworks/*.dylib
do
for r in $(otool -L $f | grep "@rpath" | awk '{print $1;}')
do
install_name_tool -change $r ${r//rpath/loader_path} $f
done
done
# Copy Qt frameworks and plugins
cp -Ra $DEP_LOCATION/lib/Qt*.framework $BUNDLE_NAME/Contents/Frameworks
cp -Ra $DEP_LOCATION/plugins $BUNDLE_NAME/Contents
# Crashpad symbols
$DEP_LOCATION/bin/dump_syms $BUNDLE_NAME/Contents/MacOS/Olive > Olive.sym
SYM_HEADER=($(head -n 1 Olive.sym)) # Read first line of symbol file
SYM_DIR=$BUNDLE_NAME/Contents/Resources/symbols/Olive/${SYM_HEADER[3]}
mkdir -p "$SYM_DIR"
mv Olive.sym "$SYM_DIR"
# HACK: On x86_64, dylibbundler doesn't resolve this symlink. Weirdly it does on ARM64,
# but perhaps I'll bring it up with them soon.
cp -a $BUNDLE_NAME/Contents/Frameworks/libpng16.16.37.0.dylib $BUNDLE_NAME/Contents/Frameworks/libpng16.16.dylib
if [ "${{ matrix.os-arch }}" == "x86_64" ]
then
# Crashpad symbols
$DEP_LOCATION/bin/dump_syms $BUNDLE_NAME/Contents/MacOS/Olive > Olive.sym
SYM_HEADER=($(head -n 1 Olive.sym)) # Read first line of symbol file
SYM_DIR=$BUNDLE_NAME/Contents/Resources/symbols/Olive/${SYM_HEADER[3]}
mkdir -p "$SYM_DIR"
mv Olive.sym "$SYM_DIR"
fi
- name: Sign Application
working-directory: ${{ runner.workspace }}/build
shell: bash
if: github.event_name == 'push'
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
BUNDLE_NAME="Olive.app"
# Install certificate
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# import certificate from secrets
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode --output $CERTIFICATE_PATH
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
# HACK: Remove unsignable frameworks
rm -r $BUNDLE_NAME/Contents/Frameworks/QtUiPlugin.framework
if [ "${{ matrix.os-arch }}" == "arm64" ]
then
rm -r $BUNDLE_NAME/Contents/Frameworks/QtZlib.framework
fi
# Sign application
codesign --deep --sign "Developer ID Application: Olive Studios LLC" $BUNDLE_NAME
- name: Deploy Packages
working-directory: ${{ runner.workspace }}/build
+1 -1
View File
@@ -74,7 +74,7 @@ find_package(OpenGL REQUIRED)
list(APPEND OLIVE_LIBRARIES OpenGL::GL)
# Link OpenColorIO
find_package(OpenColorIO 2.0.0 REQUIRED)
find_package(OpenColorIO 2.1.1 REQUIRED)
list(APPEND OLIVE_LIBRARIES ${OCIO_LIBRARIES})
list(APPEND OLIVE_INCLUDE_DIRS ${OCIO_INCLUDE_DIRS})
+2
View File
@@ -22,6 +22,8 @@ set(OLIVE_SOURCES
audio/audiovisualwaveform.h
audio/packedprocessor.cpp
audio/packedprocessor.h
audio/planarprocessor.cpp
audio/planarprocessor.h
audio/tempoprocessor.cpp
audio/tempoprocessor.h
PARENT_SCOPE
+3 -8
View File
@@ -76,17 +76,12 @@ QByteArray PackedProcessor::Convert(SampleBufferPtr planar)
return QByteArray();
}
int nb_channels = planar->audio_params().channel_count();
QByteArray output(planar->audio_params().samples_to_bytes(nb_samples), Qt::Uninitialized);
uint8_t *output_data = reinterpret_cast<uint8_t*>(output.data());
QVector<const uint8_t*> input_arrays(nb_channels);
for (int i=0; i<nb_channels; i++) {
input_arrays[i] = reinterpret_cast<const uint8_t*>(planar->data(i));
}
int ret = swr_convert(swr_ctx_, &output_data, nb_samples, input_arrays.data(), nb_samples);
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);
+104
View File
@@ -0,0 +1,104 @@
/***
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 &params)
{
if (IsOpen()) {
return true;
}
swr_ctx_ = swr_alloc_set_opts(nullptr,
params.channel_layout(),
FFmpegUtils::GetFFmpegSampleFormat(params.format(), true),
params.sample_rate(),
params.channel_layout(),
FFmpegUtils::GetFFmpegSampleFormat(params.format(), false),
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_);
}
}
}
+62
View File
@@ -0,0 +1,62 @@
/***
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 PLANARPROCESSOR_H
#define PLANARPROCESSOR_H
extern "C" {
#include <libswresample/swresample.h>
}
#include "codec/samplebuffer.h"
#include "render/audioparams.h"
namespace olive {
class PlanarProcessor
{
public:
PlanarProcessor();
~PlanarProcessor();
DISABLE_COPY_MOVE(PlanarProcessor)
bool Open(const AudioParams &params);
SampleBufferPtr Convert(const QByteArray &packed);
void Close();
bool IsOpen() const
{
return swr_ctx_;
}
private:
SwrContext *swr_ctx_;
AudioParams params_;
};
}
#endif // PLANARPROCESSOR_H
+56 -68
View File
@@ -36,7 +36,6 @@ TempoProcessor::TempoProcessor() :
filter_graph_(nullptr),
buffersrc_ctx_(nullptr),
buffersink_ctx_(nullptr),
processed_frame_(nullptr),
open_(false)
{
}
@@ -150,47 +149,42 @@ bool TempoProcessor::Open(const AudioParams &params, const double& speed)
return true;
}
void TempoProcessor::Push(const char *data, int length)
void TempoProcessor::Push(const QByteArray &packed)
{
if (flushed_) {
if (length > 0) {
qCritical() << "Tried to push" << length << "bytes after TempoProcessor was closed";
}
if (!IsOpen()) {
qWarning() << "Tried to push to closed TempoProcessor";
return;
}
AVFrame* src_frame;
if (length == 0) {
// No audio data, flush the last out of the filter graph
src_frame = nullptr;
flushed_ = true;
} else {
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(length);
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], data, static_cast<size_t>(length));
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) {
@@ -202,46 +196,45 @@ void TempoProcessor::Push(const char *data, int length)
}
}
int TempoProcessor::Pull(char *data, int max_length)
void TempoProcessor::Flush()
{
if (!processed_frame_) {
processed_frame_ = av_frame_alloc();
// Try to pull samples from the buffersink
int ret = av_buffersink_get_frame(buffersink_ctx_, processed_frame_);
if (!flushed_) {
int ret = av_buffersrc_add_frame_flags(buffersrc_ctx_, nullptr, AV_BUFFERSRC_FLAG_KEEP_REF);
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;
}
qCritical() << "Failed to feed buffer source" << ret;
}
flushed_ = true;
}
}
av_frame_free(&processed_frame_);
QByteArray TempoProcessor::Pull()
{
QByteArray b;
AVFrame *processed_frame = av_frame_alloc();
return 0;
// 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;
}
processed_frame_byte_index_ = 0;
processed_frame_max_bytes_ = params_.samples_to_bytes(processed_frame_->nb_samples);
av_frame_free(&processed_frame);
return b;
}
// Determine how many bytes we should copy into the data array
int copy_length = qMin(max_length, processed_frame_max_bytes_ - processed_frame_byte_index_);
b.resize(params_.samples_to_bytes(processed_frame->nb_samples));
// Copy the bytes
memcpy(data, processed_frame_->data[0] + processed_frame_byte_index_, static_cast<size_t>(copy_length));
// Add the copied amount to the current index
processed_frame_byte_index_ += copy_length;
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
if (processed_frame_byte_index_ == processed_frame_max_bytes_) {
av_frame_free(&processed_frame_);
processed_frame_ = nullptr;
}
av_frame_free(&processed_frame);
return copy_length;
return b;
}
void TempoProcessor::Close()
@@ -253,11 +246,6 @@ void TempoProcessor::Close()
filter_graph_ = nullptr;
}
if (processed_frame_) {
av_frame_free(&processed_frame_);
processed_frame_ = nullptr;
}
buffersrc_ctx_ = nullptr;
buffersink_ctx_ = nullptr;
}
+4 -6
View File
@@ -52,9 +52,11 @@ public:
bool Open(const AudioParams& params, const double &speed);
void Push(const char *data, int length);
void Push(const QByteArray &packed);
int Pull(char* data, int max_length);
void Flush();
QByteArray Pull();
void Close();
@@ -67,10 +69,6 @@ private:
AVFilterContext* buffersink_ctx_;
AVFrame* processed_frame_;
int processed_frame_byte_index_;
int processed_frame_max_bytes_;
AudioParams params_;
int64_t timestamp_;
+3
View File
@@ -44,6 +44,8 @@ QString ExportCodec::GetCodecName(ExportCodec::Codec c)
return tr("PNG");
case kCodecProRes:
return tr("ProRes");
case kCodecCineform:
return tr("Cineform");
case kCodecTIFF:
return tr("TIFF");
case kCodecMP2:
@@ -79,6 +81,7 @@ bool ExportCodec::IsCodecAStillImage(ExportCodec::Codec c)
case kCodecH264rgb:
case kCodecH265:
case kCodecProRes:
case kCodecCineform:
case kCodecMP2:
case kCodecMP3:
case kCodecAAC:
+1
View File
@@ -42,6 +42,7 @@ public:
kCodecOpenEXR,
kCodecPNG,
kCodecProRes,
kCodecCineform,
kCodecTIFF,
kCodecVP9,
+1 -1
View File
@@ -117,7 +117,7 @@ QList<ExportCodec::Codec> ExportFormat::GetVideoCodecs(ExportFormat::Format f)
case kFormatTIFF:
return {ExportCodec::kCodecTIFF};
case kFormatQuickTime:
return {ExportCodec::kCodecH264, ExportCodec::kCodecH264rgb, ExportCodec::kCodecH265, ExportCodec::kCodecProRes};
return {ExportCodec::kCodecH264, ExportCodec::kCodecH264rgb, ExportCodec::kCodecH265, ExportCodec::kCodecProRes, ExportCodec::kCodecCineform};
case kFormatWebM:
return {ExportCodec::kCodecVP9};
case kFormatOgg:
+7 -7
View File
@@ -268,7 +268,7 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, const QAtomicIn
AVStream* avstream = fmt_ctx->streams[i];
// Find decoder for this stream, if it exists we can proceed
AVCodec* decoder = avcodec_find_decoder(avstream->codecpar->codec_id);
const AVCodec* decoder = avcodec_find_decoder(avstream->codecpar->codec_id);
if (decoder
&& (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO
@@ -323,7 +323,7 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, const QAtomicIn
int64_t new_dur;
do {
new_dur = frame->pts;
new_dur = frame->best_effort_timestamp;
} while (instance.GetFrame(pkt, frame) >= 0);
avstream->duration = new_dur;
@@ -388,7 +388,7 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, const QAtomicIn
int64_t new_dur;
do {
new_dur = frame->pts;
new_dur = frame->best_effort_timestamp;
} while (instance.GetFrame(pkt, frame) >= 0);
avstream->duration = new_dur;
@@ -540,7 +540,7 @@ bool FFmpegDecoder::ConformAudioInternal(const QVector<QString> &filenames, cons
break;
}
SignalProcessingProgress(frame->pts, duration);
SignalProcessingProgress(frame->best_effort_timestamp, duration);
}
wave_out.close();
@@ -733,7 +733,7 @@ FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time, c
if (still_seeking) {
// Handle a failure to seek (occurs on some media)
// We'll only be here if the frame cache was emptied earlier
if (!cache_at_zero_ && (ret == AVERROR_EOF || working_frame->pts > target_ts)) {
if (!cache_at_zero_ && (ret == AVERROR_EOF || working_frame->best_effort_timestamp > target_ts)) {
seek_ts = qMax(min_seek, seek_ts - second_ts_);
instance_.Seek(seek_ts);
@@ -784,7 +784,7 @@ FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time, c
av_image_copy(&destination_data, &destination_linesize, const_cast<const uint8_t**>(working_frame->data), working_frame->linesize, static_cast<AVPixelFormat>(working_frame->format), working_frame->width, working_frame->height);
// Set timestamp so this frame can be identified later
cached->set_timestamp(working_frame->pts);
cached->set_timestamp(working_frame->best_effort_timestamp);
// Store frame before just in case
FFmpegFramePool::ElementPtr previous;
@@ -1010,7 +1010,7 @@ bool FFmpegDecoder::Instance::Open(const char *filename, int stream_index)
avstream_ = fmt_ctx_->streams[stream_index];
// Find decoder
AVCodec* codec = avcodec_find_decoder(avstream_->codecpar->codec_id);
const AVCodec* codec = avcodec_find_decoder(avstream_->codecpar->codec_id);
// Handle failure to find decoder
if (codec == nullptr) {
+7 -5
View File
@@ -50,7 +50,7 @@ QStringList FFmpegEncoder::GetPixelFormatsForCodec(ExportCodec::Codec c) const
{
QStringList pix_fmts;
AVCodec* codec_info = GetEncoder(c);
const AVCodec* codec_info = GetEncoder(c);
if (codec_info) {
for (int i=0; codec_info->pix_fmts[i]!=-1; i++) {
@@ -570,7 +570,7 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV
}
// Find encoder
AVCodec* encoder = GetEncoder(codec);
const AVCodec* encoder = GetEncoder(codec);
if (!encoder) {
SetError(tr("Failed to find codec for 0x%1").arg(codec, 16));
return false;
@@ -669,7 +669,7 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV
return true;
}
bool FFmpegEncoder::InitializeCodecContext(AVStream **stream, AVCodecContext **codec_ctx, AVCodec* codec)
bool FFmpegEncoder::InitializeCodecContext(AVStream **stream, AVCodecContext **codec_ctx, const AVCodec* codec)
{
*stream = avformat_new_stream(fmt_ctx_, nullptr);
if (!(*stream)) {
@@ -687,7 +687,7 @@ bool FFmpegEncoder::InitializeCodecContext(AVStream **stream, AVCodecContext **c
return true;
}
bool FFmpegEncoder::SetupCodecContext(AVStream* stream, AVCodecContext* codec_ctx, AVCodec* codec)
bool FFmpegEncoder::SetupCodecContext(AVStream* stream, AVCodecContext* codec_ctx, const AVCodec* codec)
{
int error_code;
@@ -833,7 +833,7 @@ bool FFmpegEncoder::InitializeResampleContext(SampleBufferPtr audio)
return true;
}
AVCodec *FFmpegEncoder::GetEncoder(ExportCodec::Codec c)
const AVCodec *FFmpegEncoder::GetEncoder(ExportCodec::Codec c)
{
switch (c) {
case ExportCodec::kCodecH264:
@@ -844,6 +844,8 @@ AVCodec *FFmpegEncoder::GetEncoder(ExportCodec::Codec c)
return avcodec_find_encoder(AV_CODEC_ID_DNXHD);
case ExportCodec::kCodecProRes:
return avcodec_find_encoder(AV_CODEC_ID_PRORES);
case ExportCodec::kCodecCineform:
return avcodec_find_encoder(AV_CODEC_ID_CFHD);
case ExportCodec::kCodecH265:
return avcodec_find_encoder(AV_CODEC_ID_HEVC);
case ExportCodec::kCodecVP9:
+4 -3
View File
@@ -22,6 +22,7 @@
#define FFMPEGENCODER_H
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
@@ -69,15 +70,15 @@ private:
bool WriteAVFrame(AVFrame* frame, AVCodecContext *codec_ctx, AVStream *stream);
bool InitializeStream(enum AVMediaType type, AVStream** stream, AVCodecContext** codec_ctx, const ExportCodec::Codec &codec);
bool InitializeCodecContext(AVStream** stream, AVCodecContext** codec_ctx, AVCodec* codec);
bool SetupCodecContext(AVStream *stream, AVCodecContext *codec_ctx, AVCodec *codec);
bool InitializeCodecContext(AVStream** stream, AVCodecContext** codec_ctx, const AVCodec* codec);
bool SetupCodecContext(AVStream *stream, AVCodecContext *codec_ctx, const AVCodec *codec);
void FlushEncoders();
void FlushCodecCtx(AVCodecContext* codec_ctx, AVStream *stream);
bool InitializeResampleContext(SampleBufferPtr audio);
static AVCodec *GetEncoder(ExportCodec::Codec c);
static const AVCodec *GetEncoder(ExportCodec::Codec c);
AVFormatContext* fmt_ctx_;
+3
View File
@@ -37,6 +37,8 @@ set(OLIVE_SOURCES
common/flipmodifiers.cpp
common/flipmodifiers.h
common/functiontimer.h
common/html.cpp
common/html.h
common/jobtime.cpp
common/jobtime.h
common/lerp.h
@@ -61,6 +63,7 @@ set(OLIVE_SOURCES
common/timerange.cpp
common/timerange.h
common/tohex.h
common/util.h
common/xmlutils.cpp
common/xmlutils.h
PARENT_SCOPE
+1
View File
@@ -22,6 +22,7 @@
#define FFMPEGABSTRACTION_H
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
+7 -21
View File
@@ -139,7 +139,10 @@ void FileFunctions::CopyDirectory(const QString &source, const QString &dest, bo
} else {
// Copy file
if (overwrite && QFile::exists(dest_file_path)) {
QFile::remove(dest_file_path);
QFile file(dest_file_path);
file.setPermissions(file.permissions() | QFileDevice::WriteOwner | QFileDevice::WriteUser |
QFileDevice::WriteGroup | QFileDevice::WriteOther);
file.remove();
}
QFile::copy(info.absoluteFilePath(), dest_file_path);
@@ -147,27 +150,10 @@ void FileFunctions::CopyDirectory(const QString &source, const QString &dest, bo
}
}
bool FileFunctions::DirectoryIsValid(const QString &dir, bool try_to_create)
bool FileFunctions::DirectoryIsValid(const QDir &d, bool try_to_create_if_not_exists)
{
// Empty string is invalid
if (dir.isEmpty()) {
return false;
}
QDir d(dir);
// If directory already exists, this is valid
if (d.exists()) {
return true;
}
// If we can create and creation is successful, this is valid
if (try_to_create && d.mkpath(".")) {
return true;
}
// Otherwise, invalid
return false;
// Return whether the directory exists, or whether it could be created if it doesn't
return d.exists() || d.mkpath(QStringLiteral("."));
}
QString FileFunctions::EnsureFilenameExtension(QString fn, const QString &extension)
+2 -1
View File
@@ -21,6 +21,7 @@
#ifndef FILEFUNCTIONS_H
#define FILEFUNCTIONS_H
#include <QDir>
#include <QString>
#include "common/define.h"
@@ -52,7 +53,7 @@ public:
static void CopyDirectory(const QString& source, const QString& dest, bool overwrite = false);
static bool DirectoryIsValid(const QString& dir, bool try_to_create);
static bool DirectoryIsValid(const QDir& dir, bool try_to_create_if_not_exists = true);
/**
* @brief Ensures a given filename has a certain extension
+469
View File
@@ -0,0 +1,469 @@
#include "html.h"
#include <QDebug>
#include <QTextBlock>
#include "xmlutils.h"
namespace olive {
const QVector<QString> Html::kBlockTags = {
QStringLiteral("p"),
QStringLiteral("div")
};
inline bool StrEquals(const QString &a, const QStringRef &b)
{
return !a.compare(b, Qt::CaseInsensitive);
}
inline bool StrEquals(const QString &a, const QString &b)
{
return !a.compare(b, Qt::CaseInsensitive);
}
inline bool StrEquals(const QStringRef &a, const QString &b)
{
return !a.compare(b, Qt::CaseInsensitive);
}
inline bool StrEquals(const QStringRef &a, const QStringRef &b)
{
return !a.compare(b, Qt::CaseInsensitive);
}
QString Html::DocToHtml(const QTextDocument *doc)
{
QString html;
QXmlStreamWriter writer(&html);
//writer.setAutoFormatting(true);
for (auto it=doc->begin(); it!=doc->end(); it=it.next()) {
WriteBlock(&writer, it);
}
return html;
}
struct HtmlNode {
QString tag;
QTextCharFormat format;
};
QTextCharFormat MergeHtmlFormats(const QVector<HtmlNode> &stack)
{
QTextCharFormat f;
for (int i=0; i<stack.size(); i++) {
f.merge(stack.at(i).format);
}
return f;
}
void Html::HtmlToDoc(QTextDocument *doc, const QString &html)
{
// Empty doc
doc->clear();
bool inside_block = true;
// Create cursor, which appears to be Qt's official way of inserting blocks and fragments
QTextCursor c(doc);
QString wrapped = QStringLiteral("<html>").append(html).append("</html>");
QXmlStreamReader reader(wrapped);
QVector<HtmlNode> fmt_stack;
QTextCharFormat default_fmt;
default_fmt.setFontWeight(QFont::Normal);
fmt_stack.append({QStringLiteral("html"), default_fmt});
QTextCharFormat current_fmt;
while (!reader.atEnd()) {
reader.readNext();
if (reader.tokenType() == QXmlStreamReader::StartElement) {
QString tag = reader.name().toString().toLower();
fmt_stack.append({tag, ReadCharFormat(reader.attributes())});
current_fmt = MergeHtmlFormats(fmt_stack);
if (kBlockTags.contains(tag)) {
QTextBlockFormat block_fmt = ReadBlockFormat(reader.attributes());
if (inside_block) {
c.setBlockFormat(block_fmt);
c.setBlockCharFormat(current_fmt);
} else {
c.insertBlock(block_fmt, current_fmt);
inside_block = true;
}
}
} else if (reader.tokenType() == QXmlStreamReader::Characters) {
QString characters = reader.text().toString();
c.insertText(characters, current_fmt);
} else if (reader.tokenType() == QXmlStreamReader::EndElement) {
QString tag = reader.name().toString().toLower();
for (int i=fmt_stack.size()-1; i>=0; i--) {
if (fmt_stack.at(i).tag == tag) {
fmt_stack.removeAt(i);
current_fmt = MergeHtmlFormats(fmt_stack);
if (kBlockTags.contains(tag)) {
inside_block = false;
}
break;
}
}
}
}
if (reader.error()) {
qCritical() << "Failed to parse HTML:" << reader.errorString();
}
}
void Html::WriteBlock(QXmlStreamWriter *writer, const QTextBlock &block)
{
writer->writeStartElement(QStringLiteral("p"));
const QTextBlockFormat &fmt = block.blockFormat();
// Write block alignment
if (!(fmt.alignment() & Qt::AlignLeft)) {
if (fmt.alignment() & Qt::AlignRight) {
writer->writeAttribute(QStringLiteral("align"), QStringLiteral("right"));
} else if (fmt.alignment() & Qt::AlignCenter) {
writer->writeAttribute(QStringLiteral("align"), QStringLiteral("center"));
} else if (fmt.alignment() & Qt::AlignJustify) {
writer->writeAttribute(QStringLiteral("align"), QStringLiteral("justify"));
}
}
// RTL support
if (block.textDirection() == Qt::RightToLeft) {
writer->writeAttribute(QStringLiteral("dir"), QStringLiteral("rtl"));
}
// Write CSS attributes
QString style;
if (fmt.lineHeightType() != QTextBlockFormat::SingleHeight) {
WriteCSSProperty(&style, QStringLiteral("line-height"), QStringLiteral("%1%").arg(fmt.lineHeight()));
}
//WriteCharFormat(&style, block.charFormat());
if (!style.isEmpty()) {
writer->writeAttribute(QStringLiteral("style"), style);
}
auto it = block.begin();
if (it == block.end()) {
// FIXME: Might not be necessary with our custom HTML implementation
QString s;
s.append(QChar::Nbsp);
writer->writeCharacters(s);
} else {
for (; it!=block.end(); it++) {
WriteFragment(writer, it.fragment());
}
}
writer->writeEndElement(); // p
}
void Html::WriteFragment(QXmlStreamWriter *writer, const QTextFragment &fragment)
{
const QTextCharFormat &fmt = fragment.charFormat();
writer->writeStartElement(QStringLiteral("span"));
// Write CSS attributes
QString style;
WriteCharFormat(&style, fmt);
if (!style.isEmpty()) {
writer->writeAttribute(QStringLiteral("style"), style);
}
QStringList lines = fragment.text().split(QChar::LineSeparator);
bool first_line = true;
foreach (const QString &l, lines) {
if (first_line) {
first_line = false;
} else {
writer->writeEmptyElement(QStringLiteral("br"));
}
writer->writeCharacters(l);
}
writer->writeEndElement(); // span
}
void Html::WriteCSSProperty(QString *style, const QString &key, const QStringList &values)
{
QString value;
foreach (QString v, values) {
if (v.contains(' ')) {
v = QStringLiteral("'%1'").arg(v);
}
AppendStringAutoSpace(&value, v);
}
AppendStringAutoSpace(style, QStringLiteral("%1: %2;").arg(key, value));
}
void Html::WriteCharFormat(QString *style, const QTextCharFormat &fmt)
{
if (!fmt.fontFamily().isEmpty()) {
WriteCSSProperty(style, QStringLiteral("font-family"), fmt.fontFamily());
}
if (fmt.hasProperty(QTextFormat::FontPointSize)) {
WriteCSSProperty(style, QStringLiteral("font-size"), QStringLiteral("%1pt").arg(QString::number(fmt.fontPointSize())));
}
if (fmt.hasProperty(QTextFormat::FontWeight)) {
WriteCSSProperty(style, QStringLiteral("font-weight"), QString::number(fmt.fontWeight() * 8));
}
if (fmt.hasProperty(QTextFormat::FontItalic)) {
WriteCSSProperty(style, QStringLiteral("font-style"), fmt.fontItalic() ? QStringLiteral("italic") : QStringLiteral("normal"));
}
if (fmt.hasProperty(QTextFormat::FontStyleName)) {
WriteCSSProperty(style, QStringLiteral("-ove-font-style"), fmt.fontStyleName().toString());
}
QStringList deco;
if (fmt.fontUnderline()) {
deco.append(QStringLiteral("underline"));
}
if (fmt.fontStrikeOut()) {
deco.append(QStringLiteral("line-through"));
}
if (fmt.fontOverline()) {
deco.append(QStringLiteral("overline"));
}
if (!deco.isEmpty()) {
WriteCSSProperty(style, QStringLiteral("text-decoration"), deco);
}
if (fmt.foreground().style() != Qt::NoBrush) {
const QColor &color = fmt.foreground().color();
QString cs;
if (color.alpha() == 255) {
cs = color.name();
} else if (color.alpha()) {
cs = QStringLiteral("rgba(%1, %2, %3, %4)").arg(QString::number(color.red()), QString::number(color.green()), QString::number(color.blue()), QString::number(color.alphaF()));
}
WriteCSSProperty(style, QStringLiteral("color"), cs);
}
if (fmt.fontCapitalization() != QFont::MixedCase) {
if (fmt.fontCapitalization() == QFont::SmallCaps) {
WriteCSSProperty(style, QStringLiteral("font-variant"), QStringLiteral("small-caps"));
// TODO: Add others
}
}
if (fmt.fontLetterSpacing() != 0.0) {
WriteCSSProperty(style, QStringLiteral("letter-spacing"), QStringLiteral("%1%").arg(QString::number(fmt.fontLetterSpacing())));
}
if (fmt.fontStretch() != 0) {
WriteCSSProperty(style, QStringLiteral("font-stretch"), QStringLiteral("%1%").arg(QString::number(fmt.fontStretch())));
}
}
QTextCharFormat Html::ReadCharFormat(const QXmlStreamAttributes &attributes)
{
QTextCharFormat fmt;
foreach (const QXmlStreamAttribute &attr, attributes) {
if (StrEquals(attr.name(), QStringLiteral("style"))) {
auto css = GetCSSFromStyle(attr.value().toString());
for (auto it=css.begin(); it!=css.end(); it++) {
const QString &first_val = it.value().first();
if (it.key() == QStringLiteral("font-family")) {
fmt.setFontFamily(first_val);
} else if (it.key() == QStringLiteral("font-size")) {
if (first_val.endsWith(QStringLiteral("pt"), Qt::CaseInsensitive)) {
fmt.setFontPointSize(first_val.chopped(2).toDouble());
}
} else if (it.key() == QStringLiteral("font-weight")) {
fmt.setFontWeight(first_val.toInt()/8);
} else if (it.key() == QStringLiteral("font-style")) {
fmt.setFontItalic(StrEquals(first_val, QStringLiteral("italic")));
} else if (it.key() == QStringLiteral("text-decoration")) {
foreach (const QString &v, it.value()) {
if (StrEquals(v, QStringLiteral("underline"))) {
fmt.setFontUnderline(true);
} else if (StrEquals(v, QStringLiteral("line-through"))) {
fmt.setFontStrikeOut(true);
} else if (StrEquals(v, QStringLiteral("overline"))) {
fmt.setFontOverline(true);
}
}
} else if (it.key() == QStringLiteral("color")) {
if (first_val.startsWith(QStringLiteral("rgba"), Qt::CaseInsensitive)) {
QString vals_only = first_val;
vals_only.remove(QStringLiteral("rgba"));
vals_only.remove(QStringLiteral("("));
vals_only.remove(QStringLiteral(")"));
QStringList rgba = vals_only.split(',');
if (rgba.size() == 4) {
QColor c;
c.setRedF(rgba.at(0).toDouble());
c.setGreenF(rgba.at(1).toDouble());
c.setBlueF(rgba.at(2).toDouble());
c.setAlphaF(rgba.at(3).toDouble());
fmt.setForeground(c);
}
} else {
fmt.setForeground(QColor(first_val));
}
} else if (it.key() == QStringLiteral("font-variant")) {
if (StrEquals(first_val, QStringLiteral("small-caps"))) {
fmt.setFontCapitalization(QFont::SmallCaps);
}
} else if (it.key() == QStringLiteral("letter-spacing")) {
if (first_val.contains(QChar('%'))) {
fmt.setFontLetterSpacing(first_val.chopped(1).toDouble());
}
} else if (it.key() == QStringLiteral("font-stretch")) {
if (first_val.contains(QChar('%'))) {
fmt.setFontStretch(first_val.chopped(1).toInt());
}
} else if (it.key() == QStringLiteral("-ove-font-style")) {
fmt.setFontStyleName(first_val);
}
}
}
}
return fmt;
}
QTextBlockFormat Html::ReadBlockFormat(const QXmlStreamAttributes &attributes)
{
QTextBlockFormat block_fmt;
foreach (const QXmlStreamAttribute &attr, attributes) {
if (StrEquals(attr.name(), QStringLiteral("align"))) {
if (StrEquals(attr.value(), QStringLiteral("right"))) {
block_fmt.setAlignment(Qt::AlignRight);
} else if (StrEquals(attr.value(), QStringLiteral("center"))) {
block_fmt.setAlignment(Qt::AlignCenter);
} else if (StrEquals(attr.value(), QStringLiteral("justify"))) {
block_fmt.setAlignment(Qt::AlignJustify);
}
} else if (StrEquals(attr.name(), QStringLiteral("dir"))) {
if (StrEquals(attr.value(), QStringLiteral("rtl"))) {
block_fmt.setLayoutDirection(Qt::RightToLeft);
}
} else if (StrEquals(attr.name(), QStringLiteral("style"))) {
auto css = GetCSSFromStyle(attr.value().toString());
for (auto it=css.begin(); it!=css.end(); it++) {
if (it.key() == QStringLiteral("line-height")) {
const QString &first_val = it.value().constFirst();
if (first_val.contains(QChar('%'))) {
block_fmt.setLineHeight(first_val.chopped(1).toDouble(), QTextBlockFormat::ProportionalHeight);
}
}
}
}
}
return block_fmt;
}
void Html::AppendStringAutoSpace(QString *s, const QString &append)
{
if (!s->isEmpty()) {
s->append(QChar(' '));
}
s->append(append);
}
QMap<QString, QStringList> Html::GetCSSFromStyle(const QString &s)
{
QMap<QString, QStringList> map;
QStringList list = s.split(QChar(';'));
foreach (const QString &a, list) {
QStringList kv = a.split(QChar(':'));
if (kv.size() != 2) {
continue;
}
// I'm sure there's regex that could do this, but I couldn't figure it out. It needs to split
// by space EXCEPT within quotes OR double-quotes, and said quotes should be EXCLUDED from each
// match. Also commas should be filtered out.
QStringList values;
const QString &val = kv.at(1);
QChar in_quote = 0;
QString current_str;
for (int i=0; i<val.size(); i++) {
const QChar &current_char = val.at(i);
if (!in_quote.isNull()) {
// If inside quotes and character isn't quote, indiscriminately append char
if (current_char == in_quote) {
in_quote = 0;
} else {
current_str.append(current_char);
}
} else if (current_char.isSpace() || current_char == QChar(',')) {
// Dump current
if (!current_str.isEmpty()) {
values.append(current_str);
current_str.clear();
}
} else if (in_quote.isNull() && (current_char == QChar('\'') || current_char == QChar('"'))) {
in_quote = current_char;
} else {
current_str.append(current_char);
}
}
if (!current_str.isEmpty()) {
values.append(current_str);
}
// Not sure if this will ever happen, but just in case, we will avoid assert failures with this
if (values.isEmpty()) {
values.append(QString());
}
map[kv.at(0).trimmed().toLower()] = values;
}
return map;
}
}
+60
View File
@@ -0,0 +1,60 @@
#ifndef HTML_H
#define HTML_H
#include <QTextDocument>
#include <QTextFragment>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
namespace olive {
/**
* @brief Functions for converting HTML to QTextDocument and vice versa
*
* Qt does contain its own functions for this, however they have some limitations. Some things that
* we want to support (e.g. kerning/spacing and font stretch) are not implemented in Qt's
* QTextHtmlExporter and QTextHtmlParser. Additionally, since these functions are not part of Qt's
* public API, and make many references to other parts of Qt that are not part of the public API,
* there is no way to subclass or extend their functionality without forking Qt as a whole.
*
* Therefore, it became necessary to write a custom class for the conversion so that we can
* ensure support for the features we need.
*
* If someone wishes to extend this class for more feature support, feel free to open a pull
* request. But this is NOT intended to be an exhaustive HTML implementation, and is primarily
* designed to store rich text in a standard format for the purpose of text formatting for video.
*/
class Html {
public:
static QString DocToHtml(const QTextDocument *doc);
static void HtmlToDoc(QTextDocument *doc, const QString &html);
private:
static void WriteBlock(QXmlStreamWriter *writer, const QTextBlock &block);
static void WriteFragment(QXmlStreamWriter *writer, const QTextFragment &fragment);
static void WriteCSSProperty(QString *style, const QString &key, const QStringList &value);
static void WriteCSSProperty(QString *style, const QString &key, const QString &value)
{
WriteCSSProperty(style, key, QStringList({value}));
}
static void WriteCharFormat(QString *style, const QTextCharFormat &fmt);
static QTextCharFormat ReadCharFormat(const QXmlStreamAttributes &attributes);
static QTextBlockFormat ReadBlockFormat(const QXmlStreamAttributes &attributes);
static void AppendStringAutoSpace(QString *s, const QString &append);
static QMap<QString, QStringList> GetCSSFromStyle(const QString &s);
static const QVector<QString> kBlockTags;
};
}
#endif // HTML_H
+7
View File
@@ -38,6 +38,13 @@ QFrame *QtUtils::CreateHorizontalLine()
return horizontal_line;
}
QFrame *QtUtils::CreateVerticalLine()
{
QFrame *l = CreateHorizontalLine();
l->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
return l;
}
int QtUtils::MessageBox(QWidget *parent, QMessageBox::Icon icon, const QString &title, const QString &message, QMessageBox::StandardButtons buttons)
{
QMessageBox b(parent);
+2
View File
@@ -54,6 +54,8 @@ public:
static QFrame* CreateHorizontalLine();
static QFrame* CreateVerticalLine();
static int MessageBox(QWidget *parent, QMessageBox::Icon icon, const QString& title, const QString& message, QMessageBox::StandardButtons buttons = QMessageBox::Ok);
static QDateTime GetCreationDate(const QFileInfo &info);
+9 -1
View File
@@ -72,7 +72,7 @@ void rational::fix_signs()
void rational::reduce()
{
if (!isNull()) {
if (!isNull() && denom_ != 1) {
// Euclidean often fails if numbers are negative, we abs it and re-neg it later if necessary
bool neg = numer_ < 0;
@@ -186,6 +186,8 @@ const rational& rational::operator=(const rational &rhs)
const rational& rational::operator+=(const rational &rhs)
{
Q_ASSERT(*this != RATIONAL_MIN && *this != RATIONAL_MAX && rhs != RATIONAL_MIN && rhs != RATIONAL_MAX);
if (!isNaN()) {
if (rhs.isNaN()) {
// Set to NaN
@@ -209,6 +211,8 @@ const rational& rational::operator+=(const rational &rhs)
const rational& rational::operator-=(const rational &rhs)
{
Q_ASSERT(*this != RATIONAL_MIN && *this != RATIONAL_MAX && rhs != RATIONAL_MIN && rhs != RATIONAL_MAX);
if (!isNaN()) {
if (rhs.isNaN()) {
// Set to NaN
@@ -232,6 +236,8 @@ const rational& rational::operator-=(const rational &rhs)
const rational& rational::operator/=(const rational &rhs)
{
Q_ASSERT(*this != RATIONAL_MIN && *this != RATIONAL_MAX && rhs != RATIONAL_MIN && rhs != RATIONAL_MAX);
if (!isNaN()) {
if (rhs.isNaN()) {
// Set to NaN
@@ -250,6 +256,8 @@ const rational& rational::operator/=(const rational &rhs)
const rational& rational::operator*=(const rational &rhs)
{
Q_ASSERT(*this != RATIONAL_MIN && *this != RATIONAL_MAX && rhs != RATIONAL_MIN && rhs != RATIONAL_MAX);
if (!isNaN()) {
if (rhs.isNaN()) {
denom_ = 0;
+1 -1
View File
@@ -1,7 +1,7 @@
//Copyright 2015 Adam Quintero
//This program is distributed under the terms of the GNU General Public License.
// Adapted by MattKC for the Olive Video Editor (2019)
// Adapted by MattKC for the Olive Video Editor (2019-2022)
#ifndef RATIONAL_H
#define RATIONAL_H
+6 -1
View File
@@ -157,10 +157,12 @@ int64_t Timecode::timecode_to_timestamp(const QString &timecode, const rational
// Convert values to integers
QList<int64_t> timecode_numbers;
bool negative = timecode.trimmed().startsWith('-');
foreach (const QString& element, timecode_split) {
valid = true;
timecode_numbers.append((element.isEmpty()) ? 0 : element.toLong(&valid));
timecode_numbers.append((element.isEmpty()) ? 0 : qAbs(element.toLong(&valid)));
// If element cannot be converted to a number,
if (!valid) {
@@ -203,6 +205,9 @@ int64_t Timecode::timecode_to_timestamp(const QString &timecode, const rational
}
if (ok) *ok = true;
if (negative) timestamp = -timestamp;
return timestamp;
}
case kMilliseconds:
+6 -1
View File
@@ -44,6 +44,7 @@ const rational &TimeRange::out() const
const rational &TimeRange::length() const
{
Q_ASSERT(!length_.isNaN());
return length_;
}
@@ -173,7 +174,11 @@ void TimeRange::normalize()
}
// Calculate length
length_ = out_ - in_;
if (out_ == RATIONAL_MIN || out_ == RATIONAL_MAX || in_ == RATIONAL_MIN || in_ == RATIONAL_MAX) {
length_ = rational::NaN;
} else {
length_ = out_ - in_;
}
}
void TimeRangeList::insert(const TimeRangeList &list_to_add)
+30
View File
@@ -0,0 +1,30 @@
/***
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 UTIL_H
#define UTIL_H
template <typename T>
inline T mid(T a, T b)
{
return (a + b) * 0.5;
}
#endif // UTIL_H
+1 -1
View File
@@ -921,7 +921,7 @@ void Core::SaveAutorecovery()
foreach (Project* p, open_projects_) {
if (!p->has_autorecovery_been_saved()) {
QDir project_autorecovery_dir(QDir(FileFunctions::GetAutoRecoveryRoot()).filePath(p->GetUuid().toString()));
if (project_autorecovery_dir.mkpath(QStringLiteral("."))) {
if (FileFunctions::DirectoryIsValid(project_autorecovery_dir)) {
QString this_autorecovery_path = project_autorecovery_dir.filePath(QStringLiteral("%1.ove").arg(QString::number(QDateTime::currentSecsSinceEpoch())));
SaveProjectInternal(p, this_autorecovery_path);
+7 -3
View File
@@ -16,11 +16,15 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
dialog/export/codec/codecsection.h
dialog/export/codec/cineformsection.cpp
dialog/export/codec/cineformsection.h
dialog/export/codec/codecsection.cpp
dialog/export/codec/h264section.h
dialog/export/codec/codecsection.h
dialog/export/codec/codecstack.cpp
dialog/export/codec/codecstack.h
dialog/export/codec/h264section.cpp
dialog/export/codec/imagesection.h
dialog/export/codec/h264section.h
dialog/export/codec/imagesection.cpp
dialog/export/codec/imagesection.h
PARENT_SCOPE
)
@@ -0,0 +1,85 @@
/***
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 "cineformsection.h"
#include <QGridLayout>
#include <QLabel>
namespace olive {
CineformSection::CineformSection(QWidget *parent) :
CodecSection(parent)
{
QGridLayout *layout = new QGridLayout(this);
layout->setMargin(0);
int row = 0;
layout->addWidget(new QLabel(tr("Quality:")), row, 0);
quality_combobox_ = new QComboBox();
/* Correspond to the following indexes for FFmpeg
*
* -quality <int> E..V....... set quality (from 0 to 12) (default film3+)
* film3+ 0 E..V.......
* film3 1 E..V.......
* film2+ 2 E..V.......
* film2 3 E..V.......
* film1.5 4 E..V.......
* film1+ 5 E..V.......
* film1 6 E..V.......
* high+ 7 E..V.......
* high 8 E..V.......
* medium+ 9 E..V.......
* medium 10 E..V.......
* low+ 11 E..V.......
* low 12 E..V.......
*
*/
quality_combobox_->addItem(tr("Film Scan 3+"));
quality_combobox_->addItem(tr("Film Scan 3"));
quality_combobox_->addItem(tr("Film Scan 2+"));
quality_combobox_->addItem(tr("Film Scan 2"));
quality_combobox_->addItem(tr("Film Scan 1.5"));
quality_combobox_->addItem(tr("Film Scan 1+"));
quality_combobox_->addItem(tr("Film Scan 1"));
quality_combobox_->addItem(tr("High+"));
quality_combobox_->addItem(tr("High"));
quality_combobox_->addItem(tr("Medium+"));
quality_combobox_->addItem(tr("Medium"));
quality_combobox_->addItem(tr("Low+"));
quality_combobox_->addItem(tr("Low"));
// Default to "medium"
quality_combobox_->setCurrentIndex(10);
layout->addWidget(quality_combobox_, row, 1);
}
void CineformSection::AddOpts(EncodingParams *params)
{
params->set_video_option(QStringLiteral("quality"), QString::number(quality_combobox_->currentIndex()));
}
}
+45
View File
@@ -0,0 +1,45 @@
/***
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 CINEFORMSECTION_H
#define CINEFORMSECTION_H
#include <QComboBox>
#include "codecsection.h"
namespace olive {
class CineformSection : public CodecSection
{
Q_OBJECT
public:
CineformSection(QWidget *parent = nullptr);
virtual void AddOpts(EncodingParams* params) override;
private:
QComboBox *quality_combobox_;
};
}
#endif // CINEFORMSECTION_H
+53
View File
@@ -0,0 +1,53 @@
/***
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 "codecstack.h"
namespace olive {
#define super QStackedWidget
CodecStack::CodecStack(QWidget *parent)
: super{parent}
{
connect(this, &CodecStack::currentChanged, this, &CodecStack::OnChange);
}
void CodecStack::addWidget(QWidget *widget)
{
super::addWidget(widget);
OnChange(currentIndex());
}
void CodecStack::OnChange(int index)
{
for (int i=0; i<count(); i++) {
if (i == index) {
widget(i)->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
} else {
widget(i)->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
}
widget(i)->adjustSize();
}
adjustSize();
}
}
+45
View File
@@ -0,0 +1,45 @@
/***
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 CODECSTACK_H
#define CODECSTACK_H
#include <QStackedWidget>
namespace olive {
class CodecStack : public QStackedWidget
{
Q_OBJECT
public:
explicit CodecStack(QWidget *parent = nullptr);
void addWidget(QWidget *widget);
signals:
private slots:
void OnChange(int index);
};
}
#endif // CODECSTACK_H
+27
View File
@@ -42,6 +42,31 @@ H264Section::H264Section(int default_crf, QWidget *parent) :
layout->setMargin(0);
int row = 0;
layout->addWidget(new QLabel(tr("Encode Speed:")), row, 0);
preset_combobox_ = new QComboBox();
preset_combobox_->setToolTip(tr("This setting allows you to tweak the ratio of export speed to compression quality. \n\n"
"If using Constant Rate Factor, slower speeds will result in smaller file sizes for the same quality. \n\n"
"If using Target Bit Rate or Target File Size, slower speeds will result in higher quality for the same bitrate/filesize. \n\n"
"This setting is equivalent to the `preset` setting in libx264."));
preset_combobox_->addItem(tr("Ultra Fast"));
preset_combobox_->addItem(tr("Super Fast"));
preset_combobox_->addItem(tr("Very Fast"));
preset_combobox_->addItem(tr("Faster"));
preset_combobox_->addItem(tr("Fast"));
preset_combobox_->addItem(tr("Medium"));
preset_combobox_->addItem(tr("Slow"));
preset_combobox_->addItem(tr("Slower"));
preset_combobox_->addItem(tr("Very Slow"));
//Default to "medium"
preset_combobox_->setCurrentIndex(5);
layout->addWidget(preset_combobox_, row, 1);
row++;
layout->addWidget(new QLabel(tr("Compression Method:")), row, 0);
@@ -110,6 +135,8 @@ void H264Section::AddOpts(EncodingParams *params)
params->set_video_buffer_size(2000000);
}
params->set_video_option(QStringLiteral("preset"), QString::number(preset_combobox_->currentIndex()));
}
H264CRFSection::H264CRFSection(int default_crf, QWidget *parent) :
+2
View File
@@ -23,6 +23,7 @@
#include <QSlider>
#include <QStackedWidget>
#include <QComboBox>
#include "codecsection.h"
#include "widget/slider/floatslider.h"
@@ -111,6 +112,7 @@ private:
H264FileSizeSection* filesize_section_;
QComboBox *preset_combobox_;
};
class H265Section : public H264Section
+2 -1
View File
@@ -312,7 +312,8 @@ void ExportDialog::StartExport()
QFileInfo dir_info(file_info.path());
// If the directory does not exist, try to create it
if (!QDir(file_info.path()).mkpath(QStringLiteral("."))) {
QDir dest_dir(file_info.path());
if (!FileFunctions::DirectoryIsValid(dest_dir)) {
QtUtils::MessageBox(this, QMessageBox::Critical, tr("Failed to create output directory"),
tr("The intended output directory doesn't exist and Olive couldn't create it. "
"Please choose a different filename."));
@@ -48,6 +48,7 @@ ExportAdvancedVideoDialog::ExportAdvancedVideoDialog(const QList<QString> &pix_f
thread_slider_ = new IntegerSlider();
thread_slider_->SetMinimum(0);
thread_slider_->SetDefaultValue(0);
thread_slider_->InsertLabelSubstitution(0, tr("Auto"));
performance_layout->addWidget(thread_slider_, row, 1);
row++;
+7 -2
View File
@@ -173,7 +173,7 @@ QWidget *ExportVideoTab::SetupCodecSection()
row++;
codec_stack_ = new QStackedWidget();
codec_stack_ = new CodecStack();
codec_layout->addWidget(codec_stack_, row, 0, 1, 2);
image_section_ = new ImageSection();
@@ -185,6 +185,9 @@ QWidget *ExportVideoTab::SetupCodecSection()
h265_section_ = new H265Section();
codec_stack_->addWidget(h265_section_);
cineform_section_ = new CineformSection();
codec_stack_->addWidget(cineform_section_);
row++;
QPushButton* advanced_btn = new QPushButton(tr("Advanced"));
@@ -240,6 +243,9 @@ void ExportVideoTab::VideoCodecChanged()
case ExportCodec::kCodecH265:
SetCodecSection(h265_section_);
break;
case ExportCodec::kCodecCineform:
SetCodecSection(cineform_section_);
break;
default:
SetCodecSection(ExportCodec::IsCodecAStillImage(codec) ? image_section_ : nullptr);
}
@@ -251,7 +257,6 @@ void ExportVideoTab::VideoCodecChanged()
} else {
pix_fmt_.clear();
}
qDebug() << "Set default pix fmt" << pix_fmt_;
}
void ExportVideoTab::SetTime(const rational &time)
+4 -1
View File
@@ -26,6 +26,8 @@
#include <QWidget>
#include "common/rational.h"
#include "dialog/export/codec/cineformsection.h"
#include "dialog/export/codec/codecstack.h"
#include "dialog/export/codec/h264section.h"
#include "dialog/export/codec/imagesection.h"
#include "node/color/colormanager/colormanager.h"
@@ -155,10 +157,11 @@ private:
QCheckBox* maintain_aspect_checkbox_;
QComboBox* scaling_method_combobox_;
QStackedWidget* codec_stack_;
CodecStack* codec_stack_;
ImageSection* image_section_;
H264Section* h264_section_;
H264Section* h265_section_;
CineformSection *cineform_section_;
ColorSpaceChooser* color_space_chooser_;
@@ -96,7 +96,7 @@ bool PreferencesDiskTab::Validate()
}
// Check validity of the new path
if (!FileFunctions::DirectoryIsValid(disk_cache_location_->text(), true)) {
if (!FileFunctions::DirectoryIsValid(disk_cache_location_->text())) {
QMessageBox::critical(this,
tr("Disk Cache"),
tr("Failed to set disk cache location. Access was denied."));
@@ -186,7 +186,7 @@ void ProjectPropertiesDialog::accept()
bool ProjectPropertiesDialog::VerifyPathAndWarnIfBad(const QString &path)
{
if (!FileFunctions::DirectoryIsValid(path, true)) {
if (!FileFunctions::DirectoryIsValid(path)) {
QMessageBox mb(this);
mb.setWindowModality(Qt::WindowModal);
mb.setIcon(QMessageBox::Critical);
@@ -31,6 +31,7 @@
#include "common/filefunctions.h"
#include "config/config.h"
#include "render/audioparams.h"
#include "render/videoparams.h"
#include "ui/icons/icons.h"
#include "widget/menu/menu.h"
@@ -22,6 +22,7 @@
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QMessageBox>
#include "core.h"
@@ -39,58 +40,82 @@ SpeedDurationDialog::SpeedDurationDialog(const QVector<ClipBlock *> &clips, cons
{
setWindowTitle(tr("Speed/Duration"));
QGridLayout *layout = new QGridLayout(this);
QVBoxLayout *layout = new QVBoxLayout(this);
int row = 0;
{
QGroupBox *speed_group = new QGroupBox();
layout->addWidget(speed_group);
layout->addWidget(new QLabel(tr("Speed:")), row, 0);
QGridLayout *speed_layout = new QGridLayout(speed_group);
speed_slider_ = new FloatSlider();
speed_slider_->SetDisplayType(FloatSlider::kPercentage);
connect(speed_slider_, &FloatSlider::ValueChanged, this, &SpeedDurationDialog::SpeedChanged);
layout->addWidget(speed_slider_, row, 1);
int row = 0;
row++;
speed_layout->addWidget(new QLabel(tr("Speed:")), row, 0);
layout->addWidget(new QLabel(tr("Duration:")), row, 0);
speed_slider_ = new FloatSlider();
speed_slider_->SetDisplayType(FloatSlider::kPercentage);
connect(speed_slider_, &FloatSlider::ValueChanged, this, &SpeedDurationDialog::SpeedChanged);
speed_layout->addWidget(speed_slider_, row, 1);
dur_slider_ = new RationalSlider();
dur_slider_->SetTimebase(timebase);
dur_slider_->SetDisplayType(RationalSlider::kTime);
connect(dur_slider_, &RationalSlider::ValueChanged, this, &SpeedDurationDialog::DurationChanged);
layout->addWidget(dur_slider_, row, 1);
row++;
row++;
speed_layout->addWidget(new QLabel(tr("Duration:")), row, 0);
link_box_ = new QCheckBox(tr("Link Speed and Duration"));
link_box_->setChecked(true);
layout->addWidget(link_box_, row, 0, 1, 2);
dur_slider_ = new RationalSlider();
dur_slider_->SetTimebase(timebase);
dur_slider_->SetDisplayType(RationalSlider::kTime);
connect(dur_slider_, &RationalSlider::ValueChanged, this, &SpeedDurationDialog::DurationChanged);
speed_layout->addWidget(dur_slider_, row, 1);
row++;
row++;
link_box_ = new QCheckBox(tr("Link Speed and Duration"));
link_box_->setChecked(true);
speed_layout->addWidget(link_box_, row, 0, 1, 2);
}
reverse_box_ = new QCheckBox(tr("Reverse"));
layout->addWidget(reverse_box_);
maintain_audio_pitch_box_ = new QCheckBox(tr("Maintain Audio Pitch"));
layout->addWidget(maintain_audio_pitch_box_);
ripple_box_ = new QCheckBox(tr("Ripple Trailing Clips"));
layout->addWidget(ripple_box_, row, 0, 1, 2);
row++;
layout->addWidget(ripple_box_);
QDialogButtonBox *btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
btns->setCenterButtons(true);
connect(btns, &QDialogButtonBox::accepted, this, &SpeedDurationDialog::accept);
connect(btns, &QDialogButtonBox::rejected, this, &SpeedDurationDialog::reject);
layout->addWidget(btns, row, 0, 1, 2);
layout->addWidget(btns);
// Determine which speed value to use
start_speed_ = clips.first()->speed();
start_duration_ = clips.first()->length();
start_reverse_ = clips.first()->reverse();
start_maintain_audio_pitch_ = clips.first()->maintain_audio_pitch();
for (int i=1; i<clips.size(); i++) {
if (!qIsNaN(start_speed_) && !qFuzzyCompare(start_speed_, clips.at(i)->speed())) {
ClipBlock *c = clips.at(i);
if (!qIsNaN(start_speed_) && !qFuzzyCompare(start_speed_, c->speed())) {
// Speed differs per clip
start_speed_ = qSNaN();
}
if (start_duration_ != -1 && clips.at(i)->length() != start_duration_) {
if (start_duration_ != -1 && c->length() != start_duration_) {
start_duration_ = -1;
}
// Yes, in theory a bool should only ever be 0 or 1 anyway, but MSVC complained and it is
// *possible* that a bool could be something else, so this code is safer
int clip_reverse = c->reverse() ? 1 : 0;
int clip_maintain_pitch = c->maintain_audio_pitch() ? 1 : 0;
if (start_reverse_ != -1 && clip_reverse != start_reverse_) {
start_reverse_ = -1;
}
if (start_maintain_audio_pitch_ != -1 && clip_maintain_pitch != start_maintain_audio_pitch_) {
start_maintain_audio_pitch_ = -1;
}
}
if (qIsNaN(start_speed_)) {
@@ -104,6 +129,18 @@ SpeedDurationDialog::SpeedDurationDialog(const QVector<ClipBlock *> &clips, cons
} else {
dur_slider_->SetValue(start_duration_);
}
if (start_reverse_ == -1) {
reverse_box_->setTristate();
} else {
reverse_box_->setChecked(start_reverse_);
}
if (start_maintain_audio_pitch_ == -1) {
maintain_audio_pitch_box_->setTristate();
} else {
maintain_audio_pitch_box_->setChecked(start_maintain_audio_pitch_);
}
}
void SpeedDurationDialog::accept()
@@ -118,21 +155,6 @@ void SpeedDurationDialog::accept()
MultiUndoCommand *command = new MultiUndoCommand();
// Set speed values
if (speed_slider_->IsTristate()) {
if (link_box_->isChecked() && !dur_slider_->IsTristate()) {
// Automatically determine speed from duration
foreach (ClipBlock *c, clips_) {
command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(c, ClipBlock::kSpeedInput)), GetSpeedAdjustment(c->speed(), c->length(), dur_slider_->GetValue())));
}
}
} else {
// Set speeds to value of slider
foreach (ClipBlock *c, clips_) {
command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(c, ClipBlock::kSpeedInput)), speed_slider_->GetValue()));
}
}
// Set duration values
foreach (ClipBlock *c, clips_) {
rational proposed_length = c->length();
@@ -161,6 +183,35 @@ void SpeedDurationDialog::accept()
}
}
// Set speed values
if (speed_slider_->IsTristate()) {
if (link_box_->isChecked() && !dur_slider_->IsTristate()) {
// Automatically determine speed from duration
foreach (ClipBlock *c, clips_) {
command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(c, ClipBlock::kSpeedInput)), GetSpeedAdjustment(c->speed(), c->length(), dur_slider_->GetValue())));
}
}
} else {
// Set speeds to value of slider
foreach (ClipBlock *c, clips_) {
command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(c, ClipBlock::kSpeedInput)), speed_slider_->GetValue()));
}
}
// Set reverse values
if (!reverse_box_->isTristate()) {
foreach (ClipBlock *c, clips_) {
command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(c, ClipBlock::kReverseInput)), reverse_box_->isChecked()));
}
}
// Set reverse values
if (!maintain_audio_pitch_box_->isTristate()) {
foreach (ClipBlock *c, clips_) {
command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(c, ClipBlock::kMaintainAudioPitchInput)), maintain_audio_pitch_box_->isChecked()));
}
}
Core::instance()->undo_stack()->push(command);
super::accept();
@@ -56,8 +56,16 @@ private:
QCheckBox *link_box_;
QCheckBox *reverse_box_;
QCheckBox *maintain_audio_pitch_box_;
QCheckBox *ripple_box_;
int start_reverse_;
int start_maintain_audio_pitch_;
double start_speed_;
rational start_duration_;
+1 -2
View File
@@ -190,9 +190,8 @@ int main(int argc, char *argv[])
//
// https://bugreports.qt.io/browse/QTBUG-46140
QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
format.setVersion(2, 0);
format.setVersion(3, 2);
format.setProfile(QSurfaceFormat::CoreProfile);
format.setOption(QSurfaceFormat::DeprecatedFunctions);
format.setDepthBufferSize(24);
QSurfaceFormat::setDefaultFormat(format);
+2
View File
@@ -21,8 +21,10 @@ add_subdirectory(distort)
add_subdirectory(effect)
add_subdirectory(filter)
add_subdirectory(generator)
add_subdirectory(gizmo)
add_subdirectory(group)
add_subdirectory(input)
add_subdirectory(keying)
add_subdirectory(math)
add_subdirectory(output)
add_subdirectory(project)
+8 -1
View File
@@ -27,6 +27,8 @@ namespace olive {
const QString PanNode::kSamplesInput = QStringLiteral("samples_in");
const QString PanNode::kPanningInput = QStringLiteral("panning_in");
#define super Node
PanNode::PanNode()
{
AddInput(kSamplesInput, NodeValue::kSamples, InputFlags(kInputFlagNotKeyframable));
@@ -35,6 +37,9 @@ PanNode::PanNode()
SetInputProperty(kPanningInput, QStringLiteral("min"), -1.0);
SetInputProperty(kPanningInput, QStringLiteral("max"), 1.0);
SetInputProperty(kPanningInput, QStringLiteral("view"), FloatSlider::kPercentage);
SetFlags(kAudioEffect);
SetEffectInput(kSamplesInput);
}
Node *PanNode::copy() const
@@ -54,7 +59,7 @@ QString PanNode::id() const
QVector<Node::CategoryID> PanNode::Category() const
{
return {kCategoryChannels};
return {kCategoryFilter};
}
QString PanNode::Description() const
@@ -115,6 +120,8 @@ void PanNode::ProcessSamples(const NodeValueRow &values, const SampleBufferPtr i
void PanNode::Retranslate()
{
super::Retranslate();
SetInputName(kSamplesInput, tr("Samples"));
SetInputName(kPanningInput, tr("Pan"));
}
+7
View File
@@ -27,6 +27,8 @@ namespace olive {
const QString VolumeNode::kSamplesInput = QStringLiteral("samples_in");
const QString VolumeNode::kVolumeInput = QStringLiteral("volume_in");
#define super MathNodeBase
VolumeNode::VolumeNode()
{
AddInput(kSamplesInput, NodeValue::kSamples, InputFlags(kInputFlagNotKeyframable));
@@ -34,6 +36,9 @@ VolumeNode::VolumeNode()
AddInput(kVolumeInput, NodeValue::kFloat, 1.0);
SetInputProperty(kVolumeInput, QStringLiteral("min"), 0.0);
SetInputProperty(kVolumeInput, QStringLiteral("view"), FloatSlider::kDecibel);
SetFlags(kAudioEffect);
SetEffectInput(kSamplesInput);
}
Node *VolumeNode::copy() const
@@ -80,6 +85,8 @@ void VolumeNode::ProcessSamples(const NodeValueRow &values, const SampleBufferPt
void VolumeNode::Retranslate()
{
super::Retranslate();
SetInputName(kSamplesInput, tr("Samples"));
SetInputName(kVolumeInput, tr("Volume"));
}
-3
View File
@@ -32,7 +32,6 @@ namespace olive {
#define super Node
const QString Block::kLengthInput = QStringLiteral("length_in");
const QString Block::kEnabledInput = QStringLiteral("enabled_in");
Block::Block() :
previous_(nullptr),
@@ -46,8 +45,6 @@ Block::Block() :
SetInputProperty(kLengthInput, QStringLiteral("viewlock"), true);
IgnoreHashingFrom(kLengthInput);
AddInput(kEnabledInput, NodeValue::kBoolean, true, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
SetFlags(kDontShowInParamView);
}
-1
View File
@@ -118,7 +118,6 @@ public:
virtual void InvalidateCache(const TimeRange& range, const QString& from, int element = -1, InvalidateCacheOptions options = InvalidateCacheOptions()) override;
static const QString kLengthInput;
static const QString kEnabledInput;
public slots:
+31 -29
View File
@@ -33,6 +33,7 @@ const QString ClipBlock::kBufferIn = QStringLiteral("buffer_in");
const QString ClipBlock::kMediaInInput = QStringLiteral("media_in_in");
const QString ClipBlock::kSpeedInput = QStringLiteral("speed_in");
const QString ClipBlock::kReverseInput = QStringLiteral("reverse_in");
const QString ClipBlock::kMaintainAudioPitchInput = QStringLiteral("maintain_audio_pitch_in");
ClipBlock::ClipBlock() :
in_transition_(nullptr),
@@ -52,8 +53,12 @@ ClipBlock::ClipBlock() :
AddInput(kReverseInput, NodeValue::kBoolean, false, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
IgnoreHashingFrom(kReverseInput);
AddInput(kMaintainAudioPitchInput, NodeValue::kBoolean, false, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
PrependInput(kBufferIn, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable));
SetValueHintForInput(kBufferIn, ValueHint(NodeValue::kBuffer));
SetEffectInput(kBufferIn);
}
Node *ClipBlock::copy() const
@@ -92,7 +97,8 @@ void ClipBlock::set_length_and_media_out(const rational &length)
if (reverse()) {
// Calculate media_in adjustment
set_media_in(SequenceToMediaTime(length - this->length(), true));
rational proposed_media_in = SequenceToMediaTime(this->length() - length, true);
set_media_in(proposed_media_in);
}
super::set_length_and_media_out(length);
@@ -106,7 +112,14 @@ void ClipBlock::set_length_and_media_in(const rational &length)
if (!reverse()) {
// Calculate media_in adjustment
set_media_in(SequenceToMediaTime(this->length() - length));
rational proposed_media_in = SequenceToMediaTime(this->length() - length, false, true);
waveform_.TrimIn(proposed_media_in - media_in());
set_media_in(proposed_media_in);
} else {
// Trim waveform out point
waveform_.TrimIn(this->length() - length);
}
super::set_length_and_media_in(length);
@@ -122,7 +135,7 @@ void ClipBlock::set_media_in(const rational &media_in)
SetStandardValue(kMediaInInput, QVariant::fromValue(media_in));
}
rational ClipBlock::SequenceToMediaTime(const rational &sequence_time, bool ignore_reverse) const
rational ClipBlock::SequenceToMediaTime(const rational &sequence_time, bool ignore_reverse, bool ignore_speed) const
{
// These constants are not considered "values" per se, so we don't modify them
if (sequence_time == RATIONAL_MIN || sequence_time == RATIONAL_MAX) {
@@ -131,19 +144,21 @@ rational ClipBlock::SequenceToMediaTime(const rational &sequence_time, bool igno
rational media_time = sequence_time;
double speed_value = speed();
if (qIsNull(speed_value)) {
// Effectively holds the frame at the in point
media_time = 0;
} else if (!qFuzzyCompare(speed_value, 1.0)) {
// Multiply time
media_time = rational::fromDouble(media_time.toDouble() * speed_value);
}
if (reverse() && !ignore_reverse) {
media_time = length() - media_time;
}
if (!ignore_speed) {
double speed_value = speed();
if (qIsNull(speed_value)) {
// Effectively holds the frame at the in point
media_time = 0;
} else if (!qFuzzyCompare(speed_value, 1.0)) {
// Multiply time
media_time = rational::fromDouble(media_time.toDouble() * speed_value);
}
}
media_time += media_in();
return media_time;
@@ -158,10 +173,6 @@ rational ClipBlock::MediaToSequenceTime(const rational &media_time) const
rational sequence_time = media_time - media_in();
if (reverse()) {
sequence_time = length() - sequence_time;
}
double speed_value = speed();
if (qIsNull(speed_value)) {
// I don't know what to return here yet...
@@ -171,6 +182,10 @@ rational ClipBlock::MediaToSequenceTime(const rational &media_time) const
sequence_time = rational::fromDouble(sequence_time.toDouble() / speed_value);
}
if (reverse()) {
sequence_time = length() - sequence_time;
}
return sequence_time;
}
@@ -219,19 +234,6 @@ void ClipBlock::LinkChangeEvent()
}
}
void ClipBlock::InputValueChangedEvent(const QString &input, int element)
{
super::InputValueChangedEvent(input, element);
if (input == kMediaInInput) {
// Shift waveform in the inverse that the media in moved
rational diff = media_in() - last_media_in_;
waveform_.TrimIn(diff);
last_media_in_ = media_in();
}
}
TimeRange ClipBlock::InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const
{
Q_UNUSED(element)
+17 -3
View File
@@ -71,6 +71,21 @@ public:
return GetStandardValue(kReverseInput).toBool();
}
void set_reverse(bool e)
{
SetStandardValue(kReverseInput, e);
}
bool maintain_audio_pitch() const
{
return GetStandardValue(kMaintainAudioPitchInput).toBool();
}
void set_maintain_audio_pitch(bool e)
{
SetStandardValue(kMaintainAudioPitchInput, e);
}
TransitionBlock* in_transition()
{
return in_transition_;
@@ -110,16 +125,15 @@ public:
static const QString kMediaInInput;
static const QString kSpeedInput;
static const QString kReverseInput;
static const QString kMaintainAudioPitchInput;
protected:
virtual void LinkChangeEvent() override;
virtual void InputValueChangedEvent(const QString &input, int element) override;
virtual void Hash(QCryptographicHash &hash, const NodeGlobals &globals, const VideoParams& video_params) const override;
private:
rational SequenceToMediaTime(const rational& sequence_time, bool ignore_reverse = false) const;
rational SequenceToMediaTime(const rational& sequence_time, bool ignore_reverse = false, bool ignore_speed = false) const;
rational MediaToSequenceTime(const rational& media_time) const;
+5 -1
View File
@@ -34,6 +34,8 @@ const QString ColorManager::kConfigFilenameIn = QStringLiteral("config");
const QString ColorManager::kDefaultColorspaceIn = QStringLiteral("default_input");
const QString ColorManager::kReferenceSpaceIn = QStringLiteral("reference_space");
#define super Node
OCIO::ConstConfigRcPtr ColorManager::default_config_ = nullptr;
ColorManager::ColorManager() :
@@ -50,7 +52,7 @@ ColorManager::ColorManager() :
// Set config to our built-in default
SetConfig(GetDefaultConfig());
SetDefaultInputColorSpace(QStringLiteral("sRGB OETF"));
SetDefaultInputColorSpace(config_->getCanonicalName(OCIO::ROLE_DEFAULT));
}
OCIO::ConstConfigRcPtr ColorManager::GetConfig() const
@@ -253,6 +255,8 @@ void ColorManager::GetDefaultLumaCoefs(double *rgb) const
void ColorManager::Retranslate()
{
super::Retranslate();
SetInputName(kConfigFilenameIn, tr("Configuration"));
SetInputName(kDefaultColorspaceIn, tr("Default Input"));
SetInputName(kReferenceSpaceIn, tr("Reference Space"));
+1
View File
@@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
add_subdirectory(cornerpin)
add_subdirectory(crop)
add_subdirectory(flip)
add_subdirectory(transform)
+22
View File
@@ -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/cornerpin/cornerpindistortnode.cpp
node/distort/cornerpin/cornerpindistortnode.h
PARENT_SCOPE
)
@@ -0,0 +1,179 @@
/***
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 "cornerpindistortnode.h"
#include "common/lerp.h"
#include "core.h"
#include "widget/slider/floatslider.h"
namespace olive {
const QString CornerPinDistortNode::kTextureInput = QStringLiteral("tex_in");
const QString CornerPinDistortNode::kTopLeftInput = QStringLiteral("top_left_in");
const QString CornerPinDistortNode::kTopRightInput = QStringLiteral("top_right_in");
const QString CornerPinDistortNode::kBottomRightInput = QStringLiteral("bottom_right_in");
const QString CornerPinDistortNode::kBottomLeftInput = QStringLiteral("bottom_left_in");
const QString CornerPinDistortNode::kPerspectiveInput = QStringLiteral("perspective_in");
#define super Node
CornerPinDistortNode::CornerPinDistortNode()
{
AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
AddInput(kPerspectiveInput, NodeValue::kBoolean, true);
AddInput(kTopLeftInput, NodeValue::kVec2, QVector2D(0.0, 0.0));
AddInput(kTopRightInput, NodeValue::kVec2, QVector2D(0.0, 0.0));
AddInput(kBottomRightInput, NodeValue::kVec2, QVector2D(0.0, 0.0));
AddInput(kBottomLeftInput, NodeValue::kVec2, QVector2D(0.0, 0.0));
// Initiate gizmos
gizmo_whole_rect_ = AddDraggableGizmo<PolygonGizmo>();
gizmo_resize_handle_[0] = AddDraggableGizmo<PointGizmo>({NodeKeyframeTrackReference(NodeInput(this, kTopLeftInput), 0), NodeKeyframeTrackReference(NodeInput(this, kTopLeftInput), 1)});
gizmo_resize_handle_[1] = AddDraggableGizmo<PointGizmo>({NodeKeyframeTrackReference(NodeInput(this, kTopRightInput), 0), NodeKeyframeTrackReference(NodeInput(this, kTopRightInput), 1)});
gizmo_resize_handle_[2] = AddDraggableGizmo<PointGizmo>({NodeKeyframeTrackReference(NodeInput(this, kBottomRightInput), 0), NodeKeyframeTrackReference(NodeInput(this, kBottomRightInput), 1)});
gizmo_resize_handle_[3] = AddDraggableGizmo<PointGizmo>({NodeKeyframeTrackReference(NodeInput(this, kBottomLeftInput), 0), NodeKeyframeTrackReference(NodeInput(this, kBottomLeftInput), 1)});
SetFlags(kVideoEffect);
SetEffectInput(kTextureInput);
}
void CornerPinDistortNode::Retranslate()
{
super::Retranslate();
SetInputName(kTextureInput, tr("Texture"));
SetInputName(kPerspectiveInput, tr("Perspective"));
SetInputName(kTopLeftInput, tr("Top Left"));
SetInputName(kTopRightInput, tr("Top Right"));
SetInputName(kBottomRightInput, tr("Bottom Right"));
SetInputName(kBottomLeftInput, tr("Bottom Left"));
}
void CornerPinDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
{
ShaderJob job;
job.InsertValue(value);
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
job.InsertValue(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.
const QVector2D &resolution = globals.resolution();
QVector2D half_resolution = resolution * 0.5;
QVector2D top_left = QVector2D(ValueToPixel(0, value, resolution)) / half_resolution - QVector2D(1.0, 1.0);
QVector2D top_right = QVector2D(ValueToPixel(1, value, resolution)) / half_resolution - QVector2D(1.0, 1.0);
QVector2D bottom_right = QVector2D(ValueToPixel(2, value, resolution)) / half_resolution - QVector2D(1.0, 1.0);
QVector2D bottom_left = QVector2D(ValueToPixel(3, value, resolution)) / half_resolution - QVector2D(1.0, 1.0);
// Override default vertex coordinates.
QVector<float> adjusted_vertices = {top_left.x(), top_left.y(), 0.0f,
top_right.x(), top_right.y(), 0.0f,
bottom_right.x(), bottom_right.y(), 0.0f,
top_left.x(), top_left.y(), 0.0f,
bottom_left.x(), bottom_left.y(), 0.0f,
bottom_right.x(), bottom_right.y(), 0.0f};
job.SetVertexCoordinates(adjusted_vertices);
// If no texture do nothing
if (!job.GetValue(kTextureInput).data().isNull()) {
// 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())) {
table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this);
} else {
table->Push(NodeValue::kTexture, job.GetValue(kTextureInput).data(), this);
}
}
}
ShaderCode CornerPinDistortNode::GetShaderCode(const QString &shader_id) const
{
Q_UNUSED(shader_id)
return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/cornerpin.frag")),
FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/cornerpin.vert")));
}
QPointF CornerPinDistortNode::ValueToPixel(int value, const NodeValueRow& row, const QVector2D &resolution) const
{
Q_ASSERT(value >= 0 && value <= 3);
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();
}
}
void CornerPinDistortNode::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers)
{
DraggableGizmo *gizmo = static_cast<DraggableGizmo*>(sender());
if (gizmo != gizmo_whole_rect_) {
gizmo->GetDraggers()[0].Drag(gizmo->GetDraggers()[0].GetStartValue().toDouble() + x);
gizmo->GetDraggers()[1].Drag(gizmo->GetDraggers()[1].GetStartValue().toDouble() + y);
}
}
void CornerPinDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals)
{
const QVector2D &resolution = globals.resolution();
QPointF top_left = ValueToPixel(0, row, resolution);
QPointF top_right = ValueToPixel(1, row, resolution);
QPointF bottom_right = ValueToPixel(2, row, resolution);
QPointF bottom_left = ValueToPixel(3, row, resolution);
// Add the correct offset to each slider
SetInputProperty(kTopLeftInput, QStringLiteral("offset"), QVector2D(0.0, 0.0));
SetInputProperty(kTopRightInput, QStringLiteral("offset"), QVector2D(resolution.x() , 0.0));
SetInputProperty(kBottomRightInput, QStringLiteral("offset"), resolution);
SetInputProperty(kBottomLeftInput, QStringLiteral("offset"), QVector2D(0.0, resolution.y()));
// Draw bounding box
gizmo_whole_rect_->SetPolygon(QPolygonF({top_left, top_right, bottom_right, bottom_left, top_left}));
// Create handles
gizmo_resize_handle_[0]->SetPoint(top_left);
gizmo_resize_handle_[1]->SetPoint(top_right);
gizmo_resize_handle_[2]->SetPoint(bottom_right);
gizmo_resize_handle_[3]->SetPoint(bottom_left);
}
}
@@ -0,0 +1,100 @@
/***
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 CORNERPINDISTORTNODE_H
#define CORNERPINDISTORTNODE_H
#include <QVector2D>
#include "node/gizmo/point.h"
#include "node/gizmo/polygon.h"
#include "node/inputdragger.h"
#include "node/node.h"
namespace olive {
class CornerPinDistortNode : public Node
{
Q_OBJECT
public:
CornerPinDistortNode();
NODE_DEFAULT_DESTRUCTOR(CornerPinDistortNode)
virtual Node* copy() const override
{
return new CornerPinDistortNode();
}
virtual QString Name() const override
{
return tr("Corner Pin");
}
virtual QString id() const override
{
return QStringLiteral("org.olivevideoeditor.Olive.cornerpin");
}
virtual QVector<CategoryID> Category() const override
{
return {kCategoryDistort};
}
virtual QString Description() const override
{
return tr("Distort the image by dragging the corners.");
}
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 void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override;
/**
* @brief Convenience function - converts the 2D slider values from being
* an offset to the actual pixel value.
*/
QPointF ValueToPixel(int value, const NodeValueRow &row, const QVector2D &resolution) const;
static const QString kTextureInput;
static const QString kPerspectiveInput;
static const QString kTopLeftInput;
static const QString kTopRightInput;
static const QString kBottomRightInput;
static const QString kBottomLeftInput;
protected slots:
virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override;
private:
// Gizmo variables
static const int kGizmoCornerCount = 4;
PointGizmo *gizmo_resize_handle_[kGizmoCornerCount];
PolygonGizmo *gizmo_whole_rect_;
};
}
#endif // CORNERPINDISTORTNODE_H
+48 -143
View File
@@ -20,7 +20,7 @@
#include "cropdistortnode.h"
#include "common/lerp.h"
#include "common/util.h"
#include "core.h"
#include "widget/slider/floatslider.h"
@@ -33,6 +33,8 @@ const QString CropDistortNode::kRightInput = QStringLiteral("right_in");
const QString CropDistortNode::kBottomInput = QStringLiteral("bottom_in");
const QString CropDistortNode::kFeatherInput = QStringLiteral("feather_in");
#define super Node
CropDistortNode::CropDistortNode()
{
AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
@@ -44,10 +46,27 @@ CropDistortNode::CropDistortNode()
AddInput(kFeatherInput, NodeValue::kFloat, 0.0);
SetInputProperty(kFeatherInput, QStringLiteral("min"), 0.0);
// Initiate gizmos
poly_gizmo_ = AddDraggableGizmo<PolygonGizmo>({kLeftInput, kTopInput, kRightInput, kBottomInput});
point_gizmo_[kGizmoScaleTopLeft] = AddDraggableGizmo<PointGizmo>({kLeftInput, kTopInput});
point_gizmo_[kGizmoScaleTopCenter] = AddDraggableGizmo<PointGizmo>({kTopInput});
point_gizmo_[kGizmoScaleTopRight] = AddDraggableGizmo<PointGizmo>({kRightInput, kTopInput});
point_gizmo_[kGizmoScaleBottomLeft] = AddDraggableGizmo<PointGizmo>({kLeftInput, kBottomInput});
point_gizmo_[kGizmoScaleBottomCenter] = AddDraggableGizmo<PointGizmo>({kBottomInput});
point_gizmo_[kGizmoScaleBottomRight] = AddDraggableGizmo<PointGizmo>({kRightInput, kBottomInput});
point_gizmo_[kGizmoScaleCenterLeft] = AddDraggableGizmo<PointGizmo>({kLeftInput});
point_gizmo_[kGizmoScaleCenterRight] = AddDraggableGizmo<PointGizmo>({kRightInput});
SetFlags(kVideoEffect);
SetEffectInput(kTextureInput);
}
void CropDistortNode::Retranslate()
{
super::Retranslate();
SetInputName(kTextureInput, tr("Texture"));
SetInputName(kLeftInput, tr("Left"));
SetInputName(kTopInput, tr("Top"));
@@ -81,164 +100,50 @@ ShaderCode CropDistortNode::GetShaderCode(const QString &shader_id) const
return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/crop.frag")));
}
void CropDistortNode::DrawGizmos(const NodeValueRow &row, const NodeGlobals &globals, QPainter *p)
void CropDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals)
{
const QVector2D &resolution = globals.resolution();
const double handle_radius = GetGizmoHandleRadius(p->transform());
p->setPen(QPen(Qt::white, 0));
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 center_x_pt = lerp(left_pt, right_pt, 0.5);
double center_y_pt = lerp(top_pt, bottom_pt, 0.5);
double center_x_pt = mid(left_pt, right_pt);
double center_y_pt = mid(top_pt, bottom_pt);
gizmo_whole_rect_ = QRectF(left_pt, top_pt, right_pt - left_pt, bottom_pt - top_pt);
p->drawRect(gizmo_whole_rect_);
point_gizmo_[kGizmoScaleTopLeft]->SetPoint(QPointF(left_pt, top_pt));
point_gizmo_[kGizmoScaleTopCenter]->SetPoint(QPointF(center_x_pt, top_pt));
point_gizmo_[kGizmoScaleTopRight]->SetPoint(QPointF(right_pt, top_pt));
point_gizmo_[kGizmoScaleBottomLeft]->SetPoint(QPointF(left_pt, bottom_pt));
point_gizmo_[kGizmoScaleBottomCenter]->SetPoint(QPointF(center_x_pt, bottom_pt));
point_gizmo_[kGizmoScaleBottomRight]->SetPoint(QPointF(right_pt, bottom_pt));
point_gizmo_[kGizmoScaleCenterLeft]->SetPoint(QPointF(left_pt, center_y_pt));
point_gizmo_[kGizmoScaleCenterRight]->SetPoint(QPointF(right_pt, center_y_pt));
gizmo_resize_handle_[kGizmoScaleTopLeft] = CreateGizmoHandleRect(QPointF(left_pt, top_pt), handle_radius);
gizmo_resize_handle_[kGizmoScaleTopCenter] = CreateGizmoHandleRect(QPointF(center_x_pt, top_pt), handle_radius);
gizmo_resize_handle_[kGizmoScaleTopRight] = CreateGizmoHandleRect(QPointF(right_pt, top_pt), handle_radius);
gizmo_resize_handle_[kGizmoScaleBottomLeft] = CreateGizmoHandleRect(QPointF(left_pt, bottom_pt), handle_radius);
gizmo_resize_handle_[kGizmoScaleBottomCenter] = CreateGizmoHandleRect(QPointF(center_x_pt, bottom_pt), handle_radius);
gizmo_resize_handle_[kGizmoScaleBottomRight] = CreateGizmoHandleRect(QPointF(right_pt, bottom_pt), handle_radius);
gizmo_resize_handle_[kGizmoScaleCenterLeft] = CreateGizmoHandleRect(QPointF(left_pt, center_y_pt), handle_radius);
gizmo_resize_handle_[kGizmoScaleCenterRight] = CreateGizmoHandleRect(QPointF(right_pt, center_y_pt), handle_radius);
DrawAndExpandGizmoHandles(p, handle_radius, gizmo_resize_handle_, kGizmoScaleCount);
poly_gizmo_->SetPolygon(QRectF(left_pt, top_pt, right_pt - left_pt, bottom_pt - top_pt));
}
bool CropDistortNode::GizmoPress(const NodeValueRow &row, const NodeGlobals &globals, const QPointF &p)
void CropDistortNode::GizmoDragMove(double x_diff, double y_diff, const Qt::KeyboardModifiers &modifiers)
{
bool found_handle = false;
DraggableGizmo *gizmo = static_cast<DraggableGizmo*>(sender());
bool gizmo_active[kGizmoScaleCount] = {false};
QVector2D res = gizmo->GetGlobals().resolution();
x_diff /= res.x();
y_diff /= res.y();
for (int i=0; i<kGizmoScaleCount; i++) {
gizmo_active[i] = gizmo_resize_handle_[i].contains(p);
if (gizmo_active[i]) {
found_handle = true;
break;
for (int j=0; j<gizmo->GetDraggers().size(); j++) {
NodeInputDragger& i = gizmo->GetDraggers()[j];
double s = i.GetStartValue().toDouble();
if (i.GetInput().input().input() == kLeftInput) {
i.Drag(s + x_diff);
} else if (i.GetInput().input().input() == kTopInput) {
i.Drag(s + y_diff);
} else if (i.GetInput().input().input() == kRightInput) {
i.Drag(s - x_diff);
} else if (i.GetInput().input().input() == kBottomInput) {
i.Drag(s - y_diff);
}
}
bool in_rect = found_handle ? false : gizmo_whole_rect_.contains(p);
gizmo_drag_ = kGizmoNone;
// Drag handle
if (gizmo_active[kGizmoScaleTopLeft]
|| gizmo_active[kGizmoScaleCenterLeft]
|| gizmo_active[kGizmoScaleBottomLeft]
|| in_rect) {
gizmo_drag_ |= kGizmoLeft;
gizmo_start_.append(row[kLeftInput].data());
}
if (gizmo_active[kGizmoScaleTopLeft]
|| gizmo_active[kGizmoScaleTopCenter]
|| gizmo_active[kGizmoScaleTopRight]
|| in_rect) {
gizmo_drag_ |= kGizmoTop;
gizmo_start_.append(row[kTopInput].data());
}
if (gizmo_active[kGizmoScaleTopRight]
|| gizmo_active[kGizmoScaleCenterRight]
|| gizmo_active[kGizmoScaleBottomRight]
|| in_rect) {
gizmo_drag_ |= kGizmoRight;
gizmo_start_.append(row[kRightInput].data());
}
if (gizmo_active[kGizmoScaleBottomLeft]
|| gizmo_active[kGizmoScaleBottomCenter]
|| gizmo_active[kGizmoScaleBottomRight]
|| in_rect) {
gizmo_drag_ |= kGizmoBottom;
gizmo_start_.append(row[kBottomInput].data());
}
if (gizmo_drag_ > kGizmoNone) {
gizmo_res_ = globals.resolution();
gizmo_drag_start_ = p;
return true;
}
return false;
}
void CropDistortNode::GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers)
{
if (gizmo_dragger_.isEmpty()) {
gizmo_dragger_.resize(gizmo_start_.size());
int counter = 0;
if (gizmo_drag_ & kGizmoLeft) {
gizmo_dragger_[counter].Start(NodeInput(this, kLeftInput), time);
counter++;
}
if (gizmo_drag_ & kGizmoTop) {
gizmo_dragger_[counter].Start(NodeInput(this, kTopInput), time);
counter++;
}
if (gizmo_drag_ & kGizmoRight) {
gizmo_dragger_[counter].Start(NodeInput(this, kRightInput), time);
counter++;
}
if (gizmo_drag_ & kGizmoBottom) {
gizmo_dragger_[counter].Start(NodeInput(this, kBottomInput), time);
counter++;
}
}
int counter = 0;
double x_diff = (p.x() - gizmo_drag_start_.x()) / gizmo_res_.x();
double y_diff = (p.y() - gizmo_drag_start_.y()) / gizmo_res_.y();
if (gizmo_drag_ & kGizmoLeft) {
gizmo_dragger_[counter].Drag(gizmo_start_[counter].toDouble() + x_diff);
counter++;
}
if (gizmo_drag_ & kGizmoTop) {
gizmo_dragger_[counter].Drag(gizmo_start_[counter].toDouble() + y_diff);
counter++;
}
if (gizmo_drag_ & kGizmoRight) {
gizmo_dragger_[counter].Drag(gizmo_start_[counter].toDouble() - x_diff);
counter++;
}
if (gizmo_drag_ & kGizmoBottom) {
gizmo_dragger_[counter].Drag(gizmo_start_[counter].toDouble() - y_diff);
counter++;
}
}
void CropDistortNode::GizmoRelease(MultiUndoCommand *command)
{
for (NodeInputDragger& i : gizmo_dragger_) {
i.End(command);
}
gizmo_dragger_.clear();
gizmo_start_.clear();
}
void CropDistortNode::CreateCropSideInput(const QString &id)
+8 -27
View File
@@ -23,6 +23,8 @@
#include <QVector2D>
#include "node/gizmo/point.h"
#include "node/gizmo/polygon.h"
#include "node/inputdragger.h"
#include "node/node.h"
@@ -67,16 +69,7 @@ public:
virtual ShaderCode GetShaderCode(const QString &shader_id) const override;
virtual bool HasGizmos() const override
{
return true;
}
virtual void DrawGizmos(const NodeValueRow& row, const NodeGlobals &globals, QPainter *p) override;
virtual bool GizmoPress(const NodeValueRow& row, const NodeGlobals &globals, const QPointF &p) override;
virtual void GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) override;
virtual void GizmoRelease(MultiUndoCommand *command) override;
virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override;
static const QString kTextureInput;
static const QString kLeftInput;
@@ -85,27 +78,15 @@ public:
static const QString kBottomInput;
static const QString kFeatherInput;
protected slots:
virtual void GizmoDragMove(double delta_x, double delta_y, const Qt::KeyboardModifiers &modifiers) override;
private:
void CreateCropSideInput(const QString& id);
// Gizmo variables
QRectF gizmo_resize_handle_[kGizmoScaleCount];
QRectF gizmo_whole_rect_;
enum GizmoDragDirection {
kGizmoNone = 0x0,
kGizmoLeft = 0x1,
kGizmoTop = 0x2,
kGizmoRight = 0x4,
kGizmoBottom = 0x8,
kGizmoRectangle = 0xFF
};
int gizmo_drag_;
QVector<NodeInputDragger> gizmo_dragger_;
QVector<QVariant> gizmo_start_;
QPointF gizmo_drag_start_;
QVector2D gizmo_res_;
PointGizmo *point_gizmo_[kGizmoScaleCount];
PolygonGizmo *poly_gizmo_;
};
@@ -26,6 +26,8 @@ const QString FlipDistortNode::kTextureInput = QStringLiteral("tex_in");
const QString FlipDistortNode::kHorizontalInput = QStringLiteral("horiz_in");
const QString FlipDistortNode::kVerticalInput = QStringLiteral("vert_in");
#define super Node
FlipDistortNode::FlipDistortNode()
{
AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
@@ -33,6 +35,9 @@ FlipDistortNode::FlipDistortNode()
AddInput(kHorizontalInput, NodeValue::kBoolean, false);
AddInput(kVerticalInput, NodeValue::kBoolean, false);
SetFlags(kVideoEffect);
SetEffectInput(kTextureInput);
}
Node* FlipDistortNode::copy() const
@@ -62,6 +67,8 @@ QString FlipDistortNode::Description() const
void FlipDistortNode::Retranslate()
{
super::Retranslate();
SetInputName(kTextureInput, tr("Input"));
SetInputName(kHorizontalInput, tr("Horizontal"));
SetInputName(kVerticalInput, tr("Vertical"));
@@ -32,7 +32,7 @@ const QString TransformDistortNode::kTextureInput = QStringLiteral("tex_in");
const QString TransformDistortNode::kAutoscaleInput = QStringLiteral("autoscale_in");
const QString TransformDistortNode::kInterpolationInput = QStringLiteral("interpolation_in");
#define super Node
#define super MatrixGenerator
TransformDistortNode::TransformDistortNode()
{
@@ -41,11 +41,37 @@ TransformDistortNode::TransformDistortNode()
AddInput(kInterpolationInput, NodeValue::kCombo, 2);
PrependInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
// Initiate gizmos
rotation_gizmo_ = AddDraggableGizmo<ScreenGizmo>();
rotation_gizmo_->AddInput(NodeInput(this, kRotationInput));
rotation_gizmo_->SetDragValueBehavior(ScreenGizmo::kAbsolute);
poly_gizmo_ = AddDraggableGizmo<PolygonGizmo>();
poly_gizmo_->AddInput(NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 0));
poly_gizmo_->AddInput(NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 1));
anchor_gizmo_ = AddDraggableGizmo<PointGizmo>();
anchor_gizmo_->SetShape(PointGizmo::kAnchorPoint);
anchor_gizmo_->AddInput(NodeKeyframeTrackReference(NodeInput(this, kAnchorInput), 0));
anchor_gizmo_->AddInput(NodeKeyframeTrackReference(NodeInput(this, kAnchorInput), 1));
anchor_gizmo_->AddInput(NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 0));
anchor_gizmo_->AddInput(NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 1));
for (int i=0; i<kGizmoScaleCount; i++) {
point_gizmo_[i] = AddDraggableGizmo<PointGizmo>();
point_gizmo_[i]->AddInput(NodeKeyframeTrackReference(NodeInput(this, kScaleInput), 0));
point_gizmo_[i]->AddInput(NodeKeyframeTrackReference(NodeInput(this, kScaleInput), 1));
point_gizmo_[i]->SetDragValueBehavior(PointGizmo::kAbsolute);
}
SetFlags(kVideoEffect);
SetEffectInput(kTextureInput);
}
void TransformDistortNode::Retranslate()
{
MatrixGenerator::Retranslate();
super::Retranslate();
SetInputName(kAutoscaleInput, tr("Auto-Scale"));
SetInputName(kTextureInput, tr("Texture"));
@@ -101,218 +127,6 @@ ShaderCode TransformDistortNode::GetShaderCode(const QString &shader_id) const
return ShaderCode();
}
bool TransformDistortNode::GizmoPress(const NodeValueRow &row, const NodeGlobals &globals, const QPointF &p)
{
TexturePtr tex = row[kTextureInput].data().value<TexturePtr>();
if (!tex) {
return false;
}
// Store cursor position
gizmo_drag_pos_ = p;
// Check scaling
bool gizmo_scale_active[kGizmoScaleCount] = {false};
bool scaling = false;
for (int i=0; i<kGizmoScaleCount; i++) {
gizmo_scale_active[i] = gizmo_resize_handle_[i].contains(p);
if (gizmo_scale_active[i]) {
scaling = true;
break;
}
}
if (scaling) {
// Dragging scale handle
gizmo_start_ = {row[kScaleInput].data()};
gizmo_drag_ = kScaleInput;
gizmo_scale_uniform_ = row[kUniformScaleInput].data().toBool();
if (gizmo_scale_active[kGizmoScaleTopLeft] || gizmo_scale_active[kGizmoScaleTopRight]
|| gizmo_scale_active[kGizmoScaleBottomLeft] || gizmo_scale_active[kGizmoScaleBottomRight]) {
gizmo_scale_axes_ = kGizmoScaleBoth;
} else if (gizmo_scale_active[kGizmoScaleCenterLeft] || gizmo_scale_active[kGizmoScaleCenterRight]) {
gizmo_scale_axes_ = kGizmoScaleXOnly;
} else {
gizmo_scale_axes_ = kGizmoScaleYOnly;
}
// 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;
if (gizmo_scale_active[kGizmoScaleTopRight]
|| gizmo_scale_active[kGizmoScaleBottomRight]
|| gizmo_scale_active[kGizmoScaleCenterRight]) {
// Right handles, flip X axis
gizmo_scale_anchor_.setX(texture_sz.x() - gizmo_scale_anchor_.x());
}
if (gizmo_scale_active[kGizmoScaleBottomLeft]
|| gizmo_scale_active[kGizmoScaleBottomRight]
|| gizmo_scale_active[kGizmoScaleBottomCenter]) {
// Bottom handles, flip Y axis
gizmo_scale_anchor_.setY(texture_sz.y() - gizmo_scale_anchor_.y());
}
// Store current matrix
gizmo_matrix_ = GenerateMatrix(row, false, true, true, true);
return true;
} else if (gizmo_anchor_pt_.contains(p)) {
// Dragging the anchor point specifically
gizmo_start_ = {row[kAnchorInput].data(),
row[kPositionInput].data()};
gizmo_drag_ = kAnchorInput;
// Store current matrix
gizmo_matrix_ = GenerateMatrix(row, false, true, true, false);
return true;
} else if (gizmo_rect_.containsPoint(p, Qt::OddEvenFill)) {
// Dragging the main rectangle
gizmo_start_ = {row[kPositionInput].data()};
gizmo_drag_ = kPositionInput;
return true;
} else {
// Dragging rotation
gizmo_start_ = {row[kRotationInput].data()};
gizmo_drag_ = kRotationInput;
gizmo_start_angle_ = qAtan2(gizmo_drag_pos_.y() - gizmo_anchor_pt_.center().y(),
gizmo_drag_pos_.x() - gizmo_anchor_pt_.center().x());
return true;
}
return false;
}
void TransformDistortNode::GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers)
{
QPointF movement = (p - gizmo_drag_pos_);
QVector2D vec_movement(movement);
if (gizmo_drag_ == kAnchorInput) {
// Dragging the anchor point around
if (gizmo_dragger_.isEmpty()) {
gizmo_dragger_.resize(4);
gizmo_dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, kAnchorInput), 0), time);
gizmo_dragger_[1].Start(NodeKeyframeTrackReference(NodeInput(this, kAnchorInput), 1), time);
gizmo_dragger_[2].Start(NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 0), time);
gizmo_dragger_[3].Start(NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 1), time);
}
QVector2D inverted_movement(gizmo_matrix_.toTransform().inverted().map(movement));
QVector2D anchor_cont = gizmo_start_[0].value<QVector2D>() + inverted_movement;
QVector2D position_cont = gizmo_start_[1].value<QVector2D>() + vec_movement;
gizmo_dragger_[0].Drag(anchor_cont.x());
gizmo_dragger_[1].Drag(anchor_cont.y());
gizmo_dragger_[2].Drag(position_cont.x());
gizmo_dragger_[3].Drag(position_cont.y());
} else if (gizmo_drag_ == kPositionInput) {
// Dragging the main rectangle around
if (gizmo_dragger_.isEmpty()) {
gizmo_dragger_.resize(2);
gizmo_dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 0), time);
gizmo_dragger_[1].Start(NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 1), time);
}
QVector2D position_cont = gizmo_start_[0].value<QVector2D>() + vec_movement;
gizmo_dragger_[0].Drag(position_cont.x());
gizmo_dragger_[1].Drag(position_cont.y());
} else if (gizmo_drag_ == kScaleInput) {
// Dragging a resize handle
if (gizmo_dragger_.isEmpty()) {
if (gizmo_scale_uniform_ || gizmo_scale_axes_ == kGizmoScaleXOnly) {
gizmo_dragger_.resize(1);
gizmo_dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, kScaleInput), 0), time);
} else if (gizmo_scale_axes_ == kGizmoScaleYOnly) {
gizmo_dragger_.resize(1);
gizmo_dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, kScaleInput), 1), time);
} else {
gizmo_dragger_.resize(2);
gizmo_dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, kScaleInput), 0), time);
gizmo_dragger_[1].Start(NodeKeyframeTrackReference(NodeInput(this, kScaleInput), 1), time);
}
}
QPointF mouse_relative = gizmo_matrix_.toTransform().inverted().map(QPointF(p - gizmo_anchor_pt_.center()));
double x_scaled_movement = qAbs(mouse_relative.x() / gizmo_scale_anchor_.x());
double y_scaled_movement = qAbs(mouse_relative.y() / gizmo_scale_anchor_.y());
switch (gizmo_scale_axes_) {
case kGizmoScaleXOnly:
gizmo_dragger_[0].Drag(x_scaled_movement);
break;
case kGizmoScaleYOnly:
gizmo_dragger_[0].Drag(y_scaled_movement);
break;
case kGizmoScaleBoth:
if (gizmo_scale_uniform_) {
double distance = std::hypot(mouse_relative.x(), mouse_relative.y());
double texture_diag = std::hypot(gizmo_scale_anchor_.x(), gizmo_scale_anchor_.y());
gizmo_dragger_[0].Drag(qAbs(distance / texture_diag));
} else {
gizmo_dragger_[0].Drag(x_scaled_movement);
gizmo_dragger_[1].Drag(y_scaled_movement);
}
break;
}
} else if (gizmo_drag_ == kRotationInput) {
// Dragging outside the rectangle to rotate
if (gizmo_dragger_.isEmpty()) {
gizmo_dragger_.resize(1);
gizmo_dragger_[0].Start(NodeInput(this, kRotationInput), time);
}
double current_angle = qAtan2(p.y() - gizmo_anchor_pt_.center().y(),
p.x() - gizmo_anchor_pt_.center().x());
double rotation_difference = (current_angle - gizmo_start_angle_) * 57.2958;
double rotation_cont = gizmo_start_[0].toDouble() + rotation_difference;
gizmo_dragger_[0].Drag(rotation_cont);
}
}
void TransformDistortNode::GizmoRelease(MultiUndoCommand *command)
{
for (NodeInputDragger& i : gizmo_dragger_) {
i.End(command);
}
gizmo_dragger_.clear();
gizmo_start_.clear();
gizmo_drag_ = nullptr;
}
void TransformDistortNode::Hash(QCryptographicHash &hash, const NodeGlobals &globals, const VideoParams &video_params) const
{
// If not connected to output, this will produce nothing
@@ -339,7 +153,169 @@ void TransformDistortNode::Hash(QCryptographicHash &hash, const NodeGlobals &glo
}
}
Node::Hash(out, GetValueHintForInput(kTextureInput), hash, globals, video_params);
super::Hash(out, GetValueHintForInput(kTextureInput), hash, globals, video_params);
}
void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, double y, const rational &time)
{
DraggableGizmo *gizmo = static_cast<DraggableGizmo*>(sender());
if (gizmo == anchor_gizmo_) {
gizmo_inverted_transform_ = GenerateMatrix(row, false, true, true, false).toTransform().inverted();
} else if (IsAScaleGizmo(gizmo)) {
// Dragging scale handle
TexturePtr tex = row[kTextureInput].data().value<TexturePtr>();
if (!tex) {
return;
}
gizmo_scale_uniform_ = row[kUniformScaleInput].data().toBool();
gizmo_anchor_pt_ = (row[kAnchorInput].data().value<QVector2D>() + gizmo->GetGlobals().resolution()/2).toPointF();
if (gizmo == point_gizmo_[kGizmoScaleTopLeft] || gizmo == point_gizmo_[kGizmoScaleTopRight]
|| gizmo == point_gizmo_[kGizmoScaleBottomLeft] || gizmo == point_gizmo_[kGizmoScaleBottomRight]) {
gizmo_scale_axes_ = kGizmoScaleBoth;
} else if (gizmo == point_gizmo_[kGizmoScaleCenterLeft] || gizmo == point_gizmo_[kGizmoScaleCenterRight]) {
gizmo_scale_axes_ = kGizmoScaleXOnly;
} else {
gizmo_scale_axes_ = kGizmoScaleYOnly;
}
// 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;
if (gizmo == point_gizmo_[kGizmoScaleTopRight]
|| gizmo == point_gizmo_[kGizmoScaleBottomRight]
|| gizmo == point_gizmo_[kGizmoScaleCenterRight]) {
// Right handles, flip X axis
gizmo_scale_anchor_.setX(texture_sz.x() - gizmo_scale_anchor_.x());
}
if (gizmo == point_gizmo_[kGizmoScaleBottomLeft]
|| gizmo == point_gizmo_[kGizmoScaleBottomRight]
|| gizmo == point_gizmo_[kGizmoScaleBottomCenter]) {
// Bottom handles, flip Y axis
gizmo_scale_anchor_.setY(texture_sz.y() - gizmo_scale_anchor_.y());
}
// Store current matrix
gizmo_inverted_transform_ = GenerateMatrix(row, false, true, true, true).toTransform().inverted();
} else if (gizmo == rotation_gizmo_) {
gizmo_anchor_pt_ = (row[kAnchorInput].data().value<QVector2D>() + 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());
gizmo_rotate_wrap_ = 0;
gizmo_rotate_last_dir_ = kDirectionNone;
}
}
void TransformDistortNode::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers)
{
DraggableGizmo *gizmo = static_cast<DraggableGizmo*>(sender());
if (gizmo == poly_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);
} else if (gizmo == anchor_gizmo_) {
NodeInputDragger &x_anchor_drag = gizmo->GetDraggers()[0];
NodeInputDragger &y_anchor_drag = gizmo->GetDraggers()[1];
NodeInputDragger &x_pos_drag = gizmo->GetDraggers()[2];
NodeInputDragger &y_pos_drag = gizmo->GetDraggers()[3];
QPointF inverted_movement(gizmo_inverted_transform_.map(QPointF(x, y)));
x_anchor_drag.Drag(x_anchor_drag.GetStartValue().toDouble() + inverted_movement.x());
y_anchor_drag.Drag(y_anchor_drag.GetStartValue().toDouble() + inverted_movement.y());
x_pos_drag.Drag(x_pos_drag.GetStartValue().toDouble() + x);
y_pos_drag.Drag(y_pos_drag.GetStartValue().toDouble() + y);
} else if (gizmo == rotation_gizmo_) {
double raw_angle = qAtan2(y - gizmo_anchor_pt_.y(), x - gizmo_anchor_pt_.x());
double alt_angle = qAtan2(x - gizmo_anchor_pt_.x(), y - gizmo_anchor_pt_.y());
double current_angle = raw_angle;
// Detect rotation wrap around
RotationDirection this_dir = GetDirectionFromAngles(gizmo_last_angle_, raw_angle);
RotationDirection alt_dir = GetDirectionFromAngles(gizmo_last_alt_angle_, alt_angle);
if (gizmo_rotate_last_dir_ != kDirectionNone && this_dir != gizmo_rotate_last_dir_) {
if (alt_dir == gizmo_rotate_last_alt_dir_) {
if ((raw_angle - gizmo_last_angle_) < 0) {
gizmo_rotate_wrap_++;
} else {
gizmo_rotate_wrap_--;
}
this_dir = gizmo_rotate_last_dir_;
alt_dir = gizmo_rotate_last_alt_dir_;
}
}
gizmo_rotate_last_dir_ = this_dir;
gizmo_rotate_last_alt_dir_ = alt_dir;
gizmo_last_angle_ = raw_angle;
gizmo_last_alt_angle_ = alt_angle;
current_angle += M_PI*2*gizmo_rotate_wrap_;
// Convert radians to degrees
double rotation_difference = (current_angle - gizmo_start_angle_) * 57.2958;
NodeInputDragger &d = gizmo->GetDraggers()[0];
d.Drag(d.GetStartValue().toDouble() + rotation_difference);
} else if (IsAScaleGizmo(gizmo)) {
QPointF mouse_relative = gizmo_inverted_transform_.map(QPointF(x, y) - gizmo_anchor_pt_);
double x_scaled_movement = qAbs(mouse_relative.x() / gizmo_scale_anchor_.x());
double y_scaled_movement = qAbs(mouse_relative.y() / gizmo_scale_anchor_.y());
NodeInputDragger &x_drag = gizmo->GetDraggers()[0];
NodeInputDragger &y_drag = gizmo->GetDraggers()[1];
switch (gizmo_scale_axes_) {
case kGizmoScaleXOnly:
x_drag.Drag(x_scaled_movement);
break;
case kGizmoScaleYOnly:
if (gizmo_scale_uniform_) {
x_drag.Drag(y_scaled_movement);
} else {
y_drag.Drag(y_scaled_movement);
}
break;
case kGizmoScaleBoth:
if (gizmo_scale_uniform_) {
double distance = std::hypot(mouse_relative.x(), mouse_relative.y());
double texture_diag = std::hypot(gizmo_scale_anchor_.x(), gizmo_scale_anchor_.y());
x_drag.Drag(qAbs(distance / texture_diag));
} else {
x_drag.Drag(x_scaled_movement);
y_drag.Drag(y_scaled_movement);
}
break;
}
}
}
QMatrix4x4 TransformDistortNode::AdjustMatrixByResolutions(const QMatrix4x4 &mat, const QVector2D &sequence_res, const QVector2D &texture_res, AutoScaleType autoscale_type)
@@ -387,33 +363,13 @@ QMatrix4x4 TransformDistortNode::AdjustMatrixByResolutions(const QMatrix4x4 &mat
return adjusted_matrix;
}
QPointF TransformDistortNode::CreateScalePoint(double x, double y, const QPointF &half_res, const QMatrix4x4 &mat)
{
return mat.map(QPointF(x, y)) + half_res;
}
QMatrix4x4 TransformDistortNode::GenerateAutoScaledMatrix(const QMatrix4x4& generated_matrix, const NodeValueRow& value, const NodeGlobals &globals, const VideoParams& texture_params) const
{
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());
return AdjustMatrixByResolutions(generated_matrix,
sequence_res,
texture_res,
autoscale);
}
void TransformDistortNode::DrawGizmos(const NodeValueRow &row, const NodeGlobals &globals, QPainter *p)
void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals)
{
TexturePtr tex = row[kTextureInput].data().value<TexturePtr>();
if (!tex) {
return;
}
// 0 pen width is always 1px wide despite any transform
p->setPen(QPen(Qt::white, 0));
// Get the sequence resolution
const QVector2D &sequence_res = globals.resolution();
QVector2D sequence_half_res = sequence_res * 0.5;
@@ -441,14 +397,9 @@ void TransformDistortNode::DrawGizmos(const NodeValueRow &row, const NodeGlobals
QPointF(-1, 1),
QPointF(-1, -1)};
QTransform rectangle_transform = rectangle_matrix.toTransform();
gizmo_rect_ = rectangle_transform.map(points);
gizmo_rect_.translate(sequence_half_res_pt);
// Draw rectangle
p->drawPolyline(gizmo_rect_);
// Get handle size (relative to screen space rather than buffer space)
const double resize_handle_rad = GetGizmoHandleRadius(p->transform());
QPolygonF r = rectangle_transform.map(points);
r.translate(sequence_half_res_pt);
poly_gizmo_->SetPolygon(r);
// Draw anchor point
QMatrix4x4 anchor_matrix;
@@ -457,32 +408,17 @@ void TransformDistortNode::DrawGizmos(const NodeValueRow &row, const NodeGlobals
sequence_res,
tex_sz,
autoscale);
QPointF anchor_pt = anchor_matrix.toTransform().map(QPointF(0, 0)) + sequence_half_res_pt;
const double anchor_pt_radius = resize_handle_rad * 2;
gizmo_anchor_pt_ = QRectF(anchor_pt.x() - anchor_pt_radius,
anchor_pt.y() - anchor_pt_radius,
anchor_pt_radius*2,
anchor_pt_radius*2);
p->drawEllipse(anchor_pt, anchor_pt_radius, anchor_pt_radius);
p->drawLines({QLineF(anchor_pt.x() - anchor_pt_radius, anchor_pt.y(),
anchor_pt.x() + anchor_pt_radius, anchor_pt.y()),
QLineF(anchor_pt.x(), anchor_pt.y() - anchor_pt_radius,
anchor_pt.x(), anchor_pt.y() + anchor_pt_radius)});
anchor_gizmo_->SetPoint(anchor_matrix.toTransform().map(QPointF(0, 0)) + sequence_half_res_pt);
// Draw scale handles
gizmo_resize_handle_[kGizmoScaleTopLeft] = CreateGizmoHandleRect(CreateScalePoint(-1, -1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad);
gizmo_resize_handle_[kGizmoScaleTopCenter] = CreateGizmoHandleRect(CreateScalePoint( 0, -1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad);
gizmo_resize_handle_[kGizmoScaleTopRight] = CreateGizmoHandleRect(CreateScalePoint( 1, -1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad);
gizmo_resize_handle_[kGizmoScaleBottomLeft] = CreateGizmoHandleRect(CreateScalePoint(-1, 1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad);
gizmo_resize_handle_[kGizmoScaleBottomCenter] = CreateGizmoHandleRect(CreateScalePoint( 0, 1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad);
gizmo_resize_handle_[kGizmoScaleBottomRight] = CreateGizmoHandleRect(CreateScalePoint( 1, 1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad);
gizmo_resize_handle_[kGizmoScaleCenterLeft] = CreateGizmoHandleRect(CreateScalePoint(-1, 0, sequence_half_res_pt, rectangle_matrix), resize_handle_rad);
gizmo_resize_handle_[kGizmoScaleCenterRight] = CreateGizmoHandleRect(CreateScalePoint( 1, 0, sequence_half_res_pt, rectangle_matrix), resize_handle_rad);
DrawAndExpandGizmoHandles(p, resize_handle_rad, gizmo_resize_handle_, kGizmoScaleCount);
point_gizmo_[kGizmoScaleTopLeft]->SetPoint(CreateScalePoint(-1, -1, sequence_half_res_pt, rectangle_matrix));
point_gizmo_[kGizmoScaleTopCenter]->SetPoint(CreateScalePoint( 0, -1, sequence_half_res_pt, rectangle_matrix));
point_gizmo_[kGizmoScaleTopRight]->SetPoint(CreateScalePoint( 1, -1, sequence_half_res_pt, rectangle_matrix));
point_gizmo_[kGizmoScaleBottomLeft]->SetPoint(CreateScalePoint(-1, 1, sequence_half_res_pt, rectangle_matrix));
point_gizmo_[kGizmoScaleBottomCenter]->SetPoint(CreateScalePoint( 0, 1, sequence_half_res_pt, rectangle_matrix));
point_gizmo_[kGizmoScaleBottomRight]->SetPoint(CreateScalePoint( 1, 1, sequence_half_res_pt, rectangle_matrix));
point_gizmo_[kGizmoScaleCenterLeft]->SetPoint(CreateScalePoint(-1, 0, sequence_half_res_pt, rectangle_matrix));
point_gizmo_[kGizmoScaleCenterRight]->SetPoint(CreateScalePoint( 1, 0, sequence_half_res_pt, rectangle_matrix));
// Use offsets to make the appearance of values that start in the top left, even though we
// really anchor around the center
@@ -490,4 +426,37 @@ void TransformDistortNode::DrawGizmos(const NodeValueRow &row, const NodeGlobals
SetInputProperty(kAnchorInput, QStringLiteral("offset"), tex_sz * 0.5);
}
QPointF TransformDistortNode::CreateScalePoint(double x, double y, const QPointF &half_res, const QMatrix4x4 &mat)
{
return mat.map(QPointF(x, y)) + half_res;
}
QMatrix4x4 TransformDistortNode::GenerateAutoScaledMatrix(const QMatrix4x4& generated_matrix, const NodeValueRow& value, const NodeGlobals &globals, const VideoParams& texture_params) const
{
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());
return AdjustMatrixByResolutions(generated_matrix,
sequence_res,
texture_res,
autoscale);
}
bool TransformDistortNode::IsAScaleGizmo(NodeGizmo *g) const
{
for (int i=0; i<kGizmoScaleCount; i++) {
if (point_gizmo_[i] == g) {
return true;
}
}
return false;
}
TransformDistortNode::RotationDirection TransformDistortNode::GetDirectionFromAngles(double last, double current)
{
return (current > last) ? kDirectionPositive : kDirectionNegative;
}
}
@@ -22,6 +22,9 @@
#define TRANSFORMDISTORTNODE_H
#include "node/generator/matrix/matrix.h"
#include "node/gizmo/point.h"
#include "node/gizmo/polygon.h"
#include "node/gizmo/screen.h"
namespace olive {
@@ -69,17 +72,6 @@ public:
virtual ShaderCode GetShaderCode(const QString& shader_id) const override;
virtual bool HasGizmos() const override
{
return true;
}
virtual void DrawGizmos(const NodeValueRow &row, const NodeGlobals &globals, QPainter *p) override;
virtual bool GizmoPress(const NodeValueRow &row, const NodeGlobals &globals, const QPointF &p) override;
virtual void GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) override;
virtual void GizmoRelease(MultiUndoCommand *command) override;
enum AutoScaleType {
kAutoScaleNone,
kAutoScaleFit,
@@ -92,6 +84,8 @@ public:
const QVector2D& texture_res,
AutoScaleType autoscale_type = kAutoScaleNone);
virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override;
static const QString kTextureInput;
static const QString kAutoscaleInput;
static const QString kInterpolationInput;
@@ -99,18 +93,36 @@ public:
protected:
virtual void Hash(QCryptographicHash& hash, const NodeGlobals &globals, const VideoParams& video_params) const override;
protected slots:
virtual void GizmoDragStart(const olive::NodeValueRow &row, double x, double y, const olive::rational &time) override;
virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override;
private:
static QPointF CreateScalePoint(double x, double y, const QPointF& half_res, const QMatrix4x4& mat);
QMatrix4x4 GenerateAutoScaledMatrix(const QMatrix4x4 &generated_matrix, const NodeValueRow &db, const NodeGlobals &globals, const VideoParams &texture_params) const;
bool IsAScaleGizmo(NodeGizmo *g) const;
// Gizmo variables
QString gizmo_drag_;
QVector<QVariant> gizmo_start_;
QVector<NodeInputDragger> gizmo_dragger_;
QPointF gizmo_drag_pos_;
double gizmo_start_angle_;
QTransform gizmo_inverted_transform_;
QPointF gizmo_anchor_pt_;
bool gizmo_scale_uniform_;
double gizmo_last_angle_;
double gizmo_last_alt_angle_;
int gizmo_rotate_wrap_;
enum RotationDirection {
kDirectionNone,
kDirectionPositive, // Clockwise
kDirectionNegative // Counter-clockwise
};
static RotationDirection GetDirectionFromAngles(double last, double current);
RotationDirection gizmo_rotate_last_dir_;
RotationDirection gizmo_rotate_last_alt_dir_;
enum GizmoScaleType {
kGizmoScaleXOnly,
@@ -119,13 +131,13 @@ private:
};
GizmoScaleType gizmo_scale_axes_;
QMatrix4x4 gizmo_matrix_;
QVector2D gizmo_scale_anchor_;
// Gizmo on screen object storage
QPolygonF gizmo_rect_;
QRectF gizmo_anchor_pt_;
QRectF gizmo_resize_handle_[kGizmoScaleCount];
PointGizmo *point_gizmo_[kGizmoScaleCount];
PointGizmo *anchor_gizmo_;
PolygonGizmo *poly_gizmo_;
ScreenGizmo *rotation_gizmo_;
};
+37 -11
View File
@@ -5,7 +5,10 @@
namespace olive {
#define super NodeGroup
#define super Node
const QString OpacityEffect::kTextureInput = QStringLiteral("tex_in");
const QString OpacityEffect::kValueInput = QStringLiteral("opacity_in");
OpacityEffect::OpacityEffect()
{
@@ -15,24 +18,47 @@ OpacityEffect::OpacityEffect()
SetNodePositionInContext(math, QPointF(0, 0));
tex_in_pass_ = AddInputPassthrough(NodeInput(math, MathNode::kParamAIn), InputFlags(kInputFlagNotKeyframable));
SetInputDataType(tex_in_pass_, NodeValue::kTexture);
AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
value_in_pass_ = AddInputPassthrough(NodeInput(math, MathNode::kParamBIn));
SetInputProperty(value_in_pass_, QStringLiteral("view"), FloatSlider::kPercentage);
SetInputProperty(value_in_pass_, QStringLiteral("min"), 0.0);
SetInputProperty(value_in_pass_, QStringLiteral("max"), 1.0);
math->SetStandardValue(MathNode::kParamBIn, 1.0);
AddInput(kValueInput, NodeValue::kFloat, 1.0);
SetInputProperty(kValueInput, QStringLiteral("view"), FloatSlider::kPercentage);
SetInputProperty(kValueInput, QStringLiteral("min"), 0.0);
SetInputProperty(kValueInput, QStringLiteral("max"), 1.0);
SetOutputPassthrough(math);
SetFlags(kVideoEffect);
SetEffectInput(kTextureInput);
}
void OpacityEffect::Retranslate()
{
super::Retranslate();
SetInputName(tex_in_pass_, tr("Texture"));
SetInputName(value_in_pass_, tr("Opacity"));
SetInputName(kTextureInput, tr("Texture"));
SetInputName(kValueInput, tr("Opacity"));
}
ShaderCode OpacityEffect::GetShaderCode(const QString &shader_id) const
{
Q_UNUSED(shader_id)
return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/opacity.frag"));
}
void OpacityEffect::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
{
ShaderJob job;
job.InsertValue(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)) {
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this);
} else {
// 1.0 float is a no-op, so just push the texture
table->Push(job.GetValue(kTextureInput));
}
}
}
}
+8 -6
View File
@@ -5,7 +5,7 @@
namespace olive {
class OpacityEffect : public NodeGroup
class OpacityEffect : public Node
{
public:
OpacityEffect();
@@ -21,12 +21,12 @@ public:
virtual QString id() const override
{
return QStringLiteral("org.olivevideoeditor.Olive.opacityeffect");
return QStringLiteral("org.olivevideoeditor.Olive.opacity");
}
virtual QVector<CategoryID> Category() const override
{
return {kCategoryFilter, kCategoryVideoEffect};
return {kCategoryFilter};
}
virtual QString Description() const override
@@ -36,9 +36,11 @@ public:
virtual void Retranslate() override;
private:
QString tex_in_pass_;
QString value_in_pass_;
virtual ShaderCode GetShaderCode(const QString &shader_id) const override;
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
static const QString kTextureInput;
static const QString kValueInput;
};
+28 -8
View File
@@ -29,6 +29,7 @@
#include "block/subtitle/subtitle.h"
#include "block/transition/crossdissolve/crossdissolvetransition.h"
#include "block/transition/diptocolor/diptocolortransition.h"
#include "distort/cornerpin/cornerpindistortnode.h"
#include "distort/crop/cropdistortnode.h"
#include "distort/flip/flipdistortnode.h"
#include "distort/transform/transformdistortnode.h"
@@ -38,8 +39,9 @@
#include "generator/polygon/polygon.h"
#include "generator/shape/shapenode.h"
#include "generator/solid/solid.h"
#include "generator/text/text.h"
#include "generator/text/textlegacy.h"
#include "generator/text/textv1.h"
#include "generator/text/textv2.h"
#include "generator/text/textv3.h"
#include "filter/blur/blur.h"
#include "filter/mosaic/mosaicfilternode.h"
#include "filter/stroke/stroke.h"
@@ -48,11 +50,14 @@
#include "math/math/math.h"
#include "math/merge/merge.h"
#include "math/trigonometry/trigonometry.h"
#include "keying/colordifferencekey/colordifferencekey.h"
#include "keying/despill/despill.h"
#include "output/track/track.h"
#include "output/viewer/viewer.h"
#include "project/folder/folder.h"
#include "project/footage/footage.h"
#include "project/sequence/sequence.h"
#include "time/timeoffset/timeoffsetnode.h"
#include "time/timeremap/timeremap.h"
namespace olive {
@@ -70,7 +75,8 @@ void NodeFactory::Initialize()
library_.append(created_node);
}
hidden_.append(kTextGeneratorLegacy);
hidden_.append(kTextGeneratorV1);
hidden_.append(kTextGeneratorV2);
hidden_.append(kGroupNode);
}
@@ -80,7 +86,7 @@ void NodeFactory::Destroy()
library_.clear();
}
Menu *NodeFactory::CreateMenu(QWidget* parent, bool create_none_item, Node::CategoryID restrict_to)
Menu *NodeFactory::CreateMenu(QWidget* parent, bool create_none_item, Node::CategoryID restrict_to, uint64_t restrict_flags)
{
Menu* menu = new Menu(parent);
menu->setToolTipsVisible(true);
@@ -93,6 +99,10 @@ Menu *NodeFactory::CreateMenu(QWidget* parent, bool create_none_item, Node::Cate
continue;
}
if (restrict_flags && !(n->GetFlags() & restrict_flags)) {
continue;
}
if (hidden_.contains(i)) {
// Skip this node
continue;
@@ -227,10 +237,12 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id)
return new MergeNode();
case kStrokeFilter:
return new StrokeFilterNode();
case kTextGeneratorLegacy:
return new TextGeneratorLegacy();
case kTextGenerator:
return new TextGenerator();
case kTextGeneratorV1:
return new TextGeneratorV1();
case kTextGeneratorV2:
return new TextGeneratorV2();
case kTextGeneratorV3:
return new TextGeneratorV3();
case kCrossDissolveTransition:
return new CrossDissolveTransition();
case kDipToColorTransition:
@@ -253,6 +265,10 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id)
return new SubtitleBlock();
case kShapeGenerator:
return new ShapeNode();
case kColorDifferenceKeyKeying:
return new ColorDifferenceKeyNode();
case kDespillKeying:
return new DespillNode();
case kGroupNode:
return new NodeGroup();
case kOpacityEffect:
@@ -261,6 +277,10 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id)
return new FlipDistortNode();
case kNoiseGenerator:
return new NoiseGeneratorNode();
case kTimeOffsetNode:
return new TimeOffsetNode();
case kCornerPinDistort:
return new CornerPinDistortNode();
case kInternalNodeCount:
break;
+8 -3
View File
@@ -48,8 +48,9 @@ public:
kSolidGenerator,
kMerge,
kStrokeFilter,
kTextGeneratorLegacy,
kTextGenerator,
kTextGeneratorV1,
kTextGeneratorV2,
kTextGeneratorV3,
kCrossDissolveTransition,
kDipToColorTransition,
kMosaicFilter,
@@ -61,10 +62,14 @@ public:
kTimeRemapNode,
kSubtitleBlock,
kShapeGenerator,
kColorDifferenceKeyKeying,
kDespillKeying,
kGroupNode,
kOpacityEffect,
kFlipDistort,
kNoiseGenerator,
kTimeOffsetNode,
kCornerPinDistort,
// Count value
kInternalNodeCount
@@ -76,7 +81,7 @@ public:
static void Destroy();
static Menu* CreateMenu(QWidget *parent, bool create_none_item = false, Node::CategoryID restrict_to = Node::kCategoryUnknown);
static Menu* CreateMenu(QWidget *parent, bool create_none_item = false, Node::CategoryID restrict_to = Node::kCategoryUnknown, uint64_t restrict_flags = 0);
static Node* CreateFromMenuAction(QAction* action);
+8 -1
View File
@@ -29,6 +29,8 @@ const QString BlurFilterNode::kHorizInput = QStringLiteral("horiz_in");
const QString BlurFilterNode::kVertInput = QStringLiteral("vert_in");
const QString BlurFilterNode::kRepeatEdgePixelsInput = QStringLiteral("repeat_edge_pixels_in");
#define super Node
BlurFilterNode::BlurFilterNode()
{
AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
@@ -42,7 +44,10 @@ BlurFilterNode::BlurFilterNode()
AddInput(kVertInput, NodeValue::kBoolean, true);
AddInput(kRepeatEdgePixelsInput, NodeValue::kBoolean, false);
AddInput(kRepeatEdgePixelsInput, NodeValue::kBoolean, true);
SetFlags(kVideoEffect);
SetEffectInput(kTextureInput);
}
Node *BlurFilterNode::copy() const
@@ -72,6 +77,8 @@ QString BlurFilterNode::Description() const
void BlurFilterNode::Retranslate()
{
super::Retranslate();
SetInputName(kTextureInput, tr("Input"));
SetInputName(kMethodInput, tr("Method"));
SetComboBoxStrings(kMethodInput, { tr("Box"), tr("Gaussian") });
@@ -26,6 +26,8 @@ const QString MosaicFilterNode::kTextureInput = QStringLiteral("tex_in");
const QString MosaicFilterNode::kHorizInput = QStringLiteral("horiz_in");
const QString MosaicFilterNode::kVertInput = QStringLiteral("vert_in");
#define super Node
MosaicFilterNode::MosaicFilterNode()
{
AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
@@ -35,10 +37,15 @@ MosaicFilterNode::MosaicFilterNode()
AddInput(kVertInput, NodeValue::kFloat, 18.0);
SetInputProperty(kVertInput, QStringLiteral("min"), 1.0);
SetFlags(kVideoEffect);
SetEffectInput(kTextureInput);
}
void MosaicFilterNode::Retranslate()
{
super::Retranslate();
SetInputName(kTextureInput, tr("Texture"));
SetInputName(kHorizInput, tr("Horizontal"));
SetInputName(kVertInput, tr("Vertical"));
+7
View File
@@ -31,6 +31,8 @@ const QString StrokeFilterNode::kRadiusInput = QStringLiteral("radius_in");
const QString StrokeFilterNode::kOpacityInput = QStringLiteral("opacity_in");
const QString StrokeFilterNode::kInnerInput = QStringLiteral("inner_in");
#define super Node
StrokeFilterNode::StrokeFilterNode()
{
AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
@@ -46,6 +48,9 @@ StrokeFilterNode::StrokeFilterNode()
SetInputProperty(kOpacityInput, QStringLiteral("max"), 1.0f);
AddInput(kInnerInput, NodeValue::kBoolean, false);
SetFlags(kVideoEffect);
SetEffectInput(kTextureInput);
}
Node *StrokeFilterNode::copy() const
@@ -75,6 +80,8 @@ QString StrokeFilterNode::Description() const
void StrokeFilterNode::Retranslate()
{
super::Retranslate();
SetInputName(kTextureInput, tr("Input"));
SetInputName(kColorInput, tr("Color"));
SetInputName(kRadiusInput, tr("Radius"));
+4
View File
@@ -33,6 +33,8 @@ const QString MatrixGenerator::kScaleInput = QStringLiteral("scale_in");
const QString MatrixGenerator::kUniformScaleInput = QStringLiteral("uniform_scale_in");
const QString MatrixGenerator::kAnchorInput = QStringLiteral("anchor_in");
#define super Node
MatrixGenerator::MatrixGenerator()
{
AddInput(kPositionInput, NodeValue::kVec2, QVector2D(0.0, 0.0));
@@ -81,6 +83,8 @@ QString MatrixGenerator::Description() const
void MatrixGenerator::Retranslate()
{
super::Retranslate();
SetInputName(kPositionInput, tr("Position"));
SetInputName(kRotationInput, tr("Rotation"));
SetInputName(kScaleInput, tr("Scale"));
+4
View File
@@ -25,6 +25,8 @@ namespace olive {
const QString NoiseGeneratorNode::kColorInput = QStringLiteral("color_in");
const QString NoiseGeneratorNode::kStrengthInput = QStringLiteral("strength_in");
#define super Node
NoiseGeneratorNode::NoiseGeneratorNode()
{
AddInput(kStrengthInput, NodeValue::kFloat, 20);
@@ -59,6 +61,8 @@ QString NoiseGeneratorNode::Description() const
void NoiseGeneratorNode::Retranslate()
{
super::Retranslate();
SetInputName(kStrengthInput, tr("Strength"));
SetInputName(kColorInput, tr("Color"));
}
+76 -107
View File
@@ -30,6 +30,8 @@ namespace olive {
const QString PolygonGenerator::kPointsInput = QStringLiteral("points_in");
const QString PolygonGenerator::kColorInput = QStringLiteral("color_in");
#define super Node
PolygonGenerator::PolygonGenerator()
{
AddInput(kPointsInput, NodeValue::kBezier, QVector2D(0, 0), InputFlags(kInputFlagArray));
@@ -54,6 +56,9 @@ PolygonGenerator::PolygonGenerator()
SetSplitStandardValueOnTrack(kPointsInput, 1, kBottomY, 3);
SetSplitStandardValueOnTrack(kPointsInput, 0, -kMiddleX, 4);
SetSplitStandardValueOnTrack(kPointsInput, 1, -kMiddleY, 4);
// Initiate gizmos
poly_gizmo_ = new PathGizmo(this);
}
Node *PolygonGenerator::copy() const
@@ -83,6 +88,8 @@ QString PolygonGenerator::Description() const
void PolygonGenerator::Retranslate()
{
super::Retranslate();
SetInputName(kPointsInput, tr("Points"));
SetInputName(kColorInput, tr("Color"));
}
@@ -149,139 +156,101 @@ void PolygonGenerator::GenerateFrame(FramePtr frame, const GenerateJob &job) con
}
}
bool PolygonGenerator::HasGizmos() const
template<typename T>
NodeGizmo *PolygonGenerator::CreateAppropriateGizmo()
{
return true;
return new T(this);
}
void PolygonGenerator::DrawGizmos(const NodeValueRow &row, const NodeGlobals &globals, QPainter *p)
template<>
NodeGizmo *PolygonGenerator::CreateAppropriateGizmo<PointGizmo>()
{
const double handle_radius = GetGizmoHandleRadius(p->transform());
const double bezier_radius = handle_radius/2;
return AddDraggableGizmo<PointGizmo>();
}
p->setPen(Qt::white);
p->setBrush(Qt::white);
p->translate(globals.resolution_by_par().x()/2, globals.resolution_by_par().y()/2);
template<typename T>
void PolygonGenerator::ValidateGizmoVectorSize(QVector<T*> &vec, int new_sz)
{
int old_sz = vec.size();
if (old_sz != new_sz) {
if (old_sz > new_sz) {
for (int i=new_sz; i<old_sz; i++) {
delete vec.at(i);
}
}
vec.resize(new_sz);
if (old_sz < new_sz) {
for (int i=old_sz; i<new_sz; i++) {
vec[i] = static_cast<T*>(CreateAppropriateGizmo<T>());
}
}
}
}
void PolygonGenerator::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals)
{
QPointF half_res(globals.resolution_by_par().x()/2, globals.resolution_by_par().y()/2);
QVector<NodeValue> points = row[kPointsInput].data().value< QVector<NodeValue> >();
gizmo_position_handles_.resize(points.size());
gizmo_bezier_handles_.resize(points.size() * 2);
int current_pos_sz = gizmo_position_handles_.size();
p->setPen(QPen(Qt::white, 0));
p->setBrush(Qt::NoBrush);
ValidateGizmoVectorSize(gizmo_position_handles_, points.size());
ValidateGizmoVectorSize(gizmo_bezier_handles_, points.size() * 2);
ValidateGizmoVectorSize(gizmo_bezier_lines_, points.size() * 2);
for (int i=current_pos_sz; i<gizmo_position_handles_.size(); i++) {
gizmo_position_handles_.at(i)->AddInput(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 0));
gizmo_position_handles_.at(i)->AddInput(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 1));
PointGizmo *bez_gizmo1 = gizmo_bezier_handles_.at(i*2+0);
bez_gizmo1->AddInput(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 2));
bez_gizmo1->AddInput(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 3));
bez_gizmo1->SetShape(PointGizmo::kCircle);
bez_gizmo1->SetSmaller(true);
PointGizmo *bez_gizmo2 = gizmo_bezier_handles_.at(i*2+1);
bez_gizmo2->AddInput(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 4));
bez_gizmo2->AddInput(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 5));
bez_gizmo2->SetShape(PointGizmo::kCircle);
bez_gizmo2->SetSmaller(true);
}
if (!points.isEmpty()) {
QVector<QLineF> lines(points.size() * 2);
for (int i=0; i<points.size(); i++) {
const Bezier &pt = points.at(i).data().value<Bezier>();
QPointF main = pt.ToPointF();
QPointF main = pt.ToPointF() + half_res;
QPointF cp1 = main + pt.ControlPoint1ToPointF();
QPointF cp2 = main + pt.ControlPoint2ToPointF();
gizmo_position_handles_[i] = CreateGizmoHandleRect(main, handle_radius);
gizmo_position_handles_[i]->SetPoint(main);
gizmo_bezier_handles_[i*2] = CreateGizmoHandleRect(cp1, bezier_radius);
lines[i*2] = QLineF(main, cp1);
gizmo_bezier_handles_[i*2+1] = CreateGizmoHandleRect(cp2, bezier_radius);
lines[i*2+1] = QLineF(main, cp2);
gizmo_bezier_handles_[i*2]->SetPoint(cp1);
gizmo_bezier_lines_[i*2]->SetLine(QLineF(main, cp1));
gizmo_bezier_handles_[i*2+1]->SetPoint(cp2);
gizmo_bezier_lines_[i*2+1]->SetLine(QLineF(main, cp2));
}
p->drawLines(lines);
}
gizmo_polygon_path_ = GeneratePath(points);
p->drawPath(gizmo_polygon_path_);
DrawAndExpandGizmoHandles(p, handle_radius, gizmo_position_handles_.data(), gizmo_position_handles_.size());
DrawAndExpandGizmoHandles(p, handle_radius, gizmo_bezier_handles_.data(), gizmo_bezier_handles_.size());
poly_gizmo_->SetPath(GeneratePath(points).translated(half_res));
}
bool PolygonGenerator::GizmoPress(const NodeValueRow& row, const NodeGlobals &globals, const QPointF &p)
void PolygonGenerator::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers)
{
QPointF adjusted = p - (globals.resolution_by_par() / 2).toPointF();
DraggableGizmo *gizmo = static_cast<DraggableGizmo*>(sender());
// First, look for main points
for (int i=0; i<gizmo_position_handles_.size(); i++) {
if (gizmo_position_handles_.at(i).contains(adjusted)) {
gizmo_x_active_.append(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 0));
gizmo_y_active_.append(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 1));
break;
}
if (gizmo == poly_gizmo_) {
// FIXME: Drag all points
} else {
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);
}
// Next, if no main points were found, look for beziers
if (gizmo_x_active_.isEmpty() && gizmo_y_active_.isEmpty()) {
for (int i=0; i<gizmo_bezier_handles_.size(); i++) {
if (gizmo_bezier_handles_.at(i).contains(adjusted)) {
int start = (i%2 == 0) ? 2 : 4;
int element = i/2;
gizmo_x_active_.append(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, element), start + 0));
gizmo_y_active_.append(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, element), start + 1));
break;
}
}
// Finally, see if the cursor is inside the polygon
if (gizmo_x_active_.isEmpty() && gizmo_y_active_.isEmpty()) {
if (gizmo_polygon_path_.contains(adjusted)) {
gizmo_x_active_.resize(gizmo_position_handles_.size());
gizmo_y_active_.resize(gizmo_position_handles_.size());
for (int i=0; i<gizmo_position_handles_.size(); i++) {
gizmo_x_active_[i] = NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 0);
gizmo_y_active_[i] = NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 1);
}
}
}
}
gizmo_drag_start_ = p;
return !gizmo_x_active_.isEmpty() || !gizmo_y_active_.isEmpty();
}
void PolygonGenerator::GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers)
{
if (gizmo_x_draggers_.isEmpty() && gizmo_y_draggers_.isEmpty()) {
gizmo_x_draggers_.resize(gizmo_x_active_.size());
gizmo_y_draggers_.resize(gizmo_y_active_.size());
for (int i=0; i<gizmo_x_active_.size(); i++) {
gizmo_x_draggers_[i].Start(gizmo_x_active_.at(i), time);
}
for (int i=0; i<gizmo_y_active_.size(); i++) {
gizmo_y_draggers_[i].Start(gizmo_y_active_.at(i), time);
}
}
QPointF diff = p - gizmo_drag_start_;
for (NodeInputDragger &dragger : gizmo_x_draggers_) {
dragger.Drag(dragger.GetStartValue().toDouble() + diff.x());
}
for (NodeInputDragger &dragger : gizmo_y_draggers_) {
dragger.Drag(dragger.GetStartValue().toDouble() + diff.y());
}
}
void PolygonGenerator::GizmoRelease(MultiUndoCommand *command)
{
for (NodeInputDragger &dragger : gizmo_x_draggers_) {
dragger.End(command);
}
gizmo_x_draggers_.clear();
for (NodeInputDragger &dragger : gizmo_y_draggers_) {
dragger.End(command);
}
gizmo_y_draggers_.clear();
gizmo_x_active_.clear();
gizmo_y_active_.clear();
}
void PolygonGenerator::AddPointToPath(QPainterPath *path, const Bezier &before, const Bezier &after)
+16 -14
View File
@@ -24,6 +24,9 @@
#include <QPainterPath>
#include "common/bezier.h"
#include "node/gizmo/line.h"
#include "node/gizmo/path.h"
#include "node/gizmo/point.h"
#include "node/node.h"
#include "node/inputdragger.h"
@@ -50,30 +53,29 @@ public:
virtual void GenerateFrame(FramePtr frame, const GenerateJob &job) const override;
virtual bool HasGizmos() const override;
virtual void DrawGizmos(const NodeValueRow& row, const NodeGlobals &globals, QPainter *p) override;
virtual bool GizmoPress(const NodeValueRow& row, const NodeGlobals &globals, const QPointF &p) override;
virtual void GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) override;
virtual void GizmoRelease(MultiUndoCommand *command) override;
virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override;
static const QString kPointsInput;
static const QString kColorInput;
protected slots:
virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override;
private:
static void AddPointToPath(QPainterPath *path, const Bezier &before, const Bezier &after);
static QPainterPath GeneratePath(const QVector<NodeValue> &points);
QPainterPath gizmo_polygon_path_;
QVector<QRectF> gizmo_position_handles_;
QVector<QRectF> gizmo_bezier_handles_;
template<typename T>
void ValidateGizmoVectorSize(QVector<T*> &vec, int new_sz);
QVector<NodeKeyframeTrackReference> gizmo_x_active_;
QVector<NodeKeyframeTrackReference> gizmo_y_active_;
QVector<NodeInputDragger> gizmo_x_draggers_;
QVector<NodeInputDragger> gizmo_y_draggers_;
QPointF gizmo_drag_start_;
template<typename T>
NodeGizmo *CreateAppropriateGizmo();
PathGizmo *poly_gizmo_;
QVector<PointGizmo*> gizmo_position_handles_;
QVector<PointGizmo*> gizmo_bezier_handles_;
QVector<LineGizmo*> gizmo_bezier_lines_;
};
+148 -147
View File
@@ -23,7 +23,7 @@
#include <QtMath>
#include <QVector2D>
#include "common/lerp.h"
#include "common/util.h"
#include "core.h"
namespace olive {
@@ -34,12 +34,30 @@ QString ShapeNodeBase::kPositionInput = QStringLiteral("pos_in");
QString ShapeNodeBase::kSizeInput = QStringLiteral("size_in");
QString ShapeNodeBase::kColorInput = QStringLiteral("color_in");
ShapeNodeBase::ShapeNodeBase()
ShapeNodeBase::ShapeNodeBase(bool create_color_input)
{
AddInput(kPositionInput, NodeValue::kVec2, QVector2D(0, 0));
AddInput(kSizeInput, NodeValue::kVec2, QVector2D(100, 100));
SetInputProperty(kSizeInput, QStringLiteral("min"), QVector2D(0, 0));
AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(1.0, 0.0, 0.0, 1.0)));
if (create_color_input) {
AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(1.0, 0.0, 0.0, 1.0)));
}
// Initiate gizmos
QVector<NodeKeyframeTrackReference> pos_n_sz = {
NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 0),
NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 1),
NodeKeyframeTrackReference(NodeInput(this, kSizeInput), 0),
NodeKeyframeTrackReference(NodeInput(this, kSizeInput), 1)
};
poly_gizmo_ = AddDraggableGizmo<PolygonGizmo>({
NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 0),
NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 1),
});
for (int i=0; i<kGizmoScaleCount; i++) {
point_gizmo_[i] = AddDraggableGizmo<PointGizmo>(pos_n_sz, PointGizmo::kAbsolute);
}
}
void ShapeNodeBase::Retranslate()
@@ -48,20 +66,19 @@ void ShapeNodeBase::Retranslate()
SetInputName(kPositionInput, tr("Position"));
SetInputName(kSizeInput, tr("Size"));
SetInputName(kColorInput, tr("Color"));
if (HasInputWithID(kColorInput)) {
SetInputName(kColorInput, tr("Color"));
}
}
void ShapeNodeBase::DrawGizmos(const NodeValueRow &row, const NodeGlobals &globals, QPainter *p)
void ShapeNodeBase::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals)
{
// Use offsets to make the appearance of values that start in the top left, even though we
// really anchor around the center
QVector2D center_pt = globals.resolution() * 0.5;
SetInputProperty(kPositionInput, QStringLiteral("offset"), center_pt);
const double handle_radius = GetGizmoHandleRadius(p->transform());
p->setPen(QPen(Qt::white, 0));
QVector2D pos = row[kPositionInput].data().value<QVector2D>();
QVector2D sz = row[kSizeInput].data().value<QVector2D>();
QVector2D half_sz = sz * 0.5;
@@ -70,151 +87,73 @@ void ShapeNodeBase::DrawGizmos(const NodeValueRow &row, const NodeGlobals &globa
double top_pt = pos.y() + center_pt.y() - half_sz.y();
double right_pt = left_pt + sz.x();
double bottom_pt = top_pt + sz.y();
double center_x_pt = lerp(left_pt, right_pt, 0.5);
double center_y_pt = lerp(top_pt, bottom_pt, 0.5);
double center_x_pt = mid(left_pt, right_pt);
double center_y_pt = mid(top_pt, bottom_pt);
gizmo_whole_rect_ = QRectF(left_pt, top_pt, right_pt - left_pt, bottom_pt - top_pt);
p->drawRect(gizmo_whole_rect_);
point_gizmo_[kGizmoScaleTopLeft]->SetPoint(QPointF(left_pt, top_pt));
point_gizmo_[kGizmoScaleTopCenter]->SetPoint(QPointF(center_x_pt, top_pt));
point_gizmo_[kGizmoScaleTopRight]->SetPoint(QPointF(right_pt, top_pt));
point_gizmo_[kGizmoScaleBottomLeft]->SetPoint(QPointF(left_pt, bottom_pt));
point_gizmo_[kGizmoScaleBottomCenter]->SetPoint(QPointF(center_x_pt, bottom_pt));
point_gizmo_[kGizmoScaleBottomRight]->SetPoint(QPointF(right_pt, bottom_pt));
point_gizmo_[kGizmoScaleCenterLeft]->SetPoint(QPointF(left_pt, center_y_pt));
point_gizmo_[kGizmoScaleCenterRight]->SetPoint(QPointF(right_pt, center_y_pt));
gizmo_resize_handle_[kGizmoScaleTopLeft] = CreateGizmoHandleRect(QPointF(left_pt, top_pt), handle_radius);
gizmo_resize_handle_[kGizmoScaleTopCenter] = CreateGizmoHandleRect(QPointF(center_x_pt, top_pt), handle_radius);
gizmo_resize_handle_[kGizmoScaleTopRight] = CreateGizmoHandleRect(QPointF(right_pt, top_pt), handle_radius);
gizmo_resize_handle_[kGizmoScaleBottomLeft] = CreateGizmoHandleRect(QPointF(left_pt, bottom_pt), handle_radius);
gizmo_resize_handle_[kGizmoScaleBottomCenter] = CreateGizmoHandleRect(QPointF(center_x_pt, bottom_pt), handle_radius);
gizmo_resize_handle_[kGizmoScaleBottomRight] = CreateGizmoHandleRect(QPointF(right_pt, bottom_pt), handle_radius);
gizmo_resize_handle_[kGizmoScaleCenterLeft] = CreateGizmoHandleRect(QPointF(left_pt, center_y_pt), handle_radius);
gizmo_resize_handle_[kGizmoScaleCenterRight] = CreateGizmoHandleRect(QPointF(right_pt, center_y_pt), handle_radius);
DrawAndExpandGizmoHandles(p, handle_radius, gizmo_resize_handle_, kGizmoScaleCount);
poly_gizmo_->SetPolygon(QRectF(left_pt, top_pt, right_pt - left_pt, bottom_pt - top_pt));
}
bool ShapeNodeBase::GizmoPress(const NodeValueRow &row, const NodeGlobals &globals, const QPointF &p)
void ShapeNodeBase::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers)
{
gizmo_drag_ = -1;
DraggableGizmo *gizmo = static_cast<DraggableGizmo*>(sender());
// See if any of our resize handles have the pointer
for (int i=0; i<kGizmoScaleCount; i++) {
if (gizmo_resize_handle_[i].contains(p)) {
gizmo_drag_ = i;
break;
}
}
NodeInputDragger &x_drag = gizmo->GetDraggers()[0];
NodeInputDragger &y_drag = gizmo->GetDraggers()[1];
// See if the rect has the pointer
if (gizmo_drag_ == -1 && gizmo_whole_rect_.contains(p)) {
gizmo_drag_ = kGizmoWholeRect;
}
if (gizmo_drag_ >= 0) {
gizmo_pos_start_ = row[kPositionInput].data().value<QVector2D>();
gizmo_sz_start_ = row[kSizeInput].data().value<QVector2D>();
gizmo_drag_start_ = p;
gizmo_half_res_ = globals.resolution()/2;
// Get aspect ratio (avoiding potential zero)
if (qFuzzyIsNull(gizmo_sz_start_.y())) {
gizmo_aspect_ratio_ = 0;
} else {
gizmo_aspect_ratio_ = gizmo_sz_start_.x() / gizmo_sz_start_.y();
}
return true;
}
return false;
}
enum {
kGDPosX,
kGDPosY,
kGDSzX,
kGDSzY,
kGDCount
};
QVector2D ShapeNodeBase::GenerateGizmoAnchor(const QVector2D &pos, const QVector2D &size, int drag, QVector2D *pt = nullptr)
{
QVector2D anchor = pos;
QVector2D half_sz = size/2;
if (drag == kGizmoScaleTopLeft || drag == kGizmoScaleCenterLeft || drag == kGizmoScaleBottomLeft) {
anchor.setX(anchor.x() + half_sz.x());
if (pt && pt->x() > anchor.x()) {
pt->setX(anchor.x());
}
}
if (drag == kGizmoScaleTopRight || drag == kGizmoScaleCenterRight || drag == kGizmoScaleBottomRight) {
anchor.setX(anchor.x() - half_sz.x());
if (pt && pt->x() < anchor.x()) {
pt->setX(anchor.x());
}
}
if (drag == kGizmoScaleTopLeft || drag == kGizmoScaleTopCenter || drag == kGizmoScaleTopRight) {
anchor.setY(anchor.y() + half_sz.y());
if (pt && pt->y() > anchor.y()) {
pt->setY(anchor.y());
}
}
if (drag == kGizmoScaleBottomLeft || drag == kGizmoScaleBottomCenter || drag == kGizmoScaleBottomRight) {
anchor.setY(anchor.y() - half_sz.y());
if (pt && pt->y() < anchor.y()) {
pt->setY(anchor.y());
}
}
return anchor;
}
void ShapeNodeBase::GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers)
{
if (gizmo_dragger_.isEmpty()) {
gizmo_dragger_.resize(kGDCount);
gizmo_dragger_[kGDPosX].Start(NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 0), time);
gizmo_dragger_[kGDPosY].Start(NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 1), time);
gizmo_dragger_[kGDSzX].Start(NodeKeyframeTrackReference(NodeInput(this, kSizeInput), 0), time);
gizmo_dragger_[kGDSzY].Start(NodeKeyframeTrackReference(NodeInput(this, kSizeInput), 1), time);
}
bool from_center = modifiers & Qt::AltModifier;
bool keep_ratio = modifiers & Qt::ShiftModifier;
QVector2D adjusted_pt(p.x(), p.y());
QVector2D diff(adjusted_pt.x() - gizmo_drag_start_.x(), adjusted_pt.y() - gizmo_drag_start_.y());
if (gizmo_drag_ == kGizmoWholeRect) {
// Simply move position by difference
gizmo_dragger_[kGDPosX].Drag(gizmo_pos_start_.x() + diff.x());
gizmo_dragger_[kGDPosY].Drag(gizmo_pos_start_.y() + diff.y());
if (gizmo == poly_gizmo_) {
x_drag.Drag(x_drag.GetStartValue().toDouble() + x);
y_drag.Drag(y_drag.GetStartValue().toDouble() + y);
} else {
bool from_center = modifiers & Qt::AltModifier;
bool keep_ratio = modifiers & Qt::ShiftModifier;
NodeInputDragger &w_drag = gizmo->GetDraggers()[2];
NodeInputDragger &h_drag = gizmo->GetDraggers()[3];
QVector2D gizmo_sz_start(w_drag.GetStartValue().toDouble(), h_drag.GetStartValue().toDouble());
QVector2D gizmo_pos_start(x_drag.GetStartValue().toDouble(), y_drag.GetStartValue().toDouble());
QVector2D gizmo_half_res = gizmo->GetGlobals().resolution()/2;
QVector2D adjusted_pt(x, y);
QVector2D new_size;
QVector2D new_pos;
QVector2D anchor;
static const int kXYCount = 2;
bool negative[kXYCount] = {false};
double original_ratio;
if (keep_ratio) {
original_ratio = w_drag.GetStartValue().toDouble() / h_drag.GetStartValue().toDouble();
}
// Calculate new size
if (from_center) {
// Calculate new size by using distance from center and doubling it
new_size = (adjusted_pt - gizmo_half_res_ - gizmo_pos_start_) * 2;
new_size = (adjusted_pt - gizmo_half_res - gizmo_pos_start) * 2;
if (gizmo_drag_ == kGizmoScaleTopCenter || gizmo_drag_ == kGizmoScaleTopLeft || gizmo_drag_ == kGizmoScaleTopRight) {
if (IsGizmoTop(gizmo)) {
new_size.setY(-new_size.y());
}
if (gizmo_drag_ == kGizmoScaleTopLeft || gizmo_drag_ == kGizmoScaleCenterLeft || gizmo_drag_ == kGizmoScaleBottomLeft) {
if (IsGizmoLeft(gizmo)) {
new_size.setX(-new_size.x());
}
} else {
// Calculate new size by using distance from "anchor" - i.e. the opposite point of the shape
// from the gizmo being dragged
adjusted_pt -= gizmo_half_res_;
adjusted_pt -= gizmo_half_res;
anchor = GenerateGizmoAnchor(gizmo_pos_start_, gizmo_sz_start_, gizmo_drag_, &adjusted_pt) + gizmo_half_res_;
anchor = GenerateGizmoAnchor(gizmo_pos_start, gizmo_sz_start, gizmo, &adjusted_pt) + gizmo_half_res;
adjusted_pt += gizmo_half_res_;
adjusted_pt += gizmo_half_res;
// Calculate size and position
new_size = adjusted_pt - anchor;
@@ -229,32 +168,31 @@ void ShapeNodeBase::GizmoMove(const QPointF &p, const rational &time, const Qt::
}
// Restrict sizes by constraints
if (gizmo_drag_ == kGizmoScaleTopCenter || gizmo_drag_ == kGizmoScaleBottomCenter) {
if (IsGizmoVerticalCenter(gizmo)) {
if (keep_ratio) {
// Calculate width from new height
new_size.setX(new_size.y() * gizmo_aspect_ratio_);
new_size.setX(new_size.y() * original_ratio);
} else {
// Constrain to original width
new_size.setX(gizmo_sz_start_.x());
new_size.setX(gizmo_sz_start.x());
}
}
if (gizmo_drag_ == kGizmoScaleCenterLeft || gizmo_drag_ == kGizmoScaleCenterRight) {
if (IsGizmoHorizontalCenter(gizmo)) {
if (keep_ratio) {
// Calculate height from new width
new_size.setY(new_size.x() / gizmo_aspect_ratio_);
new_size.setY(new_size.x() / original_ratio);
} else {
// Constrain to original height
new_size.setY(gizmo_sz_start_.y());
new_size.setY(gizmo_sz_start.y());
}
}
if (gizmo_drag_ == kGizmoScaleTopLeft || gizmo_drag_ == kGizmoScaleTopRight
|| gizmo_drag_ == kGizmoScaleBottomRight || gizmo_drag_ == kGizmoScaleBottomLeft) {
if (IsGizmoCorner(gizmo)) {
if (keep_ratio) {
float hypot = std::hypot(new_size.x(), new_size.y());
float original_angle = std::atan2(gizmo_sz_start_.x(), gizmo_sz_start_.y());
float original_angle = std::atan2(gizmo_sz_start.x(), gizmo_sz_start.y());
// Calculate new size based on original angle and hypotenuse
new_size.setX(std::sin(original_angle) * hypot);
@@ -264,7 +202,7 @@ void ShapeNodeBase::GizmoMove(const QPointF &p, const rational &time, const Qt::
// Calculate position
if (from_center) {
new_pos = gizmo_pos_start_;
new_pos = gizmo_pos_start;
} else {
QVector2D using_size = new_size;
@@ -276,31 +214,94 @@ void ShapeNodeBase::GizmoMove(const QPointF &p, const rational &time, const Qt::
}
// I'm pretty sure there's an algorithmic way of doing this, but I'm tired and this works
if (gizmo_drag_ == kGizmoScaleCenterLeft || gizmo_drag_ == kGizmoScaleCenterRight) {
if (IsGizmoHorizontalCenter(gizmo)) {
using_size.setY(0);
}
if (gizmo_drag_ == kGizmoScaleTopCenter || gizmo_drag_ == kGizmoScaleBottomCenter) {
if (IsGizmoVerticalCenter(gizmo)) {
using_size.setX(0);
}
new_pos = GenerateGizmoAnchor(gizmo_pos_start_, gizmo_sz_start_, gizmo_drag_) + using_size / 2;
new_pos = GenerateGizmoAnchor(gizmo_pos_start, gizmo_sz_start, gizmo) + using_size / 2;
}
gizmo_dragger_[kGDPosX].Drag(new_pos.x());
gizmo_dragger_[kGDPosY].Drag(new_pos.y());
gizmo_dragger_[kGDSzX].Drag(new_size.x());
gizmo_dragger_[kGDSzY].Drag(new_size.y());
x_drag.Drag(new_pos.x());
y_drag.Drag(new_pos.y());
w_drag.Drag(new_size.x());
h_drag.Drag(new_size.y());
}
}
void ShapeNodeBase::GizmoRelease(MultiUndoCommand *command)
QVector2D ShapeNodeBase::GenerateGizmoAnchor(const QVector2D &pos, const QVector2D &size, NodeGizmo *gizmo, QVector2D *pt) const
{
for (NodeInputDragger& i : gizmo_dragger_) {
i.End(command);
QVector2D anchor = pos;
QVector2D half_sz = size/2;
if (IsGizmoLeft(gizmo)) {
anchor.setX(anchor.x() + half_sz.x());
if (pt && pt->x() > anchor.x()) {
pt->setX(anchor.x());
}
}
gizmo_dragger_.clear();
if (IsGizmoRight(gizmo)) {
anchor.setX(anchor.x() - half_sz.x());
if (pt && pt->x() < anchor.x()) {
pt->setX(anchor.x());
}
}
if (IsGizmoTop(gizmo)) {
anchor.setY(anchor.y() + half_sz.y());
if (pt && pt->y() > anchor.y()) {
pt->setY(anchor.y());
}
}
if (IsGizmoBottom(gizmo)) {
anchor.setY(anchor.y() - half_sz.y());
if (pt && pt->y() < anchor.y()) {
pt->setY(anchor.y());
}
}
return anchor;
}
bool ShapeNodeBase::IsGizmoTop(NodeGizmo *g) const
{
return g == point_gizmo_[kGizmoScaleTopCenter] || g == point_gizmo_[kGizmoScaleTopLeft] || g == point_gizmo_[kGizmoScaleTopRight];
}
bool ShapeNodeBase::IsGizmoBottom(NodeGizmo *g) const
{
return g == point_gizmo_[kGizmoScaleBottomCenter] || g == point_gizmo_[kGizmoScaleBottomLeft] || g == point_gizmo_[kGizmoScaleBottomRight];
}
bool ShapeNodeBase::IsGizmoLeft(NodeGizmo *g) const
{
return g == point_gizmo_[kGizmoScaleTopLeft] || g == point_gizmo_[kGizmoScaleCenterLeft] || g == point_gizmo_[kGizmoScaleBottomLeft];
}
bool ShapeNodeBase::IsGizmoRight(NodeGizmo *g) const
{
return g == point_gizmo_[kGizmoScaleTopRight] || g == point_gizmo_[kGizmoScaleCenterRight] || g == point_gizmo_[kGizmoScaleBottomRight];
}
bool ShapeNodeBase::IsGizmoHorizontalCenter(NodeGizmo *g) const
{
return g == point_gizmo_[kGizmoScaleCenterLeft] || g == point_gizmo_[kGizmoScaleCenterRight];
}
bool ShapeNodeBase::IsGizmoVerticalCenter(NodeGizmo *g) const
{
return g == point_gizmo_[kGizmoScaleTopCenter] || g == point_gizmo_[kGizmoScaleBottomCenter];
}
bool ShapeNodeBase::IsGizmoCorner(NodeGizmo *g) const
{
return g == point_gizmo_[kGizmoScaleTopLeft] || g == point_gizmo_[kGizmoScaleTopRight]
|| g == point_gizmo_[kGizmoScaleBottomRight] || g == point_gizmo_[kGizmoScaleBottomLeft];
}
}
+21 -19
View File
@@ -21,6 +21,8 @@
#ifndef SHAPENODEBASE_H
#define SHAPENODEBASE_H
#include "node/gizmo/point.h"
#include "node/gizmo/polygon.h"
#include "node/inputdragger.h"
#include "node/node.h"
@@ -30,42 +32,42 @@ class ShapeNodeBase : public Node
{
Q_OBJECT
public:
ShapeNodeBase();
ShapeNodeBase(bool create_color_input = true);
NODE_DEFAULT_DESTRUCTOR(ShapeNodeBase)
virtual void Retranslate() override;
virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override;
static QString kPositionInput;
static QString kSizeInput;
static QString kColorInput;
virtual bool HasGizmos() const override
protected:
PolygonGizmo *poly_gizmo() const
{
return true;
return poly_gizmo_;
}
virtual void DrawGizmos(const NodeValueRow &row, const NodeGlobals &globals, QPainter *p) override;
virtual bool GizmoPress(const NodeValueRow& row, const NodeGlobals &globals, const QPointF &p) override;
virtual void GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) override;
virtual void GizmoRelease(MultiUndoCommand *command) override;
protected slots:
virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override;
private:
static QVector2D GenerateGizmoAnchor(const QVector2D &pos, const QVector2D &size, int drag, QVector2D *pt);
QVector2D GenerateGizmoAnchor(const QVector2D &pos, const QVector2D &size, NodeGizmo *gizmo, QVector2D *pt = nullptr) const;
bool IsGizmoTop(NodeGizmo *g) const;
bool IsGizmoBottom(NodeGizmo *g) const;
bool IsGizmoLeft(NodeGizmo *g) const;
bool IsGizmoRight(NodeGizmo *g) const;
bool IsGizmoHorizontalCenter(NodeGizmo *g) const;
bool IsGizmoVerticalCenter(NodeGizmo *g) const;
bool IsGizmoCorner(NodeGizmo *g) const;
// Gizmo variables
static const int kGizmoWholeRect = kGizmoScaleCount;
QRectF gizmo_resize_handle_[kGizmoScaleCount];
QRectF gizmo_whole_rect_;
int gizmo_drag_;
QVector<NodeInputDragger> gizmo_dragger_;
QVector2D gizmo_pos_start_;
QVector2D gizmo_sz_start_;
QPointF gizmo_drag_start_;
float gizmo_aspect_ratio_;
QVector2D gizmo_half_res_;
PointGizmo *point_gizmo_[kGizmoScaleCount];
PolygonGizmo *poly_gizmo_;
};
+4
View File
@@ -26,6 +26,8 @@ namespace olive {
const QString SolidGenerator::kColorInput = QStringLiteral("color_in");
#define super Node
SolidGenerator::SolidGenerator()
{
// Default to a color that isn't black
@@ -59,6 +61,8 @@ QString SolidGenerator::Description() const
void SolidGenerator::Retranslate()
{
super::Retranslate();
SetInputName(kColorInput, tr("Color"));
}
+6 -4
View File
@@ -16,9 +16,11 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
node/generator/text/text.cpp
node/generator/text/text.h
node/generator/text/textlegacy.cpp
node/generator/text/textlegacy.h
node/generator/text/textv1.cpp
node/generator/text/textv1.h
node/generator/text/textv2.cpp
node/generator/text/textv2.h
node/generator/text/textv3.cpp
node/generator/text/textv3.h
PARENT_SCOPE
)
@@ -18,7 +18,7 @@
***/
#include "textlegacy.h"
#include "textv1.h"
#include <QAbstractTextDocumentLayout>
#include <QTextDocument>
@@ -31,14 +31,16 @@ enum TextVerticalAlign {
kVerticalAlignBottom,
};
const QString TextGeneratorLegacy::kTextInput = QStringLiteral("text_in");
const QString TextGeneratorLegacy::kHtmlInput = QStringLiteral("html_in");
const QString TextGeneratorLegacy::kColorInput = QStringLiteral("color_in");
const QString TextGeneratorLegacy::kVAlignInput = QStringLiteral("valign_in");
const QString TextGeneratorLegacy::kFontInput = QStringLiteral("font_in");
const QString TextGeneratorLegacy::kFontSizeInput = QStringLiteral("font_size_in");
const QString TextGeneratorV1::kTextInput = QStringLiteral("text_in");
const QString TextGeneratorV1::kHtmlInput = QStringLiteral("html_in");
const QString TextGeneratorV1::kColorInput = QStringLiteral("color_in");
const QString TextGeneratorV1::kVAlignInput = QStringLiteral("valign_in");
const QString TextGeneratorV1::kFontInput = QStringLiteral("font_in");
const QString TextGeneratorV1::kFontSizeInput = QStringLiteral("font_size_in");
TextGeneratorLegacy::TextGeneratorLegacy()
#define super Node
TextGeneratorV1::TextGeneratorV1()
{
AddInput(kTextInput, NodeValue::kText, tr("Sample Text"));
@@ -53,28 +55,30 @@ TextGeneratorLegacy::TextGeneratorLegacy()
AddInput(kFontSizeInput, NodeValue::kFloat, 72.0f);
}
QString TextGeneratorLegacy::Name() const
QString TextGeneratorV1::Name() const
{
return tr("Text (Legacy)");
}
QString TextGeneratorLegacy::id() const
QString TextGeneratorV1::id() const
{
return QStringLiteral("org.olivevideoeditor.Olive.textgenerator");
}
QVector<Node::CategoryID> TextGeneratorLegacy::Category() const
QVector<Node::CategoryID> TextGeneratorV1::Category() const
{
return {kCategoryGenerator};
}
QString TextGeneratorLegacy::Description() const
QString TextGeneratorV1::Description() const
{
return tr("Generate rich text.");
}
void TextGeneratorLegacy::Retranslate()
void TextGeneratorV1::Retranslate()
{
super::Retranslate();
SetInputName(kTextInput, tr("Text"));
SetInputName(kHtmlInput, tr("Enable HTML"));
SetInputName(kFontInput, tr("Font"));
@@ -84,7 +88,7 @@ void TextGeneratorLegacy::Retranslate()
SetComboBoxStrings(kVAlignInput, {tr("Top"), tr("Center"), tr("Bottom")});
}
void TextGeneratorLegacy::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
void TextGeneratorV1::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
{
GenerateJob job;
job.InsertValue(value);
@@ -95,7 +99,7 @@ void TextGeneratorLegacy::Value(const NodeValueRow &value, const NodeGlobals &gl
}
}
void TextGeneratorLegacy::GenerateFrame(FramePtr frame, const GenerateJob& job) const
void TextGeneratorV1::GenerateFrame(FramePtr frame, const GenerateJob& job) const
{
// This could probably be more optimized, but for now we use Qt to draw to a QImage.
// QImages only support integer pixels and we use float pixels, so what we do here is draw onto
@@ -18,21 +18,21 @@
***/
#ifndef TEXTGENERATORLEGACY_H
#define TEXTGENERATORLEGACY_H
#ifndef TEXTGENERATORV1_H
#define TEXTGENERATORV1_H
#include "node/node.h"
namespace olive {
class TextGeneratorLegacy : public Node
class TextGeneratorV1 : public Node
{
Q_OBJECT
public:
TextGeneratorLegacy();
TextGeneratorV1();
NODE_DEFAULT_DESTRUCTOR(TextGeneratorLegacy)
NODE_COPY_FUNCTION(TextGeneratorLegacy)
NODE_DEFAULT_DESTRUCTOR(TextGeneratorV1)
NODE_COPY_FUNCTION(TextGeneratorV1)
virtual QString Name() const override;
virtual QString id() const override;
@@ -56,4 +56,4 @@ public:
}
#endif // TEXTGENERATORLEGACY_H
#endif // TEXTGENERATORV1_H
@@ -18,7 +18,7 @@
***/
#include "text.h"
#include "textv2.h"
#include <QAbstractTextDocumentLayout>
#include <QDateTime>
@@ -37,13 +37,13 @@ enum TextVerticalAlign {
kVerticalAlignBottom,
};
const QString TextGenerator::kTextInput = QStringLiteral("text_in");
const QString TextGenerator::kHtmlInput = QStringLiteral("html_in");
const QString TextGenerator::kVAlignInput = QStringLiteral("valign_in");
const QString TextGenerator::kFontInput = QStringLiteral("font_in");
const QString TextGenerator::kFontSizeInput = QStringLiteral("font_size_in");
const QString TextGeneratorV2::kTextInput = QStringLiteral("text_in");
const QString TextGeneratorV2::kHtmlInput = QStringLiteral("html_in");
const QString TextGeneratorV2::kVAlignInput = QStringLiteral("valign_in");
const QString TextGeneratorV2::kFontInput = QStringLiteral("font_in");
const QString TextGeneratorV2::kFontSizeInput = QStringLiteral("font_size_in");
TextGenerator::TextGenerator()
TextGeneratorV2::TextGeneratorV2()
{
AddInput(kTextInput, NodeValue::kText, tr("Sample Text"));
@@ -59,27 +59,27 @@ TextGenerator::TextGenerator()
SetStandardValue(kSizeInput, QVector2D(400, 300));
}
QString TextGenerator::Name() const
QString TextGeneratorV2::Name() const
{
return tr("Text");
return tr("Text (Legacy)");
}
QString TextGenerator::id() const
QString TextGeneratorV2::id() const
{
return QStringLiteral("org.olivevideoeditor.Olive.text2");
}
QVector<Node::CategoryID> TextGenerator::Category() const
QVector<Node::CategoryID> TextGeneratorV2::Category() const
{
return {kCategoryGenerator};
}
QString TextGenerator::Description() const
QString TextGeneratorV2::Description() const
{
return tr("Generate rich text.");
}
void TextGenerator::Retranslate()
void TextGeneratorV2::Retranslate()
{
super::Retranslate();
@@ -91,7 +91,7 @@ void TextGenerator::Retranslate()
SetComboBoxStrings(kVAlignInput, {tr("Top"), tr("Center"), tr("Bottom")});
}
void TextGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
void TextGeneratorV2::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
{
GenerateJob job;
job.InsertValue(value);
@@ -103,7 +103,7 @@ void TextGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals,
}
}
void TextGenerator::GenerateFrame(FramePtr frame, const GenerateJob& job) const
void TextGeneratorV2::GenerateFrame(FramePtr frame, const GenerateJob& job) const
{
// This could probably be more optimized, but for now we use Qt to draw to a QImage.
// QImages only support integer pixels and we use float pixels, so what we do here is draw onto
@@ -18,21 +18,21 @@
***/
#ifndef TEXTGENERATOR_H
#define TEXTGENERATOR_H
#ifndef TEXTGENERATORV2_H
#define TEXTGENERATORV2_H
#include "node/generator/shape/shapenodebase.h"
namespace olive {
class TextGenerator : public ShapeNodeBase
class TextGeneratorV2 : public ShapeNodeBase
{
Q_OBJECT
public:
TextGenerator();
TextGeneratorV2();
NODE_DEFAULT_DESTRUCTOR(TextGenerator)
NODE_COPY_FUNCTION(TextGenerator)
NODE_DEFAULT_DESTRUCTOR(TextGeneratorV2)
NODE_COPY_FUNCTION(TextGeneratorV2)
virtual QString Name() const override;
virtual QString id() const override;
@@ -55,4 +55,4 @@ public:
}
#endif // TEXTGENERATOR_H
#endif // TEXTGENERATORV2_H
+140
View File
@@ -0,0 +1,140 @@
/***
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 "textv3.h"
#include <QAbstractTextDocumentLayout>
#include <QDateTime>
#include <QTextDocument>
#include "common/functiontimer.h"
#include "common/html.h"
#include "node/project/project.h"
namespace olive {
#define super ShapeNodeBase
enum TextVerticalAlign {
kVerticalAlignTop,
kVerticalAlignCenter,
kVerticalAlignBottom,
};
const QString TextGeneratorV3::kTextInput = QStringLiteral("text_in");
TextGeneratorV3::TextGeneratorV3() :
ShapeNodeBase(false)
{
AddInput(kTextInput, NodeValue::kText, QStringLiteral("<p style='font-size: 72pt; color: white;'>%1</p>").arg(tr("Sample Text")));
SetStandardValue(kSizeInput, QVector2D(400, 300));
text_gizmo_ = new TextGizmo(this);
text_gizmo_->SetInput(NodeInput(this, kTextInput));
}
QString TextGeneratorV3::Name() const
{
return tr("Text");
}
QString TextGeneratorV3::id() const
{
return QStringLiteral("org.olivevideoeditor.Olive.text3");
}
QVector<Node::CategoryID> TextGeneratorV3::Category() const
{
return {kCategoryGenerator};
}
QString TextGeneratorV3::Description() const
{
return tr("Generate rich text.");
}
void TextGeneratorV3::Retranslate()
{
super::Retranslate();
SetInputName(kTextInput, tr("Text"));
}
void TextGeneratorV3::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
{
GenerateJob job;
job.InsertValue(value);
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
job.SetRequestedFormat(VideoParams::kFormatUnsigned8);
// FIXME: Provide user override for this
job.SetColorspace(project()->color_manager()->GetDefaultInputColorSpace());
if (!job.GetValue(kTextInput).data().toString().isEmpty()) {
table->Push(NodeValue::kGenerateJob, QVariant::fromValue(job), this);
}
}
void TextGeneratorV3::GenerateFrame(FramePtr frame, const GenerateJob& job) const
{
QImage img(reinterpret_cast<uchar*>(frame->data()), frame->width(), frame->height(), frame->linesize_bytes(), QImage::Format_RGBA8888_Premultiplied);
img.fill(Qt::transparent);
// 96 DPI in DPM (96 / 2.54 * 100)
const int dpm = 3780;
img.setDotsPerMeterX(dpm);
img.setDotsPerMeterY(dpm);
QTextDocument text_doc;
text_doc.documentLayout()->setPaintDevice(&img);
QString html = job.GetValue(kTextInput).data().toString();
Html::HtmlToDoc(&text_doc, html);
QVector2D size = job.GetValue(kSizeInput).data().value<QVector2D>();
text_doc.setTextWidth(size.x());
// Draw rich text onto image
QPainter p(&img);
p.scale(1.0 / frame->video_params().divider(), 1.0 / frame->video_params().divider());
QVector2D pos = job.GetValue(kPositionInput).data().value<QVector2D>();
p.translate(pos.x() - size.x()/2, pos.y() - size.y()/2);
p.translate(frame->video_params().width()/2, frame->video_params().height()/2);
p.setClipRect(0, 0, size.x(), size.y());
// Ensure default text color is white
QAbstractTextDocumentLayout::PaintContext ctx;
ctx.palette.setColor(QPalette::Text, Qt::white);
text_doc.documentLayout()->draw(&p, ctx);
}
void TextGeneratorV3::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals)
{
super::UpdateGizmoPositions(row, globals);
QRectF rect = poly_gizmo()->GetPolygon().boundingRect();
text_gizmo_->SetRect(rect);
text_gizmo_->SetHtml(row[kTextInput].data().toString());
}
}
+60
View File
@@ -0,0 +1,60 @@
/***
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 TEXTGENERATORV3_H
#define TEXTGENERATORV3_H
#include "node/generator/shape/shapenodebase.h"
#include "node/gizmo/text.h"
namespace olive {
class TextGeneratorV3 : public ShapeNodeBase
{
Q_OBJECT
public:
TextGeneratorV3();
NODE_DEFAULT_DESTRUCTOR(TextGeneratorV3)
NODE_COPY_FUNCTION(TextGeneratorV3)
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 Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
virtual void GenerateFrame(FramePtr frame, const GenerateJob &job) const override;
virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override;
static const QString kTextInput;
private:
TextGizmo *text_gizmo_;
};
}
#endif // TEXTGENERATORV3_H
+36
View File
@@ -0,0 +1,36 @@
# 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/gizmo/draggable.cpp
node/gizmo/draggable.h
node/gizmo/gizmo.cpp
node/gizmo/gizmo.h
node/gizmo/line.cpp
node/gizmo/line.h
node/gizmo/path.cpp
node/gizmo/path.h
node/gizmo/point.cpp
node/gizmo/point.h
node/gizmo/polygon.cpp
node/gizmo/polygon.h
node/gizmo/screen.cpp
node/gizmo/screen.h
node/gizmo/text.cpp
node/gizmo/text.h
PARENT_SCOPE
)
+52
View File
@@ -0,0 +1,52 @@
/***
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 "draggable.h"
namespace olive {
DraggableGizmo::DraggableGizmo(QObject *parent)
: NodeGizmo{parent},
drag_value_behavior_(kAbsolute)
{
}
void DraggableGizmo::DragStart(const NodeValueRow &row, double abs_x, double abs_y, const rational &time)
{
for (int i=0; i<draggers_.size(); i++) {
draggers_[i].Start(inputs_[i], time);
}
emit HandleStart(row, abs_x, abs_y, time);
}
void DraggableGizmo::DragMove(double x, double y, const Qt::KeyboardModifiers &modifiers)
{
emit HandleMovement(x, y, modifiers);
}
void DraggableGizmo::DragEnd(MultiUndoCommand *command)
{
for (int i=0; i<draggers_.size(); i++) {
draggers_[i].End(command);
}
}
}
+85
View File
@@ -0,0 +1,85 @@
/***
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 DRAGGABLEGIZMO_H
#define DRAGGABLEGIZMO_H
#include "common/rational.h"
#include "gizmo.h"
#include "node/inputdragger.h"
#include "undo/undocommand.h"
namespace olive {
class DraggableGizmo : public NodeGizmo
{
Q_OBJECT
public:
/// Changes what the X/Y coordinates emitted from HandleMovement specify
enum DragValueBehavior {
/// X/Y will be the exact mouse coordinates (in sequence pixels)
kAbsolute,
/// X/Y will be the movement since the last time HandleMovement was called
kDeltaFromPrevious,
/// X/Y will be the movement from the start of the drag
kDeltaFromStart
};
explicit DraggableGizmo(QObject *parent = nullptr);
void DragStart(const NodeValueRow &row, double abs_x, double abs_y, const olive::rational &time);
void DragMove(double x, double y, const Qt::KeyboardModifiers &modifiers);
void DragEnd(olive::MultiUndoCommand *command);
void AddInput(const NodeKeyframeTrackReference &input)
{
inputs_.append(input);
draggers_.append(NodeInputDragger());
}
QVector<NodeInputDragger> &GetDraggers()
{
return draggers_;
}
DragValueBehavior GetDragValueBehavior() const { return drag_value_behavior_; }
void SetDragValueBehavior(DragValueBehavior d) { drag_value_behavior_ = d; }
signals:
void HandleStart(const olive::NodeValueRow &row, double x, double y, const olive::rational &time);
void HandleMovement(double x, double y, const Qt::KeyboardModifiers &modifiers);
private:
QVector<NodeKeyframeTrackReference> inputs_;
QVector<NodeInputDragger> draggers_;
DragValueBehavior drag_value_behavior_;
};
}
#endif // DRAGGABLEGIZMO_H
+30
View File
@@ -0,0 +1,30 @@
/***
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 "gizmo.h"
namespace olive {
NodeGizmo::NodeGizmo(QObject *parent)
{
setParent(parent);
}
}
+51
View File
@@ -0,0 +1,51 @@
/***
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 NODEGIZMO_H
#define NODEGIZMO_H
#include <QObject>
#include <QPainter>
#include "node/globals.h"
namespace olive {
class NodeGizmo : public QObject
{
Q_OBJECT
public:
explicit NodeGizmo(QObject *parent = nullptr);
virtual void Draw(QPainter *p) const {}
const NodeGlobals &GetGlobals() const { return globals_; }
void SetGlobals(const NodeGlobals &globals) { globals_ = globals; }
signals:
private:
NodeGlobals globals_;
};
}
#endif // NODEGIZMO_H
+38
View File
@@ -0,0 +1,38 @@
/***
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 "line.h"
namespace olive {
LineGizmo::LineGizmo(QObject *parent) :
NodeGizmo(parent)
{
}
void LineGizmo::Draw(QPainter *p) const
{
p->setBrush(Qt::NoBrush);
p->setPen(QPen(Qt::white, 0));
p->drawLine(line_);
}
}
+48
View File
@@ -0,0 +1,48 @@
/***
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 LINEGIZMO_H
#define LINEGIZMO_H
#include <QLineF>
#include "gizmo.h"
namespace olive {
class LineGizmo : public NodeGizmo
{
Q_OBJECT
public:
LineGizmo(QObject *parent = nullptr);
const QLineF &GetLine() const { return line_; }
void SetLine(const QLineF &line) { line_ = line; }
virtual void Draw(QPainter *p) const override;
private:
QLineF line_;
};
}
#endif // LINEGIZMO_H
+39
View File
@@ -0,0 +1,39 @@
/***
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 "path.h"
namespace olive {
PathGizmo::PathGizmo(QObject *parent) :
DraggableGizmo{parent}
{
}
void PathGizmo::Draw(QPainter *p) const
{
p->setPen(QPen(Qt::white, 0));
p->setBrush(Qt::NoBrush);
p->drawPath(path_);
}
}
+48
View File
@@ -0,0 +1,48 @@
/***
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 PATHGIZMO_H
#define PATHGIZMO_H
#include <QPainterPath>
#include "draggable.h"
namespace olive {
class PathGizmo : public DraggableGizmo
{
Q_OBJECT
public:
explicit PathGizmo(QObject *parent = nullptr);
const QPainterPath &GetPath() const { return path_; }
void SetPath(const QPainterPath &path) { path_ = path; }
virtual void Draw(QPainter *p) const override;
private:
QPainterPath path_;
};
}
#endif // PATHGIZMO_H
+99
View File
@@ -0,0 +1,99 @@
/***
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 "point.h"
#include <QApplication>
namespace olive {
PointGizmo::PointGizmo(const Shape &shape, bool smaller, QObject *parent) :
DraggableGizmo{parent},
shape_(shape),
smaller_(smaller)
{
}
PointGizmo::PointGizmo(const Shape &shape, QObject *parent) :
PointGizmo(shape, false, parent)
{
}
PointGizmo::PointGizmo(QObject *parent) :
PointGizmo(kSquare, parent)
{
}
void PointGizmo::Draw(QPainter *p) const
{
QRectF rect = GetDrawingRect(GetStandardRadius() / p->transform().m11());
if (shape_ != kAnchorPoint) {
p->setPen(Qt::NoPen);
p->setBrush(Qt::white);
}
switch (shape_) {
case kSquare:
p->drawRect(rect);
break;
case kCircle:
p->drawEllipse(rect);
break;
case kAnchorPoint:
p->setPen(QPen(Qt::white, 0));
p->setBrush(Qt::NoBrush);
p->drawEllipse(rect);
p->drawLines({QPointF(rect.left(), rect.center().y()),
QPointF(rect.right(), rect.center().y()),
QPointF(rect.center().x(), rect.top()),
QPointF(rect.center().x(), rect.bottom())});
break;
}
}
QRectF PointGizmo::GetClickingRect(const QTransform &t) const
{
return GetDrawingRect(GetStandardRadius() / t.m11() * 1.5);
}
double PointGizmo::GetStandardRadius()
{
return QFontMetrics(qApp->font()).height() * 0.25;
}
QRectF PointGizmo::GetDrawingRect(double radius) const
{
if (shape_ == kAnchorPoint) {
radius *= 2;
}
if (smaller_) {
radius *= 0.5;
}
return QRectF(point_.x() - radius,
point_.y() - radius,
2*radius,
2*radius);
}
}
+72
View File
@@ -0,0 +1,72 @@
/***
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 POINTGIZMO_H
#define POINTGIZMO_H
#include <QPointF>
#include "draggable.h"
namespace olive {
class PointGizmo : public DraggableGizmo
{
Q_OBJECT
public:
enum Shape {
kSquare,
kCircle,
kAnchorPoint
};
explicit PointGizmo(const Shape &shape, bool smaller, QObject *parent = nullptr);
explicit PointGizmo(const Shape &shape, QObject *parent = nullptr);
explicit PointGizmo(QObject *parent = nullptr);
const Shape &GetShape() const { return shape_; }
void SetShape(const Shape &s) { shape_ = s; }
const QPointF &GetPoint() const { return point_; }
void SetPoint(const QPointF &pt) { point_ = pt; }
bool GetSmaller() const { return smaller_; }
void SetSmaller(bool e) { smaller_ = e; }
virtual void Draw(QPainter *p) const override;
QRectF GetClickingRect(const QTransform &t) const;
private:
static double GetStandardRadius();
QRectF GetDrawingRect(double radius) const;
Shape shape_;
QPointF point_;
bool smaller_;
};
}
#endif // POINTGIZMO_H
+38
View File
@@ -0,0 +1,38 @@
/***
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 "polygon.h"
namespace olive {
PolygonGizmo::PolygonGizmo(QObject *parent)
: DraggableGizmo{parent}
{
}
void PolygonGizmo::Draw(QPainter *p) const
{
p->setPen(QPen(Qt::white, 0));
p->setBrush(Qt::NoBrush);
p->drawPolyline(polygon_);
}
}
+48
View File
@@ -0,0 +1,48 @@
/***
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 POLYGONGIZMO_H
#define POLYGONGIZMO_H
#include <QPolygonF>
#include "draggable.h"
namespace olive {
class PolygonGizmo : public DraggableGizmo
{
Q_OBJECT
public:
explicit PolygonGizmo(QObject *parent = nullptr);
const QPolygonF &GetPolygon() const { return polygon_; }
void SetPolygon(const QPolygonF &polygon) { polygon_ = polygon; }
virtual void Draw(QPainter *p) const override;
private:
QPolygonF polygon_;
};
}
#endif // POLYGONGIZMO_H
+31
View File
@@ -0,0 +1,31 @@
/***
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 "screen.h"
namespace olive {
ScreenGizmo::ScreenGizmo(QObject *parent)
: DraggableGizmo{parent}
{
}
}
+38
View File
@@ -0,0 +1,38 @@
/***
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 SCREENGIZMO_H
#define SCREENGIZMO_H
#include "draggable.h"
namespace olive {
class ScreenGizmo : public DraggableGizmo
{
Q_OBJECT
public:
explicit ScreenGizmo(QObject *parent = nullptr);
};
}
#endif // SCREENGIZMO_H
+43
View File
@@ -0,0 +1,43 @@
/***
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 "text.h"
#include "core.h"
#include "undo/undocommand.h"
namespace olive {
TextGizmo::TextGizmo(QObject *parent)
: NodeGizmo{parent}
{
}
void TextGizmo::UpdateInputHtml(const QString &s, const rational &time)
{
if (input_.IsValid()) {
MultiUndoCommand *command = new MultiUndoCommand();
Node::SetValueAtTime(input_.input(), time, s, input_.track(), command, true);
Core::instance()->undo_stack()->pushIfHasChildren(command);
}
}
}
+56
View File
@@ -0,0 +1,56 @@
/***
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 TEXTGIZMO_H
#define TEXTGIZMO_H
#include "gizmo.h"
#include "node/param.h"
namespace olive {
class TextGizmo : public NodeGizmo
{
Q_OBJECT
public:
explicit TextGizmo(QObject *parent = nullptr);
const QRectF &GetRect() const { return rect_; }
void SetRect(const QRectF &r) { rect_ = r; }
const QString &GetHtml() const { return text_; }
void SetHtml(const QString &t) { text_ = t; }
void SetInput(const NodeKeyframeTrackReference &input) { input_ = input; }
void UpdateInputHtml(const QString &s, const rational &time);
private:
QRectF rect_;
QString text_;
NodeKeyframeTrackReference input_;
};
}
#endif // TEXTGIZMO_H

Some files were not shown because too many files have changed in this diff Show More