From d12eedb9fe87e97e6572ce909fd95d314d36dc41 Mon Sep 17 00:00:00 2001 From: James South Date: Thu, 7 Apr 2016 00:48:36 +1000 Subject: [PATCH 1/4] Image now IDisposable This appears to have injected some voom into things. Not benchmarked though and needs double checking for memory leaks. touch #360 Former-commit-id: 0707a36781e3a5fa4d273d933d1bd1b04b022092 Former-commit-id: f7ec165c3d10d9e1e35b8eec3545ca75cf302cf9 Former-commit-id: d5bc57ce1cde9f1486a954d3453040514dc4d629 --- src/ImageProcessorCore/Filters/Blend.cs | 6 + .../Formats/Png/PngEncoder.cs | 2 +- .../Formats/Png/PngEncoderCore.cs | 2 +- src/ImageProcessorCore/IImageBase.cs | 2 +- src/ImageProcessorCore/Image.cs | 26 ++- src/ImageProcessorCore/ImageBase.cs | 156 ++++++++++++------ src/ImageProcessorCore/ImageExtensions.cs | 1 + .../ParallelImageProcessor.cs | 40 ++--- .../Samplers/EntropyCrop.cs | 25 +-- src/ImageProcessorCore/Samplers/Resize.cs | 2 + .../ImageProcessorCore.Tests/FileTestBase.cs | 2 +- .../Formats/BitmapTests.cs | 23 +-- .../Formats/EncoderDecoderTests.cs | 120 ++++++++------ .../Formats/PngTests.cs | 11 +- .../Processors/Filters/FilterTests.cs | 15 +- .../Processors/Samplers/SamplerTests.cs | 87 ++++++---- 16 files changed, 316 insertions(+), 204 deletions(-) diff --git a/src/ImageProcessorCore/Filters/Blend.cs b/src/ImageProcessorCore/Filters/Blend.cs index 9fffe5191..e48a26c52 100644 --- a/src/ImageProcessorCore/Filters/Blend.cs +++ b/src/ImageProcessorCore/Filters/Blend.cs @@ -73,5 +73,11 @@ namespace ImageProcessorCore.Filters } }); } + + /// + protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) + { + this.toBlend?.Dispose(); + } } } 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..0065d61bc 100644 --- a/src/ImageProcessorCore/ImageBase.cs +++ b/src/ImageProcessorCore/ImageBase.cs @@ -6,13 +6,39 @@ 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; + + 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 +49,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 +62,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 +81,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 +109,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 +146,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 +162,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 +187,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 +214,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..533d33701 100644 --- a/src/ImageProcessorCore/ImageExtensions.cs +++ b/src/ImageProcessorCore/ImageExtensions.cs @@ -164,6 +164,7 @@ namespace ImageProcessorCore } } + 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..275f72454 100644 --- a/src/ImageProcessorCore/Samplers/Resize.cs +++ b/src/ImageProcessorCore/Samplers/Resize.cs @@ -164,6 +164,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/FileTestBase.cs b/tests/ImageProcessorCore.Tests/FileTestBase.cs index d5471f429..6c5079cd1 100644 --- a/tests/ImageProcessorCore.Tests/FileTestBase.cs +++ b/tests/ImageProcessorCore.Tests/FileTestBase.cs @@ -29,7 +29,7 @@ namespace ImageProcessorCore.Tests //"TestImages/Formats/Png/indexed.png", // Perf: Enable for local testing only "TestImages/Formats/Png/splash.png", "TestImages/Formats/Gif/rings.gif", - //"TestImages/Formats/Gif/giphy.gif" // Perf: Enable for local testing only + "TestImages/Formats/Gif/giphy.gif" // Perf: Enable for local testing only }; protected void ProgressUpdate(object sender, ProgressEventArgs e) 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..4aaffb328 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs @@ -60,14 +60,16 @@ { 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; 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 +90,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 +119,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 +149,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 +178,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 +207,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 +235,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 +259,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); + } } } } From 57637ab14ddf43b11f6fac397277f798ca7f4fff Mon Sep 17 00:00:00 2001 From: James South Date: Thu, 7 Apr 2016 00:53:24 +1000 Subject: [PATCH 2/4] Update code samples Former-commit-id: f455e4ad28147a0621cf97d3470963fc6dcac41e Former-commit-id: b980688edacfc45f78dfbad54f1cc2ebd31f5558 Former-commit-id: a3456a554be3af09bf121be2633a280221018dfe --- README.md | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 4f3f9ac02..17de16944 100644 --- a/README.md +++ b/README.md @@ -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: From 6cf216b3c3d122c53583ae229bda51464080a32e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 7 Apr 2016 15:46:05 +1000 Subject: [PATCH 3/4] Fix blend Former-commit-id: 2ff994ec48236f6d1293dfbaefdbf2a2f91fee67 Former-commit-id: 734ec6a70dd782ac2d26420621e989d9943b12ed Former-commit-id: 87b984ebbace9d0b8108267d0d09f736ba5e1b1a --- README.md | 4 ++-- src/ImageProcessorCore/Filters/Blend.cs | 12 +++++------- .../Filters/ImageFilterExtensions.cs | 10 ++++++++-- src/ImageProcessorCore/ImageBase.cs | 3 +++ src/ImageProcessorCore/ImageExtensions.cs | 5 +++++ tests/ImageProcessorCore.Tests/FileTestBase.cs | 2 +- .../Processors/Samplers/SamplerTests.cs | 3 ++- 7 files changed, 26 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 17de16944..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) @@ -174,7 +174,7 @@ 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 (Image image = new Image(stream)) using (FileStream output = File.OpenWrite("bar.jpg")) { List processors = new List() diff --git a/src/ImageProcessorCore/Filters/Blend.cs b/src/ImageProcessorCore/Filters/Blend.cs index e48a26c52..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,15 +72,10 @@ namespace ImageProcessorCore.Filters target[x, y] = color; } + this.OnRowProcessed(); } }); } - - /// - protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) - { - this.toBlend?.Dispose(); - } } } 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/ImageBase.cs b/src/ImageProcessorCore/ImageBase.cs index 0065d61bc..4d4541645 100644 --- a/src/ImageProcessorCore/ImageBase.cs +++ b/src/ImageProcessorCore/ImageBase.cs @@ -24,6 +24,9 @@ namespace ImageProcessorCore /// private float[] pixelsArray; + /// + /// Provides a way to access the pixels from unmanaged memory. + /// private GCHandle pixelsHandle; /// diff --git a/src/ImageProcessorCore/ImageExtensions.cs b/src/ImageProcessorCore/ImageExtensions.cs index 533d33701..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,7 @@ namespace ImageProcessorCore } } + // Clean up. source.Dispose(); return transformedImage; } diff --git a/tests/ImageProcessorCore.Tests/FileTestBase.cs b/tests/ImageProcessorCore.Tests/FileTestBase.cs index 6c5079cd1..d5471f429 100644 --- a/tests/ImageProcessorCore.Tests/FileTestBase.cs +++ b/tests/ImageProcessorCore.Tests/FileTestBase.cs @@ -29,7 +29,7 @@ namespace ImageProcessorCore.Tests //"TestImages/Formats/Png/indexed.png", // Perf: Enable for local testing only "TestImages/Formats/Png/splash.png", "TestImages/Formats/Gif/rings.gif", - "TestImages/Formats/Gif/giphy.gif" // Perf: Enable for local testing only + //"TestImages/Formats/Gif/giphy.gif" // Perf: Enable for local testing only }; protected void ProgressUpdate(object sender, ProgressEventArgs e) diff --git a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs index 4aaffb328..2e9eef9cf 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs @@ -65,6 +65,7 @@ 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; @@ -96,7 +97,7 @@ using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}")) { image.Resize(image.Width / 2, image.Height / 2, sampler, false, this.ProgressUpdate) - .Save(output); + .Save(output); } } Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms"); From bae30daeadd381dcc475a742af06c3908e235cd2 Mon Sep 17 00:00:00 2001 From: James South Date: Mon, 11 Apr 2016 21:31:04 +1000 Subject: [PATCH 4/4] No need for bounds check. Former-commit-id: 6004db66d17f9fc01328774b9a15e2876ae6e57a Former-commit-id: badb280bc5a2206643bf403856e98335f63c410b Former-commit-id: fd532c37d6ed95609736410b30e6b4037e1ec795 --- src/ImageProcessorCore/Samplers/Resize.cs | 61 +++++++++++------------ 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/src/ImageProcessorCore/Samplers/Resize.cs b/src/ImageProcessorCore/Samplers/Resize.cs index 275f72454..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(); }); }