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