BGD_DECLARE(void) gdImageGifAnimAddCtx(gdImagePtr im, gdIOCtxPtr out,
                                       int LocalCM, int LeftOfs, int TopOfs,
                                       int Delay, int Disposal,
                                       gdImagePtr previm)
{
	gdImagePtr pim = NULL, tim = im;
	int interlace, transparent, BitsPerPixel;
	interlace = im->interlace;
	transparent = im->transparent;

	/* Default is no local color map */
	if(LocalCM < 0) {
		LocalCM = 0;
	}

	if(im->trueColor) {
		/* Expensive, but the only way that produces an
			acceptable result: mix down to a palette
			based temporary image. */
		pim = gdImageCreatePaletteFromTrueColor(im, 1, 256);
		if (!pim) {
			return;
		}
		tim = pim;
	}

	if (previm) {
		/* create optimized animation.  Compare this image to
		   the previous image and crop the temporary copy of
		   current image to include only changed rectangular
		   area.  Also replace unchanged pixels inside this
		   area with transparent color.  Transparent color
		   needs to be already allocated!
		   Preconditions:
		   TopOfs, LeftOfs are assumed 0

		   Images should be of same size.  If not, a temporary
		   copy is made with the same size as previous image.

		*/
		gdImagePtr prev_pim = 0, prev_tim = previm;
		int x, y;
		int min_x = 0;
		int min_y = tim->sy;
		int max_x = 0;
		int max_y = 0;
		int colorMap[256];

		if (previm->trueColor) {
			prev_pim = gdImageCreatePaletteFromTrueColor(previm, 1, 256);
			if (!prev_pim) {
				goto fail_end;
			}
			prev_tim = prev_pim;
		}

		for (x = 0; x < 256; ++x) {
			colorMap[x] = -2;
		}

		/* First find bounding box of changed areas. */
		/* first find the top changed row */
		for (y = 0; y < tim->sy; ++y) {
			for (x = 0; x < tim->sx; ++x) {
				if (!comparewithmap(prev_tim, tim,
				                    prev_tim->pixels[y][x],
				                    tim->pixels[y][x],
				                    colorMap)) {
					min_y = max_y = y;
					min_x = max_x = x;
					goto break_top;
				}
			}
		}

break_top:
		if (tim->sy == min_y) {
			/* No changes in this frame!! Encode empty image. */
			transparent = 0;
			min_x = min_y = 1;
			max_x = max_y = 0;
		} else {
			/* Then the bottom row */
			for (y = tim->sy - 1; y > min_y; --y) {
				for (x = 0; x < tim->sx; ++x) {
					if (!comparewithmap
					        (prev_tim, tim,
					         prev_tim->pixels[y][x],
					         tim->pixels[y][x],
					         colorMap)) {
						max_y = y;
						if(x < min_x) {
							min_x = x;
						}
						if(x > max_x) {
							max_x = x;
						}
						goto break_bot;
					}
				}
			}

break_bot:
			/* left side */
			for (x = 0; x < min_x; ++x) {
				for (y = min_y; y <= max_y; ++y) {
					if (!comparewithmap
					        (prev_tim, tim,
					         prev_tim->pixels[y][x],
					         tim->pixels[y][x],
					         colorMap)) {
						min_x = x;
						goto break_left;
					}
				}
			}

break_left:
			/* right side */
			for (x = tim->sx - 1; x > max_x; --x) {
				for (y = min_y; y <= max_y; ++y) {
					if (!comparewithmap
					        (prev_tim, tim,
					         prev_tim->pixels[y][x],
					         tim->pixels[y][x],
					         colorMap)) {
						max_x = x;
						goto break_right;
					}
				}
			}

break_right:
			;
		}

		LeftOfs = min_x;
		TopOfs = min_y;
		Disposal = 1;

		/* Make a copy of the image with the new offsets.
		   But only if necessary. */
		if (min_x != 0 || max_x != tim->sx - 1
		        || min_y != 0 || max_y != tim->sy - 1
		        || transparent >= 0) {

			gdImagePtr pim2 = gdImageCreate(max_x-min_x + 1, max_y-min_y + 1);

			if (!pim2) {
				if (prev_pim) {
					gdImageDestroy(prev_pim);
				}
				goto fail_end;
			}

			gdImagePaletteCopy(pim2, LocalCM ? tim : prev_tim);
			gdImageCopy(pim2, tim, 0, 0, min_x, min_y,
			            max_x - min_x + 1, max_y - min_y + 1);

			if (pim) {
				gdImageDestroy(pim);
			}

			tim = pim = pim2;
		}

		/* now let's compare pixels for transparent
		   optimization.  But only if transparent is set. */
		if (transparent >= 0) {
			for(y = 0; y < tim->sy; ++y) {
				for (x = 0; x < tim->sx; ++x) {
					if(comparewithmap
					        (prev_tim, tim,
					         prev_tim->pixels[min_y + y][min_x + x],
					         tim->pixels[y][x], 0)) {
						gdImageSetPixel(tim, x, y, transparent);
						break;
					}
				}
			}
		}

		if(prev_pim) {
			gdImageDestroy(prev_pim);
		}
	}

	BitsPerPixel = colorstobpp(tim->colorsTotal);

	/* All set, let's do it. */
	GIFAnimEncode(
	    out, tim->sx, tim->sy, LeftOfs, TopOfs, interlace, transparent,
	    Delay, Disposal, BitsPerPixel,
	    LocalCM ? tim->red : 0, tim->green, tim->blue, tim);

fail_end:
	if(pim) {
		/* Destroy palette based temporary image. */
		gdImageDestroy(pim);
	}
}