diff --git a/effects/paneffect.cpp b/effects/audio/paneffect.cpp similarity index 94% rename from effects/paneffect.cpp rename to effects/audio/paneffect.cpp index b3a26ae9b..1066795da 100644 --- a/effects/paneffect.cpp +++ b/effects/audio/paneffect.cpp @@ -1,4 +1,4 @@ -#include "effects/effects.h" +#include "paneffect.h" #include #include diff --git a/effects/audio/paneffect.h b/effects/audio/paneffect.h new file mode 100644 index 000000000..1386497d0 --- /dev/null +++ b/effects/audio/paneffect.h @@ -0,0 +1,16 @@ +#ifndef PANEFFECT_H +#define PANEFFECT_H + +#include "../effect.h" + +class PanEffect : public Effect { +public: + PanEffect(Clip* c); + void refresh(); + void process_audio(quint8* samples, int nb_bytes); + Effect* copy(Clip* c); + + EffectField* pan_val; +}; + +#endif // PANEFFECT_H diff --git a/effects/volumeeffect.cpp b/effects/audio/volumeeffect.cpp similarity index 94% rename from effects/volumeeffect.cpp rename to effects/audio/volumeeffect.cpp index f2a8855ff..fcd23c0ae 100644 --- a/effects/volumeeffect.cpp +++ b/effects/audio/volumeeffect.cpp @@ -1,4 +1,4 @@ -#include "effects/effects.h" +#include "volumeeffect.h" #include #include diff --git a/effects/audio/volumeeffect.h b/effects/audio/volumeeffect.h new file mode 100644 index 000000000..8757f32c1 --- /dev/null +++ b/effects/audio/volumeeffect.h @@ -0,0 +1,16 @@ +#ifndef VOLUMEEFFECT_H +#define VOLUMEEFFECT_H + +#include "../effect.h" + +class VolumeEffect : public Effect { +public: + VolumeEffect(Clip* c); + void refresh(); + void process_audio(quint8* samples, int nb_bytes); + Effect* copy(Clip* c); + + EffectField* volume_val; +}; + +#endif // VOLUMEEFFECT_H diff --git a/effects/effect.cpp b/effects/effect.cpp index 0541823c8..34bec4376 100644 --- a/effects/effect.cpp +++ b/effects/effect.cpp @@ -4,7 +4,6 @@ #include "panels/viewer.h" #include "ui/viewerwidget.h" #include "ui/collapsiblewidget.h" -#include "effects/effects.h" #include "panels/project.h" #include "project/undo.h" #include "ui/labelslider.h" @@ -12,19 +11,65 @@ #include "ui/comboboxex.h" #include "ui/fontcombobox.h" #include "ui/checkboxex.h" +#include "project/clip.h" + +#include "effects/video/transformeffect.h" +#include "effects/video/inverteffect.h" +#include "effects/video/shakeeffect.h" +#include "effects/video/solideffect.h" +#include "effects/video/texteffect.h" + +#include "effects/audio/paneffect.h" +#include "effects/audio/volumeeffect.h" #include #include #include +#include +#include + +QVector video_effect_names; +QVector 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"; + + audio_effect_names[AUDIO_VOLUME_EFFECT] = "Volume"; + audio_effect_names[AUDIO_PAN_EFFECT] = "Pan"; +} + +Effect* create_effect(int effect_id, Clip* c) { + if (c->track < 0) { + switch (effect_id) { + case VIDEO_TRANSFORM_EFFECT: return new TransformEffect(c); break; + case VIDEO_SHAKE_EFFECT: return new ShakeEffect(c); break; + case VIDEO_TEXT_EFFECT: return new TextEffect(c); break; + case VIDEO_SOLID_EFFECT: return new SolidEffect(c); break; + case VIDEO_INVERT_EFFECT: return new InvertEffect(c); break; + } + } else { + switch (effect_id) { + case AUDIO_VOLUME_EFFECT: return new VolumeEffect(c); break; + case AUDIO_PAN_EFFECT: return new PanEffect(c); break; + } + } + qDebug() << "[ERROR] Invalid effect ID"; + return NULL; +} Effect::Effect(Clip* c, int t, int i) : parent_clip(c), type(t), id(i), - enable_ffmpeg(false), - enable_qimage(false), - enable_pre_gl(false), - enable_post_gl(false) + enable_image(false), + enable_opengl(false) { container = new CollapsibleWidget(); @@ -57,6 +102,10 @@ EffectRow* Effect::row(int i) { return rows.at(i); } +int Effect::row_count() { + return rows.size(); +} + void Effect::refresh() {} void Effect::field_changed() { @@ -142,14 +191,15 @@ void Effect::save(QXmlStreamWriter* stream) { } Effect* Effect::copy(Clip*) {return NULL;} -void Effect::process_gl(int*, int*) {} -void Effect::post_gl() {} +void Effect::process_image(QImage&) {} +void Effect::process_gl(QOpenGLShaderProgram&, int*, int*) {} void Effect::process_audio(uint8_t*, int) {} /* Effect Row Definitions */ EffectRow::EffectRow(Effect *parent, QGridLayout *uilayout, const QString &n, int row) : parent_effect(parent), ui(uilayout), name(n), ui_row(row) { - ui->addWidget(new QLabel(name), row, 0); + label = new QLabel(name); + ui->addWidget(label, row, 0); /*keyframe_enable = new CheckboxEx(); keyframe_enable->setToolTip("Enable Keyframes"); @@ -258,27 +308,27 @@ void EffectField::set_double_maximum_value(double v) { } void EffectField::add_combo_item(const QString& name, const QVariant& data) { - static_cast(ui_element)->addItem(name, data); + static_cast(ui_element)->addItem(name, data); } int EffectField::get_combo_index() { - return static_cast(ui_element)->currentIndex(); + return static_cast(ui_element)->currentIndex(); } const QVariant EffectField::get_combo_data() { - return static_cast(ui_element)->currentData(); + return static_cast(ui_element)->currentData(); } const QString EffectField::get_combo_string() { - return static_cast(ui_element)->currentText(); + return static_cast(ui_element)->currentText(); } void EffectField::set_combo_index(int index) { - static_cast(ui_element)->setCurrentIndex(index); + static_cast(ui_element)->setCurrentIndexEx(index); } void EffectField::set_combo_string(const QString& s) { - static_cast(ui_element)->setCurrentText(s); + static_cast(ui_element)->setCurrentTextEx(s); } bool EffectField::get_bool_value() { diff --git a/effects/effect.h b/effects/effect.h index 182cdfabc..726107db1 100644 --- a/effects/effect.h +++ b/effects/effect.h @@ -5,6 +5,8 @@ #include #include #include +#include +class QLabel; class QWidget; class CollapsibleWidget; class QGridLayout; @@ -15,6 +17,26 @@ class QXmlStreamWriter; class Effect; class CheckboxEx; +enum VideoEffects { + VIDEO_TRANSFORM_EFFECT, + VIDEO_SHAKE_EFFECT, + VIDEO_TEXT_EFFECT, + VIDEO_SOLID_EFFECT, + VIDEO_INVERT_EFFECT, + VIDEO_EFFECT_COUNT +}; + +enum AudioEffects { + AUDIO_VOLUME_EFFECT, + AUDIO_PAN_EFFECT, + AUDIO_EFFECT_COUNT +}; + +extern QVector video_effect_names; +extern QVector audio_effect_names; +void init_effects(); +Effect* create_effect(int effect_id, Clip* c); + #define EFFECT_TYPE_INVALID 0 #define EFFECT_TYPE_VIDEO 1 #define EFFECT_TYPE_AUDIO 2 @@ -93,6 +115,7 @@ public: void set_keyframe(int field, long time); void move_keyframe(int field, long from, long to); void delete_keyframe(int field, long time); + QLabel* label; private: Effect* parent_effect; QGridLayout* ui; @@ -116,6 +139,7 @@ public: EffectRow* add_row(const QString &name); EffectRow* row(int i); + int row_count(); bool is_enabled(); @@ -126,15 +150,13 @@ public: void load(QXmlStreamReader* stream); void save(QXmlStreamWriter* stream); - bool enable_ffmpeg; - bool enable_qimage; - bool enable_pre_gl; - bool enable_post_gl; + bool enable_image; + bool enable_opengl; const char* ffmpeg_filter; - virtual void process_gl(int* anchor_x, int* anchor_y); - virtual void post_gl(); + virtual void process_image(QImage& img); + virtual void process_gl(QOpenGLShaderProgram& shader_prog, int* anchor_x, int* anchor_y); virtual void process_audio(quint8* samples, int nb_bytes); public slots: void field_changed(); diff --git a/effects/effects.cpp b/effects/effects.cpp deleted file mode 100644 index af785ab8c..000000000 --- a/effects/effects.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include "effects/effects.h" - -#include "project/clip.h" - -#include -#include - -QVector video_effect_names; -QVector 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"; - - audio_effect_names[AUDIO_VOLUME_EFFECT] = "Volume"; - audio_effect_names[AUDIO_PAN_EFFECT] = "Pan"; -} - -Effect* create_effect(int effect_id, Clip* c) { - if (c->track < 0) { - switch (effect_id) { - case VIDEO_TRANSFORM_EFFECT: return new TransformEffect(c); break; - case VIDEO_SHAKE_EFFECT: return new ShakeEffect(c); break; - case VIDEO_TEXT_EFFECT: return new TextEffect(c); break; - case VIDEO_SOLID_EFFECT: return new SolidEffect(c); break; - case VIDEO_INVERT_EFFECT: return new InvertEffect(c); break; - } - } else { - switch (effect_id) { - case AUDIO_VOLUME_EFFECT: return new VolumeEffect(c); break; - case AUDIO_PAN_EFFECT: return new PanEffect(c); break; - } - } - qDebug() << "[ERROR] Invalid effect ID"; - return NULL; -} diff --git a/effects/effects.h b/effects/effects.h deleted file mode 100644 index d33268204..000000000 --- a/effects/effects.h +++ /dev/null @@ -1,179 +0,0 @@ -#ifndef EFFECTS_H -#define EFFECTS_H - -#include "effect.h" - -#include -#include -#include -#include -#include -class QSpinBox; -class QCheckBox; -class LabelSlider; -class QOpenGLTexture; -class QTextEdit; -class QPushButton; -class ColorButton; -class ComboBoxEx; - -enum VideoEffects { - VIDEO_TRANSFORM_EFFECT, - VIDEO_SHAKE_EFFECT, - VIDEO_TEXT_EFFECT, - VIDEO_SOLID_EFFECT, - VIDEO_INVERT_EFFECT, - VIDEO_EFFECT_COUNT -}; - -enum AudioEffects { - AUDIO_VOLUME_EFFECT, - AUDIO_PAN_EFFECT, - AUDIO_EFFECT_COUNT -}; - -extern QVector video_effect_names; -extern QVector audio_effect_names; -void init_effects(); -Effect* create_effect(int effect_id, Clip* c); - -// video effects -class TransformEffect : public Effect { - Q_OBJECT -public: - TransformEffect(Clip* c); - void refresh(); - void process_gl(int* anchor_x, int* anchor_y); - Effect* copy(Clip *c); - - EffectField* position_x; - EffectField* position_y; - EffectField* scale_x; - EffectField* scale_y; - EffectField* uniform_scale_field; - EffectField* rotation; - EffectField* anchor_x_box; - EffectField* anchor_y_box; - EffectField* opacity; - EffectField* blend_mode_box; -public slots: - void toggle_uniform_scale(bool enabled); -private: - int default_anchor_x; - int default_anchor_y; -}; - -class ShakeEffect : public Effect { - Q_OBJECT -public: - ShakeEffect(Clip* c); - void process_gl(int* anchor_x, int* anchor_y); - Effect* copy(Clip *c); - - 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; -}; - -class TextEffect : public Effect { - Q_OBJECT -public: - TextEffect(Clip* c); - ~TextEffect(); - void post_gl(); - void refresh(); - Effect* copy(Clip* c); - - EffectField* text_val; - EffectField* size_val; - EffectField* set_color_button; - EffectField* set_font_combobox; - EffectField* halign_field; - EffectField* valign_field; - EffectField* word_wrap_field; - - EffectField* outline_bool; - EffectField* outline_width; - EffectField* outline_color; - - EffectField* shadow_bool; - EffectField* shadow_distance; - EffectField* shadow_color; - EffectField* shadow_softness; - EffectField* shadow_opacity; -private slots: - void update_texture(); - void outline_enable(bool); - void shadow_enable(bool); -private: - void destroy_texture(); - QOpenGLTexture* texture; - QFont font; - QImage pixmap; -}; - -class SolidEffect : public Effect { - Q_OBJECT -public: - SolidEffect(Clip* c); - void post_gl(); - EffectField* solid_type; - EffectField* solid_color_field; - EffectField* opacity_field; -private slots: - void enable_color(); - void update_texture(); -private: - QOpenGLTexture* texture; -}; - -class InvertEffect : public Effect { - Q_OBJECT -public: - InvertEffect(Clip* c); - void process_gl(int *anchor_x, int *anchor_y); - Effect* copy(Clip *c); - - EffectField* amount_val; -}; - -// audio effects -class VolumeEffect : public Effect { -public: - VolumeEffect(Clip* c); - void refresh(); - void process_audio(quint8* samples, int nb_bytes); - Effect* copy(Clip* c); - - EffectField* volume_val; -}; - -class PanEffect : public Effect { -public: - PanEffect(Clip* c); - void refresh(); - void process_audio(quint8* samples, int nb_bytes); - Effect* copy(Clip* c); - - EffectField* pan_val; -}; - -#endif // EFFECTS_H diff --git a/effects/inverteffect.cpp b/effects/inverteffect.cpp deleted file mode 100644 index 25a8fe86d..000000000 --- a/effects/inverteffect.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "effects/effects.h" - -#include "ui/labelslider.h" - -#include -#include -#include -#include -#include - -InvertEffect::InvertEffect(Clip* c) : Effect(c, EFFECT_TYPE_VIDEO, VIDEO_INVERT_EFFECT) { - enable_ffmpeg = true; - ffmpeg_filter = "negate"; - - 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); -} - -Effect* InvertEffect::copy(Clip* c) { - /*InvertEffect* i = new InvertEffect(c); - i->amount_val->set_value(amount_val->value()); - return i;*/ - return NULL; -} - -void InvertEffect::process_gl(int* anchor_x, int* anchor_y) { - -// gl_FragColor = vec4(1.0 - textureColor.r, 1.0 -textureColor.g, 1.0 -textureColor.b, 1); -} diff --git a/effects/texteffect.cpp b/effects/texteffect.cpp deleted file mode 100644 index f04d1bb7b..000000000 --- a/effects/texteffect.cpp +++ /dev/null @@ -1,358 +0,0 @@ -#include "effects.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ui/labelslider.h" -#include "ui/collapsiblewidget.h" -#include "project/clip.h" -#include "project/sequence.h" -#include "ui/comboboxex.h" -#include "ui/colorbutton.h" -#include "ui/fontcombobox.h" - -TextEffect::TextEffect(Clip *c) : - Effect(c, EFFECT_TYPE_VIDEO, VIDEO_TEXT_EFFECT), - texture(NULL) -{ - enable_post_gl = true; - - text_val = add_row("Text:")->add_field(EFFECT_FIELD_STRING, 2); - - set_font_combobox = add_row("Font:")->add_field(EFFECT_FIELD_FONT, 2); - - size_val = add_row("Size:")->add_field(EFFECT_FIELD_DOUBLE, 2); - size_val->set_double_minimum_value(0); - - set_color_button = add_row("Color:")->add_field(EFFECT_FIELD_COLOR, 2); - - EffectRow* alignment_row = add_row("Alignment:"); - halign_field = alignment_row->add_field(EFFECT_FIELD_COMBO); - halign_field->add_combo_item("Left", Qt::AlignLeft); - halign_field->add_combo_item("Center", Qt::AlignHCenter); - halign_field->add_combo_item("Right", Qt::AlignRight); - halign_field->add_combo_item("Justify", Qt::AlignJustify); - 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); - - word_wrap_field = add_row("Word Wrap:")->add_field(EFFECT_FIELD_BOOL, 2); - - outline_bool = add_row("Outline:")->add_field(EFFECT_FIELD_BOOL, 2); - outline_color = add_row("Outline Color:")->add_field(EFFECT_FIELD_COLOR, 2); - outline_width = add_row("Outline Width:")->add_field(EFFECT_FIELD_DOUBLE, 2); - outline_width->set_double_minimum_value(0); - - shadow_bool = add_row("Shadow:")->add_field(EFFECT_FIELD_BOOL, 2); - shadow_color = add_row("Shadow Color:")->add_field(EFFECT_FIELD_COLOR, 2); - shadow_distance = add_row("Shadow Distance:")->add_field(EFFECT_FIELD_DOUBLE, 2); - shadow_distance->set_double_minimum_value(0); - shadow_softness = add_row("Shadow Softness:")->add_field(EFFECT_FIELD_DOUBLE, 2); - shadow_softness->set_double_minimum_value(0); - shadow_opacity = add_row("Shadow Opacity:")->add_field(EFFECT_FIELD_DOUBLE, 2); - shadow_opacity->set_double_minimum_value(0); - shadow_opacity->set_double_maximum_value(100); - - size_val->set_double_default_value(48); - text_val->set_string_value("Sample Text"); - halign_field->set_combo_index(1); - valign_field->set_combo_index(1); - word_wrap_field->set_bool_value(true); - outline_color->set_color_value(Qt::black); - shadow_color->set_color_value(Qt::black); - shadow_opacity->set_double_default_value(100); - shadow_softness->set_double_default_value(5); - shadow_distance->set_double_default_value(5); - shadow_opacity->set_double_default_value(80); - outline_width->set_double_default_value(2); - - outline_enable(false); - shadow_enable(false); - - connect(text_val, SIGNAL(changed()), this, SLOT(update_texture())); - connect(size_val, SIGNAL(changed()), this, SLOT(update_texture())); - connect(set_color_button, SIGNAL(changed()), this, SLOT(update_texture())); - connect(set_font_combobox, SIGNAL(changed()), this, SLOT(update_texture())); - connect(halign_field, SIGNAL(changed()), this, SLOT(update_texture())); - connect(valign_field, SIGNAL(changed()), this, SLOT(update_texture())); - connect(word_wrap_field, SIGNAL(changed()), this, SLOT(update_texture())); - connect(outline_bool, SIGNAL(changed()), this, SLOT(update_texture())); - connect(outline_color, SIGNAL(changed()), this, SLOT(update_texture())); - connect(outline_width, SIGNAL(changed()), this, SLOT(update_texture())); - connect(shadow_bool, SIGNAL(changed()), this, SLOT(update_texture())); - connect(shadow_color, SIGNAL(changed()), this, SLOT(update_texture())); - connect(shadow_distance, SIGNAL(changed()), this, SLOT(update_texture())); - connect(shadow_softness, SIGNAL(changed()), this, SLOT(update_texture())); - connect(shadow_opacity, SIGNAL(changed()), this, SLOT(update_texture())); - - connect(shadow_bool, SIGNAL(toggled(bool)), this, SLOT(shadow_enable(bool))); - connect(outline_bool, SIGNAL(toggled(bool)), this, SLOT(outline_enable(bool))); - - update_texture(); -} - -TextEffect::~TextEffect() { - destroy_texture(); -} - -void TextEffect::refresh() { - update_texture(); -} - -void TextEffect::destroy_texture() { - texture->destroy(); -} - -void TextEffect::shadow_enable(bool e) { - shadow_color->set_enabled(e); - shadow_distance->set_enabled(e); - shadow_softness->set_enabled(e); - shadow_opacity->set_enabled(e); -} - -void TextEffect::outline_enable(bool e) { - outline_color->set_enabled(e); - outline_width->set_enabled(e); -} - -QImage blurred(const QImage& image, const QRect& rect, int radius, bool alphaOnly = false) -{ - int tab[] = { 14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2 }; - int alpha = (radius < 1) ? 16 : (radius > 17) ? 1 : tab[radius-1]; - - QImage result = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); - int r1 = rect.top(); - int r2 = rect.bottom(); - int c1 = rect.left(); - int c2 = rect.right(); - - int bpl = result.bytesPerLine(); - int rgba[4]; - unsigned char* p; - - int i1 = 0; - int i2 = 3; - - if (alphaOnly) - i1 = i2 = (QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3); - - for (int col = c1; col <= c2; col++) { - p = result.scanLine(r1) + col * 4; - for (int i = i1; i <= i2; i++) - rgba[i] = p[i] << 4; - - p += bpl; - for (int j = r1; j < r2; j++, p += bpl) - for (int i = i1; i <= i2; i++) - p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; - } - - for (int row = r1; row <= r2; row++) { - p = result.scanLine(row) + c1 * 4; - for (int i = i1; i <= i2; i++) - rgba[i] = p[i] << 4; - - p += 4; - for (int j = c1; j < c2; j++, p += 4) - for (int i = i1; i <= i2; i++) - p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; - } - - for (int col = c1; col <= c2; col++) { - p = result.scanLine(r2) + col * 4; - for (int i = i1; i <= i2; i++) - rgba[i] = p[i] << 4; - - p -= bpl; - for (int j = r1; j < r2; j++, p -= bpl) - for (int i = i1; i <= i2; i++) - p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; - } - - for (int row = r1; row <= r2; row++) { - p = result.scanLine(row) + c2 * 4; - for (int i = i1; i <= i2; i++) - rgba[i] = p[i] << 4; - - p -= 4; - for (int j = c1; j < c2; j++, p -= 4) - for (int i = i1; i <= i2; i++) - p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; - } - - return result; -} - -void TextEffect::update_texture() { - if (parent_clip->sequence != NULL) { - if (pixmap.width() != parent_clip->sequence->width || pixmap.height() != parent_clip->sequence->height) { - pixmap = QImage(parent_clip->sequence->width, parent_clip->sequence->height, QImage::Format_ARGB32); - } - pixmap.fill(Qt::transparent); - - QPainter p(&pixmap); - p.setRenderHint(QPainter::Antialiasing); - - // set font - font.setStyleHint(QFont::Helvetica, QFont::PreferAntialias); - font.setFamily(set_font_combobox->get_font_name()); - font.setPointSize(size_val->get_double_value()); - p.setFont(font); - QFontMetrics fm(font); - - QStringList lines = text_val->get_string_value().split('\n'); - - // word wrap function - if (word_wrap_field->get_bool_value()) { - for (int i=0;i pixmap.width()) { - int last_space_index = 0; - for (int j=0;j pixmap.width()) { - break; - } else { - last_space_index = j; - } - } - } - if (last_space_index > 0) { - lines.insert(i+1, s.mid(last_space_index + 1)); - lines[i] = s.left(last_space_index); - } - } - } - } - - QPainterPath path; - - int text_height = fm.height()*lines.size(); - - for (int i=0;iget_combo_data().toInt()) { - case Qt::AlignLeft: text_x = 0; break; - case Qt::AlignHCenter: text_x = (pixmap.width()/2) - (fm.width(lines.at(i))/2); break; - case Qt::AlignRight: text_x = pixmap.width() - fm.width(lines.at(i)); break; - case Qt::AlignJustify: - // add spaces until the string is too big - text_x = 0; - while (fm.width(lines.at(i) < pixmap.width())) { - bool space = false; - QString spaced(lines.at(i)); - for (int i=0;i pixmap.width() || !space) { - break; - } else { - lines[i] = spaced; - } - } - break; - } - - switch (valign_field->get_combo_data().toInt()) { - case Qt::AlignTop: text_y = (fm.height()*i)+fm.ascent(); break; - case Qt::AlignVCenter: text_y = ((pixmap.height()/2) - (text_height/2) - fm.descent()) + (fm.height()*(i+1)); break; - case Qt::AlignBottom: text_y = (pixmap.height() - text_height - fm.descent()) + (fm.height()*(i+1)); break; - } - - path.addText(text_x, text_y, font, lines.at(i)); - } - - p.setPen(Qt::NoPen); - - // draw shadow - if (shadow_bool->get_bool_value()) { - int shadow_offset = shadow_distance->get_double_value(); - QColor col = shadow_color->get_color_value(); - col.setAlphaF(shadow_opacity->get_double_value()*0.01); - p.setBrush(col); - p.drawPath(path); - - QImage shadow = blurred(pixmap, pixmap.rect(), shadow_softness->get_double_value(), false); - p.drawImage(shadow_offset, shadow_offset, shadow); - } - - // draw outline - int outline_width_val = outline_width->get_double_value(); - if (outline_bool->get_bool_value() && outline_width_val > 0) { - QPen outline(outline_color->get_color_value()); - outline.setWidth(outline_width_val); - p.setPen(outline); - } - - // draw "master" text - p.setBrush(set_color_button->get_color_value()); - p.drawPath(path); - - p.end(); - - if (texture == NULL) { - texture = new QOpenGLTexture(pixmap); - } else { - destroy_texture(); - texture->setData(pixmap); - } - - texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear); - - // queue a repaint of the canvas - field_changed(); - } -} - -void TextEffect::post_gl() { - if (texture != NULL) { - texture->bind(); - - int half_width = pixmap.width()/2; - int half_height = pixmap.height()/2; - - glBegin(GL_QUADS); - glTexCoord2f(0.0, 0.0); - glVertex2f(-half_width, -half_height); - glTexCoord2f(1.0, 0.0); - glVertex2f(half_width, -half_height); - glTexCoord2f(1.0, 1.0); - glVertex2f(half_width, half_height); - glTexCoord2f(0.0, 1.0); - glVertex2f(-half_width, half_height); - glEnd(); - - texture->release(); - } -} - -Effect* TextEffect::copy(Clip* c) { - TextEffect* e = new TextEffect(c); - e->text_val->set_string_value(text_val->get_string_value()); - e->size_val->set_double_value(size_val->get_double_value()); - e->set_color_button->set_color_value(set_color_button->get_color_value()); - e->set_font_combobox->set_font_name(set_font_combobox->get_font_name()); - e->halign_field->set_combo_index(halign_field->get_combo_index()); - e->valign_field->set_combo_index(valign_field->get_combo_index()); - return e; -} diff --git a/effects/video/inverteffect.cpp b/effects/video/inverteffect.cpp new file mode 100644 index 000000000..28562f073 --- /dev/null +++ b/effects/video/inverteffect.cpp @@ -0,0 +1,43 @@ +#include "inverteffect.h" + +#include "ui/labelslider.h" + +#include +#include +#include +#include +#include + +InvertEffect::InvertEffect(Clip* c) : Effect(c, EFFECT_TYPE_VIDEO, VIDEO_INVERT_EFFECT), vert_shader(QOpenGLShader::Vertex), frag_shader(QOpenGLShader::Fragment) { + enable_opengl = 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(compile())); + + compile(); +} + +void InvertEffect::compile() { + double value = amount_val->get_double_value()*0.01; + vert_shader.compileSourceCode("varying vec2 vTexCoord; void main() { vTexCoord = gl_MultiTexCoord0; gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; }"); + frag_shader.compileSourceCode("uniform sampler2D myTexture; varying vec2 vTexCoord; void main(void) { vec4 textureColor = texture2D(myTexture, vTexCoord); gl_FragColor = vec4(textureColor.r+((1.0-textureColor.r-textureColor.r)*" + QString::number(value) + "), textureColor.g+((1.0-textureColor.g-textureColor.g)*" + QString::number(value) + "), textureColor.b+((1.0-textureColor.b-textureColor.b)*" + QString::number(value) + "), 1); }"); + field_changed(); +} + +Effect* InvertEffect::copy(Clip* c) { + InvertEffect* i = new InvertEffect(c); + i->amount_val->set_double_value(amount_val->get_double_value()); + return i; +} + +void InvertEffect::process_gl(QOpenGLShaderProgram& shader_prog, int* anchor_x, int* anchor_y) { + shader_prog.addShader(&vert_shader); + shader_prog.addShader(&frag_shader); +} diff --git a/effects/video/inverteffect.h b/effects/video/inverteffect.h new file mode 100644 index 000000000..924506545 --- /dev/null +++ b/effects/video/inverteffect.h @@ -0,0 +1,23 @@ +#ifndef INVERTEFFECT_H +#define INVERTEFFECT_H + +#include "../effect.h" + +//#include + +class InvertEffect : public Effect { + Q_OBJECT +public: + InvertEffect(Clip* c); + void process_gl(QOpenGLShaderProgram& shader_prog, int *anchor_x, int *anchor_y); + Effect* copy(Clip *c); + + EffectField* amount_val; +private slots: + void compile(); +private: + QOpenGLShader vert_shader; + QOpenGLShader frag_shader; +}; + +#endif // INVERTEFFECT_H diff --git a/effects/shakeeffect.cpp b/effects/video/shakeeffect.cpp similarity index 96% rename from effects/shakeeffect.cpp rename to effects/video/shakeeffect.cpp index bbc582340..7c03c10c8 100644 --- a/effects/shakeeffect.cpp +++ b/effects/video/shakeeffect.cpp @@ -1,4 +1,4 @@ -#include "effects/effects.h" +#include "shakeeffect.h" #include #include @@ -13,7 +13,7 @@ #include "panels/timeline.h" ShakeEffect::ShakeEffect(Clip *c) : Effect(c, EFFECT_TYPE_VIDEO, VIDEO_SHAKE_EFFECT), inside(false) { - enable_pre_gl = true; + enable_opengl = true; EffectRow* intensity_row = add_row("Intensity:"); intensity_val = intensity_row->add_field(EFFECT_FIELD_DOUBLE); @@ -57,7 +57,7 @@ Effect* ShakeEffect::copy(Clip* c) { return e; } -void ShakeEffect::process_gl(int*, int*) { +void ShakeEffect::process_gl(QOpenGLShaderProgram&, int*, int*) { if (shake_progress > shake_limit) { double ival = intensity_val->get_double_value(); if ((int)ival > 0) { diff --git a/effects/video/shakeeffect.h b/effects/video/shakeeffect.h new file mode 100644 index 000000000..738923ace --- /dev/null +++ b/effects/video/shakeeffect.h @@ -0,0 +1,36 @@ +#ifndef SHAKEEFFECT_H +#define SHAKEEFFECT_H + +#include "../effect.h" + +class ShakeEffect : public Effect { + Q_OBJECT +public: + ShakeEffect(Clip* c); + void process_gl(QOpenGLShaderProgram& shader_prog, int* anchor_x, int* anchor_y); + Effect* copy(Clip *c); + + 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 diff --git a/effects/solideffect.cpp b/effects/video/solideffect.cpp similarity index 72% rename from effects/solideffect.cpp rename to effects/video/solideffect.cpp index 7f6f3741d..5e9005a05 100644 --- a/effects/solideffect.cpp +++ b/effects/video/solideffect.cpp @@ -1,4 +1,4 @@ -#include "effects.h" +#include "solideffect.h" #include #include @@ -15,8 +15,8 @@ #define SMPTE_STRIP_COUNT 3 #define SMPTE_LOWER_BARS 4 -SolidEffect::SolidEffect(Clip* c) : Effect(c, EFFECT_TYPE_VIDEO, VIDEO_SOLID_EFFECT), texture(NULL) { - enable_post_gl = true; +SolidEffect::SolidEffect(Clip* c) : Effect(c, EFFECT_TYPE_VIDEO, VIDEO_SOLID_EFFECT) { + enable_image = true; solid_type = add_row("Type:")->add_field(EFFECT_FIELD_COMBO); solid_type->add_combo_item("Solid Color", SOLID_TYPE_COLOR); @@ -30,30 +30,30 @@ SolidEffect::SolidEffect(Clip* c) : Effect(c, EFFECT_TYPE_VIDEO, VIDEO_SOLID_EFF opacity_field->set_double_maximum_value(100); opacity_field->set_double_default_value(100); - connect(solid_type, SIGNAL(changed()), this, SLOT(update_texture())); + connect(solid_type, SIGNAL(changed()), this, SLOT(field_changed())); connect(solid_type, SIGNAL(changed()), this, SLOT(enable_color())); - connect(solid_color_field, SIGNAL(changed()), this, SLOT(update_texture())); + connect(solid_color_field, SIGNAL(changed()), this, SLOT(field_changed())); connect(opacity_field, SIGNAL(changed()), this, SLOT(field_changed())); - - update_texture(); } void SolidEffect::enable_color() { solid_color_field->set_enabled(solid_type->get_combo_data() == SOLID_TYPE_COLOR); } -void SolidEffect::update_texture() { - QImage img(parent_clip->sequence->width, parent_clip->sequence->height, QImage::Format_RGB888); +void SolidEffect::process_image(QImage& img) { + QPainter p(&img); + int width = img.width(); + int height = img.height(); switch (solid_type->get_combo_data().toInt()) { case SOLID_TYPE_COLOR: - img.fill(solid_color_field->get_color_value()); + { + QColor brush = solid_color_field->get_color_value(); + p.fillRect(0, 0, width, height, QColor(brush.red(), brush.green(), brush.blue(), opacity_field->get_double_value()*2.55)); + } break; case SOLID_TYPE_BARS: // draw smpte bars - img.fill(Qt::black); - - QPainter p(&img); int bar_width = qCeil((double) parent_clip->sequence->width / 7.0); int first_bar_height = qCeil((double) parent_clip->sequence->height / 3.0 * 2.0); int second_bar_height = qCeil((double) parent_clip->sequence->height / 12.5); @@ -130,41 +130,4 @@ void SolidEffect::update_texture() { } break; } - - if (texture == NULL) { - texture = new QOpenGLTexture(img); - } else { - texture->destroy(); - texture->setData(img); - } - - field_changed(); -} - -void SolidEffect::post_gl() { - if (texture != NULL) { - float color[4]; - glGetFloatv(GL_CURRENT_COLOR, color); - glColor4f(1.0, 1.0, 1.0, color[3]*(opacity_field->get_double_value()*0.01)); - - texture->bind(); - - int half_width = parent_clip->sequence->width/2; - int half_height = parent_clip->sequence->height/2; - - glBegin(GL_QUADS); - glTexCoord2f(0.0, 0.0); - glVertex2f(-half_width, -half_height); - glTexCoord2f(1.0, 0.0); - glVertex2f(half_width, -half_height); - glTexCoord2f(1.0, 1.0); - glVertex2f(half_width, half_height); - glTexCoord2f(0.0, 1.0); - glVertex2f(-half_width, half_height); - glEnd(); - - texture->release(); - - glColor4f(color[0], color[1], color[2], color[3]); - } } diff --git a/effects/video/solideffect.h b/effects/video/solideffect.h new file mode 100644 index 000000000..a08a8a814 --- /dev/null +++ b/effects/video/solideffect.h @@ -0,0 +1,18 @@ +#ifndef SOLIDEFFECT_H +#define SOLIDEFFECT_H + +#include "../effect.h" + +class SolidEffect : public Effect { + Q_OBJECT +public: + SolidEffect(Clip* c); + EffectField* solid_type; + EffectField* solid_color_field; + EffectField* opacity_field; + void process_image(QImage &img); +private slots: + void enable_color(); +}; + +#endif // SOLIDEFFECT_H diff --git a/effects/video/texteffect.cpp b/effects/video/texteffect.cpp new file mode 100644 index 000000000..d4d398d5d --- /dev/null +++ b/effects/video/texteffect.cpp @@ -0,0 +1,370 @@ +#include "texteffect.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ui/labelslider.h" +#include "ui/collapsiblewidget.h" +#include "project/clip.h" +#include "project/sequence.h" +#include "ui/comboboxex.h" +#include "ui/colorbutton.h" +#include "ui/fontcombobox.h" + +TextEffect::TextEffect(Clip *c) : + Effect(c, EFFECT_TYPE_VIDEO, VIDEO_TEXT_EFFECT) +{ + enable_image = true; + + text_val = add_row("Text:")->add_field(EFFECT_FIELD_STRING, 2); + + set_font_combobox = add_row("Font:")->add_field(EFFECT_FIELD_FONT, 2); + + size_val = add_row("Size:")->add_field(EFFECT_FIELD_DOUBLE, 2); + size_val->set_double_minimum_value(0); + + set_color_button = add_row("Color:")->add_field(EFFECT_FIELD_COLOR, 2); + + EffectRow* alignment_row = add_row("Alignment:"); + halign_field = alignment_row->add_field(EFFECT_FIELD_COMBO); + halign_field->add_combo_item("Left", Qt::AlignLeft); + halign_field->add_combo_item("Center", Qt::AlignHCenter); + halign_field->add_combo_item("Right", Qt::AlignRight); + halign_field->add_combo_item("Justify", Qt::AlignJustify); + 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); + + word_wrap_field = add_row("Word Wrap:")->add_field(EFFECT_FIELD_BOOL, 2); + + outline_bool = add_row("Outline:")->add_field(EFFECT_FIELD_BOOL, 2); + outline_color = add_row("Outline Color:")->add_field(EFFECT_FIELD_COLOR, 2); + outline_width = add_row("Outline Width:")->add_field(EFFECT_FIELD_DOUBLE, 2); + outline_width->set_double_minimum_value(0); + + shadow_bool = add_row("Shadow:")->add_field(EFFECT_FIELD_BOOL, 2); + shadow_color = add_row("Shadow Color:")->add_field(EFFECT_FIELD_COLOR, 2); + shadow_distance = add_row("Shadow Distance:")->add_field(EFFECT_FIELD_DOUBLE, 2); + shadow_distance->set_double_minimum_value(0); + shadow_softness = add_row("Shadow Softness:")->add_field(EFFECT_FIELD_DOUBLE, 2); + shadow_softness->set_double_minimum_value(0); + shadow_opacity = add_row("Shadow Opacity:")->add_field(EFFECT_FIELD_DOUBLE, 2); + shadow_opacity->set_double_minimum_value(0); + shadow_opacity->set_double_maximum_value(100); + + size_val->set_double_default_value(48); + text_val->set_string_value("Sample Text"); + halign_field->set_combo_index(1); + valign_field->set_combo_index(1); + word_wrap_field->set_bool_value(true); + outline_color->set_color_value(Qt::black); + shadow_color->set_color_value(Qt::black); + shadow_opacity->set_double_default_value(100); + shadow_softness->set_double_default_value(5); + shadow_distance->set_double_default_value(5); + shadow_opacity->set_double_default_value(80); + outline_width->set_double_default_value(2); + + outline_enable(false); + shadow_enable(false); + + connect(text_val, SIGNAL(changed()), this, SLOT(field_changed())); + connect(size_val, SIGNAL(changed()), this, SLOT(field_changed())); + connect(set_color_button, SIGNAL(changed()), this, SLOT(field_changed())); + connect(set_font_combobox, SIGNAL(changed()), this, SLOT(field_changed())); + connect(halign_field, SIGNAL(changed()), this, SLOT(field_changed())); + connect(valign_field, SIGNAL(changed()), this, SLOT(field_changed())); + connect(word_wrap_field, SIGNAL(changed()), this, SLOT(field_changed())); + connect(outline_bool, SIGNAL(changed()), this, SLOT(field_changed())); + connect(outline_color, SIGNAL(changed()), this, SLOT(field_changed())); + connect(outline_width, SIGNAL(changed()), this, SLOT(field_changed())); + connect(shadow_bool, SIGNAL(changed()), this, SLOT(field_changed())); + connect(shadow_color, SIGNAL(changed()), this, SLOT(field_changed())); + connect(shadow_distance, SIGNAL(changed()), this, SLOT(field_changed())); + connect(shadow_softness, SIGNAL(changed()), this, SLOT(field_changed())); + connect(shadow_opacity, SIGNAL(changed()), this, SLOT(field_changed())); + + connect(shadow_bool, SIGNAL(toggled(bool)), this, SLOT(shadow_enable(bool))); + connect(outline_bool, SIGNAL(toggled(bool)), this, SLOT(outline_enable(bool))); +} + +void TextEffect::shadow_enable(bool e) { + shadow_color->set_enabled(e); + shadow_distance->set_enabled(e); + shadow_softness->set_enabled(e); + shadow_opacity->set_enabled(e); +} + +void TextEffect::outline_enable(bool e) { + outline_color->set_enabled(e); + outline_width->set_enabled(e); +} + +QImage blurred(const QImage& image, const QRect& rect, int radius, bool alphaOnly = false) +{ + int tab[] = { 14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2 }; + int alpha = (radius < 1) ? 16 : (radius > 17) ? 1 : tab[radius-1]; + + QImage result = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); + int r1 = rect.top(); + int r2 = rect.bottom(); + int c1 = rect.left(); + int c2 = rect.right(); + + int bpl = result.bytesPerLine(); + int rgba[4]; + unsigned char* p; + + int i1 = 0; + int i2 = 3; + + if (alphaOnly) + i1 = i2 = (QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3); + + for (int col = c1; col <= c2; col++) { + p = result.scanLine(r1) + col * 4; + for (int i = i1; i <= i2; i++) + rgba[i] = p[i] << 4; + + p += bpl; + for (int j = r1; j < r2; j++, p += bpl) + for (int i = i1; i <= i2; i++) + p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; + } + + for (int row = r1; row <= r2; row++) { + p = result.scanLine(row) + c1 * 4; + for (int i = i1; i <= i2; i++) + rgba[i] = p[i] << 4; + + p += 4; + for (int j = c1; j < c2; j++, p += 4) + for (int i = i1; i <= i2; i++) + p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; + } + + for (int col = c1; col <= c2; col++) { + p = result.scanLine(r2) + col * 4; + for (int i = i1; i <= i2; i++) + rgba[i] = p[i] << 4; + + p -= bpl; + for (int j = r1; j < r2; j++, p -= bpl) + for (int i = i1; i <= i2; i++) + p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; + } + + for (int row = r1; row <= r2; row++) { + p = result.scanLine(row) + c2 * 4; + for (int i = i1; i <= i2; i++) + rgba[i] = p[i] << 4; + + p -= 4; + for (int j = c1; j < c2; j++, p -= 4) + for (int i = i1; i <= i2; i++) + p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; + } + + return result; +} + +void blurred2(QImage& result, const QRect& rect, int radius, bool alphaOnly = false) { + int tab[] = { 14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2 }; + int alpha = (radius < 1) ? 16 : (radius > 17) ? 1 : tab[radius-1]; + + int r1 = rect.top(); + int r2 = rect.bottom(); + int c1 = rect.left(); + int c2 = rect.right(); + + int bpl = result.bytesPerLine(); + int rgba[4]; + unsigned char* p; + + int i1 = 0; + int i2 = 3; + + if (alphaOnly) + i1 = i2 = (QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3); + + for (int col = c1; col <= c2; col++) { + p = result.scanLine(r1) + col * 4; + for (int i = i1; i <= i2; i++) + rgba[i] = p[i] << 4; + + p += bpl; + for (int j = r1; j < r2; j++, p += bpl) + for (int i = i1; i <= i2; i++) + p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; + } + + for (int row = r1; row <= r2; row++) { + p = result.scanLine(row) + c1 * 4; + for (int i = i1; i <= i2; i++) + rgba[i] = p[i] << 4; + + p += 4; + for (int j = c1; j < c2; j++, p += 4) + for (int i = i1; i <= i2; i++) + p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; + } + + for (int col = c1; col <= c2; col++) { + p = result.scanLine(r2) + col * 4; + for (int i = i1; i <= i2; i++) + rgba[i] = p[i] << 4; + + p -= bpl; + for (int j = r1; j < r2; j++, p -= bpl) + for (int i = i1; i <= i2; i++) + p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; + } + + for (int row = r1; row <= r2; row++) { + p = result.scanLine(row) + c2 * 4; + for (int i = i1; i <= i2; i++) + rgba[i] = p[i] << 4; + + p -= 4; + for (int j = c1; j < c2; j++, p -= 4) + for (int i = i1; i <= i2; i++) + p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; + } +} + +void TextEffect::process_image(QImage& img) { + QPainter p(&img); + p.setRenderHint(QPainter::Antialiasing); + int width = img.width(); + int height = img.height(); + + // set font + font.setStyleHint(QFont::Helvetica, QFont::PreferAntialias); + font.setFamily(set_font_combobox->get_font_name()); + font.setPointSize(size_val->get_double_value()); + p.setFont(font); + QFontMetrics fm(font); + + QStringList lines = text_val->get_string_value().split('\n'); + + // word wrap function + if (word_wrap_field->get_bool_value()) { + for (int i=0;i width) { + int last_space_index = 0; + for (int j=0;j width) { + break; + } else { + last_space_index = j; + } + } + } + if (last_space_index > 0) { + lines.insert(i+1, s.mid(last_space_index + 1)); + lines[i] = s.left(last_space_index); + } + } + } + } + + QPainterPath path; + + int text_height = fm.height()*lines.size(); + + for (int i=0;iget_combo_data().toInt()) { + case Qt::AlignLeft: text_x = 0; break; + case Qt::AlignHCenter: text_x = (width/2) - (fm.width(lines.at(i))/2); break; + case Qt::AlignRight: text_x = width - fm.width(lines.at(i)); break; + case Qt::AlignJustify: + // add spaces until the string is too big + text_x = 0; + while (fm.width(lines.at(i) < width)) { + bool space = false; + QString spaced(lines.at(i)); + for (int i=0;i width || !space) { + break; + } else { + lines[i] = spaced; + } + } + break; + } + + switch (valign_field->get_combo_data().toInt()) { + case Qt::AlignTop: text_y = (fm.height()*i)+fm.ascent(); break; + case Qt::AlignVCenter: text_y = ((height/2) - (text_height/2) - fm.descent()) + (fm.height()*(i+1)); break; + case Qt::AlignBottom: text_y = (height - text_height - fm.descent()) + (fm.height()*(i+1)); break; + } + + path.addText(text_x, text_y, font, lines.at(i)); + } + + p.setPen(Qt::NoPen); + + // draw shadow + if (shadow_bool->get_bool_value()) { + QImage shadow(width, height, QImage::Format_ARGB32_Premultiplied); + shadow.fill(Qt::transparent); + QPainter spaint(&shadow); + + int shadow_offset = shadow_distance->get_double_value(); + QColor col = shadow_color->get_color_value(); + col.setAlphaF(shadow_opacity->get_double_value()*0.01); + spaint.setBrush(col); + spaint.drawPath(path); + + blurred2(shadow, shadow.rect(), shadow_softness->get_double_value(), false); + p.drawImage(shadow_offset, shadow_offset, shadow); + + spaint.end(); + } + + // draw outline + int outline_width_val = outline_width->get_double_value(); + if (outline_bool->get_bool_value() && outline_width_val > 0) { + QPen outline(outline_color->get_color_value()); + outline.setWidth(outline_width_val); + p.setPen(outline); + } + + // draw "master" text + p.setBrush(set_color_button->get_color_value()); + p.drawPath(path); +} + +Effect* TextEffect::copy(Clip* c) { + TextEffect* e = new TextEffect(c); + e->text_val->set_string_value(text_val->get_string_value()); + e->size_val->set_double_value(size_val->get_double_value()); + e->set_color_button->set_color_value(set_color_button->get_color_value()); + e->set_font_combobox->set_font_name(set_font_combobox->get_font_name()); + e->halign_field->set_combo_index(halign_field->get_combo_index()); + e->valign_field->set_combo_index(valign_field->get_combo_index()); + return e; +} diff --git a/effects/video/texteffect.h b/effects/video/texteffect.h new file mode 100644 index 000000000..0574f2e8d --- /dev/null +++ b/effects/video/texteffect.h @@ -0,0 +1,39 @@ +#ifndef TEXTEFFECT_H +#define TEXTEFFECT_H + +#include "../effect.h" + +#include + +class TextEffect : public Effect { + Q_OBJECT +public: + TextEffect(Clip* c); + void process_image(QImage& img); + Effect* copy(Clip* c); + + EffectField* text_val; + EffectField* size_val; + EffectField* set_color_button; + EffectField* set_font_combobox; + EffectField* halign_field; + EffectField* valign_field; + EffectField* word_wrap_field; + + EffectField* outline_bool; + EffectField* outline_width; + EffectField* outline_color; + + EffectField* shadow_bool; + EffectField* shadow_distance; + EffectField* shadow_color; + EffectField* shadow_softness; + EffectField* shadow_opacity; +private slots: + void outline_enable(bool); + void shadow_enable(bool); +private: + QFont font; +}; + +#endif // TEXTEFFECT_H diff --git a/effects/transformeffect.cpp b/effects/video/transformeffect.cpp similarity index 95% rename from effects/transformeffect.cpp rename to effects/video/transformeffect.cpp index 93d61f03e..1e4b50342 100644 --- a/effects/transformeffect.cpp +++ b/effects/video/transformeffect.cpp @@ -1,4 +1,4 @@ -#include "effects/effects.h" +#include "transformeffect.h" #include #include @@ -23,7 +23,7 @@ #define BLEND_MODE_OVERLAY 3 TransformEffect::TransformEffect(Clip* c) : Effect(c, EFFECT_TYPE_VIDEO, VIDEO_TRANSFORM_EFFECT) { - enable_pre_gl = true; + enable_opengl = true; EffectRow* position_row = add_row("Position:"); position_x = position_row->add_field(EFFECT_FIELD_DOUBLE); // position X @@ -136,7 +136,7 @@ void TransformEffect::toggle_uniform_scale(bool enabled) { scale_y->set_enabled(!enabled); } -void TransformEffect::process_gl(int* anchor_x, int* anchor_y) { +void TransformEffect::process_gl(QOpenGLShaderProgram&, int* anchor_x, int* anchor_y) { // position glTranslatef(position_x->get_double_value()-(parent_clip->sequence->width/2), position_y->get_double_value()-(parent_clip->sequence->height/2), 0); diff --git a/effects/video/transformeffect.h b/effects/video/transformeffect.h new file mode 100644 index 000000000..7f8df6550 --- /dev/null +++ b/effects/video/transformeffect.h @@ -0,0 +1,31 @@ +#ifndef TRANSFORMEFFECT_H +#define TRANSFORMEFFECT_H + +#include "../effect.h" + +class TransformEffect : public Effect { + Q_OBJECT +public: + TransformEffect(Clip* c); + void refresh(); + void process_gl(QOpenGLShaderProgram& shader_prog, int* anchor_x, int* anchor_y); + Effect* copy(Clip *c); + + EffectField* position_x; + EffectField* position_y; + EffectField* scale_x; + EffectField* scale_y; + EffectField* uniform_scale_field; + EffectField* rotation; + EffectField* anchor_x_box; + EffectField* anchor_y_box; + EffectField* opacity; + EffectField* blend_mode_box; +public slots: + void toggle_uniform_scale(bool enabled); +private: + int default_anchor_x; + int default_anchor_y; +}; + +#endif // TRANSFORMEFFECT_H diff --git a/mainwindow.cpp b/mainwindow.cpp index ba868978b..9e01514f6 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -206,6 +206,8 @@ void MainWindow::on_actionZoom_In_triggered() { if (panel_timeline->focused()) { panel_timeline->set_zoom(true); + } else if (panel_effect_controls->keyframe_focus()) { + panel_effect_controls->set_zoom(true); } } @@ -213,6 +215,8 @@ void MainWindow::on_actionZoom_out_triggered() { if (panel_timeline->focused()) { panel_timeline->set_zoom(false); + } else if (panel_effect_controls->keyframe_focus()) { + panel_effect_controls->set_zoom(false); } } diff --git a/olive.pro b/olive.pro index 4478656a9..2120a858f 100644 --- a/olive.pro +++ b/olive.pro @@ -44,11 +44,7 @@ SOURCES += \ ui/viewercontainer.cpp \ dialogs/exportdialog.cpp \ ui/collapsiblewidget.cpp \ - effects/transformeffect.cpp \ panels/panels.cpp \ - effects/volumeeffect.cpp \ - effects/paneffect.cpp \ - effects/effects.cpp \ playback/cacher.cpp \ io/exportthread.cpp \ ui/timelineheader.cpp \ @@ -60,17 +56,21 @@ SOURCES += \ ui/audiomonitor.cpp \ project/undo.cpp \ ui/scrollarea.cpp \ - effects/shakeeffect.cpp \ - effects/texteffect.cpp \ ui/comboboxex.cpp \ ui/colorbutton.cpp \ - effects/solideffect.cpp \ dialogs/replaceclipmediadialog.cpp \ - effects/inverteffect.cpp \ effects/linearfadetransition.cpp \ ui/fontcombobox.cpp \ ui/checkboxex.cpp \ - effects/effect.cpp + effects/effect.cpp \ + effects/video/transformeffect.cpp \ + effects/audio/volumeeffect.cpp \ + effects/audio/paneffect.cpp \ + effects/video/texteffect.cpp \ + effects/video/solideffect.cpp \ + effects/video/shakeeffect.cpp \ + effects/video/inverteffect.cpp \ + ui/keyframeview.cpp HEADERS += \ mainwindow.h \ @@ -86,7 +86,6 @@ HEADERS += \ project/clip.h \ playback/playback.h \ playback/audio.h \ - effects/effects.h \ io/config.h \ dialogs/newsequencedialog.h \ ui/viewerwidget.h \ @@ -110,7 +109,15 @@ HEADERS += \ dialogs/replaceclipmediadialog.h \ ui/fontcombobox.h \ ui/checkboxex.h \ - effects/effect.h + effects/effect.h \ + effects/video/transformeffect.h \ + effects/video/solideffect.h \ + effects/video/shakeeffect.h \ + effects/video/texteffect.h \ + effects/video/inverteffect.h \ + effects/audio/volumeeffect.h \ + effects/audio/paneffect.h \ + ui/keyframeview.h FORMS += \ mainwindow.ui \ diff --git a/panels/effectcontrols.cpp b/panels/effectcontrols.cpp index 0e59b6201..72264ddce 100644 --- a/panels/effectcontrols.cpp +++ b/panels/effectcontrols.cpp @@ -6,7 +6,7 @@ #include #include "panels/panels.h" -#include "effects/effects.h" +#include "effects/effect.h" #include "effects/transition.h" #include "project/clip.h" #include "effects/effect.h" @@ -20,19 +20,33 @@ EffectControls::EffectControls(QWidget *parent) : QDockWidget(parent), - ui(new Ui::EffectControls) + ui(new Ui::EffectControls), + zoom(1) { ui->setupUi(this); init_effects(); init_transitions(); clear_effects(false); + ui->headers->snapping = false; } -EffectControls::~EffectControls() -{ +EffectControls::~EffectControls() { delete ui; } +bool EffectControls::keyframe_focus() { + return ui->headers->hasFocus() || ui->keyframeView->hasFocus(); +} + +void EffectControls::set_zoom(bool in) { + if (in) { + zoom *= 2; + } else { + zoom *= 0.5; + } + update_keyframes(); +} + void EffectControls::menu_select(QAction* q) { TimelineAction* ta = new TimelineAction(); for (int i=0;iredraw_all_clips(true); } else { reload_clips(); + panel_viewer->viewer_widget->update(); } } +void EffectControls::update_keyframes() { + if (ui->headers->isVisible()) ui->headers->update_header(zoom); + ui->keyframeView->update(); +} + void EffectControls::show_effect_menu(bool video, bool transitions) { video_menu = video; transition_menu = transitions; @@ -109,24 +129,28 @@ void EffectControls::show_effect_menu(bool video, bool transitions) { void EffectControls::clear_effects(bool clear_cache) { // clear existing clips + ui->keyframeView->effects.clear(); QVBoxLayout* video_layout = static_cast(ui->video_effect_area->layout()); QVBoxLayout* audio_layout = static_cast(ui->audio_effect_area->layout()); QLayoutItem* item; while ((item = video_layout->takeAt(0))) { item->widget()->setParent(NULL); - disconnect(static_cast(item->widget()), SIGNAL(deselect_others(QWidget*)), this, SLOT(deselect_all_effects(QWidget*))); + disconnect(static_cast(item->widget()), SIGNAL(deselect_others(QWidget*)), this, SLOT(deselect_all_effects(QWidget*))); + disconnect(static_cast(item->widget()), SIGNAL(visibleChanged()), ui->keyframeView, SLOT(reload())); } while ((item = audio_layout->takeAt(0))) { item->widget()->setParent(NULL); disconnect(static_cast(item->widget()), SIGNAL(deselect_others(QWidget*)), this, SLOT(deselect_all_effects(QWidget*))); + disconnect(static_cast(item->widget()), SIGNAL(visibleChanged()), ui->keyframeView, SLOT(reload())); } ui->vcontainer->setVisible(false); ui->acontainer->setVisible(false); + ui->headers->setVisible(false); + ui->keyframeView->setEnabled(false); if (clear_cache) selected_clips.clear(); } void EffectControls::deselect_all_effects(QWidget* sender) { - QVector delete_effects; for (int i=0;iget_clip(selected_clips.at(i)); for (int j=0;jeffects.size();j++) { @@ -138,26 +162,37 @@ void EffectControls::deselect_all_effects(QWidget* sender) { } void EffectControls::load_effects() { - // load in new clips + // load in new clips + long effects_in = LONG_MAX; + long effects_out = 0; for (int i=0;iget_clip(selected_clips.at(i)); - if (c->track < 0) { - ui->vcontainer->setVisible(true); - } else { - ui->acontainer->setVisible(true); - } + Clip* c = sequence->get_clip(selected_clips.at(i)); + effects_in = qMin(effects_in, c->timeline_in); + effects_out = qMax(effects_out, c->timeline_out); for (int j=0;jeffects.size();j++) { - CollapsibleWidget* container = c->effects.at(j)->container; + Effect* e = c->effects.at(j); + ui->keyframeView->effects.append(e); + CollapsibleWidget* container = e->container; if (c->track < 0) { static_cast(ui->video_effect_area->layout())->addWidget(container); ui->vcontainer->setVisible(true); } else { static_cast(ui->audio_effect_area->layout())->addWidget(container); ui->acontainer->setVisible(true); - } + } connect(container, SIGNAL(deselect_others(QWidget*)), this, SLOT(deselect_all_effects(QWidget*))); + connect(container, SIGNAL(visibleChanged()), ui->keyframeView, SLOT(reload())); } } + if (selected_clips.size() > 0) { + ui->keyframeView->setEnabled(true); + ui->keyframeView->visible_in = effects_in; + ui->keyframeView->visible_out = effects_out; + ui->keyframeView->update(); + + ui->headers->set_visible_in(effects_in); + ui->headers->setVisible(true); + } } void EffectControls::delete_effects() { diff --git a/panels/effectcontrols.h b/panels/effectcontrols.h index 50cab070e..ccb94d15d 100644 --- a/panels/effectcontrols.h +++ b/panels/effectcontrols.h @@ -7,6 +7,7 @@ struct Clip; class QMenu; class Effect; +class TimelineHeader; namespace Ui { class EffectControls; @@ -24,7 +25,11 @@ public: void delete_effects(); bool is_focused(); void reload_clips(); + void update_keyframes(); + void set_zoom(bool in); + bool keyframe_focus(); + double zoom; private slots: void menu_select(QAction* q); void on_add_video_effect_button_clicked(); @@ -36,10 +41,11 @@ private slots: void on_add_audio_transition_button_clicked(); private: - Ui::EffectControls *ui; + Ui::EffectControls *ui; QVector selected_clips; void show_effect_menu(bool video, bool transitions); void load_effects(); + void load_keyframes(); bool video_menu; bool transition_menu; diff --git a/panels/effectcontrols.ui b/panels/effectcontrols.ui index a5c46f64e..f18fdbbbf 100644 --- a/panels/effectcontrols.ui +++ b/panels/effectcontrols.ui @@ -23,7 +23,7 @@ Effect Controls - + 0 @@ -53,7 +53,7 @@ 489 - + 0 @@ -70,307 +70,396 @@ 0 - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - #veHeader { - background: rgba(0, 0, 0, 0.25); -} - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Add Video Effect - - - - - - - :/icons/add-effect.png - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 9 - - - - VIDEO EFFECTS - - - Qt::AlignCenter - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Add Video Transition - - - - - - - :/icons/add-transition.png - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - #aeHeader { - background: rgba(0, 0, 0, 0.25); -} - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Add Audio Effect - - - - - - - :/icons/add-effect.png - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 9 - - - - AUDIO EFFECTS - - - Qt::AlignCenter - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Add Audio Transition - - - - - - - :/icons/add-transition.png - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - + - Qt::Vertical + Qt::Horizontal - - - 20 - 40 - + + false - + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + #veHeader { + background: rgba(0, 0, 0, 0.25); +} + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Add Video Effect + + + + + + + :/icons/add-effect.png + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 9 + + + + VIDEO EFFECTS + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Add Video Transition + + + + + + + :/icons/add-transition.png + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + #aeHeader { + background: rgba(0, 0, 0, 0.25); +} + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Add Audio Effect + + + + + + + :/icons/add-effect.png + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 9 + + + + AUDIO EFFECTS + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Add Audio Transition + + + + + + + :/icons/add-transition.png + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + 1 + 0 + + + + true + + + + + 0 + 0 + 388 + 487 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 15 + + + + Qt::ClickFocus + + + + + + + + + + @@ -379,6 +468,20 @@ + + + KeyframeView + QWidget +
ui/keyframeview.h
+ 1 +
+ + TimelineHeader + QWidget +
ui/timelineheader.h
+ 1 +
+
diff --git a/panels/project.cpp b/panels/project.cpp index a7a9dc44a..19aaf94c0 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -6,7 +6,7 @@ #include "panels/timeline.h" #include "panels/viewer.h" #include "playback/playback.h" -#include "effects/effects.h" +#include "effects/effect.h" #include "panels/timeline.h" #include "project/sequence.h" #include "effects/effect.h" diff --git a/panels/timeline.cpp b/panels/timeline.cpp index 96f3e9018..7a7baef95 100644 --- a/panels/timeline.cpp +++ b/panels/timeline.cpp @@ -254,7 +254,7 @@ void Timeline::update_sequence() { } int Timeline::get_snap_range() { - return getFrameFromScreenPoint(10); + return getTimelineFrameFromScreenPoint(10); } bool Timeline::focused() { @@ -265,9 +265,12 @@ void Timeline::repaint_timeline() { if (playing) { playhead = round(playhead_start + ((QDateTime::currentMSecsSinceEpoch()-start_msecs) * 0.001 * sequence->frame_rate)); } - ui->headers->update(); + + ui->headers->update_header(zoom); ui->video_area->update(); ui->audio_area->update(); + panel_effect_controls->update_keyframes(); + if (last_frame != playhead) { panel_viewer->viewer_widget->update(); ui->audio_monitor->update(); @@ -282,9 +285,10 @@ void Timeline::redraw_all_clips(bool changed) { panel_viewer->viewer_widget->update(); } + ui->headers->update_header(zoom); ui->video_area->redraw_clips(); ui->audio_area->redraw_clips(); - ui->headers->update(); + panel_effect_controls->update_keyframes(); panel_viewer->update_end_timecode(); } @@ -396,25 +400,13 @@ void Timeline::set_zoom(bool in) { ui->timeline_area->horizontalScrollBar()->setValue( lerp( ui->timeline_area->horizontalScrollBar()->value(), - getScreenPointFromFrame(playhead) - (ui->timeline_area->width()/2), + getTimelineScreenPointFromFrame(playhead) - (ui->timeline_area->width()/2), 0.99 ) ); redraw_all_clips(false); } -/*void Timeline::ripple(TimelineAction* ta, long ripple_point, long ripple_length) { - // ripple the selections - for (int i=0;i= ripple_point) { - s.in += ripple_length; - s.out += ripple_length; - } - } -}*/ - void Timeline::decheck_tool_buttons(QObject* sender) { for (int i=0;isetChecked(tool_buttons.at(i) == sender); @@ -1083,16 +1075,24 @@ void Timeline::deselect() { repaint_timeline(); } -long Timeline::getFrameFromScreenPoint(int x) { - long f = round((float) x / zoom); - if (f < 0) { - return 0; - } - return f; +long getFrameFromScreenPoint(double zoom, int x) { + long f = round((float) x / zoom); + if (f < 0) { + return 0; + } + return f; } -int Timeline::getScreenPointFromFrame(long frame) { - return (int) round(frame*zoom); +int getScreenPointFromFrame(double zoom, long frame) { + return (int) round(frame*zoom); +} + +long Timeline::getTimelineFrameFromScreenPoint(int x) { + return getFrameFromScreenPoint(zoom, x); +} + +int Timeline::getTimelineScreenPointFromFrame(long frame) { + return getScreenPointFromFrame(zoom, frame); } void Timeline::on_toolArrowButton_clicked() { diff --git a/panels/timeline.h b/panels/timeline.h index f5e57729f..4152aaab2 100644 --- a/panels/timeline.h +++ b/panels/timeline.h @@ -22,6 +22,8 @@ struct MediaStream; int lerp(int a, int b, double t); long refactor_frame_number(long framenumber, double source_frame_rate, double target_frame_rate); +int getScreenPointFromFrame(double zoom, long frame); +long getFrameFromScreenPoint(double zoom, int x); struct Ghost { int clip; @@ -100,8 +102,8 @@ public: void delete_in_out(bool ripple); int get_snap_range(); - int getScreenPointFromFrame(long frame); - long getFrameFromScreenPoint(int x); + int getTimelineScreenPointFromFrame(long frame); + long getTimelineFrameFromScreenPoint(int x); bool snap_to_point(long point, long* l); void snap_to_clip(long* l, bool playhead_inclusive); @@ -177,9 +179,6 @@ public: // importing bool importing; - // ripple -// void ripple(TimelineAction* ta, long ripple_point, long ripple_length); - Ui::Timeline *ui; public slots: void repaint_timeline(); diff --git a/panels/timeline.ui b/panels/timeline.ui index 126e6b0b7..3e301d5db 100644 --- a/panels/timeline.ui +++ b/panels/timeline.ui @@ -328,6 +328,9 @@ true + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + @@ -379,6 +382,9 @@ true + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + diff --git a/playback/cacher.cpp b/playback/cacher.cpp index 20e4584a0..0422ab0f2 100644 --- a/playback/cacher.cpp +++ b/playback/cacher.cpp @@ -5,7 +5,7 @@ #include "io/media.h" #include "playback/audio.h" #include "playback/playback.h" -#include "effects/effects.h" +#include "effects/effect.h" #include "panels/timeline.h" #include "panels/project.h" #include "effects/transition.h" @@ -187,6 +187,20 @@ void cache_video_worker(Clip* c, long playhead, ClipCache* cache) { bool error = false; int i = 0; + + /* swscale solution - might be faster? but AVFilter solution is more "future-proof" + if (!c->reached_end) { + while (i < c->cache_size) { + time = QDateTime::currentMSecsSinceEpoch(); + retrieve_next_frame_raw_data(c, cache->frames[i]); + qDebug() << (QDateTime::currentMSecsSinceEpoch() - time); + if (c->reached_end) break; + i++; + } + } + */ + + /* AVFilter solution - not definitely slower, may allow for cool things later */ if (!c->reached_end) { while (i < c->cache_size) { av_frame_unref(cache->frames[i]); @@ -195,30 +209,6 @@ void cache_video_worker(Clip* c, long playhead, ClipCache* cache) { if (ret < 0) { if (ret == AVERROR(EAGAIN)) { - if (c->filter_graph != NULL) { - avfilter_graph_free(&c->filter_graph); - } - c->filter_graph = avfilter_graph_alloc(); - char args[512]; - snprintf(args, sizeof(args), - "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", - c->stream->codecpar->width, c->stream->codecpar->height, c->stream->codecpar->format, - c->stream->time_base.num, c->stream->time_base.den, - c->stream->codecpar->sample_aspect_ratio.num, c->stream->codecpar->sample_aspect_ratio.den); - avfilter_graph_create_filter(&c->buffersrc_ctx, avfilter_get_by_name("buffer"), "in", args, NULL, c->filter_graph); - avfilter_graph_create_filter(&c->buffersink_ctx, avfilter_get_by_name("buffersink"), "out", NULL, NULL, c->filter_graph); - enum AVPixelFormat pix_fmts[] = { static_cast(dest_format), AV_PIX_FMT_NONE }; - av_opt_set_int_list(c->buffersink_ctx, "pix_fmts", pix_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN); - - AVFilterContext* gblur_ctx; -// avfilter_graph_create_filter(&gblur_ctx, avfilter_get_by_name("gblur"), "ol_gblur", "sigma=20:steps=1", NULL, c->filter_graph); - avfilter_graph_create_filter(&gblur_ctx, avfilter_get_by_name("negate"), "ol_gblur", NULL, NULL, c->filter_graph); -// avfilter_graph_create_filter(&gblur_ctx, avfilter_get_by_name("null"), "ol_gblur", NULL, NULL, c->filter_graph); - avfilter_link(c->buffersrc_ctx, 0, gblur_ctx, 0); - avfilter_link(gblur_ctx, 0, c->buffersink_ctx, 0); - - avfilter_graph_config(c->filter_graph, NULL); - ret = retrieve_next_frame(c, c->frame); if (ret >= 0) { if ((ret = av_buffersrc_add_frame_flags(c->buffersrc_ctx, c->frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) { @@ -246,7 +236,7 @@ void cache_video_worker(Clip* c, long playhead, ClipCache* cache) { i++; } } - } + } cache->write_count = i; if (!error) { @@ -262,13 +252,7 @@ void cache_video_worker(Clip* c, long playhead, ClipCache* cache) { void reset_cache(Clip* c, long target_frame) { // if we seek to a whole other place in the timeline, we'll need to reset the cache with new values MediaStream* ms = static_cast(c->media)->get_stream_from_file_index(c->media_stream); - if (ms->infinite_length) { - // if this clip is a still image, we only need one frame - if (!c->cache_A.written) { - retrieve_next_frame_raw_data(c, c->sws_frame); - c->cache_A.written = true; - } - } else { + if (!ms->infinite_length) { // flush ffmpeg codecs avcodec_flush_buffers(c->codecCtx); @@ -360,10 +344,10 @@ void open_clip_worker(Clip* clip) { if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { // set up swscale context - primarily used for colorspace conversion // as "scaling" is actually done by OpenGL - int dstW = ceil(clip->stream->codecpar->width/2)*2; - int dstH = ceil(clip->stream->codecpar->height/2)*2; +// int dstW = ceil(clip->stream->codecpar->width/2)*2; +// int dstH = ceil(clip->stream->codecpar->height/2)*2; - clip->sws_ctx = sws_getContext( + /*clip->sws_ctx = sws_getContext( clip->stream->codecpar->width, clip->stream->codecpar->height, static_cast(clip->stream->codecpar->format), @@ -374,25 +358,24 @@ void open_clip_worker(Clip* clip) { NULL, NULL, NULL - ); + );*/ // create memory cache for video - if (ms->infinite_length) { - clip->cache_size = 1; - } else { - clip->cache_size = ceil(av_q2d(av_guess_frame_rate(clip->formatCtx, clip->stream, NULL))/4); // cache is half a second in total + clip->cache_size = (ms->infinite_length) ? 1 : ceil(av_q2d(av_guess_frame_rate(clip->formatCtx, clip->stream, NULL))/4); // cache is half a second in total - // infinite length doesn't need cache B - clip->cache_A.frames = new AVFrame* [clip->cache_size]; - clip->cache_B.frames = new AVFrame* [clip->cache_size]; - for (int i=0;icache_size;i++) { - clip->cache_A.frames[i] = av_frame_alloc(); - clip->cache_B.frames[i] = av_frame_alloc(); - } + // infinite length doesn't need cache B + clip->cache_A.frames = new AVFrame* [clip->cache_size]; + clip->cache_B.frames = new AVFrame* [clip->cache_size]; + for (int i=0;icache_size;i++) { + clip->cache_A.frames[i] = av_frame_alloc(); + clip->cache_B.frames[i] = av_frame_alloc(); } + clip->comp_frame_size = clip->stream->codecpar->width * clip->stream->codecpar->height * 4; + clip->comp_frame = new uchar[clip->comp_frame_size]; + // alloc temporary scale frame - clip->sws_frame = av_frame_alloc(); + /*clip->sws_frame = av_frame_alloc(); av_frame_make_writable(clip->sws_frame); clip->sws_frame->width = dstW; clip->sws_frame->height = dstH; @@ -400,7 +383,33 @@ void open_clip_worker(Clip* clip) { if (av_frame_get_buffer(clip->sws_frame, 0)) { qDebug() << "[ERROR] Could not allocate buffer for sws_frame"; } - clip->sws_frame->linesize[0] = clip->stream->codecpar->width*4; + clip->sws_frame->linesize[0] = clip->stream->codecpar->width*4;*/ + + clip->filter_graph = avfilter_graph_alloc(); + char args[512]; + snprintf(args, sizeof(args), + "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", + clip->stream->codecpar->width, clip->stream->codecpar->height, clip->stream->codecpar->format, + clip->stream->time_base.num, clip->stream->time_base.den, + clip->stream->codecpar->sample_aspect_ratio.num, clip->stream->codecpar->sample_aspect_ratio.den); + avfilter_graph_create_filter(&clip->buffersrc_ctx, avfilter_get_by_name("buffer"), "in", args, NULL, clip->filter_graph); + avfilter_graph_create_filter(&clip->buffersink_ctx, avfilter_get_by_name("buffersink"), "out", NULL, NULL, clip->filter_graph); + enum AVPixelFormat pix_fmts[] = { static_cast(dest_format), AV_PIX_FMT_NONE }; + av_opt_set_int_list(clip->buffersink_ctx, "pix_fmts", pix_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN); + + avfilter_link(clip->buffersrc_ctx, 0, clip->buffersink_ctx, 0); + + avfilter_graph_config(clip->filter_graph, NULL); + + /* old AVFilter code, looks like it'll be unusable + AVFilterContext* gblur_ctx; + avfilter_graph_create_filter(&gblur_ctx, avfilter_get_by_name("gblur"), "ol_gblur", "sigma=20:steps=1", NULL, c->filter_graph); + avfilter_graph_create_filter(&gblur_ctx, avfilter_get_by_name("negate"), "ol_gblur", NULL, NULL, clip->filter_graph); + avfilter_graph_create_filter(&gblur_ctx, avfilter_get_by_name("null"), "ol_gblur", NULL, NULL, clip->filter_graph); + avfilter_link(clip->buffersrc_ctx, 0, gblur_ctx, 0); + avfilter_link(gblur_ctx, 0, clip->buffersink_ctx, 0); + avfilter_link(clip->buffersrc_ctx, 0, clip->buffersink_ctx, 0); + */ } else if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { // if FFmpeg can't pick up the channel layout (usually WAV), assume // based on channel count (doesn't support surround sound sources yet) @@ -468,10 +477,12 @@ void close_clip_worker(Clip* clip) { // closes ffmpeg file handle and frees any memory used for caching MediaStream* ms = static_cast(clip->media)->get_stream_from_file_index(clip->media_stream); if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - sws_freeContext(clip->sws_ctx); +// sws_freeContext(clip->sws_ctx); // TODO will eventually be in audio too - av_frame_free(&clip->sws_frame); +// av_frame_free(&clip->sws_frame); + avfilter_graph_free(&clip->filter_graph); + delete [] clip->comp_frame; } else if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { swr_free(&clip->swr_ctx); } diff --git a/playback/playback.cpp b/playback/playback.cpp index 7b895e988..ef08e1bb9 100644 --- a/playback/playback.cpp +++ b/playback/playback.cpp @@ -8,7 +8,7 @@ #include "panels/panels.h" #include "panels/timeline.h" #include "panels/viewer.h" -#include +#include "effects/effect.h" extern "C" { #include @@ -17,6 +17,7 @@ extern "C" { #include } +#include #include #include #include @@ -79,7 +80,7 @@ void get_clip_frame(Clip* c, long playhead) { // do we need to update the texture? MediaStream* ms = static_cast(c->media)->get_stream_from_file_index(c->media_stream); - if ((!ms->infinite_length && c->texture_frame != clip_time) || + if ((!ms->infinite_length/* && c->texture_frame != clip_time*/) || (ms->infinite_length && c->texture_frame == -1)) { AVFrame* current_frame = NULL; bool no_frame = false; @@ -88,16 +89,11 @@ void get_clip_frame(Clip* c, long playhead) { if (ms->infinite_length) { // if clip is a still frame, we only need one if (c->cache_A.written) { // retrieve cached frame - current_frame = c->sws_frame; - } else if (c->multithreaded) { - if (c->lock.tryLock()) { - // grab image (multi-threaded) - cache_clip(c, 0, false, false, true, NULL); - c->lock.unlock(); - } - } else { - // grab image (single-threaded) - reset_cache(c, playhead); + current_frame = c->cache_A.frames[0]; + } else if (c->lock.tryLock()) { + // grab image + cache_clip(c, 0, true, false, false, NULL); + c->lock.unlock(); } } else { // keeping a RAM cache improves performance, however it's detrimental when rendering @@ -169,7 +165,15 @@ void get_clip_frame(Clip* c, long playhead) { c->texture->allocateStorage(QOpenGLTexture::RGBA, QOpenGLTexture::UInt8); } - c->texture->setData(0, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, current_frame->data[0]); + memcpy(c->comp_frame, current_frame->data[0], c->comp_frame_size); + QImage img(c->comp_frame, current_frame->width, current_frame->height, QImage::Format_RGBA8888); + for (int i=0;ieffects.size();i++) { + if (c->effects.at(i)->enable_image) { + c->effects.at(i)->process_image(img); + } + } + + c->texture->setData(0, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, c->comp_frame); c->texture_frame = clip_time; } else if (!no_frame) { texture_failed = true; @@ -240,7 +244,7 @@ void retrieve_next_frame_raw_data(Clip* c, AVFrame* output) { int ret = retrieve_next_frame(c, c->frame); if (ret >= 0) { if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - sws_scale(c->sws_ctx, c->frame->data, c->frame->linesize, 0, c->stream->codecpar->height, output->data, output->linesize); +// sws_scale(c->sws_ctx, c->frame->data, c->frame->linesize, 0, c->stream->codecpar->height, output->data, output->linesize); // output->pts = c->frame->best_effort_timestamp; } else if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { output->pts = c->frame->pts; diff --git a/project/clip.cpp b/project/clip.cpp index 288fa30e3..0f64573fc 100644 --- a/project/clip.cpp +++ b/project/clip.cpp @@ -121,32 +121,6 @@ void Clip::refresh() { } } -void Clip::run_video_pre_effect_stack(long playhead, int* anchor_x, int* anchor_y) { - for (int j=0;jenable_pre_gl && effects.at(j)->is_enabled()) effects.at(j)->process_gl(anchor_x, anchor_y); - } - - if (opening_transition != NULL) { - int transition_progress = playhead - timeline_in; - if (transition_progress < opening_transition->length) { - opening_transition->process_transition((double)transition_progress/(double)opening_transition->length); - } - } - - if (closing_transition != NULL) { - int transition_progress = closing_transition->length - (playhead - timeline_in - getLength() + closing_transition->length); - if (transition_progress < closing_transition->length) { - closing_transition->process_transition((double)transition_progress/(double)closing_transition->length); - } - } -} - -void Clip::run_video_post_effect_stack() { - for (int j=0;jenable_post_gl && effects.at(j)->is_enabled()) effects.at(j)->post_gl(); - } -} - Clip::~Clip() { if (open) { close_clip(this); diff --git a/project/clip.h b/project/clip.h index 0566a7533..08724a1fc 100644 --- a/project/clip.h +++ b/project/clip.h @@ -44,9 +44,7 @@ struct Clip Clip* copy(Sequence* s); void reset_audio(); void reset(); - void refresh(); - void run_video_pre_effect_stack(long playhead, int *anchor_x, int *anchor_y); - void run_video_post_effect_stack(); + void refresh(); // timeline variables Sequence* sequence; @@ -80,8 +78,9 @@ struct Clip AVCodec* codec; AVCodecContext* codecCtx; AVPacket* pkt; - AVFrame* frame; - AVFrame* sws_frame; + AVFrame* frame; + uchar* comp_frame; + int comp_frame_size; // ffmpeg filters AVFilterGraph* filter_graph; @@ -105,7 +104,7 @@ struct Clip QMutex open_lock; // video playback variables - SwsContext* sws_ctx; + //SwsContext* sws_ctx; QOpenGLTexture* texture; long texture_frame; diff --git a/project/undo.cpp b/project/undo.cpp index d8757f32e..c1f96a846 100644 --- a/project/undo.cpp +++ b/project/undo.cpp @@ -12,7 +12,7 @@ #include "panels/effectcontrols.h" #include "playback/playback.h" #include "ui/sourcetable.h" -#include "effects/effects.h" +#include "effects/effect.h" #include "io/media.h" #include "playback/cacher.h" #include "effects/transition.h" diff --git a/ui/collapsiblewidget.cpp b/ui/collapsiblewidget.cpp index 0bfadf068..78c360614 100644 --- a/ui/collapsiblewidget.cpp +++ b/ui/collapsiblewidget.cpp @@ -29,7 +29,7 @@ CollapsibleWidget::CollapsibleWidget(QWidget* parent) : QWidget(parent) { enabled_check->setChecked(true); header = new QLabel(); collapse_button = new QPushButton("[-]"); - collapse_button->setStyleSheet("QPushButton { border: none; }"); + collapse_button->setStyleSheet("QPushButton { border: none; } QPushButton:hover { text-decoration: underline; }"); collapse_button->setMaximumWidth(25); setText(""); title_bar_layout->addWidget(collapse_button); @@ -61,6 +61,10 @@ bool CollapsibleWidget::is_focused() { return title_bar->hasFocus(); } +bool CollapsibleWidget::is_expanded() { + return contents->isVisible(); +} + void CollapsibleWidget::setContents(QWidget* c) { bool existing = (contents != NULL); contents = c; @@ -82,6 +86,7 @@ void CollapsibleWidget::on_enabled_change(bool b) { void CollapsibleWidget::on_visible_change() { contents->setVisible(!contents->isVisible()); collapse_button->setText(contents->isVisible() ? "[-]" : "[+]"); + emit visibleChanged(); } CollapsibleWidgetHeader::CollapsibleWidgetHeader(QWidget* parent) : QWidget(parent), selected(false) {} diff --git a/ui/collapsiblewidget.h b/ui/collapsiblewidget.h index 1478870b2..d21c38945 100644 --- a/ui/collapsiblewidget.h +++ b/ui/collapsiblewidget.h @@ -30,19 +30,21 @@ public: void setContents(QWidget* c); void setText(const QString &); bool is_focused(); + bool is_expanded(); CheckboxEx* enabled_check; bool selected; + QWidget* contents; + CollapsibleWidgetHeader* title_bar; private: QLabel* header; - CollapsibleWidgetHeader* title_bar; QVBoxLayout* layout; QPushButton* collapse_button; - QWidget* contents; QFrame* line; signals: void deselect_others(QWidget*); + void visibleChanged(); private slots: void on_enabled_change(bool b); diff --git a/ui/comboboxex.cpp b/ui/comboboxex.cpp index 0aa84914a..147dd27a3 100644 --- a/ui/comboboxex.cpp +++ b/ui/comboboxex.cpp @@ -1,6 +1,7 @@ #include "comboboxex.h" #include "project/undo.h" +#include "panels/project.h" #include #include @@ -8,32 +9,45 @@ class ComboBoxExCommand : public QUndoCommand { public: ComboBoxExCommand(ComboBoxEx* obj, int old_index, int new_index) : - combobox(obj), old_val(old_index), new_val(new_index), done(true) {} + combobox(obj), old_val(old_index), new_val(new_index), done(true), old_project_changed(project_changed) {} void undo() { combobox->setCurrentIndex(old_val); done = false; + project_changed = old_project_changed; } void redo() { if (!done) { combobox->setCurrentIndex(new_val); } + project_changed = true; } private: ComboBoxEx* combobox; int old_val; int new_val; bool done; + bool old_project_changed; }; ComboBoxEx::ComboBoxEx(QWidget *parent) : QComboBox(parent), index(0) { connect(this, SIGNAL(activated(int)), this, SLOT(index_changed(int))); } +void ComboBoxEx::setCurrentIndexEx(int i) { + index = i; + setCurrentIndex(i); +} + +void ComboBoxEx::setCurrentTextEx(const QString &text) { + setCurrentText(text); + index = currentIndex(); +} + void ComboBoxEx::index_changed(int i) { - if (index != i) { - undo_stack.push(new ComboBoxExCommand(this, index, i)); - index = i; - } + if (index != i) { + undo_stack.push(new ComboBoxExCommand(this, index, i)); + index = i; + } } void ComboBoxEx::wheelEvent(QWheelEvent* e) { diff --git a/ui/comboboxex.h b/ui/comboboxex.h index 1aae6b9c4..341acf264 100644 --- a/ui/comboboxex.h +++ b/ui/comboboxex.h @@ -8,10 +8,12 @@ class ComboBoxEx : public QComboBox { Q_OBJECT public: ComboBoxEx(QWidget* parent = 0); + void setCurrentIndexEx(int i); + void setCurrentTextEx(const QString &text); private slots: - void index_changed(int); + void index_changed(int); private: - int index; + int index; void wheelEvent(QWheelEvent* e); }; diff --git a/ui/keyframeview.cpp b/ui/keyframeview.cpp new file mode 100644 index 000000000..7a23e3cb5 --- /dev/null +++ b/ui/keyframeview.cpp @@ -0,0 +1,82 @@ +#include "keyframeview.h" + +#include "effects/effect.h" +#include "ui/collapsiblewidget.h" +#include "panels/panels.h" +#include "panels/effectcontrols.h" +#include "project/clip.h" +#include "panels/timeline.h" + +#include +#include + +#define KEYFRAME_SIZE 5 +#define KEYFRAME_POINT_COUNT 4 + +KeyframeView::KeyframeView(QWidget *parent) : QWidget(parent), mouseover(false), visible_in(0), visible_out(0) { + setFocusPolicy(Qt::ClickFocus); + setMouseTracking(true); + reload(); +} + +void KeyframeView::reload() { + enable_reload = true; + update(); +} + +void KeyframeView::paintEvent(QPaintEvent*) { + QPainter p(this); + + setMinimumWidth(getScreenPointFromFrame(panel_effect_controls->zoom, visible_out - visible_in)); + + rowY.clear(); + for (int i=0;icontainer->is_expanded()) { + for (int j=0;jrow_count();j++) { + QLabel* label = e->row(j)->label; + QWidget* contents = e->container->contents; + long frame = 20; + int keyframe_y = label->y() + (label->height()>>1) + mapFrom(panel_effect_controls, contents->mapTo(panel_effect_controls, contents->pos())).y() - e->container->title_bar->height(); + draw_keyframe(p, getScreenPointFromFrame(panel_effect_controls->zoom, frame), keyframe_y, false); + rowY.append(keyframe_y); + } + } + } + + if (rowY.size() > 0) { + int playhead_x = getScreenPointFromFrame(panel_effect_controls->zoom, panel_timeline->playhead-visible_in); + p.setPen(Qt::red); + p.drawLine(playhead_x, 0, playhead_x, height()); + } + + if (mouseover && mouseover_row < rowY.size()) { + draw_keyframe(p, getScreenPointFromFrame(panel_effect_controls->zoom, mouseover_frame) - visible_in, rowY.at(mouseover_row), true); + } +} + +void KeyframeView::draw_keyframe(QPainter &p, int x, int y, bool semiTransparent) { + QPoint points[KEYFRAME_POINT_COUNT] = {QPoint(x-KEYFRAME_SIZE, y), QPoint(x, y-KEYFRAME_SIZE), QPoint(x+KEYFRAME_SIZE, y), QPoint(x, y+KEYFRAME_SIZE)}; + int alpha = (semiTransparent) ? 128 : 255; + p.setPen(QColor(0, 0, 0, alpha)); + p.setBrush(QColor(160, 160, 160, alpha)); + p.drawPolygon(points, KEYFRAME_POINT_COUNT); +} + +void KeyframeView::mouseMoveEvent(QMouseEvent* event) { + unsetCursor(); + bool new_mo = false; + for (int i=0;iy() > rowY.at(i)-KEYFRAME_SIZE-KEYFRAME_SIZE && event->y() < rowY.at(i)+KEYFRAME_SIZE+KEYFRAME_SIZE) { + setCursor(Qt::CrossCursor); + mouseover_frame = getFrameFromScreenPoint(panel_effect_controls->zoom, event->x()) + visible_in; + mouseover_row = i; + new_mo = true; + break; + } + } + if (new_mo || (new_mo != mouseover)) { + update(); + } + mouseover = new_mo; +} diff --git a/ui/keyframeview.h b/ui/keyframeview.h new file mode 100644 index 000000000..986b5faf4 --- /dev/null +++ b/ui/keyframeview.h @@ -0,0 +1,35 @@ +#ifndef KEYFRAMEVIEW_H +#define KEYFRAMEVIEW_H + +#include +#include + +struct Clip; +class Effect; +class EffectRow; + +class KeyframeView : public QWidget { + Q_OBJECT +public: + KeyframeView(QWidget* parent = 0); + QVector effects; + + long visible_in; + long visible_out; +public slots: + void reload(); +private: + QVector rowY; + QVector rows; + void mouseMoveEvent(QMouseEvent* event); + /*void mousePressEvent(QMouseEvent* event); + void mouseReleaseEvent(QMouseEvent *event);*/ + void paintEvent(QPaintEvent *event); + void draw_keyframe(QPainter& p, int x, int y, bool semiTransparent); + bool enable_reload; + bool mouseover; + long mouseover_frame; + int mouseover_row; +}; + +#endif // KEYFRAMEVIEW_H diff --git a/ui/timelineheader.cpp b/ui/timelineheader.cpp index 1ed76bb7a..0d1bd04fb 100644 --- a/ui/timelineheader.cpp +++ b/ui/timelineheader.cpp @@ -12,18 +12,23 @@ #define CLICK_RANGE 5 #define PLAYHEAD_SIZE 6 -TimelineHeader::TimelineHeader(QWidget *parent) : QWidget(parent), dragging(false), resizing_workarea(false) { +TimelineHeader::TimelineHeader(QWidget *parent) : QWidget(parent), dragging(false), resizing_workarea(false), zoom(1), in_visible(0), snapping(true) { setCursor(Qt::ArrowCursor); setMouseTracking(true); } -void set_playhead(int mouse_x) { - long frame = panel_timeline->getFrameFromScreenPoint(mouse_x); - panel_timeline->snap_to_clip(&frame, false); +void TimelineHeader::set_playhead(int mouse_x) { + long frame = getFrameFromScreenPoint(zoom, mouse_x); + if (snapping) panel_timeline->snap_to_clip(&frame, false); panel_timeline->seek(frame); panel_timeline->repaint_timeline(); } +void TimelineHeader::set_visible_in(long i) { + in_visible = i; + update(); +} + void TimelineHeader::set_in_point(long new_in) { long new_out = sequence->workarea_out; if (new_out == new_in) { @@ -66,7 +71,7 @@ void TimelineHeader::mousePressEvent(QMouseEvent* event) { void TimelineHeader::mouseMoveEvent(QMouseEvent* event) { if (dragging) { if (resizing_workarea) { - long frame = panel_timeline->getFrameFromScreenPoint(event->pos().x()); + long frame = getFrameFromScreenPoint(zoom, event->pos().x()); panel_timeline->snap_to_clip(&frame, true); if (resizing_workarea_in) { temp_workarea_in = qMax(qMin(temp_workarea_out-1, frame), 0L); @@ -81,8 +86,8 @@ void TimelineHeader::mouseMoveEvent(QMouseEvent* event) { resizing_workarea = false; unsetCursor(); if (sequence->using_workarea) { - long min_frame = panel_timeline->getFrameFromScreenPoint(event->pos().x() - CLICK_RANGE) - 1; - long max_frame = panel_timeline->getFrameFromScreenPoint(event->pos().x() + CLICK_RANGE) + 1; + long min_frame = getFrameFromScreenPoint(zoom, event->pos().x() - CLICK_RANGE) - 1; + long max_frame = getFrameFromScreenPoint(zoom, event->pos().x() + CLICK_RANGE) + 1; if (sequence->workarea_in > min_frame && sequence->workarea_in < max_frame) { resizing_workarea = true; resizing_workarea_in = true; @@ -112,6 +117,11 @@ void TimelineHeader::mouseReleaseEvent(QMouseEvent*) { panel_timeline->repaint_timeline(); } +void TimelineHeader::update_header(double z) { + zoom = z; + update(); +} + void TimelineHeader::paintEvent(QPaintEvent*) { if (sequence != NULL) { QPainter p(this); @@ -120,7 +130,7 @@ void TimelineHeader::paintEvent(QPaintEvent*) { int multiplier = 0; do { multiplier++; - interval = panel_timeline->getScreenPointFromFrame(sequence->frame_rate*multiplier); + interval = getScreenPointFromFrame(zoom, sequence->frame_rate*multiplier); } while (interval < 10); for (int i=0;iusing_workarea) { - in_x = panel_timeline->getScreenPointFromFrame(resizing_workarea ? temp_workarea_in : sequence->workarea_in); - int out_x = panel_timeline->getScreenPointFromFrame(resizing_workarea ? temp_workarea_out :sequence->workarea_out); + in_x = getScreenPointFromFrame(zoom, resizing_workarea ? temp_workarea_in : sequence->workarea_in); + int out_x = getScreenPointFromFrame(zoom, resizing_workarea ? temp_workarea_out :sequence->workarea_out); p.fillRect(QRect(in_x, 0, out_x-in_x, height()), QColor(0, 192, 255, 128)); p.setPen(Qt::white); p.drawLine(in_x, 0, in_x, height()); @@ -138,7 +148,7 @@ void TimelineHeader::paintEvent(QPaintEvent*) { } // draw playhead triangle - in_x = panel_timeline->getScreenPointFromFrame(panel_timeline->playhead); + in_x = getScreenPointFromFrame(zoom, panel_timeline->playhead - in_visible); QPoint start(in_x, height()); QPainterPath path; path.moveTo(start); diff --git a/ui/timelineheader.h b/ui/timelineheader.h index 2c5dd27d5..e39e84db9 100644 --- a/ui/timelineheader.h +++ b/ui/timelineheader.h @@ -7,9 +7,14 @@ class TimelineHeader : public QWidget { Q_OBJECT public: - explicit TimelineHeader(QWidget *parent = 0); + explicit TimelineHeader(QWidget *parent = 0); void set_in_point(long p); void set_out_point(long p); + void set_visible_in(long i); + + bool snapping; + + void update_header(double z); protected: void paintEvent(QPaintEvent*); @@ -26,6 +31,12 @@ private: long temp_workarea_out; long sequence_end; + long in_visible; + + double zoom; + + void set_playhead(int mouse_x); + signals: public slots: diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp index bd30ca091..963429ad6 100644 --- a/ui/timelinewidget.cpp +++ b/ui/timelinewidget.cpp @@ -11,7 +11,7 @@ #include "panels/effectcontrols.h" #include "project/undo.h" -#include "effects/effects.h" +#include "effects/effect.h" #include "effects/transition.h" #include @@ -123,8 +123,8 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { if (got_video_values && got_audio_values) break; } } else { - entry_point = panel_timeline->getFrameFromScreenPoint(pos.x()); - panel_timeline->drag_frame_start = entry_point + panel_timeline->getFrameFromScreenPoint(50); + entry_point = panel_timeline->getTimelineFrameFromScreenPoint(pos.x()); + panel_timeline->drag_frame_start = entry_point + panel_timeline->getTimelineFrameFromScreenPoint(50); panel_timeline->drag_track_start = (bottom_align) ? -1 : 0; predicted_new_frame_rate = sequence->frame_rate; } @@ -369,7 +369,7 @@ void TimelineWidget::mousePressEvent(QMouseEvent *event) { panel_timeline->drag_track_start = panel_timeline->cursor_track; } else { QPoint pos = event->pos(); - panel_timeline->drag_frame_start = panel_timeline->getFrameFromScreenPoint(pos.x()); + panel_timeline->drag_frame_start = panel_timeline->getTimelineFrameFromScreenPoint(pos.x()); panel_timeline->drag_track_start = getTrackFromScreenPoint(pos.y()); } @@ -867,7 +867,7 @@ void validate_snapping(const Ghost& g, long* frame_diff) { void TimelineWidget::update_ghosts(QPoint& mouse_pos) { int mouse_track = getTrackFromScreenPoint(mouse_pos.y()); - long frame_diff = panel_timeline->getFrameFromScreenPoint(mouse_pos.x()) - panel_timeline->drag_frame_start; + long frame_diff = panel_timeline->getTimelineFrameFromScreenPoint(mouse_pos.x()) - panel_timeline->drag_frame_start; int track_diff = (panel_timeline->tool == TIMELINE_TOOL_SLIDE || panel_timeline->transition_select != TA_NO_TRANSITION) ? 0 : mouse_track - panel_timeline->drag_track_start; long validator; @@ -1073,7 +1073,7 @@ void TimelineWidget::mouseMoveEvent(QMouseEvent *event) { if (sequence != NULL) { bool alt = (event->modifiers() & Qt::AltModifier); - panel_timeline->cursor_frame = panel_timeline->getFrameFromScreenPoint(event->pos().x()); + panel_timeline->cursor_frame = panel_timeline->getTimelineFrameFromScreenPoint(event->pos().x()); panel_timeline->cursor_track = getTrackFromScreenPoint(event->pos().y()); if (panel_timeline->tool == TIMELINE_TOOL_EDIT || panel_timeline->tool == TIMELINE_TOOL_RAZOR) { @@ -1359,8 +1359,8 @@ void TimelineWidget::mouseMoveEvent(QMouseEvent *event) { panel_timeline->rect_select_h = event->pos().y() - panel_timeline->rect_select_y; if (bottom_align) panel_timeline->rect_select_h -= height(); - long frame_start = panel_timeline->getFrameFromScreenPoint(panel_timeline->rect_select_x); - long frame_end = panel_timeline->getFrameFromScreenPoint(event->pos().x()); + long frame_start = panel_timeline->getTimelineFrameFromScreenPoint(panel_timeline->rect_select_x); + long frame_end = panel_timeline->getTimelineFrameFromScreenPoint(event->pos().x()); long frame_min = qMin(frame_start, frame_end); long frame_max = qMax(frame_start, frame_end); @@ -1432,8 +1432,8 @@ void TimelineWidget::mouseMoveEvent(QMouseEvent *event) { int lim = 5; int mouse_track = getTrackFromScreenPoint(pos.y()); - long mouse_frame_lower = panel_timeline->getFrameFromScreenPoint(pos.x()-lim)-1; - long mouse_frame_upper = panel_timeline->getFrameFromScreenPoint(pos.x()+lim)+1; + long mouse_frame_lower = panel_timeline->getTimelineFrameFromScreenPoint(pos.x()-lim)-1; + long mouse_frame_upper = panel_timeline->getTimelineFrameFromScreenPoint(pos.x()+lim)+1; bool found = false; bool cursor_contains_clip = false; int closeness = INT_MAX; @@ -1563,7 +1563,7 @@ void TimelineWidget::redraw_clips() { audio_track_limit = qMax(audio_track_limit, clip->track); } } - int panel_width = panel_timeline->getScreenPointFromFrame(end_frame) + 100; + int panel_width = panel_timeline->getTimelineScreenPointFromFrame(end_frame) + 100; int panel_height = 0; if (bottom_align) { for (int i=-1;i>=video_track_limit;i--) { @@ -1589,7 +1589,7 @@ void TimelineWidget::redraw_clips() { for (int i=0;iclip_count();i++) { Clip* clip = sequence->get_clip(i); if (clip != NULL && is_track_visible(clip->track)) { - QRect clip_rect(panel_timeline->getScreenPointFromFrame(clip->timeline_in), getScreenPointFromTrack(clip->track), clip->getLength() * panel_timeline->zoom, panel_timeline->calculate_track_height(clip->track, -1)); + QRect clip_rect(panel_timeline->getTimelineScreenPointFromFrame(clip->timeline_in), getScreenPointFromTrack(clip->track), clip->getLength() * panel_timeline->zoom, panel_timeline->calculate_track_height(clip->track, -1)); clip_painter.fillRect(clip_rect, QColor(clip->color_r, clip->color_g, clip->color_b)); int thumb_x = clip_rect.x() + 1; @@ -1606,7 +1606,7 @@ void TimelineWidget::redraw_clips() { } else if (ms->preview_done) { // draw thumbnail/waveform long media_length = m->get_length_in_frames(clip->sequence->frame_rate); - int waveform_limit = qMin(clip_rect.width(), panel_timeline->getScreenPointFromFrame(media_length - clip->clip_in)); + int waveform_limit = qMin(clip_rect.width(), panel_timeline->getTimelineScreenPointFromFrame(media_length - clip->clip_in)); if (waveform_limit < clip_rect.width()) { draw_checkerboard = true; @@ -1616,12 +1616,12 @@ void TimelineWidget::redraw_clips() { if (clip->track < 0) { int space_for_thumb = clip_rect.width(); if (clip->opening_transition != NULL) { - int ot_width = panel_timeline->getScreenPointFromFrame(clip->opening_transition->length); + int ot_width = panel_timeline->getTimelineScreenPointFromFrame(clip->opening_transition->length); thumb_x += ot_width; space_for_thumb -= ot_width; } if (clip->closing_transition != NULL) { - space_for_thumb -= panel_timeline->getScreenPointFromFrame(clip->closing_transition->length); + space_for_thumb -= panel_timeline->getTimelineScreenPointFromFrame(clip->closing_transition->length); } int thumb_y = clip_painter.fontMetrics().height()+CLIP_TEXT_PADDING+CLIP_TEXT_PADDING; int thumb_height = clip_rect.height()-thumb_y; @@ -1688,7 +1688,7 @@ void TimelineWidget::redraw_clips() { for (int i=0;i<2;i++) { Transition* t = (i == 0) ? clip->opening_transition : clip->closing_transition; if (t != NULL) { - int transition_width = panel_timeline->getScreenPointFromFrame(t->length); + int transition_width = panel_timeline->getTimelineScreenPointFromFrame(t->length); int transition_height = clip_rect.height(); int tr_y = clip_rect.y(); int tr_x = 0; @@ -1780,8 +1780,8 @@ void TimelineWidget::paintEvent(QPaintEvent*) { const Selection& s = panel_timeline->selections.at(i); if (is_track_visible(s.track)) { int selection_y = getScreenPointFromTrack(s.track); - int selection_x = panel_timeline->getScreenPointFromFrame(s.in); - p.fillRect(selection_x, selection_y, panel_timeline->getScreenPointFromFrame(s.out) - selection_x, panel_timeline->calculate_track_height(s.track, -1), QColor(0, 0, 0, 64)); + int selection_x = panel_timeline->getTimelineScreenPointFromFrame(s.in); + p.fillRect(selection_x, selection_y, panel_timeline->getTimelineScreenPointFromFrame(s.out) - selection_x, panel_timeline->calculate_track_height(s.track, -1), QColor(0, 0, 0, 64)); } } @@ -1802,9 +1802,9 @@ void TimelineWidget::paintEvent(QPaintEvent*) { for (int i=0;ighosts.size();i++) { const Ghost& g = panel_timeline->ghosts.at(i); if (is_track_visible(g.track)) { - int ghost_x = panel_timeline->getScreenPointFromFrame(g.in); + int ghost_x = panel_timeline->getTimelineScreenPointFromFrame(g.in); int ghost_y = getScreenPointFromTrack(g.track); - int ghost_width = panel_timeline->getScreenPointFromFrame(g.out - g.in) - 1; + int ghost_width = panel_timeline->getTimelineScreenPointFromFrame(g.out - g.in) - 1; int ghost_height = panel_timeline->calculate_track_height(g.track, -1) - 1; p.setPen(QColor(255, 255, 0)); for (int j=0;jsplitting) { for (int i=0;isplit_tracks.size();i++) { if (is_track_visible(panel_timeline->split_tracks.at(i))) { - int cursor_x = panel_timeline->getScreenPointFromFrame(panel_timeline->drag_frame_start); + int cursor_x = panel_timeline->getTimelineScreenPointFromFrame(panel_timeline->drag_frame_start); int cursor_y = getScreenPointFromTrack(panel_timeline->split_tracks.at(i)); p.setPen(QColor(64, 64, 64)); @@ -1828,7 +1828,7 @@ void TimelineWidget::paintEvent(QPaintEvent*) { // Draw playhead p.setPen(Qt::red); - int playhead_x = panel_timeline->getScreenPointFromFrame(panel_timeline->playhead); + int playhead_x = panel_timeline->getTimelineScreenPointFromFrame(panel_timeline->playhead); p.drawLine(playhead_x, rect().top(), playhead_x, rect().bottom()); // draw border @@ -1839,15 +1839,15 @@ void TimelineWidget::paintEvent(QPaintEvent*) { // draw snap point if (panel_timeline->snapped) { p.setPen(Qt::white); - int snap_x = panel_timeline->getScreenPointFromFrame(panel_timeline->snap_point); + int snap_x = panel_timeline->getTimelineScreenPointFromFrame(panel_timeline->snap_point); p.drawLine(snap_x, 0, snap_x, height()); } // Draw edit cursor if (panel_timeline->tool == TIMELINE_TOOL_EDIT || panel_timeline->tool == TIMELINE_TOOL_RAZOR) { if (is_track_visible(panel_timeline->cursor_track)) { - int cursor_x = panel_timeline->getScreenPointFromFrame(panel_timeline->cursor_frame); - int cursor_y = getScreenPointFromTrack(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)); diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index 269272852..567f48290 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include extern "C" { @@ -171,18 +172,34 @@ void ViewerWidget::compose_sequence(QVector& nests, bool render_audio) { if (flip) half_height = -half_height; glOrtho(-half_width, half_width, half_height, -half_height, -1, 1); int anchor_x = ms->video_width/2; - int anchor_y = ms->video_height/2; + int anchor_y = ms->video_height/2; - // perform all transform effects - c->run_video_pre_effect_stack(playhead, &anchor_x, &anchor_y); + QOpenGLShaderProgram shader; - for (int i=nests.size()-1;i>=0;i--) { - nests.at(i)->run_video_pre_effect_stack(playhead, &anchor_x, &anchor_y); + for (int j=0;jeffects.size();j++) { + if (c->effects.at(j)->enable_opengl && c->effects.at(j)->is_enabled()) c->effects.at(j)->process_gl(shader, &anchor_x, &anchor_y); + } + + if (c->opening_transition != NULL) { + int transition_progress = playhead - c->timeline_in; + if (transition_progress < c->opening_transition->length) { + c->opening_transition->process_transition((double)transition_progress/(double)c->opening_transition->length); + } + } + + if (c->closing_transition != NULL) { + int transition_progress = c->closing_transition->length - (playhead - c->timeline_in - c->getLength() + c->closing_transition->length); + if (transition_progress < c->closing_transition->length) { + c->closing_transition->process_transition((double)transition_progress/(double)c->closing_transition->length); + } } int anchor_right = ms->video_width - anchor_x; int anchor_bottom = ms->video_height - anchor_y; + bool use_gl_shaders = shader.link(); + if (use_gl_shaders) shader.bind(); + c->texture->bind(); glBegin(GL_QUADS); @@ -196,10 +213,9 @@ void ViewerWidget::compose_sequence(QVector& nests, bool render_audio) { glVertex2f(-anchor_x, anchor_bottom); glEnd(); - c->texture->release(); + c->texture->release(); - // perform all transform effects - c->run_video_post_effect_stack(); + if (use_gl_shaders) shader.release(); } } else if (render_audio && c->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && @@ -212,7 +228,6 @@ void ViewerWidget::compose_sequence(QVector& nests, bool render_audio) { case MEDIA_TYPE_SEQUENCE: nests.append(c); compose_sequence(nests, render_audio); - c->run_video_post_effect_stack(); break; } }