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

38
src/ImageProcessor.UnitTests/AssertionHelpers.cs

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

18
src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs

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

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

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

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

@ -79,11 +79,7 @@ namespace ImageProcessor.Imaging.Formats
if (decoder.IsAnimated)
{
OctreeQuantizer quantizer = new OctreeQuantizer(255, 8);
// 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))
using (GifEncoder encoder = new GifEncoder(null, null, decoder.LoopCount))
{
foreach (GifFrame frame in decoder.GifFrames)
{
@ -91,10 +87,9 @@ namespace ImageProcessor.Imaging.Formats
frame.Image = quantizer.Quantize(processor.Invoke(factory));
encoder.AddFrame(frame);
}
}
stream.Position = 0;
factory.Image = Image.FromStream(stream);
factory.Image = encoder.Save();
}
}
else
{

Loading…
Cancel
Save