Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

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

0.00/5 (No votes)
14 Oct 2013 1  
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 a MemoryStream
  • 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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here