1 /*
2  *  filter_logo.c
3  *
4  *  Copyright (C) Tilmann Bitterberg - April 2002
5  *  filter updates, enhancements and cleanup:
6  *  Copyright (C) Sebastian Kun <seb at sarolta dot com> - March 2006
7  *
8  *  This file is part of transcode, a video stream processing tool
9  *
10  *  transcode is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation; either version 2, or (at your option)
13  *  any later version.
14  *
15  *  transcode is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with GNU Make; see the file COPYING.  If not, write to
22  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23  *
24  */
25 
26     /* TODO:
27         - animated gif/png support -> done
28         - sequences of jpgs maybe
29           would be nice.
30      */
31 
32 #define MOD_NAME    "filter_logo.so"
33 #define MOD_VERSION "v0.10 (2003-10-16)"
34 #define MOD_CAP     "render image in videostream"
35 #define MOD_AUTHOR  "Tilmann Bitterberg"
36 
37 /* Note: because of ImageMagick bogosity, this must be included first, so
38  * we can undefine the PACKAGE_* symbols it splats into our namespace */
39 #include <magick/api.h>
40 #undef PACKAGE_BUGREPORT
41 #undef PACKAGE_NAME
42 #undef PACKAGE_STRING
43 #undef PACKAGE_TARNAME
44 #undef PACKAGE_VERSION
45 
46 /* Add workaround for deprecated ScaleCharToQuantum() function */
47 #undef ScaleCharToQuantum
48 #if MAGICKCORE_QUANTUM_DEPTH == 8
49 # define ScaleCharToQuantum(x) ((x))
50 #elif MAGICKCORE_QUANTUM_DEPTH == 16
51 # define ScaleCharToQuantum(x) ((x)*257)
52 #elif MAGICKCORE_QUANTUM_DEPTH == 32
53 # define ScaleCharToQuantum(x) ((x)*16843009)
54 #elif MAGICKCORE_QUANTUM_DEPTH == 64
55 # define ScaleCharToQuantum(x) ((x)*71777214294589695ULL)
56 #endif
57 
58 #include "transcode.h"
59 #include "filter.h"
60 #include "libtc/libtc.h"
61 #include "libtc/optstr.h"
62 #include "libtcvideo/tcvideo.h"
63 
64 #include <stdlib.h>
65 #include <stdio.h>
66 
67 #define MAX_UINT8_VAL   ((uint8_t)(-1))
68 
69 // basic parameter
70 
71 enum POS { NONE, TOP_LEFT, TOP_RIGHT, BOT_LEFT, BOT_RIGHT, CENTER };
72 
73 typedef struct MyFilterData {
74     /* public */
75     char         file[PATH_MAX]; /* input filename                  */
76     int          posx;           /* X offset in video               */
77     int          posy;           /* Y offset in video               */
78     enum POS     pos;            /* predifined position             */
79     int          flip;           /* bool if to flip image           */
80     int          ignoredelay;    /* allow the user to ignore delays */
81     int          rgbswap;        /* bool if swap colors             */
82     int          grayout;        /* only render lume values         */
83     int          hqconv;         /* do high quality rgb->yuv conv.  */
84     unsigned int start, end;     /* ranges                          */
85     unsigned int fadein;         /* No. of frames to fade in        */
86     unsigned int fadeout;        /* No. of frames to fade out       */
87 
88     /* private */
89     unsigned int nr_of_images;   /* animated: number of images      */
90     unsigned int cur_seq;        /* animated: current image         */
91     int          cur_delay;      /* animated: current delay         */
92     uint8_t    **yuv;            /* buffer for RGB->YUV conversion  */
93 
94     TCVHandle    tcvhandle;      /* handle for RGB->YUV conversion  */
95 
96     /* These used to be static (per-module), but are now per-instance. */
97     vob_t       *vob;            /* video info from transcode       */
98     Image       *image;          /* Magick image handle             */
99     Image       *images;         /* tmp Magick handle (todo:remove) */
100 } MyFilterData;
101 
102 /* FIXME: this uses the filter ID as an index--the ID can grow
103  * arbitrarily large, so this needs to be fixed */
104 static MyFilterData *mfd_all[100] = {NULL};
105 
106 /* Only one instance of the module needs to initialize ImageMagick */
107 static int magick_usecount = 0;
108 
109 /* Coefficients used for transparency calculations. Pre-generating these
110  * in a lookup table provides a small speed boost.
111  */
112 static float img_coeff_lookup[MAX_UINT8_VAL + 1] = {-1.0};
113 static float vid_coeff_lookup[MAX_UINT8_VAL + 1] = {-1.0};
114 
115 /* from /src/transcode.c */
116 extern int rgbswap;
117 extern int flip;
118 /* should probably honor the other flags too */
119 
120 /*-------------------------------------------------
121  *
122  * single function interface
123  *
124  *-------------------------------------------------*/
125 
flogo_help_optstr(void)126 static void flogo_help_optstr(void)
127 {
128     tc_log_info(MOD_NAME, "(%s) help\n"
129 "* Overview\n"
130 "    This filter renders an user specified image into the video.\n"
131 "    Any image format ImageMagick can read is accepted.\n"
132 "    Transparent images are also supported.\n"
133 "    Image origin is at the very top left.\n"
134 "\n"
135 "* Options\n"
136 "        'file' Image filename (required) [logo.png]\n"
137 "         'pos' Position (0-width x 0-height) [0x0]\n"
138 "      'posdef' Position (0=None, 1=TopL, 2=TopR, 3=BotL, 4=BotR, 5=Center) [0]\n"
139 "       'range' Restrict rendering to framerange (0-oo) [0-end]\n"
140 "        'fade' Fade image in/out (# of frames) (0-oo) [0-0]\n"
141 "        'flip' Mirror image (0=off, 1=on) [0]\n"
142 "     'rgbswap' Swap colors [0]\n"
143 "     'grayout' YUV only: don't write Cb and Cr, makes a nice effect [0]\n"
144 "      'hqconv' YUV only: do high quality rgb->yuv img conversion [0]\n"
145 " 'ignoredelay' Ignore delay specified in animations [0]\n"
146 		, MOD_CAP);
147 }
148 
149 
150 /**
151  * flogo_yuvbuf_free: Frees a set of YUV frame buffers allocated with
152  *                    flogo_yuvbuf_alloc().
153  * Parameters:     yuv: a pointer to a set of YUV frames
154  *                 num: the number of frames to free
155  * Return value:   N/A
156  * Preconditions:  yuv was allocated with flogo_yuvbuf_alloc
157  *                 num > 0
158  * Postconditions: N/A
159  */
flogo_yuvbuf_free(uint8_t ** yuv,int num)160 static void flogo_yuvbuf_free(uint8_t **yuv, int num) {
161     int i;
162 
163     if (yuv) {
164         for (i = 0; i < num; i++) {
165             if (yuv[i] != NULL)
166                 tc_free(yuv[i]);
167         }
168         tc_free(yuv);
169     }
170 
171     return;
172 }
173 
174 
175 /**
176  * flogo_yuvbuf_alloc: Allocates a set of zeroed YUV frame buffers.
177  *
178  * Parameters:     size: the size of each frame
179  *                 num:  the number of frames to allocate.
180  * Return value:   An array of pointers to zeroed YUV buffers.
181  * Preconditions:  size > 0
182  *                 num > 0
183  * Postconditions: The returned pointer should be freed with
184  *                 flogo_yuvbuf_free.
185  */
flogo_yuvbuf_alloc(size_t size,int num)186 static uint8_t **flogo_yuvbuf_alloc(size_t size, int num) {
187     uint8_t **yuv;
188     int i;
189 
190     yuv = tc_zalloc(sizeof(uint8_t *) * num);
191     if (yuv == NULL)
192         return NULL;
193 
194     for (i = 0; i < num; i++) {
195         yuv[i] = tc_malloc(sizeof(uint8_t) * size);
196         if (yuv[i] == NULL) {
197             // free what's already been allocated
198             flogo_yuvbuf_free(yuv, i-1);
199             return NULL;
200         }
201     }
202 
203     return yuv;
204 }
205 
206 
207 /**
208  * flogo_convert_image: Converts a single ImageMagick RGB image into a format
209  *                      usable by transcode.
210  *
211  * Parameters:     tcvhandle:  Opaque libtcvideo handle
212  *                 src:        An ImageMagick handle (the source image)
213  *                 dst:        A pointer to the output buffer
214  *                 ifmt:       The output format (see aclib/imgconvert.h)
215  *                 do_rgbswap: zero for no swap, nonzero to swap red and blue
216  *                             pixel positions
217  * Return value:   1 on success, 0 on failure
218  * Preconditions:  tcvhandle != null, was returned by a call to tcv_init()
219  *                 src is a valid ImageMagick RGB image handle
220  *                 dst buffer is large enough to hold the result of the
221  *                   requested conversion
222  * Postconditions: dst get overwritten with the result of the conversion
223  */
flogo_convert_image(TCVHandle tcvhandle,Image * src,uint8_t * dst,ImageFormat ifmt,int do_rgbswap)224 static int flogo_convert_image(TCVHandle    tcvhandle,
225                                Image       *src,
226                                uint8_t     *dst,
227                                ImageFormat  ifmt,
228                                int          do_rgbswap)
229 {
230     PixelPacket *pixel_packet;
231     uint8_t *dst_ptr = dst;
232 
233     int row, col;
234     int height = src->rows;
235     int width  = src->columns;
236     int ret;
237 
238     unsigned long r_off, g_off, b_off;
239 
240     if (!do_rgbswap) {
241         r_off = 0;
242         b_off = 2;
243     } else {
244         r_off = 2;
245         b_off = 0;
246     }
247     g_off = 1;
248 
249     pixel_packet = GetImagePixels(src, 0, 0, width, height);
250 
251     for (row = 0; row < height; row++) {
252         for (col = 0; col < width; col++) {
253             *(dst_ptr + r_off) = (uint8_t)ScaleQuantumToChar(pixel_packet->red);
254             *(dst_ptr + g_off) = (uint8_t)ScaleQuantumToChar(pixel_packet->green);
255             *(dst_ptr + b_off) = (uint8_t)ScaleQuantumToChar(pixel_packet->blue);
256 
257             dst_ptr += 3;
258             pixel_packet++;
259         }
260     }
261 
262     ret = tcv_convert(tcvhandle, dst, dst, width, height, IMG_RGB24, ifmt);
263     if (ret == 0) {
264         tc_log_error(MOD_NAME, "RGB->YUV conversion failed");
265         return 0;
266     }
267 
268     return 1;
269 }
270 
271 
tc_filter(frame_list_t * ptr_,char * options)272 int tc_filter(frame_list_t *ptr_, char *options)
273 {
274     vframe_list_t *ptr = (vframe_list_t *)ptr_;
275     vob_t         *vob = NULL;
276 
277     int instance = ptr->filter_id;
278     MyFilterData  *mfd = mfd_all[instance];
279 
280     if (mfd != NULL) {
281         vob = mfd->vob;
282     }
283 
284     //----------------------------------
285     //
286     // filter init
287     //
288     //----------------------------------
289 
290 
291     if (ptr->tag & TC_FILTER_GET_CONFIG) {
292         optstr_filter_desc(options, MOD_NAME, MOD_CAP, MOD_VERSION, MOD_AUTHOR, "VRYO", "1");
293         // buf, name, comment, format, val, from, to
294         optstr_param(options, "file",   "Image filename",    "%s",    "logo.png");
295         optstr_param(options, "posdef", "Position (0=None, 1=TopL, 2=TopR, 3=BotL, 4=BotR, 5=Center)",  "%d", "0", "0", "5");
296         optstr_param(options, "pos",    "Position (0-width x 0-height)",  "%dx%d", "0x0", "0", "width", "0", "height");
297         optstr_param(options, "range",  "Restrict rendering to framerange",  "%u-%u", "0-0", "0", "oo", "0", "oo");
298         optstr_param(options, "fade",   "Fade image in/out (# of frames)",  "%u-%u", "0-0", "0", "oo", "0", "oo");
299         // bools
300         optstr_param(options, "ignoredelay", "Ignore delay specified in animations", "", "0");
301         optstr_param(options, "rgbswap", "Swap red/blue colors", "", "0");
302         optstr_param(options, "grayout", "YUV only: don't write Cb and Cr, makes a nice effect", "",  "0");
303         optstr_param(options, "hqconv",  "YUV only: do high quality rgb->yuv img conversion", "",  "0");
304         optstr_param(options, "flip",    "Mirror image",  "", "0");
305 
306         return 0;
307     }
308 
309     if (ptr->tag & TC_FILTER_INIT) {
310         Image         *timg;
311         Image         *nimg;
312         ImageInfo     *image_info;
313         ExceptionInfo  exception_info;
314 
315         int rgb_off = 0;
316 
317         vob_t *tmpvob;
318 
319         tmpvob = tc_get_vob();
320         if (tmpvob == NULL)
321             return -1;
322         mfd_all[instance] = tc_zalloc(sizeof(MyFilterData));
323         if (mfd_all[instance] == NULL)
324             return -1;
325 
326         mfd = mfd_all[instance];
327 
328         strlcpy(mfd->file, "logo.png", PATH_MAX);
329         mfd->end = (unsigned int)-1;
330         mfd->vob = tmpvob;
331         vob      = mfd->vob;
332 
333         if (options != NULL) {
334             if (verbose)
335                 tc_log_info(MOD_NAME, "options=%s", options);
336 
337             optstr_get(options, "file",     "%[^:]", mfd->file);
338             optstr_get(options, "posdef",   "%d",    (int *)&mfd->pos);
339             optstr_get(options, "pos",      "%dx%d", &mfd->posx,  &mfd->posy);
340             optstr_get(options, "range",    "%u-%u", &mfd->start, &mfd->end);
341             optstr_get(options, "fade",     "%u-%u", &mfd->fadein, &mfd->fadeout);
342 
343             if (optstr_lookup(options, "ignoredelay") != NULL)
344                 mfd->ignoredelay = !mfd->ignoredelay;
345             if (optstr_lookup(options, "flip") != NULL)
346                 mfd->flip    = !mfd->flip;
347             if (optstr_lookup(options, "rgbswap") != NULL)
348                 mfd->rgbswap = !mfd->rgbswap;
349             if (optstr_lookup(options, "grayout") != NULL)
350                 mfd->grayout = !mfd->grayout;
351             if (optstr_lookup(options, "hqconv") != NULL)
352                 mfd->hqconv  = !mfd->hqconv;
353 
354             if (optstr_lookup (options, "help") != NULL)
355                 flogo_help_optstr();
356         }
357 
358         if (verbose > 1) {
359             tc_log_info(MOD_NAME, " Logo renderer Settings:");
360             tc_log_info(MOD_NAME, "         file = %s",    mfd->file);
361             tc_log_info(MOD_NAME, "       posdef = %d",    mfd->pos);
362             tc_log_info(MOD_NAME, "          pos = %dx%d", mfd->posx,
363                                                            mfd->posy);
364             tc_log_info(MOD_NAME, "        range = %u-%u", mfd->start,
365                                                            mfd->end);
366             tc_log_info(MOD_NAME, "         fade = %u-%u", mfd->fadein,
367                                                            mfd->fadeout);
368             tc_log_info(MOD_NAME, "         flip = %d",    mfd->flip);
369             tc_log_info(MOD_NAME, "  ignoredelay = %d",    mfd->ignoredelay);
370             tc_log_info(MOD_NAME, "      rgbswap = %d",    mfd->rgbswap);
371             tc_log_info(MOD_NAME, "      grayout = %d",    mfd->grayout);
372             tc_log_info(MOD_NAME, "       hqconv = %d",    mfd->hqconv);
373         }
374 
375         /* Transcode serializes module execution, so this does not need a
376          * semaphore.
377          */
378         magick_usecount++;
379         if (!IsMagickInstantiated()) {
380             InitializeMagick("");
381         }
382 
383         GetExceptionInfo(&exception_info);
384         image_info = CloneImageInfo((ImageInfo *) NULL);
385         strlcpy(image_info->filename, mfd->file, MaxTextExtent);
386 
387         mfd->image = ReadImage(image_info, &exception_info);
388         if (mfd->image == (Image *) NULL) {
389             MagickWarning(exception_info.severity,
390                           exception_info.reason,
391                           exception_info.description);
392             strlcpy(mfd->file, "/dev/null", PATH_MAX);
393             return 0;
394         }
395         DestroyImageInfo(image_info);
396 
397         if (mfd->image->columns > vob->ex_v_width
398          || mfd->image->rows    > vob->ex_v_height
399         ) {
400             tc_log_error(MOD_NAME, "\"%s\" is too large", mfd->file);
401             return -1;
402         }
403 
404         if (vob->im_v_codec == CODEC_YUV) {
405             if ((mfd->image->columns & 1) || (mfd->image->rows & 1)) {
406                 tc_log_error(MOD_NAME, "\"%s\" has odd sizes", mfd->file);
407                 return -1;
408             }
409         }
410 
411         mfd->images = (Image *)GetFirstImageInList(mfd->image);
412         nimg = NewImageList();
413 
414         while (mfd->images != (Image *)NULL) {
415             if (mfd->flip || flip) {
416                 timg = FlipImage(mfd->images, &exception_info);
417                 if (timg == (Image *) NULL) {
418                     MagickError(exception_info.severity,
419                                 exception_info.reason,
420                                 exception_info.description);
421                     return -1;
422                 }
423                 AppendImageToList(&nimg, timg);
424             }
425 
426             mfd->images = GetNextImageInList(mfd->images);
427             mfd->nr_of_images++;
428         }
429 
430         // check for memleaks;
431         //DestroyImageList(image);
432         if (mfd->flip || flip) {
433             mfd->image = nimg;
434         }
435 
436         /* initial delay. real delay = 1/100 sec * delay */
437         mfd->cur_delay = mfd->image->delay*vob->fps/100;
438 
439         if (verbose & TC_DEBUG)
440             tc_log_info(MOD_NAME, "Nr: %d Delay: %d mfd->image->del %lu|",
441                         mfd->nr_of_images, mfd->cur_delay, mfd->image->delay);
442 
443         if (vob->im_v_codec == CODEC_YUV) {
444             /* convert Magick RGB image format to YUV */
445             /* todo: convert the magick image if it's not rgb! (e.g. cmyk) */
446             Image   *image;
447             uint8_t *yuv_hqbuf = NULL;
448 
449             /* Round up for odd-size images */
450             unsigned long width  = mfd->image->columns;
451             unsigned long height = mfd->image->rows;
452             int do_rgbswap  = (rgbswap || mfd->rgbswap);
453             int i;
454 
455             /* Allocate buffers for the YUV420P frames. mfd->nr_of_images
456              * will be 1 unless this is an animated GIF or MNG.
457              * This buffer needs to be large enough to store a temporary
458              * 24-bit RGB image (extracted from the ImageMagick handle).
459              */
460             mfd->yuv = flogo_yuvbuf_alloc(width*height * 3, mfd->nr_of_images);
461             if (mfd->yuv == NULL) {
462                 tc_log_error(MOD_NAME, "(%d) out of memory\n", __LINE__);
463                 return -1;
464             }
465 
466             if (mfd->hqconv) {
467                 /* One temporary buffer, to hold full Y, U, and V planes. */
468                 yuv_hqbuf = tc_malloc(width*height * 3);
469                 if (yuv_hqbuf == NULL) {
470                     tc_log_error(MOD_NAME, "(%d) out of memory\n", __LINE__);
471                     return -1;
472                 }
473             }
474 
475             mfd->tcvhandle = tcv_init();
476             if (mfd->tcvhandle == NULL) {
477                 tc_log_error(MOD_NAME, "image conversion init failed");
478                 return -1;
479             }
480 
481             image = GetFirstImageInList(mfd->image);
482 
483             for (i = 0; i < mfd->nr_of_images; i++) {
484                 if (!mfd->hqconv) {
485                     flogo_convert_image(mfd->tcvhandle, image, mfd->yuv[i],
486                                         IMG_YUV420P, do_rgbswap);
487                 } else {
488                     flogo_convert_image(mfd->tcvhandle, image, yuv_hqbuf,
489                                         IMG_YUV444P, do_rgbswap);
490 
491                     // Copy over Y data from the 444 image
492                     ac_memcpy(mfd->yuv[i], yuv_hqbuf, width * height);
493 
494                     // Resize U plane by 1/2 in each dimension, into the
495                     // mfd YUV buffer
496                     tcv_zoom(mfd->tcvhandle,
497                              yuv_hqbuf + (width * height),
498                              mfd->yuv[i] + (width * height),
499                              width,
500                              height,
501                              1,
502                              width / 2,
503                              height / 2,
504                              TCV_ZOOM_LANCZOS3
505                     );
506 
507                     // Do the same with the V plane
508                     tcv_zoom(mfd->tcvhandle,
509                              yuv_hqbuf + 2*width*height,
510                              mfd->yuv[i] + width*height + (width/2)*(height/2),
511                              width,
512                              height,
513                              1,
514                              width / 2,
515                              height / 2,
516                              TCV_ZOOM_LANCZOS3
517                     );
518                 }
519                 image = GetNextImageInList(image);
520             }
521 
522             if (mfd->hqconv)
523                 tc_free(yuv_hqbuf);
524 
525             tcv_free(mfd->tcvhandle);
526         } else {
527             /* for RGB format is origin bottom left */
528             /* for RGB, rgbswap is done in the frame routine */
529             rgb_off = vob->ex_v_height - mfd->image->rows;
530             mfd->posy = rgb_off - mfd->posy;
531         }
532 
533         switch (mfd->pos) {
534           case NONE: /* 0 */
535             break;
536           case TOP_LEFT:
537             mfd->posx = 0;
538             mfd->posy = rgb_off;
539             break;
540           case TOP_RIGHT:
541             mfd->posx = vob->ex_v_width  - mfd->image->columns;
542             break;
543           case BOT_LEFT:
544             mfd->posy = vob->ex_v_height - mfd->image->rows - rgb_off;
545             break;
546           case BOT_RIGHT:
547             mfd->posx = vob->ex_v_width  - mfd->image->columns;
548             mfd->posy = vob->ex_v_height - mfd->image->rows - rgb_off;
549             break;
550           case CENTER:
551             mfd->posx = (vob->ex_v_width - mfd->image->columns)/2;
552             mfd->posy = (vob->ex_v_height- mfd->image->rows)/2;
553             /* align to not cause color disruption */
554             if (mfd->posx & 1)
555                 mfd->posx++;
556             if (mfd->posy & 1)
557                 mfd->posy++;
558             break;
559         }
560 
561 
562         if (mfd->posy < 0 || mfd->posx < 0
563          || (mfd->posx + mfd->image->columns) > vob->ex_v_width
564          || (mfd->posy + mfd->image->rows)    > vob->ex_v_height) {
565             tc_log_error(MOD_NAME, "invalid position");
566             return -1;
567         }
568 
569         /* for running through image sequence */
570         mfd->images = mfd->image;
571 
572 
573         /* Set up image/video coefficient lookup tables */
574         if (img_coeff_lookup[0] < 0) {
575             int i;
576             float maxrgbval = (float)MaxRGB; // from ImageMagick
577 
578             for (i = 0; i <= MAX_UINT8_VAL; i++) {
579                 float x = (float)ScaleCharToQuantum(i);
580 
581                 /* Alternatively:
582                  *  img_coeff = (maxrgbval - x) / maxrgbval;
583                  *  vid_coeff = x / maxrgbval;
584                  */
585                 img_coeff_lookup[i] = 1.0 - (x / maxrgbval);
586                 vid_coeff_lookup[i] = 1.0 - img_coeff_lookup[i];
587             }
588         }
589 
590         // filter init ok.
591         if (verbose)
592             tc_log_info(MOD_NAME, "%s %s", MOD_VERSION, MOD_CAP);
593 
594         return 0;
595     }
596 
597 
598     //----------------------------------
599     //
600     // filter close
601     //
602     //----------------------------------
603     if (ptr->tag & TC_FILTER_CLOSE) {
604         if (mfd) {
605             flogo_yuvbuf_free(mfd->yuv, mfd->nr_of_images);
606             mfd->yuv = NULL;
607 
608             if (mfd->image) {
609                 DestroyImage(mfd->image);
610             }
611 
612             tc_free(mfd);
613             mfd = NULL;
614             mfd_all[instance] = NULL;
615         }
616 
617         magick_usecount--;
618         if (magick_usecount == 0 && IsMagickInstantiated()) {
619             DestroyMagick();
620         }
621 
622         return 0;
623     } /* filter close */
624 
625 
626     //----------------------------------
627     //
628     // filter frame routine
629     //
630     //----------------------------------
631 
632 
633     // tag variable indicates, if we are called before
634     // transcodes internal video/audo frame processing routines
635     // or after and determines video/audio context
636 
637     if ((ptr->tag & TC_POST_M_PROCESS)
638         && (ptr->tag & TC_VIDEO)
639         && !(ptr->attributes & TC_FRAME_IS_SKIPPED)
640     ) {
641         PixelPacket *pixel_packet;
642         uint8_t     *video_buf;
643 
644         int   do_fade    = 0;
645         float fade_coeff = 0.0;
646         float img_coeff, vid_coeff;
647 
648         /* Note: ImageMagick defines opacity = 0 as fully visible, and
649          * opacity = MaxRGB as fully transparent.
650          */
651         Quantum opacity;
652 
653         int row, col;
654 
655         if (ptr->id < mfd->start || ptr->id > mfd->end)
656             return 0;
657 
658         if (strcmp(mfd->file, "/dev/null") == 0)
659             return 0;
660 
661         if (ptr->id - mfd->start < mfd->fadein) {
662             // fading-in
663             fade_coeff = (float)(mfd->start - ptr->id + mfd->fadein) / (float)(mfd->fadein);
664             do_fade = 1;
665         } else if (mfd->end - ptr->id < mfd->fadeout) {
666             // fading-out
667             fade_coeff = (float)(ptr->id - mfd->end + mfd->fadeout) / (float)(mfd->fadeout);
668             do_fade = 1;
669         }
670 
671         mfd->cur_delay--;
672 
673         if (mfd->cur_delay < 0 || mfd->ignoredelay) {
674             int seq;
675 
676             mfd->cur_seq = (mfd->cur_seq + 1) % mfd->nr_of_images;
677 
678             mfd->images = mfd->image;
679             for (seq=0; seq<mfd->cur_seq; seq++)
680                 mfd->images = mfd->images->next;
681 
682             mfd->cur_delay = mfd->images->delay * vob->fps/100;
683         }
684 
685         pixel_packet = GetImagePixels(mfd->images, 0, 0,
686                                       mfd->images->columns,
687                                       mfd->images->rows);
688 
689         if (vob->im_v_codec == CODEC_RGB) {
690             unsigned long r_off, g_off, b_off;
691 
692             if (!(rgbswap || mfd->rgbswap)) {
693                 r_off = 0;
694                 b_off = 2;
695             } else {
696                 r_off = 2;
697                 b_off = 0;
698             }
699             g_off = 1;
700 
701             for (row = 0; row < mfd->image->rows; row++) {
702                 video_buf = ptr->video_buf + 3 * ((row + mfd->posy) * vob->ex_v_width + mfd->posx);
703 
704                 for (col = 0; col < mfd->image->columns; col++) {
705                     opacity = pixel_packet->opacity;
706 
707                     if (do_fade)
708                         opacity += (Quantum)((MaxRGB - opacity) * fade_coeff);
709 
710                     if (opacity == 0) {
711                         *(video_buf + r_off) = ScaleQuantumToChar(pixel_packet->red);
712                         *(video_buf + g_off) = ScaleQuantumToChar(pixel_packet->green);
713                         *(video_buf + b_off) = ScaleQuantumToChar(pixel_packet->blue);
714                     } else if (opacity < MaxRGB) {
715                         unsigned char opacity_uchar = ScaleQuantumToChar(opacity);
716                         img_coeff = img_coeff_lookup[opacity_uchar];
717                         vid_coeff = vid_coeff_lookup[opacity_uchar];
718 
719                         *(video_buf + r_off) = (uint8_t)((*(video_buf + r_off)) * vid_coeff)
720                                                 + (uint8_t)(ScaleQuantumToChar(pixel_packet->red)   * img_coeff);
721                         *(video_buf + g_off) = (uint8_t)((*(video_buf + g_off)) * vid_coeff)
722                                                 + (uint8_t)(ScaleQuantumToChar(pixel_packet->green) * img_coeff);
723                         *(video_buf + b_off) = (uint8_t)((*(video_buf + b_off)) * vid_coeff)
724                                                 + (uint8_t)(ScaleQuantumToChar(pixel_packet->blue)  * img_coeff);
725                     }
726 
727                     video_buf += 3;
728                     pixel_packet++;
729                 }
730             }
731         } else { /* !RGB */
732             unsigned long vid_size = vob->ex_v_width * vob->ex_v_height;
733             unsigned long img_size = mfd->images->columns * mfd->images->rows;
734 
735             uint8_t *img_pixel_Y, *img_pixel_U, *img_pixel_V;
736             uint8_t *vid_pixel_Y, *vid_pixel_U, *vid_pixel_V;
737 
738             img_pixel_Y = mfd->yuv[mfd->cur_seq];
739             img_pixel_U = img_pixel_Y + img_size;
740             img_pixel_V = img_pixel_U + img_size/4;
741 
742             for (row = 0; row < mfd->images->rows; row++) {
743                 vid_pixel_Y = ptr->video_buf + (row + mfd->posy)*mfd->vob->ex_v_width + mfd->posx;
744                 vid_pixel_U = ptr->video_buf + vid_size + (row/2 + mfd->posy/2)*(mfd->vob->ex_v_width/2) + mfd->posx/2;
745                 vid_pixel_V = vid_pixel_U + vid_size/4;
746                 for (col = 0; col < mfd->images->columns; col++) {
747                     int do_UV_pixels = (mfd->grayout == 0 && !(row % 2) && !(col % 2)) ? 1 : 0;
748                     opacity = pixel_packet->opacity;
749 
750                     if (do_fade)
751                         opacity += (Quantum)((MaxRGB - opacity) * fade_coeff);
752 
753                     if (opacity == 0) {
754                         *vid_pixel_Y = *img_pixel_Y;
755                         if (do_UV_pixels) {
756                                 *vid_pixel_U = *img_pixel_U;
757                                 *vid_pixel_V = *img_pixel_V;
758                         }
759                     } else if (opacity < MaxRGB) {
760                         unsigned char opacity_uchar = ScaleQuantumToChar(opacity);
761                         img_coeff = img_coeff_lookup[opacity_uchar];
762                         vid_coeff = vid_coeff_lookup[opacity_uchar];
763 
764                         *vid_pixel_Y = (uint8_t)(*vid_pixel_Y * vid_coeff) + (uint8_t)(*img_pixel_Y * img_coeff);
765 
766                         if (do_UV_pixels) {
767                             *vid_pixel_U = (uint8_t)(*vid_pixel_U * vid_coeff) + (uint8_t)(*img_pixel_U * img_coeff);
768                             *vid_pixel_V = (uint8_t)(*vid_pixel_V * vid_coeff) + (uint8_t)(*img_pixel_V * img_coeff);
769                         }
770                     }
771 
772                     vid_pixel_Y++;
773                     img_pixel_Y++;
774                     if (do_UV_pixels) {
775                         vid_pixel_U++;
776                         img_pixel_U++;
777                         vid_pixel_V++;
778                         img_pixel_V++;
779                     }
780                     pixel_packet++;
781                 }
782             }
783         }
784     }
785 
786     return 0;
787 }
788 
789 /*************************************************************************/
790 
791 /*
792  * Local variables:
793  *   c-file-style: "stroustrup"
794  *   c-file-offsets: ((case-label . *) (statement-case-intro . *))
795  *   indent-tabs-mode: nil
796  * End:
797  *
798  * vim: expandtab shiftwidth=4:
799  */
800