]> git.sesse.net Git - mlt/blob - src/modules/qt/producer_qtext.cpp
ed7466473d89bf1a148be227169f0988acb119d7
[mlt] / src / modules / qt / producer_qtext.cpp
1 /*
2  * producer_qtext.c -- text generating producer
3  * Copyright (C) 2013 Brian Matherly
4  * Author: Brian Matherly <pez4brian@yahoo.com>
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 #include <framework/mlt.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <QApplication>
26 #include <QImage>
27 #include <QColor>
28 #include <QLocale>
29 #include <QPainter>
30 #include <QFont>
31 #include <QString>
32 #include <QTextCodec>
33 #include <QTextDecoder>
34
35 /** Init QT (if necessary)
36 */
37
38 static bool init_qt( mlt_producer producer )
39 {
40         int argc = 1;
41         char* argv[1];
42         argv[0] = (char*) "xxx";
43
44         if ( !qApp )
45         {
46 #ifdef linux
47                 if ( getenv("DISPLAY") == 0 )
48                 {
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" );
50                         return false;
51                 }
52 #endif
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 ) );
56         }
57         return true;
58 }
59
60 static void close_qimg( void* qimg )
61 {
62         delete static_cast<QImage*>( qimg );
63 }
64
65 static void close_qpath( void* qpath )
66 {
67         delete static_cast<QPainterPath*>( qpath );
68 }
69
70 static void copy_qimage_to_mlt_image( QImage* qImg, uint8_t* mImg )
71 {
72         int height = qImg->height();
73         int width = qImg->width();
74         int y = 0;
75
76         // convert qimage to mlt
77         y = height + 1;
78         while ( --y )
79         {
80                 QRgb* src = (QRgb*) qImg->scanLine( height - y );
81                 int x = width + 1;
82                 while ( --x )
83                 {
84                         *mImg++ = qRed( *src );
85                         *mImg++ = qGreen( *src );
86                         *mImg++ = qBlue( *src );
87                         *mImg++ = qAlpha( *src );
88                         src++;
89                 }
90         }
91 }
92
93 static void copy_image_to_alpha( uint8_t* image, uint8_t* alpha, int width, int height )
94 {
95         register int len = width * height;
96         // Extract the alpha mask from the RGBA image using Duff's Device
97         register uint8_t *s = image + 3; // start on the alpha component
98         register uint8_t *d = alpha;
99         register int n = ( len + 7 ) / 8;
100
101         switch ( len % 8 )
102         {
103                 case 0: do { *d++ = *s; s += 4;
104                 case 7:          *d++ = *s; s += 4;
105                 case 6:          *d++ = *s; s += 4;
106                 case 5:          *d++ = *s; s += 4;
107                 case 4:          *d++ = *s; s += 4;
108                 case 3:          *d++ = *s; s += 4;
109                 case 2:          *d++ = *s; s += 4;
110                 case 1:          *d++ = *s; s += 4;
111                                 }
112                                 while ( --n > 0 );
113         }
114 }
115
116 /** Check if the qpath needs to be regenerated. Return true if regeneration is required.
117 */
118
119 static bool check_qpath( mlt_properties producer_properties )
120 {
121         #define MAX_SIG 500
122         char new_path_sig[MAX_SIG];
123
124         // Generate a signature that represents the current properties
125         snprintf( new_path_sig, MAX_SIG, "%s%s%s%s%s%s%s%s%s%s%s",
126                         mlt_properties_get( producer_properties, "text" ),
127                         mlt_properties_get( producer_properties, "fgcolour" ),
128                         mlt_properties_get( producer_properties, "bgcolour" ),
129                         mlt_properties_get( producer_properties, "olcolour" ),
130                         mlt_properties_get( producer_properties, "outline" ),
131                         mlt_properties_get( producer_properties, "align" ),
132                         mlt_properties_get( producer_properties, "pad" ),
133                         mlt_properties_get( producer_properties, "size" ),
134                         mlt_properties_get( producer_properties, "style" ),
135                         mlt_properties_get( producer_properties, "weight" ),
136                         mlt_properties_get( producer_properties, "encoding" ) );
137         new_path_sig[ MAX_SIG - 1 ] = '\0';
138
139         // Check if the properties have changed by comparing this signature with the
140         // last one.
141         char* last_path_sig = mlt_properties_get( producer_properties, "_path_sig" );
142
143         if( !last_path_sig || strcmp( new_path_sig, last_path_sig ) )
144         {
145                 mlt_properties_set( producer_properties, "_path_sig", new_path_sig );
146                 return true;
147         }
148         return false;
149 }
150
151 static void generate_qpath( mlt_properties producer_properties )
152 {
153         QPainterPath* qPath = static_cast<QPainterPath*>( mlt_properties_get_data( producer_properties, "_qpath", NULL ) );
154         int outline = mlt_properties_get_int( producer_properties, "outline" );
155         char* align = mlt_properties_get( producer_properties, "align" );
156         char* style = mlt_properties_get( producer_properties, "style" );
157         char* text = mlt_properties_get( producer_properties, "text" );
158         char* encoding = mlt_properties_get( producer_properties, "encoding" );
159         int pad = mlt_properties_get_int( producer_properties, "pad" );
160         int offset = pad + ( outline / 2 );
161         int width = 0;
162         int height = 0;
163
164         // Make the path empty
165         *qPath = QPainterPath();
166
167         // Get the strings to display
168         QTextCodec *codec = QTextCodec::codecForName( encoding );
169         QTextDecoder *decoder = codec->makeDecoder();
170         QString s = decoder->toUnicode( text );
171         delete decoder;
172         QStringList lines = s.split( "\n" );
173
174         // Configure the font
175         QFont font;
176         font.setPixelSize( mlt_properties_get_int( producer_properties, "size" ) );
177         font.setFamily( mlt_properties_get( producer_properties, "family" ) );
178         font.setWeight( ( mlt_properties_get_int( producer_properties, "weight" ) / 10 ) -1 );
179         switch( style[0] )
180         {
181         case 'i':
182         case 'I':
183                 font.setStyle( QFont::StyleItalic );
184                 break;
185         }
186         QFontMetrics fm( font );
187
188         // Determine the text rectangle size
189         height = fm.lineSpacing() * lines.size();
190         for( int i = 0; i < lines.size(); ++i )
191         {
192                 int line_width = fm.width( lines.at(i) );
193                 if( line_width > width ) width = line_width;
194         }
195
196         // Lay out the text in the path
197         int x = 0;
198         int y = fm.ascent() + 1 + offset;
199         for( int i = 0; i < lines.size(); ++i )
200         {
201                 QString line = lines.at(i);
202                 x = offset;
203                 switch( align[0] )
204                 {
205                         default:
206                         case 'l':
207                         case 'L':
208                                 break;
209                         case 'c':
210                         case 'C':
211                                 x += ( width - fm.width( line ) ) / 2;
212                                 break;
213                         case 'r':
214                         case 'R':
215                                 x += width - fm.width( line );
216                                 break;
217                 }
218                 qPath->addText( x, y, font, line );
219                 y += fm.lineSpacing();
220         }
221
222         // Account for outline and pad
223         width += offset * 2;
224         height += offset * 2;
225         // Sanity check
226         if( width == 0 ) width = 1;
227         if( height == 0 ) height = 1;
228         mlt_properties_set_int( producer_properties, "meta.media.width", width );
229         mlt_properties_set_int( producer_properties, "meta.media.height", height );
230 }
231
232 static bool check_qimage( mlt_properties frame_properties )
233 {
234         mlt_producer producer = static_cast<mlt_producer>( mlt_properties_get_data( frame_properties, "_producer_qtext", NULL ) );
235         mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
236         QImage* qImg = static_cast<QImage*>( mlt_properties_get_data( producer_properties, "_qimg", NULL ) );
237         QSize target_size( mlt_properties_get_int( frame_properties, "rescale_width" ),
238                                            mlt_properties_get_int( frame_properties, "rescale_height" ) );
239         QSize native_size( mlt_properties_get_int( frame_properties, "meta.media.width" ),
240                                            mlt_properties_get_int( frame_properties, "meta.media.height" ) );
241
242         // Check if the last image signature is different from the path signature
243         // for this frame.
244         char* last_img_sig = mlt_properties_get( producer_properties, "_img_sig" );
245         char* path_sig = mlt_properties_get( frame_properties, "_path_sig" );
246
247         if( !last_img_sig || strcmp( path_sig, last_img_sig ) )
248         {
249                 mlt_properties_set( producer_properties, "_img_sig", path_sig );
250                 return true;
251         }
252
253         // Check if the last image size matches the requested image size
254         QSize output_size = target_size;
255         if( output_size.isEmpty() )
256         {
257                 output_size = native_size;
258         }
259         if( output_size != qImg->size() )
260         {
261                 return true;
262         }
263
264         return false;
265 }
266
267 static void generate_qimage( mlt_properties frame_properties )
268 {
269         mlt_producer producer = static_cast<mlt_producer>( mlt_properties_get_data( frame_properties, "_producer_qtext", NULL ) );
270         mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
271         QImage* qImg = static_cast<QImage*>( mlt_properties_get_data( producer_properties, "_qimg", NULL ) );
272         QSize target_size( mlt_properties_get_int( frame_properties, "rescale_width" ),
273                                            mlt_properties_get_int( frame_properties, "rescale_height" ) );
274         QSize native_size( mlt_properties_get_int( frame_properties, "meta.media.width" ),
275                                            mlt_properties_get_int( frame_properties, "meta.media.height" ) );
276         QPainterPath* qPath = static_cast<QPainterPath*>( mlt_properties_get_data( frame_properties, "_qpath", NULL ) );
277         mlt_color bg_color = mlt_properties_get_color( frame_properties, "_bgcolour" );
278         mlt_color fg_color = mlt_properties_get_color( frame_properties, "_fgcolour" );
279         mlt_color ol_color = mlt_properties_get_color( frame_properties, "_olcolour" );
280         int outline = mlt_properties_get_int( frame_properties, "_outline" );
281         qreal sx = 1.0;
282         qreal sy = 1.0;
283
284         // Create a new image and set up scaling
285         if( !target_size.isEmpty() && target_size != native_size )
286         {
287                 *qImg = QImage( target_size, QImage::Format_ARGB32 );
288                 sx = (qreal)target_size.width() / (qreal)native_size.width();
289                 sy = (qreal)target_size.height() / (qreal)native_size.height();
290         }
291         else
292         {
293                 *qImg = QImage( native_size, QImage::Format_ARGB32 );
294         }
295         qImg->fill( QColor( bg_color.r, bg_color.g, bg_color.b, bg_color.a ).rgba() );
296
297         // Draw the text
298         QPainter painter( qImg );
299         // Scale the painter rather than the image for better looking results.
300         painter.scale( sx, sy );
301         painter.setRenderHints( QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::HighQualityAntialiasing );
302
303         QPen pen;
304         pen.setWidth( outline );
305         if( outline )
306         {
307                 pen.setColor( QColor( ol_color.r, ol_color.g, ol_color.b, ol_color.a ) );
308         }
309         else
310         {
311                 pen.setColor( QColor( bg_color.r, bg_color.g, bg_color.b, bg_color.a ) );
312         }
313         painter.setPen( pen );
314         QBrush brush( QColor( fg_color.r, fg_color.g, fg_color.b, fg_color.a ) );
315         painter.setBrush( brush );
316         painter.drawPath( *qPath );
317 }
318
319 static int producer_get_image( mlt_frame frame, uint8_t** buffer, mlt_image_format* format, int* width, int* height, int writable )
320 {
321         mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame );
322         mlt_producer producer = static_cast<mlt_producer>( mlt_properties_get_data( frame_properties, "_producer_qtext", NULL ) );
323         mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
324         int img_size = 0;
325         int alpha_size = 0;
326         uint8_t* alpha = NULL;
327         QImage* qImg = static_cast<QImage*>( mlt_properties_get_data( producer_properties, "_qimg", NULL ) );
328
329         mlt_service_lock( MLT_PRODUCER_SERVICE( producer ) );
330
331         // Regenerate the qimage if necessary
332         if( check_qimage( frame_properties ) == true )
333         {
334                 generate_qimage( frame_properties );
335         }
336
337         *format = mlt_image_rgb24a;
338         *width = qImg->width();
339         *height = qImg->height();
340
341         // Allocate and fill the image buffer
342         img_size = mlt_image_format_size( *format, *width, *height, NULL );
343         *buffer = static_cast<uint8_t*>( mlt_pool_alloc( img_size ) );
344         copy_qimage_to_mlt_image( qImg, *buffer );
345
346         mlt_service_unlock( MLT_PRODUCER_SERVICE( producer ) );
347
348         // Allocate and fill the alpha buffer
349         alpha_size = *width * *height;
350         alpha = static_cast<uint8_t*>( mlt_pool_alloc( alpha_size ) );
351         copy_image_to_alpha( *buffer, alpha, *width, *height );
352
353         // Update the frame
354         mlt_frame_set_image( frame, *buffer, img_size, mlt_pool_release );
355         mlt_frame_set_alpha( frame, alpha, alpha_size, mlt_pool_release );
356         mlt_properties_set_int( frame_properties, "width", *width );
357         mlt_properties_set_int( frame_properties, "height", *height );
358
359         return 0;
360 }
361
362 static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index )
363 {
364         // Generate a frame
365         *frame = mlt_frame_init( MLT_PRODUCER_SERVICE( producer ) );
366
367         if ( *frame != NULL )
368         {
369                 mlt_properties frame_properties = MLT_FRAME_PROPERTIES( *frame );
370                 mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
371
372                 // Regenerate the QPainterPath if necessary
373                 if( check_qpath( producer_properties ) )
374                 {
375                         generate_qpath( producer_properties );
376                 }
377
378                 // Give the frame a copy of the painter path
379                 QPainterPath* prodPath = static_cast<QPainterPath*>( mlt_properties_get_data( producer_properties, "_qpath", NULL ) );
380                 QPainterPath* framePath = new QPainterPath( *prodPath );
381                 mlt_properties_set_data( frame_properties, "_qpath", static_cast<void*>( framePath ), 0, close_qpath, NULL );
382
383                 // Pass properties to the frame that will be needed to render the path
384                 mlt_properties_set( frame_properties, "_path_sig", mlt_properties_get( producer_properties, "_path_sig" ) );
385                 mlt_properties_set( frame_properties, "_bgcolour", mlt_properties_get( producer_properties, "bgcolour" ) );
386                 mlt_properties_set( frame_properties, "_fgcolour", mlt_properties_get( producer_properties, "fgcolour" ) );
387                 mlt_properties_set( frame_properties, "_olcolour", mlt_properties_get( producer_properties, "olcolour" ) );
388                 mlt_properties_set( frame_properties, "_outline", mlt_properties_get( producer_properties, "outline" ) );
389                 mlt_properties_set_data( frame_properties, "_producer_qtext", static_cast<void*>( producer ), 0, NULL, NULL );
390
391                 // Set frame properties
392                 mlt_properties_set_int( frame_properties, "progressive", 1 );
393                 double force_ratio = mlt_properties_get_double( producer_properties, "force_aspect_ratio" );
394                 if ( force_ratio > 0.0 )
395                         mlt_properties_set_double( frame_properties, "aspect_ratio", force_ratio );
396                 else
397                         mlt_properties_set_double( frame_properties, "aspect_ratio", 1.0);
398
399                 // Update time code on the frame
400                 mlt_frame_set_position( *frame, mlt_producer_position( producer ) );
401
402                 // Configure callbacks
403                 mlt_frame_push_get_image( *frame, producer_get_image );
404         }
405
406         // Calculate the next time code
407         mlt_producer_prepare_next( producer );
408
409         return 0;
410 }
411
412 static void producer_close( mlt_producer producer )
413 {
414         producer->close = NULL;
415         mlt_producer_close( producer );
416         free( producer );
417 }
418
419 /** Initialize.
420 */
421 extern "C" {
422
423 mlt_producer producer_qtext_init( mlt_profile profile, mlt_service_type type, const char *id, char *filename )
424 {
425         // Create a new producer object
426         mlt_producer producer = mlt_producer_new( profile );
427         mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
428
429         // Initialize the producer
430         if ( producer )
431         {
432                 if( init_qt( producer ) == false )
433                 {
434                         mlt_producer_close( producer );
435                         return NULL;
436                 }
437
438                 mlt_properties_set( producer_properties, "text",     "" );
439                 mlt_properties_set( producer_properties, "fgcolour", "0xffffffff" );
440                 mlt_properties_set( producer_properties, "bgcolour", "0x00000000" );
441                 mlt_properties_set( producer_properties, "olcolour", "0x00000000" );
442                 mlt_properties_set( producer_properties, "outline",  "0" );
443                 mlt_properties_set( producer_properties, "align",    "left" );
444                 mlt_properties_set( producer_properties, "pad",      "0" );
445                 mlt_properties_set( producer_properties, "family",   "Sans" );
446                 mlt_properties_set( producer_properties, "size",     "48" );
447                 mlt_properties_set( producer_properties, "style",    "normal" );
448                 mlt_properties_set( producer_properties, "weight",   "400" );
449                 mlt_properties_set( producer_properties, "encoding", "UTF-8" );
450
451                 // Parse the filename argument
452                 if ( filename == NULL ||
453                      !strcmp( filename, "" ) ||
454                          strstr( filename, "<producer>" ) )
455                 {
456                 }
457                 else if( filename[ 0 ] == '+' || strstr( filename, "/+" ) )
458                 {
459                         char *copy = strdup( filename + 1 );
460                         char *tmp = copy;
461                         if ( strstr( tmp, "/+" ) )
462                                 tmp = strstr( tmp, "/+" ) + 2;
463                         if ( strrchr( tmp, '.' ) )
464                                 ( *strrchr( tmp, '.' ) ) = '\0';
465                         while ( strchr( tmp, '~' ) )
466                                 ( *strchr( tmp, '~' ) ) = '\n';
467                         mlt_properties_set( producer_properties, "text", tmp );
468                         mlt_properties_set( producer_properties, "resource", filename );
469                         free( copy );
470                 }
471                 else
472                 {
473                         FILE *f = fopen( filename, "r" );
474                         if ( f != NULL )
475                         {
476                                 char line[81];
477                                 char *tmp = NULL;
478                                 size_t size = 0;
479                                 line[80] = '\0';
480
481                                 while ( fgets( line, 80, f ) )
482                                 {
483                                         size += strlen( line ) + 1;
484                                         if ( tmp )
485                                         {
486                                                 tmp = (char*)realloc( tmp, size );
487                                                 if ( tmp )
488                                                         strcat( tmp, line );
489                                         }
490                                         else
491                                         {
492                                                 tmp = strdup( line );
493                                         }
494                                 }
495                                 fclose( f );
496
497                                 if ( tmp && tmp[ strlen( tmp ) - 1 ] == '\n' )
498                                         tmp[ strlen( tmp ) - 1 ] = '\0';
499
500                                 if ( tmp )
501                                         mlt_properties_set( producer_properties, "text", tmp );
502                                 mlt_properties_set( producer_properties, "resource", filename );
503                                 free( tmp );
504                         }
505                 }
506
507                 // Create QT objects to be reused.
508                 mlt_properties_set_data( producer_properties, "_qimg", static_cast<void*>( new QImage() ), 0, close_qimg, NULL );
509                 mlt_properties_set_data( producer_properties, "_qpath", static_cast<void*>( new QPainterPath() ), 0, close_qpath, NULL );
510
511                 // Callback registration
512                 producer->get_frame = producer_get_frame;
513                 producer->close = ( mlt_destructor )producer_close;
514         }
515
516         return producer;
517 }
518
519 }