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