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