#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; }