static int unoptimize_image(Gif_Stream *gfs, Gif_Image *gfi, uint16_t *screen) { unsigned size = gfs->screen_width * gfs->screen_height; int used_transparent; uint8_t *new_data = Gif_NewArray(uint8_t, size); uint16_t *new_screen = screen; if (!new_data) return 0; /* Oops! May need to uncompress it */ Gif_UncompressImage(gfs, gfi); Gif_ReleaseCompressedImage(gfi); if (gfi->disposal == GIF_DISPOSAL_PREVIOUS) { new_screen = Gif_NewArray(uint16_t, size); if (!new_screen) return 0; memcpy(new_screen, screen, size * sizeof(uint16_t)); } put_image_in_screen(gfs, gfi, new_screen); if (!create_image_data(gfs, gfi, new_screen, new_data, &used_transparent)) { Gif_DeleteArray(new_data); return 0; } if (gfi->disposal == GIF_DISPOSAL_PREVIOUS) Gif_DeleteArray(new_screen); else if (gfi->disposal == GIF_DISPOSAL_BACKGROUND) put_background_in_screen(gfs, gfi, screen); gfi->left = 0; gfi->top = 0; gfi->width = gfs->screen_width; gfi->height = gfs->screen_height; gfi->disposal = used_transparent; Gif_SetUncompressedImage(gfi, new_data, Gif_Free, 0); return 1; }
static void create_new_image_data(Gif_Stream *gfs, int optimize_flags) { Gif_Image cur_unopt_gfi; /* placehoder; maintains pre-optimization image size so we can apply background disposal */ unsigned screen_size = (unsigned) screen_width * (unsigned) screen_height; uint16_t *previous_data = 0; Gif_CompressInfo gcinfo = gif_write_info; if ((optimize_flags & GT_OPT_MASK) >= 3) gcinfo.flags |= GIF_WRITE_OPTIMIZE; gfs->global = out_global_map; /* do first image. Remember to uncompress it if necessary */ erase_screen(last_data); erase_screen(this_data); for (image_index = 0; image_index < gfs->nimages; image_index++) { Gif_Image *cur_gfi = gfs->images[image_index]; Gif_OptData *opt = (Gif_OptData *)cur_gfi->user_data; int was_compressed = (cur_gfi->img == 0); /* save previous data if necessary */ if (cur_gfi->disposal == GIF_DISPOSAL_PREVIOUS) { if (!previous_data) previous_data = Gif_NewArray(uint16_t, screen_size); copy_data_area(previous_data, this_data, cur_gfi); } /* set up this_data to be equal to the current image */ apply_frame(this_data, cur_gfi, 0, 0); /* save actual bounds and disposal from unoptimized version so we can apply the disposal correctly next time through */ cur_unopt_gfi = *cur_gfi; /* set bounds and disposal from optdata */ Gif_ReleaseUncompressedImage(cur_gfi); cur_gfi->left = opt->left; cur_gfi->top = opt->top; cur_gfi->width = opt->width; cur_gfi->height = opt->height; cur_gfi->disposal = opt->disposal; if (image_index > 0) cur_gfi->interlace = 0; /* find the new image's colormap and then make new data */ { uint8_t *map = prepare_colormap(cur_gfi, opt->needed_colors); uint8_t *data = Gif_NewArray(uint8_t, (size_t) cur_gfi->width * (size_t) cur_gfi->height); Gif_SetUncompressedImage(cur_gfi, data, Gif_DeleteArrayFunc, 0); /* don't use transparency on first frame */ if ((optimize_flags & GT_OPT_MASK) > 1 && image_index > 0 && cur_gfi->transparent >= 0) transp_frame_data(gfs, cur_gfi, map, optimize_flags, &gcinfo); else simple_frame_data(cur_gfi, map); if (cur_gfi->img) { if (was_compressed || (optimize_flags & GT_OPT_MASK) > 1) { Gif_FullCompressImage(gfs, cur_gfi, &gcinfo); Gif_ReleaseUncompressedImage(cur_gfi); } else /* bug fix 22.May.2001 */ Gif_ReleaseCompressedImage(cur_gfi); } Gif_DeleteArray(map); } delete_opt_data(opt); cur_gfi->user_data = 0; /* Set up last_data and this_data. last_data must contain this_data + new disposal. this_data must contain this_data + old disposal. */ if (cur_gfi->disposal == GIF_DISPOSAL_NONE || cur_gfi->disposal == GIF_DISPOSAL_ASIS) copy_data_area(last_data, this_data, cur_gfi); else if (cur_gfi->disposal == GIF_DISPOSAL_BACKGROUND) fill_data_area(last_data, background, cur_gfi); else if (cur_gfi->disposal != GIF_DISPOSAL_PREVIOUS) assert(0 && "optimized frame has strange disposal"); if (cur_unopt_gfi.disposal == GIF_DISPOSAL_BACKGROUND) fill_data_area(this_data, background, &cur_unopt_gfi); else if (cur_unopt_gfi.disposal == GIF_DISPOSAL_PREVIOUS) copy_data_area(this_data, previous_data, &cur_unopt_gfi); } if (previous_data) Gif_DeleteArray(previous_data); }
static void transp_frame_data(Gif_Stream *gfs, Gif_Image *gfi, uint8_t *map, int optimize_flags, Gif_CompressInfo *gcinfo) { Gif_OptBounds ob = safe_bounds(gfi); int x, y, transparent = gfi->transparent; uint16_t *last = 0; uint16_t *cur = 0; uint8_t *data, *begin_same; uint8_t *t2_data = 0, *last_for_t2; int nsame; /* First, try w/o transparency. Compare this to the result using transparency and pick the better of the two. */ simple_frame_data(gfi, map); Gif_FullCompressImage(gfs, gfi, gcinfo); gcinfo->flags |= GIF_WRITE_SHRINK; /* Actually copy data to frame. Use transparency if possible to shrink the size of the written GIF. The written GIF will be small if patterns (sequences of pixel values) recur in the image. We could conceivably use transparency to produce THE OPTIMAL image, with the most recurring patterns of the best kinds; but this would be very hard (wouldn't it?). Instead, we settle for a heuristic: we try and create RUNS. (Since we *try* to create them, they will presumably recur!) A RUN is a series of adjacent pixels all with the same value. By & large, we just use the regular image's values. However, we might create a transparent run *not in* the regular image, if TWO OR MORE adjacent runs OF DIFFERENT COLORS *could* be made transparent. (An area can be made transparent if the corresponding area in the previous frame had the same colors as the area does now.) Why? If only one run (say of color C) could be transparent, we get no large immediate advantage from making it transparent (it'll be a run of the same length regardless). Also, we might LOSE: what if the run was adjacent to some more of color C, which couldn't be made transparent? If we use color C (instead of the transparent color), then we get a longer run. This simple heuristic does a little better than Gifwizard's (6/97) on some images, but does *worse than nothing at all* on others. However, it DOES do better than the complicated, greedy algorithm that preceded it; and now we pick either the transparency-optimized version or the normal version, whichever compresses smaller, for the best of both worlds. (9/98) On several images, making SINGLE color runs transparent wins over the previous heuristic, so try both at optimize level 3 or above (the cost is ~30%). (2/11) */ data = begin_same = last_for_t2 = gfi->image_data; nsame = 0; for (y = 0; y < ob.height; ++y) { last = last_data + (unsigned) screen_width * (y + ob.top) + ob.left; cur = this_data + (unsigned) screen_width * (y + ob.top) + ob.left; for (x = 0; x < ob.width; ++x) { if (*cur != *last && map[*cur] != transparent) { if (nsame == 1 && data[-1] != transparent && (optimize_flags & GT_OPT_MASK) > 2) { if (!t2_data) t2_data = Gif_NewArray(uint8_t, (size_t) ob.width * (size_t) ob.height); memcpy(t2_data + (last_for_t2 - gfi->image_data), last_for_t2, begin_same - last_for_t2); memset(t2_data + (begin_same - gfi->image_data), transparent, data - begin_same); last_for_t2 = data; } nsame = 0; } else if (nsame == 0) { begin_same = data; ++nsame; } else if (nsame == 1 && map[*cur] != data[-1]) { memset(begin_same, transparent, data - begin_same); ++nsame; } if (nsame > 1) *data = transparent; else *data = map[*cur]; ++data, ++cur, ++last; } } if (t2_data) memcpy(t2_data + (last_for_t2 - gfi->image_data), last_for_t2, data - last_for_t2); /* Now, try compressed transparent version(s) and pick the better of the two (or three). */ Gif_FullCompressImage(gfs, gfi, gcinfo); if (t2_data) { Gif_SetUncompressedImage(gfi, t2_data, Gif_DeleteArrayFunc, 0); Gif_FullCompressImage(gfs, gfi, gcinfo); } Gif_ReleaseUncompressedImage(gfi); gcinfo->flags &= ~GIF_WRITE_SHRINK; }