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
{
pathToImage = AppDomain.CurrentDomain.BaseDirectory + pathToImage;
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
{
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))
{
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");
}
FrameDimension dimension = new FrameDimension(img.FrameDimensionsList[0]);
int frameCount = img.GetFrameCount(dimension);
for (int i = 0; i < frameCount; i++)
{
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;
}
private Bitmap ConvertBytesToImage(byte[] imageBytes)
{
if (imageBytes == null || imageBytes.Length == 0)
{
return null;
}
try
{
using (MemoryStream ms = new MemoryStream(imageBytes))
{
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;
}
private void lbFrames_SelectedIndexChanged(object sender, EventArgs e)
{
try
{
if (lbFrames.SelectedIndex == -1)
{
return;
}
if (frames == null || frames.Count() == 0)
{
throw new NoNullAllowedException("Frames have not been extracted");
}
if (lbFrames.SelectedIndex > frames.Count() - 1)
{
throw new IndexOutOfRangeException("Frame list does not contain index: "
+ lbFrames.SelectedIndex.ToString());
}
ClearPictureBoxImage();
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.