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 10 years ago
parent
commit
af5a1dcc59
  1. 36
      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. 40
      src/ImageProcessorCore/ParallelImageProcessor.cs
  11. 25
      src/ImageProcessorCore/Samplers/EntropyCrop.cs
  12. 63
      src/ImageProcessorCore/Samplers/Resize.cs
  13. 23
      tests/ImageProcessorCore.Tests/Formats/BitmapTests.cs
  14. 120
      tests/ImageProcessorCore.Tests/Formats/EncoderDecoderTests.cs
  15. 11
      tests/ImageProcessorCore.Tests/Formats/PngTests.cs
  16. 15
      tests/ImageProcessorCore.Tests/Processors/Filters/FilterTests.cs
  17. 88
      tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs

36
README.md

@ -50,7 +50,7 @@ git clone https://github.com/JimBobSquarePants/ImageProcessor
###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] Bmp (Read: 32bit, 24bit, 16 bit. Write: 32bit, 24bit 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
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.
@ -161,14 +161,12 @@ Here's an example of the code required to resize an image using the default Bicu
```csharp
using (FileStream stream = File.OpenRead("foo.jpg"))
using (Image image = new Image(stream))
using (FileStream output = File.OpenWrite("bar.jpg"))
{
Image image = new Image(stream);
using (FileStream output = File.OpenWrite("bar.jpg"))
{
image.Resize(image.Width / 2, image.Height / 2)
.Greyscale()
.Save(output);
}
image.Resize(image.Width / 2, image.Height / 2)
.Greyscale()
.Save(output);
}
```
@ -176,19 +174,17 @@ It will also be possible to pass collections of processors as params to manipula
```csharp
using (FileStream stream = File.OpenRead("foo.jpg"))
using (Image image = new Image(stream))
using (FileStream output = File.OpenWrite("bar.jpg"))
{
Image image = new Image(stream);
using (FileStream output = File.OpenWrite("bar.jpg"))
List<IImageProcessor> processors = new List<IImageProcessor>()
{
List<IImageProcessor> processors = new List<IImageProcessor>()
{
new GuassianBlur(5),
new Sobel { Greyscale = true }
};
image.Process(processors.ToArray())
.Save(output);
}
new GuassianBlur(5),
new Sobel { Greyscale = true }
};
image.Process(processors.ToArray())
.Save(output);
}
```
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>
/// Initializes a new instance of the <see cref="Blend"/> class.
/// </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>
public Blend(ImageBase image, int alpha = 100)
{
@ -69,6 +72,7 @@ namespace ImageProcessorCore.Filters
target[x, y] = color;
}
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.
/// </summary>
/// <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="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <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.
/// </summary>
/// <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="rectangle">
/// 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>
/// Gets or sets the quality of output for images.
/// </summary>
public int Quality { get; set; } = int.MaxValue;
public int Quality { get; set; }
/// <inheritdoc/>
public string MimeType => "image/png";

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

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

2
src/ImageProcessorCore/IImageBase.cs

@ -17,7 +17,7 @@ namespace ImageProcessorCore
/// </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
/// and stores the red, the green, the blue, and the alpha value for
/// each pixel in this order.
/// </remarks>
float[] Pixels { get; }

26
src/ImageProcessorCore/Image.cs

@ -45,7 +45,7 @@ namespace ImageProcessorCore
new BmpFormat(),
new JpegFormat(),
new PngFormat(),
new GifFormat(),
new GifFormat()
});
/// <summary>
@ -223,6 +223,30 @@ namespace ImageProcessorCore
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>
/// Loads the image from the given stream.
/// </summary>

159
src/ImageProcessorCore/ImageBase.cs

@ -6,13 +6,42 @@
namespace ImageProcessorCore
{
using System;
using System.Runtime.InteropServices;
/// <summary>
/// The base class of all images. Encapsulates the basic properties and methods
/// required to manipulate images.
/// </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>
/// Initializes a new instance of the <see cref="ImageBase"/> class.
/// </summary>
@ -23,12 +52,8 @@ namespace ImageProcessorCore
/// <summary>
/// Initializes a new instance of the <see cref="ImageBase"/> class.
/// </summary>
/// <param name="width">
/// The width of the image in pixels.
/// </param>
/// <param name="height">
/// The height of the image in pixels.
/// </param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if either <paramref name="width"/> or <paramref name="height"/> are less than or equal to 0.
/// </exception>
@ -40,7 +65,10 @@ namespace ImageProcessorCore
this.Width = width;
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>
@ -56,14 +84,22 @@ namespace ImageProcessorCore
{
Guard.NotNull(other, nameof(other), "Other image cannot be null.");
float[] pixels = other.Pixels;
this.Width = other.Width;
this.Height = other.Height;
this.Quality = other.Quality;
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>
@ -76,45 +112,25 @@ namespace ImageProcessorCore
/// </summary>
public static int MaxHeight { get; set; } = int.MaxValue;
/// <summary>
/// Gets the image pixels as byte array.
/// </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; }
/// <inheritdoc/>
public float[] Pixels => this.pixelsArray;
/// <summary>
/// Gets the width in pixels.
/// </summary>
/// <inheritdoc/>
public int Width { get; private set; }
/// <summary>
/// Gets the height in pixels.
/// </summary>
/// <inheritdoc/>
public int Height { get; private set; }
/// <summary>
/// Gets the pixel ratio made up of the width and height.
/// </summary>
/// <inheritdoc/>
public double PixelRatio => (double)this.Width / this.Height;
/// <summary>
/// Gets the <see cref="Rectangle"/> representing the bounds of the image.
/// </summary>
/// <inheritdoc/>
public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height);
/// <inheritdoc/>
public int Quality { get; set; }
/// <summary>
/// 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>
/// <inheritdoc/>
public int FrameDelay { get; set; }
/// <inheritdoc/>
@ -133,9 +149,7 @@ namespace ImageProcessorCore
throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height.");
}
#endif
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]);
return *((Color*)(this.pixelsBase + ((y * this.Width) + x) * 4));
}
set
@ -151,12 +165,7 @@ namespace ImageProcessorCore
throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height.");
}
#endif
int start = ((y * this.Width) + x) * 4;
this.Pixels[start + 0] = value.R;
this.Pixels[start + 1] = value.G;
this.Pixels[start + 2] = value.B;
this.Pixels[start + 3] = value.A;
*(Color*)(this.pixelsBase + (((y * this.Width) + x) * 4)) = value;
}
}
@ -181,7 +190,10 @@ namespace ImageProcessorCore
#endif
this.Width = width;
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/>
@ -205,9 +217,52 @@ namespace ImageProcessorCore
#endif
this.Width = width;
this.Height = height;
float[] clonedPixels = new float[pixels.Length];
Array.Copy(pixels, clonedPixels, pixels.Length);
this.Pixels = clonedPixels;
// Assign the pointer and copy the pixels.
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>
/// Applies the collection of processors to the image.
/// <remarks>This method does not resize the target image.</remarks>
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="processors">Any processors to apply to the image.</param>
@ -85,6 +86,9 @@ namespace ImageProcessorCore
/// <summary>
/// Applies the collection of processors to the image.
/// <remarks>
/// This method is not chainable.
/// </remarks>
/// </summary>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="width">The target image width.</param>
@ -164,6 +168,8 @@ namespace ImageProcessorCore
}
}
// Clean up.
source.Dispose();
return transformedImage;
}
}

40
src/ImageProcessorCore/ParallelImageProcessor.cs

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

25
src/ImageProcessorCore/Samplers/EntropyCrop.cs

@ -42,20 +42,21 @@ namespace ImageProcessorCore.Samplers
/// <inheritdoc/>
protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
{
ImageBase temp = new Image(source.Width, source.Height);
// Detect the edges.
new Sobel().Apply(temp, source, sourceRectangle);
using (ImageBase temp = new Image(source.Width, source.Height))
{
// Detect the edges.
new Sobel().Apply(temp, source, sourceRectangle);
// Apply threshold binarization filter.
new Threshold(.5f).Apply(temp, temp, sourceRectangle);
// Apply threshold binarization filter.
new Threshold(.5f).Apply(temp, temp, sourceRectangle);
// Search for the first white pixels
Rectangle rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0);
// Search for the first white pixels
Rectangle rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0);
// Reset the target pixel to the correct size.
target.SetPixels(rectangle.Width, rectangle.Height, new float[rectangle.Width * rectangle.Height * 4]);
this.cropRectangle = rectangle;
// Reset the target pixel to the correct size.
target.SetPixels(rectangle.Width, rectangle.Height, new float[rectangle.Width * rectangle.Height * 4]);
this.cropRectangle = rectangle;
}
}
/// <inheritdoc/>
@ -73,7 +74,7 @@ namespace ImageProcessorCore.Samplers
int endX = this.cropRectangle.Width;
int maxX = this.cropRectangle.Right - 1;
int maxY = this.cropRectangle.Bottom - 1;
Parallel.For(
startY,
endY,

63
src/ImageProcessorCore/Samplers/Resize.cs

@ -54,7 +54,6 @@ namespace ImageProcessorCore.Samplers
int sourceBottom = source.Bounds.Bottom;
int targetY = targetRectangle.Y;
int targetBottom = targetRectangle.Bottom;
int startX = targetRectangle.X;
int endX = targetRectangle.Right;
bool compand = this.Compand;
@ -70,20 +69,18 @@ namespace ImageProcessorCore.Samplers
endY,
y =>
{
if (y >= targetY && y < targetBottom)
{
// Y coordinates of source points
int originY = (int)((y - targetY) * heightFactor);
// Y coordinates of source points
int originY = (int)((y - targetY) * heightFactor);
for (int x = startX; x < endX; x++)
{
// X coordinates of source points
int originX = (int)((x - startX) * widthFactor);
for (int x = startX; x < endX; x++)
{
// X coordinates of source points
int originX = (int)((x - startX) * widthFactor);
target[x, y] = source[originX, originY];
}
this.OnRowProcessed();
target[x, y] = source[originX, originY];
}
this.OnRowProcessed();
});
// Break out now.
@ -127,32 +124,30 @@ namespace ImageProcessorCore.Samplers
endY,
y =>
{
if (y >= targetY && y < targetBottom)
Weight[] verticalValues = this.VerticalWeights[y].Values;
for (int x = startX; x < endX; x++)
{
Weight[] verticalValues = this.VerticalWeights[y].Values;
// Destination color components
Color destination = new Color();
for (int x = startX; x < endX; x++)
foreach (Weight yw in verticalValues)
{
// Destination color components
Color destination = new Color();
foreach (Weight yw in verticalValues)
{
int originY = yw.Index;
int originX = x;
Color sourceColor = compand ? Color.Expand(this.firstPass[originX, originY]) : this.firstPass[originX, originY];
destination += sourceColor * yw.Value;
}
if (compand)
{
destination = Color.Compress(destination);
}
target[x, y] = destination;
int originY = yw.Index;
int originX = x;
Color sourceColor = compand ? Color.Expand(this.firstPass[originX, originY]) : this.firstPass[originX, originY];
destination += sourceColor * yw.Value;
}
this.OnRowProcessed();
if (compand)
{
destination = Color.Compress(destination);
}
target[x, y] = destination;
}
this.OnRowProcessed();
});
}
@ -164,6 +159,8 @@ namespace ImageProcessorCore.Samplers
{
target.ClonePixels(target.Width, target.Height, source.Pixels);
}
this.firstPass?.Dispose();
}
}
}

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

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

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

@ -30,13 +30,14 @@ namespace ImageProcessorCore.Tests
using (FileStream stream = File.OpenRead(file))
{
Stopwatch watch = Stopwatch.StartNew();
Image image = new Image(stream);
string encodeFilename = "TestOutput/Encode/" + Path.GetFileName(file);
using (FileStream output = File.OpenWrite(encodeFilename))
using (Image image = new Image(stream))
{
image.Save(output);
string encodeFilename = "TestOutput/Encode/" + Path.GetFileName(file);
using (FileStream output = File.OpenWrite(encodeFilename))
{
image.Save(output);
}
}
Trace.WriteLine($"{file} : {watch.ElapsedMilliseconds}ms");
@ -56,30 +57,37 @@ namespace ImageProcessorCore.Tests
{
using (FileStream stream = File.OpenRead(file))
{
Image image = new Image(stream);
IQuantizer quantizer = new OctreeQuantizer();
QuantizedImage quantizedImage = quantizer.Quantize(image, 256);
using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Octree-{Path.GetFileName(file)}"))
using (Image image = new Image(stream))
{
quantizedImage.ToImage().Save(output, image.CurrentImageFormat);
}
IQuantizer quantizer = new OctreeQuantizer();
QuantizedImage quantizedImage = quantizer.Quantize(image, 256);
quantizer = new WuQuantizer();
quantizedImage = quantizer.Quantize(image, 256);
using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Octree-{Path.GetFileName(file)}"))
{
using (Image qi = quantizedImage.ToImage())
{
qi.Save(output, image.CurrentImageFormat);
}
}
using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Wu-{Path.GetFileName(file)}"))
{
quantizedImage.ToImage().Save(output, image.CurrentImageFormat);
}
quantizer = new WuQuantizer();
quantizedImage = quantizer.Quantize(image, 256);
quantizer = new PaletteQuantizer();
quantizedImage = quantizer.Quantize(image, 256);
using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Wu-{Path.GetFileName(file)}"))
{
quantizedImage.ToImage().Save(output, image.CurrentImageFormat);
}
using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Palette-{Path.GetFileName(file)}"))
{
quantizedImage.ToImage().Save(output, image.CurrentImageFormat);
quantizer = new PaletteQuantizer();
quantizedImage = quantizer.Quantize(image, 256);
using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Palette-{Path.GetFileName(file)}"))
{
using (Image qi = quantizedImage.ToImage())
{
qi.Save(output, image.CurrentImageFormat);
}
}
}
}
}
@ -97,26 +105,27 @@ namespace ImageProcessorCore.Tests
{
using (FileStream stream = File.OpenRead(file))
{
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.gif"))
using (Image image = new Image(stream))
{
image.SaveAsGif(output);
}
using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.gif"))
{
image.SaveAsGif(output);
}
using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.bmp"))
{
image.SaveAsBmp(output);
}
using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.bmp"))
{
image.SaveAsBmp(output);
}
using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.jpg"))
{
image.SaveAsJpeg(output);
}
using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.jpg"))
{
image.SaveAsJpeg(output);
}
using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.png"))
{
image.SaveAsPng(output);
using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.png"))
{
image.SaveAsPng(output);
}
}
}
}
@ -134,22 +143,25 @@ namespace ImageProcessorCore.Tests
{
using (FileStream stream = File.OpenRead(file))
{
Image image = new Image(stream);
byte[] serialized;
using (MemoryStream memoryStream = new MemoryStream())
using (Image image = new Image(stream))
{
image.Save(memoryStream);
memoryStream.Flush();
serialized = memoryStream.ToArray();
}
using (MemoryStream memoryStream = new MemoryStream(serialized))
{
Image image2 = new Image(memoryStream);
byte[] serialized;
using (MemoryStream memoryStream = new MemoryStream())
{
image.Save(memoryStream);
memoryStream.Flush();
serialized = memoryStream.ToArray();
}
using (FileStream output = File.OpenWrite($"TestOutput/Serialized/{Path.GetFileName(file)}"))
using (MemoryStream memoryStream = new MemoryStream(serialized))
{
image2.Save(output);
using (Image image2 = new Image(memoryStream))
{
using (FileStream output = File.OpenWrite($"TestOutput/Serialized/{Path.GetFileName(file)}"))
{
image2.Save(output);
}
}
}
}
}

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

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

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

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

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

@ -60,14 +60,17 @@
{
Stopwatch watch = Stopwatch.StartNew();
Image image = new Image(stream);
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;
// Not Chainable.
image = image.Process(image.Width / 2, image.Height / 2, processor);
image.Save(output);
processor.OnProgress -= this.ProgressUpdate;
}
image.Dispose();
Trace.WriteLine($"{ name }: { watch.ElapsedMilliseconds}ms");
}
@ -88,14 +91,15 @@
using (FileStream stream = File.OpenRead(file))
{
Stopwatch watch = Stopwatch.StartNew();
Image image = new Image(stream);
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
using (Image image = new Image(stream))
{
image.Resize(image.Width / 2, image.Height / 2, sampler, false, this.ProgressUpdate)
.Save(output);
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
{
image.Resize(image.Width / 2, image.Height / 2, sampler, false, this.ProgressUpdate)
.Save(output);
}
}
Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms");
}
}
@ -116,12 +120,14 @@
using (FileStream stream = File.OpenRead(file))
{
Stopwatch watch = Stopwatch.StartNew();
Image image = new Image(stream);
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
using (Image image = new Image(stream))
{
image.Resize(image.Width / 3, 0, new TriangleResampler(), false, this.ProgressUpdate)
.Save(output);
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
{
image.Resize(image.Width / 3, 0, new TriangleResampler(), false, this.ProgressUpdate)
.Save(output);
}
}
Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms");
@ -144,12 +150,14 @@
using (FileStream stream = File.OpenRead(file))
{
Stopwatch watch = Stopwatch.StartNew();
Image image = new Image(stream);
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
using (Image image = new Image(stream))
{
image.Resize(0, image.Height / 3, new TriangleResampler(), false, this.ProgressUpdate)
.Save(output);
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
{
image.Resize(0, image.Height / 3, new TriangleResampler(), false, this.ProgressUpdate)
.Save(output);
}
}
Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms");
@ -171,12 +179,14 @@
using (FileStream stream = File.OpenRead(file))
{
Stopwatch watch = Stopwatch.StartNew();
Image image = new Image(stream);
string filename = Path.GetFileNameWithoutExtension(file) + "-" + rotateType + flipType + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/RotateFlip/{filename}"))
using (Image image = new Image(stream))
{
image.RotateFlip(rotateType, flipType, this.ProgressUpdate)
.Save(output);
string filename = Path.GetFileNameWithoutExtension(file) + "-" + rotateType + flipType
+ Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/RotateFlip/{filename}"))
{
image.RotateFlip(rotateType, flipType, this.ProgressUpdate).Save(output);
}
}
Trace.WriteLine($"{rotateType + "-" + flipType}: {watch.ElapsedMilliseconds}ms");
@ -198,13 +208,15 @@
using (FileStream stream = File.OpenRead(file))
{
Stopwatch watch = Stopwatch.StartNew();
Image image = new Image(stream);
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Rotate/{filename}"))
using (Image image = new Image(stream))
{
image.Rotate(45, sampler, false, this.ProgressUpdate)
//.BackgroundColor(Color.Aqua)
.Save(output);
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Rotate/{filename}"))
{
image.Rotate(45, sampler, false, this.ProgressUpdate)
//.BackgroundColor(Color.Aqua)
.Save(output);
}
}
Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms");
@ -224,11 +236,13 @@
{
using (FileStream stream = File.OpenRead(file))
{
Image image = new Image(stream);
string filename = Path.GetFileNameWithoutExtension(file) + "-EntropyCrop" + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/EntropyCrop/{filename}"))
using (Image image = new Image(stream))
{
image.EntropyCrop(.5f, this.ProgressUpdate).Save(output);
string filename = Path.GetFileNameWithoutExtension(file) + "-EntropyCrop" + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/EntropyCrop/{filename}"))
{
image.EntropyCrop(.5f, this.ProgressUpdate).Save(output);
}
}
}
}
@ -246,11 +260,13 @@
{
using (FileStream stream = File.OpenRead(file))
{
Image image = new Image(stream);
string filename = Path.GetFileNameWithoutExtension(file) + "-Crop" + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Crop/{filename}"))
using (Image image = new Image(stream))
{
image.Crop(image.Width / 2, image.Height / 2, this.ProgressUpdate).Save(output);
string filename = Path.GetFileNameWithoutExtension(file) + "-Crop" + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Crop/{filename}"))
{
image.Crop(image.Width / 2, image.Height / 2, this.ProgressUpdate).Save(output);
}
}
}
}

Loading…
Cancel
Save