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