Browse Source

Merge remote-tracking branch 'refs/remotes/origin/Core-IDisposable' into Core

Former-commit-id: dcb89b26e6586df8fad2e265558d7c59e0779794
Former-commit-id: ee25eca0cb34d6983ec9b3acce13456af6556ed6
Former-commit-id: eb700f049d4ca2f4e9ef01c2595513549eebf6a9
pull/1/head
James South 11 years ago
parent
commit
af5a1dcc59
  1. 12
      README.md
  2. 6
      src/ImageProcessorCore/Filters/Blend.cs
  3. 10
      src/ImageProcessorCore/Filters/ImageFilterExtensions.cs
  4. 2
      src/ImageProcessorCore/Formats/Png/PngEncoder.cs
  5. 2
      src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs
  6. 2
      src/ImageProcessorCore/IImageBase.cs
  7. 26
      src/ImageProcessorCore/Image.cs
  8. 159
      src/ImageProcessorCore/ImageBase.cs
  9. 6
      src/ImageProcessorCore/ImageExtensions.cs
  10. 30
      src/ImageProcessorCore/ParallelImageProcessor.cs
  11. 5
      src/ImageProcessorCore/Samplers/EntropyCrop.cs
  12. 11
      src/ImageProcessorCore/Samplers/Resize.cs
  13. 5
      tests/ImageProcessorCore.Tests/Formats/BitmapTests.cs
  14. 34
      tests/ImageProcessorCore.Tests/Formats/EncoderDecoderTests.cs
  15. 5
      tests/ImageProcessorCore.Tests/Formats/PngTests.cs
  16. 5
      tests/ImageProcessorCore.Tests/Processors/Filters/FilterTests.cs
  17. 38
      tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs

12
README.md

@ -50,7 +50,7 @@ git clone https://github.com/JimBobSquarePants/ImageProcessor
###What works so far/ What is planned? ###What works so far/ What is planned?
- Encoding/decoding of image formats (plugable), progressive required - Encoding/decoding of image formats (plugable).
- [x] Jpeg (Includes Subsampling. Progressive writing required) - [x] Jpeg (Includes Subsampling. Progressive writing required)
- [x] Bmp (Read: 32bit, 24bit, 16 bit. Write: 32bit, 24bit just now) - [x] Bmp (Read: 32bit, 24bit, 16 bit. Write: 32bit, 24bit just now)
- [x] Png (Read: TrueColor, Grayscale, Indexed. Write: True color, Indexed just now) - [x] Png (Read: TrueColor, Grayscale, Indexed. Write: True color, Indexed just now)
@ -153,7 +153,7 @@ git clone https://github.com/JimBobSquarePants/ImageProcessor
###API Changes ###API Changes
With this version the API will change dramatically. Without the constraints of `System.Drawing` I have been able to develop something much more flexible, easier to code against, and much, much less prone to memory leaks. Gone are using image classes which implements `IDisposable`, Gone are system wide proces locks with Images and processors thread safe usable in parallel processing utilizing all the availables cores. With this version the API will change dramatically. Without the constraints of `System.Drawing` I have been able to develop something much more flexible, easier to code against, and much, much less prone to memory leaks. Gone are system wide proces locks with Images and processors thread safe usable in parallel processing utilizing all the availables cores.
Image methods are also fluent which allow chaining much like the `ImageFactory` class in the Framework version. Image methods are also fluent which allow chaining much like the `ImageFactory` class in the Framework version.
@ -161,23 +161,20 @@ Here's an example of the code required to resize an image using the default Bicu
```csharp ```csharp
using (FileStream stream = File.OpenRead("foo.jpg")) using (FileStream stream = File.OpenRead("foo.jpg"))
{ using (Image image = new Image(stream))
Image image = new Image(stream);
using (FileStream output = File.OpenWrite("bar.jpg")) using (FileStream output = File.OpenWrite("bar.jpg"))
{ {
image.Resize(image.Width / 2, image.Height / 2) image.Resize(image.Width / 2, image.Height / 2)
.Greyscale() .Greyscale()
.Save(output); .Save(output);
} }
}
``` ```
It will also be possible to pass collections of processors as params to manipulate images. For example here I am applying a Gaussian blur with a sigma of 5 to an image, then detecting the edges using a Sobel operator working in greyscale mode. It will also be possible to pass collections of processors as params to manipulate images. For example here I am applying a Gaussian blur with a sigma of 5 to an image, then detecting the edges using a Sobel operator working in greyscale mode.
```csharp ```csharp
using (FileStream stream = File.OpenRead("foo.jpg")) using (FileStream stream = File.OpenRead("foo.jpg"))
{ using (Image image = new Image(stream))
Image image = new Image(stream);
using (FileStream output = File.OpenWrite("bar.jpg")) using (FileStream output = File.OpenWrite("bar.jpg"))
{ {
List<IImageProcessor> processors = new List<IImageProcessor>() List<IImageProcessor> processors = new List<IImageProcessor>()
@ -189,7 +186,6 @@ using (FileStream stream = File.OpenRead("foo.jpg"))
image.Process(processors.ToArray()) image.Process(processors.ToArray())
.Save(output); .Save(output);
} }
}
``` ```
Individual processors can be initialised and apply processing against images. This allows nesting which will allow the powerful combination of processing methods: Individual processors can be initialised and apply processing against images. This allows nesting which will allow the powerful combination of processing methods:

6
src/ImageProcessorCore/Filters/Blend.cs

@ -20,7 +20,10 @@ namespace ImageProcessorCore.Filters
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Blend"/> class. /// Initializes a new instance of the <see cref="Blend"/> class.
/// </summary> /// </summary>
/// <param name="image">The image to blend.</param> /// <param name="image">
/// The image to blend with the currently processing image.
/// Disposal of this image is the responsibility of the developer.
/// </param>
/// <param name="alpha">The opacity of the image to blend. Between 0 and 100.</param> /// <param name="alpha">The opacity of the image to blend. Between 0 and 100.</param>
public Blend(ImageBase image, int alpha = 100) public Blend(ImageBase image, int alpha = 100)
{ {
@ -69,6 +72,7 @@ namespace ImageProcessorCore.Filters
target[x, y] = color; target[x, y] = color;
} }
this.OnRowProcessed(); this.OnRowProcessed();
} }
}); });

10
src/ImageProcessorCore/Filters/ImageFilterExtensions.cs

@ -73,7 +73,10 @@ namespace ImageProcessorCore.Filters
/// Combines the given image together with the current one by blending their pixels. /// Combines the given image together with the current one by blending their pixels.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param> /// <param name="image">
/// The image to blend with the currently processing image.
/// Disposal of this image is the responsibility of the developer.
/// </param>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 100.</param> /// <param name="percent">The opacity of the image image to blend. Must be between 0 and 100.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param> /// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
@ -86,7 +89,10 @@ namespace ImageProcessorCore.Filters
/// Combines the given image together with the current one by blending their pixels. /// Combines the given image together with the current one by blending their pixels.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param> /// <param name="image">
/// The image to blend with the currently processing image.
/// Disposal of this image is the responsibility of the developer.
/// </param>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 100.</param> /// <param name="percent">The opacity of the image image to blend. Must be between 0 and 100.</param>
/// <param name="rectangle"> /// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter. /// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.

2
src/ImageProcessorCore/Formats/Png/PngEncoder.cs

@ -18,7 +18,7 @@ namespace ImageProcessorCore.Formats
/// <summary> /// <summary>
/// Gets or sets the quality of output for images. /// Gets or sets the quality of output for images.
/// </summary> /// </summary>
public int Quality { get; set; } = int.MaxValue; public int Quality { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public string MimeType => "image/png"; public string MimeType => "image/png";

2
src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs

@ -35,7 +35,7 @@ namespace ImageProcessorCore.Formats
/// <summary> /// <summary>
/// Gets or sets the quality of output for images. /// Gets or sets the quality of output for images.
/// </summary> /// </summary>
public int Quality { get; set; } = int.MaxValue; public int Quality { get; set; }
/// <summary> /// <summary>
/// The compression level 1-9. /// The compression level 1-9.

2
src/ImageProcessorCore/IImageBase.cs

@ -17,7 +17,7 @@ namespace ImageProcessorCore
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The returned array has a length of Width * Height * 4 bytes /// The returned array has a length of Width * Height * 4 bytes
/// and stores the blue, the green, the red and the alpha value for /// and stores the red, the green, the blue, and the alpha value for
/// each pixel in this order. /// each pixel in this order.
/// </remarks> /// </remarks>
float[] Pixels { get; } float[] Pixels { get; }

26
src/ImageProcessorCore/Image.cs

@ -45,7 +45,7 @@ namespace ImageProcessorCore
new BmpFormat(), new BmpFormat(),
new JpegFormat(), new JpegFormat(),
new PngFormat(), new PngFormat(),
new GifFormat(), new GifFormat()
}); });
/// <summary> /// <summary>
@ -223,6 +223,30 @@ namespace ImageProcessorCore
encoder.Encode(this, stream); encoder.Encode(this, stream);
} }
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (this.IsDisposed)
{
return;
}
if (disposing)
{
// Dispose of any managed resources here.
if (this.Frames.Any())
{
foreach (ImageFrame frame in this.Frames)
{
frame.Dispose();
}
this.Frames.Clear();
}
}
base.Dispose(disposing);
}
/// <summary> /// <summary>
/// Loads the image from the given stream. /// Loads the image from the given stream.
/// </summary> /// </summary>

159
src/ImageProcessorCore/ImageBase.cs

@ -6,13 +6,42 @@
namespace ImageProcessorCore namespace ImageProcessorCore
{ {
using System; using System;
using System.Runtime.InteropServices;
/// <summary> /// <summary>
/// The base class of all images. Encapsulates the basic properties and methods /// The base class of all images. Encapsulates the basic properties and methods
/// required to manipulate images. /// required to manipulate images.
/// </summary> /// </summary>
public abstract class ImageBase : IImageBase public abstract unsafe class ImageBase : IImageBase, IDisposable
{ {
/// <summary>
/// The position of the first pixel in the bitmap.
/// </summary>
private float* pixelsBase;
/// <summary>
/// The array of pixels.
/// </summary>
private float[] pixelsArray;
/// <summary>
/// Provides a way to access the pixels from unmanaged memory.
/// </summary>
private GCHandle pixelsHandle;
/// <summary>
/// A value indicating whether this instance of the given entity has been disposed.
/// </summary>
/// <value><see langword="true"/> if this instance has been disposed; otherwise, <see langword="false"/>.</value>
/// <remarks>
/// If the entity is disposed, it must not be disposed a second
/// time. The isDisposed field is set the first time the entity
/// is disposed. If the isDisposed field is true, then the Dispose()
/// method will not dispose again. This help not to prolong the entity's
/// life in the Garbage Collector.
/// </remarks>
protected bool IsDisposed;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ImageBase"/> class. /// Initializes a new instance of the <see cref="ImageBase"/> class.
/// </summary> /// </summary>
@ -23,12 +52,8 @@ namespace ImageProcessorCore
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ImageBase"/> class. /// Initializes a new instance of the <see cref="ImageBase"/> class.
/// </summary> /// </summary>
/// <param name="width"> /// <param name="width">The width of the image in pixels.</param>
/// The width of the image in pixels. /// <param name="height">The height of the image in pixels.</param>
/// </param>
/// <param name="height">
/// The height of the image in pixels.
/// </param>
/// <exception cref="ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// Thrown if either <paramref name="width"/> or <paramref name="height"/> are less than or equal to 0. /// Thrown if either <paramref name="width"/> or <paramref name="height"/> are less than or equal to 0.
/// </exception> /// </exception>
@ -40,7 +65,10 @@ namespace ImageProcessorCore
this.Width = width; this.Width = width;
this.Height = height; this.Height = height;
this.Pixels = new float[width * height * 4]; // Assign the pointer and pixels.
this.pixelsArray = new float[width * height * 4];
this.pixelsHandle = GCHandle.Alloc(this.pixelsArray, GCHandleType.Pinned);
this.pixelsBase = (float*)this.pixelsHandle.AddrOfPinnedObject().ToPointer();
} }
/// <summary> /// <summary>
@ -56,14 +84,22 @@ namespace ImageProcessorCore
{ {
Guard.NotNull(other, nameof(other), "Other image cannot be null."); Guard.NotNull(other, nameof(other), "Other image cannot be null.");
float[] pixels = other.Pixels;
this.Width = other.Width; this.Width = other.Width;
this.Height = other.Height; this.Height = other.Height;
this.Quality = other.Quality; this.Quality = other.Quality;
this.FrameDelay = other.FrameDelay; this.FrameDelay = other.FrameDelay;
this.Pixels = new float[pixels.Length];
Array.Copy(pixels, this.Pixels, pixels.Length); // Assign the pointer and copy the pixels.
this.pixelsArray = new float[this.Width * this.Height * 4];
this.pixelsHandle = GCHandle.Alloc(this.pixelsArray, GCHandleType.Pinned);
this.pixelsBase = (float*)this.pixelsHandle.AddrOfPinnedObject().ToPointer();
Array.Copy(other.pixelsArray, this.pixelsArray, other.pixelsArray.Length);
}
/// <inheritdoc/>
~ImageBase()
{
this.Dispose(false);
} }
/// <summary> /// <summary>
@ -76,45 +112,25 @@ namespace ImageProcessorCore
/// </summary> /// </summary>
public static int MaxHeight { get; set; } = int.MaxValue; public static int MaxHeight { get; set; } = int.MaxValue;
/// <summary> /// <inheritdoc/>
/// Gets the image pixels as byte array. public float[] Pixels => this.pixelsArray;
/// </summary>
/// <remarks>
/// The returned array has a length of Width * Height * 4 bytes
/// and stores the blue, the green, the red and the alpha value for
/// each pixel in this order.
/// </remarks>
public float[] Pixels { get; private set; }
/// <summary> /// <inheritdoc/>
/// Gets the width in pixels.
/// </summary>
public int Width { get; private set; } public int Width { get; private set; }
/// <summary> /// <inheritdoc/>
/// Gets the height in pixels.
/// </summary>
public int Height { get; private set; } public int Height { get; private set; }
/// <summary> /// <inheritdoc/>
/// Gets the pixel ratio made up of the width and height.
/// </summary>
public double PixelRatio => (double)this.Width / this.Height; public double PixelRatio => (double)this.Width / this.Height;
/// <summary> /// <inheritdoc/>
/// Gets the <see cref="Rectangle"/> representing the bounds of the image.
/// </summary>
public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height); public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height);
/// <inheritdoc/> /// <inheritdoc/>
public int Quality { get; set; } public int Quality { get; set; }
/// <summary> /// <inheritdoc/>
/// Gets or sets the frame delay for animated images.
/// If not 0, this field specifies the number of hundredths (1/100) of a second to
/// wait before continuing with the processing of the Data Stream.
/// The clock starts ticking immediately after the graphic is rendered.
/// </summary>
public int FrameDelay { get; set; } public int FrameDelay { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
@ -133,9 +149,7 @@ namespace ImageProcessorCore
throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height.");
} }
#endif #endif
return *((Color*)(this.pixelsBase + ((y * this.Width) + x) * 4));
int start = ((y * this.Width) + x) * 4;
return new Color(this.Pixels[start], this.Pixels[start + 1], this.Pixels[start + 2], this.Pixels[start + 3]);
} }
set set
@ -151,12 +165,7 @@ namespace ImageProcessorCore
throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height.");
} }
#endif #endif
int start = ((y * this.Width) + x) * 4; *(Color*)(this.pixelsBase + (((y * this.Width) + x) * 4)) = value;
this.Pixels[start + 0] = value.R;
this.Pixels[start + 1] = value.G;
this.Pixels[start + 2] = value.B;
this.Pixels[start + 3] = value.A;
} }
} }
@ -181,7 +190,10 @@ namespace ImageProcessorCore
#endif #endif
this.Width = width; this.Width = width;
this.Height = height; this.Height = height;
this.Pixels = pixels;
this.pixelsArray = pixels;
this.pixelsHandle = GCHandle.Alloc(this.pixelsArray, GCHandleType.Pinned);
this.pixelsBase = (float*)this.pixelsHandle.AddrOfPinnedObject().ToPointer();
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -205,9 +217,52 @@ namespace ImageProcessorCore
#endif #endif
this.Width = width; this.Width = width;
this.Height = height; this.Height = height;
float[] clonedPixels = new float[pixels.Length];
Array.Copy(pixels, clonedPixels, pixels.Length); // Assign the pointer and copy the pixels.
this.Pixels = clonedPixels; this.pixelsArray = new float[pixels.Length];
this.pixelsHandle = GCHandle.Alloc(this.pixelsArray, GCHandleType.Pinned);
this.pixelsBase = (float*)this.pixelsHandle.AddrOfPinnedObject().ToPointer();
Array.Copy(pixels, this.pixelsArray, pixels.Length);
}
/// <inheritdoc/>
public void Dispose()
{
this.Dispose(true);
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SuppressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">If true, the object gets disposed.</param>
protected virtual void Dispose(bool disposing)
{
if (this.IsDisposed)
{
return;
}
if (disposing)
{
// Dispose of any managed resources here.
this.pixelsArray = null;
}
if (this.pixelsHandle.IsAllocated)
{
this.pixelsHandle.Free();
this.pixelsBase = null;
}
// Note disposing is done.
this.IsDisposed = true;
} }
} }
} }

6
src/ImageProcessorCore/ImageExtensions.cs

@ -53,6 +53,7 @@ namespace ImageProcessorCore
/// <summary> /// <summary>
/// Applies the collection of processors to the image. /// Applies the collection of processors to the image.
/// <remarks>This method does not resize the target image.</remarks>
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="processors">Any processors to apply to the image.</param> /// <param name="processors">Any processors to apply to the image.</param>
@ -85,6 +86,9 @@ namespace ImageProcessorCore
/// <summary> /// <summary>
/// Applies the collection of processors to the image. /// Applies the collection of processors to the image.
/// <remarks>
/// This method is not chainable.
/// </remarks>
/// </summary> /// </summary>
/// <param name="source">The source image. Cannot be null.</param> /// <param name="source">The source image. Cannot be null.</param>
/// <param name="width">The target image width.</param> /// <param name="width">The target image width.</param>
@ -164,6 +168,8 @@ namespace ImageProcessorCore
} }
} }
// Clean up.
source.Dispose();
return transformedImage; return transformedImage;
} }
} }

30
src/ImageProcessorCore/ParallelImageProcessor.cs

@ -37,10 +37,7 @@ namespace ImageProcessorCore
{ {
try try
{ {
// We don't want to affect the original source pixels so we make clone here. this.OnApply(source, target, target.Bounds, sourceRectangle);
ImageFrame frame = source as ImageFrame;
Image temp = frame != null ? new Image(frame) : new Image((Image)source);
this.OnApply(temp, target, target.Bounds, sourceRectangle);
this.numRowsProcessed = 0; this.numRowsProcessed = 0;
this.totalRows = sourceRectangle.Height; this.totalRows = sourceRectangle.Height;
@ -54,13 +51,16 @@ namespace ImageProcessorCore
for (int p = 0; p < partitionCount; p++) for (int p = 0; p < partitionCount; p++)
{ {
int current = p; int current = p;
tasks[p] = Task.Run(() => tasks[p] = Task.Run(
() =>
{ {
int batchSize = sourceRectangle.Height / partitionCount; int batchSize = sourceRectangle.Height / partitionCount;
int yStart = sourceRectangle.Y + (current * batchSize); int yStart = sourceRectangle.Y + (current * batchSize);
int yEnd = current == partitionCount - 1 ? sourceRectangle.Bottom : yStart + batchSize; int yEnd = current == partitionCount - 1
? sourceRectangle.Bottom
: yStart + batchSize;
this.Apply(target, temp, target.Bounds, sourceRectangle, yStart, yEnd); this.Apply(target, source, target.Bounds, sourceRectangle, yStart, yEnd);
}); });
} }
@ -68,10 +68,10 @@ namespace ImageProcessorCore
} }
else else
{ {
this.Apply(target, temp, target.Bounds, sourceRectangle, sourceRectangle.Y, sourceRectangle.Bottom); this.Apply(target, source, target.Bounds, sourceRectangle, sourceRectangle.Y, sourceRectangle.Bottom);
} }
this.AfterApply(temp, target, target.Bounds, sourceRectangle); this.AfterApply(source, target, target.Bounds, sourceRectangle);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -93,10 +93,7 @@ namespace ImageProcessorCore
sourceRectangle = source.Bounds; sourceRectangle = source.Bounds;
} }
// We don't want to affect the original source pixels so we make clone here. this.OnApply(source, target, target.Bounds, sourceRectangle);
ImageFrame frame = source as ImageFrame;
Image temp = frame != null ? new Image(frame) : new Image((Image)source);
this.OnApply(temp, target, target.Bounds, sourceRectangle);
targetRectangle = target.Bounds; targetRectangle = target.Bounds;
this.numRowsProcessed = 0; this.numRowsProcessed = 0;
@ -117,7 +114,7 @@ namespace ImageProcessorCore
int yStart = current * batchSize; int yStart = current * batchSize;
int yEnd = current == partitionCount - 1 ? targetRectangle.Bottom : yStart + batchSize; int yEnd = current == partitionCount - 1 ? targetRectangle.Bottom : yStart + batchSize;
this.Apply(target, temp, targetRectangle, sourceRectangle, yStart, yEnd); this.Apply(target, source, targetRectangle, sourceRectangle, yStart, yEnd);
}); });
} }
@ -125,14 +122,13 @@ namespace ImageProcessorCore
} }
else else
{ {
this.Apply(target, temp, targetRectangle, sourceRectangle, targetRectangle.Y, targetRectangle.Bottom); this.Apply(target, source, targetRectangle, sourceRectangle, targetRectangle.Y, targetRectangle.Bottom);
} }
this.AfterApply(temp, target, target.Bounds, sourceRectangle); this.AfterApply(source, target, target.Bounds, sourceRectangle);
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex);
} }
} }

5
src/ImageProcessorCore/Samplers/EntropyCrop.cs

@ -42,8 +42,8 @@ namespace ImageProcessorCore.Samplers
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
{ {
ImageBase temp = new Image(source.Width, source.Height); using (ImageBase temp = new Image(source.Width, source.Height))
{
// Detect the edges. // Detect the edges.
new Sobel().Apply(temp, source, sourceRectangle); new Sobel().Apply(temp, source, sourceRectangle);
@ -57,6 +57,7 @@ namespace ImageProcessorCore.Samplers
target.SetPixels(rectangle.Width, rectangle.Height, new float[rectangle.Width * rectangle.Height * 4]); target.SetPixels(rectangle.Width, rectangle.Height, new float[rectangle.Width * rectangle.Height * 4]);
this.cropRectangle = rectangle; this.cropRectangle = rectangle;
} }
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)

11
src/ImageProcessorCore/Samplers/Resize.cs

@ -54,7 +54,6 @@ namespace ImageProcessorCore.Samplers
int sourceBottom = source.Bounds.Bottom; int sourceBottom = source.Bounds.Bottom;
int targetY = targetRectangle.Y; int targetY = targetRectangle.Y;
int targetBottom = targetRectangle.Bottom;
int startX = targetRectangle.X; int startX = targetRectangle.X;
int endX = targetRectangle.Right; int endX = targetRectangle.Right;
bool compand = this.Compand; bool compand = this.Compand;
@ -69,8 +68,6 @@ namespace ImageProcessorCore.Samplers
startY, startY,
endY, endY,
y => y =>
{
if (y >= targetY && y < targetBottom)
{ {
// Y coordinates of source points // Y coordinates of source points
int originY = (int)((y - targetY) * heightFactor); int originY = (int)((y - targetY) * heightFactor);
@ -82,8 +79,8 @@ namespace ImageProcessorCore.Samplers
target[x, y] = source[originX, originY]; target[x, y] = source[originX, originY];
} }
this.OnRowProcessed(); this.OnRowProcessed();
}
}); });
// Break out now. // Break out now.
@ -126,8 +123,6 @@ namespace ImageProcessorCore.Samplers
startY, startY,
endY, endY,
y => y =>
{
if (y >= targetY && y < targetBottom)
{ {
Weight[] verticalValues = this.VerticalWeights[y].Values; Weight[] verticalValues = this.VerticalWeights[y].Values;
@ -151,8 +146,8 @@ namespace ImageProcessorCore.Samplers
target[x, y] = destination; target[x, y] = destination;
} }
this.OnRowProcessed(); this.OnRowProcessed();
}
}); });
} }
@ -164,6 +159,8 @@ namespace ImageProcessorCore.Samplers
{ {
target.ClonePixels(target.Width, target.Height, source.Pixels); target.ClonePixels(target.Width, target.Height, source.Pixels);
} }
this.firstPass?.Dispose();
} }
} }
} }

5
tests/ImageProcessorCore.Tests/Formats/BitmapTests.cs

@ -27,8 +27,8 @@ namespace ImageProcessorCore.Tests
using (FileStream stream = File.OpenRead(file)) using (FileStream stream = File.OpenRead(file))
{ {
Stopwatch watch = Stopwatch.StartNew(); Stopwatch watch = Stopwatch.StartNew();
Image image = new Image(stream); using (Image image = new Image(stream))
{
string encodeFilename = "TestOutput/Encode/Bitmap/" + "24-" + Path.GetFileNameWithoutExtension(file) + ".bmp"; string encodeFilename = "TestOutput/Encode/Bitmap/" + "24-" + Path.GetFileNameWithoutExtension(file) + ".bmp";
using (FileStream output = File.OpenWrite(encodeFilename)) using (FileStream output = File.OpenWrite(encodeFilename))
@ -42,6 +42,7 @@ namespace ImageProcessorCore.Tests
{ {
image.Save(output, new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel32 }); image.Save(output, new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel32 });
} }
}
Trace.WriteLine($"{file} : {watch.ElapsedMilliseconds}ms"); Trace.WriteLine($"{file} : {watch.ElapsedMilliseconds}ms");
} }

34
tests/ImageProcessorCore.Tests/Formats/EncoderDecoderTests.cs

@ -30,14 +30,15 @@ namespace ImageProcessorCore.Tests
using (FileStream stream = File.OpenRead(file)) using (FileStream stream = File.OpenRead(file))
{ {
Stopwatch watch = Stopwatch.StartNew(); Stopwatch watch = Stopwatch.StartNew();
Image image = new Image(stream); using (Image image = new Image(stream))
{
string encodeFilename = "TestOutput/Encode/" + Path.GetFileName(file); string encodeFilename = "TestOutput/Encode/" + Path.GetFileName(file);
using (FileStream output = File.OpenWrite(encodeFilename)) using (FileStream output = File.OpenWrite(encodeFilename))
{ {
image.Save(output); image.Save(output);
} }
}
Trace.WriteLine($"{file} : {watch.ElapsedMilliseconds}ms"); Trace.WriteLine($"{file} : {watch.ElapsedMilliseconds}ms");
} }
@ -56,14 +57,17 @@ namespace ImageProcessorCore.Tests
{ {
using (FileStream stream = File.OpenRead(file)) using (FileStream stream = File.OpenRead(file))
{ {
Image image = new Image(stream); using (Image image = new Image(stream))
{
IQuantizer quantizer = new OctreeQuantizer(); IQuantizer quantizer = new OctreeQuantizer();
QuantizedImage quantizedImage = quantizer.Quantize(image, 256); QuantizedImage quantizedImage = quantizer.Quantize(image, 256);
using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Octree-{Path.GetFileName(file)}")) using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Octree-{Path.GetFileName(file)}"))
{ {
quantizedImage.ToImage().Save(output, image.CurrentImageFormat); using (Image qi = quantizedImage.ToImage())
{
qi.Save(output, image.CurrentImageFormat);
}
} }
quantizer = new WuQuantizer(); quantizer = new WuQuantizer();
@ -79,7 +83,11 @@ namespace ImageProcessorCore.Tests
using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Palette-{Path.GetFileName(file)}")) using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Palette-{Path.GetFileName(file)}"))
{ {
quantizedImage.ToImage().Save(output, image.CurrentImageFormat); using (Image qi = quantizedImage.ToImage())
{
qi.Save(output, image.CurrentImageFormat);
}
}
} }
} }
} }
@ -97,8 +105,8 @@ namespace ImageProcessorCore.Tests
{ {
using (FileStream stream = File.OpenRead(file)) using (FileStream stream = File.OpenRead(file))
{ {
Image image = new Image(stream); using (Image image = new Image(stream))
{
using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.gif")) using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.gif"))
{ {
image.SaveAsGif(output); image.SaveAsGif(output);
@ -121,6 +129,7 @@ namespace ImageProcessorCore.Tests
} }
} }
} }
}
[Fact] [Fact]
public void ImageShouldPreservePixelByteOrderWhenSerialized() public void ImageShouldPreservePixelByteOrderWhenSerialized()
@ -134,7 +143,8 @@ namespace ImageProcessorCore.Tests
{ {
using (FileStream stream = File.OpenRead(file)) using (FileStream stream = File.OpenRead(file))
{ {
Image image = new Image(stream); using (Image image = new Image(stream))
{
byte[] serialized; byte[] serialized;
using (MemoryStream memoryStream = new MemoryStream()) using (MemoryStream memoryStream = new MemoryStream())
{ {
@ -145,8 +155,8 @@ namespace ImageProcessorCore.Tests
using (MemoryStream memoryStream = new MemoryStream(serialized)) using (MemoryStream memoryStream = new MemoryStream(serialized))
{ {
Image image2 = new Image(memoryStream); using (Image image2 = new Image(memoryStream))
{
using (FileStream output = File.OpenWrite($"TestOutput/Serialized/{Path.GetFileName(file)}")) using (FileStream output = File.OpenWrite($"TestOutput/Serialized/{Path.GetFileName(file)}"))
{ {
image2.Save(output); image2.Save(output);
@ -157,3 +167,5 @@ namespace ImageProcessorCore.Tests
} }
} }
} }
}
}

5
tests/ImageProcessorCore.Tests/Formats/PngTests.cs

@ -25,8 +25,8 @@ namespace ImageProcessorCore.Tests
{ {
using (FileStream stream = File.OpenRead(file)) using (FileStream stream = File.OpenRead(file))
{ {
Image image = new Image(stream); using (Image image = new Image(stream))
{
using (FileStream output = File.OpenWrite($"TestOutput/Encode/Png/{Path.GetFileNameWithoutExtension(file)}.png")) using (FileStream output = File.OpenWrite($"TestOutput/Encode/Png/{Path.GetFileNameWithoutExtension(file)}.png"))
{ {
image.Quality = 256; image.Quality = 256;
@ -37,3 +37,4 @@ namespace ImageProcessorCore.Tests
} }
} }
} }
}

5
tests/ImageProcessorCore.Tests/Processors/Filters/FilterTests.cs

@ -70,7 +70,8 @@ namespace ImageProcessorCore.Tests
using (FileStream stream = File.OpenRead(file)) using (FileStream stream = File.OpenRead(file))
{ {
Stopwatch watch = Stopwatch.StartNew(); Stopwatch watch = Stopwatch.StartNew();
Image image = new Image(stream); using (Image image = new Image(stream))
{
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Filter/{Path.GetFileName(filename)}")) using (FileStream output = File.OpenWrite($"TestOutput/Filter/{Path.GetFileName(filename)}"))
{ {
@ -78,7 +79,7 @@ namespace ImageProcessorCore.Tests
image.Process(processor).Save(output); image.Process(processor).Save(output);
processor.OnProgress -= this.ProgressUpdate; processor.OnProgress -= this.ProgressUpdate;
} }
}
Trace.WriteLine($"{ name }: { watch.ElapsedMilliseconds}ms"); Trace.WriteLine($"{ name }: { watch.ElapsedMilliseconds}ms");
} }
} }

38
tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs

@ -60,14 +60,17 @@
{ {
Stopwatch watch = Stopwatch.StartNew(); Stopwatch watch = Stopwatch.StartNew();
Image image = new Image(stream); Image image = new Image(stream);
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Sample/{Path.GetFileName(filename)}")) using (FileStream output = File.OpenWrite($"TestOutput/Sample/{Path.GetFileName(filename)}"))
{ {
processor.OnProgress += this.ProgressUpdate; processor.OnProgress += this.ProgressUpdate;
// Not Chainable.
image = image.Process(image.Width / 2, image.Height / 2, processor); image = image.Process(image.Width / 2, image.Height / 2, processor);
image.Save(output); image.Save(output);
processor.OnProgress -= this.ProgressUpdate; processor.OnProgress -= this.ProgressUpdate;
} }
image.Dispose();
Trace.WriteLine($"{ name }: { watch.ElapsedMilliseconds}ms"); Trace.WriteLine($"{ name }: { watch.ElapsedMilliseconds}ms");
} }
@ -88,14 +91,15 @@
using (FileStream stream = File.OpenRead(file)) using (FileStream stream = File.OpenRead(file))
{ {
Stopwatch watch = Stopwatch.StartNew(); Stopwatch watch = Stopwatch.StartNew();
Image image = new Image(stream); using (Image image = new Image(stream))
{
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}")) using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
{ {
image.Resize(image.Width / 2, image.Height / 2, sampler, false, this.ProgressUpdate) image.Resize(image.Width / 2, image.Height / 2, sampler, false, this.ProgressUpdate)
.Save(output); .Save(output);
} }
}
Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms"); Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms");
} }
} }
@ -116,13 +120,15 @@
using (FileStream stream = File.OpenRead(file)) using (FileStream stream = File.OpenRead(file))
{ {
Stopwatch watch = Stopwatch.StartNew(); Stopwatch watch = Stopwatch.StartNew();
Image image = new Image(stream); using (Image image = new Image(stream))
{
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}")) using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
{ {
image.Resize(image.Width / 3, 0, new TriangleResampler(), false, this.ProgressUpdate) image.Resize(image.Width / 3, 0, new TriangleResampler(), false, this.ProgressUpdate)
.Save(output); .Save(output);
} }
}
Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms"); Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms");
} }
@ -144,13 +150,15 @@
using (FileStream stream = File.OpenRead(file)) using (FileStream stream = File.OpenRead(file))
{ {
Stopwatch watch = Stopwatch.StartNew(); Stopwatch watch = Stopwatch.StartNew();
Image image = new Image(stream); using (Image image = new Image(stream))
{
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}")) using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
{ {
image.Resize(0, image.Height / 3, new TriangleResampler(), false, this.ProgressUpdate) image.Resize(0, image.Height / 3, new TriangleResampler(), false, this.ProgressUpdate)
.Save(output); .Save(output);
} }
}
Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms"); Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms");
} }
@ -171,12 +179,14 @@
using (FileStream stream = File.OpenRead(file)) using (FileStream stream = File.OpenRead(file))
{ {
Stopwatch watch = Stopwatch.StartNew(); Stopwatch watch = Stopwatch.StartNew();
Image image = new Image(stream); using (Image image = new Image(stream))
string filename = Path.GetFileNameWithoutExtension(file) + "-" + rotateType + flipType + Path.GetExtension(file); {
string filename = Path.GetFileNameWithoutExtension(file) + "-" + rotateType + flipType
+ Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/RotateFlip/{filename}")) using (FileStream output = File.OpenWrite($"TestOutput/RotateFlip/{filename}"))
{ {
image.RotateFlip(rotateType, flipType, this.ProgressUpdate) image.RotateFlip(rotateType, flipType, this.ProgressUpdate).Save(output);
.Save(output); }
} }
Trace.WriteLine($"{rotateType + "-" + flipType}: {watch.ElapsedMilliseconds}ms"); Trace.WriteLine($"{rotateType + "-" + flipType}: {watch.ElapsedMilliseconds}ms");
@ -198,7 +208,8 @@
using (FileStream stream = File.OpenRead(file)) using (FileStream stream = File.OpenRead(file))
{ {
Stopwatch watch = Stopwatch.StartNew(); Stopwatch watch = Stopwatch.StartNew();
Image image = new Image(stream); using (Image image = new Image(stream))
{
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Rotate/{filename}")) using (FileStream output = File.OpenWrite($"TestOutput/Rotate/{filename}"))
{ {
@ -206,6 +217,7 @@
//.BackgroundColor(Color.Aqua) //.BackgroundColor(Color.Aqua)
.Save(output); .Save(output);
} }
}
Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms"); Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms");
} }
@ -224,7 +236,8 @@
{ {
using (FileStream stream = File.OpenRead(file)) using (FileStream stream = File.OpenRead(file))
{ {
Image image = new Image(stream); using (Image image = new Image(stream))
{
string filename = Path.GetFileNameWithoutExtension(file) + "-EntropyCrop" + Path.GetExtension(file); string filename = Path.GetFileNameWithoutExtension(file) + "-EntropyCrop" + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/EntropyCrop/{filename}")) using (FileStream output = File.OpenWrite($"TestOutput/EntropyCrop/{filename}"))
{ {
@ -233,6 +246,7 @@
} }
} }
} }
}
[Fact] [Fact]
public void ImageShouldCrop() public void ImageShouldCrop()
@ -246,7 +260,8 @@
{ {
using (FileStream stream = File.OpenRead(file)) using (FileStream stream = File.OpenRead(file))
{ {
Image image = new Image(stream); using (Image image = new Image(stream))
{
string filename = Path.GetFileNameWithoutExtension(file) + "-Crop" + Path.GetExtension(file); string filename = Path.GetFileNameWithoutExtension(file) + "-Crop" + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Crop/{filename}")) using (FileStream output = File.OpenWrite($"TestOutput/Crop/{filename}"))
{ {
@ -255,6 +270,7 @@
} }
} }
} }
}
[Theory] [Theory]
[InlineData(-2, 0)] [InlineData(-2, 0)]

Loading…
Cancel
Save