sweeping effects rewrite that allows external plugins

This commit is contained in:
itsmattkc
2018-10-31 17:14:40 +11:00
parent 10347f9418
commit 01435f0d0c
63 changed files with 887 additions and 1033 deletions
+2
View File
@@ -2,6 +2,8 @@
#include <QOpenGLFunctions>
// TODO port to GLSL?
CrossDissolveTransition::CrossDissolveTransition() : Transition(VIDEO_DISSOLVE_TRANSITION) {}
void CrossDissolveTransition::process_transition(double progress) {
+468 -259
View File
@@ -1,5 +1,6 @@
#include "effect.h"
#include "effects/qpainterwrapper.h"
#include "panels/panels.h"
#include "panels/viewer.h"
#include "ui/viewerwidget.h"
@@ -18,6 +19,15 @@
#include "panels/effectcontrols.h"
#include "debug.h"
#include "io/path.h"
#include "mainwindow.h"
#include "effects/internal/transformeffect.h"
#include "effects/internal/texteffect.h"
#include "effects/internal/solideffect.h"
#include "effects/internal/audionoiseeffect.h"
#include "effects/internal/toneeffect.h"
#include "effects/internal/volumeeffect.h"
#include "effects/internal/paneffect.h"
#include <QCheckBox>
#include <QGridLayout>
@@ -26,99 +36,147 @@
#include <QMessageBox>
#include <QOpenGLContext>
#include <QDir>
#include <QPainter>
#include <QtMath>
QVector<EffectMeta> video_effects;
QVector<EffectMeta> audio_effects;
QMutex effects_loaded;
QVector<QString> video_effect_names;
QVector<QString> audio_effect_names;
void init_effects() {
video_effect_names.resize(VIDEO_EFFECT_COUNT);
audio_effect_names.resize(AUDIO_EFFECT_COUNT);
video_effect_names[VIDEO_TRANSFORM_EFFECT] = "Transform";
video_effect_names[VIDEO_SHAKE_EFFECT] = "Shake";
video_effect_names[VIDEO_TEXT_EFFECT] = "Text";
video_effect_names[VIDEO_SOLID_EFFECT] = "Solid";
video_effect_names[VIDEO_INVERT_EFFECT] = "Invert";
video_effect_names[VIDEO_CHROMAKEY_EFFECT] = "Chroma Key";
video_effect_names[VIDEO_GAUSSIANBLUR_EFFECT] = "Gaussian Blur";
video_effect_names[VIDEO_CROP_EFFECT] = "Crop";
video_effect_names[VIDEO_FLIP_EFFECT] = "Flip";
video_effect_names[VIDEO_BOXBLUR_EFFECT] = "Box Blur";
video_effect_names[VIDEO_WAVE_EFFECT] = "Wave";
video_effect_names[VIDEO_TEMPERATURE_EFFECT] = "Temperature";
audio_effect_names[AUDIO_VOLUME_EFFECT] = "Volume";
audio_effect_names[AUDIO_PAN_EFFECT] = "Pan";
audio_effect_names[AUDIO_NOISE_EFFECT] = "Noise";
audio_effect_names[AUDIO_TONE_EFFECT] = "Tone";
dout << "Starting init effect (TODO: multithread this)";
QString effects_path = get_effects_dir();
QDir effects_dir(effects_path);
if (effects_dir.exists()) {
QList<QString> entries = effects_dir.entryList(QStringList("*.xml"), QDir::Files);
for (int i=0;i<entries.size();i++) {
QFile file(effects_path + "/" + entries.at(i));
if (!file.open(QIODevice::ReadOnly)) {
dout << "[ERROR] Could not open file";
return;
}
QXmlStreamReader reader(&file);
while (!reader.atEnd()) {
if (reader.name() == "effect") {
QString effect_name = "";
QString effect_cat = "";
int effect_type = EFFECT_TYPE_INVALID;
const QXmlStreamAttributes attr = reader.attributes();
for (int j=0;j<attr.size();j++) {
if (attr.at(j).name() == "name") {
effect_name = attr.at(j).value().toString();
} else if (attr.at(j).name() == "category") {
effect_cat = attr.at(j).value().toString();
} else if (attr.at(j).name() == "type") {
QString compare = attr.at(j).value().toString().toUpper();
if (compare == "VIDEO") {
effect_type = EFFECT_TYPE_VIDEO;
} else if (compare == "AUDIO") {
effect_type = EFFECT_TYPE_AUDIO;
}
}
}
if (effect_type != EFFECT_TYPE_INVALID && !effect_name.isEmpty()) {
EffectMeta em;
em.name = effect_name;
em.category = effect_cat;
em.filename = entries.at(i);
if (effect_type == EFFECT_TYPE_VIDEO) {
video_effects.append(em);
} else {
audio_effects.append(em);
}
} else {
dout << "[ERROR] Invalid effect found in" << entries.at(i);
}
break;
}
reader.readNext();
}
}
}
dout << "Completed init effect (TODO: multithread this)";
Effect* create_effect(Clip* c, const EffectMeta* em) {
if (!em->filename.isEmpty()) {
// load effect from file
return new Effect(c, em);
} else if (em->internal >= 0 && em->internal < EFFECT_INTERNAL_COUNT) {
// must be an internal effect
switch (em->internal) {
case EFFECT_INTERNAL_TRANSFORM: return new TransformEffect(c, em); break;
case EFFECT_INTERNAL_TEXT: return new TextEffect(c, em); break;
case EFFECT_INTERNAL_SOLID: return new SolidEffect(c, em); break;
case EFFECT_INTERNAL_NOISE: return new AudioNoiseEffect(c, em); break;
case EFFECT_INTERNAL_VOLUME: return new VolumeEffect(c, em); break;
case EFFECT_INTERNAL_PAN: return new PanEffect(c, em); break;
case EFFECT_INTERNAL_TONE: return new ToneEffect(c, em); break;
}
} else {
dout << "[ERROR] Invalid effect data";
QMessageBox::critical(mainWindow, "Invalid effect", "No candidate for effect '" + em->name + "'. This effect may be corrupt. Try reinstalling it or Olive.");
}
return NULL;
}
Effect* create_effect(int effect_id, Clip* c) {
return NULL;
const EffectMeta* get_internal_meta(int internal_id) {
for (int i=0;i<audio_effects.size();i++) {
if (audio_effects.at(i).internal == internal_id) {
return &audio_effects.at(i);
}
}
for (int i=0;i<video_effects.size();i++) {
if (video_effects.at(i).internal == internal_id) {
return &video_effects.at(i);
}
}
}
void load_internal_effects() {
EffectMeta em;
em.name = "Volume";
em.internal = EFFECT_INTERNAL_VOLUME;
audio_effects.append(em);
em.name = "Pan";
em.internal = EFFECT_INTERNAL_PAN;
audio_effects.append(em);
em.name = "Tone";
em.internal = EFFECT_INTERNAL_TONE;
audio_effects.append(em);
em.name = "Noise";
em.internal = EFFECT_INTERNAL_NOISE;
audio_effects.append(em);
em.name = "Transform";
em.category = "Distort";
em.internal = EFFECT_INTERNAL_TRANSFORM;
video_effects.append(em);
em.name = "Text";
em.category = "Render";
em.internal = EFFECT_INTERNAL_TEXT;
video_effects.append(em);
em.name = "Solid";
em.category = "Render";
em.internal = EFFECT_INTERNAL_SOLID;
video_effects.append(em);
}
void load_shader_effects() {
QString effects_path = get_effects_dir();
QDir effects_dir(effects_path);
if (effects_dir.exists()) {
QList<QString> entries = effects_dir.entryList(QStringList("*.xml"), QDir::Files);
for (int i=0;i<entries.size();i++) {
QFile file(effects_path + "/" + entries.at(i));
if (!file.open(QIODevice::ReadOnly)) {
dout << "[ERROR] Could not open" << entries.at(i);
return;
}
QXmlStreamReader reader(&file);
while (!reader.atEnd()) {
if (reader.name() == "effect") {
QString effect_name = "";
QString effect_cat = "";
const QXmlStreamAttributes attr = reader.attributes();
for (int j=0;j<attr.size();j++) {
if (attr.at(j).name() == "name") {
effect_name = attr.at(j).value().toString();
} else if (attr.at(j).name() == "category") {
effect_cat = attr.at(j).value().toString();
}
}
if (!effect_name.isEmpty()) {
EffectMeta em;
em.name = effect_name;
em.category = effect_cat;
em.filename = entries.at(i);
em.internal = -1;
video_effects.append(em);
} else {
dout << "[ERROR] Invalid effect found in" << entries.at(i);
}
break;
}
reader.readNext();
}
file.close();
}
}
}
void load_vst_effects() {
}
void init_effects() {
dout << "Starting init effect (TODO: multithread this)";
effects_loaded.lock();
load_internal_effects();
load_shader_effects();
load_vst_effects();
effects_loaded.unlock();
dout << "Completed init effect (TODO: multithread this)";
}
double double_lerp(double a, double b, double t) {
return ((1.0 - t) * a) + (t * b);
}
Effect::Effect(Clip* c, const EffectMeta &em) :
Effect::Effect(Clip* c, const EffectMeta *em) :
parent_clip(c),
meta(em),
enable_shader(false),
@@ -126,6 +184,7 @@ Effect::Effect(Clip* c, const EffectMeta &em) :
enable_superimpose(false),
isOpen(false),
glslProgram(NULL),
texture(NULL),
bound(false)
{
// set up base UI
@@ -138,147 +197,184 @@ Effect::Effect(Clip* c, const EffectMeta &em) :
container->setContents(ui);
// set up UI from effect file
container->setText(em.name);
QFile effect_file(get_effects_dir() + "/" + em.filename);
container->setText(em->name);
if (effect_file.open(QFile::ReadOnly)) {
QXmlStreamReader reader(&effect_file);
if (!em->filename.isEmpty()) {
QFile effect_file(get_effects_dir() + "/" + em->filename);
if (effect_file.open(QFile::ReadOnly)) {
QXmlStreamReader reader(&effect_file);
while (!reader.atEnd()) {
if (reader.name() == "row" && reader.isStartElement()) {
QString row_name;
const QXmlStreamAttributes& attributes = reader.attributes();
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "name") {
row_name = attr.value().toString();
}
}
if (!row_name.isEmpty()) {
EffectRow* row = add_row(row_name);
while (!reader.atEnd() && !(reader.name() == "row" && reader.isEndElement())) {
reader.readNext();
if (reader.name() == "field" && reader.isStartElement()) {
int type = -1;
while (!reader.atEnd()) {
if (reader.name() == "row" && reader.isStartElement()) {
QString row_name;
const QXmlStreamAttributes& attributes = reader.attributes();
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "name") {
row_name = attr.value().toString();
}
}
if (!row_name.isEmpty()) {
EffectRow* row = add_row(row_name + ":");
while (!reader.atEnd() && !(reader.name() == "row" && reader.isEndElement())) {
reader.readNext();
if (reader.name() == "field" && reader.isStartElement()) {
int type = EFFECT_TYPE_VIDEO;
QString id;
// get field type
const QXmlStreamAttributes& attributes = reader.attributes();
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "type") {
QString comp = attr.value().toString().toUpper();
if (comp == "DOUBLE") {
type = EFFECT_FIELD_DOUBLE;
} else if (comp == "BOOL") {
type = EFFECT_FIELD_BOOL;
} else if (comp == "COLOR") {
type = EFFECT_FIELD_COLOR;
} else if (comp == "COMBO") {
type = EFFECT_FIELD_COMBO;
} else if (comp == "FONT") {
type = EFFECT_FIELD_FONT;
} else if (comp == "STRING") {
type = EFFECT_FIELD_STRING;
}
}
}
// get field type
const QXmlStreamAttributes& attributes = reader.attributes();
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "type") {
QString comp = attr.value().toString().toUpper();
if (comp == "DOUBLE") {
type = EFFECT_FIELD_DOUBLE;
} else if (comp == "BOOL") {
type = EFFECT_FIELD_BOOL;
} else if (comp == "COLOR") {
type = EFFECT_FIELD_COLOR;
} else if (comp == "COMBO") {
type = EFFECT_FIELD_COMBO;
} else if (comp == "FONT") {
type = EFFECT_FIELD_FONT;
} else if (comp == "STRING") {
type = EFFECT_FIELD_STRING;
}
} else if (attr.name() == "id") {
id = attr.value().toString();
}
}
qDebug() << "add field" << type;
if (type > -1) {
EffectField* field = row->add_field(type);
switch (type) {
case EFFECT_FIELD_DOUBLE:
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "default") {
field->set_double_default_value(attr.value().toDouble());
} else if (attr.name() == "min") {
field->set_double_minimum_value(attr.value().toDouble());
} else if (attr.name() == "max") {
field->set_double_maximum_value(attr.value().toDouble());
}
}
break;
case EFFECT_FIELD_COLOR:
{
QColor color;
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "r") {
color.setRed(attr.value().toInt());
} else if (attr.name() == "g") {
color.setGreen(attr.value().toInt());
} else if (attr.name() == "b") {
color.setBlue(attr.value().toInt());
} else if (attr.name() == "rf") {
color.setRedF(attr.value().toFloat());
} else if (attr.name() == "gf") {
color.setGreenF(attr.value().toFloat());
} else if (attr.name() == "bf") {
color.setBlueF(attr.value().toFloat());
} else if (attr.name() == "hex") {
color.setNamedColor(attr.value());
}
}
}
break;
case EFFECT_FIELD_STRING:
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "default") {
field->set_string_value(attr.value().toString());
}
}
break;
case EFFECT_FIELD_BOOL:
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "default") {
field->set_bool_value(attr.value() == "1");
}
}
break;
case EFFECT_FIELD_COMBO:
{
int combo_index = 0;
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "default") {
combo_index = attr.value().toInt();
break;
}
}
while (!reader.atEnd() && !(reader.name() == "field" && reader.isEndElement())) {
reader.readNext();
if (reader.name() == "option" && reader.isStartElement()) {
reader.readNext();
field->add_combo_item(reader.text().toString(), 0);
}
}
field->set_combo_index(combo_index);
}
break;
case EFFECT_FIELD_FONT:
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "default") {
field->set_font_name(attr.value().toString());
}
}
break;
}
}
}
}
}
}
reader.readNext();
}
if (id.isEmpty()) {
dout << "[ERROR] Couldn't load field from" << em->filename << "- ID cannot be empty.";
} else if (type > -1) {
EffectField* field = row->add_field(type);
field->id = id;
connect(field, SIGNAL(changed()), this, SLOT(field_changed()));
switch (type) {
case EFFECT_FIELD_DOUBLE:
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "default") {
field->set_double_default_value(attr.value().toDouble());
} else if (attr.name() == "min") {
field->set_double_minimum_value(attr.value().toDouble());
} else if (attr.name() == "max") {
field->set_double_maximum_value(attr.value().toDouble());
}
}
break;
case EFFECT_FIELD_COLOR:
{
QColor color;
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "r") {
color.setRed(attr.value().toInt());
} else if (attr.name() == "g") {
color.setGreen(attr.value().toInt());
} else if (attr.name() == "b") {
color.setBlue(attr.value().toInt());
} else if (attr.name() == "rf") {
color.setRedF(attr.value().toFloat());
} else if (attr.name() == "gf") {
color.setGreenF(attr.value().toFloat());
} else if (attr.name() == "bf") {
color.setBlueF(attr.value().toFloat());
} else if (attr.name() == "hex") {
color.setNamedColor(attr.value());
}
}
field->set_color_value(color);
}
break;
case EFFECT_FIELD_STRING:
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "default") {
field->set_string_value(attr.value().toString());
}
}
break;
case EFFECT_FIELD_BOOL:
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "default") {
field->set_bool_value(attr.value() == "1");
}
}
break;
case EFFECT_FIELD_COMBO:
{
int combo_index = 0;
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "default") {
combo_index = attr.value().toInt();
break;
}
}
while (!reader.atEnd() && !(reader.name() == "field" && reader.isEndElement())) {
reader.readNext();
if (reader.name() == "option" && reader.isStartElement()) {
reader.readNext();
field->add_combo_item(reader.text().toString(), 0);
}
}
field->set_combo_index(combo_index);
}
break;
case EFFECT_FIELD_FONT:
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "default") {
field->set_font_name(attr.value().toString());
}
}
break;
}
}
}
}
}
} else if (reader.name() == "shader" && reader.isStartElement()) {
enable_shader = true;
const QXmlStreamAttributes& attributes = reader.attributes();
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "vert") {
vertPath = attr.value().toString();
} else if (attr.name() == "frag") {
fragPath = attr.value().toString();
}
}
} else if (reader.name() == "superimpose" && reader.isStartElement()) {
enable_superimpose = true;
const QXmlStreamAttributes& attributes = reader.attributes();
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "script") {
QFile script_file = get_effects_dir() + "/" + attr.value().toString();
if (script_file.open(QFile::ReadOnly)) {
script = script_file.readAll();
wrapper_obj = jsEngine.newQObject(&painter_wrapper);
} else {
dout << "[ERROR] Failed to open superimpose script file for" << em->filename;
enable_superimpose = false;
}
break;
}
}
}
reader.readNext();
}
effect_file.close();
} else {
dout << "[ERROR] Failed to open effect file" << em.filename;
}
effect_file.close();
} else {
dout << "[ERROR] Failed to open effect file" << em->filename;
}
}
}
Effect::~Effect() {
@@ -294,13 +390,14 @@ Effect::~Effect() {
void Effect::copy_field_keyframes(Effect* e) {
for (int i=0;i<rows.size();i++) {
EffectRow* row = rows.at(i);
e->rows.at(i)->setKeyframing(rows.at(i)->isKeyframing());
e->rows.at(i)->keyframe_times = rows.at(i)->keyframe_times;
e->rows.at(i)->keyframe_types = rows.at(i)->keyframe_types;
EffectRow* copy_row = e->rows.at(i);
copy_row->setKeyframing(row->isKeyframing());
copy_row->keyframe_times = row->keyframe_times;
copy_row->keyframe_types = row->keyframe_types;
for (int j=0;j<row->fieldCount();j++) {
EffectField* field = row->field(j);
e->rows.at(i)->field(j)->set_current_data(field->get_current_data());
e->rows.at(i)->field(j)->keyframe_data = field->keyframe_data;
copy_row->field(j)->set_current_data(field->get_current_data());
copy_row->field(j)->keyframe_data = field->keyframe_data;
}
}
}
@@ -405,8 +502,30 @@ void Effect::load(QXmlStreamReader& stream) {
// read field
if (stream.name() == "field" && stream.isStartElement()) {
if (field_count < row->fieldCount()) {
EffectField* field = row->field(field_count);
// match field using ID
bool found_field_by_id = false;
int field_number = field_count;
for (int k=0;k<stream.attributes().size();k++) {
const QXmlStreamAttribute& attr = stream.attributes().at(k);
if (attr.name() == "id") {
for (int l=0;l<row->fieldCount();l++) {
if (row->field(l)->id == attr.value()) {
field_number = l;
found_field_by_id = true;
dout << "[INFO] Found field by ID";
break;
}
}
break;
}
}
// TODO DEPRECATED, only used for backwards compatibility with 180820
if (!found_field_by_id) dout << "[INFO] Found field by field number";
EffectField* field = row->field(field_number);
// get current field value
for (int k=0;k<stream.attributes().size();k++) {
const QXmlStreamAttribute& attr = stream.attributes().at(k);
if (attr.name() == "value") {
@@ -455,6 +574,7 @@ void Effect::save(QXmlStreamWriter& stream) {
for (int j=0;j<row->fieldCount();j++) {
EffectField* field = row->field(j);
stream.writeStartElement("field"); // field
stream.writeAttribute("id", field->id);
stream.writeAttribute("value", save_data_to_string(field->type, field->get_current_data()));
for (int k=0;k<field->keyframe_data.size();k++) {
stream.writeTextElement("key", save_data_to_string(field->type, field->keyframe_data.at(k)));
@@ -470,24 +590,34 @@ void Effect::open() {
dout << "[WARNING] Tried to open an effect that was already open";
close();
}
if (QOpenGLContext::currentContext() == NULL) {
dout << "[WARNING] No current context to create a shader program for - will retry next repaint";
if (enable_shader) {
if (QOpenGLContext::currentContext() == NULL) {
dout << "[WARNING] No current context to create a shader program for - will retry next repaint";
} else {
glslProgram = new QOpenGLShaderProgram();
if (!vertPath.isEmpty()) glslProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, get_effects_dir() + "/" + vertPath);
if (!fragPath.isEmpty()) glslProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, get_effects_dir() + "/" + fragPath);
glslProgram->link();
isOpen = true;
}
} else {
glslProgram = new QOpenGLShaderProgram();
if (!vertPath.isEmpty()) glslProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, vertPath);
if (!fragPath.isEmpty()) glslProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, fragPath);
glslProgram->link();
isOpen = true;
}
if (enable_superimpose) {
texture = new QOpenGLTexture(QOpenGLTexture::Target2D);
}
}
void Effect::close() {
if (!isOpen) {
dout << "[WARNING] Tried to close an effect that was already closed";
} else {
delete glslProgram;
}
glslProgram = NULL;
if (glslProgram != NULL) {
delete glslProgram;
glslProgram = NULL;
}
delete_texture();
isOpen = false;
}
@@ -496,7 +626,7 @@ void Effect::startEffect() {
open();
dout << "[WARNING] Tried to start a closed effect - opening";
}
bound = glslProgram->bind();
if (enable_shader) bound = glslProgram->bind();
}
void Effect::endEffect() {
@@ -505,32 +635,44 @@ void Effect::endEffect() {
}
Effect* Effect::copy(Clip* c) {
Effect* copy = create_effect(id, c);
Effect* copy = create_effect(c, meta);
copy->set_enabled(is_enabled());
copy_field_keyframes(copy);
return copy;
}
void Effect::process_shader(double) {}
void Effect::process_shader(double timecode) {
glslProgram->setUniformValue("resolution", parent_clip->getWidth(), parent_clip->getHeight());
for (int i=0;i<rows.size();i++) {
EffectRow* row = rows.at(i);
for (int j=0;j<row->fieldCount();j++) {
EffectField* field = row->field(j);
if (!field->id.isEmpty()) {
switch (field->type) {
case EFFECT_FIELD_DOUBLE:
glslProgram->setUniformValue(field->id.toLatin1().constData(), (GLfloat) field->get_double_value(timecode));
break;
case EFFECT_FIELD_COLOR:
glslProgram->setUniformValue(field->id.toLatin1().constData(), field->get_color_value(timecode));
break;
case EFFECT_FIELD_STRING: break; // can you even send a string to a uniform value?
case EFFECT_FIELD_BOOL:
glslProgram->setUniformValue(field->id.toLatin1().constData(), field->get_bool_value(timecode));
break;
case EFFECT_FIELD_COMBO:
glslProgram->setUniformValue(field->id.toLatin1().constData(), field->get_combo_index(timecode));
break;
case EFFECT_FIELD_FONT: break; // can you even send a string to a uniform value?
}
}
}
}
}
void Effect::process_coords(double, GLTextureCoords&) {}
GLuint Effect::process_superimpose(double) {return 0;}
void Effect::process_audio(double, double, quint8*, int, int) {}
SuperimposeEffect::SuperimposeEffect(Clip* c, const EffectMeta& e) : Effect(c, e), texture(NULL) {
enable_superimpose = true;
}
void SuperimposeEffect::open() {
Effect::open();
texture = new QOpenGLTexture(QOpenGLTexture::Target2D);
}
void SuperimposeEffect::close() {
Effect::close();
deleteTexture();
}
GLuint SuperimposeEffect::process_superimpose(double timecode) {
GLuint Effect::process_superimpose(double timecode) {
bool recreate_texture = false;
int width = parent_clip->getWidth();
int height = parent_clip->getHeight();
@@ -546,7 +688,7 @@ GLuint SuperimposeEffect::process_superimpose(double timecode) {
if (texture != NULL) {
if (recreate_texture || texture->width() != img.width() || texture->height() != img.height()) {
deleteTexture();
delete_texture();
texture = new QOpenGLTexture(QOpenGLTexture::Target2D);
texture->setData(img);
} else {
@@ -555,17 +697,72 @@ GLuint SuperimposeEffect::process_superimpose(double timecode) {
return texture->textureId();
}
return 0;
}
void SuperimposeEffect::redraw(double) {}
void Effect::process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count) {
// only volume/pan, hand off to AU and VST for all other cases
void SuperimposeEffect::deleteTexture() {
delete texture;
texture = NULL;
double interval = (timecode_end-timecode_start)/nb_bytes;
for (int i=0;i<nb_bytes;i+=2) {
qint32 samp = (qint16) (((samples[i+1] & 0xFF) << 8) | (samples[i] & 0xFF));
/*jsEngine.globalObject().setProperty("sample", samp);
jsEngine.globalObject().setProperty("volume", row(0)->field(0)->get_double_value(timecode_start+(interval*i), true));
QJSValue result = eval.call();
samp = result.toInt();*/
/*QJSValueList args;
args << samples << nb_bytes;*/
samples[i+1] = (quint8) (samp >> 8);
samples[i] = (quint8) samp;
}
}
bool SuperimposeEffect::valueHasChanged(double timecode) {
void Effect::redraw(double timecode) {
// run javascript
QPainter p(&img);
painter_wrapper.img = &img;
painter_wrapper.painter = &p;
jsEngine.globalObject().setProperty("painter", wrapper_obj);
jsEngine.globalObject().setProperty("width", parent_clip->getWidth());
jsEngine.globalObject().setProperty("height", parent_clip->getHeight());
for (int i=0;i<rows.size();i++) {
EffectRow* row = rows.at(i);
for (int j=0;j<row->fieldCount();j++) {
EffectField* field = row->field(j);
if (!field->id.isEmpty()) {
switch (field->type) {
case EFFECT_FIELD_DOUBLE:
jsEngine.globalObject().setProperty(field->id, field->get_double_value(timecode));
break;
case EFFECT_FIELD_COLOR:
jsEngine.globalObject().setProperty(field->id, field->get_color_value(timecode).name());
break;
case EFFECT_FIELD_STRING:
jsEngine.globalObject().setProperty(field->id, field->get_string_value(timecode));
break;
case EFFECT_FIELD_BOOL:
jsEngine.globalObject().setProperty(field->id, field->get_bool_value(timecode));
break;
case EFFECT_FIELD_COMBO:
jsEngine.globalObject().setProperty(field->id, field->get_combo_index(timecode));
break;
case EFFECT_FIELD_FONT:
jsEngine.globalObject().setProperty(field->id, field->get_font_name(timecode));
break;
}
}
}
}
jsEngine.evaluate(script);
}
bool Effect::valueHasChanged(double timecode) {
if (cachedValues.size() == 0) {
for (int i=0;i<row_count();i++) {
EffectRow* crow = row(i);
@@ -593,6 +790,13 @@ bool SuperimposeEffect::valueHasChanged(double timecode) {
}
}
void Effect::delete_texture() {
if (texture != NULL) {
delete texture;
texture = NULL;
}
}
/* Effect Row Definitions */
EffectRow::EffectRow(Effect *parent, QGridLayout *uilayout, const QString &n, int row) :
@@ -1118,3 +1322,8 @@ qint16 mix_audio_sample(qint16 a, qint16 b) {
mixed_sample = qMax(qMin(mixed_sample, static_cast<qint32>(INT16_MAX)), static_cast<qint32>(INT16_MIN));
return static_cast<qint16>(mixed_sample);
}
double log_volume(double linear) {
// expects a value between 0 and 1 (or more if amplifying)
return (qExp(linear)-1)/(M_E-1);
}
+38 -47
View File
@@ -8,6 +8,8 @@
#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QJSEngine>
#include <QMutex>
class QLabel;
class QWidget;
class CollapsibleWidget;
@@ -22,43 +24,22 @@ class EffectRow;
class CheckboxEx;
class KeyframeDelete;
enum VideoEffects {
VIDEO_TRANSFORM_EFFECT,
VIDEO_SHAKE_EFFECT,
VIDEO_TEXT_EFFECT,
VIDEO_SOLID_EFFECT,
VIDEO_INVERT_EFFECT,
VIDEO_CHROMAKEY_EFFECT,
VIDEO_GAUSSIANBLUR_EFFECT,
VIDEO_CROP_EFFECT,
VIDEO_FLIP_EFFECT,
VIDEO_BOXBLUR_EFFECT,
VIDEO_WAVE_EFFECT,
VIDEO_TEMPERATURE_EFFECT,
VIDEO_EFFECT_COUNT
};
enum AudioEffects {
AUDIO_VOLUME_EFFECT,
AUDIO_PAN_EFFECT,
AUDIO_NOISE_EFFECT,
AUDIO_TONE_EFFECT,
AUDIO_EFFECT_COUNT
};
struct EffectMeta {
QString name;
QString category;
QString filename;
int internal;
};
extern QVector<EffectMeta> video_effects;
extern QVector<EffectMeta> audio_effects;
extern QVector<QString> video_effect_names; // deprecated
extern QVector<QString> audio_effect_names; // deprecated
double log_volume(double linear);
void init_effects();
Effect* create_effect(int effect_id, Clip* c);
Effect* create_effect(Clip* c, const EffectMeta *em);
const EffectMeta* get_internal_meta(int internal_id);
extern QMutex effects_loaded;
#define EFFECT_TYPE_INVALID 0
#define EFFECT_TYPE_VIDEO 1
@@ -75,6 +56,15 @@ Effect* create_effect(int effect_id, Clip* c);
#define EFFECT_KEYFRAME_HOLD 1
#define EFFECT_KEYFRAME_BEZIER 2
#define EFFECT_INTERNAL_TRANSFORM 0
#define EFFECT_INTERNAL_TEXT 1
#define EFFECT_INTERNAL_SOLID 2
#define EFFECT_INTERNAL_NOISE 3
#define EFFECT_INTERNAL_VOLUME 4
#define EFFECT_INTERNAL_PAN 5
#define EFFECT_INTERNAL_TONE 6
#define EFFECT_INTERNAL_COUNT 7
struct GLTextureCoords {
int vertexTopLeftX;
int vertexTopLeftY;
@@ -103,6 +93,7 @@ public:
EffectField(EffectRow* parent, int t);
EffectRow* parent_row;
int type;
QString id;
QVariant get_previous_data();
QVariant get_current_data();
@@ -193,10 +184,10 @@ private:
class Effect : public QObject {
Q_OBJECT
public:
Effect(Clip* c, const EffectMeta& em);
Effect(Clip* c, const EffectMeta* em);
~Effect();
Clip* parent_clip;
const EffectMeta& meta;
const EffectMeta* meta;
int id;
QString name;
CollapsibleWidget* container;
@@ -210,15 +201,15 @@ public:
virtual void refresh();
Effect* copy(Clip* c);
Effect* copy(Clip* c);
void copy_field_keyframes(Effect *e);
void load(QXmlStreamReader& stream);
void save(QXmlStreamWriter& stream);
// glsl handling
virtual void open();
virtual void close();
void open();
void close();
virtual void startEffect();
virtual void endEffect();
@@ -231,38 +222,38 @@ public:
const char* ffmpeg_filter;
virtual void process_shader(double timecode);
void process_shader(double timecode);
virtual void process_coords(double timecode, GLTextureCoords& coords);
virtual GLuint process_superimpose(double timecode);
virtual GLuint process_superimpose(double timecode);
virtual void process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count);
public slots:
void field_changed();
protected:
// glsl effect
QOpenGLShaderProgram* glslProgram;
QString vertPath;
QString fragPath;
bool isOpen;
// superimpose effect
QImage img;
QOpenGLTexture* texture;
private:
// superimpose effect
QJSEngine jsEngine;
QJSValue wrapper_obj;
QString script;
bool isOpen;
QVector<EffectRow*> rows;
QGridLayout* ui_layout;
QWidget* ui;
bool bound;
};
class SuperimposeEffect : public Effect {
public:
SuperimposeEffect(Clip* c, const EffectMeta& e);
virtual void open();
virtual void close();
virtual GLuint process_superimpose(double timecode);
// superimpose functions
virtual void redraw(double timecode);
protected:
QImage img;
QOpenGLTexture* texture;
void deleteTexture();
bool valueHasChanged(double timecode);
private:
QVector<QVariant> cachedValues;
void delete_texture();
};
#endif // EFFECT_H
+1
View File
@@ -0,0 +1 @@
Place this folder in the runtime folder of Olive.
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<effect name="Box Blur" category="Blur">
<row name="Radius">
<field type="double" min="0" default="10" id="radius"/>
</row>
<row name="Horizontal">
<field type="bool" default="1" id="horiz_blur"/>
</row>
<row name="Vertical">
<field type="bool" default="1" id="vert_blur"/>
</row>
<shader vert="common.vert" frag="boxblur.frag"/>
</effect>
+36
View File
@@ -0,0 +1,36 @@
#version 110
uniform float left;
uniform float top;
uniform float right;
uniform float bottom;
uniform float feather;
uniform mediump float amount_val;
uniform sampler2D myTexture;
varying vec2 vTexCoord;
void main(void) {
vec4 textureColor = texture2D(myTexture, vec2(vTexCoord.x, vTexCoord.y));
float alpha = textureColor.a;
if (feather == 0.0) {
if (vTexCoord.x < (left*0.01) || vTexCoord.y < (top*0.01) || vTexCoord.x > (1.0-(right*0.01)) || vTexCoord.y > (1.0-(bottom*0.01))) {
alpha = 0.0;
}
} else {
float f = pow(2.0, 10.0-(feather*0.1));
if (left > 0.0) alpha = alpha * clamp(((vTexCoord.x+(0.5/f))-(left*0.01))*f, 0.0, 1.0); // left
if (top > 0.0) alpha = alpha * clamp(((vTexCoord.y+(0.5/f))-(top*0.01))*f, 0.0, 1.0); // top
if (right > 0.0) alpha = alpha * clamp((((1.0-vTexCoord.x)+(0.5/f))-(right*0.01))*f, 0.0, 1.0); // right
if (bottom > 0.0) alpha = alpha * clamp((((1.0-vTexCoord.y)+(0.5/f))-(bottom*0.01))*f, 0.0, 1.0); // bottom
}
gl_FragColor = vec4(
textureColor.r,
textureColor.g,
textureColor.b,
alpha
);
}
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<effect name="Crop" category="Distort">
<row name="Left">
<field type="double" min="0" default="0" max="100" id="left"/>
</row>
<row name="Top">
<field type="double" min="0" default="0" max="100" id="top"/>
</row>
<row name="Right">
<field type="double" min="0" default="0" max="100" id="right"/>
</row>
<row name="Bottom">
<field type="double" min="0" default="0" max="100" id="bottom"/>
</row>
<row name="Feather">
<field type="double" min="0" default="0" id="feather"/>
</row>
<shader vert="common.vert" frag="crop.frag"/>
</effect>
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<effect name="Gaussian Blur" category="Blur">
<row name="Radius">
<field type="double" min="0" default="10" id="radius"/>
</row>
<row name="Sigma">
<field type="double" min="0" default="5.5" id="sigma"/>
</row>
<row name="Horizontal">
<field type="bool" default="1" id="horiz_blur"/>
</row>
<row name="Vertical">
<field type="bool" default="1" id="vert_blur"/>
</row>
<shader vert="common.vert" frag="gaussianblur.frag"/>
</effect>
@@ -1,11 +1,12 @@
#version 110
uniform mediump float amount_val;
uniform mediump float amount;
uniform sampler2D myTexture;
varying vec2 vTexCoord;
void main(void) {
vec4 textureColor = texture2D(myTexture, vTexCoord);
float amount_val = amount * 0.01;
gl_FragColor = vec4(
textureColor.r+((1.0-textureColor.r-textureColor.r)*amount_val),
textureColor.g+((1.0-textureColor.g-textureColor.g)*amount_val),
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<effect name="Invert" category="Color">
<row name="Amount">
<field type="double" min="0" default="100" max="100" id="amount"/>
</row>
<shader vert="common.vert" frag="invert.frag"/>
</effect>
@@ -15,9 +15,9 @@ void main(void) {
float y = vTexCoord.y;
if (vertical) {
x -= sin((vTexCoord.y-evolution)*frequency)*intensity;
x -= sin((vTexCoord.y-(evolution*0.01))*frequency)*intensity*0.01;
} else {
y -= sin((vTexCoord.x-evolution)*frequency)*intensity;
y -= sin((vTexCoord.x-(evolution*0.01))*frequency)*intensity*0.01;
}
if (y < 0.0 || y > 1.0 || x < 0.0 || x > 1.0) {
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<effect name="Wave" category="Distort">
<row name="Frequency">
<field type="double" min="0" default="10" id="frequency"/>
</row>
<row name="Intensity">
<field type="double" default="10" id="intensity"/>
</row>
<row name="Evolution">
<field type="double" default="0" id="evolution"/>
</row>
<row name="Vertical">
<field type="bool" default="0" id="vertical"/>
</row>
<shader vert="common.vert" frag="wave.frag"/>
</effect>
@@ -3,13 +3,15 @@
#include <QDateTime>
#include <QtMath>
AudioNoiseEffect::AudioNoiseEffect(Clip* c) : Effect(c, EFFECT_TYPE_AUDIO, AUDIO_NOISE_EFFECT) {
AudioNoiseEffect::AudioNoiseEffect(Clip* c, const EffectMeta *em) : Effect(c, em) {
amount_val = add_row("Amount:")->add_field(EFFECT_FIELD_DOUBLE);
amount_val->set_double_minimum_value(0);
amount_val->set_double_maximum_value(100);
amount_val->set_double_default_value(20);
amount_val->id = "amount";
mix_val = add_row("Mix:")->add_field(EFFECT_FIELD_BOOL);
mix_val->id = "mix";
mix_val->set_bool_value(true);
srand(QDateTime::currentMSecsSinceEpoch());
@@ -27,7 +29,7 @@ void AudioNoiseEffect::process_audio(double timecode_start, double timecode_end,
qint16 right_noise_sample = rand();
// set noise volume
double vol = qSqrt(amount_val->get_double_value(timecode, true)*0.01);
double vol = log_volume( amount_val->get_double_value(timecode, true)*0.01 );
left_noise_sample *= vol;
right_noise_sample *= vol;
@@ -5,7 +5,7 @@
class AudioNoiseEffect : public Effect {
public:
AudioNoiseEffect(Clip* c);
AudioNoiseEffect(Clip* c, const EffectMeta* em);
void process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count);
EffectField* amount_val;
@@ -8,11 +8,12 @@
#include "ui/labelslider.h"
#include "ui/collapsiblewidget.h"
PanEffect::PanEffect(Clip* c) : Effect(c, EFFECT_TYPE_AUDIO, AUDIO_PAN_EFFECT) {
PanEffect::PanEffect(Clip* c, const EffectMeta *em) : Effect(c, em) {
EffectRow* pan_row = add_row("Pan:");
pan_val = pan_row->add_field(EFFECT_FIELD_DOUBLE);
pan_val->set_double_minimum_value(-100);
pan_val->set_double_maximum_value(100);
pan_val->id = "pan";
// set defaults
pan_val->set_double_default_value(0);
@@ -23,7 +24,7 @@ PanEffect::PanEffect(Clip* c) : Effect(c, EFFECT_TYPE_AUDIO, AUDIO_PAN_EFFECT) {
void PanEffect::process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int) {
double interval = (timecode_end - timecode_start)/nb_bytes;
for (int i=0;i<nb_bytes;i+=4) {
double pval = qSqrt(pan_val->get_double_value(timecode_start+(interval*i), true)*0.01);
double pval = log_volume(pan_val->get_double_value(timecode_start+(interval*i), true)*0.01);
qint16 left_sample = (qint16) (((samples[i+1] & 0xFF) << 8) | (samples[i] & 0xFF));
qint16 right_sample = (qint16) (((samples[i+3] & 0xFF) << 8) | (samples[i+2] & 0xFF));
@@ -5,7 +5,7 @@
class PanEffect : public Effect {
public:
PanEffect(Clip* c);
PanEffect(Clip* c, const EffectMeta* em);
void process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count);
EffectField* pan_val;
@@ -16,18 +16,23 @@
#define SMPTE_STRIP_COUNT 3
#define SMPTE_LOWER_BARS 4
SolidEffect::SolidEffect(Clip* c) : SuperimposeEffect(c, EFFECT_TYPE_VIDEO, VIDEO_SOLID_EFFECT) {
SolidEffect::SolidEffect(Clip* c, const EffectMeta* em) : Effect(c, em) {
enable_superimpose = true;
solid_type = add_row("Type:")->add_field(EFFECT_FIELD_COMBO);
solid_type->add_combo_item("Solid Color", SOLID_TYPE_COLOR);
solid_type->add_combo_item("SMPTE Bars", SOLID_TYPE_BARS);
solid_type->id = "type";
solid_color_field = add_row("Color:")->add_field(EFFECT_FIELD_COLOR);
solid_color_field->set_color_value(Qt::red);
solid_color_field->id = "color";
opacity_field = add_row("Opacity:")->add_field(EFFECT_FIELD_DOUBLE);
opacity_field->set_double_minimum_value(0);
opacity_field->set_double_maximum_value(100);
opacity_field->set_double_default_value(100);
opacity_field->id = "opacity";
connect(solid_type, SIGNAL(changed()), this, SLOT(field_changed()));
connect(solid_color_field, SIGNAL(changed()), this, SLOT(field_changed()));
@@ -6,10 +6,10 @@
class QOpenGLTexture;
#include <QImage>
class SolidEffect : public SuperimposeEffect {
class SolidEffect : public Effect {
Q_OBJECT
public:
SolidEffect(Clip* c);
SolidEffect(Clip* c, const EffectMeta *em);
EffectField* solid_type;
EffectField* solid_color_field;
EffectField* opacity_field;
@@ -19,19 +19,23 @@
#include "ui/colorbutton.h"
#include "ui/fontcombobox.h"
TextEffect::TextEffect(Clip *c) :
SuperimposeEffect(c, EFFECT_TYPE_VIDEO, VIDEO_TEXT_EFFECT)
TextEffect::TextEffect(Clip *c, const EffectMeta* em) :
Effect(c, em)
{
enable_superimpose = true;
text_val = add_row("Text:")->add_field(EFFECT_FIELD_STRING, 2);
text_val->id = "text";
set_font_combobox = add_row("Font:")->add_field(EFFECT_FIELD_FONT, 2);
set_font_combobox->id = "font";
size_val = add_row("Size:")->add_field(EFFECT_FIELD_DOUBLE, 2);
size_val->set_double_minimum_value(0);
size_val->id = "size";
set_color_button = add_row("Color:")->add_field(EFFECT_FIELD_COLOR, 2);
set_color_button->id = "color";
EffectRow* alignment_row = add_row("Alignment:");
halign_field = alignment_row->add_field(EFFECT_FIELD_COMBO);
@@ -39,25 +43,37 @@ TextEffect::TextEffect(Clip *c) :
halign_field->add_combo_item("Center", Qt::AlignHCenter);
halign_field->add_combo_item("Right", Qt::AlignRight);
halign_field->add_combo_item("Justify", Qt::AlignJustify);
halign_field->id = "halign";
valign_field = alignment_row->add_field(EFFECT_FIELD_COMBO);
valign_field->add_combo_item("Top", Qt::AlignTop);
valign_field->add_combo_item("Center", Qt::AlignVCenter);
valign_field->add_combo_item("Bottom", Qt::AlignBottom);
valign_field->id = "valign";
word_wrap_field = add_row("Word Wrap:")->add_field(EFFECT_FIELD_BOOL, 2);
word_wrap_field->id = "wordwrap";
outline_bool = add_row("Outline:")->add_field(EFFECT_FIELD_BOOL, 2);
outline_bool->id = "outline";
outline_color = add_row("Outline Color:")->add_field(EFFECT_FIELD_COLOR, 2);
outline_color->id = "outlinecolor";
outline_width = add_row("Outline Width:")->add_field(EFFECT_FIELD_DOUBLE, 2);
outline_width->id = "outlinewidth";
outline_width->set_double_minimum_value(0);
shadow_bool = add_row("Shadow:")->add_field(EFFECT_FIELD_BOOL, 2);
shadow_bool->id = "shadow";
shadow_color = add_row("Shadow Color:")->add_field(EFFECT_FIELD_COLOR, 2);
shadow_color->id = "shadowcolor";
shadow_distance = add_row("Shadow Distance:")->add_field(EFFECT_FIELD_DOUBLE, 2);
shadow_distance->id = "shadowdistance";
shadow_distance->set_double_minimum_value(0);
shadow_softness = add_row("Shadow Softness:")->add_field(EFFECT_FIELD_DOUBLE, 2);
shadow_softness->id = "shadowsoftness";
shadow_softness->set_double_minimum_value(0);
shadow_opacity = add_row("Shadow Opacity:")->add_field(EFFECT_FIELD_DOUBLE, 2);
shadow_opacity->id = "shadowopacity";
shadow_opacity->set_double_minimum_value(0);
shadow_opacity->set_double_maximum_value(100);
@@ -7,10 +7,10 @@
#include <QImage>
class QOpenGLTexture;
class TextEffect : public SuperimposeEffect {
class TextEffect : public Effect {
Q_OBJECT
public:
TextEffect(Clip* c);
TextEffect(Clip* c, const EffectMeta *em);
void redraw(double timecode);
EffectField* text_val;
@@ -8,21 +8,25 @@
#include "project/sequence.h"
#include "debug.h"
ToneEffect::ToneEffect(Clip* c) : Effect(c, EFFECT_TYPE_AUDIO, AUDIO_TONE_EFFECT), sinX(INT_MIN) {
ToneEffect::ToneEffect(Clip* c, const EffectMeta *em) : Effect(c, em), sinX(INT_MIN) {
type_val = add_row("Type:")->add_field(EFFECT_FIELD_COMBO);
type_val->id = "type";
type_val->add_combo_item("Sine", TONE_TYPE_SINE);
freq_val = add_row("Frequency:")->add_field(EFFECT_FIELD_DOUBLE);
freq_val->id = "frequency";
freq_val->set_double_minimum_value(20);
freq_val->set_double_maximum_value(20000);
freq_val->set_double_default_value(1000);
amount_val = add_row("Amount:")->add_field(EFFECT_FIELD_DOUBLE);
amount_val->id = "amount";
amount_val->set_double_minimum_value(0);
amount_val->set_double_maximum_value(100);
amount_val->set_double_default_value(25);
mix_val = add_row("Mix:")->add_field(EFFECT_FIELD_BOOL);
mix_val->id = "mix";
mix_val->set_bool_value(true);
connect(freq_val, SIGNAL(changed()), this, SLOT(field_changed()));
@@ -35,10 +39,9 @@ void ToneEffect::process_audio(double timecode_start, double timecode_end, quint
for (int i=0;i<nb_bytes;i+=4) {
double timecode = timecode_start+(interval*i);
qint16 left_tone_sample = qSin((2*M_PI*sinX*freq_val->get_double_value(timecode, true))/parent_clip->sequence->audio_frequency)*qSqrt((amount_val->get_double_value(timecode, true)*0.01))*INT16_MAX;
qint16 left_tone_sample = qSin((2*M_PI*sinX*freq_val->get_double_value(timecode, true))/parent_clip->sequence->audio_frequency)*log_volume(amount_val->get_double_value(timecode, true)*0.01)*INT16_MAX;
qint16 right_tone_sample = left_tone_sample;
// mix with source audio
if (mix_val->get_bool_value(timecode, true)) {
qint16 left_sample = (qint16) (((samples[i+1] & 0xFF) << 8) | (samples[i] & 0xFF));
@@ -5,7 +5,7 @@
class ToneEffect : public Effect {
public:
ToneEffect(Clip *c);
ToneEffect(Clip *c, const EffectMeta* em);
void process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count);
EffectField* type_val;
@@ -22,38 +22,48 @@
#define BLEND_MODE_MULTIPLY 2
#define BLEND_MODE_OVERLAY 3
TransformEffect::TransformEffect(Clip* c) : Effect(c, EFFECT_TYPE_VIDEO, VIDEO_TRANSFORM_EFFECT) {
TransformEffect::TransformEffect(Clip* c, const EffectMeta* em) : Effect(c, em) {
enable_coords = true;
EffectRow* position_row = add_row("Position:");
position_x = position_row->add_field(EFFECT_FIELD_DOUBLE); // position X
position_x->id = "posx";
position_y = position_row->add_field(EFFECT_FIELD_DOUBLE); // position Y
position_y->id = "posy";
EffectRow* scale_row = add_row("Scale:");
scale_x = scale_row->add_field(EFFECT_FIELD_DOUBLE); // scale X (and Y is uniform scale is selected)
scale_x->id = "scalex";
scale_x->set_double_minimum_value(0);
scale_x->set_double_maximum_value(3000);
scale_y = scale_row->add_field(EFFECT_FIELD_DOUBLE); // scale Y (disabled if uniform scale is selected)
scale_y->id = "scaley";
scale_y->set_double_minimum_value(0);
scale_y->set_double_maximum_value(3000);
EffectRow* uniform_scale_row = add_row("Uniform Scale:");
uniform_scale_field = uniform_scale_row->add_field(EFFECT_FIELD_BOOL); // uniform scale option
uniform_scale_field->id = "uniformscale";
EffectRow* rotation_row = add_row("Rotation:");
rotation = rotation_row->add_field(EFFECT_FIELD_DOUBLE);
rotation->id = "rotation";
EffectRow* anchor_point_row = add_row("Anchor Point:");
anchor_x_box = anchor_point_row->add_field(EFFECT_FIELD_DOUBLE); // anchor point X
anchor_x_box->id = "anchorx";
anchor_y_box = anchor_point_row->add_field(EFFECT_FIELD_DOUBLE); // anchor point Y
anchor_y_box->id = "anchory";
EffectRow* opacity_row = add_row("Opacity:");
opacity = opacity_row->add_field(EFFECT_FIELD_DOUBLE); // opacity
opacity->id = "opacity";
opacity->set_double_minimum_value(0);
opacity->set_double_maximum_value(100);
EffectRow* blend_mode_row = add_row("Blend Mode:");
blend_mode_box = blend_mode_row->add_field(EFFECT_FIELD_COMBO); // blend mode
blend_mode_box->id = "blendmode";
blend_mode_box->add_combo_item("Normal", BLEND_MODE_NORMAL);
blend_mode_box->add_combo_item("Overlay", BLEND_MODE_OVERLAY);
blend_mode_box->add_combo_item("Screen", BLEND_MODE_SCREEN);
@@ -6,7 +6,7 @@
class TransformEffect : public Effect {
Q_OBJECT
public:
TransformEffect(Clip* c);
TransformEffect(Clip* c, const EffectMeta* em);
void refresh();
void process_coords(double timecode, GLTextureCoords& coords);
@@ -8,11 +8,11 @@
#include "ui/labelslider.h"
#include "ui/collapsiblewidget.h"
VolumeEffect::VolumeEffect(Clip* c) : Effect(c, EFFECT_TYPE_AUDIO, AUDIO_VOLUME_EFFECT) {
VolumeEffect::VolumeEffect(Clip* c, const EffectMeta *em) : Effect(c, em) {
EffectRow* volume_row = add_row("Volume:");
volume_val = volume_row->add_field(EFFECT_FIELD_DOUBLE);
volume_val->id = "volume";
volume_val->set_double_minimum_value(0);
// volume_val->set_double_maximum_value(1000);
// set defaults
volume_val->set_double_default_value(100);
@@ -23,7 +23,7 @@ VolumeEffect::VolumeEffect(Clip* c) : Effect(c, EFFECT_TYPE_AUDIO, AUDIO_VOLUME_
void VolumeEffect::process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int) {
double interval = (timecode_end-timecode_start)/nb_bytes;
for (int i=0;i<nb_bytes;i+=2) {
double vol_val = qSqrt(volume_val->get_double_value(timecode_start+(interval*i), true)*0.01);
double vol_val = log_volume(volume_val->get_double_value(timecode_start+(interval*i), true)*0.01);
qint32 samp = (qint16) (((samples[i+1] & 0xFF) << 8) | (samples[i] & 0xFF));
samp *= vol_val;
if (samp > INT16_MAX) {
@@ -5,7 +5,7 @@
class VolumeEffect : public Effect {
public:
VolumeEffect(Clip* c);
VolumeEffect(Clip* c, const EffectMeta* em);
void process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count);
EffectField* volume_val;
+43
View File
@@ -0,0 +1,43 @@
#include "qpainterwrapper.h"
#include <QPainter>
#include "debug.h"
// exposes several QPainter functions to QJSEngine
QPainterWrapper painter_wrapper;
QPainterWrapper::QPainterWrapper() {}
QColor get_color_from_string(const QString& s) {
dout << s;
// workaround for alpha
if (s.at(0) == '#' && s.length() == 9) {
QColor color(s.left(7));
color.setAlpha(s.mid(7).toInt(NULL, 16));
return color;
} else {
return QColor(s);
}
}
void QPainterWrapper::fill(const QString& c) {
img->fill(get_color_from_string(c));
}
void QPainterWrapper::fillRect(int x, int y, int width, int height, const QString& brush) {
painter->fillRect(x, y, width, height, get_color_from_string(brush));
}
void QPainterWrapper::drawRect(int x, int y, int width, int height) {
painter->drawRect(x, y, width, height);
}
void QPainterWrapper::setPen(const QString& pen) {
painter->setPen(get_color_from_string(pen));
}
void QPainterWrapper::setBrush(const QString& brush) {
painter->setBrush(get_color_from_string(brush));
}
+24
View File
@@ -0,0 +1,24 @@
#ifndef QPAINTERWRAPPER_H
#define QPAINTERWRAPPER_H
#include <QObject>
class QPainter;
class QPainterWrapper : public QObject {
Q_OBJECT
public:
QPainterWrapper();
QImage* img;
QPainter* painter;
public slots:
void fill(const QString& color);
void fillRect(int x, int y, int width, int height, const QString& brush);
void drawRect(int x, int y, int width, int height);
void setPen(const QString& pen);
void setBrush(const QString& brush);
};
extern QPainterWrapper painter_wrapper;
#endif // QPAINTERWRAPPER_H
-41
View File
@@ -1,41 +0,0 @@
#include "boxblureffect.h"
#include "project/clip.h"
BoxBlurEffect::BoxBlurEffect(Clip *c) : Effect(c, EFFECT_TYPE_VIDEO, VIDEO_BOXBLUR_EFFECT) {
enable_shader = true;
radius_val = add_row("Radius:")->add_field(EFFECT_FIELD_DOUBLE);
// iteration_val = add_row("Iterations:")->add_field(EFFECT_FIELD_DOUBLE);
horiz_val = add_row("Horizontal:")->add_field(EFFECT_FIELD_BOOL);
vert_val = add_row("Vertical:")->add_field(EFFECT_FIELD_BOOL);
radius_val->set_double_default_value(9);
radius_val->set_double_minimum_value(0);
// iteration_val->set_double_default_value(1);
// iteration_val->set_double_minimum_value(0);
horiz_val->set_bool_value(true);
vert_val->set_bool_value(true);
vertPath = ":/shaders/common.vert";
fragPath = ":/shaders/boxblureffect.frag";
/*vert.compileSourceFile(":/shaders/common.vert");
frag.compileSourceFile(":/shaders/boxblureffect.frag");
program.addShader(&vert);
program.addShader(&frag);
program.link();*/
connect(radius_val, SIGNAL(changed()), this, SLOT(field_changed()));
// connect(iteration_val, SIGNAL(changed()), this, SLOT(field_changed()));
connect(horiz_val, SIGNAL(changed()), this, SLOT(field_changed()));
connect(vert_val, SIGNAL(changed()), this, SLOT(field_changed()));
}
void BoxBlurEffect::process_shader(double timecode) {
glslProgram->setUniformValue("resolution", parent_clip->getWidth(), parent_clip->getHeight());
glslProgram->setUniformValue("radius", (GLfloat) radius_val->get_double_value(timecode));
glslProgram->setUniformValue("horiz_blur", horiz_val->get_bool_value(timecode));
glslProgram->setUniformValue("vert_blur", vert_val->get_bool_value(timecode));
}
-18
View File
@@ -1,18 +0,0 @@
#ifndef BOXBLUREFFECT_H
#define BOXBLUREFFECT_H
#include "../effect.h"
class BoxBlurEffect : public Effect
{
public:
BoxBlurEffect(Clip* c);
void process_shader(double timecode);
private:
EffectField* radius_val;
EffectField* iteration_val;
EffectField* horiz_val;
EffectField* vert_val;
};
#endif // BOXBLUREFFECT_H
-25
View File
@@ -1,25 +0,0 @@
#include "chromakeyeffect.h"
ChromaKeyEffect::ChromaKeyEffect(Clip* c) : Effect(c, EFFECT_TYPE_VIDEO, VIDEO_CHROMAKEY_EFFECT) {
enable_shader = true;
color_field = add_row("Color:")->add_field(EFFECT_FIELD_COLOR);
tolerance_field = add_row("Tolerance:")->add_field(EFFECT_FIELD_DOUBLE);
color_field->set_color_value(Qt::green);
tolerance_field->set_double_default_value(10);
tolerance_field->set_double_minimum_value(0);
tolerance_field->set_double_maximum_value(100);
vertPath = ":/shaders/common.vert";
fragPath = ":/shaders/chromakeyeffect.frag";
connect(color_field, SIGNAL(changed()), this, SLOT(field_changed()));
connect(tolerance_field, SIGNAL(changed()), this, SLOT(field_changed()));
}
void ChromaKeyEffect::process_shader(double timecode) {
glslProgram->setUniformValue("keyColor", color_field->get_color_value(timecode));
glslProgram->setUniformValue("threshold", (GLfloat) (tolerance_field->get_double_value(timecode)*0.01));
}
-15
View File
@@ -1,15 +0,0 @@
#ifndef CHROMAKEYEFFECT_H
#define CHROMAKEYEFFECT_H
#include "effects/effect.h"
class ChromaKeyEffect : public Effect {
public:
ChromaKeyEffect(Clip* c);
void process_shader(double timecode);
private:
EffectField* color_field;
EffectField* tolerance_field;
};
#endif // CHROMAKEYEFFECT_H
-67
View File
@@ -1,67 +0,0 @@
#include "cropeffect.h"
CropEffect::CropEffect(Clip *c) : Effect(c, EFFECT_TYPE_VIDEO, VIDEO_CROP_EFFECT) {
enable_coords = true;
left_field = add_row("Left:")->add_field(EFFECT_FIELD_DOUBLE);
top_field = add_row("Top:")->add_field(EFFECT_FIELD_DOUBLE);
right_field = add_row("Right:")->add_field(EFFECT_FIELD_DOUBLE);
bottom_field = add_row("Bottom:")->add_field(EFFECT_FIELD_DOUBLE);
left_field->set_double_minimum_value(0);
left_field->set_double_maximum_value(100);
top_field->set_double_minimum_value(0);
top_field->set_double_maximum_value(100);
right_field->set_double_minimum_value(0);
right_field->set_double_maximum_value(100);
bottom_field->set_double_minimum_value(0);
bottom_field->set_double_maximum_value(100);
connect(left_field, SIGNAL(changed()), this, SLOT(field_changed()));
connect(top_field, SIGNAL(changed()), this, SLOT(field_changed()));
connect(right_field, SIGNAL(changed()), this, SLOT(field_changed()));
connect(bottom_field, SIGNAL(changed()), this, SLOT(field_changed()));
}
void CropEffect::process_coords(double timecode, GLTextureCoords &coords) {
// store initial coord data
int left = coords.vertexTopLeftX;
int top = coords.vertexTopLeftY;
int right = coords.vertexBottomRightX;
int bottom = coords.vertexBottomRightY;
int width = right - left;
int height = bottom - top;
double texLeft = coords.textureTopLeftX;
double texTop = coords.textureTopLeftY;
double texRight = coords.textureBottomRightX;
double texBottom = coords.textureBottomRightY;
double texWidth = texRight - texLeft;
double texHeight = texBottom - texTop;
// retrieve values
double left_field_value = (left_field->get_double_value(timecode)*0.01);
double right_field_value = 1-(right_field->get_double_value(timecode)*0.01);
double top_field_value = (top_field->get_double_value(timecode)*0.01);
double bottom_field_value = 1-(bottom_field->get_double_value(timecode)*0.01);
// validate values
if (left_field_value > right_field_value) right_field_value = left_field_value;
if (top_field_value > bottom_field_value) bottom_field_value = top_field_value;
// left
coords.textureTopLeftX = coords.textureBottomLeftX = texLeft+(left_field_value*texWidth);
coords.vertexTopLeftX = coords.vertexBottomLeftX = left+(width*left_field_value);
// right
coords.textureTopRightX = coords.textureBottomRightX = texLeft+(right_field_value*texWidth);
coords.vertexTopRightX = coords.vertexBottomRightX = left+(width*right_field_value);
// top
coords.textureTopLeftY = coords.textureTopRightY = texTop+(top_field_value*texHeight);
coords.vertexTopLeftY = coords.vertexTopRightY = top+(height*top_field_value);
// bottom
coords.textureBottomLeftY = coords.textureBottomRightY = texTop+(bottom_field_value*texHeight);
coords.vertexBottomLeftY = coords.vertexBottomRightY = top+(height*bottom_field_value);
}
-18
View File
@@ -1,18 +0,0 @@
#ifndef CROPEFFECT_H
#define CROPEFFECT_H
#include "../effect.h"
class CropEffect : public Effect
{
public:
CropEffect(Clip* c);
void process_coords(double timecode, GLTextureCoords &coords);
private:
EffectField* left_field;
EffectField* top_field;
EffectField* right_field;
EffectField* bottom_field;
};
#endif // CROPEFFECT_H
-36
View File
@@ -1,36 +0,0 @@
#include "flipeffect.h"
FlipEffect::FlipEffect(Clip *c) : Effect(c, EFFECT_TYPE_VIDEO, VIDEO_FLIP_EFFECT) {
enable_coords = true;
horizontal_field = add_row("Horizontal:")->add_field(EFFECT_FIELD_BOOL);
vertical_field = add_row("Vertical:")->add_field(EFFECT_FIELD_BOOL);
connect(horizontal_field, SIGNAL(changed()), this, SLOT(field_changed()));
connect(vertical_field, SIGNAL(changed()), this, SLOT(field_changed()));
}
void FlipEffect::process_coords(double timecode, GLTextureCoords &coords) {
if (horizontal_field->get_bool_value(timecode)) {
double tlX = coords.textureTopLeftX;
double blX = coords.textureBottomLeftX;
double trX = coords.textureTopRightX;
double brX = coords.textureBottomRightX;
coords.textureTopLeftX = trX;
coords.textureTopRightX = tlX;
coords.textureBottomLeftX = brX;
coords.textureBottomRightX = blX;
}
if (vertical_field->get_bool_value(timecode)) {
double tlY = coords.textureTopLeftY;
double blY = coords.textureBottomLeftY;
double trY = coords.textureTopRightY;
double brY = coords.textureBottomRightY;
coords.textureTopLeftY = blY;
coords.textureTopRightY = brY;
coords.textureBottomLeftY = tlY;
coords.textureBottomRightY = trY;
}
}
-16
View File
@@ -1,16 +0,0 @@
#ifndef FLIPEFFECT_H
#define FLIPEFFECT_H
#include "../effect.h"
class FlipEffect : public Effect
{
public:
FlipEffect(Clip* c);
void process_coords(double timecode, GLTextureCoords &coords);
private:
EffectField* horizontal_field;
EffectField* vertical_field;
};
#endif // FLIPEFFECT_H
-39
View File
@@ -1,39 +0,0 @@
#include "gaussianblureffect.h"
#include "project/clip.h"
#include <QImage>
GaussianBlurEffect::GaussianBlurEffect(Clip *c) : Effect(c, EFFECT_TYPE_VIDEO, VIDEO_GAUSSIANBLUR_EFFECT) {
enable_shader = true;
radius_val = add_row("Radius:")->add_field(EFFECT_FIELD_DOUBLE);
sigma_val = add_row("Sigma:")->add_field(EFFECT_FIELD_DOUBLE);
horiz_val = add_row("Horizontal:")->add_field(EFFECT_FIELD_BOOL);
vert_val = add_row("Vertical:")->add_field(EFFECT_FIELD_BOOL);
radius_val->set_double_minimum_value(0);
sigma_val->set_double_minimum_value(0);
radius_val->set_double_default_value(9);
sigma_val->set_double_default_value(5.5);
horiz_val->set_bool_value(true);
vert_val->set_bool_value(true);
vertPath = ":/shaders/common.vert";
fragPath = ":/shaders/gaussianblureffect.frag";
connect(radius_val, SIGNAL(changed()), this, SLOT(field_changed()));
connect(sigma_val, SIGNAL(changed()), this, SLOT(field_changed()));
connect(horiz_val, SIGNAL(changed()), this, SLOT(field_changed()));
connect(vert_val, SIGNAL(changed()), this, SLOT(field_changed()));
}
void GaussianBlurEffect::process_shader(double timecode) {
glslProgram->setUniformValue("resolution", parent_clip->getWidth(), parent_clip->getHeight());
glslProgram->setUniformValue("radius", (GLfloat) radius_val->get_double_value(timecode));
glslProgram->setUniformValue("sigma", (GLfloat) sigma_val->get_double_value(timecode));
glslProgram->setUniformValue("horiz_blur", horiz_val->get_bool_value(timecode));
glslProgram->setUniformValue("vert_blur", vert_val->get_bool_value(timecode));
}
-18
View File
@@ -1,18 +0,0 @@
#ifndef GAUSSIANBLUREFFECT_H
#define GAUSSIANBLUREFFECT_H
#include "../effect.h"
class GaussianBlurEffect : public Effect
{
public:
GaussianBlurEffect(Clip* c);
void process_shader(double timecode);
private:
EffectField* radius_val;
EffectField* sigma_val;
EffectField* horiz_val;
EffectField* vert_val;
};
#endif // GAUSSIANBLUREFFECT_H
-16
View File
@@ -1,16 +0,0 @@
#version 110
uniform vec4 keyColor;
uniform float threshold;
uniform sampler2D myTexture;
varying vec2 vTexCoord;
void main(void) {
vec4 textureColor = texture2D(myTexture, vTexCoord);
float diff = length(keyColor - textureColor);
if (diff < threshold) {
gl_FragColor = vec4(0, 0, 0, 0);
} else {
gl_FragColor = textureColor;
}
}
-12
View File
@@ -1,12 +0,0 @@
<RCC>
<qresource prefix="/shaders">
<file>gaussianblureffect.frag</file>
<file>common.vert</file>
<file>inverteffect.frag</file>
<file>chromakeyeffect.frag</file>
<file>solideffect.frag</file>
<file>boxblureffect.frag</file>
<file>waveeffect.frag</file>
<file>temperatureeffect.frag</file>
</qresource>
</RCC>
-16
View File
@@ -1,16 +0,0 @@
#version 110
uniform vec4 solidColor;
uniform float amount_val;
uniform sampler2D myTexture;
varying vec2 vTexCoord;
void main(void) {
vec4 textureColor = texture2D(myTexture, vTexCoord);
gl_FragColor = vec4(
textureColor.r+((solidColor.r-textureColor.r)*amount_val),
textureColor.g+((solidColor.g-textureColor.g)*amount_val),
textureColor.b+((solidColor.b-textureColor.b)*amount_val),
textureColor.a
);
}
-42
View File
@@ -1,42 +0,0 @@
#version 110
// adapted from Tanner Helland's temperature algorithm
// (http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/)
uniform float temperature;
uniform mediump float amount_val;
uniform sampler2D myTexture;
varying vec2 vTexCoord;
void main(void) {
vec4 textureColor = texture2D(myTexture, vTexCoord);
// red value
float red = (temperature <= 66.0) ? 1.0 : min(1.0, max(0.0,
(1.2929361861 * pow(temperature - 60.0, -0.1332047592))
));
// green value
float green = min(1.0, max(0.0,
(temperature <= 66.0) ? (0.3900815788 * log(temperature) - 0.6318414438) : (1.1298908609 * pow(temperature - 60.0, -0.0755148492))
));
// blue value
float blue = (temperature >= 66.0) ? 1.0 : min(1.0, max(0.0,
(0.5432067891 * log(temperature - 10.0) - 1.1962540891)
));
gl_FragColor = vec4(
textureColor.r*red,
textureColor.g*green,
textureColor.b*blue,
textureColor.a
);
/*gl_FragColor = vec4(
red,
green,
blue,
1.0
);*/
}
-27
View File
@@ -1,27 +0,0 @@
#include "inverteffect.h"
#include "ui/labelslider.h"
#include <QLabel>
#include <QGridLayout>
InvertEffect::InvertEffect(Clip* c) : Effect(c, EFFECT_TYPE_VIDEO, VIDEO_INVERT_EFFECT) {
enable_shader = true;
EffectRow* amount_row = add_row("Amount:");
amount_val = amount_row->add_field(EFFECT_FIELD_DOUBLE);
amount_val->set_double_minimum_value(0);
amount_val->set_double_maximum_value(100);
// set defaults
amount_val->set_double_default_value(100);
connect(amount_val, SIGNAL(changed()), this, SLOT(field_changed()));
vertPath = ":/shaders/common.vert";
fragPath = ":/shaders/inverteffect.frag";
}
void InvertEffect::process_shader(double timecode) {
glslProgram->setUniformValue("amount_val", (GLfloat) (amount_val->get_double_value(timecode)*0.01));
}
-15
View File
@@ -1,15 +0,0 @@
#ifndef INVERTEFFECT_H
#define INVERTEFFECT_H
#include "../effect.h"
class InvertEffect : public Effect {
Q_OBJECT
public:
InvertEffect(Clip* c);
void process_shader(double timecode);
private:
EffectField* amount_val;
};
#endif // INVERTEFFECT_H
-107
View File
@@ -1,107 +0,0 @@
#include "shakeeffect.h"
#include <QGridLayout>
#include <QLabel>
#include <QtMath>
#include <QOpenGLFunctions>
#include "ui/labelslider.h"
#include "ui/collapsiblewidget.h"
#include "project/clip.h"
#include "project/sequence.h"
#include "panels/timeline.h"
ShakeEffect::ShakeEffect(Clip *c) : Effect(c, EFFECT_TYPE_VIDEO, VIDEO_SHAKE_EFFECT), inside(false) {
enable_coords = true;
EffectRow* intensity_row = add_row("Intensity:");
intensity_val = intensity_row->add_field(EFFECT_FIELD_DOUBLE);
intensity_val->set_double_minimum_value(0);
EffectRow* rotation_row = add_row("Rotation:");
rotation_val = rotation_row->add_field(EFFECT_FIELD_DOUBLE);
rotation_val->set_double_minimum_value(0);
EffectRow* frequency_row = add_row("Frequency:");
frequency_val = frequency_row->add_field(EFFECT_FIELD_DOUBLE);
frequency_val->set_double_minimum_value(0);
// set defaults
intensity_val->set_double_default_value(50);
rotation_val->set_double_default_value(0);
frequency_val->set_double_default_value(10);
refresh();
connect(intensity_val, SIGNAL(changed()), this, SLOT(field_changed()));
connect(rotation_val, SIGNAL(changed()), this, SLOT(field_changed()));
connect(frequency_val, SIGNAL(changed()), this, SLOT(field_changed()));
}
void ShakeEffect::refresh() {
if (parent_clip->sequence != NULL) {
shake_limit = qRound(parent_clip->sequence->frame_rate / frequency_val->get_double_value(-1));
shake_progress = shake_limit;
next_x = 0;
next_y = 0;
next_rot = 0;
}
}
void ShakeEffect::process_coords(double timecode, GLTextureCoords&) {
if (shake_progress > shake_limit) {
double ival = intensity_val->get_double_value(timecode);
if ((int)ival > 0) {
prev_x = next_x;
prev_y = next_y;
next_x = (qrand() % (int) (ival * 2)) - ival;
next_y = (qrand() % (int) (ival * 2)) - ival;
// find perpendicular slope that passes through (mid_x, mid_y)
int mid_x = (prev_x + next_x) / 2;
int mid_y = (prev_y + next_y) / 2;
int slope_num = (next_y-prev_y);
if (slope_num > 0) {
int slope_den = (next_x-prev_x);
int add = (next_x-prev_x)/4;
if (inside) add = -add;
inside = !inside;
perp_x = mid_x + add;
perp_y = (-(slope_den / slope_num)) * perp_x + mid_y;
} else {
perp_x = mid_x;
perp_y = mid_y;
}
} else {
prev_x = 0;
prev_y = 0;
next_x = 0;
next_y = 0;
offset_x = 0;
offset_y = 0;
}
double rot_val = rotation_val->get_double_value(timecode);
if ((int)rot_val > 0) {
prev_rot = next_rot;
next_rot = (qrand() % (int) (rot_val * 2)) - rot_val;
} else {
prev_rot = 0;
next_rot = 0;
offset_rot = 0;
}
shake_progress = 0;
}
t = (double) shake_progress / (double) shake_limit;
double oneminust = 1 - t;
offset_x = (qPow(oneminust, 2)*prev_x) + (2*oneminust*t*perp_x) + (qPow(t, 2) * next_x);
offset_y = (qPow(oneminust, 2)*prev_y) + (2*oneminust*t*perp_y) + (qPow(t, 2) * next_y);
offset_rot = lerp(prev_rot, next_rot, t);
glTranslatef(offset_x, offset_y, 0);
glRotatef(offset_rot, 0, 0, 1);
shake_progress++;
}
-35
View File
@@ -1,35 +0,0 @@
#ifndef SHAKEEFFECT_H
#define SHAKEEFFECT_H
#include "../effect.h"
class ShakeEffect : public Effect {
Q_OBJECT
public:
ShakeEffect(Clip* c);
void process_coords(double timecode, GLTextureCoords& coords);
EffectField* intensity_val;
EffectField* rotation_val;
EffectField* frequency_val;
public slots:
void refresh();
private:
int shake_progress;
int shake_limit;
int next_x;
int next_y;
int next_rot;
int offset_x;
int offset_y;
int offset_rot;
int prev_x;
int prev_y;
int prev_rot;
int perp_x;
int perp_y;
double t;
bool inside;
};
#endif // SHAKEEFFECT_H
-19
View File
@@ -1,19 +0,0 @@
#include "temperatureeffect.h"
TemperatureEffect::TemperatureEffect(Clip* c) : Effect(c, EFFECT_TYPE_VIDEO, VIDEO_TEMPERATURE_EFFECT) {
enable_shader = true;
temp_val = add_row("Temperature:")->add_field(EFFECT_FIELD_DOUBLE);
temp_val->set_double_minimum_value(2000);
temp_val->set_double_default_value(5200);
temp_val->set_double_maximum_value(40000);
connect(temp_val, SIGNAL(changed()), this, SLOT(field_changed()));
vertPath = ":/shaders/common.vert";
fragPath = ":/shaders/temperatureeffect.frag";
}
void TemperatureEffect::process_shader(double timecode) {
glslProgram->setUniformValue("temperature", (GLfloat) (temp_val->get_double_value(timecode)*0.01));
}
-15
View File
@@ -1,15 +0,0 @@
#ifndef TEMPERATUREEFFECT_H
#define TEMPERATUREEFFECT_H
#include "../effect.h"
class TemperatureEffect : public Effect {
Q_OBJECT
public:
TemperatureEffect(Clip* c);
void process_shader(double timecode);
private:
EffectField* temp_val;
};
#endif // TEMPERATUREEFFECT_H
-33
View File
@@ -1,33 +0,0 @@
#include "waveeffect.h"
WaveEffect::WaveEffect(Clip* c) : Effect(c, EFFECT_TYPE_VIDEO, VIDEO_WAVE_EFFECT) {
enable_shader = true;
frequency_val = add_row("Frequency:")->add_field(EFFECT_FIELD_DOUBLE);
frequency_val->set_double_minimum_value(0);
frequency_val->set_double_default_value(10);
intensity_val = add_row("Intensity:")->add_field(EFFECT_FIELD_DOUBLE);
intensity_val->set_double_default_value(10);
evolution_val = add_row("Evolution:")->add_field(EFFECT_FIELD_DOUBLE);
evolution_val->set_double_default_value(0);
vertical_val = add_row("Vertical:")->add_field(EFFECT_FIELD_BOOL);
vertical_val->set_bool_value(false);
connect(frequency_val, SIGNAL(changed()), this, SLOT(field_changed()));
connect(intensity_val, SIGNAL(changed()), this, SLOT(field_changed()));
connect(evolution_val, SIGNAL(changed()), this, SLOT(field_changed()));
connect(vertical_val, SIGNAL(changed()), this, SLOT(field_changed()));
vertPath = ":/shaders/common.vert";
fragPath = ":/shaders/waveeffect.frag";
}
void WaveEffect::process_shader(double timecode) {
glslProgram->setUniformValue("frequency", (GLfloat) (frequency_val->get_double_value(timecode)));
glslProgram->setUniformValue("intensity", (GLfloat) (intensity_val->get_double_value(timecode)*0.01));
glslProgram->setUniformValue("evolution", (GLfloat) (evolution_val->get_double_value(timecode)*0.01));
glslProgram->setUniformValue("vertical", vertical_val->get_bool_value(timecode));
}
-19
View File
@@ -1,19 +0,0 @@
#ifndef WAVEEFFECT_H
#define WAVEEFFECT_H
#include "../effect.h"
class WaveEffect : public Effect {
Q_OBJECT
public:
WaveEffect(Clip* c);
void process_shader(double timecode);
private:
EffectField* frequency_val;
EffectField* intensity_val;
EffectField* evolution_val;
EffectField* vertical_val;
};
#endif // WAVEEFFECT_H
+1 -1
View File
@@ -110,7 +110,7 @@ void Config::save(QString path) {
stream.writeStartDocument(); // doc
stream.writeStartElement("Configuration"); // configuration
stream.writeTextElement("Version", SAVE_VERSION);
stream.writeTextElement("Version", QString::number(SAVE_VERSION));
stream.writeTextElement("SavedLayout", QString::number(saved_layout));
stream.writeTextElement("ShowTrackLines", QString::number(show_track_lines));
stream.writeTextElement("ScrollZooms", QString::number(scroll_zooms));
+2 -1
View File
@@ -3,7 +3,8 @@
#include <QString>
#define SAVE_VERSION "181030" // YYMMDD
#define SAVE_VERSION 181030 // YYMMDD
#define MIN_SAVE_VERSION 180820 // lowest compatible project version
#define TIMECODE_DROP 0
#define TIMECODE_NONDROP 1
+21 -6
View File
@@ -4,7 +4,7 @@
#
#-------------------------------------------------
QT += core gui multimedia opengl
QT += core gui multimedia opengl qml
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
@@ -59,7 +59,6 @@ SOURCES += \
ui/comboboxex.cpp \
ui/colorbutton.cpp \
dialogs/replaceclipmediadialog.cpp \
effects/linearfadetransition.cpp \
ui/fontcombobox.cpp \
ui/checkboxex.cpp \
effects/effect.cpp \
@@ -72,7 +71,16 @@ SOURCES += \
io/crc32.cpp \
dialogs/loaddialog.cpp \
debug.cpp \
io/path.cpp
io/path.cpp \
effects/qpainterwrapper.cpp \
effects/internal/linearfadetransition.cpp \
effects/internal/transformeffect.cpp \
effects/internal/solideffect.cpp \
effects/internal/texteffect.cpp \
effects/internal/audionoiseeffect.cpp \
effects/internal/paneffect.cpp \
effects/internal/toneeffect.cpp \
effects/internal/volumeeffect.cpp
HEADERS += \
mainwindow.h \
@@ -123,7 +131,15 @@ HEADERS += \
io/crc32.h \
dialogs/loaddialog.h \
debug.h \
io/path.h
io/path.h \
effects/qpainterwrapper.h \
effects/internal/transformeffect.h \
effects/internal/solideffect.h \
effects/internal/texteffect.h \
effects/internal/audionoiseeffect.h \
effects/internal/paneffect.h \
effects/internal/toneeffect.h \
effects/internal/volumeeffect.h
FORMS += \
mainwindow.ui \
@@ -153,5 +169,4 @@ linux {
}
RESOURCES += \
icons/icons.qrc \
effects/video/glsl/shaders.qrc
icons/icons.qrc
+28 -10
View File
@@ -137,12 +137,15 @@ void EffectControls::show_effect_menu(bool video, bool transitions) {
break;
}
}
if (!added) effects_menu.addAction(action);
if (!added) effects_menu.addAction(action);
}
connect(&effects_menu, SIGNAL(triggered(QAction*)), this, SLOT(menu_select(QAction*)));
effects_menu.exec(QCursor::pos());*/
effects_loaded.lock();
video_menu = video;
transition_menu = transitions;
@@ -152,9 +155,8 @@ void EffectControls::show_effect_menu(bool video, bool transitions) {
const EffectMeta& em = effect_list.at(i);
QAction* action = new QAction(&effects_menu);
action->setText(em.name);
action->setData(reinterpret_cast<quintptr>(&em));
action->setData(reinterpret_cast<quintptr>(&em));
// TODO alphabetical ordering
QMenu* parent = &effects_menu;
if (!em.category.isEmpty()) {
bool found = false;
@@ -171,17 +173,33 @@ void EffectControls::show_effect_menu(bool video, bool transitions) {
if (!found) {
parent = new QMenu(&effects_menu);
parent->setTitle(em.category);
effects_menu.addMenu(parent);
bool found = false;
for (int i=0;i<effects_menu.actions().size();i++) {
QAction* comp_action = effects_menu.actions().at(i);
if (comp_action->text() > em.category) {
effects_menu.insertMenu(comp_action, parent);
found = true;
break;
}
}
if (!found) effects_menu.addMenu(parent);
}
}
parent->addAction(action);
}
bool found = false;
for (int i=0;i<parent->actions().size();i++) {
QAction* comp_action = parent->actions().at(i);
if (comp_action->text() > action->text()) {
parent->insertAction(comp_action, action);
found = true;
break;
}
}
if (!found) parent->addAction(action);
}
QMenu test_menu(this);
test_menu.setTitle("HEY NOW");
test_menu.addAction("YOU'RE AN ALL STAR");
effects_menu.addMenu(&test_menu);
effects_loaded.unlock();
connect(&effects_menu, SIGNAL(triggered(QAction*)), this, SLOT(menu_select(QAction*)));
effects_menu.exec(QCursor::pos());
+59 -9
View File
@@ -726,7 +726,8 @@ bool Project::load_worker(QFile& f, QXmlStreamReader& stream, int type) {
stream.readNextStartElement();
if (stream.name() == root_search) {
if (type == LOAD_TYPE_VERSION) {
if (stream.readElementText() != SAVE_VERSION) {
int proj_version = stream.readElementText().toInt();
if (proj_version < MIN_SAVE_VERSION && proj_version > SAVE_VERSION) {
if (QMessageBox::warning(this, "Version Mismatch", "This project was saved in a different version of Olive and may not be fully compatible with this version. Would you like to attempt loading it anyway?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) {
show_err = false;
return false;
@@ -760,7 +761,6 @@ bool Project::load_worker(QFile& f, QXmlStreamReader& stream, int type) {
QTreeWidgetItem* item = new_item();
Media* m = new Media();
// TODO make save/load-able
m->using_inout = false;
for (int j=0;j<stream.attributes().size();j++) {
@@ -775,7 +775,13 @@ bool Project::load_worker(QFile& f, QXmlStreamReader& stream, int type) {
m->url = attr.value().toString();
} else if (attr.name() == "duration") {
m->length = attr.value().toLongLong();
}
} else if (attr.name() == "using_inout") {
m->using_inout = (attr.value() == "1");
} else if (attr.name() == "in") {
m->in = attr.value().toLong();
} else if (attr.name() == "out") {
m->out = attr.value().toLong();
}
}
set_footage_of_tree(item, m);
@@ -937,6 +943,7 @@ bool Project::load_worker(QFile& f, QXmlStreamReader& stream, int type) {
stream.readNext();
if (stream.name() == "effect" && stream.isStartElement()) {
int effect_id = -1;
QString effect_name;
bool effect_enabled = true;
for (int j=0;j<stream.attributes().size();j++) {
const QXmlStreamAttribute& attr = stream.attributes().at(j);
@@ -944,15 +951,55 @@ bool Project::load_worker(QFile& f, QXmlStreamReader& stream, int type) {
effect_id = attr.value().toInt();
} else if (attr.name() == "enabled") {
effect_enabled = (attr.value() == "1");
} else if (attr.name() == "name") {
effect_name = attr.value().toString();
}
}
if (effect_id != -1) {
Effect* e = create_effect(effect_id, c);
// backwards compatibility with 180820
if (effect_id != -1) {
switch (effect_id) {
case 0: effect_name = (c->track < 0) ? "Transform" : "Volume"; break;
case 1: effect_name = (c->track < 0) ? "Shake" : "Pan"; break;
case 2: effect_name = (c->track < 0) ? "Text" : "Noise"; break;
case 3: effect_name = (c->track < 0) ? "Solid" : "Tone"; break;
case 4: effect_name = "Invert"; break;
case 5: effect_name = "Chroma Key"; break;
case 6: effect_name = "Gaussian Blur"; break;
case 7: effect_name = "Crop"; break;
case 8: effect_name = "Flip"; break;
case 9: effect_name = "Box Blur"; break;
case 10: effect_name = "Wave"; break;
case 11: effect_name = "Temperature"; break;
}
}
// wait for effects to be loaded
effects_loaded.lock();
const EffectMeta* meta = NULL;
// find effect with this name
if (!effect_name.isEmpty()) {
QVector<EffectMeta>& effect_list = (c->track < 0) ? video_effects : audio_effects;
for (int j=0;j<effect_list.size();j++) {
if (effect_list.at(j).name == effect_name) {
meta = &effect_list.at(j);
break;
}
}
}
if (meta == NULL) {
dout << "[WARNING] An effect used by this project is missing. It was not loaded.";
} else {
Effect* e = create_effect(c, meta);
e->set_enabled(effect_enabled);
// stream.writeAttribute("enabled", QString::number(e->is_enabled()));
e->load(stream);
c->effects.append(e);
c->effects.append(e);
}
effects_loaded.unlock();
}
}
}
@@ -1125,6 +1172,9 @@ void Project::save_folder(QXmlStreamWriter& stream, QTreeWidgetItem* parent, int
stream.writeAttribute("name", m->name);
stream.writeAttribute("url", m->url);
stream.writeAttribute("duration", QString::number(m->length));
stream.writeAttribute("using_inout", QString::number(m->using_inout));
stream.writeAttribute("in", QString::number(m->in));
stream.writeAttribute("out", QString::number(m->out));
for (int j=0;j<m->video_tracks.size();j++) {
MediaStream* ms = m->video_tracks.at(j);
stream.writeStartElement("video");
@@ -1216,7 +1266,7 @@ void Project::save_folder(QXmlStreamWriter& stream, QTreeWidgetItem* parent, int
for (int k=0;k<c->effects.size();k++) {
stream.writeStartElement("effect"); // effect
Effect* e = c->effects.at(k);
stream.writeAttribute("id", QString::number(e->id));
stream.writeAttribute("name", e->meta->name);
stream.writeAttribute("enabled", QString::number(e->is_enabled()));
e->save(stream);
stream.writeEndElement(); // effect
@@ -1260,7 +1310,7 @@ void Project::save_project(bool autorecovery) {
stream.writeStartElement("project"); // project
stream.writeTextElement("version", SAVE_VERSION);
stream.writeTextElement("version", QString::number(SAVE_VERSION));
save_folder(stream, NULL, MEDIA_TYPE_FOLDER, true);
+3 -1
View File
@@ -27,6 +27,8 @@
#include <QInputDialog>
#include <QMessageBox>
#include <QJSEngine>
long refactor_frame_number(long framenumber, double source_frame_rate, double target_frame_rate) {
if (source_frame_rate == target_frame_rate) return framenumber;
return qFloor(((double)framenumber/source_frame_rate)*target_frame_rate);
@@ -385,7 +387,7 @@ bool Timeline::is_clip_selected(Clip* clip, bool containing) {
}
void Timeline::on_snappingButton_toggled(bool checked) {
snapping = checked;
//snapping = checked;
}
Clip* Timeline::split_clip(ComboAction* ca, int p, long frame) {
+1 -1
View File
@@ -213,7 +213,7 @@ void AddEffectCommand::undo() {
void AddEffectCommand::redo() {
if (ref == NULL) {
ref = new Effect(clip, *meta);
ref = create_effect(clip, meta);
}
clip->effects.append(ref);
done = true;
+22 -22
View File
@@ -19,7 +19,9 @@
#include "effects/effect.h"
#include "effects/transition.h"
#include "effects/video/solideffect.h"
#include "effects/internal/solideffect.h"
#include "effects/internal/texteffect.h"
#include <QPainter>
#include <QColor>
@@ -623,14 +625,14 @@ void TimelineWidget::dropEvent(QDropEvent* event) {
}
}
/*if (c->track < 0) {
if (c->track < 0) {
// add default video effects
c->effects.append(create_effect(VIDEO_TRANSFORM_EFFECT, c));
c->effects.append(create_effect(c, get_internal_meta(EFFECT_INTERNAL_TRANSFORM)));
} else {
// add default audio effects
c->effects.append(create_effect(AUDIO_VOLUME_EFFECT, c));
c->effects.append(create_effect(AUDIO_PAN_EFFECT, c));
}*/
c->effects.append(create_effect(c, get_internal_meta(EFFECT_INTERNAL_VOLUME)));
c->effects.append(create_effect(c, get_internal_meta(EFFECT_INTERNAL_PAN)));
}
}
panel_timeline->ghosts.clear();
@@ -874,41 +876,41 @@ void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) {
if (c->track < 0) {
// default video effects (before custom effects)
c->effects.append(create_effect(VIDEO_TRANSFORM_EFFECT, c));
c->effects.append(create_effect(c, get_internal_meta(EFFECT_INTERNAL_TRANSFORM)));
c->media_type = MEDIA_TYPE_SOLID;
}
switch (panel_timeline->creating_object) {
case ADD_OBJ_TITLE:
c->name = "Title";
c->effects.append(create_effect(VIDEO_TEXT_EFFECT, c));
c->effects.append(create_effect(c, get_internal_meta(EFFECT_INTERNAL_TEXT)));
break;
case ADD_OBJ_SOLID:
c->name = "Solid Color";
c->effects.append(create_effect(VIDEO_SOLID_EFFECT, c));
c->effects.append(create_effect(c, get_internal_meta(EFFECT_INTERNAL_SOLID)));
break;
case ADD_OBJ_BARS:
{
c->name = "Bars";
Effect* e = create_effect(VIDEO_SOLID_EFFECT, c);
Effect* e = create_effect(c, get_internal_meta(EFFECT_INTERNAL_SOLID));
e->row(0)->field(0)->set_combo_index(1);
c->effects.append(e);
c->effects.append(e);
}
break;
case ADD_OBJ_TONE:
c->name = "Tone";
c->effects.append(create_effect(AUDIO_TONE_EFFECT, c));
c->effects.append(create_effect(c, get_internal_meta(EFFECT_INTERNAL_TONE)));
break;
case ADD_OBJ_NOISE:
c->name = "Noise";
c->effects.append(create_effect(AUDIO_NOISE_EFFECT, c));
c->effects.append(create_effect(c, get_internal_meta(EFFECT_INTERNAL_NOISE)));
break;
}
if (c->track >= 0) {
// default audio effects (after custom effects)
c->effects.append(create_effect(AUDIO_VOLUME_EFFECT, c));
c->effects.append(create_effect(AUDIO_PAN_EFFECT, c));
c->effects.append(create_effect(c, get_internal_meta(EFFECT_INTERNAL_VOLUME)));
c->effects.append(create_effect(c, get_internal_meta(EFFECT_INTERNAL_PAN)));
c->media_type = MEDIA_TYPE_TONE;
}
@@ -2333,14 +2335,12 @@ void TimelineWidget::paintEvent(QPaintEvent*) {
}
// Draw edit cursor
if (isLiveEditing()) {
if (is_track_visible(panel_timeline->cursor_track)) {
int cursor_x = panel_timeline->getTimelineScreenPointFromFrame(panel_timeline->cursor_frame);
int cursor_y = getScreenPointFromTrack(panel_timeline->cursor_track);
if (isLiveEditing() && is_track_visible(panel_timeline->cursor_track)) {
int cursor_x = panel_timeline->getTimelineScreenPointFromFrame(panel_timeline->cursor_frame);
int cursor_y = getScreenPointFromTrack(panel_timeline->cursor_track);
p.setPen(Qt::gray);
p.drawLine(cursor_x, cursor_y, cursor_x, cursor_y + panel_timeline->calculate_track_height(panel_timeline->cursor_track, -1));
}
p.setPen(Qt::gray);
p.drawLine(cursor_x, cursor_y, cursor_x, cursor_y + panel_timeline->calculate_track_height(panel_timeline->cursor_track, -1));
}
}
}
+3 -1
View File
@@ -366,7 +366,9 @@ GLuint ViewerWidget::compose_sequence(Clip* nest, bool render_audio) {
if (e->enable_shader || e->enable_superimpose) {
e->startEffect();
//for (int k=0;k<e->getIterations();k++) {
e->process_shader(timecode);
if (e->enable_shader) {
e->process_shader(timecode);
}
composite_texture = draw_clip(c->fbo[fbo_switcher], composite_texture);
if (e->enable_superimpose) {
GLuint superimpose_texture = e->process_superimpose(timecode);