1 /*
2 * kdenlivetitle_wrapper.cpp -- kdenlivetitle wrapper
3 * Copyright (c) 2009 Marco Gittler <g.marco@freenet.de>
4 * Copyright (c) 2009 Jean-Baptiste Mardelle <jb@kdenlive.org>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library 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 GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include "kdenlivetitle_wrapper.h"
22 #include "typewriter.h"
23
24 #include "common.h"
25
26 #include <QImage>
27 #include <QPainter>
28 #include <QDebug>
29 #include <QMutex>
30 #include <QGraphicsScene>
31 #include <QGraphicsTextItem>
32 #include <QGraphicsSvgItem>
33 #include <QSvgRenderer>
34 #include <QTextCursor>
35 #include <QTextDocument>
36 #include <QStyleOptionGraphicsItem>
37 #include <QString>
38 #include <math.h>
39
40 #include <QDomElement>
41 #include <QRectF>
42 #include <QColor>
43 #include <QWidget>
44 #include <framework/mlt_log.h>
45
46 #if QT_VERSION >= 0x040600
47 #include <QGraphicsEffect>
48 #include <QGraphicsBlurEffect>
49 #include <QGraphicsDropShadowEffect>
50 #endif
51
52 #include <memory>
53
54 Q_DECLARE_METATYPE(QTextCursor);
55 Q_DECLARE_METATYPE(std::shared_ptr<TypeWriter>);
56
57 // Private Constants
58 static const double PI = 3.14159265358979323846;
59
60 class ImageItem: public QGraphicsItem
61 {
62 public:
ImageItem(QImage img)63 ImageItem(QImage img)
64 {
65 m_img = img;
66 }
67
boundingRect() const68 virtual QRectF boundingRect() const
69 {
70 return QRectF(0, 0, m_img.width(), m_img.height());
71 }
72
paint(QPainter * painter,const QStyleOptionGraphicsItem *,QWidget *)73 virtual void paint( QPainter *painter,
74 const QStyleOptionGraphicsItem * /*option*/,
75 QWidget* )
76 {
77 painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
78 painter->drawImage(QPoint(), m_img);
79 }
80
81 private:
82 QImage m_img;
83
84
85 };
86
blur(QImage & image,int radius)87 void blur( QImage& image, int radius )
88 {
89 int tab[] = { 14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2 };
90 int alpha = (radius < 1) ? 16 : (radius > 17) ? 1 : tab[radius-1];
91
92 int r1 = 0;
93 int r2 = image.height() - 1;
94 int c1 = 0;
95 int c2 = image.width() - 1;
96
97 int bpl = image.bytesPerLine();
98 int rgba[4];
99 unsigned char* p;
100
101 int i1 = 0;
102 int i2 = 3;
103
104 for (int col = c1; col <= c2; col++) {
105 p = image.scanLine(r1) + col * 4;
106 for (int i = i1; i <= i2; i++)
107 rgba[i] = p[i] << 4;
108
109 p += bpl;
110 for (int j = r1; j < r2; j++, p += bpl)
111 for (int i = i1; i <= i2; i++)
112 p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4;
113 }
114
115 for (int row = r1; row <= r2; row++) {
116 p = image.scanLine(row) + c1 * 4;
117 for (int i = i1; i <= i2; i++)
118 rgba[i] = p[i] << 4;
119
120 p += 4;
121 for (int j = c1; j < c2; j++, p += 4)
122 for (int i = i1; i <= i2; i++)
123 p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4;
124 }
125
126 for (int col = c1; col <= c2; col++) {
127 p = image.scanLine(r2) + col * 4;
128 for (int i = i1; i <= i2; i++)
129 rgba[i] = p[i] << 4;
130
131 p -= bpl;
132 for (int j = r1; j < r2; j++, p -= bpl)
133 for (int i = i1; i <= i2; i++)
134 p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4;
135 }
136
137 for (int row = r1; row <= r2; row++) {
138 p = image.scanLine(row) + c2 * 4;
139 for (int i = i1; i <= i2; i++)
140 rgba[i] = p[i] << 4;
141
142 p -= 4;
143 for (int j = c1; j < c2; j++, p -= 4)
144 for (int i = i1; i <= i2; i++)
145 p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4;
146 }
147
148 }
149
150 class PlainTextItem: public QGraphicsItem
151 {
152 public:
PlainTextItem(QString text,QFont font,double width,double height,QBrush brush,QColor outlineColor,double outline,int align,int lineSpacing)153 PlainTextItem(QString text, QFont font, double width, double height, QBrush brush, QColor outlineColor, double outline, int align, int lineSpacing) : m_metrics(QFontMetrics(font))
154 {
155 m_boundingRect = QRectF(0, 0, width, height);
156 m_brush = brush;
157 m_outline = outline;
158 m_pen = QPen(outlineColor);
159 m_pen.setWidthF(outline);
160 m_font = font;
161 m_lineSpacing = lineSpacing + m_metrics.lineSpacing();
162 m_path.setFillRule(Qt::WindingFill);
163 m_align = align;
164 m_width = width;
165 updateText(text);
166 }
167
updateText(QString text)168 void updateText(QString text) {
169 #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
170 m_path.clear();
171 #else
172 m_path = QPainterPath();
173 #endif
174 // Calculate line width
175 QStringList lines = text.split('\n');
176 double linePos = m_metrics.ascent();
177 foreach(const QString &line, lines)
178 {
179 QPainterPath linePath;
180 linePath.addText(0, linePos, m_font, line);
181 linePos += m_lineSpacing;
182 if ( m_align == Qt::AlignHCenter )
183 {
184 double offset = (m_width - m_metrics.width(line)) / 2;
185 linePath.translate(offset, 0);
186 } else if ( m_align == Qt::AlignRight ) {
187 double offset = (m_width - m_metrics.width(line));
188 linePath.translate(offset, 0);
189 }
190 m_path.addPath(linePath);
191 }
192 }
193
boundingRect() const194 virtual QRectF boundingRect() const
195 {
196 return m_boundingRect;
197 }
198
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * w)199 virtual void paint( QPainter *painter,
200 const QStyleOptionGraphicsItem * option,
201 QWidget* w)
202 {
203 if ( !m_shadow.isNull() )
204 {
205 painter->drawImage(m_shadowOffset, m_shadow);
206 }
207 painter->fillPath(m_path, m_brush);
208 if ( m_outline > 0 )
209 {
210 painter->strokePath(m_path, m_pen);
211 }
212 }
213
addShadow(QStringList params)214 void addShadow(QStringList params)
215 {
216 m_params = params;
217 updateShadows();
218 }
219
updateShadows()220 void updateShadows() {
221 if (m_params.count() < 5 || m_params.at( 0 ).toInt() == false)
222 {
223 // Invalid or no shadow wanted
224 return;
225 }
226 // Build shadow image
227 QColor shadowColor = QColor( m_params.at( 1 ) );
228 int blurRadius = m_params.at( 2 ).toInt();
229 int offsetX = m_params.at( 3 ).toInt();
230 int offsetY = m_params.at( 4 ).toInt();
231 m_shadow = QImage( m_boundingRect.width() + abs( offsetX ) + 4 * blurRadius, m_boundingRect.height() + abs( offsetY ) + 4 * blurRadius, QImage::Format_ARGB32_Premultiplied );
232 m_shadow.fill( Qt::transparent );
233 QPainterPath shadowPath = m_path;
234 offsetX -= 2 * blurRadius;
235 offsetY -= 2 * blurRadius;
236 m_shadowOffset = QPoint( offsetX, offsetY );
237 shadowPath.translate(2 * blurRadius, 2 * blurRadius);
238 QPainter shadowPainter( &m_shadow );
239 shadowPainter.fillPath( shadowPath, QBrush( shadowColor ) );
240 shadowPainter.end();
241 blur( m_shadow, blurRadius );
242 }
243
244 private:
245 QRectF m_boundingRect;
246 QImage m_shadow;
247 QPoint m_shadowOffset;
248 QPainterPath m_path;
249 QBrush m_brush;
250 QPen m_pen;
251 QFont m_font;
252 int m_lineSpacing;
253 int m_align;
254 double m_width;
255 QFontMetrics m_metrics;
256 double m_outline;
257 QStringList m_params;
258 };
259
stringToRect(const QString & s)260 QRectF stringToRect( const QString & s )
261 {
262
263 QStringList l = s.split( ',' );
264 if ( l.size() < 4 )
265 return QRectF();
266 return QRectF( l.at( 0 ).toDouble(), l.at( 1 ).toDouble(), l.at( 2 ).toDouble(), l.at( 3 ).toDouble() ).normalized();
267 }
268
stringToColor(const QString & s)269 QColor stringToColor( const QString & s )
270 {
271 QStringList l = s.split( ',' );
272 if ( l.size() < 4 )
273 return QColor();
274 return QColor( l.at( 0 ).toInt(), l.at( 1 ).toInt(), l.at( 2 ).toInt(), l.at( 3 ).toInt() );
275 ;
276 }
stringToTransform(const QString & s)277 QTransform stringToTransform( const QString& s )
278 {
279 QStringList l = s.split( ',' );
280 if ( l.size() < 9 )
281 return QTransform();
282 return QTransform(
283 l.at( 0 ).toDouble(), l.at( 1 ).toDouble(), l.at( 2 ).toDouble(),
284 l.at( 3 ).toDouble(), l.at( 4 ).toDouble(), l.at( 5 ).toDouble(),
285 l.at( 6 ).toDouble(), l.at( 7 ).toDouble(), l.at( 8 ).toDouble()
286 );
287 }
288
qscene_delete(void * data)289 static void qscene_delete( void *data )
290 {
291 QGraphicsScene *scene = ( QGraphicsScene * )data;
292 if (scene) delete scene;
293 scene = NULL;
294 }
295
296
loadFromXml(producer_ktitle self,QGraphicsScene * scene,const char * templateXml,const char * templateText)297 void loadFromXml( producer_ktitle self, QGraphicsScene *scene, const char *templateXml, const char *templateText )
298 {
299 scene->clear();
300 mlt_producer producer = &self->parent;
301 self->has_alpha = true;
302 mlt_properties producer_props = MLT_PRODUCER_PROPERTIES( producer );
303 QDomDocument doc;
304 QString data = QString::fromUtf8(templateXml);
305 QString replacementText = QString::fromUtf8(templateText);
306 doc.setContent(data);
307 QDomElement title = doc.documentElement();
308
309 // Check for invalid title
310 if ( title.isNull() || title.tagName() != "kdenlivetitle" ) return;
311
312 // Check title locale
313 if ( title.hasAttribute( "LC_NUMERIC" ) ) {
314 QString locale = title.attribute( "LC_NUMERIC" );
315 QLocale::setDefault( locale );
316 }
317
318 int originalWidth;
319 int originalHeight;
320 if ( title.hasAttribute("width") ) {
321 originalWidth = title.attribute("width").toInt();
322 originalHeight = title.attribute("height").toInt();
323 scene->setSceneRect(0, 0, originalWidth, originalHeight);
324 }
325 else {
326 originalWidth = scene->sceneRect().width();
327 originalHeight = scene->sceneRect().height();
328 }
329 if ( title.hasAttribute( "out" ) ) {
330 mlt_properties_set_position( producer_props, "_animation_out", title.attribute( "out" ).toDouble() );
331 }
332 else {
333 mlt_properties_set_position( producer_props, "_animation_out", mlt_producer_get_out( producer ) );
334 }
335 mlt_properties_set_int( producer_props, "meta.media.width", originalWidth );
336 mlt_properties_set_int( producer_props, "meta.media.height", originalHeight );
337
338 QDomNode node;
339 QDomNodeList items = title.elementsByTagName("item");
340 for ( int i = 0; i < items.count(); i++ )
341 {
342 QGraphicsItem *gitem = NULL;
343 node = items.item( i );
344 QDomNamedNodeMap nodeAttributes = node.attributes();
345 int zValue = nodeAttributes.namedItem( "z-index" ).nodeValue().toInt();
346 if ( zValue > -1000 )
347 {
348 if ( nodeAttributes.namedItem( "type" ).nodeValue() == "QGraphicsTextItem" )
349 {
350 QDomNamedNodeMap txtProperties = node.namedItem( "content" ).attributes();
351 QFont font( txtProperties.namedItem( "font" ).nodeValue() );
352 QDomNode propsNode = txtProperties.namedItem( "font-bold" );
353 if ( !propsNode.isNull() )
354 {
355 // Old: Bold/Not bold.
356 font.setBold( propsNode.nodeValue().toInt() );
357 }
358 else
359 {
360 // New: Font weight (QFont::)
361 font.setWeight( txtProperties.namedItem( "font-weight" ).nodeValue().toInt() );
362 }
363 font.setItalic( txtProperties.namedItem( "font-italic" ).nodeValue().toInt() );
364 font.setUnderline( txtProperties.namedItem( "font-underline" ).nodeValue().toInt() );
365
366 int letterSpacing = txtProperties.namedItem( "font-spacing" ).nodeValue().toInt();
367 if ( letterSpacing != 0 ) {
368 font.setLetterSpacing( QFont::AbsoluteSpacing, letterSpacing );
369 }
370 // Older Kdenlive version did not store pixel size but point size
371 if ( txtProperties.namedItem( "font-pixel-size" ).isNull() )
372 {
373 QFont f2;
374 f2.setPointSize( txtProperties.namedItem( "font-size" ).nodeValue().toInt() );
375 font.setPixelSize( QFontInfo( f2 ).pixelSize() );
376 }
377 else
378 {
379 font.setPixelSize( txtProperties.namedItem( "font-pixel-size" ).nodeValue().toInt() );
380 }
381 if ( !txtProperties.namedItem( "letter-spacing" ).isNull() )
382 {
383 font.setLetterSpacing(QFont::AbsoluteSpacing, txtProperties.namedItem( "letter-spacing" ).nodeValue().toInt());
384 }
385 QColor col( stringToColor( txtProperties.namedItem( "font-color" ).nodeValue() ) );
386 QString text = node.namedItem( "content" ).firstChild().nodeValue();
387 if ( !replacementText.isEmpty() )
388 {
389 text = text.replace( "%s", replacementText );
390 }
391 QColor outlineColor(stringToColor( txtProperties.namedItem( "font-outline-color" ).nodeValue() ) );
392
393 int align = 1;
394 if ( txtProperties.namedItem( "alignment" ).isNull() == false )
395 {
396 align = txtProperties.namedItem( "alignment" ).nodeValue().toInt();
397 }
398
399 double boxWidth = 0;
400 double boxHeight = 0;
401 if ( txtProperties.namedItem( "box-width" ).isNull() )
402 {
403 // This is an old version title, find out dimensions from QGraphicsTextItem
404 QGraphicsTextItem *txt = scene->addText(text, font);
405 QRectF br = txt->boundingRect();
406 boxWidth = br.width();
407 boxHeight = br.height();
408 scene->removeItem(txt);
409 delete txt;
410 } else {
411 boxWidth = txtProperties.namedItem( "box-width" ).nodeValue().toDouble();
412 boxHeight = txtProperties.namedItem( "box-height" ).nodeValue().toDouble();
413 }
414 QBrush brush;
415 if ( txtProperties.namedItem( "gradient" ).isNull() == false )
416 {
417 // Calculate gradient
418 QString gradientData = txtProperties.namedItem( "gradient" ).nodeValue();
419 QStringList values = gradientData.split(";");
420 if (values.count() < 5) {
421 // invalid gradient, use default
422 values = QStringList() << "#ff0000" << "#2e0046" << "0" << "100" << "90";
423 }
424 QLinearGradient gr;
425 gr.setColorAt(values.at(2).toDouble() / 100, values.at(0));
426 gr.setColorAt(values.at(3).toDouble() / 100, values.at(1));
427 double angle = values.at(4).toDouble();
428 if (angle <= 90) {
429 gr.setStart(0, 0);
430 gr.setFinalStop(boxWidth * cos( angle * PI / 180 ), boxHeight * sin( angle * PI / 180 ));
431 } else {
432 gr.setStart(boxWidth, 0);
433 gr.setFinalStop(boxWidth + boxWidth * cos( angle * PI / 180 ), boxHeight * sin( angle * PI / 180 ));
434 }
435 brush = QBrush(gr);
436 }
437 else
438 {
439 brush = QBrush(col);
440 }
441
442 if ( txtProperties.namedItem( "compatibility" ).isNull() ) {
443 // Workaround Qt5 crash in threaded drawing of QGraphicsTextItem, paint by ourselves
444 PlainTextItem *txt = new PlainTextItem(text, font, boxWidth, boxHeight, brush, outlineColor, txtProperties.namedItem("font-outline").nodeValue().toDouble(), align, txtProperties.namedItem("line-spacing").nodeValue().toInt());
445 if ( txtProperties.namedItem( "shadow" ).isNull() == false )
446 {
447 QStringList values = txtProperties.namedItem( "shadow" ).nodeValue().split(";");
448 txt->addShadow(values);
449 }
450 if (!txtProperties.namedItem( "typewriter" ).isNull()) {
451 // typewriter effect
452
453 QStringList values = txtProperties.namedItem( "typewriter" ).nodeValue().split(";");
454 int enabled = (static_cast<bool>(values.at(0).toInt()));
455
456 if (enabled and values.count() >= 5) {
457 mlt_properties_set_int( producer_props, "_animated", 1 );
458 std::shared_ptr<TypeWriter> tw(new TypeWriter);
459 tw->setFrameStep(values.at(1).toInt());
460 int macro = values.at(2).toInt();
461 tw->setStepSigma(values.at(3).toInt());
462 tw->setStepSeed(values.at(4).toInt());
463 QString pattern;
464 if (macro) {
465 char c = 0;
466 switch (macro) {
467 case 1: c = 'c'; break;
468 case 2: c = 'w'; break;
469 case 3: c = 'l'; break;
470 default: break;
471 }
472 pattern = QString(":%1{%2}").arg(c).arg(text);
473 }
474 else
475 {
476 pattern = text;
477 }
478
479 tw->setPattern(pattern.toStdString());
480 tw->parse();
481 tw->printParseResult();
482 txt->setData(0, QVariant::fromValue<std::shared_ptr<TypeWriter>>(tw));
483 } else {
484 txt->setData(0, QVariant());
485 }
486 }
487 scene->addItem( txt );
488 gitem = txt;
489 } else {
490 QGraphicsTextItem *txt = scene->addText(text, font);
491 gitem = txt;
492 if (txtProperties.namedItem("font-outline").nodeValue().toDouble()>0.0){
493 QTextDocument *doc = txt->document();
494 // Make sure some that the text item does not request refresh by itself
495 doc->blockSignals(true);
496 QTextCursor cursor(doc);
497 cursor.select(QTextCursor::Document);
498 QTextCharFormat format;
499 format.setTextOutline(
500 QPen(QColor( stringToColor( txtProperties.namedItem( "font-outline-color" ).nodeValue() ) ),
501 txtProperties.namedItem("font-outline").nodeValue().toDouble(),
502 Qt::SolidLine,Qt::RoundCap,Qt::RoundJoin)
503 );
504 format.setForeground(QBrush(col));
505 cursor.mergeCharFormat(format);
506 } else {
507 txt->setDefaultTextColor( col );
508 }
509
510 // Effects
511 if (!txtProperties.namedItem( "typewriter" ).isNull()) {
512 // typewriter effect
513 mlt_properties_set_int( producer_props, "_animated", 1 );
514 QStringList effetData = QStringList() << "typewriter" << text << txtProperties.namedItem( "typewriter" ).nodeValue();
515 txt->setData(0, effetData);
516 if ( !txtProperties.namedItem( "textwidth" ).isNull() )
517 txt->setData( 1, txtProperties.namedItem( "textwidth" ).nodeValue() );
518 }
519
520 if ( txtProperties.namedItem( "alignment" ).isNull() == false )
521 {
522 txt->setTextWidth( txt->boundingRect().width() );
523 QTextOption opt = txt->document()->defaultTextOption ();
524 opt.setAlignment(( Qt::Alignment ) txtProperties.namedItem( "alignment" ).nodeValue().toInt() );
525 txt->document()->setDefaultTextOption (opt);
526 }
527 if ( !txtProperties.namedItem( "kdenlive-axis-x-inverted" ).isNull() )
528 {
529 //txt->setData(OriginXLeft, txtProperties.namedItem("kdenlive-axis-x-inverted").nodeValue().toInt());
530 }
531 if ( !txtProperties.namedItem( "kdenlive-axis-y-inverted" ).isNull() )
532 {
533 //txt->setData(OriginYTop, txtProperties.namedItem("kdenlive-axis-y-inverted").nodeValue().toInt());
534 }
535 if ( !txtProperties.namedItem("preferred-width").isNull() )
536 {
537 txt->setTextWidth( txtProperties.namedItem("preferred-width").nodeValue().toInt() );
538 }
539 }
540 }
541 else if ( nodeAttributes.namedItem( "type" ).nodeValue() == "QGraphicsRectItem" || nodeAttributes.namedItem( "type" ).nodeValue() == "QGraphicsEllipseItem")
542 {
543 QDomNamedNodeMap rectProperties = node.namedItem( "content" ).attributes();
544 QRectF rect = stringToRect( rectProperties.namedItem( "rect" ).nodeValue() );
545 QString pen_str = rectProperties.namedItem( "pencolor" ).nodeValue();
546 double penwidth = rectProperties.namedItem( "penwidth") .nodeValue().toDouble();
547 QBrush brush;
548 if ( !rectProperties.namedItem( "gradient" ).isNull() )
549 {
550 // Calculate gradient
551 QString gradientData = rectProperties.namedItem( "gradient" ).nodeValue();
552 QStringList values = gradientData.split(";");
553 if (values.count() < 5) {
554 // invalid gradient, use default
555 values = QStringList() << "#ff0000" << "#2e0046" << "0" << "100" << "90";
556 }
557 QLinearGradient gr;
558 gr.setColorAt(values.at(2).toDouble() / 100, values.at(0));
559 gr.setColorAt(values.at(3).toDouble() / 100, values.at(1));
560 double angle = values.at(4).toDouble();
561 if (angle <= 90) {
562 gr.setStart(0, 0);
563 gr.setFinalStop(rect.width() * cos( angle * PI / 180 ), rect.height() * sin( angle * PI / 180 ));
564 } else {
565 gr.setStart(rect.width(), 0);
566 gr.setFinalStop(rect.width() + rect.width()* cos( angle * PI / 180 ), rect.height() * sin( angle * PI / 180 ));
567 }
568 brush = QBrush(gr);
569 }
570 else
571 {
572 brush = QBrush(stringToColor( rectProperties.namedItem( "brushcolor" ).nodeValue() ) );
573 }
574 QPen pen;
575 if ( penwidth == 0 )
576 {
577 pen = QPen( Qt::NoPen );
578 }
579 else
580 {
581 pen = QPen( QBrush( stringToColor( pen_str ) ), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin );
582 }
583 if(nodeAttributes.namedItem( "type" ).nodeValue() == "QGraphicsEllipseItem")
584 {
585 QGraphicsEllipseItem *ellipse = scene->addEllipse( rect, pen, brush );
586 gitem = ellipse;
587 }
588 else
589 {
590 // QGraphicsRectItem
591 QGraphicsRectItem *rec = scene->addRect( rect, pen, brush );
592 gitem = rec;
593 }
594 }
595 else if ( nodeAttributes.namedItem( "type" ).nodeValue() == "QGraphicsPixmapItem" )
596 {
597 const QString url = node.namedItem( "content" ).attributes().namedItem( "url" ).nodeValue();
598 const QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
599 QImage img;
600 if (base64.isEmpty()){
601 img.load(url);
602 }else{
603 img.loadFromData(QByteArray::fromBase64(base64.toLatin1()));
604 }
605 ImageItem *rec = new ImageItem(img);
606 scene->addItem( rec );
607 gitem = rec;
608 }
609 else if ( nodeAttributes.namedItem( "type" ).nodeValue() == "QGraphicsSvgItem" )
610 {
611 QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
612 QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
613 QGraphicsSvgItem *rec = NULL;
614 if (base64.isEmpty()){
615 rec = new QGraphicsSvgItem(url);
616 }else{
617 rec = new QGraphicsSvgItem();
618 QSvgRenderer *renderer= new QSvgRenderer(QByteArray::fromBase64(base64.toLatin1()), rec );
619 rec->setSharedRenderer(renderer);
620 }
621 if (rec){
622 scene->addItem(rec);
623 gitem = rec;
624 }
625 }
626 }
627 //pos and transform
628 if ( gitem )
629 {
630 QPointF p( node.namedItem( "position" ).attributes().namedItem( "x" ).nodeValue().toDouble(),
631 node.namedItem( "position" ).attributes().namedItem( "y" ).nodeValue().toDouble() );
632 gitem->setPos( p );
633 gitem->setTransform( stringToTransform( node.namedItem( "position" ).firstChild().firstChild().nodeValue() ) );
634 int zValue = nodeAttributes.namedItem( "z-index" ).nodeValue().toInt();
635 gitem->setZValue( zValue );
636
637 #if QT_VERSION >= 0x040600
638 // effects
639 QDomNode eff = items.item(i).namedItem("effect");
640 if (!eff.isNull()) {
641 QDomElement e = eff.toElement();
642 if (e.attribute("type") == "blur") {
643 QGraphicsBlurEffect *blur = new QGraphicsBlurEffect();
644 blur->setBlurRadius(e.attribute("blurradius").toInt());
645 gitem->setGraphicsEffect(blur);
646 }
647 else if (e.attribute("type") == "shadow") {
648 QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect();
649 shadow->setBlurRadius(e.attribute("blurradius").toInt());
650 shadow->setOffset(e.attribute("xoffset").toInt(), e.attribute("yoffset").toInt());
651 gitem->setGraphicsEffect(shadow);
652 }
653 }
654 #endif
655 }
656 }
657
658 QDomNode n = title.firstChildElement("background");
659 if (!n.isNull()) {
660 QColor color = QColor( stringToColor( n.attributes().namedItem( "color" ).nodeValue() ) );
661 self->has_alpha = color.alpha() != 255;
662 if (color.alpha() > 0) {
663 QGraphicsRectItem *rec = scene->addRect(0, 0, scene->width(), scene->height() , QPen( Qt::NoPen ), QBrush( color ) );
664 rec->setZValue(-1100);
665 }
666 }
667
668 QString startRect;
669 n = title.firstChildElement( "startviewport" );
670 // Check if node exists, if it has an x attribute, it is an old version title, don't use viewport
671 if (!n.isNull() && !n.toElement().hasAttribute("x"))
672 {
673 startRect = n.attributes().namedItem( "rect" ).nodeValue();
674 }
675 n = title.firstChildElement( "endviewport" );
676 // Check if node exists, if it has an x attribute, it is an old version title, don't use viewport
677 if (!n.isNull() && !n.toElement().hasAttribute("x"))
678 {
679 QString rect = n.attributes().namedItem( "rect" ).nodeValue();
680 if (startRect != rect)
681 mlt_properties_set( producer_props, "_endrect", rect.toUtf8().data() );
682 }
683 if (!startRect.isEmpty()) {
684 mlt_properties_set( producer_props, "_startrect", startRect.toUtf8().data() );
685 }
686 return;
687 }
688
689
drawKdenliveTitle(producer_ktitle self,mlt_frame frame,mlt_image_format format,int width,int height,double position,int force_refresh)690 void drawKdenliveTitle( producer_ktitle self, mlt_frame frame, mlt_image_format format, int width, int height, double position, int force_refresh )
691 {
692 // Obtain the producer
693 mlt_producer producer = &self->parent;
694 mlt_profile profile = mlt_service_profile ( MLT_PRODUCER_SERVICE( producer ) ) ;
695 mlt_properties producer_props = MLT_PRODUCER_PROPERTIES( producer );
696
697 // Obtain properties of frame
698 mlt_properties properties = MLT_FRAME_PROPERTIES( frame );
699
700 pthread_mutex_lock( &self->mutex );
701
702 // Check if user wants us to reload the image or if we need animation
703 bool animated = mlt_properties_get( producer_props, "_endrect" ) != NULL;
704
705 if ( mlt_properties_get( producer_props, "_animated" ) != NULL || force_refresh == 1 || width != self->current_width || height != self->current_height || animated )
706 {
707 if ( !animated )
708 {
709 // Cache image only if no animation
710 self->current_image = NULL;
711 mlt_properties_set_data( producer_props, "_cached_image", NULL, 0, NULL, NULL );
712 }
713 mlt_properties_set_int( producer_props, "force_reload", 0 );
714 }
715 int image_size = width * height * 4;
716 if ( self->current_image == NULL || animated ) {
717 // restore QGraphicsScene
718 QGraphicsScene *scene = static_cast<QGraphicsScene *> (mlt_properties_get_data( producer_props, "qscene", NULL ));
719 self->current_alpha = NULL;
720
721 if ( force_refresh == 1 && scene )
722 {
723 scene = NULL;
724 mlt_properties_set_data( producer_props, "qscene", NULL, 0, NULL, NULL );
725 }
726
727 if ( scene == NULL )
728 {
729 if ( !createQApplicationIfNeeded( MLT_PRODUCER_SERVICE(producer) ) ) {
730 pthread_mutex_unlock( &self->mutex );
731 return;
732 }
733 if ( !QMetaType::type("QTextCursor") )
734 qRegisterMetaType<QTextCursor>( "QTextCursor" );
735 scene = new QGraphicsScene();
736 scene->setItemIndexMethod( QGraphicsScene::NoIndex );
737 scene->setSceneRect(0, 0, mlt_properties_get_int( properties, "width" ), mlt_properties_get_int( properties, "height" ));
738 if ( mlt_properties_get( producer_props, "resource" ) && mlt_properties_get( producer_props, "resource" )[0] != '\0' )
739 {
740 // The title has a resource property, so we read all properties from the resource.
741 // Do not serialize the xmldata
742 loadFromXml( self, scene, mlt_properties_get( producer_props, "_xmldata" ), mlt_properties_get( producer_props, "templatetext" ) );
743 }
744 else
745 {
746 // The title has no resource, all data should be serialized
747 loadFromXml( self, scene, mlt_properties_get( producer_props, "xmldata" ), mlt_properties_get( producer_props, "templatetext" ) );
748
749 }
750 mlt_properties_set_data( producer_props, "qscene", scene, 0, ( mlt_destructor )qscene_delete, NULL );
751 }
752
753 QRectF start = stringToRect( QString( mlt_properties_get( producer_props, "_startrect" ) ) );
754 QRectF end = stringToRect( QString( mlt_properties_get( producer_props, "_endrect" ) ) );
755 const QRectF source( 0, 0, width, height );
756
757 if (start.isNull()) {
758 start = QRectF( 0, 0, mlt_properties_get_int( producer_props, "meta.media.width" ), mlt_properties_get_int( producer_props, "meta.media.height" ) );
759 }
760
761 // Effects
762 QList <QGraphicsItem *> items = scene->items();
763 PlainTextItem *titem = NULL;
764 for (int i = 0; i < items.count(); i++) {
765 titem = dynamic_cast <PlainTextItem*> ( items.at( i ) );
766 if (titem && !titem->data( 0 ).isNull()) {
767 std::shared_ptr<TypeWriter> ptr = titem->data( 0 ).value<std::shared_ptr<TypeWriter>>();
768 titem->updateText(ptr->render(position).c_str());
769 titem->updateShadows();
770 }
771 }
772
773 //must be extracted from kdenlive title
774 self->rgba_image = (uint8_t *) mlt_pool_alloc( image_size );
775 #if QT_VERSION >= 0x050200
776 // QImage::Format_RGBA8888 was added in Qt5.2
777 // Initialize the QImage with the MLT image because the data formats match.
778 QImage img( self->rgba_image, width, height, QImage::Format_RGBA8888 );
779 #else
780 QImage img( width, height, QImage::Format_ARGB32 );
781 #endif
782 img.fill( 0 );
783 QPainter p1;
784 p1.begin( &img );
785 p1.setRenderHints( QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::HighQualityAntialiasing );
786 //| QPainter::SmoothPixmapTransform );
787 mlt_position anim_out = mlt_properties_get_position( producer_props, "_animation_out" );
788
789 if (end.isNull())
790 {
791 scene->render( &p1, source, start, Qt::IgnoreAspectRatio );
792 }
793 else if ( position > anim_out ) {
794 scene->render( &p1, source, end, Qt::IgnoreAspectRatio );
795 }
796 else {
797 double percentage = 0;
798 if ( position && anim_out )
799 percentage = position / anim_out;
800 QPointF topleft = start.topLeft() + ( end.topLeft() - start.topLeft() ) * percentage;
801 QPointF bottomRight = start.bottomRight() + ( end.bottomRight() - start.bottomRight() ) * percentage;
802 const QRectF r1( topleft, bottomRight );
803 scene->render( &p1, source, r1, Qt::IgnoreAspectRatio );
804 if ( profile && !profile->progressive ){
805 int line=0;
806 double percentage_next_filed = ( position + 0.5 ) / anim_out;
807 QPointF topleft_next_field = start.topLeft() + ( end.topLeft() - start.topLeft() ) * percentage_next_filed;
808 QPointF bottomRight_next_field = start.bottomRight() + ( end.bottomRight() - start.bottomRight() ) * percentage_next_filed;
809 const QRectF r2( topleft_next_field, bottomRight_next_field );
810 QImage img1( width, height, QImage::Format_ARGB32 );
811 img1.fill( 0 );
812 QPainter p2;
813 p2.begin(&img1);
814 p2.setRenderHints( QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::HighQualityAntialiasing );
815 scene->render(&p2,source,r2, Qt::IgnoreAspectRatio );
816 p2.end();
817 int next_field_line = ( mlt_properties_get_int( producer_props, "top_field_first" ) ? 1 : 0 );
818 for (line = next_field_line ;line<height;line+=2){
819 memcpy(img.scanLine(line),img1.scanLine(line),img.bytesPerLine());
820 }
821 }
822 }
823 p1.end();
824 self->format = mlt_image_rgb24a;
825
826 convert_qimage_to_mlt_rgba(&img, self->rgba_image, width, height);
827 self->current_image = (uint8_t *) mlt_pool_alloc( image_size );
828 memcpy( self->current_image, self->rgba_image, image_size );
829 mlt_properties_set_data( producer_props, "_cached_buffer", self->rgba_image, image_size, mlt_pool_release, NULL );
830 mlt_properties_set_data( producer_props, "_cached_image", self->current_image, image_size, mlt_pool_release, NULL );
831 self->current_width = width;
832 self->current_height = height;
833
834 uint8_t *alpha = NULL;
835 if ( ( alpha = mlt_frame_get_alpha( frame ) ) )
836 {
837 self->current_alpha = (uint8_t*) mlt_pool_alloc( width * height );
838 memcpy( self->current_alpha, alpha, width * height );
839 mlt_properties_set_data( producer_props, "_cached_alpha", self->current_alpha, width * height, mlt_pool_release, NULL );
840 }
841 }
842
843 // Convert image to requested format
844 if ( format != mlt_image_none && format != mlt_image_glsl && format != self->format )
845 {
846 uint8_t *buffer = NULL;
847 if ( self->format != mlt_image_rgb24a ) {
848 // Image buffer was previously converted, revert to original rgba buffer
849 self->current_image = (uint8_t *) mlt_pool_alloc( image_size );
850 memcpy(self->current_image, self->rgba_image, image_size);
851 mlt_properties_set_data( producer_props, "_cached_image", self->current_image, image_size, mlt_pool_release, NULL );
852 self->format = mlt_image_rgb24a;
853 }
854
855 // First, set the image so it can be converted when we get it
856 mlt_frame_replace_image( frame, self->current_image, self->format, width, height );
857 mlt_frame_set_image( frame, self->current_image, image_size, NULL );
858 self->format = format;
859
860 // get_image will do the format conversion
861 mlt_frame_get_image( frame, &buffer, &format, &width, &height, 0 );
862
863 // cache copies of the image and alpha buffers
864 if ( buffer )
865 {
866 image_size = mlt_image_format_size( format, width, height, NULL );
867 self->current_image = (uint8_t*) mlt_pool_alloc( image_size );
868 memcpy( self->current_image, buffer, image_size );
869 mlt_properties_set_data( producer_props, "_cached_image", self->current_image, image_size, mlt_pool_release, NULL );
870 }
871 if ( ( buffer = mlt_frame_get_alpha( frame ) ) )
872 {
873 self->current_alpha = (uint8_t*) mlt_pool_alloc( width * height );
874 memcpy( self->current_alpha, buffer, width * height );
875 mlt_properties_set_data( producer_props, "_cached_alpha", self->current_alpha, width * height, mlt_pool_release, NULL );
876 }
877 }
878
879 pthread_mutex_unlock( &self->mutex );
880 mlt_properties_set_int( properties, "width", self->current_width );
881 mlt_properties_set_int( properties, "height", self->current_height );
882 }
883
884
885