1 /***
2
3 Olive - Non-Linear Video Editor
4 Copyright (C) 2019 Olive Team
5
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 ***/
20
21 #include "texteffect.h"
22
23 #include <QGridLayout>
24 #include <QLabel>
25 #include <QOpenGLTexture>
26 #include <QTextEdit>
27 #include <QPainter>
28 #include <QPainterPath>
29 #include <QPushButton>
30 #include <QColorDialog>
31 #include <QFontDatabase>
32 #include <QComboBox>
33 #include <QWidget>
34 #include <QtMath>
35 #include <QMenu>
36
37 #include "ui/labelslider.h"
38 #include "ui/collapsiblewidget.h"
39 #include "timeline/clip.h"
40 #include "timeline/sequence.h"
41 #include "ui/comboboxex.h"
42 #include "ui/colorbutton.h"
43 #include "ui/fontcombobox.h"
44 #include "ui/blur.h"
45 #include "global/config.h"
46
TextEffect(Clip * c,const EffectMeta * em)47 TextEffect::TextEffect(Clip* c, const EffectMeta* em) :
48 Effect(c, em)
49 {
50 SetFlags(Effect::SuperimposeFlag);
51
52 EffectRow* text_field = new EffectRow(this, tr("Text"));
53 text_val = new StringField(text_field, "text", false);
54 text_val->SetColumnSpan(2);
55
56 EffectRow* font_row = new EffectRow(this, tr("Font"));
57 set_font_combobox = new FontField(font_row, "font");
58 set_font_combobox->SetColumnSpan(2);
59
60 EffectRow* size_row = new EffectRow(this, tr("Size"));
61 size_val = new DoubleField(size_row, "size");
62 size_val->SetMinimum(0);
63 size_val->SetColumnSpan(2);
64
65 EffectRow* color_row = new EffectRow(this, tr("Color"));
66 set_color_button = new ColorField(color_row, "color");
67 set_color_button->SetColumnSpan(2);
68
69 EffectRow* alignment_row = new EffectRow(this, tr("Alignment"));
70 halign_field = new ComboField(alignment_row, "halign");
71 halign_field->AddItem(tr("Left"), Qt::AlignLeft);
72 halign_field->AddItem(tr("Center"), Qt::AlignHCenter);
73 halign_field->AddItem(tr("Right"), Qt::AlignRight);
74 halign_field->AddItem(tr("Justify"), Qt::AlignJustify);
75
76 valign_field = new ComboField(alignment_row, "valign");
77 valign_field->AddItem(tr("Top"), Qt::AlignTop);
78 valign_field->AddItem(tr("Center"), Qt::AlignVCenter);
79 valign_field->AddItem(tr("Bottom"), Qt::AlignBottom);
80
81 EffectRow* word_wrap_row = new EffectRow(this, tr("Word Wrap"));
82 word_wrap_field = new BoolField(word_wrap_row, "wordwrap");
83 word_wrap_field->SetColumnSpan(2);
84
85 EffectRow* padding_row = new EffectRow(this, tr("Padding"));
86 padding_field = new DoubleField(padding_row, "padding");
87 padding_field->SetColumnSpan(2);
88
89 EffectRow* position_row = new EffectRow(this, tr("Position"));
90 position_x = new DoubleField(position_row, "posx");
91 position_y = new DoubleField(position_row, "posy");
92
93 EffectRow* outline_row = new EffectRow(this, tr("Outline"));
94 outline_bool = new BoolField(outline_row, "outline");
95 outline_bool->SetColumnSpan(2);
96
97 EffectRow* outline_color_row = new EffectRow(this, tr("Outline Color"));
98 outline_color = new ColorField(outline_color_row, "outlinecolor");
99 outline_color->SetColumnSpan(2);
100
101 EffectRow* outline_width_row = new EffectRow(this, tr("Outline Width"));
102 outline_width = new DoubleField(outline_width_row, "outlinewidth");
103 outline_width->SetColumnSpan(2);
104 outline_width->SetMinimum(0);
105
106 EffectRow* shadow_row = new EffectRow(this, tr("Shadow"));
107 shadow_bool = new BoolField(shadow_row, "shadow");
108 shadow_bool->SetColumnSpan(2);
109
110 EffectRow* shadow_color_row = new EffectRow(this, tr("Shadow Color"));
111 shadow_color = new ColorField(shadow_color_row, "shadowcolor");
112 shadow_color->SetColumnSpan(2);
113
114 EffectRow* shadow_angle_row = new EffectRow(this, tr("Shadow Angle"));
115 shadow_angle = new DoubleField(shadow_angle_row, "shadowangle");
116 shadow_angle->SetColumnSpan(2);
117
118 EffectRow* shadow_distance_row = new EffectRow(this, tr("Shadow Distance"));
119 shadow_distance = new DoubleField(shadow_distance_row, "shadowdistance");
120 shadow_distance->SetColumnSpan(2);
121 shadow_distance->SetMinimum(0);
122
123 EffectRow* shadow_softness_row = new EffectRow(this, tr("Shadow Softness"));
124 shadow_softness = new DoubleField(shadow_softness_row, "shadowsoftness");
125 shadow_softness->SetColumnSpan(2);
126 shadow_softness->SetMinimum(0);
127
128 EffectRow* shadow_opacity_row = new EffectRow(this, tr("Shadow Opacity"));
129 shadow_opacity = new DoubleField(shadow_opacity_row, "shadowopacity");
130 shadow_opacity->SetColumnSpan(2);
131 shadow_opacity->SetMinimum(0);
132 shadow_opacity->SetMaximum(100);
133
134 size_val->SetDefault(48);
135 text_val->SetValueAt(0, tr("Sample Text"));
136 set_color_button->SetValueAt(0, QColor(Qt::white));
137 halign_field->SetValueAt(0, Qt::AlignHCenter);
138 valign_field->SetValueAt(0, Qt::AlignVCenter);
139 word_wrap_field->SetValueAt(0, true);
140 outline_color->SetValueAt(0, QColor(Qt::black));
141 shadow_color->SetValueAt(0, QColor(Qt::black));
142 shadow_angle->SetDefault(45);
143 shadow_opacity->SetDefault(100);
144 shadow_softness->SetDefault(5);
145 shadow_distance->SetDefault(5);
146 shadow_opacity->SetDefault(80);
147 outline_width->SetDefault(20);
148
149 outline_enable(false);
150 shadow_enable(false);
151
152 connect(shadow_bool, SIGNAL(Toggled(bool)), this, SLOT(shadow_enable(bool)));
153 connect(outline_bool, SIGNAL(Toggled(bool)), this, SLOT(outline_enable(bool)));
154
155 vertPath = "common.vert";
156 fragPath = "dropshadow.frag";
157 }
158
redraw(double timecode)159 void TextEffect::redraw(double timecode) {
160 if (size_val->GetDoubleAt(timecode) <= 0) {
161 return;
162 }
163
164 QColor bkg = set_color_button->GetColorAt(timecode);
165 bkg.setAlpha(0);
166 img.fill(bkg);
167
168 QPainter p(&img);
169 p.setRenderHint(QPainter::Antialiasing);
170 int padding = qRound(padding_field->GetDoubleAt(timecode));
171 int width = img.width() - padding * 2;
172 int height = img.height() - padding * 2;
173
174 // set font
175 font.setStyleHint(QFont::Helvetica, QFont::PreferAntialias);
176 font.setFamily(set_font_combobox->GetFontAt(timecode));
177 font.setPointSize(qRound(size_val->GetDoubleAt(timecode)));
178 p.setFont(font);
179 QFontMetrics fm(font);
180
181 QStringList lines = text_val->GetStringAt(timecode).split('\n');
182
183 // word wrap function
184 if (word_wrap_field->GetBoolAt(timecode)) {
185 for (int i=0;i<lines.size();i++) {
186 QString s(lines.at(i));
187 if (fm.width(s) > width) {
188 int last_space_index = 0;
189 for (int j=0;j<s.length();j++) {
190 if (s.at(j) == ' ') {
191 if (fm.width(s.left(j)) > width) {
192 break;
193 } else {
194 last_space_index = j;
195 }
196 }
197 }
198 if (last_space_index > 0) {
199 lines.insert(i+1, s.mid(last_space_index + 1));
200 lines[i] = s.left(last_space_index);
201 }
202 }
203 }
204 }
205
206 QPainterPath path;
207
208 int text_height = fm.height()*lines.size();
209
210 for (int i=0;i<lines.size();i++) {
211 int text_x, text_y;
212
213 switch (halign_field->GetValueAt(timecode).toInt()) {
214 case Qt::AlignLeft: text_x = 0; break;
215 case Qt::AlignRight: text_x = width - fm.width(lines.at(i)); break;
216 case Qt::AlignJustify:
217 // add spaces until the string is too big
218 text_x = 0;
219 while (fm.width(lines.at(i)) < width) {
220 bool space = false;
221 QString spaced(lines.at(i));
222 for (int i=0;i<spaced.length();i++) {
223 if (spaced.at(i) == ' ') {
224 // insert a space
225 spaced.insert(i, ' ');
226 space = true;
227
228 // scan to next non-space
229 while (i < spaced.length() && spaced.at(i) == ' ') i++;
230 }
231 }
232 if (fm.width(spaced) > width || !space) {
233 break;
234 } else {
235 lines[i] = spaced;
236 }
237 }
238 break;
239 case Qt::AlignHCenter:
240 default:
241 text_x = (width/2) - (fm.width(lines.at(i))/2);
242 break;
243 }
244
245 switch (valign_field->GetValueAt(timecode).toInt()) {
246 case Qt::AlignTop:
247 text_y = (fm.height()*i)+fm.ascent();
248 break;
249 case Qt::AlignBottom:
250 text_y = (height - text_height - fm.descent()) + (fm.height()*(i+1));
251 break;
252 case Qt::AlignVCenter:
253 default:
254 text_y = ((height/2) - (text_height/2) - fm.descent()) + (fm.height()*(i+1));
255 break;
256 }
257
258 path.addText(text_x, text_y, font, lines.at(i));
259 }
260
261 path.translate(position_x->GetDoubleAt(timecode) + padding, position_y->GetDoubleAt(timecode) + padding);
262
263 // draw software shadow
264 if (shadow_bool->GetBoolAt(timecode)) {
265 p.setPen(Qt::NoPen);
266
267 // calculate offset using distance and angle
268 double angle = shadow_angle->GetDoubleAt(timecode) * M_PI / 180.0;
269 double distance = qFloor(shadow_distance->GetDoubleAt(timecode));
270 int shadow_x_offset = qRound(qCos(angle) * distance);
271 int shadow_y_offset = qRound(qSin(angle) * distance);
272
273 QPainterPath shadow_path(path);
274 shadow_path.translate(shadow_x_offset, shadow_y_offset);
275
276 QColor col = shadow_color->GetColorAt(timecode);
277 col.setAlpha(0);
278 img.fill(col);
279
280 col.setAlphaF(shadow_opacity->GetDoubleAt(timecode)*0.01);
281 p.setBrush(col);
282 p.drawPath(shadow_path);
283
284 int blurSoftness = qFloor(shadow_softness->GetDoubleAt(timecode));
285 if (blurSoftness > 0) olive::ui::blur(img, img.rect(), blurSoftness, true);
286 }
287
288 // draw outline
289 int outline_width_val = qCeil(outline_width->GetDoubleAt(timecode));
290 if (outline_bool->GetBoolAt(timecode) && outline_width_val > 0) {
291 QPen outline(outline_color->GetColorAt(timecode));
292 outline.setWidth(outline_width_val);
293 p.setPen(outline);
294 p.setBrush(Qt::NoBrush);
295 p.drawPath(path);
296 }
297
298 // draw "master" text
299 p.setPen(Qt::NoPen);
300 p.setBrush(set_color_button->GetColorAt(timecode));
301 p.drawPath(path);
302
303 p.end();
304 }
305
shadow_enable(bool e)306 void TextEffect::shadow_enable(bool e) {
307 shadow_color->SetEnabled(e);
308 shadow_angle->SetEnabled(e);
309 shadow_distance->SetEnabled(e);
310 shadow_softness->SetEnabled(e);
311 shadow_opacity->SetEnabled(e);
312 }
313
outline_enable(bool e)314 void TextEffect::outline_enable(bool e) {
315 outline_color->SetEnabled(e);
316 outline_width->SetEnabled(e);
317 }
318