]> git.sesse.net Git - mlt/blob - src/modules/qimage/producer_qtext.cpp
abf2b30848249bf98df0753c8766692028304616
[mlt] / src / modules / qimage / 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 /** Check for change to property. Copy to private if changed. Return true if private changed.
61 */
62
63 static bool update_private_property( mlt_properties producer_properties, const char* pub, const char* priv )
64 {
65         char* pub_val = mlt_properties_get( producer_properties, pub );
66         char* priv_val = mlt_properties_get( producer_properties, priv );
67
68         if( pub_val == NULL )
69         {
70                 // Can't use public value
71                 return false;
72         }
73         else if( priv_val == NULL || strcmp( pub_val, priv_val ) )
74         {
75                 mlt_properties_set( producer_properties, priv, pub_val );
76                 return true;
77         }
78
79         return false;
80 }
81
82 /** Check if the qpath needs to be regenerated. Return true if regeneration is required.
83 */
84
85 static bool check_qpath( mlt_properties producer_properties )
86 {
87         bool stale = false;
88         QPainterPath* qPath = static_cast<QPainterPath*>( mlt_properties_get_data( producer_properties, "_qpath", NULL ) );
89
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" );
102
103         // Make sure qPath is valid.
104         stale |= qPath->isEmpty();
105
106         return stale;
107 }
108
109 static void generate_qpath( mlt_properties producer_properties )
110 {
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 );
120         int width = 0;
121         int height = 0;
122
123         // Make the image invalid. It must be regenerated from the new path.
124         *qImg = QImage();
125         // Make the path empty
126         *qPath = QPainterPath();
127
128         // Get the strings to display
129         QTextCodec *codec = QTextCodec::codecForName( encoding );
130         QTextDecoder *decoder = codec->makeDecoder();
131         QString s = decoder->toUnicode( text );
132         delete decoder;
133         QStringList lines = s.split( "\n" );
134
135         // Configure the font
136         QFont 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 );
140         switch( style[0] )
141         {
142         case 'i':
143         case 'I':
144                 font.setStyle( QFont::StyleItalic );
145                 break;
146         }
147         QFontMetrics fm( font );
148
149         // Determine the text rectangle size
150         height = fm.lineSpacing() * lines.size();
151         for( int i = 0; i < lines.size(); ++i )
152         {
153                 int line_width = fm.width( lines.at(i) );
154                 if( line_width > width ) width = line_width;
155         }
156
157         // Lay out the text in the path
158         int x = 0;
159         int y = fm.ascent() + 1 + offset;
160         for( int i = 0; i < lines.size(); ++i )
161         {
162                 QString line = lines.at(i);
163                 x = offset;
164                 switch( align[0] )
165                 {
166                         default:
167                         case 'l':
168                         case 'L':
169                                 break;
170                         case 'c':
171                         case 'C':
172                                 x += ( width - fm.width( line ) ) / 2;
173                                 break;
174                         case 'r':
175                         case 'R':
176                                 x += width - fm.width( line );
177                                 break;
178                 }
179                 qPath->addText( x, y, font, line );
180                 y += fm.lineSpacing();
181         }
182
183         // Account for outline and pad
184         width += offset * 2;
185         height += offset * 2;
186         // Sanity check
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 );
191 }
192
193 static void generate_qimage( mlt_properties producer_properties, QSize target_size )
194 {
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" ) );
203         qreal sx = 1.0;
204         qreal sy = 1.0;
205
206         // Create a new image and set up scaling
207         if( !target_size.isEmpty() && target_size != native_size )
208         {
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();
212         }
213         else
214         {
215                 *qImg = QImage( native_size, QImage::Format_ARGB32 );
216         }
217
218         // Draw the text
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 ) );
224         QPen pen;
225         pen.setWidth( outline );
226         if( outline )
227         {
228                 pen.setColor( QColor( ol_color.r, ol_color.g, ol_color.b, ol_color.a ) );
229         }
230         else
231         {
232                 pen.setColor( QColor( bg_color.r, bg_color.g, bg_color.b, bg_color.a ) );
233         }
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 );
238 }
239
240 static void copy_qimage_to_mlt_image( QImage* qImg, uint8_t* mImg )
241 {
242         int height = qImg->height();
243         int width = qImg->width();
244         int y = 0;
245
246         // convert qimage to mlt
247         y = height + 1;
248         while ( --y )
249         {
250                 QRgb* src = (QRgb*) qImg->scanLine( height - y );
251                 int x = width + 1;
252                 while ( --x )
253                 {
254                         *mImg++ = qRed( *src );
255                         *mImg++ = qGreen( *src );
256                         *mImg++ = qBlue( *src );
257                         *mImg++ = qAlpha( *src );
258                         src++;
259                 }
260         }
261 }
262
263 static void copy_image_to_alpha( uint8_t* image, uint8_t* alpha, int width, int height )
264 {
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;
270
271         switch ( len % 8 )
272         {
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;
281                                 }
282                                 while ( --n > 0 );
283         }
284 }
285
286 static bool check_qimage( mlt_properties producer_properties, QSize target_size )
287 {
288         QImage* qImg = static_cast<QImage*>( mlt_properties_get_data( producer_properties, "_qimg", NULL ) );
289
290         if( qImg->isNull() )
291         {
292                 return true;
293         }
294
295         QSize output_size = target_size;
296         if( output_size.isEmpty() )
297         {
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" ) );
300         }
301
302         if( output_size != qImg->size() )
303         {
304                 return true;
305         }
306
307         return false;
308 }
309
310 static int producer_get_image( mlt_frame frame, uint8_t** buffer, mlt_image_format* format, int* width, int* height, int writable )
311 {
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 );
315         int img_size = 0;
316         int alpha_size = 0;
317         uint8_t* alpha = NULL;
318         QImage* qImg = NULL;
319
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" ) );
323
324         // Regenerate the qimage if necessary
325         mlt_service_lock( MLT_PRODUCER_SERVICE( producer ) );
326
327         if( check_qimage( producer_properties, target_size ) == true )
328         {
329                 generate_qimage( producer_properties, target_size );
330         }
331
332         qImg = static_cast<QImage*>( mlt_properties_get_data( producer_properties, "_qimg", NULL ) );
333
334         *format = mlt_image_rgb24a;
335         *width = qImg->width();
336         *height = qImg->height();
337
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 );
342
343         mlt_service_unlock( MLT_PRODUCER_SERVICE( producer ) );
344
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 );
349
350         // Update the frame
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 );
355
356         return 0;
357 }
358
359 static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index )
360 {
361         // Generate a frame
362         *frame = mlt_frame_init( MLT_PRODUCER_SERVICE( producer ) );
363
364         if ( *frame != NULL )
365         {
366                 // Obtain properties of frame
367                 mlt_properties frame_properties = MLT_FRAME_PROPERTIES( *frame );
368                 mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
369
370                 if( check_qpath( producer_properties ) )
371                 {
372                         generate_qpath( producer_properties );
373                 }
374
375                 // Save the producer to be used later
376                 mlt_properties_set_data( frame_properties, "_producer_qtext", static_cast<void*>( producer ), 0, NULL, NULL );
377
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 );
383                 else
384                         mlt_properties_set_double( frame_properties, "aspect_ratio", 1.0);
385
386                 // Update time code on the frame
387                 mlt_frame_set_position( *frame, mlt_producer_position( producer ) );
388
389                 // Configure callbacks
390                 mlt_frame_push_get_image( *frame, producer_get_image );
391         }
392
393         // Calculate the next time code
394         mlt_producer_prepare_next( producer );
395
396         return 0;
397 }
398
399 static void producer_close( mlt_producer producer )
400 {
401         mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
402
403         QImage* qImg = static_cast<QImage*>( mlt_properties_get_data( producer_properties, "_qimg", NULL ) );
404         delete qImg;
405
406         QPainterPath* qPath = static_cast<QPainterPath*>( mlt_properties_get_data( producer_properties, "_qpath", NULL ) );
407         delete qPath;
408
409         producer->close = NULL;
410
411         mlt_producer_close( producer );
412         free( producer );
413 }
414
415 /** Initialize.
416 */
417 extern "C" {
418
419 mlt_producer producer_qtext_init( mlt_profile profile, mlt_service_type type, const char *id, char *filename )
420 {
421         // Create a new producer object
422         mlt_producer producer = mlt_producer_new( profile );
423         mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
424
425         // Initialize the producer
426         if ( producer )
427         {
428                 if( init_qt( producer ) == false )
429                 {
430                         mlt_producer_close( producer );
431                         return NULL;
432                 }
433
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" );
446
447                 // Parse the filename argument
448                 if ( filename == NULL ||
449                      !strcmp( filename, "" ) ||
450                          strstr( filename, "<producer>" ) )
451                 {
452                 }
453                 else if( filename[ 0 ] == '+' || strstr( filename, "/+" ) )
454                 {
455                         char *copy = strdup( filename + 1 );
456                         char *tmp = copy;
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 );
465                         free( copy );
466                 }
467                 else
468                 {
469                         FILE *f = fopen( filename, "r" );
470                         if ( f != NULL )
471                         {
472                                 char line[81];
473                                 char *tmp = NULL;
474                                 size_t size = 0;
475                                 line[80] = '\0';
476
477                                 while ( fgets( line, 80, f ) )
478                                 {
479                                         size += strlen( line ) + 1;
480                                         if ( tmp )
481                                         {
482                                                 tmp = (char*)realloc( tmp, size );
483                                                 if ( tmp )
484                                                         strcat( tmp, line );
485                                         }
486                                         else
487                                         {
488                                                 tmp = strdup( line );
489                                         }
490                                 }
491                                 fclose( f );
492
493                                 if ( tmp && tmp[ strlen( tmp ) - 1 ] == '\n' )
494                                         tmp[ strlen( tmp ) - 1 ] = '\0';
495
496                                 if ( tmp )
497                                         mlt_properties_set( producer_properties, "text", tmp );
498                                 mlt_properties_set( producer_properties, "resource", filename );
499                                 free( tmp );
500                         }
501                 }
502
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 );
508
509                 // Callback registration
510                 producer->get_frame = producer_get_frame;
511                 producer->close = ( mlt_destructor )producer_close;
512         }
513
514         return producer;
515 }
516
517 }