/* Reading of subpicture images and handling of buttons and associated palettes */ /* * Copyright (C) 2002 Scott Smith (trckjunky@users.sourceforge.net) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA. */ #include "config.h" #include "compat.h" #include #include #include #if defined(HAVE_MAGICK) || defined(HAVE_GMAGICK) #include #include #else #include #endif #include "subglobals.h" #include "subrender.h" #include "subgen.h" #define MAXX 720 #define MAXY 576 bool text_forceit = false; /* Forcing of the subtitles */ sub_data *textsub_subdata; static void constructblankpic(pict *p,int w,int h) /* allocates and fills in p with an image consisting entirely of transparent pixels */ { w+=w&1; h+=h&1; p->width=w; p->height=h; p->fname=0; p->img=malloc(w*h); p->numpal=1; p->pal[0].r=0; p->pal[0].g=0; p->pal[0].b=0; p->pal[0].a=0; memset(p->img,0,w*h); } typedef struct { /* a set of colours in a subpicture or button */ int numpal; /* nr entries used in pal */ int pal[4]; /* that's all the DVD-video spec allows */ } palgroup; // **************************************************** // // Bitmap reading code // this function scans through a picture and looks for the color that // the user indicated should mean transparent // if the color is found, it is replaced by 0,0,0,0 meaning "transparent" static void scanpict(stinfo *s, pict *p) { int i; // we won't replace the background color if there is already evidence // of alpha channel information for (i = 0; i < p->numpal; i++) if (p->pal[i].a != 255) goto skip_transreplace; for (i = 0; i < p->numpal; i++) if (!memcmp(p->pal + i, &s->transparentc, sizeof(colorspec))) { /* found matching entry */ memset(&p->pal[i], 0, sizeof(colorspec)); /* zap the RGB components */ break; } /*if; for*/ skip_transreplace: for (i = 0; i < p->numpal; i++) (void)findmasterpal(s, p->pal + i); /* just make sure all the colours are in the colour table */ } /*scanpict*/ static void putpixel(pict *p, int x, const colorspec *c) /* stores another pixel into pict p at offset x with colour c. Adds a new entry into the colour table if not already present and there's room. */ { int i; colorspec ct; if (!c->a && (c->r || c->g || c->b)) { /* all transparent pixels look alike to me */ ct.a = 0; ct.r = 0; ct.g = 0; ct.b = 0; c = &ct; } /*if*/ for (i = 0; i < p->numpal; i++) if (!memcmp(&p->pal[i], c, sizeof(colorspec))) { /* matches existing palette entry */ p->img[x] = i; return; } /*if; for*/ if (p->numpal == 256) { /* too many colours */ p->img[x] = 0; return; } /*if*/ /* allocate new palette entry */ p->img[x] = p->numpal; /* fprintf(stderr, "CREATING COLOR %d,%d,%d %d\n", c->r, c->g, c->b, c->a); */ p->pal[p->numpal++] = *c; } /*putpixel*/ static void createimage(pict *s, int w, int h) /* allocates memory for pixels in s with dimensions w and h. */ { s->numpal = 0; /* ensure allocated dimensions are even */ s->width = w + (w & 1); s->height = h + (h & 1); s->img = malloc(s->width * s->height); if (w != s->width) { /* set padding pixels along side to transparent */ int y; colorspec t; t.r = 0; t.g = 0; t.b = 0; t.a = 0; for (y = 0; y < h; y++) putpixel(s, w + y * s->width, &t); } /*if*/ if (h != s->height) { /* set padding pixels along bottom to transparent */ int x; colorspec t; t.r = 0; t.g = 0; t.b = 0; t.a = 0; for (x = 0; x < s->width; x++) putpixel(s, x + h * s->width, &t); } /*if*/ } /*createimage*/ #if defined(HAVE_MAGICK) || defined(HAVE_GMAGICK) // meaning of A in RGBA swapped in ImageMagick 6.0.0 and GraphicsMagick 1.3.8 #if defined(HAVE_MAGICK) #define XMAGICK_NEW_RGBA_MINVER 0x600 #else // HAVE_GMAGICK #define XMAGICK_NEW_RGBA_MINVER 0x060300 #define ExportImagePixels DispatchImage #endif static int read_magick(pict *s) /* uses ImageMagick/GraphicsMagick to read image s from s->fname. */ { Image *im; ImageInfo *ii; ExceptionInfo ei; int x,y; unsigned long magickver; unsigned char amask; GetExceptionInfo(&ei); ii=CloneImageInfo(NULL); strcpy(ii->filename,s->fname); im=ReadImage(ii,&ei); if( !im ) { MagickError(ei.severity,"Unable to load file",ii->filename); return -1; } if( im->columns>MAXX || im->rows>MAXY ) { fprintf(stderr,"ERR: Picture %s is too big: %lux%lu\n",s->fname,im->columns,im->rows); DestroyImage(im); return -1; } createimage(s,im->columns,im->rows); GetMagickVersion(&magickver); amask = magickver < XMAGICK_NEW_RGBA_MINVER ? 255 : 0; for( y=0; yrows; y++ ) { char pdata[MAXX*4]; if(!ExportImagePixels(im,0,y,im->columns,1,"RGBA",CharPixel,pdata,&ei)) { fprintf(stderr,"ERR: Extracting row %d from %s (%s,%s)\n",y,s->fname,ei.reason,ei.description); CatchException(&ei); MagickError(ei.severity,ei.reason,ei.description); DestroyImage(im); return -1; } for( x=0; xcolumns; x++ ) { colorspec p; p.r=pdata[x*4]; p.g=pdata[x*4+1]; p.b=pdata[x*4+2]; p.a = pdata[x*4+3] ^ amask; putpixel(s,y*s->width+x,&p); } } DestroyImage(im); DestroyExceptionInfo(&ei); fprintf(stderr,"INFO: Picture %s had %d colors\n",s->fname,s->numpal); return 0; } #else static int read_png(pict *s) /* uses libpng to read image s from s->fname. */ { unsigned char pnghead[8]; FILE *fp; png_struct *ps; png_info *pi; png_byte **rowp; png_uint_32 width,height; int bit_depth,color_type,channels,x,y; fp=fopen(s->fname,"rb"); if( !fp ) { fprintf(stderr,"ERR: Unable to open file %s\n",s->fname); return -1; } fread(pnghead,1,8,fp); if(png_sig_cmp(pnghead,0,8)) { fprintf(stderr,"ERR: File %s isn't a png\n",s->fname); fclose(fp); return -1; } ps=png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL); if( !ps ) { fprintf(stderr,"ERR: Initializing png\n"); fclose(fp); return -1; } pi=png_create_info_struct(ps); if( !pi ) { fprintf(stderr,"ERR: Initializing png\n"); png_destroy_read_struct(&ps,NULL,NULL); fclose(fp); return -1; } png_init_io(ps,fp); png_set_sig_bytes(ps,8); png_read_png(ps,pi,PNG_TRANSFORM_PACKING|PNG_TRANSFORM_STRIP_16|PNG_TRANSFORM_EXPAND,NULL); rowp=png_get_rows(ps,pi); png_get_IHDR(ps,pi,&width,&height,&bit_depth,&color_type,NULL,NULL,NULL); // format is now RGB[A] or G[A] channels=png_get_channels(ps,pi); fclose(fp); if(color_type&PNG_COLOR_MASK_COLOR) channels-=3; else channels--; if(color_type&PNG_COLOR_MASK_ALPHA) channels--; assert(bit_depth==8); // 8bpp, not 1, 2, 4, or 16 assert(!(color_type&PNG_COLOR_MASK_PALETTE)); // not a palette if( width>MAXX || height>MAXY ) { fprintf(stderr,"ERR: PNG %s is too big: %lux%lu\n",s->fname,width,height); png_destroy_read_struct(&ps,&pi,NULL); return -1; } createimage(s,width,height); for( y=0; ywidth+x,&p); } // free(rowp[y]); } // free(rowp); png_destroy_read_struct(&ps,&pi,NULL); fprintf(stderr,"INFO: PNG had %d colors\n",s->numpal); return 0; } #endif static int read_frame(pict *s) /* fills in s from textsub_image_buffer. */ { int x, y; createimage(s, movie_width, movie_height); for (y = 0; y < movie_height; y++) { const unsigned char * d = textsub_image_buffer + y * movie_width * 4; for (x = 0; x < movie_width; x++) { colorspec p; p.r = *d++; p.g = *d++; p.b = *d++; p.a = *d++; putpixel(s, y * s->width + x, &p); } /*for*/ } /*for*/ return 0; } /*read_frame*/ static int read_pic(stinfo *s, pict *p) /* gets the image(s) specified by p into s. */ { int r = 0; if (have_textsub) { vo_update_osd(s->sub_title); /* will allocate and render into textsub_image_buffer */ s->forced = text_forceit; r = read_frame(p); } else /* read image file */ { if (!p->fname) return 0; #if defined(HAVE_MAGICK) || defined(HAVE_GMAGICK) r = read_magick(p); #else r = read_png(p); #endif } /*if*/ if (!r) scanpict(s, p); return r; } /*read_pic*/ static bool checkcolor(palgroup *p,int c) /* tries to put colour c into palette p, returning true iff successful or already there. */ { int i; for( i=0; inumpal; i++ ) if( p->pal[i]==c ) return true; if( p->numpal==4 ) return false; p->pal[p->numpal++]=c; return true; } static int gettricolor(stinfo *s,int p,int useimg) /* returns an index used to represent the particular combination of colours used in s->img, s->hlt and s->sel at offset p. */ { return (useimg ? s->img.img[p] << 16 : 0) | s->hlt.img[p] << 8 | s->sel.img[p]; } /*gettricolor*/ static bool pickbuttongroups(stinfo *s, int ng, int useimg) /* tries to assign the buttons in s to ng unique groups. useimg indicates whether to look at the pixels in s->img in addition to s->hlt and s->sel. */ { palgroup *bpgs, *gs; int i, x, y, j, k, enb; bpgs = malloc(s->numbuttons * sizeof(palgroup)); /* colour tables for each button */ memset(bpgs, 0, s->numbuttons * sizeof(palgroup)); gs = malloc(ng * sizeof(palgroup)); /* colour tables for each button group */ memset(gs, 0, ng * sizeof(palgroup)); assert(!useimg || (s->xd <= s->img.width && s->yd <= s->img.height)); assert(s->xd <= s->hlt.width && s->yd <= s->hlt.height); assert(s->xd <= s->sel.width && s->yd <= s->sel.height); // fprintf(stderr,"attempt %d groups, %d useimg\n",ng,useimg); // find unique colors per button for (i = 0; i < s->numbuttons; i++) { /* check coordinates of buttons make sense, and determine their colour tables */ button *b = &s->buttons[i]; palgroup *bp = &bpgs[i]; /* button's combined colour table for all images */ if ( b->r.x0 != b->r.x1 && b->r.y0 != b->r.y1 && ( b->r.x0 < 0 || b->r.x0 > b->r.x1 || b->r.x1 > s->xd || b->r.y0 < 0 || b->r.y0 > b->r.y1 || b->r.y1 > s->yd ) ) { if (debug > -1) fprintf ( stderr, "ERR: Button coordinates out of range (%d,%d): (%d,%d)-(%d,%d)\n", s->xd, s->yd, b->r.x0, b->r.y0, b->r.x1, b->r.y1 ); exit(1); } /*if*/ for (y = b->r.y0; y < b->r.y1; y++) for (x = b->r.x0; x < b->r.x1; x++) if (!checkcolor(bp, gettricolor(s, y * s->xd + x, useimg))) goto impossible; // fprintf(stderr, "pbg: button %d has %d colors\n", i, bp->numpal); } /*for i*/ // assign to groups enb = 1; for (i = 0; i < s->numbuttons; i++) enb *= ng; /* how many combinations to try--warning! combinatorial explosion! */ for (i = 0; i < enb; i++) { /* try another combination for assigning buttons to groups */ for (j = 0; j < ng; j++) gs[j].numpal = 0; /* clear all button group palettes for new attempt */ // fprintf(stderr, "trying "); k = i; /* combinator */ for (j = 0; j < s->numbuttons; j++) { /* assemble this combination of merging button palettes into group palettes */ int l; palgroup *pd = &gs[k % ng]; /* palette for group to which to try to assign this button */ palgroup *ps = &bpgs[j]; /* palette for button */ s->buttons[j].grp = k % ng + 1; /* assign button group */ // fprintf(stderr, "%s%d",j?", ":"", s->buttons[j].grp); k /= ng; for (l = 0; l < ps->numpal; l++) /* try to merge button palette into group palette */ if (!checkcolor(pd, ps->pal[l])) { // fprintf(stderr, " -- failed mapping button %d\n", j); goto trynext; } /*if; for*/ } /*for j*/ if (useimg) { /* save the final button group palettes, ensuring the colours used in s->img for all the buttons fit in a single palette */ palgroup p; p.numpal = s->img.numpal; for (j = 0; j < p.numpal; j++) p.pal[j] = j; for (j = 0; j < ng; j++) { for (k = 0; k < 4; k++) s->groupmap[j][k] = -1; /* button group palette initially empty */ for (k = 0; k < p.numpal; k++) p.pal[k] &= 255; /* clear colour-already-matched flag */ for (k = 0; k < gs[j].numpal; k++) { int l, c; c = gs[j].pal[k] >> 16; /* s->img component of tricolor from button group palette */ for (l = 0; l < p.numpal; l++) if (p.pal[l] == c) { goto ui_found; } /*if; for */ if (p.numpal == 4) { // fprintf(stderr, " -- failed finding unique overall palette\n"); goto trynext; } /*if*/ p.numpal++; ui_found: p.pal[l] = c | 256; /* mark palette entry as already matched */ s->groupmap[j][l] = gs[j].pal[k]; /* merge colour into button group palette */ } /*for*/ } /*for j*/ } else { /* save the final button group palettes */ for (j = 0; j < ng; j++) { for (k = 0; k < gs[j].numpal; k++) s->groupmap[j][k] = gs[j].pal[k]; for (; k < 4; k++) s->groupmap[j][k] = -1; /* unused palette entries */ } /*for*/ } /*if*/ free(bpgs); free(gs); // If possible, make each palette entry 0 transparent in // all states, since some players may pad buttons with 0 // and we also pad with 0 in some cases. for (j = 0; j < ng; j++) { int spare = -1; int tri; /* tricolor */ // search for an unused color, or one that is already fully transparent for (k = 0; k < 4; k++) { tri = s->groupmap[j][k]; if ( tri == -1 || s->img.pal[(tri >> 16) & 0xFF].a == 0 && s->hlt.pal[(tri >> 8) & 0xFF].a == 0 && s->sel.pal[tri & 0xFF].a == 0 ) { spare = k; break; } /*if*/ } /*for*/ /* at this point, if spare = 0 then nothing to do, entry if any at location 0 is already transparent */ if (spare > 0 && tri == -1) { /* got an unused slot, make up a transparent entry to exchange it with */ tri = 0; for (k = 0; k < s->img.numpal; ++k) if (s->img.pal[k].a == 0) { tri |= k << 16; break; } /*if; for*/ for (k = 0; k < s->hlt.numpal; ++k) if (s->hlt.pal[k].a == 0) { tri |= k << 8; break; } /*if; for*/ for (k = 0; k < s->sel.numpal; ++k) if (s->sel.pal[k].a == 0) { tri |= k; break; } /*if*/ } /*if*/ if (spare > 0) { /* move transparent colour to location 0 */ s->groupmap[j][spare] = s->groupmap[j][0]; /* move nontransparent colour */ s->groupmap[j][0] = tri; /* to make way for transparent one */ } /*if*/ } /*for j*/ fprintf(stderr, "INFO: Pickbuttongroups, success with %d groups, useimg=%d\n", ng, useimg); s->numgroups = ng; return true; trynext: continue; // 'deprecated use of label at end of compound statement' } /*for i*/ impossible: free(bpgs); free(gs); return false; } /*pickbuttongroups*/ static void fixnames(stinfo *s) /* assigns default names to buttons that don't already have them. */ { int i; for (i = 0; i < s->numbuttons; i++) if (!s->buttons[i].name) { char n[10]; /* should be enough! */ sprintf(n, "%d", i + 1); s->buttons[i].name = strdup(n); } /*if; for*/ } /*fixnames*/ // a0 .. a1, b0 .. b1 are analogous to y coordinates, positive -> high, negative->low // d is the distance from a to b (assuming b is to the right of a, i.e. positive x) // returns angle (0 = straight right, 90 = straight up, -90 = straight down) from a to b static void brphelp(int a0, int a1, int b0, int b1, int d, int *angle, int *dist) { int d1, d2, m; if (a1 > b0 && a0 < b1) { *angle = 0; *dist = d; return; } /*if*/ d1 = -(b0 - a1 + 1); d2 = (a0 - b1 + 1); d++; if (abs(d2) y1 <= b->y0) { /* a lies above b with no vertical overlap */ brphelp(a->x0, a->x1, b->x0, b->x1, b->y0 - a->y1, angle, dist); // 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); return true; } /*if*/ if (a->y0 >= b->y1) { /* a lies below b with no vertical overlap */ brphelp(a->x0, a->x1, b->x0, b->x1, a->y0 - b->y1, angle, dist); *angle = 180 - *angle; // 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); return true; } /*if*/ if (a->x1 <= b->x0) { /* a lies to the left of b with no horizontal overlap */ brphelp(a->y0, a->y1, b->y0, b->y1, b->x0 - a->x1, angle, dist); *angle = 270 - *angle; // 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); return true; } /*if*/ if (a->x0 >= b->x1) { /* a lies to the right of b with no horizontal overlap */ brphelp(a->y0, a->y1, b->y0, b->y1, a->x0 - b->x1, angle, dist); *angle = 90 + *angle; // 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); return true; } /*if*/ /* none of the above */ // 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); return false; } /*buttonrelpos*/ static void findbestbindir(stinfo *s, const button *b, char **dest, int a) /* finds the best button to go to in the specified direction and returns its name in *dest, if that has not already been set. */ { int i, la = 0, ld = 0; if (*dest) /* already got one */ return; // fprintf(stderr,"locating nearest button from %s, angle %d\n",b->name,a); for (i = 0; i < s->numbuttons; i++) if (b != &s->buttons[i]) { int na, nd; if (buttonrelpos(&s->buttons[i].r, &b->r, &na, &nd)) { na = abs(na - a); /* error from desired direction */ if (na >= 90) /* completely wrong direction */ continue; if (!*dest || na < la || (na == la && nd < ld)) { /* first candidate, or better score than previous candidate */ // fprintf(stderr,"\tchoosing %s, na=%d, d=%d\n",s->buttons[i].name,na,nd); *dest = s->buttons[i].name; la = na; ld = nd; } /*if*/ } /*if*/ } /*if; for*/ if (*dest) *dest = strdup(*dest); /* copy the name I found */ else *dest = strdup(b->name); /* back to same button in this direction */ } /*findbestbindir*/ static void detectdirections(stinfo *s) /* automatically detects the neighbours of each button in each direction, where these have not already been specified. */ { int i; for (i = 0; i < s->numbuttons; i++) { findbestbindir(s, &s->buttons[i], &s->buttons[i].up, 0); findbestbindir(s, &s->buttons[i], &s->buttons[i].down, 180); findbestbindir(s, &s->buttons[i], &s->buttons[i].left, 270); findbestbindir(s, &s->buttons[i], &s->buttons[i].right, 90); } /*for*/ } /*detectdirections*/ #define MAX(a,b) (((a)>(b))?(a):(b)) #define MIN(a,b) (((a)<(b))?(a):(b)) static bool scanvline(stinfo *s,unsigned char *v,rectangle *r,int x,int d) { int i,j; for( j=1; j<=s->outlinewidth; j++ ) { x+=d; if( x<0 || x>=s->xd ) return false; for( i=MAX(r->y0-j,0); iy1+j,s->yd); i++ ) if( v[i*s->xd+x] ) return true; } return false; } static bool scanhline(stinfo *s,unsigned char *v,rectangle *r,int y,int d) { int i,j; for( j=1; j<=s->outlinewidth; j++ ) { y+=d; if( y<0 || y>=s->yd ) return false; for( i=MAX(r->x0-j,0); ix1+j,s->xd); i++ ) if( v[y*s->xd+i] ) return true; } return false; } static void detectbuttons(stinfo *s) /* does automatic detection of button outlines. */ { unsigned char *visitmask=malloc(s->xd*s->yd); int i,x,y; rectangle *rs=0; int numr=0; if( !s->outlinewidth ) s->outlinewidth=1; for( i=0; ixd*s->yd; i++ ) visitmask[i]=( s->hlt.pal[s->hlt.img[i]].a || s->sel.pal[s->sel.img[i]].a ) ? 1 : 0; for( y=0; yyd; y++ ) for( x=0; xxd; x++ ) if( visitmask[y*s->xd+x] ) { rectangle r; bool didwork; r.x0=x; r.y0=y; r.x1=x+1; r.y1=y+1; do { didwork = false; while( scanvline(s,visitmask,&r,r.x0,-1) ) { r.x0--; didwork = true; } while( scanvline(s,visitmask,&r,r.x1-1,1) ) { r.x1++; didwork = true; } while( scanhline(s,visitmask,&r,r.y0,-1) ) { r.y0--; didwork = true; } while( scanhline(s,visitmask,&r,r.y1-1,1) ) { r.y1++; didwork = true; } } while(didwork); r.y0-=r.y0&1; // buttons need even 'y' coordinates r.y1+=r.y1&1; // add button r rs=realloc(rs,(numr+1)*sizeof(rectangle)); rs[numr++]=r; // reset so we pass over for( i=r.y0; ixd+r.x0,0,r.x1-r.x0); } free(visitmask); while(numr) { int j=0; // find the left most button on the top row if( !s->autoorder ) { for( i=1; iautoorder?0:270) ) j=i; } // ok add rectangle 'j' for( i=0; inumbuttons; i++ ) if( s->buttons[i].r.x0<0 ) break; if( i==s->numbuttons ) { s->numbuttons++; s->buttons=realloc(s->buttons,s->numbuttons*sizeof(button)); memset(s->buttons+i,0,sizeof(button)); } fprintf(stderr,"INFO: Autodetect %d = %dx%d-%dx%d\n",i,rs[j].x0,rs[j].y0,rs[j].x1,rs[j].y1); s->buttons[i].r=rs[j]; memmove(rs+j,rs+j+1,(numr-j-1)*sizeof(rectangle)); numr--; } } static bool imgfix(stinfo *s) /* fills in the subpicture/button details. */ { int i, useimg, w, h, x, y, x0, y0; w = s->img.width; h = s->img.height; s->xd = w; // pickbuttongroups needs these values set s->yd = h; if (s->autooutline) detectbuttons(s); fixnames(s); detectdirections(s); s->fimg = malloc(w * h); memset(s->fimg, 255, w * h); /* mark all pixels as uninitialized */ // first try not to have multiple palettes for useimg = 1; if (s->numbuttons) { do { if (pickbuttongroups(s, 1, useimg)) break; if (pickbuttongroups(s, 2, useimg)) break; if (pickbuttongroups(s, 3, useimg)) break; useimg--; } while (useimg >= 0); assert(useimg); // at this point I don't want to deal with blocking the primary subtitle image if (useimg < 0) { fprintf(stderr, "ERR: Cannot pick button masks\n"); return false; } /*if*/ for (i = 0; i < s->numbuttons; i++) { /* fill in button areas of fimg */ button *b = &s->buttons[i]; for (y = b->r.y0; y < b->r.y1; y++) for (x = b->r.x0; x < b->r.x1; x++) { int dc = -1, p = y * w + x, j; int c = gettricolor(s, p, useimg); for (j = 0; j < 4; j++) if (s->groupmap[b->grp - 1][j] == c) { dc = j; break; } /*if; for*/ if (dc == -1) { /* shouldn't occur */ fprintf(stderr, "ERR: Button %d cannot find color %06x in group %d\n", i, c, b->grp - 1); assert(dc != -1); /* instant assertion failure */ } /*if*/ if (s->fimg[p] != dc && s->fimg[p] != 255) { /* pixel already occupied by another button */ fprintf(stderr, "ERR: Overlapping buttons\n"); return false; } /*if*/ s->fimg[p] = dc; } /*for; for*/ } /*for*/ } /*if s->numbuttons*/ for (i = 0; i < 4; i++) { /* initially mark all s->pal entries as "unused" (transparent) */ s->pal[i].r = 255; s->pal[i].g = 255; s->pal[i].b = 255; s->pal[i].a = 0; } /*for*/ for (i = 0; i < w * h; i++) if (s->fimg[i] != 255) s->pal[s->fimg[i]] = s->img.pal[s->img.img[i]]; for (i = 0; i < w * h; i++) /* fill in rest of fimg with "normal" image (s->img) */ if (s->fimg[i] == 255) { /* haven't already done this pixel */ int j; const colorspec * const p = &s->img.pal[s->img.img[i]]; for (j = 0; j < 4; j++) /* see if colour is already in s->pal */ if (!memcmp(&s->pal[j], p, sizeof(colorspec))) goto if_found; for (j = 0; j < 4; j++) /* insert new colour in place of unused/transparent entry */ if (s->pal[j].a == 0 && s->pal[j].r == 255) /* not checking all the components? */ { s->pal[j] = *p; goto if_found; } /*if*/ /* no room in s->pal */ fprintf(stderr, "ERR: Too many colors in base picture\n"); return false; if_found: s->fimg[i] = j; } /*if; for*/ // determine minimal visual area, and crop the subtitle accordingly x0 = w; y0 = -1; s->xd = 0; s->yd = 0; for (i = 0, y = 0; y < h; y++) { for (x = 0; x < w; i++, x++) { if ( s->img.pal[s->img.img[i]].a || s->hlt.pal[s->hlt.img[i]].a || s->sel.pal[s->sel.img[i]].a ) { if (y0 == -1) y0 = y; s->yd = y; if (x < x0) x0 = x; if (x > s->xd) s->xd = x; } /*if*/ } /*for*/ } /*for*/ if (y0 == -1) { // empty image? s->xd = w; s->yd = h; return true; } /*if*/ x0 &= -2; y0 &= -2; s->xd = (s->xd + 2 - x0) & (-2); s->yd = (s->yd + 2 - y0) & (-2); for (i = 0; i < s->yd; i++) memmove(s->fimg + i * s->xd, s->fimg + i * w + x0 + y0 * w, s->xd); for (i = 0; i < s->numbuttons; i++) { button *b = &s->buttons[i]; b->r.x0 += s->x0; b->r.y0 += s->y0; b->r.x1 += s->x0; b->r.y1 += s->y0; } /*for*/ s->x0 += x0; s->y0 += y0; return true; } /*imgfix*/ bool process_subtitle(stinfo *s) /* loads the specified image files and builds the subpicture in memory. */ { int w=0,h=0; int iline=0; if( !s ) return false; if( read_pic(s,&s->img) ) { if(debug > -1) fprintf(stderr, "WARN: Bad image, skipping line %d\n", iline - 1); return false; } if( s->img.img && !w ) { w=s->img.width; h=s->img.height; } if( read_pic(s,&s->hlt) ) { if(debug > -1) fprintf(stderr, "WARN: Bad image, skipping line %d\n", iline - 1); return false; } if( s->hlt.img && !w ) { w=s->hlt.width; h=s->hlt.height; } if( read_pic(s,&s->sel) ) { if(debug > -1) fprintf(stderr, "WARN: Bad image, skipping line %d\n", iline - 1); return false; } if( s->sel.img && !w ) { w=s->sel.width; h=s->sel.height; } if( !w ) { fprintf(stderr,"WARN: No picture, skipping line %d\n",iline-1); return false; } /* check consistent dimensions, fill in missing images with blanks */ if( !s->img.img ) { constructblankpic(&s->img,w,h); fprintf(stderr,"INFO: Constructing blank img\n"); } else if( s->img.width!=w || s->img.height!=h ) { fprintf(stderr,"WARN: Inconsistent picture widths, skipping line %d\n",iline-1); return false; } if( !s->hlt.img ) { constructblankpic(&s->hlt,w,h); fprintf(stderr,"INFO: Constructing blank hlt\n"); } else if( s->hlt.width!=w || s->hlt.height!=h ) { fprintf(stderr,"WARN: Inconsistent picture widths, skipping line %d\n",iline-1); return false; } if( !s->sel.img ) { constructblankpic(&s->sel,w,h); fprintf(stderr,"INFO: Constructing blank sel\n"); } else if( s->sel.width!=w || s->sel.height!=h ) { fprintf(stderr,"WARN: Inconsistent picture widths, skipping line %d\n",iline-1); return false; } if( !imgfix(s) ) /* data in img to fimg */ { if (debug > -1) { fprintf(stderr, "ERR: Blank image, skipping line %d\n", iline - 1); } return false; } return true; } void image_init() { #if defined(HAVE_MAGICK) || defined(HAVE_GMAGICK) InitializeMagick(NULL); #endif } void image_shutdown() { #if defined(HAVE_MAGICK) || defined(HAVE_GMAGICK) DestroyMagick(); #endif }