How to Split a GIF Frame By Frame and Save Them to Memory





5.00/5 (5 votes)
This is a demonstration of splicing an animated GIF (or any image) frame by frame without having to write to the disc.
Introduction
After searching around for an easy and reliable way to splice an animated GIF by frames, I found some decent solutions, but mostly all of them required writing a temporary file to the disc or to use "unsafe" code. At the time, I needed to splice a GIF by frames and save them to memory without having to write to the disc. The goal of this was to be able to step through each frame in a GIF, recreate a GIF, or remove/add frames to a GIF.
The code included will show you how to accomplish splicing a GIF frame by frame while avoiding disc writes. This code is also capable of working with JPEG, PNG, ICON, and BMP images.
If you are interested in recreating GIF images or looking for a library to use to work with images, I suggest taking a look at the ImageMagick Magick.NET API.
You can find it here.
Using the Code
For the examples below, "pbImage
" will represent a PictureBox
and "lbFiles
" will represent a ListBox
.
First, you will need to splice the actual image:
string pathToImage = "test.gif";
List<byte[]> frames = new List<byte[]>() { };
private void Mainform_Load(object sender, EventArgs e)
{
try
{
//Assuming "test.gif" is in the directory where this application is located
pathToImage = AppDomain.CurrentDomain.BaseDirectory + pathToImage;
//Try extracting the frames
frames = EnumerateFrames(pathToImage);
if (frames == null || frames.Count() == 0)
{
throw new NoNullAllowedException("Unable to obtain frames from " + pathToImage);
}
for (int i = 0; i < frames.Count(); i++)
{
lbFrames.Items.Add("Frame-" + i.ToString());
}
lbFrames.SelectedIndex = 0;
}
catch (Exception ex)
{
MessageBox.Show(
"Error type: " + ex.GetType().ToString() + "\n" +
"Message: " + ex.Message,
"Error in " + MethodBase.GetCurrentMethod().Name
);
}
}
private List<byte[]> EnumerateFrames(string imagePath)
{
try
{
//Make sure the image exists
if (!File.Exists(imagePath))
{
throw new FileNotFoundException("Unable to locate " + imagePath);
}
Dictionary<Guid, ImageFormat> guidToImageFormatMap = new Dictionary<Guid, ImageFormat>()
{
{ImageFormat.Bmp.Guid, ImageFormat.Bmp},
{ImageFormat.Gif.Guid, ImageFormat.Png},
{ImageFormat.Icon.Guid, ImageFormat.Png},
{ImageFormat.Jpeg.Guid, ImageFormat.Jpeg},
{ImageFormat.Png.Guid, ImageFormat.Png}
};
List<byte[]> tmpFrames = new List<byte[]>() { };
using (Image img = Image.FromFile(imagePath, true))
{
//Check the image format to determine what
//format the image will be saved to the
//memory stream in
ImageFormat imageFormat = null;
Guid imageGuid = img.RawFormat.Guid;
foreach (KeyValuePair<Guid, ImageFormat> pair in guidToImageFormatMap)
{
if (imageGuid == pair.Key)
{
imageFormat = pair.Value;
break;
}
}
if (imageFormat == null)
{
throw new NoNullAllowedException("Unable to determine image format");
}
//Get the frame count
FrameDimension dimension = new FrameDimension(img.FrameDimensionsList[0]);
int frameCount = img.GetFrameCount(dimension);
//Step through each frame
for (int i = 0; i < frameCount; i++)
{
//Set the active frame of the image and then
//write the bytes to the tmpFrames array
img.SelectActiveFrame(dimension, i);
using (MemoryStream ms = new MemoryStream())
{
img.Save(ms, imageFormat);
tmpFrames.Add(ms.ToArray());
}
}
}
return tmpFrames;
}
catch (Exception ex)
{
MessageBox.Show(
"Error type: " + ex.GetType().ToString() + "\n" +
"Message: " + ex.Message,
"Error in " + MethodBase.GetCurrentMethod().Name
);
}
return null;
}
//Now we should have a List of byte arrays if everything went well.
//Now, how to convert the bytes back to an image.
//To convert the byte array back to an image, recompile the byte array.
//This method returns null if it failed.
private Bitmap ConvertBytesToImage(byte[] imageBytes)
{
if (imageBytes == null || imageBytes.Length == 0)
{
return null;
}
try
{
//Read bytes into a MemoryStream
using (MemoryStream ms = new MemoryStream(imageBytes))
{
//Recreate the frame from the MemoryStream
using (Bitmap bmp = new Bitmap(ms))
{
return (Bitmap)bmp.Clone();
}
}
}
catch (Exception ex)
{
MessageBox.Show(
"Error type: " + ex.GetType().ToString() + "\n" +
"Message: " + ex.Message,
"Error in " + MethodBase.GetCurrentMethod().Name
);
}
return null;
}
//Now that we have the frames and a way to recompile them, we'll
//use the lbFrames ListBox to do so.
private void lbFrames_SelectedIndexChanged(object sender, EventArgs e)
{
try
{
if (lbFrames.SelectedIndex == -1)
{
return;
}
//Make sure frames have been extracted
if (frames == null || frames.Count() == 0)
{
throw new NoNullAllowedException("Frames have not been extracted");
}
//Make sure the selected index is within range
if (lbFrames.SelectedIndex > frames.Count() - 1)
{
throw new IndexOutOfRangeException("Frame list does not contain index: "
+ lbFrames.SelectedIndex.ToString());
}
//Clear the PictureBox
ClearPictureBoxImage();
//Load the image from the byte array
pbImage.Image = ConvertBytesToImage(frames[lbFrames.SelectedIndex]);
}
catch (Exception ex)
{
MessageBox.Show(
"Error type: " + ex.GetType().ToString() + "\n" +
"Message: " + ex.Message,
"Error in " + MethodBase.GetCurrentMethod().Name
);
}
}
Points of Interest
- When splicing GIF or ICON images, you MUST use the PNG
ImageFormat
when writing the frames to aMemoryStream
. - Keep in mind, the larger the image you are splicing, the more memory your program will use.
- To avoid your application from freezing or locking up when splicing an image, consider using a
BackgroundWorker
to handle splicing the image.