Recursive AI

Resize an animated GIF in C#

All oneimage recipes

Load the GIF, 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 with no extra code. The program below halves a 44-frame GIF of the rotating Earth from Wikimedia Commons (CC BY-SA 3.0).

Terminal
dotnet new console -o ResizeGif && cd ResizeGif
dotnet add package RecursiveAI.OneImage --version 1.10.4
curl -L -o earth.gif \
  "https://commons.wikimedia.org/wiki/Special:FilePath/Rotating_earth_(large).gif"

OneImage needs the .NET 10 SDK. Put this in Program.cs and run dotnet run:

C#
using OneImage;
 
Image gif = Image.Load("earth.gif");
Console.WriteLine($"earth.gif: {gif.Width}x{gif.Height}, {gif.FrameCount} frames, "
                + $"{gif.Frames[0].Delay.TotalMilliseconds} ms each");
 
// Every frame is resized; delays and the loop count are kept.
gif.Resize(width: 200).Save("earth-200.gif");
 
Image small = Image.Load("earth-200.gif");
Console.WriteLine($"earth-200.gif: {small.Width}x{small.Height}, "
                + $"{small.FrameCount} frames, "
                + $"{small.Frames[0].Delay.TotalMilliseconds} ms each");
Text
earth.gif: 400x400, 44 frames, 90 ms each
earth-200.gif: 200x200, 44 frames, 90 ms each

Frames come out composited: each is the full picture at that moment, with the GIF’s disposal and transparency rules applied, which is why one Resize call is enough. The same holds for Crop, Rotate, color changes and watermarks. Fit modes work too, such as Resize(width: 200, height: 200, fit: Fit.Cover) for a square.

A still preview, and a smaller file

Frames[i].ToImage() turns one frame into an ordinary image. Animated WebP is usually far smaller than GIF and plays in current browsers:

C#
using OneImage;
 
Image gif = Image.Load("earth.gif").Resize(width: 200);
 
// The first frame as a plain PNG. Saving the whole animation to .png
// would write an animated PNG.
gif.Frames[0].ToImage().Save("poster.png");
 
gif.Save("earth-200.webp");
gif.Save("earth-200-64.gif", new SaveOptions
{
    EncoderOptions = new GifEncoderOptions { MaxColors = 64 },
});
 
foreach (string file in new[] { "earth.gif", "earth-200.gif", "earth-200-64.gif",
                                "earth-200.webp", "poster.png" })
{
    ImageInfo info = Image.Identify(file);
    long kb = new FileInfo(file).Length / 1024;
    Console.WriteLine($"{file}: {info.Frames.Count} frame(s), {kb} KB");
}
Text
earth.gif: 44 frame(s), 978 KB
earth-200.gif: 44 frame(s), 453 KB
earth-200-64.gif: 44 frame(s), 318 KB
earth-200.webp: 44 frame(s), 98 KB
poster.png: 1 frame(s), 43 KB

To get every frame as a file, loop over Frames and save each ToImage().

Limits

  • JPEG and BMP hold one image. Saving an animation to .jpg writes the first frame only. GIF, WebP, PNG (APNG) and TIFF keep every frame.
  • Each frame is re-quantized to a palette of at most 256 colors, so a resized GIF is smaller but not proportionally: expect about half, not a quarter.
  • At most 1,000 frames load by default; raise LoadOptions.Limits if you need more.

To watermark every frame, see Add a watermark; for WebP settings, Convert an image to WebP. Animation is covered in the guide.