Hey getting this error:
An exception of type 'System.OutOfMemoryException' occurred in
System.Drawing.dll but was not handled in user code
Additional information: Out of memory.
It occurs in the below method on the DrawImage call
/// <summary>
/// Resize the image to the specified width and height.
/// </summary>
/// <param name="image">The image to resize.</param>
/// <returns>The resized image.</returns>
public Bitmap ResizeImage(Image image, System.Drawing.Size newSize)
{
var destRect = new Rectangle(0, 0, newSize.Width, newSize.Height);
var destImage = new Bitmap(newSize.Width, newSize.Height);
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (var graphics = Graphics.FromImage(destImage))
{
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
using (var wrapMode = new ImageAttributes())
{
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
}
}
return destImage;
}
I'm not sure why it occurs, I call this method multiple times, the result is converted into a Base64 String and stored within an ObservableCollection.
/// <summary>
/// Convert Image and Resize
/// </summary>
/// <param name="loc"></param>
/// <returns></returns>
public async Task<string> GenerateThumbnailBinary(string loc)
{
return await Task<string>.Factory.StartNew(() =>
{
Image image = Image.FromFile(loc, true);
// Figure out the ratio
double ratioX = (double)Properties.Settings.Default.ThumbnailWidth.Width / (double)image.Width;
double ratioY = (double)Properties.Settings.Default.ThumbnailWidth.Height / (double)image.Height;
// use whichever multiplier is smaller
double ratio = ratioX < ratioY ? ratioX : ratioY;
System.Drawing.Size newSize =
new System.Drawing.Size(
(int)(image.Width * ratio),
(int)(image.Height * ratio));
Image resized = ResizeImage(image, newSize);
return ImageToBase64(resized, ImageFormat.Jpeg);
});
}
I also display each of the strings back as an image by binding to the Collection and using a converter to convert the Base64 string back into a Bitmap, this is just for the UI to display what has been converted.
Where would my issue be starting? Could I be attempting to store too many images in memory when I display them on the UI and use the converter to convert the string to the image?
The high points in the image below obviously when it's running the method loop, but it still seems to stay higher than before the method is run at the end, do this help?
Edit:
This is the loop which starts the Tasks and runs the method.
// Generate List of images to upload
var files = Directory.EnumerateFiles(sel.Name, "*.*", SearchOption.AllDirectories)
.Where(s => s.EndsWith(".jpeg") ||
s.EndsWith(".jpg") ||
s.EndsWith(".png") ||
s.EndsWith(".JPG"));
int b = 1;
if (files.Count() > 0)
{
/// <summary>
/// Resize Images
/// </summary>
/// Set current Task first
UploadTask = Steps[0].Message;
try
{
foreach (string item in files)
{
// Generate new name
string oldname = Path.GetFileNameWithoutExtension(item);
string newName = Common.Security.KeyGenerator.GetUniqueKey(32);
string t = await GenerateThumbnailBinary(item);
ImageUploadObjects.Add(
new ImageUploadObject { OldName = oldname,
NewName = newName,
ByteImage = t });
UploadProgress = (int)Math.Round((double)(100 * b / files.Count()));
b++;
}
// Complete
Steps[0].Complete = true;
}
catch(Exception e)
{
Steps[0].Error = e.InnerException.ToString();
}
/// <summary>
/// Move full resoluation images
/// </summary>
/// Set current Task first
UploadTask = Steps[1].Message;
try
{
foreach (string item in files)
{
}
// Complete
Steps[1].Complete = true;
}
catch (Exception e)
{
Steps[1].Error = e.InnerException.ToString();
}
}
}
Edit:
How can I tell if the memory is still being used? I have added another image below, the first snapshot is before I execute the method, and the last one is when it finishes, 2 - 4 are whilst it's running
I think the easiest way to solve your issue is to do it in chunks/batches. For example, if you have 100 files, you are creating 100 tasks, which load the file content into images into memory. Perhaps do 10 (or some other number) and once that has been completely done, do the next 10 (or some other number). I am sure this will fix your issue.
Also make sure to call Dispose on any class which implements Disposable, i.e., Image and Bitmap etc.
In addition to the above, here is summarily what you are trying to do:
1. Read a directory and take all the files.
2. Create thumbnail images for each file.
3. Add thumbnail to a collection in memory.
4. Transfer the images to another location.
For item 2 above, I would not keep all the thumbnails in memory. Even if I need to show this in UI, I will incorporate paging and pull them as needed.
The Image resized = ResizeImage(image, newSize) is not being disposed of. Because the memory allocated will not be released until the finalizer thread is run, you could be leaking memory all over the place.
Image image = Image.FromFile(loc, true);
...
Image resized = ResizeImage(image, newSize);
image.Dispose();
string base64Image = ImageToBase64(resized, ImageFormat.Jpeg);
resized.Dispose();
return base64Image;
Related
In a nutshell the purpose of the following code is to resize an image based on the target size and the multiplier (1x, 2x, 3x). This works fine except for some reason I haven't determined some images are being rotated.
public void ResizeImage(TargetSize targetSize, ResizeMultiplier multiplier, Stream input, Stream output)
{
using (var image = Image.FromStream(input))
{
// Calculate the resize factor
var scaleFactor = targetSize.CalculateScaleFactor(image.Width, image.Height);
scaleFactor /= (int)multiplier; // Enum is effectively named constant with a value of 1, 2, or 3
var newWidth = (int)Math.Floor(image.Width / scaleFactor);
var newHeight = (int)Math.Floor(image.Height / scaleFactor);
using (var newBitmap = new Bitmap(newWidth, newHeight))
{
using (var imageScaler = Graphics.FromImage(newBitmap))
{
imageScaler.CompositingQuality = CompositingQuality.HighQuality;
imageScaler.SmoothingMode = SmoothingMode.HighQuality;
imageScaler.InterpolationMode = InterpolationMode.HighQualityBicubic;
var imageRectangle = new Rectangle(0, 0, newWidth, newHeight);
imageScaler.DrawImage(image, imageRectangle);
newBitmap.Save(output, image.RawFormat);
}
}
}
}
// Class definition for the class used in the method above
public class TargetSize
{
/// <summary>
/// The _width
/// </summary>
private readonly int _width;
/// <summary>
/// The _height
/// </summary>
private readonly int _height;
/// <summary>
/// Initializes a new instance of the <see cref="TargetSize"/> class.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
public TargetSize(int width, int height)
{
_height = height;
_width = width;
}
/// <summary>
/// Calculates the scale factor.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <returns></returns>
public decimal CalculateScaleFactor(int width, int height)
{
// Scale proportinately
var heightScaleFactor = decimal.Divide(height, _height);
var widthScaleFactor = decimal.Divide(width, _width);
// Use the smaller of the two as the final scale factor so the image is never undersized.
return widthScaleFactor > heightScaleFactor ? heightScaleFactor : widthScaleFactor;
}
}
// NUnit integration test case I'm using to exercise the above code
[Test]
public void ResizeImage_Persistant_Single()
{
// Read the image from disk
using (var fileStream = File.OpenRead(#"TestData\dog.jpg"))
{
using (var outputStream = new MemoryStream())
{
// Call the resize image method detailed above. ResizeMultiplier.Medium casts to 2.
_sut.ResizeImage(new TargetSize(200, 200), ResizeMultiplier.Medium, fileStream, outputStream);
using (var newImage = Image.FromStream(outputStream))
{
// Save the resized image to disk
newImage.Save(#"TestData\ImageResizerTests.ResizeImage_Persistant_Single.jpg");
}
}
}
}
For instance this image:
is scaled appropriately but this image:
is flipped upside down. It is worth mentioning that the image also appeared to be upside down when it was in the preview pane to upload it to this site. That fact (which I obviously just discovered) strongly makes me think something is funny with the image. Regardless my code needs to handle it.
Imgur "fixed" the file above (because it doesn't rotate now when I run it through my code) so I uploaded it to Google Drive. If you right click on the image (in FireFox I haven't tested other browsers) and click Save Image As... then the image doesn't rotate with my code above. If you click the download button in the header then the image does rotate with my code.... Here is another copy of the dog image that flips 180 degrees with my code. All of this is very bizarre, and I don't know what I'm doing wrong...
To be clear my goal is to resize the image without rotating the image.
Edits based on comments:
An image that rotates/flips will do so consistently, and in the same manner. For example this dog picture will always flip 180 degrees. Some pictures will lay on their side (rotate 90 or 270 degrees). I've verified that the newWidth,newHeight, scaleFactor, targetSize (private variables), and image.Height/image.Width variables are all positive when the dog picture flips 180 degrees.
I don't believe this is an artifact of a particular tool. I see the rotation via; the preview in Windows Explorer, Windows Image Viewer, the stock Macintosh image viewer, FireFox, etc. The issue was actually brought to my attention by an iOS dev in my company who saw it in our app. I think too many tools are seeing this for it to be a viewer problem.
Thanks to the excellent help by Chris Farmer1 and Mark Ransom2 I was able to answer my own question.
The core problem was the EXIF data on some of the images was altering the orientation of the images. When I resized the images all the EXIF data was stripped off. Since the EXIF data was stripped the viewers didn't know the image should be orientated differently. That caused me to think the resizer code was rotating some images. It is worth noting that this orientation information doesn't show up in the details view when you right click on the image in Windows Explorer. You need to search for it in the image property objects or use an online view like this one.
Using the data from here3 I was able to create the following named constants:
private const int OrientationKey = 0x0112;
private const int NotSpecified = 0;
private const int NormalOrientation = 1;
private const int MirrorHorizontal = 2;
private const int UpsideDown = 3;
private const int MirrorVertical = 4;
private const int MirrorHorizontalAndRotateRight = 5;
private const int RotateLeft = 6;
private const int MirorHorizontalAndRotateLeft = 7;
private const int RotateRight = 8;
Using those named constants I extended my ResizeImage method to read:
public void ResizeImage(TargetSize targetSize, ResizeMultiplier multiplier, Stream input, Stream output)
{
using (var image = Image.FromStream(input))
{
// Calculate the resize factor
var scaleFactor = targetSize.CalculateScaleFactor(image.Width, image.Height);
scaleFactor /= (int)multiplier;
var newWidth = (int)Math.Floor(image.Width / scaleFactor);
var newHeight = (int)Math.Floor(image.Height / scaleFactor);
using (var newBitmap = new Bitmap(newWidth, newHeight))
{
using (var imageScaler = Graphics.FromImage(newBitmap))
{
imageScaler.CompositingQuality = CompositingQuality.HighQuality;
imageScaler.SmoothingMode = SmoothingMode.HighQuality;
imageScaler.InterpolationMode = InterpolationMode.HighQualityBicubic;
var imageRectangle = new Rectangle(0, 0, newWidth, newHeight);
imageScaler.DrawImage(image, imageRectangle);
// Fix orientation if needed.
if (image.PropertyIdList.Contains(OrientationKey))
{
var orientation = (int)image.GetPropertyItem(OrientationKey).Value[0];
switch (orientation)
{
case NotSpecified: // Assume it is good.
case NormalOrientation:
// No rotation required.
break;
case MirrorHorizontal:
newBitmap.RotateFlip(RotateFlipType.RotateNoneFlipX);
break;
case UpsideDown:
newBitmap.RotateFlip(RotateFlipType.Rotate180FlipNone);
break;
case MirrorVertical:
newBitmap.RotateFlip(RotateFlipType.Rotate180FlipX);
break;
case MirrorHorizontalAndRotateRight:
newBitmap.RotateFlip(RotateFlipType.Rotate90FlipX);
break;
case RotateLeft:
newBitmap.RotateFlip(RotateFlipType.Rotate90FlipNone);
break;
case MirorHorizontalAndRotateLeft:
newBitmap.RotateFlip(RotateFlipType.Rotate270FlipX);
break;
case RotateRight:
newBitmap.RotateFlip(RotateFlipType.Rotate270FlipNone);
break;
default:
throw new NotImplementedException("An orientation of " + orientation + " isn't implemented.");
}
}
newBitmap.Save(output, image.RawFormat);
}
}
}
}
An observant person would notice that most of the additional code was pulled from this answer to a related question.
1: Who was right on the money but I didn't understand what he was driving at.
2:Who showed me what Chris Farmer was trying to say.
3:The Orientation key value was confirmed here.
Does anyone know how to create an animated gif using c#?
Ideally I would have some control over the color reduction used.
Is using imagemagick (as an external started process) the best choice?
This Gif Animation Creater code from https://github.com/DataDink/Bumpkit can set Delay foreach Frame:
Uses .Net standard Gif Encoding and adds Animation headers.
EDIT: Made the code similar to a typical file writer.
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading.Tasks;
/// <summary>
/// Creates a GIF using .Net GIF encoding and additional animation headers.
/// </summary>
public class GifWriter : IDisposable
{
#region Fields
const long SourceGlobalColorInfoPosition = 10,
SourceImageBlockPosition = 789;
readonly BinaryWriter _writer;
bool _firstFrame = true;
readonly object _syncLock = new object();
#endregion
/// <summary>
/// Creates a new instance of GifWriter.
/// </summary>
/// <param name="OutStream">The <see cref="Stream"/> to output the Gif to.</param>
/// <param name="DefaultFrameDelay">Default Delay between consecutive frames... FrameRate = 1000 / DefaultFrameDelay.</param>
/// <param name="Repeat">No of times the Gif should repeat... -1 not to repeat, 0 to repeat indefinitely.</param>
public GifWriter(Stream OutStream, int DefaultFrameDelay = 500, int Repeat = -1)
{
if (OutStream == null)
throw new ArgumentNullException(nameof(OutStream));
if (DefaultFrameDelay <= 0)
throw new ArgumentOutOfRangeException(nameof(DefaultFrameDelay));
if (Repeat < -1)
throw new ArgumentOutOfRangeException(nameof(Repeat));
_writer = new BinaryWriter(OutStream);
this.DefaultFrameDelay = DefaultFrameDelay;
this.Repeat = Repeat;
}
/// <summary>
/// Creates a new instance of GifWriter.
/// </summary>
/// <param name="FileName">The path to the file to output the Gif to.</param>
/// <param name="DefaultFrameDelay">Default Delay between consecutive frames... FrameRate = 1000 / DefaultFrameDelay.</param>
/// <param name="Repeat">No of times the Gif should repeat... -1 not to repeat, 0 to repeat indefinitely.</param>
public GifWriter(string FileName, int DefaultFrameDelay = 500, int Repeat = -1)
: this(new FileStream(FileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read), DefaultFrameDelay, Repeat) { }
#region Properties
/// <summary>
/// Gets or Sets the Default Width of a Frame. Used when unspecified.
/// </summary>
public int DefaultWidth { get; set; }
/// <summary>
/// Gets or Sets the Default Height of a Frame. Used when unspecified.
/// </summary>
public int DefaultHeight { get; set; }
/// <summary>
/// Gets or Sets the Default Delay in Milliseconds.
/// </summary>
public int DefaultFrameDelay { get; set; }
/// <summary>
/// The Number of Times the Animation must repeat.
/// -1 indicates no repeat. 0 indicates repeat indefinitely
/// </summary>
public int Repeat { get; }
#endregion
/// <summary>
/// Adds a frame to this animation.
/// </summary>
/// <param name="Image">The image to add</param>
/// <param name="Delay">Delay in Milliseconds between this and last frame... 0 = <see cref="DefaultFrameDelay"/></param>
public void WriteFrame(Image Image, int Delay = 0)
{
lock (_syncLock)
using (var gifStream = new MemoryStream())
{
Image.Save(gifStream, ImageFormat.Gif);
// Steal the global color table info
if (_firstFrame)
InitHeader(gifStream, _writer, Image.Width, Image.Height);
WriteGraphicControlBlock(gifStream, _writer, Delay == 0 ? DefaultFrameDelay : Delay);
WriteImageBlock(gifStream, _writer, !_firstFrame, 0, 0, Image.Width, Image.Height);
}
if (_firstFrame)
_firstFrame = false;
}
#region Write
void InitHeader(Stream SourceGif, BinaryWriter Writer, int Width, int Height)
{
// File Header
Writer.Write("GIF".ToCharArray()); // File type
Writer.Write("89a".ToCharArray()); // File Version
Writer.Write((short)(DefaultWidth == 0 ? Width : DefaultWidth)); // Initial Logical Width
Writer.Write((short)(DefaultHeight == 0 ? Height : DefaultHeight)); // Initial Logical Height
SourceGif.Position = SourceGlobalColorInfoPosition;
Writer.Write((byte)SourceGif.ReadByte()); // Global Color Table Info
Writer.Write((byte)0); // Background Color Index
Writer.Write((byte)0); // Pixel aspect ratio
WriteColorTable(SourceGif, Writer);
// App Extension Header for Repeating
if (Repeat == -1)
return;
Writer.Write(unchecked((short)0xff21)); // Application Extension Block Identifier
Writer.Write((byte)0x0b); // Application Block Size
Writer.Write("NETSCAPE2.0".ToCharArray()); // Application Identifier
Writer.Write((byte)3); // Application block length
Writer.Write((byte)1);
Writer.Write((short)Repeat); // Repeat count for images.
Writer.Write((byte)0); // terminator
}
static void WriteColorTable(Stream SourceGif, BinaryWriter Writer)
{
SourceGif.Position = 13; // Locating the image color table
var colorTable = new byte[768];
SourceGif.Read(colorTable, 0, colorTable.Length);
Writer.Write(colorTable, 0, colorTable.Length);
}
static void WriteGraphicControlBlock(Stream SourceGif, BinaryWriter Writer, int FrameDelay)
{
SourceGif.Position = 781; // Locating the source GCE
var blockhead = new byte[8];
SourceGif.Read(blockhead, 0, blockhead.Length); // Reading source GCE
Writer.Write(unchecked((short)0xf921)); // Identifier
Writer.Write((byte)0x04); // Block Size
Writer.Write((byte)(blockhead[3] & 0xf7 | 0x08)); // Setting disposal flag
Writer.Write((short)(FrameDelay / 10)); // Setting frame delay
Writer.Write(blockhead[6]); // Transparent color index
Writer.Write((byte)0); // Terminator
}
static void WriteImageBlock(Stream SourceGif, BinaryWriter Writer, bool IncludeColorTable, int X, int Y, int Width, int Height)
{
SourceGif.Position = SourceImageBlockPosition; // Locating the image block
var header = new byte[11];
SourceGif.Read(header, 0, header.Length);
Writer.Write(header[0]); // Separator
Writer.Write((short)X); // Position X
Writer.Write((short)Y); // Position Y
Writer.Write((short)Width); // Width
Writer.Write((short)Height); // Height
if (IncludeColorTable) // If first frame, use global color table - else use local
{
SourceGif.Position = SourceGlobalColorInfoPosition;
Writer.Write((byte)(SourceGif.ReadByte() & 0x3f | 0x80)); // Enabling local color table
WriteColorTable(SourceGif, Writer);
}
else Writer.Write((byte)(header[9] & 0x07 | 0x07)); // Disabling local color table
Writer.Write(header[10]); // LZW Min Code Size
// Read/Write image data
SourceGif.Position = SourceImageBlockPosition + header.Length;
var dataLength = SourceGif.ReadByte();
while (dataLength > 0)
{
var imgData = new byte[dataLength];
SourceGif.Read(imgData, 0, dataLength);
Writer.Write((byte)dataLength);
Writer.Write(imgData, 0, dataLength);
dataLength = SourceGif.ReadByte();
}
Writer.Write((byte)0); // Terminator
}
#endregion
/// <summary>
/// Frees all resources used by this object.
/// </summary>
public void Dispose()
{
// Complete File
_writer.Write((byte)0x3b); // File Trailer
_writer.BaseStream.Dispose();
_writer.Dispose();
}
}
There is a built in .NET class which will encode GIF files.
GifBitmapEncode MSDN
System.Windows.Media.Imaging.GifBitmapEncoder gEnc = new GifBitmapEncoder();
foreach (System.Drawing.Bitmap bmpImage in images)
{
var bmp = bmpImage.GetHbitmap();
var src = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
bmp,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
gEnc.Frames.Add(BitmapFrame.Create(src));
DeleteObject(bmp); // recommended, handle memory leak
}
using(FileStream fs = new FileStream(path, FileMode.Create))
{
gEnc.Save(fs);
}
You might also consider using the ImageMagick library.
There are two .net wrappers for the library listed at http://www.imagemagick.org/script/api.php
Here is an example on how to do it using the Magick.net wrapper:
using (MagickImageCollection collection = new MagickImageCollection())
{
// Add first image and set the animation delay to 100ms
collection.Add("Snakeware.png");
collection[0].AnimationDelay = 100;
// Add second image, set the animation delay to 100ms and flip the image
collection.Add("Snakeware.png");
collection[1].AnimationDelay = 100;
collection[1].Flip();
// Optionally reduce colors
QuantizeSettings settings = new QuantizeSettings();
settings.Colors = 256;
collection.Quantize(settings);
// Optionally optimize the images (images should have the same size).
collection.Optimize();
// Save gif
collection.Write("Snakeware.Animated.gif");
}
Whether or not calling imagemagick is the best choice is kind of hard to awnser without knowing the quality parameters that are important. Some other options would be:
Rick van den Bosch's code archive.org mirror
NGif article on codeplex
these have the advantage that you don't have a dependency on a third partly library which might or might not be available on all systems executing your code.
This article at MS Support explains how to save a gif with a custom color table (this does require full trust). A animated gif is just a set of gifs for each image with some additional information in the header. So combining these two articles should get you what you need.
To use the sample from a Windows Forms app, add references to these assemblies:
C:\Program Files\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\PresentationCore.dll
C:\Program Files\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\System.Xaml.dll
C:\Program Files\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\WindowsBase.dll
Then
Int32Rect is in the System.Windows namespace
BitmapSizeOptions is in the System.Windows.Media.Imaging namespace
BitmapFrame is in the System.Windows.Media.Imaging namespace
Also, don't forget to close the file stream (something like this):
using(FileStream targetFile = new FileStream(path, FileMode.Create))
{
gEnc.Save(targetFile);
}
The AnimatedGif package can make animated gifs.
using (var gif = AnimatedGif.AnimatedGif.Create(Path.Combine(outputPath, "output.gif"), 20))
{
for (var i = 0; i < 10; i++)
{
Image img = Image.FromFile(Path.Combine(outputPath, $"{i.ToString().PadLeft(3, '0')}.png"));
gif.AddFrame(img, delay: -1, quality: GifQuality.Bit8);
}
}
I noticed that one more great alternative to ImageMagic and NGif is not listed in answers yet.
FFMpeg can be used for creating animated GIFs from:
sequence of images (files)
existing video clip (say, mp4 or avi)
from C# bitmap objects by providing input data as "ravvideo" through stdin (without using any temp files)
You can start ffmpeg.exe directly from C# code (with System.Diagnostics.Process) or use one of the existing .NET ffmpeg wrappers:
var ffmpeg = new NReco.VideoConverter.FFMpegConverter();
ffmpeg.ConvertMedia("your_clip.mp4", null, "result.gif", null, new ConvertSettings() );
(this code example uses free NReco VideoConverter - I'm an author of this component, feel free to ask any questions about its usage).
GIF size can be easily reduced by decreasing frame rate and/or frame size.
Also it is possible to get fine-looking animated GIFs with 2-pass approach that generates optimal GIF palette.
Quote from fireydude answer:
https://stackoverflow.com/a/16598294/8917485
This method is not complete, cause the .gif can't repeat.
I found some additional code on other question, make .gif repeat.
.NET - Creating a looping .gif using GifBitmapEncoder
http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
The complete code should looks like the code below:
System.Windows.Media.Imaging.GifBitmapEncoder gEnc = new GifBitmapEncoder();
foreach (System.Drawing.Bitmap bmpImage in bitMaps)
{
var bmp = bmpImage.GetHbitmap();
var src = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
bmp,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
gEnc.Frames.Add(BitmapFrame.Create(src));
DeleteObject(bmp); // recommended, handle memory leak
bmpImage.Dispose(); // recommended, handle memory leak
}
// After adding all frames to gifEncoder (the GifBitmapEncoder)...
using (var ms_ = new MemoryStream())
{
gEnc.Save(ms_);
var fileBytes = ms_.ToArray();
// This is the NETSCAPE2.0 Application Extension.
var applicationExtension = new byte[] { 33, 255, 11, 78, 69, 84, 83, 67, 65, 80, 69, 50, 46, 48, 3, 1, 0, 0, 0 };
var newBytes = new List<byte>();
newBytes.AddRange(fileBytes.Take(13));
newBytes.AddRange(applicationExtension);
newBytes.AddRange(fileBytes.Skip(13));
File.WriteAllBytes("abc.gif", newBytes.ToArray());
}
Does anyone know how to create an animated gif using c#?
Ideally I would have some control over the color reduction used.
Is using imagemagick (as an external started process) the best choice?
This Gif Animation Creater code from https://github.com/DataDink/Bumpkit can set Delay foreach Frame:
Uses .Net standard Gif Encoding and adds Animation headers.
EDIT: Made the code similar to a typical file writer.
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading.Tasks;
/// <summary>
/// Creates a GIF using .Net GIF encoding and additional animation headers.
/// </summary>
public class GifWriter : IDisposable
{
#region Fields
const long SourceGlobalColorInfoPosition = 10,
SourceImageBlockPosition = 789;
readonly BinaryWriter _writer;
bool _firstFrame = true;
readonly object _syncLock = new object();
#endregion
/// <summary>
/// Creates a new instance of GifWriter.
/// </summary>
/// <param name="OutStream">The <see cref="Stream"/> to output the Gif to.</param>
/// <param name="DefaultFrameDelay">Default Delay between consecutive frames... FrameRate = 1000 / DefaultFrameDelay.</param>
/// <param name="Repeat">No of times the Gif should repeat... -1 not to repeat, 0 to repeat indefinitely.</param>
public GifWriter(Stream OutStream, int DefaultFrameDelay = 500, int Repeat = -1)
{
if (OutStream == null)
throw new ArgumentNullException(nameof(OutStream));
if (DefaultFrameDelay <= 0)
throw new ArgumentOutOfRangeException(nameof(DefaultFrameDelay));
if (Repeat < -1)
throw new ArgumentOutOfRangeException(nameof(Repeat));
_writer = new BinaryWriter(OutStream);
this.DefaultFrameDelay = DefaultFrameDelay;
this.Repeat = Repeat;
}
/// <summary>
/// Creates a new instance of GifWriter.
/// </summary>
/// <param name="FileName">The path to the file to output the Gif to.</param>
/// <param name="DefaultFrameDelay">Default Delay between consecutive frames... FrameRate = 1000 / DefaultFrameDelay.</param>
/// <param name="Repeat">No of times the Gif should repeat... -1 not to repeat, 0 to repeat indefinitely.</param>
public GifWriter(string FileName, int DefaultFrameDelay = 500, int Repeat = -1)
: this(new FileStream(FileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read), DefaultFrameDelay, Repeat) { }
#region Properties
/// <summary>
/// Gets or Sets the Default Width of a Frame. Used when unspecified.
/// </summary>
public int DefaultWidth { get; set; }
/// <summary>
/// Gets or Sets the Default Height of a Frame. Used when unspecified.
/// </summary>
public int DefaultHeight { get; set; }
/// <summary>
/// Gets or Sets the Default Delay in Milliseconds.
/// </summary>
public int DefaultFrameDelay { get; set; }
/// <summary>
/// The Number of Times the Animation must repeat.
/// -1 indicates no repeat. 0 indicates repeat indefinitely
/// </summary>
public int Repeat { get; }
#endregion
/// <summary>
/// Adds a frame to this animation.
/// </summary>
/// <param name="Image">The image to add</param>
/// <param name="Delay">Delay in Milliseconds between this and last frame... 0 = <see cref="DefaultFrameDelay"/></param>
public void WriteFrame(Image Image, int Delay = 0)
{
lock (_syncLock)
using (var gifStream = new MemoryStream())
{
Image.Save(gifStream, ImageFormat.Gif);
// Steal the global color table info
if (_firstFrame)
InitHeader(gifStream, _writer, Image.Width, Image.Height);
WriteGraphicControlBlock(gifStream, _writer, Delay == 0 ? DefaultFrameDelay : Delay);
WriteImageBlock(gifStream, _writer, !_firstFrame, 0, 0, Image.Width, Image.Height);
}
if (_firstFrame)
_firstFrame = false;
}
#region Write
void InitHeader(Stream SourceGif, BinaryWriter Writer, int Width, int Height)
{
// File Header
Writer.Write("GIF".ToCharArray()); // File type
Writer.Write("89a".ToCharArray()); // File Version
Writer.Write((short)(DefaultWidth == 0 ? Width : DefaultWidth)); // Initial Logical Width
Writer.Write((short)(DefaultHeight == 0 ? Height : DefaultHeight)); // Initial Logical Height
SourceGif.Position = SourceGlobalColorInfoPosition;
Writer.Write((byte)SourceGif.ReadByte()); // Global Color Table Info
Writer.Write((byte)0); // Background Color Index
Writer.Write((byte)0); // Pixel aspect ratio
WriteColorTable(SourceGif, Writer);
// App Extension Header for Repeating
if (Repeat == -1)
return;
Writer.Write(unchecked((short)0xff21)); // Application Extension Block Identifier
Writer.Write((byte)0x0b); // Application Block Size
Writer.Write("NETSCAPE2.0".ToCharArray()); // Application Identifier
Writer.Write((byte)3); // Application block length
Writer.Write((byte)1);
Writer.Write((short)Repeat); // Repeat count for images.
Writer.Write((byte)0); // terminator
}
static void WriteColorTable(Stream SourceGif, BinaryWriter Writer)
{
SourceGif.Position = 13; // Locating the image color table
var colorTable = new byte[768];
SourceGif.Read(colorTable, 0, colorTable.Length);
Writer.Write(colorTable, 0, colorTable.Length);
}
static void WriteGraphicControlBlock(Stream SourceGif, BinaryWriter Writer, int FrameDelay)
{
SourceGif.Position = 781; // Locating the source GCE
var blockhead = new byte[8];
SourceGif.Read(blockhead, 0, blockhead.Length); // Reading source GCE
Writer.Write(unchecked((short)0xf921)); // Identifier
Writer.Write((byte)0x04); // Block Size
Writer.Write((byte)(blockhead[3] & 0xf7 | 0x08)); // Setting disposal flag
Writer.Write((short)(FrameDelay / 10)); // Setting frame delay
Writer.Write(blockhead[6]); // Transparent color index
Writer.Write((byte)0); // Terminator
}
static void WriteImageBlock(Stream SourceGif, BinaryWriter Writer, bool IncludeColorTable, int X, int Y, int Width, int Height)
{
SourceGif.Position = SourceImageBlockPosition; // Locating the image block
var header = new byte[11];
SourceGif.Read(header, 0, header.Length);
Writer.Write(header[0]); // Separator
Writer.Write((short)X); // Position X
Writer.Write((short)Y); // Position Y
Writer.Write((short)Width); // Width
Writer.Write((short)Height); // Height
if (IncludeColorTable) // If first frame, use global color table - else use local
{
SourceGif.Position = SourceGlobalColorInfoPosition;
Writer.Write((byte)(SourceGif.ReadByte() & 0x3f | 0x80)); // Enabling local color table
WriteColorTable(SourceGif, Writer);
}
else Writer.Write((byte)(header[9] & 0x07 | 0x07)); // Disabling local color table
Writer.Write(header[10]); // LZW Min Code Size
// Read/Write image data
SourceGif.Position = SourceImageBlockPosition + header.Length;
var dataLength = SourceGif.ReadByte();
while (dataLength > 0)
{
var imgData = new byte[dataLength];
SourceGif.Read(imgData, 0, dataLength);
Writer.Write((byte)dataLength);
Writer.Write(imgData, 0, dataLength);
dataLength = SourceGif.ReadByte();
}
Writer.Write((byte)0); // Terminator
}
#endregion
/// <summary>
/// Frees all resources used by this object.
/// </summary>
public void Dispose()
{
// Complete File
_writer.Write((byte)0x3b); // File Trailer
_writer.BaseStream.Dispose();
_writer.Dispose();
}
}
There is a built in .NET class which will encode GIF files.
GifBitmapEncode MSDN
System.Windows.Media.Imaging.GifBitmapEncoder gEnc = new GifBitmapEncoder();
foreach (System.Drawing.Bitmap bmpImage in images)
{
var bmp = bmpImage.GetHbitmap();
var src = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
bmp,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
gEnc.Frames.Add(BitmapFrame.Create(src));
DeleteObject(bmp); // recommended, handle memory leak
}
using(FileStream fs = new FileStream(path, FileMode.Create))
{
gEnc.Save(fs);
}
You might also consider using the ImageMagick library.
There are two .net wrappers for the library listed at http://www.imagemagick.org/script/api.php
Here is an example on how to do it using the Magick.net wrapper:
using (MagickImageCollection collection = new MagickImageCollection())
{
// Add first image and set the animation delay to 100ms
collection.Add("Snakeware.png");
collection[0].AnimationDelay = 100;
// Add second image, set the animation delay to 100ms and flip the image
collection.Add("Snakeware.png");
collection[1].AnimationDelay = 100;
collection[1].Flip();
// Optionally reduce colors
QuantizeSettings settings = new QuantizeSettings();
settings.Colors = 256;
collection.Quantize(settings);
// Optionally optimize the images (images should have the same size).
collection.Optimize();
// Save gif
collection.Write("Snakeware.Animated.gif");
}
Whether or not calling imagemagick is the best choice is kind of hard to awnser without knowing the quality parameters that are important. Some other options would be:
Rick van den Bosch's code archive.org mirror
NGif article on codeplex
these have the advantage that you don't have a dependency on a third partly library which might or might not be available on all systems executing your code.
This article at MS Support explains how to save a gif with a custom color table (this does require full trust). A animated gif is just a set of gifs for each image with some additional information in the header. So combining these two articles should get you what you need.
To use the sample from a Windows Forms app, add references to these assemblies:
C:\Program Files\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\PresentationCore.dll
C:\Program Files\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\System.Xaml.dll
C:\Program Files\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\WindowsBase.dll
Then
Int32Rect is in the System.Windows namespace
BitmapSizeOptions is in the System.Windows.Media.Imaging namespace
BitmapFrame is in the System.Windows.Media.Imaging namespace
Also, don't forget to close the file stream (something like this):
using(FileStream targetFile = new FileStream(path, FileMode.Create))
{
gEnc.Save(targetFile);
}
The AnimatedGif package can make animated gifs.
using (var gif = AnimatedGif.AnimatedGif.Create(Path.Combine(outputPath, "output.gif"), 20))
{
for (var i = 0; i < 10; i++)
{
Image img = Image.FromFile(Path.Combine(outputPath, $"{i.ToString().PadLeft(3, '0')}.png"));
gif.AddFrame(img, delay: -1, quality: GifQuality.Bit8);
}
}
I noticed that one more great alternative to ImageMagic and NGif is not listed in answers yet.
FFMpeg can be used for creating animated GIFs from:
sequence of images (files)
existing video clip (say, mp4 or avi)
from C# bitmap objects by providing input data as "ravvideo" through stdin (without using any temp files)
You can start ffmpeg.exe directly from C# code (with System.Diagnostics.Process) or use one of the existing .NET ffmpeg wrappers:
var ffmpeg = new NReco.VideoConverter.FFMpegConverter();
ffmpeg.ConvertMedia("your_clip.mp4", null, "result.gif", null, new ConvertSettings() );
(this code example uses free NReco VideoConverter - I'm an author of this component, feel free to ask any questions about its usage).
GIF size can be easily reduced by decreasing frame rate and/or frame size.
Also it is possible to get fine-looking animated GIFs with 2-pass approach that generates optimal GIF palette.
Quote from fireydude answer:
https://stackoverflow.com/a/16598294/8917485
This method is not complete, cause the .gif can't repeat.
I found some additional code on other question, make .gif repeat.
.NET - Creating a looping .gif using GifBitmapEncoder
http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
The complete code should looks like the code below:
System.Windows.Media.Imaging.GifBitmapEncoder gEnc = new GifBitmapEncoder();
foreach (System.Drawing.Bitmap bmpImage in bitMaps)
{
var bmp = bmpImage.GetHbitmap();
var src = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
bmp,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
gEnc.Frames.Add(BitmapFrame.Create(src));
DeleteObject(bmp); // recommended, handle memory leak
bmpImage.Dispose(); // recommended, handle memory leak
}
// After adding all frames to gifEncoder (the GifBitmapEncoder)...
using (var ms_ = new MemoryStream())
{
gEnc.Save(ms_);
var fileBytes = ms_.ToArray();
// This is the NETSCAPE2.0 Application Extension.
var applicationExtension = new byte[] { 33, 255, 11, 78, 69, 84, 83, 67, 65, 80, 69, 50, 46, 48, 3, 1, 0, 0, 0 };
var newBytes = new List<byte>();
newBytes.AddRange(fileBytes.Take(13));
newBytes.AddRange(applicationExtension);
newBytes.AddRange(fileBytes.Skip(13));
File.WriteAllBytes("abc.gif", newBytes.ToArray());
}
I want to load an image into a PictureBox. This is the image I want to load: http://www.gravatar.com/avatar/6810d91caff032b202c50701dd3af745?d=identicon&r=PG
How do I do this?
The PictureBox.Load(string url) method "sets the ImageLocation to the specified URL and displays the image indicated."
Try this:
var request = WebRequest.Create("http://www.gravatar.com/avatar/6810d91caff032b202c50701dd3af745?d=identicon&r=PG");
using (var response = request.GetResponse())
using (var stream = response.GetResponseStream())
{
pictureBox1.Image = Bitmap.FromStream(stream);
}
yourPictureBox.ImageLocation = "http://www.gravatar.com/avatar/6810d91caff032b202c50701dd3af745?d=identicon&r=PG"
Here's the solution I use. I can't remember why I couldn't just use the PictureBox.Load methods. I'm pretty sure it's because I wanted to properly scale & center the downloaded image into the PictureBox control. If I recall, all the scaling options on PictureBox either stretch the image, or will resize the PictureBox to fit the image. I wanted a properly scaled and centered image in the size I set for PictureBox.
Now, I just need to make a async version...
Here's my methods:
#region Image Utilities
/// <summary>
/// Loads an image from a URL into a Bitmap object.
/// Currently as written if there is an error during downloading of the image, no exception is thrown.
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public static Bitmap LoadPicture(string url)
{
System.Net.HttpWebRequest wreq;
System.Net.HttpWebResponse wresp;
Stream mystream;
Bitmap bmp;
bmp = null;
mystream = null;
wresp = null;
try
{
wreq = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(url);
wreq.AllowWriteStreamBuffering = true;
wresp = (System.Net.HttpWebResponse)wreq.GetResponse();
if ((mystream = wresp.GetResponseStream()) != null)
bmp = new Bitmap(mystream);
}
catch
{
// Do nothing...
}
finally
{
if (mystream != null)
mystream.Close();
if (wresp != null)
wresp.Close();
}
return (bmp);
}
/// <summary>
/// Takes in an image, scales it maintaining the proper aspect ratio of the image such it fits in the PictureBox's canvas size and loads the image into picture box.
/// Has an optional param to center the image in the picture box if it's smaller then canvas size.
/// </summary>
/// <param name="image">The Image you want to load, see LoadPicture</param>
/// <param name="canvas">The canvas you want the picture to load into</param>
/// <param name="centerImage"></param>
/// <returns></returns>
public static Image ResizeImage(Image image, PictureBox canvas, bool centerImage )
{
if (image == null || canvas == null)
{
return null;
}
int canvasWidth = canvas.Size.Width;
int canvasHeight = canvas.Size.Height;
int originalWidth = image.Size.Width;
int originalHeight = image.Size.Height;
System.Drawing.Image thumbnail =
new Bitmap(canvasWidth, canvasHeight); // changed parm names
System.Drawing.Graphics graphic =
System.Drawing.Graphics.FromImage(thumbnail);
graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphic.SmoothingMode = SmoothingMode.HighQuality;
graphic.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphic.CompositingQuality = CompositingQuality.HighQuality;
/* ------------------ new code --------------- */
// Figure out the ratio
double ratioX = (double)canvasWidth / (double)originalWidth;
double ratioY = (double)canvasHeight / (double)originalHeight;
double ratio = ratioX < ratioY ? ratioX : ratioY; // use whichever multiplier is smaller
// now we can get the new height and width
int newHeight = Convert.ToInt32(originalHeight * ratio);
int newWidth = Convert.ToInt32(originalWidth * ratio);
// Now calculate the X,Y position of the upper-left corner
// (one of these will always be zero)
int posX = Convert.ToInt32((canvasWidth - (image.Width * ratio)) / 2);
int posY = Convert.ToInt32((canvasHeight - (image.Height * ratio)) / 2);
if (!centerImage)
{
posX = 0;
posY = 0;
}
graphic.Clear(Color.White); // white padding
graphic.DrawImage(image, posX, posY, newWidth, newHeight);
/* ------------- end new code ---------------- */
System.Drawing.Imaging.ImageCodecInfo[] info =
ImageCodecInfo.GetImageEncoders();
EncoderParameters encoderParameters;
encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality,
100L);
Stream s = new System.IO.MemoryStream();
thumbnail.Save(s, info[1],
encoderParameters);
return Image.FromStream(s);
}
#endregion
Here's the required includes. (Some might be needed by other code, but including all to be safe)
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using System.IO;
using System.Drawing.Imaging;
using System.Text.RegularExpressions;
using System.Drawing;
How I generally use it:
ImageUtil.ResizeImage(ImageUtil.LoadPicture( "http://someurl/img.jpg", pictureBox1, true);
If you are trying to load the image at your form_load, it's a better idea to use the code
pictureBox1.LoadAsync(#"http://google.com/test.png");
not only loading from web but also no lag in your form loading.
Does anyone know how to create an animated gif using c#?
Ideally I would have some control over the color reduction used.
Is using imagemagick (as an external started process) the best choice?
This Gif Animation Creater code from https://github.com/DataDink/Bumpkit can set Delay foreach Frame:
Uses .Net standard Gif Encoding and adds Animation headers.
EDIT: Made the code similar to a typical file writer.
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading.Tasks;
/// <summary>
/// Creates a GIF using .Net GIF encoding and additional animation headers.
/// </summary>
public class GifWriter : IDisposable
{
#region Fields
const long SourceGlobalColorInfoPosition = 10,
SourceImageBlockPosition = 789;
readonly BinaryWriter _writer;
bool _firstFrame = true;
readonly object _syncLock = new object();
#endregion
/// <summary>
/// Creates a new instance of GifWriter.
/// </summary>
/// <param name="OutStream">The <see cref="Stream"/> to output the Gif to.</param>
/// <param name="DefaultFrameDelay">Default Delay between consecutive frames... FrameRate = 1000 / DefaultFrameDelay.</param>
/// <param name="Repeat">No of times the Gif should repeat... -1 not to repeat, 0 to repeat indefinitely.</param>
public GifWriter(Stream OutStream, int DefaultFrameDelay = 500, int Repeat = -1)
{
if (OutStream == null)
throw new ArgumentNullException(nameof(OutStream));
if (DefaultFrameDelay <= 0)
throw new ArgumentOutOfRangeException(nameof(DefaultFrameDelay));
if (Repeat < -1)
throw new ArgumentOutOfRangeException(nameof(Repeat));
_writer = new BinaryWriter(OutStream);
this.DefaultFrameDelay = DefaultFrameDelay;
this.Repeat = Repeat;
}
/// <summary>
/// Creates a new instance of GifWriter.
/// </summary>
/// <param name="FileName">The path to the file to output the Gif to.</param>
/// <param name="DefaultFrameDelay">Default Delay between consecutive frames... FrameRate = 1000 / DefaultFrameDelay.</param>
/// <param name="Repeat">No of times the Gif should repeat... -1 not to repeat, 0 to repeat indefinitely.</param>
public GifWriter(string FileName, int DefaultFrameDelay = 500, int Repeat = -1)
: this(new FileStream(FileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read), DefaultFrameDelay, Repeat) { }
#region Properties
/// <summary>
/// Gets or Sets the Default Width of a Frame. Used when unspecified.
/// </summary>
public int DefaultWidth { get; set; }
/// <summary>
/// Gets or Sets the Default Height of a Frame. Used when unspecified.
/// </summary>
public int DefaultHeight { get; set; }
/// <summary>
/// Gets or Sets the Default Delay in Milliseconds.
/// </summary>
public int DefaultFrameDelay { get; set; }
/// <summary>
/// The Number of Times the Animation must repeat.
/// -1 indicates no repeat. 0 indicates repeat indefinitely
/// </summary>
public int Repeat { get; }
#endregion
/// <summary>
/// Adds a frame to this animation.
/// </summary>
/// <param name="Image">The image to add</param>
/// <param name="Delay">Delay in Milliseconds between this and last frame... 0 = <see cref="DefaultFrameDelay"/></param>
public void WriteFrame(Image Image, int Delay = 0)
{
lock (_syncLock)
using (var gifStream = new MemoryStream())
{
Image.Save(gifStream, ImageFormat.Gif);
// Steal the global color table info
if (_firstFrame)
InitHeader(gifStream, _writer, Image.Width, Image.Height);
WriteGraphicControlBlock(gifStream, _writer, Delay == 0 ? DefaultFrameDelay : Delay);
WriteImageBlock(gifStream, _writer, !_firstFrame, 0, 0, Image.Width, Image.Height);
}
if (_firstFrame)
_firstFrame = false;
}
#region Write
void InitHeader(Stream SourceGif, BinaryWriter Writer, int Width, int Height)
{
// File Header
Writer.Write("GIF".ToCharArray()); // File type
Writer.Write("89a".ToCharArray()); // File Version
Writer.Write((short)(DefaultWidth == 0 ? Width : DefaultWidth)); // Initial Logical Width
Writer.Write((short)(DefaultHeight == 0 ? Height : DefaultHeight)); // Initial Logical Height
SourceGif.Position = SourceGlobalColorInfoPosition;
Writer.Write((byte)SourceGif.ReadByte()); // Global Color Table Info
Writer.Write((byte)0); // Background Color Index
Writer.Write((byte)0); // Pixel aspect ratio
WriteColorTable(SourceGif, Writer);
// App Extension Header for Repeating
if (Repeat == -1)
return;
Writer.Write(unchecked((short)0xff21)); // Application Extension Block Identifier
Writer.Write((byte)0x0b); // Application Block Size
Writer.Write("NETSCAPE2.0".ToCharArray()); // Application Identifier
Writer.Write((byte)3); // Application block length
Writer.Write((byte)1);
Writer.Write((short)Repeat); // Repeat count for images.
Writer.Write((byte)0); // terminator
}
static void WriteColorTable(Stream SourceGif, BinaryWriter Writer)
{
SourceGif.Position = 13; // Locating the image color table
var colorTable = new byte[768];
SourceGif.Read(colorTable, 0, colorTable.Length);
Writer.Write(colorTable, 0, colorTable.Length);
}
static void WriteGraphicControlBlock(Stream SourceGif, BinaryWriter Writer, int FrameDelay)
{
SourceGif.Position = 781; // Locating the source GCE
var blockhead = new byte[8];
SourceGif.Read(blockhead, 0, blockhead.Length); // Reading source GCE
Writer.Write(unchecked((short)0xf921)); // Identifier
Writer.Write((byte)0x04); // Block Size
Writer.Write((byte)(blockhead[3] & 0xf7 | 0x08)); // Setting disposal flag
Writer.Write((short)(FrameDelay / 10)); // Setting frame delay
Writer.Write(blockhead[6]); // Transparent color index
Writer.Write((byte)0); // Terminator
}
static void WriteImageBlock(Stream SourceGif, BinaryWriter Writer, bool IncludeColorTable, int X, int Y, int Width, int Height)
{
SourceGif.Position = SourceImageBlockPosition; // Locating the image block
var header = new byte[11];
SourceGif.Read(header, 0, header.Length);
Writer.Write(header[0]); // Separator
Writer.Write((short)X); // Position X
Writer.Write((short)Y); // Position Y
Writer.Write((short)Width); // Width
Writer.Write((short)Height); // Height
if (IncludeColorTable) // If first frame, use global color table - else use local
{
SourceGif.Position = SourceGlobalColorInfoPosition;
Writer.Write((byte)(SourceGif.ReadByte() & 0x3f | 0x80)); // Enabling local color table
WriteColorTable(SourceGif, Writer);
}
else Writer.Write((byte)(header[9] & 0x07 | 0x07)); // Disabling local color table
Writer.Write(header[10]); // LZW Min Code Size
// Read/Write image data
SourceGif.Position = SourceImageBlockPosition + header.Length;
var dataLength = SourceGif.ReadByte();
while (dataLength > 0)
{
var imgData = new byte[dataLength];
SourceGif.Read(imgData, 0, dataLength);
Writer.Write((byte)dataLength);
Writer.Write(imgData, 0, dataLength);
dataLength = SourceGif.ReadByte();
}
Writer.Write((byte)0); // Terminator
}
#endregion
/// <summary>
/// Frees all resources used by this object.
/// </summary>
public void Dispose()
{
// Complete File
_writer.Write((byte)0x3b); // File Trailer
_writer.BaseStream.Dispose();
_writer.Dispose();
}
}
There is a built in .NET class which will encode GIF files.
GifBitmapEncode MSDN
System.Windows.Media.Imaging.GifBitmapEncoder gEnc = new GifBitmapEncoder();
foreach (System.Drawing.Bitmap bmpImage in images)
{
var bmp = bmpImage.GetHbitmap();
var src = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
bmp,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
gEnc.Frames.Add(BitmapFrame.Create(src));
DeleteObject(bmp); // recommended, handle memory leak
}
using(FileStream fs = new FileStream(path, FileMode.Create))
{
gEnc.Save(fs);
}
You might also consider using the ImageMagick library.
There are two .net wrappers for the library listed at http://www.imagemagick.org/script/api.php
Here is an example on how to do it using the Magick.net wrapper:
using (MagickImageCollection collection = new MagickImageCollection())
{
// Add first image and set the animation delay to 100ms
collection.Add("Snakeware.png");
collection[0].AnimationDelay = 100;
// Add second image, set the animation delay to 100ms and flip the image
collection.Add("Snakeware.png");
collection[1].AnimationDelay = 100;
collection[1].Flip();
// Optionally reduce colors
QuantizeSettings settings = new QuantizeSettings();
settings.Colors = 256;
collection.Quantize(settings);
// Optionally optimize the images (images should have the same size).
collection.Optimize();
// Save gif
collection.Write("Snakeware.Animated.gif");
}
Whether or not calling imagemagick is the best choice is kind of hard to awnser without knowing the quality parameters that are important. Some other options would be:
Rick van den Bosch's code archive.org mirror
NGif article on codeplex
these have the advantage that you don't have a dependency on a third partly library which might or might not be available on all systems executing your code.
This article at MS Support explains how to save a gif with a custom color table (this does require full trust). A animated gif is just a set of gifs for each image with some additional information in the header. So combining these two articles should get you what you need.
To use the sample from a Windows Forms app, add references to these assemblies:
C:\Program Files\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\PresentationCore.dll
C:\Program Files\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\System.Xaml.dll
C:\Program Files\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\WindowsBase.dll
Then
Int32Rect is in the System.Windows namespace
BitmapSizeOptions is in the System.Windows.Media.Imaging namespace
BitmapFrame is in the System.Windows.Media.Imaging namespace
Also, don't forget to close the file stream (something like this):
using(FileStream targetFile = new FileStream(path, FileMode.Create))
{
gEnc.Save(targetFile);
}
The AnimatedGif package can make animated gifs.
using (var gif = AnimatedGif.AnimatedGif.Create(Path.Combine(outputPath, "output.gif"), 20))
{
for (var i = 0; i < 10; i++)
{
Image img = Image.FromFile(Path.Combine(outputPath, $"{i.ToString().PadLeft(3, '0')}.png"));
gif.AddFrame(img, delay: -1, quality: GifQuality.Bit8);
}
}
I noticed that one more great alternative to ImageMagic and NGif is not listed in answers yet.
FFMpeg can be used for creating animated GIFs from:
sequence of images (files)
existing video clip (say, mp4 or avi)
from C# bitmap objects by providing input data as "ravvideo" through stdin (without using any temp files)
You can start ffmpeg.exe directly from C# code (with System.Diagnostics.Process) or use one of the existing .NET ffmpeg wrappers:
var ffmpeg = new NReco.VideoConverter.FFMpegConverter();
ffmpeg.ConvertMedia("your_clip.mp4", null, "result.gif", null, new ConvertSettings() );
(this code example uses free NReco VideoConverter - I'm an author of this component, feel free to ask any questions about its usage).
GIF size can be easily reduced by decreasing frame rate and/or frame size.
Also it is possible to get fine-looking animated GIFs with 2-pass approach that generates optimal GIF palette.
Quote from fireydude answer:
https://stackoverflow.com/a/16598294/8917485
This method is not complete, cause the .gif can't repeat.
I found some additional code on other question, make .gif repeat.
.NET - Creating a looping .gif using GifBitmapEncoder
http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
The complete code should looks like the code below:
System.Windows.Media.Imaging.GifBitmapEncoder gEnc = new GifBitmapEncoder();
foreach (System.Drawing.Bitmap bmpImage in bitMaps)
{
var bmp = bmpImage.GetHbitmap();
var src = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
bmp,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
gEnc.Frames.Add(BitmapFrame.Create(src));
DeleteObject(bmp); // recommended, handle memory leak
bmpImage.Dispose(); // recommended, handle memory leak
}
// After adding all frames to gifEncoder (the GifBitmapEncoder)...
using (var ms_ = new MemoryStream())
{
gEnc.Save(ms_);
var fileBytes = ms_.ToArray();
// This is the NETSCAPE2.0 Application Extension.
var applicationExtension = new byte[] { 33, 255, 11, 78, 69, 84, 83, 67, 65, 80, 69, 50, 46, 48, 3, 1, 0, 0, 0 };
var newBytes = new List<byte>();
newBytes.AddRange(fileBytes.Take(13));
newBytes.AddRange(applicationExtension);
newBytes.AddRange(fileBytes.Skip(13));
File.WriteAllBytes("abc.gif", newBytes.ToArray());
}