1 /*
2     Reading of subpicture images and handling of buttons and associated palettes
3 */
4 /*
5  * Copyright (C) 2002 Scott Smith (trckjunky@users.sourceforge.net)
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or (at
10  * your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20  * MA 02110-1301 USA.
21  */
22 
23 #include "config.h"
24 
25 #include "compat.h"
26 
27 #include <assert.h>
28 #include <fcntl.h>
29 #include <math.h>
30 
31 #if defined(HAVE_MAGICK) || defined(HAVE_GMAGICK)
32 #include <stdarg.h>
33 #include <magick/api.h>
34 #else
35 #include <png.h>
36 #endif
37 
38 #include "subglobals.h"
39 #include "subrender.h"
40 #include "subgen.h"
41 
42 
43 #define MAXX 720
44 #define MAXY 576
45 
46 bool text_forceit = false;     /* Forcing of the subtitles */
47 sub_data *textsub_subdata;
48 
constructblankpic(pict * p,int w,int h)49 static void constructblankpic(pict *p,int w,int h)
50   /* allocates and fills in p with an image consisting entirely of transparent pixels */
51 {
52     w+=w&1;
53     h+=h&1;
54     p->width=w;
55     p->height=h;
56     p->fname=0;
57     p->img=malloc(w*h);
58     p->numpal=1;
59     p->pal[0].r=0;
60     p->pal[0].g=0;
61     p->pal[0].b=0;
62     p->pal[0].a=0;
63     memset(p->img,0,w*h);
64 }
65 
66 typedef struct { /* a set of colours in a subpicture or button */
67     int numpal; /* nr entries used in pal */
68     int pal[4]; /* that's all the DVD-video spec allows */
69 } palgroup;
70 
71 // ****************************************************
72 //
73 // Bitmap reading code
74 
75 // this function scans through a picture and looks for the color that
76 // the user indicated should mean transparent
77 // if the color is found, it is replaced by 0,0,0,0 meaning "transparent"
scanpict(stinfo * s,pict * p)78 static void scanpict(stinfo *s, pict *p)
79   {
80     int i;
81     // we won't replace the background color if there is already evidence
82     // of alpha channel information
83     for (i = 0; i < p->numpal; i++)
84         if (p->pal[i].a != 255)
85             goto skip_transreplace;
86     for (i = 0; i < p->numpal; i++)
87         if (!memcmp(p->pal + i, &s->transparentc, sizeof(colorspec)))
88           { /* found matching entry */
89             memset(&p->pal[i], 0, sizeof(colorspec)); /* zap the RGB components */
90             break;
91           } /*if; for*/
92  skip_transreplace:
93     for (i = 0; i < p->numpal; i++)
94         (void)findmasterpal(s, p->pal + i);
95           /* just make sure all the colours are in the colour table */
96   } /*scanpict*/
97 
putpixel(pict * p,int x,const colorspec * c)98 static void putpixel(pict *p, int x, const colorspec *c)
99   /* stores another pixel into pict p at offset x with colour c. Adds a new
100     entry into the colour table if not already present and there's room. */
101   {
102     int i;
103     colorspec ct;
104     if (!c->a && (c->r || c->g || c->b))
105       {
106       /* all transparent pixels look alike to me */
107         ct.a = 0;
108         ct.r = 0;
109         ct.g = 0;
110         ct.b = 0;
111         c = &ct;
112       } /*if*/
113     for (i = 0; i < p->numpal; i++)
114         if (!memcmp(&p->pal[i], c, sizeof(colorspec)))
115           {
116           /* matches existing palette entry */
117             p->img[x] = i;
118             return;
119           } /*if; for*/
120     if (p->numpal == 256)
121       {
122       /* too many colours */
123         p->img[x] = 0;
124         return;
125       } /*if*/
126   /* allocate new palette entry */
127     p->img[x] = p->numpal;
128 /*  fprintf(stderr, "CREATING COLOR %d,%d,%d %d\n", c->r, c->g, c->b, c->a); */
129     p->pal[p->numpal++] = *c;
130   } /*putpixel*/
131 
createimage(pict * s,int w,int h)132 static void createimage(pict *s, int w, int h)
133   /* allocates memory for pixels in s with dimensions w and h. */
134   {
135     s->numpal = 0;
136   /* ensure allocated dimensions are even */
137     s->width = w + (w & 1);
138     s->height = h + (h & 1);
139     s->img = malloc(s->width * s->height);
140     if (w != s->width)
141       {
142       /* set padding pixels along side to transparent */
143         int y;
144         colorspec t;
145         t.r = 0;
146         t.g = 0;
147         t.b = 0;
148         t.a = 0;
149         for (y = 0; y < h; y++)
150             putpixel(s, w + y * s->width, &t);
151       } /*if*/
152     if (h != s->height)
153       {
154       /* set padding pixels along bottom to transparent */
155         int x;
156         colorspec t;
157         t.r = 0;
158         t.g = 0;
159         t.b = 0;
160         t.a = 0;
161         for (x = 0; x < s->width; x++)
162             putpixel(s, x + h * s->width, &t);
163       } /*if*/
164   } /*createimage*/
165 
166 #if defined(HAVE_MAGICK) || defined(HAVE_GMAGICK)
167 // meaning of A in RGBA swapped in ImageMagick 6.0.0 and GraphicsMagick 1.3.8
168 #if defined(HAVE_MAGICK)
169 #define XMAGICK_NEW_RGBA_MINVER 0x600
170 #else // HAVE_GMAGICK
171 #define XMAGICK_NEW_RGBA_MINVER 0x060300
172 #define ExportImagePixels DispatchImage
173 #endif
read_magick(pict * s)174 static int read_magick(pict *s)
175 /* uses ImageMagick/GraphicsMagick to read image s from s->fname. */
176 {
177     Image *im;
178     ImageInfo *ii;
179     ExceptionInfo ei;
180     int x,y;
181     unsigned long magickver;
182     unsigned char amask;
183 
184     GetExceptionInfo(&ei);
185     ii=CloneImageInfo(NULL);
186     strcpy(ii->filename,s->fname);
187     im=ReadImage(ii,&ei);
188 
189     if( !im ) {
190         MagickError(ei.severity,"Unable to load file",ii->filename);
191         return -1;
192     }
193 
194     if( im->columns>MAXX || im->rows>MAXY ) {
195         fprintf(stderr,"ERR:  Picture %s is too big: %lux%lu\n",s->fname,im->columns,im->rows);
196         DestroyImage(im);
197         return -1;
198     }
199     createimage(s,im->columns,im->rows);
200     GetMagickVersion(&magickver);
201     amask = magickver < XMAGICK_NEW_RGBA_MINVER ? 255 : 0;
202     for( y=0; y<im->rows; y++ ) {
203         char pdata[MAXX*4];
204 
205         if(!ExportImagePixels(im,0,y,im->columns,1,"RGBA",CharPixel,pdata,&ei)) {
206             fprintf(stderr,"ERR:  Extracting row %d from %s (%s,%s)\n",y,s->fname,ei.reason,ei.description);
207             CatchException(&ei);
208             MagickError(ei.severity,ei.reason,ei.description);
209             DestroyImage(im);
210             return -1;
211         }
212         for( x=0; x<im->columns; x++ ) {
213             colorspec p;
214             p.r=pdata[x*4];
215             p.g=pdata[x*4+1];
216             p.b=pdata[x*4+2];
217             p.a = pdata[x*4+3] ^ amask;
218             putpixel(s,y*s->width+x,&p);
219         }
220     }
221     DestroyImage(im);
222     DestroyExceptionInfo(&ei);
223     fprintf(stderr,"INFO: Picture %s had %d colors\n",s->fname,s->numpal);
224 
225     return 0;
226 }
227 #else
read_png(pict * s)228 static int read_png(pict *s)
229 /* uses libpng to read image s from s->fname. */
230 {
231     unsigned char pnghead[8];
232     FILE *fp;
233     png_struct *ps;
234     png_info *pi;
235     png_byte **rowp;
236     png_uint_32 width,height;
237     int bit_depth,color_type,channels,x,y;
238 
239     fp=fopen(s->fname,"rb");
240     if( !fp ) {
241     fprintf(stderr,"ERR:  Unable to open file %s\n",s->fname);
242     return -1;
243     }
244     fread(pnghead,1,8,fp);
245     if(png_sig_cmp(pnghead,0,8)) {
246     fprintf(stderr,"ERR:  File %s isn't a png\n",s->fname);
247     fclose(fp);
248     return -1;
249     }
250     ps=png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
251     if( !ps ) {
252         fprintf(stderr,"ERR:  Initializing png\n");
253         fclose(fp);
254         return -1;
255     }
256     pi=png_create_info_struct(ps);
257     if( !pi ) {
258         fprintf(stderr,"ERR:  Initializing png\n");
259         png_destroy_read_struct(&ps,NULL,NULL);
260         fclose(fp);
261         return -1;
262     }
263     png_init_io(ps,fp);
264     png_set_sig_bytes(ps,8);
265 
266     png_read_png(ps,pi,PNG_TRANSFORM_PACKING|PNG_TRANSFORM_STRIP_16|PNG_TRANSFORM_EXPAND,NULL);
267     rowp=png_get_rows(ps,pi);
268     png_get_IHDR(ps,pi,&width,&height,&bit_depth,&color_type,NULL,NULL,NULL);
269     // format is now RGB[A] or G[A]
270     channels=png_get_channels(ps,pi);
271     fclose(fp);
272     if(color_type&PNG_COLOR_MASK_COLOR)
273         channels-=3;
274     else
275         channels--;
276     if(color_type&PNG_COLOR_MASK_ALPHA)
277         channels--;
278     assert(bit_depth==8); // 8bpp, not 1, 2, 4, or 16
279     assert(!(color_type&PNG_COLOR_MASK_PALETTE)); // not a palette
280     if( width>MAXX || height>MAXY ) {
281         fprintf(stderr,"ERR:  PNG %s is too big: %lux%lu\n",s->fname,width,height);
282         png_destroy_read_struct(&ps,&pi,NULL);
283         return -1;
284     }
285     createimage(s,width,height);
286     for( y=0; y<height; y++ ) {
287         unsigned char *d=rowp[y];
288         for( x=0; x<width; x++ ) {
289             colorspec p;
290             if(color_type&PNG_COLOR_MASK_COLOR) {
291                 p.r=*d++;
292                 p.g=*d++;
293                 p.b=*d++;
294             } else {
295                 p.r=*d++;
296                 p.g=p.r;
297                 p.b=p.r;
298             }
299             if( color_type&PNG_COLOR_MASK_ALPHA )
300                 p.a=*d++;
301             else
302                 p.a=255;
303             d+=channels;
304             putpixel(s,y*s->width+x,&p);
305         }
306         // free(rowp[y]);
307     }
308     // free(rowp);
309     png_destroy_read_struct(&ps,&pi,NULL);
310     fprintf(stderr,"INFO: PNG had %d colors\n",s->numpal);
311 
312     return 0;
313 }
314 #endif
315 
read_frame(pict * s)316 static int read_frame(pict *s)
317   /* fills in s from textsub_image_buffer. */
318   {
319     int x, y;
320     createimage(s, movie_width, movie_height);
321     for (y = 0; y < movie_height; y++)
322       {
323           const unsigned char * d = textsub_image_buffer + y * movie_width * 4;
324           for (x = 0; x < movie_width; x++)
325             {
326               colorspec p;
327               p.r = *d++;
328               p.g = *d++;
329               p.b = *d++;
330               p.a = *d++;
331               putpixel(s, y * s->width + x, &p);
332             } /*for*/
333       } /*for*/
334     return 0;
335   } /*read_frame*/
336 
read_pic(stinfo * s,pict * p)337 static int read_pic(stinfo *s, pict *p)
338   /* gets the image(s) specified by p into s. */
339   {
340     int r = 0;
341     if (have_textsub)
342       {
343         vo_update_osd(s->sub_title); /* will allocate and render into textsub_image_buffer */
344         s->forced = text_forceit;
345         r = read_frame(p);
346       }
347     else /* read image file */
348       {
349         if (!p->fname)
350             return 0;
351 #if defined(HAVE_MAGICK) || defined(HAVE_GMAGICK)
352         r = read_magick(p);
353 #else
354         r = read_png(p);
355 #endif
356       } /*if*/
357     if (!r)
358         scanpict(s, p);
359     return r;
360   } /*read_pic*/
361 
checkcolor(palgroup * p,int c)362 static bool checkcolor(palgroup *p,int c)
363   /* tries to put colour c into palette p, returning true iff successful or already there. */
364 {
365     int i;
366     for( i=0; i<p->numpal; i++ )
367         if( p->pal[i]==c )
368             return true;
369     if( p->numpal==4 )
370         return false;
371     p->pal[p->numpal++]=c;
372     return true;
373 }
374 
gettricolor(stinfo * s,int p,int useimg)375 static int gettricolor(stinfo *s,int p,int useimg)
376   /* returns an index used to represent the particular combination of colours used
377     in s->img, s->hlt and s->sel at offset p. */
378   {
379     return
380             (useimg ? s->img.img[p] << 16 : 0)
381         |
382             s->hlt.img[p] << 8
383         |
384             s->sel.img[p];
385   } /*gettricolor*/
386 
pickbuttongroups(stinfo * s,int ng,int useimg)387 static bool pickbuttongroups(stinfo *s, int ng, int useimg)
388   /* tries to assign the buttons in s to ng unique groups. useimg indicates
389     whether to look at the pixels in s->img in addition to s->hlt and s->sel. */
390   {
391     palgroup *bpgs, *gs;
392     int i, x, y, j, k, enb;
393 
394     bpgs = malloc(s->numbuttons * sizeof(palgroup)); /* colour tables for each button */
395     memset(bpgs, 0, s->numbuttons * sizeof(palgroup));
396 
397     gs = malloc(ng * sizeof(palgroup)); /* colour tables for each button group */
398     memset(gs, 0, ng * sizeof(palgroup));
399 
400     assert(!useimg || (s->xd <= s->img.width && s->yd <= s->img.height));
401     assert(s->xd <= s->hlt.width && s->yd <= s->hlt.height);
402     assert(s->xd <= s->sel.width && s->yd <= s->sel.height);
403 
404     // fprintf(stderr,"attempt %d groups, %d useimg\n",ng,useimg);
405     // find unique colors per button
406     for (i = 0; i < s->numbuttons; i++)
407       {
408       /* check coordinates of buttons make sense, and determine their colour tables */
409         button *b = &s->buttons[i];
410         palgroup *bp = &bpgs[i]; /* button's combined colour table for all images */
411 
412         if
413           (
414                 b->r.x0 != b->r.x1
415             &&
416                 b->r.y0 != b->r.y1
417             &&
418                 (
419                     b->r.x0 < 0
420                 ||
421                     b->r.x0 > b->r.x1
422                 ||
423                     b->r.x1 > s->xd
424                 ||
425                     b->r.y0 < 0
426                 ||
427                     b->r.y0 > b->r.y1
428                 ||
429                     b->r.y1 > s->yd
430                 )
431           )
432           {
433             if (debug > -1)
434                 fprintf
435                   (
436                     stderr,
437                     "ERR:  Button coordinates out of range (%d,%d): (%d,%d)-(%d,%d)\n",
438                     s->xd, s->yd,
439                     b->r.x0, b->r.y0, b->r.x1, b->r.y1
440                   );
441             exit(1);
442           } /*if*/
443         for (y = b->r.y0; y < b->r.y1; y++)
444             for (x = b->r.x0; x < b->r.x1; x++)
445                 if (!checkcolor(bp, gettricolor(s, y * s->xd + x, useimg)))
446                     goto impossible;
447         // fprintf(stderr, "pbg: button %d has %d colors\n", i, bp->numpal);
448       } /*for i*/
449 
450     // assign to groups
451     enb = 1;
452     for (i = 0; i < s->numbuttons; i++)
453         enb *= ng; /* how many combinations to try--warning! combinatorial explosion! */
454     for (i = 0; i < enb; i++)
455       {
456       /* try another combination for assigning buttons to groups */
457         for (j = 0; j < ng; j++)
458             gs[j].numpal = 0; /* clear all button group palettes for new attempt */
459         // fprintf(stderr, "trying ");
460         k = i; /* combinator */
461         for (j = 0; j < s->numbuttons; j++)
462           {
463           /* assemble this combination of merging button palettes into group palettes */
464             int l;
465             palgroup *pd = &gs[k % ng];
466               /* palette for group to which to try to assign this button */
467             palgroup *ps = &bpgs[j]; /* palette for button */
468 
469             s->buttons[j].grp = k % ng + 1; /* assign button group */
470             // fprintf(stderr, "%s%d",j?", ":"", s->buttons[j].grp);
471             k /= ng;
472             for (l = 0; l < ps->numpal; l++)
473               /* try to merge button palette into group palette */
474                 if (!checkcolor(pd, ps->pal[l]))
475                   {
476                     // fprintf(stderr, " -- failed mapping button %d\n", j);
477                     goto trynext;
478                   } /*if; for*/
479           } /*for j*/
480         if (useimg)
481           {
482           /* save the final button group palettes, ensuring the colours used in s->img
483             for all the buttons fit in a single palette */
484             palgroup p;
485 
486             p.numpal = s->img.numpal;
487             for (j = 0; j < p.numpal; j++)
488                 p.pal[j] = j;
489             for (j = 0; j < ng; j++)
490               {
491                 for (k = 0; k < 4; k++)
492                     s->groupmap[j][k] = -1; /* button group palette initially empty */
493                 for (k = 0; k < p.numpal; k++)
494                     p.pal[k] &= 255; /* clear colour-already-matched flag */
495                 for (k = 0; k < gs[j].numpal; k++)
496                   {
497                     int l, c;
498 
499                     c = gs[j].pal[k] >> 16; /* s->img component of tricolor from button group palette */
500                     for (l = 0; l < p.numpal; l++)
501                         if (p.pal[l] == c)
502                           {
503                             goto ui_found;
504                           } /*if; for */
505                     if (p.numpal == 4)
506                       {
507                         // fprintf(stderr, " -- failed finding unique overall palette\n");
508                         goto trynext;
509                       } /*if*/
510                     p.numpal++;
511 ui_found:
512                     p.pal[l] = c | 256; /* mark palette entry as already matched */
513                     s->groupmap[j][l] = gs[j].pal[k]; /* merge colour into button group palette */
514                   } /*for*/
515               } /*for j*/
516           }
517         else
518           {
519           /* save the final button group palettes */
520             for (j = 0; j < ng; j++)
521               {
522                 for (k = 0; k < gs[j].numpal; k++)
523                     s->groupmap[j][k] = gs[j].pal[k];
524                 for (; k < 4; k++)
525                     s->groupmap[j][k] = -1; /* unused palette entries */
526               } /*for*/
527           } /*if*/
528         free(bpgs);
529         free(gs);
530 
531         // If possible, make each palette entry 0 transparent in
532         // all states, since some players may pad buttons with 0
533         // and we also pad with 0 in some cases.
534         for (j = 0; j < ng; j++)
535           {
536             int spare = -1;
537             int tri; /* tricolor */
538 
539             // search for an unused color, or one that is already fully transparent
540             for (k = 0; k < 4; k++)
541               {
542                 tri = s->groupmap[j][k];
543                 if
544                   (
545                         tri == -1
546                     ||
547                             s->img.pal[(tri >> 16) & 0xFF].a == 0
548                         &&
549                             s->hlt.pal[(tri >> 8) & 0xFF].a == 0
550                         &&
551                             s->sel.pal[tri & 0xFF].a == 0
552                   )
553                   {
554                     spare = k;
555                     break;
556                   } /*if*/
557               } /*for*/
558           /* at this point, if spare = 0 then nothing to do, entry if any at location 0
559             is already transparent */
560             if (spare > 0 && tri == -1)
561               {
562               /* got an unused slot, make up a transparent entry to exchange it with */
563                 tri = 0;
564                 for (k = 0; k < s->img.numpal; ++k)
565                     if (s->img.pal[k].a == 0)
566                       {
567                         tri |= k << 16;
568                         break;
569                       } /*if; for*/
570                 for (k = 0; k < s->hlt.numpal; ++k)
571                     if (s->hlt.pal[k].a == 0)
572                       {
573                         tri |= k << 8;
574                         break;
575                       } /*if; for*/
576                 for (k = 0; k < s->sel.numpal; ++k)
577                     if (s->sel.pal[k].a == 0)
578                       {
579                         tri |= k;
580                         break;
581                       } /*if*/
582               } /*if*/
583             if (spare > 0)
584               {
585               /* move transparent colour to location 0 */
586                 s->groupmap[j][spare] = s->groupmap[j][0]; /* move nontransparent colour */
587                 s->groupmap[j][0] = tri; /* to make way for transparent one */
588               } /*if*/
589           } /*for j*/
590 
591         fprintf(stderr, "INFO: Pickbuttongroups, success with %d groups, useimg=%d\n", ng, useimg);
592         s->numgroups = ng;
593         return true;
594 trynext:
595         continue; // 'deprecated use of label at end of compound statement'
596       } /*for i*/
597 
598 impossible:
599     free(bpgs);
600     free(gs);
601     return false;
602   } /*pickbuttongroups*/
603 
fixnames(stinfo * s)604 static void fixnames(stinfo *s)
605   /* assigns default names to buttons that don't already have them. */
606   {
607     int i;
608     for (i = 0; i < s->numbuttons; i++)
609         if (!s->buttons[i].name)
610           {
611             char n[10]; /* should be enough! */
612             sprintf(n, "%d", i + 1);
613             s->buttons[i].name = strdup(n);
614           } /*if; for*/
615   } /*fixnames*/
616 
617 // a0 .. a1, b0 .. b1 are analogous to y coordinates, positive -> high, negative->low
618 // d is the distance from a to b (assuming b is to the right of a, i.e. positive x)
619 // returns angle (0 = straight right, 90 = straight up, -90 = straight down) from a to b
brphelp(int a0,int a1,int b0,int b1,int d,int * angle,int * dist)620 static void brphelp(int a0, int a1, int b0, int b1, int d, int *angle, int *dist)
621   {
622     int d1, d2, m;
623     if (a1 > b0 && a0 < b1)
624       {
625         *angle = 0;
626         *dist = d;
627         return;
628       } /*if*/
629     d1 = -(b0 - a1 + 1);
630     d2 = (a0 - b1 + 1);
631     d++;
632     if (abs(d2) <abs(d1))
633       {
634         d1 = d2;
635       } /*if*/
636     m = 1;
637     if (d1 < 0)
638       {
639         d1 = -d1;
640         m = -1;
641       } /*if*/
642     *angle = m * 180 / M_PI * atan(((double)d1) / ((double)d));
643     *dist = sqrt(d1 * d1+d * d);
644   } /*brphelp*/
645 
buttonrelpos(const rectangle * a,const rectangle * b,int * angle,int * dist)646 static bool buttonrelpos(const rectangle *a, const rectangle *b, int *angle, int *dist)
647   /* computes an angle and distance from the position of rectangle a to that of rectangle b,
648     if I can figure one out. Returns true if the case is sufficiently simple for me to handle,
649     false otherwise. */
650   {
651     // from b to a
652     if (a->y1 <= b->y0)
653       {
654       /* a lies above b with no vertical overlap */
655         brphelp(a->x0, a->x1, b->x0, b->x1, b->y0 - a->y1, angle, dist);
656         // fprintf(stderr,"from %dx%d-%dx%d to %dx%d-%dx%d is angle %d, dist %d\n",b->x0,b->y0,b->x1,b->y1,a->x0,a->y0,a->x1,a->y1,*angle,*dist);
657         return true;
658       } /*if*/
659     if (a->y0 >= b->y1)
660       {
661       /* a lies below b with no vertical overlap */
662         brphelp(a->x0, a->x1, b->x0, b->x1, a->y0 - b->y1, angle, dist);
663         *angle = 180 - *angle;
664         // fprintf(stderr,"from %dx%d-%dx%d to %dx%d-%dx%d is angle %d, dist %d\n",b->x0,b->y0,b->x1,b->y1,a->x0,a->y0,a->x1,a->y1,*angle,*dist);
665         return true;
666       } /*if*/
667     if (a->x1 <= b->x0)
668       {
669       /* a lies to the left of b with no horizontal overlap */
670         brphelp(a->y0, a->y1, b->y0, b->y1, b->x0 - a->x1, angle, dist);
671         *angle = 270 - *angle;
672         // fprintf(stderr,"from %dx%d-%dx%d to %dx%d-%dx%d is angle %d, dist %d\n",b->x0,b->y0,b->x1,b->y1,a->x0,a->y0,a->x1,a->y1,*angle,*dist);
673         return true;
674       } /*if*/
675     if (a->x0 >= b->x1)
676       {
677       /* a lies to the right of b with no horizontal overlap */
678         brphelp(a->y0, a->y1, b->y0, b->y1, a->x0 - b->x1, angle, dist);
679         *angle = 90 + *angle;
680         // fprintf(stderr,"from %dx%d-%dx%d to %dx%d-%dx%d is angle %d, dist %d\n",b->x0,b->y0,b->x1,b->y1,a->x0,a->y0,a->x1,a->y1,*angle,*dist);
681         return true;
682       } /*if*/
683   /* none of the above */
684     // fprintf(stderr,"from %dx%d-%dx%d to %dx%d-%dx%d -- no easy comparison\n",b->x0,b->y0,b->x1,b->y1,a->x0,a->y0,a->x1,a->y1);
685     return false;
686   } /*buttonrelpos*/
687 
findbestbindir(stinfo * s,const button * b,char ** dest,int a)688 static void findbestbindir(stinfo *s, const button *b, char **dest, int a)
689   /* finds the best button to go to in the specified direction and returns its
690     name in *dest, if that has not already been set. */
691   {
692     int i, la = 0, ld = 0;
693     if (*dest) /* already got one */
694         return;
695     // fprintf(stderr,"locating nearest button from %s, angle %d\n",b->name,a);
696     for (i = 0; i < s->numbuttons; i++)
697         if (b != &s->buttons[i])
698           {
699             int na, nd;
700             if (buttonrelpos(&s->buttons[i].r, &b->r, &na, &nd))
701               {
702                 na = abs(na - a); /* error from desired direction */
703                 if (na >= 90) /* completely wrong direction */
704                     continue;
705                 if (!*dest || na < la || (na == la && nd < ld))
706                   {
707                   /* first candidate, or better score than previous candidate */
708                     // fprintf(stderr,"\tchoosing %s, na=%d, d=%d\n",s->buttons[i].name,na,nd);
709                     *dest = s->buttons[i].name;
710                     la = na;
711                     ld = nd;
712                   } /*if*/
713               } /*if*/
714           } /*if; for*/
715     if (*dest)
716         *dest = strdup(*dest); /* copy the name I found */
717     else
718         *dest = strdup(b->name); /* back to same button in this direction */
719   } /*findbestbindir*/
720 
detectdirections(stinfo * s)721 static void detectdirections(stinfo *s)
722   /* automatically detects the neighbours of each button in each direction, where
723     these have not already been specified. */
724   {
725     int i;
726     for (i = 0; i < s->numbuttons; i++)
727       {
728         findbestbindir(s, &s->buttons[i], &s->buttons[i].up, 0);
729         findbestbindir(s, &s->buttons[i], &s->buttons[i].down, 180);
730         findbestbindir(s, &s->buttons[i], &s->buttons[i].left, 270);
731         findbestbindir(s, &s->buttons[i], &s->buttons[i].right, 90);
732       } /*for*/
733   } /*detectdirections*/
734 
735 #define MAX(a,b) (((a)>(b))?(a):(b))
736 #define MIN(a,b) (((a)<(b))?(a):(b))
737 
scanvline(stinfo * s,unsigned char * v,rectangle * r,int x,int d)738 static bool scanvline(stinfo *s,unsigned char *v,rectangle *r,int x,int d)
739 {
740     int i,j;
741 
742     for( j=1; j<=s->outlinewidth; j++ ) {
743         x+=d;
744         if( x<0 || x>=s->xd )
745             return false;
746         for( i=MAX(r->y0-j,0); i<MIN(r->y1+j,s->yd); i++ )
747             if( v[i*s->xd+x] )
748                 return true;
749     }
750     return false;
751 }
752 
scanhline(stinfo * s,unsigned char * v,rectangle * r,int y,int d)753 static bool scanhline(stinfo *s,unsigned char *v,rectangle *r,int y,int d)
754 {
755     int i,j;
756 
757     for( j=1; j<=s->outlinewidth; j++ ) {
758         y+=d;
759         if( y<0 || y>=s->yd )
760             return false;
761         for( i=MAX(r->x0-j,0); i<MIN(r->x1+j,s->xd); i++ )
762             if( v[y*s->xd+i] )
763                 return true;
764     }
765     return false;
766 }
767 
detectbuttons(stinfo * s)768 static void detectbuttons(stinfo *s)
769   /* does automatic detection of button outlines. */
770 {
771     unsigned char *visitmask=malloc(s->xd*s->yd);
772     int i,x,y;
773     rectangle *rs=0;
774     int numr=0;
775 
776     if( !s->outlinewidth )
777         s->outlinewidth=1;
778     for( i=0; i<s->xd*s->yd; i++ )
779         visitmask[i]=( s->hlt.pal[s->hlt.img[i]].a || s->sel.pal[s->sel.img[i]].a ) ? 1 : 0;
780     for( y=0; y<s->yd; y++ )
781         for( x=0; x<s->xd; x++ )
782             if( visitmask[y*s->xd+x] ) {
783                 rectangle r;
784                 bool didwork;
785 
786                 r.x0=x;
787                 r.y0=y;
788                 r.x1=x+1;
789                 r.y1=y+1;
790 
791                 do {
792                     didwork = false;
793                     while( scanvline(s,visitmask,&r,r.x0,-1) ) {
794                         r.x0--;
795                         didwork = true;
796                     }
797                     while( scanvline(s,visitmask,&r,r.x1-1,1) ) {
798                         r.x1++;
799                         didwork = true;
800                     }
801                     while( scanhline(s,visitmask,&r,r.y0,-1) ) {
802                         r.y0--;
803                         didwork = true;
804                     }
805                     while( scanhline(s,visitmask,&r,r.y1-1,1) ) {
806                         r.y1++;
807                         didwork = true;
808                     }
809                 } while(didwork);
810 
811                 r.y0-=r.y0&1; // buttons need even 'y' coordinates
812                 r.y1+=r.y1&1;
813 
814                 // add button r
815                 rs=realloc(rs,(numr+1)*sizeof(rectangle));
816                 rs[numr++]=r;
817 
818                 // reset so we pass over
819                 for( i=r.y0; i<r.y1; i++ )
820                     memset(visitmask+i*s->xd+r.x0,0,r.x1-r.x0);
821             }
822     free(visitmask);
823 
824     while(numr) {
825         int j=0;
826         // find the left most button on the top row
827         if( !s->autoorder ) {
828             for( i=1; i<numr; i++ )
829                 if( rs[i].y0 < rs[j].y0 ||
830                     (rs[i].y0==rs[j].y0 && rs[i].x0 < rs[j].x0 ) )
831                     j=i;
832         } else {
833             for( i=1; i<numr; i++ )
834                 if( rs[i].x0 < rs[j].x0 ||
835                     (rs[i].x0==rs[j].x0 && rs[i].y0 < rs[j].y0 ) )
836                     j=i;
837         }
838         // see if there are any buttons to the left, i.e. slightly overlapping vertically, but possibly start a little lower
839         for( i=0; i<numr; i++ )
840             if( i!=j ) {
841                 int a,d;
842                 if(buttonrelpos(rs+i,rs+j,&a,&d))
843                     if( a==(s->autoorder?0:270) )
844                         j=i;
845             }
846 
847         // ok add rectangle 'j'
848 
849         for( i=0; i<s->numbuttons; i++ )
850             if( s->buttons[i].r.x0<0 )
851                 break;
852         if( i==s->numbuttons ) {
853             s->numbuttons++;
854             s->buttons=realloc(s->buttons,s->numbuttons*sizeof(button));
855             memset(s->buttons+i,0,sizeof(button));
856         }
857 
858         fprintf(stderr,"INFO: Autodetect %d = %dx%d-%dx%d\n",i,rs[j].x0,rs[j].y0,rs[j].x1,rs[j].y1);
859 
860         s->buttons[i].r=rs[j];
861         memmove(rs+j,rs+j+1,(numr-j-1)*sizeof(rectangle));
862         numr--;
863     }
864 }
865 
imgfix(stinfo * s)866 static bool imgfix(stinfo *s)
867   /* fills in the subpicture/button details. */
868 {
869     int i, useimg, w, h, x, y, x0, y0;
870 
871     w = s->img.width;
872     h = s->img.height;
873     s->xd = w; // pickbuttongroups needs these values set
874     s->yd = h;
875 
876     if (s->autooutline)
877         detectbuttons(s);
878 
879     fixnames(s);
880     detectdirections(s);
881 
882     s->fimg = malloc(w * h);
883     memset(s->fimg, 255, w * h); /* mark all pixels as uninitialized */
884 
885     // first try not to have multiple palettes for
886     useimg = 1;
887     if (s->numbuttons)
888       {
889         do
890           {
891             if (pickbuttongroups(s, 1, useimg))
892                 break;
893             if (pickbuttongroups(s, 2, useimg))
894                 break;
895             if (pickbuttongroups(s, 3, useimg))
896                 break;
897             useimg--;
898           }
899         while (useimg >= 0);
900         assert(useimg); // at this point I don't want to deal with blocking the primary subtitle image
901         if (useimg < 0)
902           {
903             fprintf(stderr, "ERR:  Cannot pick button masks\n");
904             return false;
905           } /*if*/
906 
907         for (i = 0; i < s->numbuttons; i++)
908           {
909           /* fill in button areas of fimg */
910             button *b = &s->buttons[i];
911 
912             for (y = b->r.y0; y < b->r.y1; y++)
913                 for (x = b->r.x0; x < b->r.x1; x++)
914                   {
915                     int dc = -1, p = y * w + x, j;
916                     int c = gettricolor(s, p, useimg);
917                     for (j = 0; j < 4; j++)
918                         if (s->groupmap[b->grp - 1][j] == c)
919                           {
920                             dc = j;
921                             break;
922                           } /*if; for*/
923                     if (dc == -1)
924                       { /* shouldn't occur */
925                         fprintf(stderr, "ERR:  Button %d cannot find color %06x in group %d\n",
926                             i, c, b->grp - 1);
927                         assert(dc != -1); /* instant assertion failure */
928                       } /*if*/
929                     if (s->fimg[p] != dc && s->fimg[p] != 255)
930                       { /* pixel already occupied by another button */
931                         fprintf(stderr, "ERR:  Overlapping buttons\n");
932                         return false;
933                       } /*if*/
934                     s->fimg[p] = dc;
935                   } /*for; for*/
936           } /*for*/
937       } /*if s->numbuttons*/
938     for (i = 0; i < 4; i++)
939       { /* initially mark all s->pal entries as "unused" (transparent) */
940         s->pal[i].r = 255;
941         s->pal[i].g = 255;
942         s->pal[i].b = 255;
943         s->pal[i].a = 0;
944       } /*for*/
945     for (i = 0; i < w * h; i++)
946         if (s->fimg[i] != 255)
947             s->pal[s->fimg[i]] = s->img.pal[s->img.img[i]];
948     for (i = 0; i < w * h; i++)
949       /* fill in rest of fimg with "normal" image (s->img) */
950         if (s->fimg[i] == 255)
951           { /* haven't already done this pixel */
952             int j;
953             const colorspec * const p = &s->img.pal[s->img.img[i]];
954             for (j = 0; j < 4; j++)
955               /* see if colour is already in s->pal */
956                 if (!memcmp(&s->pal[j], p, sizeof(colorspec)))
957                     goto if_found;
958             for (j = 0; j < 4; j++) /* insert new colour in place of unused/transparent entry */
959                 if (s->pal[j].a == 0 && s->pal[j].r == 255) /* not checking all the components? */
960                   {
961                     s->pal[j] = *p;
962                     goto if_found;
963                   } /*if*/
964           /* no room in s->pal */
965             fprintf(stderr, "ERR:  Too many colors in base picture\n");
966             return false;
967 if_found:
968             s->fimg[i] = j;
969           } /*if; for*/
970 
971     // determine minimal visual area, and crop the subtitle accordingly
972     x0 = w;
973     y0 = -1;
974     s->xd = 0;
975     s->yd = 0;
976     for (i = 0, y = 0; y < h; y++)
977       {
978         for (x = 0; x < w; i++, x++)
979           {
980             if
981               (
982                     s->img.pal[s->img.img[i]].a
983                 ||
984                     s->hlt.pal[s->hlt.img[i]].a
985                 ||
986                     s->sel.pal[s->sel.img[i]].a
987               )
988               {
989                 if (y0 == -1)
990                     y0 = y;
991                 s->yd = y;
992                 if (x < x0)
993                     x0 = x;
994                 if (x > s->xd)
995                     s->xd = x;
996               } /*if*/
997           } /*for*/
998       } /*for*/
999     if (y0 == -1)
1000       { // empty image?
1001         s->xd = w;
1002         s->yd = h;
1003         return true;
1004       } /*if*/
1005     x0 &= -2;
1006     y0 &= -2;
1007     s->xd = (s->xd + 2 - x0) & (-2);
1008     s->yd = (s->yd + 2 - y0) & (-2);
1009     for (i = 0; i < s->yd; i++)
1010         memmove(s->fimg + i * s->xd, s->fimg + i * w + x0 + y0 * w, s->xd);
1011     for (i = 0; i < s->numbuttons; i++)
1012       {
1013         button *b = &s->buttons[i];
1014         b->r.x0 += s->x0;
1015         b->r.y0 += s->y0;
1016         b->r.x1 += s->x0;
1017         b->r.y1 += s->y0;
1018       } /*for*/
1019     s->x0 += x0;
1020     s->y0 += y0;
1021     return true;
1022   } /*imgfix*/
1023 
process_subtitle(stinfo * s)1024 bool process_subtitle(stinfo *s)
1025   /* loads the specified image files and builds the subpicture in memory. */
1026 {
1027     int w=0,h=0;
1028     int iline=0;
1029 
1030     if( !s ) return false;
1031     if( read_pic(s,&s->img) ) {
1032         if(debug > -1)
1033             fprintf(stderr, "WARN: Bad image,  skipping line %d\n", iline - 1);
1034         return false;
1035     }
1036     if( s->img.img && !w ) {
1037         w=s->img.width;
1038         h=s->img.height;
1039     }
1040     if( read_pic(s,&s->hlt) ) {
1041         if(debug > -1)
1042             fprintf(stderr, "WARN: Bad image,  skipping line %d\n", iline - 1);
1043         return false;
1044     }
1045     if( s->hlt.img && !w ) {
1046         w=s->hlt.width;
1047         h=s->hlt.height;
1048     }
1049     if( read_pic(s,&s->sel) ) {
1050         if(debug > -1)
1051             fprintf(stderr, "WARN: Bad image,  skipping line %d\n", iline - 1);
1052         return false;
1053     }
1054     if( s->sel.img && !w ) {
1055         w=s->sel.width;
1056         h=s->sel.height;
1057     }
1058 
1059     if( !w ) {
1060         fprintf(stderr,"WARN: No picture, skipping line %d\n",iline-1);
1061         return false;
1062     }
1063   /* check consistent dimensions, fill in missing images with blanks */
1064     if( !s->img.img ) {
1065         constructblankpic(&s->img,w,h);
1066         fprintf(stderr,"INFO: Constructing blank img\n");
1067     } else if( s->img.width!=w || s->img.height!=h ) {
1068         fprintf(stderr,"WARN: Inconsistent picture widths, skipping line %d\n",iline-1);
1069         return false;
1070     }
1071     if( !s->hlt.img ) {
1072         constructblankpic(&s->hlt,w,h);
1073         fprintf(stderr,"INFO: Constructing blank hlt\n");
1074     } else if( s->hlt.width!=w || s->hlt.height!=h ) {
1075         fprintf(stderr,"WARN: Inconsistent picture widths, skipping line %d\n",iline-1);
1076         return false;
1077     }
1078     if( !s->sel.img ) {
1079         constructblankpic(&s->sel,w,h);
1080         fprintf(stderr,"INFO: Constructing blank sel\n");
1081     } else if( s->sel.width!=w || s->sel.height!=h ) {
1082         fprintf(stderr,"WARN: Inconsistent picture widths, skipping line %d\n",iline-1);
1083         return false;
1084     }
1085 
1086     if( !imgfix(s) ) /* data in img to fimg */
1087     {
1088         if (debug > -1)
1089         {
1090             fprintf(stderr, "ERR:  Blank image, skipping line %d\n", iline - 1);
1091         }
1092         return false;
1093     }
1094 
1095     return true;
1096 }
1097 
image_init()1098 void image_init()
1099 {
1100 #if defined(HAVE_MAGICK) || defined(HAVE_GMAGICK)
1101     InitializeMagick(NULL);
1102 #endif
1103 }
1104 
image_shutdown()1105 void image_shutdown()
1106 {
1107 #if defined(HAVE_MAGICK) || defined(HAVE_GMAGICK)
1108     DestroyMagick();
1109 #endif
1110 }
1111