2 * producer_qtext.c -- text generating producer
3 * Copyright (C) 2013 Brian Matherly
4 * Author: Brian Matherly <pez4brian@yahoo.com>
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.
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.
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 #include <framework/mlt.h>
25 #include <QApplication>
33 #include <QTextDecoder>
35 /** Init QT (if necessary)
38 static bool init_qt( mlt_producer producer )
42 argv[0] = (char*) "xxx";
47 if ( getenv("DISPLAY") == 0 )
49 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" );
53 new QApplication( argc, argv );
54 const char *localename = mlt_properties_get_lcnumeric( MLT_SERVICE_PROPERTIES( MLT_PRODUCER_SERVICE( producer ) ) );
55 QLocale::setDefault( QLocale( localename ) );
60 /** Check for change to property. Copy to private if changed. Return true if private changed.
63 static bool update_private_property( mlt_properties producer_properties, const char* pub, const char* priv )
65 char* pub_val = mlt_properties_get( producer_properties, pub );
66 char* priv_val = mlt_properties_get( producer_properties, priv );
70 // Can't use public value
73 else if( priv_val == NULL || strcmp( pub_val, priv_val ) )
75 mlt_properties_set( producer_properties, priv, pub_val );
82 /** Check if the qpath needs to be regenerated. Return true if regeneration is required.
85 static bool check_qpath( mlt_properties producer_properties )
88 QPainterPath* qPath = static_cast<QPainterPath*>( mlt_properties_get_data( producer_properties, "_qpath", NULL ) );
90 // Check if any properties changed.
91 stale |= update_private_property( producer_properties, "text", "_text" );
92 stale |= update_private_property( producer_properties, "fgcolour", "_fgcolour" );
93 stale |= update_private_property( producer_properties, "bgcolour", "_bgcolour" );
94 stale |= update_private_property( producer_properties, "olcolour", "_olcolour" );
95 stale |= update_private_property( producer_properties, "outline", "_outline" );
96 stale |= update_private_property( producer_properties, "align", "_align" );
97 stale |= update_private_property( producer_properties, "pad", "_pad" );
98 stale |= update_private_property( producer_properties, "size", "_size" );
99 stale |= update_private_property( producer_properties, "style", "_style" );
100 stale |= update_private_property( producer_properties, "weight", "_weight" );
101 stale |= update_private_property( producer_properties, "encoding", "_encoding" );
103 // Make sure qPath is valid.
104 stale |= qPath->isEmpty();
109 static void generate_qpath( mlt_properties producer_properties )
111 QImage* qImg = static_cast<QImage*>( mlt_properties_get_data( producer_properties, "_qimg", NULL ) );
112 QPainterPath* qPath = static_cast<QPainterPath*>( mlt_properties_get_data( producer_properties, "_qpath", NULL ) );
113 int outline = mlt_properties_get_int( producer_properties, "_outline" );
114 char* align = mlt_properties_get( producer_properties, "_align" );
115 char* style = mlt_properties_get( producer_properties, "_style" );
116 char* text = mlt_properties_get( producer_properties, "_text" );
117 char* encoding = mlt_properties_get( producer_properties, "_encoding" );
118 int pad = mlt_properties_get_int( producer_properties, "_pad" );
119 int offset = pad + ( outline / 2 );
123 // Make the image invalid. It must be regenerated from the new path.
125 // Make the path empty
126 *qPath = QPainterPath();
128 // Get the strings to display
129 QTextCodec *codec = QTextCodec::codecForName( encoding );
130 QTextDecoder *decoder = codec->makeDecoder();
131 QString s = decoder->toUnicode( text );
133 QStringList lines = s.split( "\n" );
135 // Configure the font
137 font.setPixelSize( mlt_properties_get_int( producer_properties, "_size" ) );
138 font.setFamily( mlt_properties_get( producer_properties, "_family" ) );
139 font.setWeight( ( mlt_properties_get_int( producer_properties, "_weight" ) / 10 ) -1 );
144 font.setStyle( QFont::StyleItalic );
147 QFontMetrics fm( font );
149 // Determine the text rectangle size
150 height = fm.lineSpacing() * lines.size();
151 for( int i = 0; i < lines.size(); ++i )
153 int line_width = fm.width( lines.at(i) );
154 if( line_width > width ) width = line_width;
157 // Lay out the text in the path
159 int y = fm.ascent() + 1 + offset;
160 for( int i = 0; i < lines.size(); ++i )
162 QString line = lines.at(i);
172 x += ( width - fm.width( line ) ) / 2;
176 x += width - fm.width( line );
179 qPath->addText( x, y, font, line );
180 y += fm.lineSpacing();
183 // Account for outline and pad
185 height += offset * 2;
187 if( width == 0 ) width = 1;
188 if( height == 0 ) height = 1;
189 mlt_properties_set_int( producer_properties, "meta.media.width", width );
190 mlt_properties_set_int( producer_properties, "meta.media.height", height );
193 static void generate_qimage( mlt_properties producer_properties, QSize target_size )
195 QImage* qImg = static_cast<QImage*>( mlt_properties_get_data( producer_properties, "_qimg", NULL ) );
196 QPainterPath* qPath = static_cast<QPainterPath*>( mlt_properties_get_data( producer_properties, "_qpath", NULL ) );
197 mlt_color bg_color = mlt_properties_get_color( producer_properties, "_bgcolour" );
198 mlt_color fg_color = mlt_properties_get_color( producer_properties, "_fgcolour" );
199 mlt_color ol_color = mlt_properties_get_color( producer_properties, "_olcolour" );
200 int outline = mlt_properties_get_int( producer_properties, "_outline" );
201 QSize native_size( mlt_properties_get_int( producer_properties, "meta.media.width" ),
202 mlt_properties_get_int( producer_properties, "meta.media.height" ) );
206 // Create a new image and set up scaling
207 if( !target_size.isEmpty() && target_size != native_size )
209 *qImg = QImage( target_size, QImage::Format_ARGB32 );
210 sx = (qreal)target_size.width() / (qreal)native_size.width();
211 sy = (qreal)target_size.height() / (qreal)native_size.height();
215 *qImg = QImage( native_size, QImage::Format_ARGB32 );
219 QPainter painter( qImg );
220 // Scale the painter rather than the image for better looking results.
221 painter.scale( sx, sy );
222 painter.setRenderHints( QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::HighQualityAntialiasing );
223 painter.fillRect ( 0, 0, qImg->width(), qImg->height(), QColor( bg_color.r, bg_color.g, bg_color.b, bg_color.a ) );
225 pen.setWidth( outline );
228 pen.setColor( QColor( ol_color.r, ol_color.g, ol_color.b, ol_color.a ) );
232 pen.setColor( QColor( bg_color.r, bg_color.g, bg_color.b, bg_color.a ) );
234 painter.setPen( pen );
235 QBrush brush( QColor( fg_color.r, fg_color.g, fg_color.b, fg_color.a ) );
236 painter.setBrush( brush );
237 painter.drawPath( *qPath );
240 static void copy_qimage_to_mlt_image( QImage* qImg, uint8_t* mImg )
242 int height = qImg->height();
243 int width = qImg->width();
246 // convert qimage to mlt
250 QRgb* src = (QRgb*) qImg->scanLine( height - y );
254 *mImg++ = qRed( *src );
255 *mImg++ = qGreen( *src );
256 *mImg++ = qBlue( *src );
257 *mImg++ = qAlpha( *src );
263 static void copy_image_to_alpha( uint8_t* image, uint8_t* alpha, int width, int height )
265 register int len = width * height;
266 // Extract the alpha mask from the RGBA image using Duff's Device
267 register uint8_t *s = image + 3; // start on the alpha component
268 register uint8_t *d = alpha;
269 register int n = ( len + 7 ) / 8;
273 case 0: do { *d++ = *s; s += 4;
274 case 7: *d++ = *s; s += 4;
275 case 6: *d++ = *s; s += 4;
276 case 5: *d++ = *s; s += 4;
277 case 4: *d++ = *s; s += 4;
278 case 3: *d++ = *s; s += 4;
279 case 2: *d++ = *s; s += 4;
280 case 1: *d++ = *s; s += 4;
286 static bool check_qimage( mlt_properties producer_properties, QSize target_size )
288 QImage* qImg = static_cast<QImage*>( mlt_properties_get_data( producer_properties, "_qimg", NULL ) );
295 QSize output_size = target_size;
296 if( output_size.isEmpty() )
298 output_size.setWidth( mlt_properties_get_int( producer_properties, "meta.media.width" ) );
299 output_size.setHeight( mlt_properties_get_int( producer_properties, "meta.media.height" ) );
302 if( output_size != qImg->size() )
310 static int producer_get_image( mlt_frame frame, uint8_t** buffer, mlt_image_format* format, int* width, int* height, int writable )
312 mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame );
313 mlt_producer producer = static_cast<mlt_producer>( mlt_properties_get_data( frame_properties, "_producer_qtext", NULL ) );
314 mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
317 uint8_t* alpha = NULL;
320 // Detect rescale request
321 QSize target_size( mlt_properties_get_int( frame_properties, "rescale_width" ),
322 mlt_properties_get_int( frame_properties, "rescale_height" ) );
324 // Regenerate the qimage if necessary
325 mlt_service_lock( MLT_PRODUCER_SERVICE( producer ) );
327 if( check_qimage( producer_properties, target_size ) == true )
329 generate_qimage( producer_properties, target_size );
332 qImg = static_cast<QImage*>( mlt_properties_get_data( producer_properties, "_qimg", NULL ) );
334 *format = mlt_image_rgb24a;
335 *width = qImg->width();
336 *height = qImg->height();
338 // Allocate and fill the image buffer
339 img_size = mlt_image_format_size( *format, *width, *height, NULL );
340 *buffer = static_cast<uint8_t*>( mlt_pool_alloc( img_size ) );
341 copy_qimage_to_mlt_image( qImg, *buffer );
343 mlt_service_unlock( MLT_PRODUCER_SERVICE( producer ) );
345 // Allocate and fill the alpha buffer
346 alpha_size = *width * *height;
347 alpha = static_cast<uint8_t*>( mlt_pool_alloc( alpha_size ) );
348 copy_image_to_alpha( *buffer, alpha, *width, *height );
351 mlt_frame_set_image( frame, *buffer, img_size, mlt_pool_release );
352 mlt_frame_set_alpha( frame, alpha, alpha_size, mlt_pool_release );
353 mlt_properties_set_int( frame_properties, "width", *width );
354 mlt_properties_set_int( frame_properties, "height", *height );
359 static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index )
362 *frame = mlt_frame_init( MLT_PRODUCER_SERVICE( producer ) );
364 if ( *frame != NULL )
366 // Obtain properties of frame
367 mlt_properties frame_properties = MLT_FRAME_PROPERTIES( *frame );
368 mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
370 if( check_qpath( producer_properties ) )
372 generate_qpath( producer_properties );
375 // Save the producer to be used later
376 mlt_properties_set_data( frame_properties, "_producer_qtext", static_cast<void*>( producer ), 0, NULL, NULL );
378 // Set frame properties
379 mlt_properties_set_int( frame_properties, "progressive", 1 );
380 double force_ratio = mlt_properties_get_double( producer_properties, "force_aspect_ratio" );
381 if ( force_ratio > 0.0 )
382 mlt_properties_set_double( frame_properties, "aspect_ratio", force_ratio );
384 mlt_properties_set_double( frame_properties, "aspect_ratio", 1.0);
386 // Update time code on the frame
387 mlt_frame_set_position( *frame, mlt_producer_position( producer ) );
389 // Configure callbacks
390 mlt_frame_push_get_image( *frame, producer_get_image );
393 // Calculate the next time code
394 mlt_producer_prepare_next( producer );
399 static void producer_close( mlt_producer producer )
401 mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
403 QImage* qImg = static_cast<QImage*>( mlt_properties_get_data( producer_properties, "_qimg", NULL ) );
406 QPainterPath* qPath = static_cast<QPainterPath*>( mlt_properties_get_data( producer_properties, "_qpath", NULL ) );
409 producer->close = NULL;
411 mlt_producer_close( producer );
419 mlt_producer producer_qtext_init( mlt_profile profile, mlt_service_type type, const char *id, char *filename )
421 // Create a new producer object
422 mlt_producer producer = mlt_producer_new( profile );
423 mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
425 // Initialize the producer
428 if( init_qt( producer ) == false )
430 mlt_producer_close( producer );
434 mlt_properties_set( producer_properties, "text", "" );
435 mlt_properties_set( producer_properties, "fgcolour", "0xffffffff" );
436 mlt_properties_set( producer_properties, "bgcolour", "0x00000000" );
437 mlt_properties_set( producer_properties, "olcolour", "0x00000000" );
438 mlt_properties_set( producer_properties, "outline", "0" );
439 mlt_properties_set( producer_properties, "align", "left" );
440 mlt_properties_set( producer_properties, "pad", "0" );
441 mlt_properties_set( producer_properties, "family", "Sans" );
442 mlt_properties_set( producer_properties, "size", "48" );
443 mlt_properties_set( producer_properties, "style", "normal" );
444 mlt_properties_set( producer_properties, "weight", "400" );
445 mlt_properties_set( producer_properties, "encoding", "UTF-8" );
447 // Parse the filename argument
448 if ( filename == NULL ||
449 !strcmp( filename, "" ) ||
450 strstr( filename, "<producer>" ) )
453 else if( filename[ 0 ] == '+' || strstr( filename, "/+" ) )
455 char *copy = strdup( filename + 1 );
457 if ( strstr( tmp, "/+" ) )
458 tmp = strstr( tmp, "/+" ) + 2;
459 if ( strrchr( tmp, '.' ) )
460 ( *strrchr( tmp, '.' ) ) = '\0';
461 while ( strchr( tmp, '~' ) )
462 ( *strchr( tmp, '~' ) ) = '\n';
463 mlt_properties_set( producer_properties, "text", tmp );
464 mlt_properties_set( producer_properties, "resource", filename );
469 FILE *f = fopen( filename, "r" );
477 while ( fgets( line, 80, f ) )
479 size += strlen( line ) + 1;
482 tmp = (char*)realloc( tmp, size );
488 tmp = strdup( line );
493 if ( tmp && tmp[ strlen( tmp ) - 1 ] == '\n' )
494 tmp[ strlen( tmp ) - 1 ] = '\0';
497 mlt_properties_set( producer_properties, "text", tmp );
498 mlt_properties_set( producer_properties, "resource", filename );
503 // Create QT objects to be reused.
504 QImage* qImg = new QImage();
505 mlt_properties_set_data( producer_properties, "_qimg", static_cast<void*>( qImg ), 0, NULL, NULL );
506 QPainterPath* qPath = new QPainterPath();
507 mlt_properties_set_data( producer_properties, "_qpath", static_cast<void*>( qPath ), 0, NULL, NULL );
509 // Callback registration
510 producer->get_frame = producer_get_frame;
511 producer->close = ( mlt_destructor )producer_close;