Browse Source

Fix memory leak in GifEncoder

Former-commit-id: b3ee297b52fa012dcc31d210b48489338ef746fb
Former-commit-id: d455ea683b3235c18e9b8772cb7ff66e87a3105d
af/merge-core
James South 11 years ago
parent
commit
6decd7b226
  1. 8
      src/ImageProcessor.Playground/Program.cs
  2. 38
      src/ImageProcessor.UnitTests/AssertionHelpers.cs
  3. 18
      src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs
  4. 49
      src/ImageProcessor/Imaging/Formats/GifEncoder.cs
  5. 11
      src/ImageProcessor/Imaging/Formats/GifFormat.cs

8
src/ImageProcessor.Playground/Program.cs

@ -50,7 +50,7 @@ namespace ImageProcessor.PlayGround
} }
Image mask = Image.FromFile(Path.Combine(resolvedPath, "mask2.png")); Image mask = Image.FromFile(Path.Combine(resolvedPath, "mask2.png"));
IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".jfif"); IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".gif");
//IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png", ".tif"); //IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png", ".tif");
foreach (FileInfo fileInfo in files) foreach (FileInfo fileInfo in files)
@ -85,9 +85,9 @@ namespace ImageProcessor.PlayGround
//.BackgroundColor(Color.Cyan) //.BackgroundColor(Color.Cyan)
//.ReplaceColor(Color.FromArgb(255, 1, 107, 165), Color.FromArgb(255, 1, 165, 13), 80) //.ReplaceColor(Color.FromArgb(255, 1, 107, 165), Color.FromArgb(255, 1, 165, 13), 80)
//.Resize(size) //.Resize(size)
.Resize(new ResizeLayer(size, ResizeMode.Max)) // .Resize(new ResizeLayer(size, ResizeMode.Max))
.Resize(new ResizeLayer(size, ResizeMode.Stretch)) // .Resize(new ResizeLayer(size, ResizeMode.Stretch))
//.DetectEdges(new SobelEdgeFilter(), true) .DetectEdges(new SobelEdgeFilter(), true)
//.DetectEdges(new LaplacianOfGaussianEdgeFilter()) //.DetectEdges(new LaplacianOfGaussianEdgeFilter())
//.EntropyCrop() //.EntropyCrop()
//.Filter(MatrixFilters.Invert) //.Filter(MatrixFilters.Invert)

38
src/ImageProcessor.UnitTests/AssertionHelpers.cs

@ -4,13 +4,14 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
// -------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.UnitTests namespace ImageProcessor.UnitTests
{ {
using System.Drawing; using System.Drawing;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.Linq;
using System.IO; using System.IO;
using NUnit.Framework; using System.Linq;
using FluentAssertions; using FluentAssertions;
/// <summary> /// <summary>
@ -21,9 +22,19 @@ namespace ImageProcessor.UnitTests
/// <summary> /// <summary>
/// Asserts that two images are identical /// Asserts that two images are identical
/// </summary> /// </summary>
/// <param name="expected">The expected result</param> /// <param name="expected">
/// <param name="tested">The tested image</param> /// The expected result
public static void AssertImagesAreIdentical(Image expected, Image tested, string because, params string[] becauseArgs) /// </param>
/// <param name="tested">
/// The tested image
/// </param>
/// <param name="because">
/// The because message.
/// </param>
/// <param name="becauseArgs">
/// The because arguments.
/// </param>
public static void AssertImagesAreIdentical(Image expected, Image tested, string because, params object[] becauseArgs)
{ {
ToByteArray(expected).SequenceEqual(ToByteArray(tested)).Should().BeTrue(because, becauseArgs); ToByteArray(expected).SequenceEqual(ToByteArray(tested)).Should().BeTrue(because, becauseArgs);
} }
@ -31,9 +42,19 @@ namespace ImageProcessor.UnitTests
/// <summary> /// <summary>
/// Asserts that two images are different /// Asserts that two images are different
/// </summary> /// </summary>
/// <param name="expected">The not-expected result</param> /// <param name="expected">
/// <param name="tested">The tested image</param> /// The not-expected result
public static void AssertImagesAreDifferent(Image expected, Image tested, string because, params string[] becauseArgs) /// </param>
/// <param name="tested">
/// The tested image
/// </param>
/// <param name="because">
/// The because message.
/// </param>
/// <param name="becauseArgs">
/// The because arguments.
/// </param>
public static void AssertImagesAreDifferent(Image expected, Image tested, string because, params object[] becauseArgs)
{ {
ToByteArray(expected).SequenceEqual(ToByteArray(tested)).Should().BeFalse(because, becauseArgs); ToByteArray(expected).SequenceEqual(ToByteArray(tested)).Should().BeFalse(because, becauseArgs);
} }
@ -42,7 +63,6 @@ namespace ImageProcessor.UnitTests
/// Converts an image to a byte array /// Converts an image to a byte array
/// </summary> /// </summary>
/// <param name="imageIn">The image to convert</param> /// <param name="imageIn">The image to convert</param>
/// <param name="format">The format to use</param>
/// <returns>An array of bytes representing the image</returns> /// <returns>An array of bytes representing the image</returns>
public static byte[] ToByteArray(Image imageIn) public static byte[] ToByteArray(Image imageIn)
{ {

18
src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs

@ -9,15 +9,15 @@ namespace ImageProcessor.UnitTests
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Drawing.Imaging;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using FluentAssertions;
using ImageProcessor.Imaging; using ImageProcessor.Imaging;
using ImageProcessor.Imaging.Filters.EdgeDetection; using ImageProcessor.Imaging.Filters.EdgeDetection;
using ImageProcessor.Imaging.Filters.Photo; using ImageProcessor.Imaging.Filters.Photo;
using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
/// <summary> /// <summary>
@ -299,7 +299,7 @@ namespace ImageProcessor.UnitTests
{ {
Image original = (Image)imageFactory.Image.Clone(); Image original = (Image)imageFactory.Image.Clone();
List<IMatrixFilter> filters = new List<IMatrixFilter>() List<IMatrixFilter> filters = new List<IMatrixFilter>
{ {
MatrixFilters.BlackWhite, MatrixFilters.BlackWhite,
MatrixFilters.Comic, MatrixFilters.Comic,
@ -533,7 +533,7 @@ namespace ImageProcessor.UnitTests
{ {
Image original = (Image)imageFactory.Image.Clone(); Image original = (Image)imageFactory.Image.Clone();
List<IEdgeFilter> filters = new List<IEdgeFilter>() List<IEdgeFilter> filters = new List<IEdgeFilter>
{ {
new KayyaliEdgeFilter(), new KayyaliEdgeFilter(),
new KirschEdgeFilter(), new KirschEdgeFilter(),
@ -606,22 +606,22 @@ namespace ImageProcessor.UnitTests
/// <returns>The list of images</returns> /// <returns>The list of images</returns>
private IEnumerable<ImageFactory> ListInputImages() private IEnumerable<ImageFactory> ListInputImages()
{ {
if (imagesFactories == null || imagesFactories.Count() == 0) if (this.imagesFactories == null || !this.imagesFactories.Any())
{ {
imagesFactories = new List<ImageFactory>(); this.imagesFactories = new List<ImageFactory>();
foreach (FileInfo fi in this.ListInputFiles()) foreach (FileInfo fi in this.ListInputFiles())
{ {
imagesFactories.Add((new ImageFactory()).Load(fi.FullName)); this.imagesFactories.Add((new ImageFactory()).Load(fi.FullName));
} }
} }
// reset all the images whenever we call this // reset all the images whenever we call this
foreach (ImageFactory image in imagesFactories) foreach (ImageFactory image in this.imagesFactories)
{ {
image.Reset(); image.Reset();
} }
return imagesFactories; return this.imagesFactories;
} }
} }
} }

49
src/ImageProcessor/Imaging/Formats/GifEncoder.cs

@ -17,6 +17,7 @@
namespace ImageProcessor.Imaging.Formats namespace ImageProcessor.Imaging.Formats
{ {
using System; using System;
using System.Drawing;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -114,7 +115,7 @@ namespace ImageProcessor.Imaging.Formats
/// The stream. /// The stream.
/// </summary> /// </summary>
// ReSharper disable once FieldCanBeMadeReadOnly.Local // ReSharper disable once FieldCanBeMadeReadOnly.Local
private MemoryStream inputStream; private MemoryStream imageStream;
/// <summary> /// <summary>
/// The height. /// The height.
@ -154,9 +155,6 @@ namespace ImageProcessor.Imaging.Formats
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="GifEncoder"/> class. /// Initializes a new instance of the <see cref="GifEncoder"/> class.
/// </summary> /// </summary>
/// <param name="stream">
/// The stream that will be written to.
/// </param>
/// <param name="width"> /// <param name="width">
/// Sets the width for this gif or null to use the first frame's width. /// Sets the width for this gif or null to use the first frame's width.
/// </param> /// </param>
@ -166,9 +164,9 @@ namespace ImageProcessor.Imaging.Formats
/// <param name="repeatCount"> /// <param name="repeatCount">
/// The number of times to repeat the animation. /// The number of times to repeat the animation.
/// </param> /// </param>
public GifEncoder(MemoryStream stream, int? width = null, int? height = null, int? repeatCount = null) public GifEncoder(int? width = null, int? height = null, int? repeatCount = null)
{ {
this.inputStream = stream; this.imageStream = new MemoryStream();
this.width = width; this.width = width;
this.height = height; this.height = height;
this.repeatCount = repeatCount; this.repeatCount = repeatCount;
@ -232,6 +230,23 @@ namespace ImageProcessor.Imaging.Formats
// from executing a second time. // from executing a second time.
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
/// <summary>
/// Saves the completed gif to an <see cref="Image"/>
/// </summary>
/// <returns>The completed animated gif.</returns>
public Image Save()
{
// Complete File
this.WriteByte(FileTrailer);
// Push the data
this.imageStream.Flush();
this.imageStream.Position = 0;
byte[] bytes = this.imageStream.ToArray();
ImageConverter converter = new ImageConverter();
return (Image)converter.ConvertFrom(bytes);
}
#endregion #endregion
#region Methods #region Methods
@ -250,11 +265,7 @@ namespace ImageProcessor.Imaging.Formats
if (disposing) if (disposing)
{ {
// Complete File this.imageStream.Dispose();
this.WriteByte(FileTrailer);
// Push the data
this.inputStream.Flush();
} }
// Call the appropriate methods to clean up // Call the appropriate methods to clean up
@ -319,7 +330,7 @@ namespace ImageProcessor.Imaging.Formats
/// </param> /// </param>
private void WriteByte(int value) private void WriteByte(int value)
{ {
this.inputStream.WriteByte(Convert.ToByte(value)); this.imageStream.WriteByte(Convert.ToByte(value));
} }
/// <summary> /// <summary>
@ -333,7 +344,7 @@ namespace ImageProcessor.Imaging.Formats
sourceGif.Position = SourceColorBlockPosition; // Locating the image color table sourceGif.Position = SourceColorBlockPosition; // Locating the image color table
byte[] colorTable = new byte[SourceColorBlockLength]; byte[] colorTable = new byte[SourceColorBlockLength];
sourceGif.Read(colorTable, 0, colorTable.Length); sourceGif.Read(colorTable, 0, colorTable.Length);
this.inputStream.Write(colorTable, 0, colorTable.Length); this.imageStream.Write(colorTable, 0, colorTable.Length);
} }
/// <summary> /// <summary>
@ -415,12 +426,12 @@ namespace ImageProcessor.Imaging.Formats
byte[] imgData = new byte[dataLength]; byte[] imgData = new byte[dataLength];
sourceGif.Read(imgData, 0, dataLength); sourceGif.Read(imgData, 0, dataLength);
this.inputStream.WriteByte(Convert.ToByte(dataLength)); this.imageStream.WriteByte(Convert.ToByte(dataLength));
this.inputStream.Write(imgData, 0, dataLength); this.imageStream.Write(imgData, 0, dataLength);
dataLength = sourceGif.ReadByte(); dataLength = sourceGif.ReadByte();
} }
this.inputStream.WriteByte(0); // Terminator this.imageStream.WriteByte(0); // Terminator
} }
/// <summary> /// <summary>
@ -432,8 +443,8 @@ namespace ImageProcessor.Imaging.Formats
private void WriteShort(int value) private void WriteShort(int value)
{ {
// Leave only one significant byte. // Leave only one significant byte.
this.inputStream.WriteByte(Convert.ToByte(value & 0xff)); this.imageStream.WriteByte(Convert.ToByte(value & 0xff));
this.inputStream.WriteByte(Convert.ToByte((value >> 8) & 0xff)); this.imageStream.WriteByte(Convert.ToByte((value >> 8) & 0xff));
} }
/// <summary> /// <summary>
@ -444,7 +455,7 @@ namespace ImageProcessor.Imaging.Formats
/// </param> /// </param>
private void WriteString(string value) private void WriteString(string value)
{ {
this.inputStream.Write(value.ToArray().Select(c => (byte)c).ToArray(), 0, value.Length); this.imageStream.Write(value.ToArray().Select(c => (byte)c).ToArray(), 0, value.Length);
} }
#endregion #endregion
} }

11
src/ImageProcessor/Imaging/Formats/GifFormat.cs

@ -79,11 +79,7 @@ namespace ImageProcessor.Imaging.Formats
if (decoder.IsAnimated) if (decoder.IsAnimated)
{ {
OctreeQuantizer quantizer = new OctreeQuantizer(255, 8); OctreeQuantizer quantizer = new OctreeQuantizer(255, 8);
using (GifEncoder encoder = new GifEncoder(null, null, decoder.LoopCount))
// We don't dispose of the memory stream as that is disposed when a new image is created and doing so
// beforehand will cause an exception.
MemoryStream stream = new MemoryStream();
using (GifEncoder encoder = new GifEncoder(stream, null, null, decoder.LoopCount))
{ {
foreach (GifFrame frame in decoder.GifFrames) foreach (GifFrame frame in decoder.GifFrames)
{ {
@ -91,10 +87,9 @@ namespace ImageProcessor.Imaging.Formats
frame.Image = quantizer.Quantize(processor.Invoke(factory)); frame.Image = quantizer.Quantize(processor.Invoke(factory));
encoder.AddFrame(frame); encoder.AddFrame(frame);
} }
}
stream.Position = 0; factory.Image = encoder.Save();
factory.Image = Image.FromStream(stream); }
} }
else else
{ {

Loading…
Cancel
Save