diff --git a/README.md b/README.md index 4f3f9ac02..d00b693dd 100644 --- a/README.md +++ b/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 processors = new List() { - List processors = new List() - { - 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: diff --git a/src/ImageProcessorCore/Filters/Blend.cs b/src/ImageProcessorCore/Filters/Blend.cs index 9fffe5191..8c36c94f2 100644 --- a/src/ImageProcessorCore/Filters/Blend.cs +++ b/src/ImageProcessorCore/Filters/Blend.cs @@ -20,7 +20,10 @@ namespace ImageProcessorCore.Filters /// /// Initializes a new instance of the class. /// - /// The image to blend. + /// + /// The image to blend with the currently processing image. + /// Disposal of this image is the responsibility of the developer. + /// /// The opacity of the image to blend. Between 0 and 100. public Blend(ImageBase image, int alpha = 100) { @@ -69,6 +72,7 @@ namespace ImageProcessorCore.Filters target[x, y] = color; } + this.OnRowProcessed(); } }); diff --git a/src/ImageProcessorCore/Filters/ImageFilterExtensions.cs b/src/ImageProcessorCore/Filters/ImageFilterExtensions.cs index 5bdb72e52..b0baffa2c 100644 --- a/src/ImageProcessorCore/Filters/ImageFilterExtensions.cs +++ b/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. /// /// The image this method extends. - /// The image to blend with the currently processing image. + /// + /// The image to blend with the currently processing image. + /// Disposal of this image is the responsibility of the developer. + /// /// The opacity of the image image to blend. Must be between 0 and 100. /// A delegate which is called as progress is made processing the image. /// The . @@ -86,7 +89,10 @@ namespace ImageProcessorCore.Filters /// Combines the given image together with the current one by blending their pixels. /// /// The image this method extends. - /// The image to blend with the currently processing image. + /// + /// The image to blend with the currently processing image. + /// Disposal of this image is the responsibility of the developer. + /// /// The opacity of the image image to blend. Must be between 0 and 100. /// /// The structure that specifies the portion of the image object to alter. diff --git a/src/ImageProcessorCore/Formats/Png/PngEncoder.cs b/src/ImageProcessorCore/Formats/Png/PngEncoder.cs index 170144ca7..18deb1046 100644 --- a/src/ImageProcessorCore/Formats/Png/PngEncoder.cs +++ b/src/ImageProcessorCore/Formats/Png/PngEncoder.cs @@ -18,7 +18,7 @@ namespace ImageProcessorCore.Formats /// /// Gets or sets the quality of output for images. /// - public int Quality { get; set; } = int.MaxValue; + public int Quality { get; set; } /// public string MimeType => "image/png"; diff --git a/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs b/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs index 89e3883b3..b2fdab051 100644 --- a/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs @@ -35,7 +35,7 @@ namespace ImageProcessorCore.Formats /// /// Gets or sets the quality of output for images. /// - public int Quality { get; set; } = int.MaxValue; + public int Quality { get; set; } /// /// The compression level 1-9. diff --git a/src/ImageProcessorCore/IImageBase.cs b/src/ImageProcessorCore/IImageBase.cs index 569dcecda..c12406d9a 100644 --- a/src/ImageProcessorCore/IImageBase.cs +++ b/src/ImageProcessorCore/IImageBase.cs @@ -17,7 +17,7 @@ namespace ImageProcessorCore /// /// /// 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. /// float[] Pixels { get; } diff --git a/src/ImageProcessorCore/Image.cs b/src/ImageProcessorCore/Image.cs index 0edbfb756..745b617b3 100644 --- a/src/ImageProcessorCore/Image.cs +++ b/src/ImageProcessorCore/Image.cs @@ -45,7 +45,7 @@ namespace ImageProcessorCore new BmpFormat(), new JpegFormat(), new PngFormat(), - new GifFormat(), + new GifFormat() }); /// @@ -223,6 +223,30 @@ namespace ImageProcessorCore encoder.Encode(this, stream); } + /// + 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); + } + /// /// Loads the image from the given stream. /// diff --git a/src/ImageProcessorCore/ImageBase.cs b/src/ImageProcessorCore/ImageBase.cs index 09a0ad741..4d4541645 100644 --- a/src/ImageProcessorCore/ImageBase.cs +++ b/src/ImageProcessorCore/ImageBase.cs @@ -6,13 +6,42 @@ namespace ImageProcessorCore { using System; + using System.Runtime.InteropServices; /// /// The base class of all images. Encapsulates the basic properties and methods /// required to manipulate images. /// - public abstract class ImageBase : IImageBase + public abstract unsafe class ImageBase : IImageBase, IDisposable { + /// + /// The position of the first pixel in the bitmap. + /// + private float* pixelsBase; + + /// + /// The array of pixels. + /// + private float[] pixelsArray; + + /// + /// Provides a way to access the pixels from unmanaged memory. + /// + private GCHandle pixelsHandle; + + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// 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. + /// + protected bool IsDisposed; + /// /// Initializes a new instance of the class. /// @@ -23,12 +52,8 @@ namespace ImageProcessorCore /// /// Initializes a new instance of the class. /// - /// - /// The width of the image in pixels. - /// - /// - /// The height of the image in pixels. - /// + /// The width of the image in pixels. + /// The height of the image in pixels. /// /// Thrown if either or are less than or equal to 0. /// @@ -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(); } /// @@ -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); + } + + /// + ~ImageBase() + { + this.Dispose(false); } /// @@ -76,45 +112,25 @@ namespace ImageProcessorCore /// public static int MaxHeight { get; set; } = int.MaxValue; - /// - /// Gets the image pixels as byte array. - /// - /// - /// 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. - /// - public float[] Pixels { get; private set; } + /// + public float[] Pixels => this.pixelsArray; - /// - /// Gets the width in pixels. - /// + /// public int Width { get; private set; } - /// - /// Gets the height in pixels. - /// + /// public int Height { get; private set; } - /// - /// Gets the pixel ratio made up of the width and height. - /// + /// public double PixelRatio => (double)this.Width / this.Height; - /// - /// Gets the representing the bounds of the image. - /// + /// public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height); /// public int Quality { get; set; } - /// - /// 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. - /// + /// public int FrameDelay { get; set; } /// @@ -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(); } /// @@ -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); + } + + /// + 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); + } + + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + /// If true, the object gets disposed. + 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; } } } diff --git a/src/ImageProcessorCore/ImageExtensions.cs b/src/ImageProcessorCore/ImageExtensions.cs index 6a6d0e3f0..509500349 100644 --- a/src/ImageProcessorCore/ImageExtensions.cs +++ b/src/ImageProcessorCore/ImageExtensions.cs @@ -53,6 +53,7 @@ namespace ImageProcessorCore /// /// Applies the collection of processors to the image. + /// This method does not resize the target image. /// /// The image this method extends. /// Any processors to apply to the image. @@ -85,6 +86,9 @@ namespace ImageProcessorCore /// /// Applies the collection of processors to the image. + /// + /// This method is not chainable. + /// /// /// The source image. Cannot be null. /// The target image width. @@ -164,6 +168,8 @@ namespace ImageProcessorCore } } + // Clean up. + source.Dispose(); return transformedImage; } } diff --git a/src/ImageProcessorCore/ParallelImageProcessor.cs b/src/ImageProcessorCore/ParallelImageProcessor.cs index c0de64800..297f796f8 100644 --- a/src/ImageProcessorCore/ParallelImageProcessor.cs +++ b/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); } } diff --git a/src/ImageProcessorCore/Samplers/EntropyCrop.cs b/src/ImageProcessorCore/Samplers/EntropyCrop.cs index 928518d53..32f3b384b 100644 --- a/src/ImageProcessorCore/Samplers/EntropyCrop.cs +++ b/src/ImageProcessorCore/Samplers/EntropyCrop.cs @@ -42,20 +42,21 @@ namespace ImageProcessorCore.Samplers /// 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; + } } /// @@ -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, diff --git a/src/ImageProcessorCore/Samplers/Resize.cs b/src/ImageProcessorCore/Samplers/Resize.cs index ce4d513ac..03aad0ec8 100644 --- a/src/ImageProcessorCore/Samplers/Resize.cs +++ b/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(); } } } \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Formats/BitmapTests.cs b/tests/ImageProcessorCore.Tests/Formats/BitmapTests.cs index 92589ee88..68b4ea2b0 100644 --- a/tests/ImageProcessorCore.Tests/Formats/BitmapTests.cs +++ b/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"); diff --git a/tests/ImageProcessorCore.Tests/Formats/EncoderDecoderTests.cs b/tests/ImageProcessorCore.Tests/Formats/EncoderDecoderTests.cs index 0b19d1b1a..116399ccf 100644 --- a/tests/ImageProcessorCore.Tests/Formats/EncoderDecoderTests.cs +++ b/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); + } + } } } } diff --git a/tests/ImageProcessorCore.Tests/Formats/PngTests.cs b/tests/ImageProcessorCore.Tests/Formats/PngTests.cs index 6c7029c85..00e90b794 100644 --- a/tests/ImageProcessorCore.Tests/Formats/PngTests.cs +++ b/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()); + } } } } diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/FilterTests.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/FilterTests.cs index 4e3fd103d..db48b7dd5 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Filters/FilterTests.cs +++ b/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"); } } diff --git a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs index 925d1340b..2e9eef9cf 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs +++ b/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); + } } } }