Recursive AI

Resize an animated GIF in C#

Resize animated GIFs in C# without losing frames: keep delays and looping, read frame info, extract frames, make a still preview and shrink the file.

To resize an animated GIF in C# and keep it animated, load it with OneImage, call Resize and save it as a GIF. Every operation applies to every frame, so the frames, their delays and the loop setting come through without extra code. It’s pure .NET code with no native libraries, on Windows, Linux, macOS and in containers. This page also shows how to read the frame count and timing, extract frames, make a still preview, crop an animation and shrink the file. Every sample was run against OneImage 1.10.2 on .NET 10.

TerminalNuGet
dotnet add package RecursiveAI.OneImage

The examples use a 44-frame, 400×400 GIF of the rotating Earth from Wikimedia Commons (CC BY-SA 3.0), saved as earth.gif.

Resize an animated GIF

Program.cs.NET 10
using OneImage;
 
// Every frame is resized; frame delays and the loop count are kept.
Image.Load("earth.gif").Resize(width: 200).Save("earth-200.gif");

The result is a 200×200 GIF with all 44 frames, the same 90 ms delay on each and the same loop setting, which we checked with an independent decoder. Give only width or height to keep the aspect ratio. The fit modes of still images work too, such as Resize(width: 200, height: 200, fit: Fit.Cover) to fill a square.

In OneImage an Image holds all frames of an animation, and they’re exposed already composited: each frame is the full picture at that moment, with the GIF’s disposal and transparency rules applied. That is why one Resize call is enough, and why the same holds for crops, rotations, color changes and watermarks.

Get the frame count and delays

Program.cs.NET 10
using OneImage;
 
// Reads only the headers: no frame is decoded.
ImageInfo info = Image.Identify("earth.gif");
Console.WriteLine($"{info.Width}x{info.Height}, {info.Frames.Count} frames");
 
Image animation = Image.Load("earth.gif");
TimeSpan total = TimeSpan.Zero;
foreach (ImageFrame frame in animation.Frames)
{
    total += frame.Delay;
}
Console.WriteLine($"first frame {animation.Frames[0].Delay.TotalMilliseconds} ms, one loop {total.TotalSeconds:0.00} s");
Output1.10.2
400x400, 44 frames
first frame 90 ms, one loop 3.96 s

Image.Identify reads only the headers, so it’s a cheap way to tell an animated GIF from a still one before deciding what to do with an upload. Frames[i].Delay is the time each frame stays on screen. LoopCount is 0 for an animation that loops forever.

Extract the frames of a GIF

Program.cs.NET 10
using OneImage;
 
Image animation = Image.Load("earth.gif");
Directory.CreateDirectory("frames");
 
// Each frame comes out fully composited, at the size of the whole animation.
for (int i = 0; i < animation.FrameCount; i++)
{
    animation.Frames[i].ToImage().Save($"frames/frame-{i:000}.png");
}

This writes 44 PNG files of 400×400. Frames[i].ToImage() turns one frame into an ordinary one-frame image that you can resize, crop or save in any format. Members of Image that read pixels directly, such as ToArray, look at the first frame only, so use Frames[i] for the others.

Make a still preview from the first frame

Program.cs.NET 10
using OneImage;
 
Image animation = Image.Load("earth.gif");
 
// A still preview from the first frame. ToImage() makes it a one-frame image,
// so the PNG is a plain PNG and not an animated one.
animation.Frames[0].ToImage().Resize(width: 200).Save("earth-poster.png");

The result is a plain 200×200 PNG. The ToImage() call matters: saving the whole animation to .png would write an animated PNG (APNG), because PNG, WebP, GIF and TIFF all hold several frames. JPEG and BMP can’t, so saving the animation to .jpg writes the first frame only.

Crop an animated GIF

Program.cs.NET 10
using OneImage;
 
Image animation = Image.Load("earth.gif");
 
// Crop the middle 300x300 of every frame, then make it 150x150.
animation.Crop(50, 50, 300, 300).Resize(width: 150).Save("earth-crop.gif");

The crop rectangle applies to every frame, and the result is a 150×150 animation with all 44 frames. Image.Thumbnail("earth.gif", 120, 120) also keeps the animation: it wrote a 120×120 GIF with 44 frames, and like every thumbnail it never enlarges a smaller GIF.

Reduce the file size of a GIF

GIF stores up to 256 colors per frame with lossless LZW compression, so animations are large for what they show. Three settings help, measured here on the Earth animation:

Program.cs.NET 10
using OneImage;
 
Image animation = Image.Load("earth.gif").Resize(width: 200);
 
animation.Save("earth-200.gif");
animation.Save("earth-200-64colors.gif", new SaveOptions
{
    EncoderOptions = new GifEncoderOptions { MaxColors = 64 },
});
animation.Save("earth-200.webp", new SaveOptions
{
    EncoderOptions = new WebPEncoderOptions { Quality = 75 },
});
 
foreach (string file in new[] { "earth.gif", "earth-200.gif", "earth-200-64colors.gif", "earth-200.webp" })
{
    Console.WriteLine($"{file}: {new FileInfo(file).Length / 1024:N0} KB");
}
Output1.10.2
earth.gif: 978 KB
earth-200.gif: 453 KB
earth-200-64colors.gif: 318 KB
earth-200.webp: 98 KB
  • Resize. Halving each side took the file from 978 KB to 453 KB. Expect less than the three quarters the pixel count suggests, since every frame is re-encoded with a new palette.
  • Fewer colors. MaxColors = 64 cut it to 318 KB. With Dither = Dither.None as well it was 298 KB, at the risk of visible bands in smooth gradients.
  • Animated WebP. The same 200×200 animation is 98 KB as WebP at quality 75, about a fifth of the GIF. Current versions of Chrome, Edge, Firefox and Safari play animated WebP in an <img> tag (Can I use: WebP). Keep the GIF where the image is shown by software you don’t control.

The GIF encoder also stores only the changed rectangle of each frame after the first (FrameDifferencing, on by default). Our guide to converting images to WebP covers WebP’s quality and lossless settings, and compressing images in C# covers JPEG and PNG.

Other animated formats

Everything on this page also works for animated WebP and animated PNG (APNG), which load as frames the same way, and multi-page TIFF files load with one frame per page. The format you save to decides the output, so Image.Load("in.gif").Resize(width: 200).Save("out.webp") converts and resizes in one step. Files are checked against limits before anything large is allocated, including at most 1,000 frames by default, which matters when you accept GIF uploads. For resizing still images, see resizing images in C#, and for the full feature list, the OneImage overview.