#include "kdenlivetitle_wrapper.h"
-#include <QtGui/QImage>
-#include <QtGui/QPainter>
-#include <QtCore/QDebug>
-#include <QtGui/QApplication>
-#include <QtCore/QMutex>
-#include <QtGui/QGraphicsScene>
-#include <QtGui/QGraphicsTextItem>
-#include <QtSvg/QGraphicsSvgItem>
-#include <QtGui/QTextCursor>
-#include <QtGui/QStyleOptionGraphicsItem>
-
-#include <QtCore/QString>
-
-#include <QtXml/QDomElement>
-#include <QtCore/QRectF>
-#include <QtGui/QColor>
-#include <QtGui/QWidget>
+#include <QImage>
+#include <QPainter>
+#include <QDebug>
+#include <QApplication>
+#include <QMutex>
+#include <QGraphicsScene>
+#include <QGraphicsTextItem>
+#include <QGraphicsSvgItem>
+#include <QSvgRenderer>
+#include <QTextCursor>
+#include <QTextDocument>
+#include <QStyleOptionGraphicsItem>
+
+#include <QString>
+
+#include <QDomElement>
+#include <QRectF>
+#include <QColor>
+#include <QWidget>
#include <framework/mlt_log.h>
#if QT_VERSION >= 0x040600
-#include <QtGui/QGraphicsEffect>
-#include <QtGui/QGraphicsBlurEffect>
-#include <QtGui/QGraphicsDropShadowEffect>
+#include <QGraphicsEffect>
+#include <QGraphicsBlurEffect>
+#include <QGraphicsDropShadowEffect>
#endif
static QApplication *app = NULL;
+Q_DECLARE_METATYPE(QTextCursor);
class ImageItem: public QGraphicsItem
{
};
-QString rectTransform( QString s, QTransform t )
-{
- QStringList l = s.split( ',' );
- return QString::number(l.at(0).toDouble() * t.m11()) + ',' + QString::number(l.at(1).toDouble() * t.m22()) + ',' + QString::number(l.at(2).toDouble() * t.m11()) + ',' + QString::number(l.at(3).toDouble() * t.m22());
-}
-
-QString colorToString( const QColor& c )
-{
- QString ret = "%1,%2,%3,%4";
- ret = ret.arg( c.red() ).arg( c.green() ).arg( c.blue() ).arg( c.alpha() );
- return ret;
-}
-
-QString rectFToString( const QRectF& c )
-{
- QString ret = "%1,%2,%3,%4";
- ret = ret.arg( c.top() ).arg( c.left() ).arg( c.width() ).arg( c.height() );
- return ret;
-}
-
QRectF stringToRect( const QString & s )
{
QString replacementText = QString::fromUtf8(templateText);
doc.setContent(data);
QDomElement title = doc.documentElement();
-
+
// Check for invalid title
if ( title.isNull() || title.tagName() != "kdenlivetitle" ) return;
+ // Check title locale
+ if ( title.hasAttribute( "LC_NUMERIC" ) ) {
+ QString locale = title.attribute( "LC_NUMERIC" );
+ QLocale::setDefault( locale );
+ }
+
int originalWidth;
int originalHeight;
if ( title.hasAttribute("width") ) {
mlt_properties_set_int( producer_props, "_original_width", originalWidth );
mlt_properties_set_int( producer_props, "_original_height", originalHeight );
+ QDomNode node;
QDomNodeList items = title.elementsByTagName("item");
for ( int i = 0; i < items.count(); i++ )
{
QGraphicsItem *gitem = NULL;
- int zValue = items.item( i ).attributes().namedItem( "z-index" ).nodeValue().toInt();
+ node = items.item( i );
+ QDomNamedNodeMap nodeAttributes = node.attributes();
+ int zValue = nodeAttributes.namedItem( "z-index" ).nodeValue().toInt();
if ( zValue > -1000 )
{
- if ( items.item( i ).attributes().namedItem( "type" ).nodeValue() == "QGraphicsTextItem" )
+ if ( nodeAttributes.namedItem( "type" ).nodeValue() == "QGraphicsTextItem" )
{
- QDomNamedNodeMap txtProperties = items.item( i ).namedItem( "content" ).attributes();
+ QDomNamedNodeMap txtProperties = node.namedItem( "content" ).attributes();
QFont font( txtProperties.namedItem( "font" ).nodeValue() );
- QDomNode node = txtProperties.namedItem( "font-bold" );
- if ( !node.isNull() )
+ QDomNode propsNode = txtProperties.namedItem( "font-bold" );
+ if ( !propsNode.isNull() )
{
- // Old: Bold/Not bold.
- font.setBold( node.nodeValue().toInt() );
+ // Old: Bold/Not bold.
+ font.setBold( propsNode.nodeValue().toInt() );
}
else
{
// New: Font weight (QFont::)
font.setWeight( txtProperties.namedItem( "font-weight" ).nodeValue().toInt() );
}
- font.setItalic( txtProperties.namedItem( "font-italic" ).nodeValue().toInt() );
+ font.setItalic( txtProperties.namedItem( "font-italic" ).nodeValue().toInt() );
font.setUnderline( txtProperties.namedItem( "font-underline" ).nodeValue().toInt() );
// Older Kdenlive version did not store pixel size but point size
if ( txtProperties.namedItem( "font-pixel-size" ).isNull() )
else
font.setPixelSize( txtProperties.namedItem( "font-pixel-size" ).nodeValue().toInt() );
QColor col( stringToColor( txtProperties.namedItem( "font-color" ).nodeValue() ) );
- QString text = items.item( i ).namedItem( "content" ).firstChild().nodeValue();
+ QString text = node.namedItem( "content" ).firstChild().nodeValue();
if ( !replacementText.isEmpty() )
{
text = text.replace( "%s", replacementText );
}
QGraphicsTextItem *txt = scene->addText(text, font);
- txt->setDefaultTextColor( col );
+ if (txtProperties.namedItem("font-outline").nodeValue().toDouble()>0.0){
+ QTextDocument *doc = txt->document();
+ // Make sure some that the text item does not request refresh by itself
+ doc->blockSignals(true);
+ QTextCursor cursor(doc);
+ cursor.select(QTextCursor::Document);
+ QTextCharFormat format;
+ format.setTextOutline(
+ QPen(QColor( stringToColor( txtProperties.namedItem( "font-outline-color" ).nodeValue() ) ),
+ txtProperties.namedItem("font-outline").nodeValue().toDouble(),
+ Qt::SolidLine,Qt::RoundCap,Qt::RoundJoin)
+ );
+ format.setForeground(QBrush(col));
+
+ cursor.mergeCharFormat(format);
+ } else {
+ txt->setDefaultTextColor( col );
+ }
+
+ // Effects
+ if (!txtProperties.namedItem( "typewriter" ).isNull()) {
+ // typewriter effect
+ mlt_properties_set_int( producer_props, "_animated", 1 );
+ QStringList effetData = QStringList() << "typewriter" << text << txtProperties.namedItem( "typewriter" ).nodeValue();
+ txt->setData(0, effetData);
+ if ( !txtProperties.namedItem( "textwidth" ).isNull() )
+ txt->setData( 1, txtProperties.namedItem( "textwidth" ).nodeValue() );
+ }
+
if ( txtProperties.namedItem( "alignment" ).isNull() == false )
{
txt->setTextWidth( txt->boundingRect().width() );
- QTextCursor cur = txt->textCursor();
- QTextBlockFormat format = cur.blockFormat();
- format.setAlignment(( Qt::Alignment ) txtProperties.namedItem( "alignment" ).nodeValue().toInt() );
- cur.select( QTextCursor::Document );
- cur.setBlockFormat( format );
- txt->setTextCursor( cur );
- cur.clearSelection();
- txt->setTextCursor( cur );
+ QTextOption opt = txt->document()->defaultTextOption ();
+ opt.setAlignment(( Qt::Alignment ) txtProperties.namedItem( "alignment" ).nodeValue().toInt() );
+ txt->document()->setDefaultTextOption (opt);
}
if ( !txtProperties.namedItem( "kdenlive-axis-x-inverted" ).isNull() )
{
}
gitem = txt;
}
- else if ( items.item( i ).attributes().namedItem( "type" ).nodeValue() == "QGraphicsRectItem" )
+ else if ( nodeAttributes.namedItem( "type" ).nodeValue() == "QGraphicsRectItem" )
{
- QString rect = items.item( i ).namedItem( "content" ).attributes().namedItem( "rect" ).nodeValue();
- QString br_str = items.item( i ).namedItem( "content" ).attributes().namedItem( "brushcolor" ).nodeValue();
- QString pen_str = items.item( i ).namedItem( "content" ).attributes().namedItem( "pencolor" ).nodeValue();
- double penwidth = items.item( i ).namedItem( "content" ).attributes().namedItem( "penwidth") .nodeValue().toDouble();
- QGraphicsRectItem *rec = scene->addRect( stringToRect( rect ), QPen( QBrush( stringToColor( pen_str ) ), penwidth ), QBrush( stringToColor( br_str ) ) );
+ QString rect = node.namedItem( "content" ).attributes().namedItem( "rect" ).nodeValue();
+ QString br_str = node.namedItem( "content" ).attributes().namedItem( "brushcolor" ).nodeValue();
+ QString pen_str = node.namedItem( "content" ).attributes().namedItem( "pencolor" ).nodeValue();
+ double penwidth = node.namedItem( "content" ).attributes().namedItem( "penwidth") .nodeValue().toDouble();
+ QGraphicsRectItem *rec = scene->addRect( stringToRect( rect ), QPen( QBrush( stringToColor( pen_str ) ), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin ), QBrush( stringToColor( br_str ) ) );
gitem = rec;
}
- else if ( items.item( i ).attributes().namedItem( "type" ).nodeValue() == "QGraphicsPixmapItem" )
+ else if ( nodeAttributes.namedItem( "type" ).nodeValue() == "QGraphicsPixmapItem" )
{
- const QString url = items.item( i ).namedItem( "content" ).attributes().namedItem( "url" ).nodeValue();
- QImage img( url );
+ const QString url = node.namedItem( "content" ).attributes().namedItem( "url" ).nodeValue();
+ const QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
+ QImage img;
+ if (base64.isEmpty()){
+ img.load(url);
+ }else{
+ img.loadFromData(QByteArray::fromBase64(base64.toLatin1()));
+ }
ImageItem *rec = new ImageItem(img);
scene->addItem( rec );
gitem = rec;
}
- else if ( items.item( i ).attributes().namedItem( "type" ).nodeValue() == "QGraphicsSvgItem" )
+ else if ( nodeAttributes.namedItem( "type" ).nodeValue() == "QGraphicsSvgItem" )
{
- const QString url = items.item( i ).namedItem( "content" ).attributes().namedItem( "url" ).nodeValue();
- QGraphicsSvgItem *rec = new QGraphicsSvgItem(url);
- scene->addItem(rec);
- gitem = rec;
+ QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
+ QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
+ QGraphicsSvgItem *rec = NULL;
+ if (base64.isEmpty()){
+ rec = new QGraphicsSvgItem(url);
+ }else{
+ rec = new QGraphicsSvgItem();
+ QSvgRenderer *renderer= new QSvgRenderer(QByteArray::fromBase64(base64.toLatin1()), rec );
+ rec->setSharedRenderer(renderer);
+ }
+ if (rec){
+ scene->addItem(rec);
+ gitem = rec;
+ }
}
}
//pos and transform
if ( gitem )
{
- QPointF p( items.item( i ).namedItem( "position" ).attributes().namedItem( "x" ).nodeValue().toDouble(),
- items.item( i ).namedItem( "position" ).attributes().namedItem( "y" ).nodeValue().toDouble() );
+ QPointF p( node.namedItem( "position" ).attributes().namedItem( "x" ).nodeValue().toDouble(),
+ node.namedItem( "position" ).attributes().namedItem( "y" ).nodeValue().toDouble() );
gitem->setPos( p );
- gitem->setTransform( stringToTransform( items.item( i ).namedItem( "position" ).firstChild().firstChild().nodeValue() ) );
- int zValue = items.item( i ).attributes().namedItem( "z-index" ).nodeValue().toInt();
+ gitem->setTransform( stringToTransform( node.namedItem( "position" ).firstChild().firstChild().nodeValue() ) );
+ int zValue = nodeAttributes.namedItem( "z-index" ).nodeValue().toInt();
gitem->setZValue( zValue );
#if QT_VERSION >= 0x040600
{
// Obtain the producer
mlt_producer producer = &self->parent;
+ mlt_profile profile = mlt_service_profile ( MLT_PRODUCER_SERVICE( producer ) ) ;
mlt_properties producer_props = MLT_PRODUCER_PROPERTIES( producer );
// Obtain properties of frame
pthread_mutex_lock( &self->mutex );
// Check if user wants us to reload the image
- if ( force_refresh == 1 || width != self->current_width || height != self->current_height || mlt_properties_get( producer_props, "_endrect" ) != NULL )
+ if ( mlt_properties_get( producer_props, "_animated" ) != NULL || force_refresh == 1 || width != self->current_width || height != self->current_height || mlt_properties_get( producer_props, "_endrect" ) != NULL )
{
//mlt_cache_item_close( self->image_cache );
self->current_image = NULL;
// Warning: all Qt graphic objects (QRect, ...) must be initialized AFTER
// the QApplication is created, otherwise their will be NULL
- if (qApp) {
- app = qApp;
- }
- else {
-#ifdef linux
- if ( getenv("DISPLAY") == 0 )
- {
- mlt_log_panic( MLT_PRODUCER_SERVICE( producer ), "Error, cannot render titles without an X11 environment.\nPlease either run melt from an X session or use a fake X server like xvfb:\nxvfb-run -a melt (...)\n" );
- pthread_mutex_unlock( &self->mutex );
- exit(1);
- return;
+ if ( app == NULL ) {
+ if ( qApp ) {
+ app = qApp;
}
+ else {
+#ifdef linux
+ if ( getenv("DISPLAY") == 0 )
+ {
+ mlt_log_panic( MLT_PRODUCER_SERVICE( producer ), "Error, cannot render titles without an X11 environment.\nPlease either run melt from an X session or use a fake X server like xvfb:\nxvfb-run -a melt (...)\n" );
+ pthread_mutex_unlock( &self->mutex );
+ return;
+ }
#endif
- app = new QApplication( argc, argv );
- //fix to let the decimal point for every locale be: "."
- setlocale(LC_NUMERIC,"POSIX");
+ app = new QApplication( argc, argv );
+ const char *localename = mlt_properties_get_lcnumeric( MLT_SERVICE_PROPERTIES( MLT_PRODUCER_SERVICE( producer ) ) );
+ QLocale::setDefault( QLocale( localename ) );
+ }
+ qRegisterMetaType<QTextCursor>( "QTextCursor" );
}
scene = new QGraphicsScene();
+ scene->setItemIndexMethod( QGraphicsScene::NoIndex );
scene->setSceneRect(0, 0, mlt_properties_get_int( properties, "width" ), mlt_properties_get_int( properties, "height" ));
- loadFromXml( producer, scene, mlt_properties_get( producer_props, "xmldata" ), mlt_properties_get( producer_props, "templatetext" ) );
+ if ( mlt_properties_get( producer_props, "resource" ) && mlt_properties_get( producer_props, "resource" )[0] != '\0' )
+ {
+ // The title has a resource property, so we read all properties from the resource.
+ // Do not serialize the xmldata
+ loadFromXml( producer, scene, mlt_properties_get( producer_props, "_xmldata" ), mlt_properties_get( producer_props, "templatetext" ) );
+ }
+ else
+ {
+ // The title has no resource, all data should be serialized
+ loadFromXml( producer, scene, mlt_properties_get( producer_props, "xmldata" ), mlt_properties_get( producer_props, "templatetext" ) );
+
+ }
mlt_properties_set_data( producer_props, "qscene", scene, 0, ( mlt_destructor )qscene_delete, NULL );
}
if (start.isNull()) {
start = QRectF( 0, 0, originalWidth, originalHeight );
}
-
+
+ // Effects
+ QList <QGraphicsItem *> items = scene->items();
+ QGraphicsTextItem *titem = NULL;
+ for (int i = 0; i < items.count(); i++) {
+ titem = static_cast <QGraphicsTextItem*> ( items.at( i ) );
+ if (titem && !titem->data( 0 ).isNull()) {
+ QStringList params = titem->data( 0 ).toStringList();
+ if (params.at( 0 ) == "typewriter" ) {
+ // typewriter effect has 2 param values:
+ // the keystroke delay and a start offset, both in frames
+ QStringList values = params.at( 2 ).split( ";" );
+ int interval = qMax( 0, ( ( int ) position - values.at( 1 ).toInt()) / values.at( 0 ).toInt() );
+ QTextCursor cursor = titem->textCursor();
+ cursor.movePosition(QTextCursor::EndOfBlock);
+ // get the font format
+ QTextCharFormat format = cursor.charFormat();
+ cursor.select(QTextCursor::Document);
+ QString txt = params.at( 1 ).left( interval );
+ // If the string to insert is empty, insert a space / linebreak so that we don't loose
+ // formatting infos for the next iterations
+ int lines = params.at( 1 ).count( '\n' );
+ QString empty = " ";
+ for (int i = 0; i < lines; i++)
+ empty.append( "\n " );
+ cursor.insertText( txt.isEmpty() ? empty : txt, format );
+ if ( !titem->data( 1 ).isNull() )
+ titem->setTextWidth( titem->data( 1 ).toDouble() );
+ }
+ }
+ }
+
//must be extracted from kdenlive title
QImage img( width, height, QImage::Format_ARGB32 );
img.fill( 0 );
scene->render( &p1, source, end, Qt::IgnoreAspectRatio );
}
else {
- double percentage = position / anim_out;
+ double percentage = 0;
+ if ( position && anim_out )
+ percentage = position / anim_out;
QPointF topleft = start.topLeft() + ( end.topLeft() - start.topLeft() ) * percentage;
QPointF bottomRight = start.bottomRight() + ( end.bottomRight() - start.bottomRight() ) * percentage;
const QRectF r1( topleft, bottomRight );
scene->render( &p1, source, r1, Qt::IgnoreAspectRatio );
+ if ( profile && !profile->progressive ){
+ int line=0;
+ double percentage_next_filed = ( position + 0.5 ) / anim_out;
+ QPointF topleft_next_field = start.topLeft() + ( end.topLeft() - start.topLeft() ) * percentage_next_filed;
+ QPointF bottomRight_next_field = start.bottomRight() + ( end.bottomRight() - start.bottomRight() ) * percentage_next_filed;
+ const QRectF r2( topleft_next_field, bottomRight_next_field );
+ QImage img1( width, height, QImage::Format_ARGB32 );
+ img1.fill( 0 );
+ QPainter p2;
+ p2.begin(&img1);
+ p2.setRenderHints( QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::HighQualityAntialiasing );
+ scene->render(&p2,source,r2, Qt::IgnoreAspectRatio );
+ p2.end();
+ int next_field_line = ( mlt_properties_get_int( producer_props, "top_field_first" ) ? 1 : 0 );
+ for (line = next_field_line ;line<height;line+=2){
+ memcpy(img.scanLine(line),img1.scanLine(line),img.bytesPerLine());
+ }
+
+ }
}
p1.end();