Resizing animated gif with dotimage
I have a piece of code which resizes animated gifs. if it helps the code will always res开发者_高级运维ize images to a smaller size. (there's no need to make them bigger for now)
I am using Atalasoft's dotimage library and their example code to do the actual resampling. The code is supposed to read an animated gif from disk, iterate through the frames and resize each frame to the new size. This works fine when the gif animation contains frames of the same size but resizing a animation with different sized frames breaks the animation (the frames don't overlap each other correctly after resizing), I think it's because the code is not computing the new offsets correctly.
I think it's this line of code which is not computing the offsets right: Point point = new Point((int)(frame.Location.X * ratio), (int)(frame.Location.Y * ratio));
Here's the complete resize routine:
static private void GenerateGifImage(FileStream fileStream, int OutputWidth, int OutputHeight)
{
// MemoryStream InputStream = new MemoryStream();
FileStream InputStream = fileStream;
// fileStream.Write(InputStream.GetBuffer(), 0, (int)InputStream.Position);
// InputStream.Seek(0, SeekOrigin.Begin);
Image InputImage = Image.FromStream(InputStream, true, false);
// this will invalidate the underlying image object in InputImage but the class properties
// will still accessible until the object is disposed
InputStream.Seek(0, SeekOrigin.Begin);
ImageInfo imageInfo = RegisteredDecoders.GetImageInfo(InputStream);
InputStream.Seek(0, SeekOrigin.Begin);
GifDecoder gifDecoder = new GifDecoder();
int count = gifDecoder.GetFrameCount(InputStream);
GifFrameCollection gifFrameCollection = new GifFrameCollection();
gifFrameCollection.Height = OutputHeight;
gifFrameCollection.Width = OutputWidth;
// gifFrameCollection.Height = gifDecoder.Frames.Height;
// gifFrameCollection.Width = gifDecoder.Frames.Width;
double ratio;
if (InputImage.Height > InputImage.Width)
{
ratio = (double)OutputHeight / (double)InputImage.Height;
}
else
{
ratio = (double)OutputWidth / (double)InputImage.Width;
}
for (int i = 0; i < count; i++)
{
GifFrame frame = gifDecoder.Frames[i];
Rectangle rectangle = new Rectangle(Point.Empty, frame.Image.Size);
int frameWidth = (int)(frame.Image.Width * ratio);
int frameHeight = (int)(frame.Image.Height * ratio);
// account for erratic rounding, seems illogical but has happened earlier when using floats instead of doubles
if (frameWidth > OutputWidth)
{
frameWidth = OutputWidth;
}
if (frameHeight > OutputHeight)
{
frameHeight = OutputHeight;
}
Size size = new Size(frameWidth, frameHeight);
// only resize if we have a measureable dimension
if (size.Width > 0 && size.Height > 0)
{
// ResampleCommand resampleCommand = new ResampleCommand(rectangle, size, ResampleMethod.NearestNeighbor);
ResampleCommand resampleCommand = new ResampleCommand(rectangle, size, ResampleMethod.NearestNeighbor);
AtalaImage atalaImage = resampleCommand.Apply(frame.Image).Image;
// save the image for debugging
// atalaImage.Save("frame" + i.ToString() + ".gif", ImageType.Gif, null);
// frame.Image.Save("frame-orig" + i.ToString() + ".gif", ImageType.Gif, null);
// AtalaImage atalaImage = frame.Image;
Point point = new Point((int)(frame.Location.X * ratio), (int)(frame.Location.Y * ratio));
// Point point = new Point((int)(frame.Location.X), (int)(frame.Location.Y));
gifFrameCollection.Add(new GifFrame(atalaImage, point, frame.DelayTime, frame.Interlaced, frame.FrameDisposal, frame.TransparentIndex, frame.UseLocalPalette));
}
}
FileStream saveStream = new FileStream("resized.gif", FileMode.Create, FileAccess.Write, FileShare.Write);
GifEncoder gifSave = new GifEncoder();
gifSave.Save(saveStream, gifFrameCollection, null);
saveStream.Close();
}
I work at Atalasoft
I looked into this -- your code is absolutely right and would work on frames of unequal size just fine. The point you are calculating is correct.
The problem is that in your 3 frame GIF, your second frame and third frame are precisely made to be overlaid on top of the first and use a very complex transparent mask to show the first frame through them. When your image is resampled to a new size, the mask might not be precise any more -- since you are resizing to just a one pixel difference on width and height, there is no way that this mask could match.
There are several solutions to this problem
- Overlay frame 2 onto frame 1, then resample and use that image instead
- Do #1, but then extract the rectangle of frame 2
- Use crop instead of resample -- this seems best since it's just 1 pixel.
I coded up #3 for you -- it works well
static private void GenerateGifImage(FileStream fileStream, int OutputWidth, int OutputHeight)
{
// MemoryStream InputStream = new MemoryStream();
FileStream InputStream = fileStream;
// fileStream.Write(InputStream.GetBuffer(), 0, (int)InputStream.Position);
// InputStream.Seek(0, SeekOrigin.Begin);
Image InputImage = Image.FromStream(InputStream, true, false);
// this will invalidate the underlying image object in InputImage but the class properties
// will still accessible until the object is disposed
InputStream.Seek(0, SeekOrigin.Begin);
ImageInfo imageInfo = RegisteredDecoders.GetImageInfo(InputStream);
InputStream.Seek(0, SeekOrigin.Begin);
GifDecoder gifDecoder = new GifDecoder();
int count = gifDecoder.GetFrameCount(InputStream);
GifFrameCollection gifFrameCollection = new GifFrameCollection();
gifFrameCollection.Height = OutputHeight;
gifFrameCollection.Width = OutputWidth;
double ratio;
if (InputImage.Height > InputImage.Width)
{
ratio = (double)OutputHeight / (double)InputImage.Height;
}
else
{
ratio = (double)OutputWidth / (double)InputImage.Width;
}
for (int i = 0; i < count; i++)
{
GifFrame frame = gifDecoder.Frames[i];
Rectangle rectangle = new Rectangle(Point.Empty, frame.Image.Size);
int newframeWidth = frame.Image.Width;
int newframeHeight = frame.Image.Height;
if (newframeWidth > OutputWidth || newframeHeight > OutputHeight)
{
newframeWidth = (int)(frame.Image.Width * ratio);
newframeHeight = (int)(frame.Image.Height * ratio);
}
// account for erratic rounding, seems illogical but has happened earlier when using floats instead of doubles
if (newframeWidth > OutputWidth)
{
newframeWidth = OutputWidth;
}
if (newframeHeight > OutputHeight)
{
newframeHeight = OutputHeight;
}
Size size = new Size(newframeWidth, newframeHeight);
// only resize if we have a measureable dimension
if (size.Width > 0 && size.Height > 0)
{
//ResampleCommand resampleCommand = new ResampleCommand(rectangle, size, ResampleMethod.);
AtalaImage atalaImage = frame.Image;
if (newframeWidth != frame.Image.Width || newframeHeight != frame.Image.Height)
{
CropCommand command = new CropCommand(new Rectangle(new Point(0, 0), size));
atalaImage = command.Apply(frame.Image).Image;
}
// AtalaImage atalaImage = frame.Image;
Point point = new Point((int)(frame.Location.X), (int)(frame.Location.Y));
// Point point = new Point((int)(frame.Location.X), (int)(frame.Location.Y));
gifFrameCollection.Add(new GifFrame(atalaImage, point, frame.DelayTime, frame.Interlaced, frame.FrameDisposal, frame.TransparentIndex, frame.UseLocalPalette));
}
}
FileStream saveStream = new FileStream("resized.gif", FileMode.Create, FileAccess.Write, FileShare.Write);
GifEncoder gifSave = new GifEncoder();
gifSave.Save(saveStream, gifFrameCollection, null);
saveStream.Close();
}
The calculated ratio values are incorrect if you're working with different frame-sizes. You should calculate the ratio per individual frame, so that the line your concerned about uses the correct ratio. I'm not familiar with the framework, so I can't provide you with an accurate example; but it should look similar to this:
GifFrame frame = gifDecoder.Frames[i];
double frameRatio;
if (frame.Height > frame.Width)
{
frameRatio = (double)OutputHeight / (double)frame.Height;
}
else
{
frameRatio = (double)OutputWidth / (double)frame.Width;
}
...
Point point = new Point((int)(frame.Location.X * frameRatio), (int)(frame.Location.Y * frameRatio));
精彩评论