/* merge.c - Functions which actually combine and manipulate GIF image data. Copyright (C) 1997-9 Eddie Kohler, eddietwo@lcs.mit.edu This file is part of gifsicle. Gifsicle is free software; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. Hypo(pa)thetical commerical developers are asked to write the author a note, which might make his day. There is no warranty, express or implied. */ #include "config.h" #include "gifsicle.h" #include #include #include #include #include #include /* First merging stage: Mark the used colors in all colormaps. */ void unmark_colors(Gif_Colormap *gfcm) { int i; if (gfcm) for (i = 0; i < gfcm->ncol; i++) gfcm->col[i].haspixel = 0; } void unmark_colors_2(Gif_Colormap *gfcm) { int i; for (i = 0; i < gfcm->ncol; i++) gfcm->col[i].pixel = 256; } static void mark_used_colors(Gif_Image *gfi, Gif_Colormap *gfcm) { Gif_Color *col = gfcm->col; int ncol = gfcm->ncol; byte have[256]; int i, j, total; /* Only mark colors until we've seen all of them. The total variable keeps track of how many we've seen. have[i] is true if we've seen color i */ for (i = 0; i < ncol; i++) have[i] = 0; for (i = ncol; i < 256; i++) have[i] = 1; total = 256 - ncol; /* Loop over every pixel (until we've seen all colors) */ for (j = 0; j < gfi->height && total < 256; j++) { byte *data = gfi->img[j]; for (i = 0; i < gfi->width; i++, data++) if (!have[*data]) { have[*data] = 1; total++; } } /* Mark the colors we've found */ for (i = 0; i < ncol; i++) col[i].haspixel = have[i]; /* Mark the transparent color specially */ if (gfi->transparent >= 0 && gfi->transparent < ncol) col[gfi->transparent].haspixel = 2; /* 1.Aug.1999 - DO NOT clear transparent index if gfi->transparent isn't within bounds! Some smart GIF optimizers will write a small colormap, not including the transparent color. */ /* 19.Aug.1999 - Don't need to set col[gfi->transparent].haspixel to 2 even if it's not within bounds! */ } int find_color_index(Gif_Color *c, int nc, Gif_Color *color) { int index; for (index = 0; index < nc; index++) if (GIF_COLOREQ(&c[index], color)) return index; return -1; } static int ensure_slot_255(Gif_Colormap *dest, int ncol) { Gif_Color *background = &dest->col[255]; int i; for (i = 0; i < ncol; i++) if (GIF_COLOREQ(&dest->col[i], background)) return ncol; return ncol + 1; } int merge_colormap_if_possible(Gif_Colormap *dest, Gif_Colormap *src) { Gif_Color *srccol = src->col; Gif_Color *destcol = dest->col; int ndestcol = dest->ncol; int dest_userflags = dest->userflags; int i, x; int trivial_map = 1; for (i = 0; i < src->ncol; i++) { if (srccol[i].haspixel == 1) { /* Store an image color cell's mapping to the global colormap in its `pixel' slot. This is useful caching: oftentimes many input frames will share a colormap */ int mapto = srccol[i].pixel < 256 ? srccol[i].pixel : -1; if (mapto == -1) mapto = find_color_index(destcol, ndestcol, &srccol[i]); if (mapto == -1 && ndestcol == 255 && (dest_userflags & COLORMAP_ENSURE_SLOT_255) != 0) { ndestcol = ensure_slot_255(dest, ndestcol); dest_userflags &= ~COLORMAP_ENSURE_SLOT_255; /* slot 255 might have been equal to `srccol[i]'! */ if (ndestcol == 256 && GIF_COLOREQ(&destcol[255], &srccol[i])) mapto = 255; } if (mapto == -1 && ndestcol < 256) { /* add the color */ mapto = ndestcol; destcol[mapto] = srccol[i]; ndestcol++; } if (mapto == -1) /* check for a pure-transparent color */ for (x = 0; x < ndestcol; x++) if (destcol[x].haspixel == 2) { mapto = x; destcol[mapto] = srccol[i]; break; } if (mapto == -1) /* give up and require a local colormap */ goto local_colormap_required; assert(mapto >= 0 && mapto < ndestcol); assert(GIF_COLOREQ(&destcol[mapto], &srccol[i])); srccol[i].pixel = mapto; destcol[mapto].haspixel = 1; if (mapto != i) trivial_map = 0; } else if (srccol[i].haspixel == 2) /* a dedicated transparent color; if trivial_map & at end of colormap insert it with haspixel == 2. (strictly not necessary; we do it to try to keep the map trivial.) */ if (trivial_map && i == ndestcol) { destcol[ndestcol] = srccol[i]; ndestcol++; } } /* success! save new number of colors */ dest->ncol = ndestcol; dest->userflags = dest_userflags; return 1; /* failure: a local colormap is required */ local_colormap_required: if (warn_local_colormaps == 1) { static int context = 0; warning("so many colors that local colormaps were required"); if (!context) warncontext("(You may want to try `--colors 256'.)"); warn_local_colormaps = 2; context = 1; } /* 9.Dec.1998 - This must have been a longstanding bug! We MUST clear the cached mappings of any pixels in the source colormap we assigned this time through, since we are throwing those colors away. We assigned it this time through if the cached mapping is >= dest->ncol. */ for (x = 0; x < i; x++) if (srccol[x].haspixel == 1 && srccol[x].pixel >= dest->ncol) srccol[x].pixel = 256; return 0; } void merge_stream(Gif_Stream *dest, Gif_Stream *src, int no_comments) { int i; assert(dest->global); /* unmark colors in global and local colormaps -- 12/9 */ if (src->global) unmark_colors_2(src->global); for (i = 0; i < src->nimages; i++) if (src->images[i]->local) unmark_colors_2(src->images[i]->local); if (dest->loopcount < 0) dest->loopcount = src->loopcount; if (src->comment && !no_comments) { if (!dest->comment) dest->comment = Gif_NewComment(); merge_comments(dest->comment, src->comment); } } void merge_comments(Gif_Comment *destc, Gif_Comment *srcc) { int i; for (i = 0; i < srcc->count; i++) Gif_AddComment(destc, srcc->str[i], srcc->len[i]); } Gif_Image * merge_image(Gif_Stream *dest, Gif_Stream *src, Gif_Image *srci) { Gif_Colormap *imagecm; Gif_Color *imagecol; int islocal; int i; Gif_Colormap *localcm = 0; Gif_Colormap *destcm = dest->global; byte map[256]; /* map[input pixel value] == output pixval */ int trivial_map = 1; /* does the map take input pixval --> the same pixel value for all colors in the image? */ byte used[256]; /* used[output pixval K] == 1 iff K was used in the image */ Gif_Image *desti; /* mark colors that were actually used in this image */ islocal = srci->local != 0; imagecm = islocal ? srci->local : src->global; if (!imagecm) fatal_error("no global or local colormap for source image"); mark_used_colors(srci, imagecm); imagecol = imagecm->col; /* may be changed by mark_used_colors */ /* map[old_pixel_value] == new_pixel_value */ for (i = 0; i < 256; i++) map[i] = used[i] = 0; /* Merge the colormap */ if (merge_colormap_if_possible(dest->global, imagecm)) { /* Create `map' and `used' for global colormap. */ for (i = 0; i < imagecm->ncol; i++) if (imagecol[i].haspixel == 1) { map[i] = imagecol[i].pixel; if (map[i] != i) trivial_map = 0; used[map[i]] = 1; } } else { /* Need a local colormap. */ int ncol = 0; destcm = localcm = Gif_NewFullColormap(0, 256); for (i = 0; i < imagecm->ncol; i++) if (imagecol[i].haspixel) { map[i] = ncol; if (ncol != i) trivial_map = 0; /* 19.Aug.1999- BUGFIX! color is only used if it was opaque */ if (imagecol[i].haspixel == 1) used[ncol] = 1; localcm->col[ ncol++ ] = imagecol[i]; } localcm->ncol = ncol; } /* Decide on a transparent index */ if (srci->transparent >= 0) { int found_transparent = -1; /* try to keep the map trivial -- prefer same transparent index */ if (trivial_map && !used[srci->transparent]) found_transparent = srci->transparent; else for (i = destcm->ncol - 1; i >= 0; i--) if (!used[i]) found_transparent = i; /* 1.Aug.1999 - Allow for the case that the transparent index is bigger than the number of colors we've created thus far. */ if (found_transparent < 0 || found_transparent >= destcm->ncol) { Gif_Color *c; found_transparent = destcm->ncol; /* 1.Aug.1999 - Don't update destcm->ncol -- we want the output colormap to be as small as possible. */ c = &destcm->col[found_transparent]; if (srci->transparent < imagecm->ncol) *c = imagecol[srci->transparent]; else c->haspixel = 2; assert(c->haspixel == 2 && found_transparent < 256); } map[srci->transparent] = found_transparent; if (srci->transparent != found_transparent) trivial_map = 0; } assert(destcm->ncol <= 256); /* Make the new image. */ desti = Gif_NewImage(); desti->identifier = Gif_CopyString(srci->identifier); if (srci->transparent > -1) desti->transparent = map[srci->transparent]; desti->delay = srci->delay; desti->disposal = srci->disposal; desti->left = srci->left; desti->top = srci->top; desti->interlace = srci->interlace; desti->width = srci->width; desti->height = srci->height; desti->local = localcm; if (srci->comment) { desti->comment = Gif_NewComment(); merge_comments(desti->comment, srci->comment); } Gif_CreateUncompressedImage(desti); { int i, j; if (trivial_map) for (j = 0; j < desti->height; j++) memcpy(desti->img[j], srci->img[j], desti->width); else for (j = 0; j < desti->height; j++) { byte *srcdata = srci->img[j]; byte *destdata = desti->img[j]; for (i = 0; i < desti->width; i++, srcdata++, destdata++) *destdata = map[*srcdata]; } } Gif_AddImage(dest, desti); return desti; }