예제 #1
0
static void
find_difference_bounds(Gif_OptData *bounds, Gif_Image *gfi, Gif_Image *last)
{
  int lf, rt, lf_min, rt_max, tp, bt, x, y;
  Gif_OptBounds ob;

  /* 1.Aug.99 - use current bounds if possible, since this function is a speed
     bottleneck */
  if (!last || last->disposal == GIF_DISPOSAL_NONE
      || last->disposal == GIF_DISPOSAL_ASIS) {
    ob = safe_bounds(gfi);
    lf_min = ob.left;
    rt_max = ob.left + ob.width - 1;
    tp = ob.top;
    bt = ob.top + ob.height - 1;
  } else {
    lf_min = 0;
    rt_max = screen_width - 1;
    tp = 0;
    bt = screen_height - 1;
  }

  for (; tp < screen_height; tp++)
    if (memcmp(last_data + (unsigned) screen_width * tp,
               this_data + (unsigned) screen_width * tp,
	       screen_width * sizeof(uint16_t)) != 0)
      break;
  for (; bt >= tp; bt--)
    if (memcmp(last_data + (unsigned) screen_width * bt,
               this_data + (unsigned) screen_width * bt,
	       screen_width * sizeof(uint16_t)) != 0)
      break;

  lf = screen_width;
  rt = 0;
  for (y = tp; y <= bt; y++) {
    uint16_t *ld = last_data + (unsigned) screen_width * y;
    uint16_t *td = this_data + (unsigned) screen_width * y;
    for (x = lf_min; x < lf; x++)
      if (ld[x] != td[x])
	break;
    lf = x;

    for (x = rt_max; x > rt; x--)
      if (ld[x] != td[x])
	break;
    rt = x;
  }

  /* 19.Aug.1999 - handle case when there's no difference between frames */
  if (tp > bt) {
    tp = bt = gfi->top;
    lf = rt = gfi->left;
  }

  bounds->left = lf;
  bounds->top = tp;
  bounds->width = rt + 1 - lf;
  bounds->height = bt + 1 - tp;
}
예제 #2
0
static void
fill_data_area(uint16_t *dst, uint16_t value, Gif_Image *area)
{
  int x, y;
  Gif_OptBounds ob = safe_bounds(area);
  dst += ob.top * (unsigned) screen_width + ob.left;
  for (y = 0; y < ob.height; y++) {
    for (x = 0; x < ob.width; x++)
      dst[x] = value;
    dst += screen_width;
  }
}
예제 #3
0
static void
simple_frame_data(Gif_Image *gfi, uint8_t *map)
{
  Gif_OptBounds ob = safe_bounds(gfi);
  int x, y, scan_width = gfi->width;

  for (y = 0; y < ob.height; y++) {
    uint16_t *from = this_data + screen_width * (y + ob.top) + ob.left;
    uint8_t *into = gfi->image_data + y * scan_width;
    for (x = 0; x < ob.width; x++)
      *into++ = map[*from++];
  }
}
예제 #4
0
static void
copy_data_area(uint16_t *dst, uint16_t *src, Gif_Image *area)
{
  Gif_OptBounds ob;
  int y;
  if (!area)
    return;
  ob = safe_bounds(area);
  dst += ob.top * (unsigned) screen_width + ob.left;
  src += ob.top * (unsigned) screen_width + ob.left;
  for (y = 0; y < ob.height; y++) {
    memcpy(dst, src, sizeof(uint16_t) * ob.width);
    dst += screen_width;
    src += screen_width;
  }
}
예제 #5
0
static void
apply_frame(uint16_t *dst, Gif_Stream* gfs, Gif_Image* gfi,
            int replace, int save_uncompressed)
{
  int i, y, was_compressed = 0;
  uint16_t map[256];
  Gif_Colormap *colormap = gfi->local ? gfi->local : in_global_map;
  Gif_OptBounds ob = safe_bounds(gfi);

  if (!gfi->img) {
    was_compressed = 1;
    Gif_UncompressImage(gfs, gfi);
  }

  /* make sure transparency maps to TRANSP */
  for (i = 0; i < colormap->ncol; i++)
    map[i] = colormap->col[i].pixel;
  /* out-of-bounds colors map to 0, for the sake of argument */
  for (i = colormap->ncol; i < 256; i++)
    map[i] = colormap->col[0].pixel;
  if (gfi->transparent >= 0 && gfi->transparent < 256)
    map[gfi->transparent] = TRANSP;
  else
    replace = 1;

  /* map the image */
  dst += ob.left + ob.top * (unsigned) screen_width;
  for (y = 0; y < ob.height; y++) {
    uint8_t *gfi_pointer = gfi->img[y];
    int x;

    if (replace)
      for (x = 0; x < ob.width; x++)
	dst[x] = map[gfi_pointer[x]];
    else
      for (x = 0; x < ob.width; x++) {
	uint16_t new_pixel = map[gfi_pointer[x]];
	if (new_pixel != TRANSP)
	    dst[x] = new_pixel;
      }

    dst += screen_width;
  }

  if (was_compressed && !save_uncompressed)
    Gif_ReleaseUncompressedImage(gfi);
}
예제 #6
0
static void
create_subimages(Gif_Stream *gfs, int optimize_flags, int save_uncompressed)
{
  unsigned screen_size;
  Gif_Image *last_gfi;
  int next_data_valid;
  uint16_t *previous_data = 0;
  int local_color_tables = 0;

  screen_size = (unsigned) screen_width * (unsigned) screen_height;

  next_data = Gif_NewArray(uint16_t, screen_size);
  next_data_valid = 0;

  /* do first image. Remember to uncompress it if necessary */
  erase_screen(last_data);
  erase_screen(this_data);
  last_gfi = 0;

  /* PRECONDITION:
     previous_data -- garbage
     last_data -- optimized image after disposal of previous optimized frame
     this_data -- input image after disposal of previous input frame
     next_data -- input image after application of current input frame,
                  if next_image_valid */
  for (image_index = 0; image_index < gfs->nimages; image_index++) {
    Gif_Image *gfi = gfs->images[image_index];
    Gif_OptData *subimage = new_opt_data();
    if (gfi->local)
        local_color_tables = 1;

    /* save previous data if necessary */
    if (gfi->disposal == GIF_DISPOSAL_PREVIOUS
        || (local_color_tables && image_index > 0
            && last_gfi->disposal > GIF_DISPOSAL_ASIS)) {
      if (!previous_data)
	previous_data = Gif_NewArray(uint16_t, screen_size);
      memcpy(previous_data, this_data, sizeof(uint16_t) * screen_size);
    }

    /* set this_data equal to the current image */
    if (next_data_valid) {
      uint16_t *temp = this_data;
      this_data = next_data;
      next_data = temp;
      next_data_valid = 0;
    } else
      apply_frame(this_data, gfi, 0, save_uncompressed);

 retry_frame:
    /* find minimum area of difference between this image and last image */
    subimage->disposal = GIF_DISPOSAL_ASIS;
    if (image_index > 0)
      find_difference_bounds(subimage, gfi, last_gfi);
    else {
      Gif_OptBounds ob = safe_bounds(gfi);
      subimage->left = ob.left;
      subimage->top = ob.top;
      subimage->width = ob.width;
      subimage->height = ob.height;
    }

    /* might need to expand difference border if transparent background &
       background disposal */
    if ((gfi->disposal == GIF_DISPOSAL_BACKGROUND
	 || gfi->disposal == GIF_DISPOSAL_PREVIOUS)
	&& background == TRANSP
	&& image_index < gfs->nimages - 1) {
      /* set up next_data */
      Gif_Image *next_gfi = gfs->images[image_index + 1];
      apply_frame_disposal(next_data, this_data, previous_data, gfi);
      apply_frame(next_data, next_gfi, 0, save_uncompressed);
      next_data_valid = 1;
      /* expand border as necessary */
      if (expand_difference_bounds(subimage, gfi))
	subimage->disposal = GIF_DISPOSAL_BACKGROUND;
    }

    fix_difference_bounds(subimage);

    /* set map of used colors */
    {
      int use_transparency = (optimize_flags & GT_OPT_MASK) > 1
          && image_index > 0;
      if (image_index == 0 && background == TRANSP)
	use_transparency = 2;
      get_used_colors(subimage, use_transparency);
      /* Gifsicle's optimization strategy normally creates frames with ASIS
         or BACKGROUND disposal (not PREVIOUS disposal). However, there are
         cases when PREVIOUS disposal is strictly required, or a frame would
         require more than 256 colors. Detect this case and try to recover. */
      if (subimage->required_color_count > 256) {
          if (image_index > 0 && local_color_tables) {
              Gif_OptData *subimage = (Gif_OptData*) last_gfi->user_data;
              if ((last_gfi->disposal == GIF_DISPOSAL_PREVIOUS
                   || last_gfi->disposal == GIF_DISPOSAL_BACKGROUND)
                  && subimage->disposal != last_gfi->disposal) {
                  subimage->disposal = last_gfi->disposal;
                  memcpy(last_data, previous_data, sizeof(uint16_t) * screen_size);
                  goto retry_frame;
              }
          }
          fatal_error("%d colors required in a frame (256 is max)",
                      subimage->required_color_count);
      }
    }

    gfi->user_data = subimage;
    last_gfi = gfi;

    /* Apply optimized disposal to last_data and unoptimized disposal to
       this_data. Before 9.Dec.1998 I applied unoptimized disposal uniformly
       to both. This led to subtle bugs. After all, to determine bounds, we
       want to compare the current image (only obtainable through unoptimized
       disposal) with what WILL be left after the previous OPTIMIZED image's
       disposal. This fix is repeated in create_new_image_data */
    if (subimage->disposal == GIF_DISPOSAL_BACKGROUND)
      fill_data_area_subimage(last_data, background, subimage);
    else
      copy_data_area_subimage(last_data, this_data, subimage);

    if (last_gfi->disposal == GIF_DISPOSAL_BACKGROUND)
      fill_data_area(this_data, background, last_gfi);
    else if (last_gfi->disposal == GIF_DISPOSAL_PREVIOUS) {
      uint16_t *temp = previous_data;
      previous_data = this_data;
      this_data = temp;
    }
  }

  Gif_DeleteArray(next_data);
  if (previous_data)
    Gif_DeleteArray(previous_data);
}
예제 #7
0
static int
expand_difference_bounds(Gif_OptData *bounds, Gif_Image *this_bounds)
{
  int x, y, expanded = 0;
  Gif_OptBounds ob = safe_bounds(this_bounds);

  if (bounds->width <= 0 || bounds->height <= 0) {
      bounds->left = bounds->top = 0;
      bounds->width = screen_width;
      bounds->height = screen_height;
  }

  /* 20.Nov.2013 - The image `bounds` might be larger than `this_bounds`
     because of a previous frame's background disposal. Don't accidentally
     shrink `this_bounds`. */
  if (ob.left > bounds->left) {
      ob.width = (ob.left + ob.width) - bounds->left;
      ob.left = bounds->left;
  }
  if (ob.top > bounds->top) {
      ob.height = (ob.top + ob.height) - bounds->top;
      ob.top = bounds->top;
  }
  if (ob.left + ob.width < bounds->left + bounds->width)
      ob.width = bounds->left + bounds->width - ob.left;
  if (ob.top + ob.height < bounds->top + bounds->height)
      ob.height = bounds->top + bounds->height - ob.top;

  for (; ob.top < bounds->top; ++ob.top, --ob.height) {
    uint16_t *now = this_data + (unsigned) screen_width * ob.top;
    uint16_t *next = next_data + (unsigned) screen_width * ob.top;
    for (x = ob.left; x < ob.left + ob.width; ++x)
      if (now[x] != TRANSP && next[x] == TRANSP) {
	expanded = 1;
	goto found_top;
      }
  }

 found_top:
  for (; ob.top + ob.height > bounds->top + bounds->height; --ob.height) {
    uint16_t *now = this_data + (unsigned) screen_width * (ob.top + ob.height - 1);
    uint16_t *next = next_data + (unsigned) screen_width * (ob.top + ob.height - 1);
    for (x = ob.left; x < ob.left + ob.width; ++x)
      if (now[x] != TRANSP && next[x] == TRANSP) {
	expanded = 1;
	goto found_bottom;
      }
  }

 found_bottom:
  for (; ob.left < bounds->left; ++ob.left, --ob.width) {
    uint16_t *now = this_data + ob.left;
    uint16_t *next = next_data + ob.left;
    for (y = ob.top; y < ob.top + ob.height; ++y)
      if (now[y * (unsigned) screen_width] != TRANSP
          && next[y * (unsigned) screen_width] == TRANSP) {
	expanded = 1;
	goto found_left;
      }
  }

 found_left:
  for (; ob.left + ob.width > bounds->left + bounds->width; --ob.width) {
    uint16_t *now = this_data + ob.left + ob.width - 1;
    uint16_t *next = next_data + ob.left + ob.width - 1;
    for (y = ob.top; y < ob.top + ob.height; ++y)
      if (now[y * (unsigned) screen_width] != TRANSP
          && next[y * (unsigned) screen_width] == TRANSP) {
	expanded = 1;
	goto found_right;
      }
  }

 found_right:
  if (!expanded)
    for (y = ob.top; y < ob.top + ob.height; ++y) {
      uint16_t *now = this_data + y * (unsigned) screen_width;
      uint16_t *next = next_data + y * (unsigned) screen_width;
      for (x = ob.left; x < ob.left + ob.width; ++x)
	if (now[x] != TRANSP && next[x] == TRANSP) {
	  expanded = 1;
	  break;
	}
    }

  bounds->left = ob.left;
  bounds->top = ob.top;
  bounds->width = ob.width;
  bounds->height = ob.height;
  return expanded;
}
예제 #8
0
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;
}
예제 #9
0
static void
create_subimages(Gif_Stream *gfs, int optimize_flags, int save_uncompressed)
{
  int screen_size;
  Gif_Image *last_gfi;
  int next_data_valid;
  uint16_t *previous_data = 0;

  screen_size = screen_width * screen_height;

  next_data = Gif_NewArray(uint16_t, screen_size);
  next_data_valid = 0;

  /* do first image. Remember to uncompress it if necessary */
  erase_screen(last_data);
  erase_screen(this_data);
  last_gfi = 0;

  /* PRECONDITION: last_data, previous_data -- garbage
     this_data -- equal to image data after disposal of previous image
     next_data -- equal to image data for next image if next_image_valid */
  for (image_index = 0; image_index < gfs->nimages; image_index++) {
    Gif_Image *gfi = gfs->images[image_index];
    Gif_OptData *subimage = new_opt_data();

    /* save previous data if necessary */
    if (gfi->disposal == GIF_DISPOSAL_PREVIOUS) {
      if (!previous_data)
	previous_data = Gif_NewArray(uint16_t, screen_size);
      memcpy(previous_data, this_data, sizeof(uint16_t) * screen_size);
    }

    /* set this_data equal to the current image */
    if (next_data_valid) {
      uint16_t *temp = this_data;
      this_data = next_data;
      next_data = temp;
      next_data_valid = 0;
    } else
      apply_frame(this_data, gfi, 0, save_uncompressed);

    /* find minimum area of difference between this image and last image */
    subimage->disposal = GIF_DISPOSAL_ASIS;
    if (image_index > 0)
      find_difference_bounds(subimage, gfi, last_gfi);
    else {
      Gif_OptBounds ob = safe_bounds(gfi);
      subimage->left = ob.left;
      subimage->top = ob.top;
      subimage->width = ob.width;
      subimage->height = ob.height;
    }

    /* might need to expand difference border if transparent background &
       background disposal */
    if ((gfi->disposal == GIF_DISPOSAL_BACKGROUND
	 || gfi->disposal == GIF_DISPOSAL_PREVIOUS)
	&& background == TRANSP
	&& image_index < gfs->nimages - 1) {
      /* set up next_data */
      Gif_Image *next_gfi = gfs->images[image_index + 1];
      apply_frame_disposal(next_data, this_data, previous_data, gfi);
      apply_frame(next_data, next_gfi, 0, save_uncompressed);
      next_data_valid = 1;
      /* expand border as necessary */
      if (expand_difference_bounds(subimage, gfi))
	subimage->disposal = GIF_DISPOSAL_BACKGROUND;
    }

    fix_difference_bounds(subimage);

    /* set map of used colors */
    {
      int use_transparency = (optimize_flags & GT_OPT_MASK) > 1 && image_index > 0;
      if (image_index == 0 && background == TRANSP)
	use_transparency = 2;
      get_used_colors(subimage, use_transparency);
    }

    gfi->user_data = subimage;
    last_gfi = gfi;

    /* Apply optimized disposal to last_data and unoptimized disposal to
       this_data. Before 9.Dec.1998 I applied unoptimized disposal uniformly
       to both. This led to subtle bugs. After all, to determine bounds, we
       want to compare the current image (only obtainable through unoptimized
       disposal) with what WILL be left after the previous OPTIMIZED image's
       disposal. This fix is repeated in create_new_image_data */
    if (subimage->disposal == GIF_DISPOSAL_BACKGROUND)
      fill_data_area_subimage(last_data, background, subimage);
    else
      copy_data_area_subimage(last_data, this_data, subimage);

    if (last_gfi->disposal == GIF_DISPOSAL_BACKGROUND)
      fill_data_area(this_data, background, last_gfi);
    else if (last_gfi->disposal == GIF_DISPOSAL_PREVIOUS) {
      uint16_t *temp = previous_data;
      previous_data = this_data;
      this_data = temp;
    }
  }

  Gif_DeleteArray(next_data);
  if (previous_data)
    Gif_DeleteArray(previous_data);
}
예제 #10
0
static int
expand_difference_bounds(Gif_OptData *bounds, Gif_Image *this_bounds)
{
  int x, y, expanded = 0;

  int lf = bounds->left, tp = bounds->top,
      rt = lf + bounds->width - 1, bt = tp + bounds->height - 1;

  Gif_OptBounds ob = safe_bounds(this_bounds);
  int tlf = ob.left, ttp = ob.top,
      trt = ob.left + ob.width - 1, tbt = ob.top + ob.height - 1;

  if (lf > rt || tp > bt)
    lf = 0, tp = 0, rt = screen_width - 1, bt = screen_height - 1;

  for (y = ttp; y < tp; y++) {
    uint16_t *now = this_data + screen_width * y;
    uint16_t *next = next_data + screen_width * y;
    for (x = tlf; x <= trt; x++)
      if (now[x] != TRANSP && next[x] == TRANSP) {
	expanded = 1;
	goto found_top;
      }
  }
 found_top:
  tp = y;

  for (y = tbt; y > bt; y--) {
    uint16_t *now = this_data + screen_width * y;
    uint16_t *next = next_data + screen_width * y;
    for (x = tlf; x <= trt; x++)
      if (now[x] != TRANSP && next[x] == TRANSP) {
	expanded = 1;
	goto found_bottom;
      }
  }
 found_bottom:
  bt = y;

  for (x = tlf; x < lf; x++) {
    uint16_t *now = this_data + x;
    uint16_t *next = next_data + x;
    for (y = tp; y <= bt; y++)
      if (now[y*screen_width] != TRANSP && next[y*screen_width] == TRANSP) {
	expanded = 1;
	goto found_left;
      }
  }
 found_left:
  lf = x;

  for (x = trt; x > rt; x--) {
    uint16_t *now = this_data + x;
    uint16_t *next = next_data + x;
    for (y = tp; y <= bt; y++)
      if (now[y*screen_width] != TRANSP && next[y*screen_width] == TRANSP) {
	expanded = 1;
	goto found_right;
      }
  }
 found_right:
  rt = x;

  if (!expanded)
    for (y = tp; y <= bt; ++y) {
      uint16_t *now = this_data + y*screen_width;
      uint16_t *next = next_data + y*screen_width;
      for (x = lf; x <= rt; ++x)
	if (now[x] != TRANSP && next[x] == TRANSP) {
	  expanded = 1;
	  goto found_expanded;
	}
    }

 found_expanded:
  bounds->left = lf;
  bounds->top = tp;
  bounds->width = rt + 1 - lf;
  bounds->height = bt + 1 - tp;
  return expanded;
}