From 3dbad0c44ab50bc089037c1d55bf5286dddd7330 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 23 Jun 2016 17:42:45 +1000 Subject: [PATCH] Use PixelAccessor Former-commit-id: 9b7174022ac6745534acff3ed69718d143e91e6f Former-commit-id: f8d1e182dec104b3c77fec79cd4d7c00a299a70f Former-commit-id: 98f22a9d62b5133aeba4b4525a71b2e6c2b18133 --- .../Common/Helpers/ImageMaths.cs | 37 ++-- src/ImageProcessorCore/Filters/Alpha.cs | 32 +-- .../Filters/BackgroundColor.cs | 51 +++-- .../Filters/Binarization/Threshold.cs | 10 +- src/ImageProcessorCore/Filters/Blend.cs | 59 ++--- src/ImageProcessorCore/Filters/Brightness.cs | 32 +-- .../Filters/ColorMatrix/ColorMatrixFilter.cs | 9 +- src/ImageProcessorCore/Filters/Contrast.cs | 35 +-- .../Convolution/Convolution2DFilter.cs | 16 +- .../Convolution/Convolution2PassFilter.cs | 10 +- .../Filters/Convolution/ConvolutionFilter.cs | 10 +- src/ImageProcessorCore/Filters/Glow.cs | 31 +-- src/ImageProcessorCore/Filters/Invert.cs | 31 +-- src/ImageProcessorCore/Filters/Pixelate.cs | 68 +++--- src/ImageProcessorCore/Filters/Vignette.cs | 28 ++- .../Formats/Bmp/BmpEncoderCore.cs | 37 ++-- .../Formats/Jpg/JpegEncoder.cs | 2 +- .../Formats/Jpg/JpegEncoderCore.cs | 43 ++-- .../Formats/Png/PngEncoderCore.cs | 21 +- src/ImageProcessorCore/IImage.cs | 103 --------- src/ImageProcessorCore/IImageBase.cs | 106 --------- src/ImageProcessorCore/Image.cs | 97 +++++--- src/ImageProcessorCore/ImageBase.cs | 207 ++++++------------ src/ImageProcessorCore/ImageExtensions.cs | 8 - src/ImageProcessorCore/PixelAccessor.cs | 164 ++++++++++++++ .../Quantizers/Octree/Quantizer.cs | 31 +-- .../Quantizers/Wu/WuQuantizer.cs | 36 +-- .../Samplers/Processors/CropProcessor.cs | 30 +-- .../Processors/EntropyCropProcessor.cs | 55 ++--- .../Samplers/Processors/ResizeProcessor.cs | 185 +++++++++------- .../Samplers/Processors/RotateFlip.cs | 150 ++++++++----- .../Samplers/Processors/RotateProcessor.cs | 43 ++-- .../Samplers/Processors/SkewProcessor.cs | 43 ++-- .../Image/GetSetPixel.cs | 7 +- .../Samplers/Crop.cs | 10 +- .../Samplers/Resize.cs | 10 +- .../Formats/BitmapTests.cs | 24 +- .../Formats/EncoderDecoderTests.cs | 127 +++++------ .../Formats/PngTests.cs | 10 +- .../Processors/Filters/FilterTests.cs | 16 +- .../Processors/Samplers/SamplerTests.cs | 33 ++- 41 files changed, 1039 insertions(+), 1018 deletions(-) delete mode 100644 src/ImageProcessorCore/IImage.cs delete mode 100644 src/ImageProcessorCore/IImageBase.cs create mode 100644 src/ImageProcessorCore/PixelAccessor.cs diff --git a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs index c85499081f..7069708352 100644 --- a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs +++ b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs @@ -171,35 +171,35 @@ namespace ImageProcessorCore Point topLeft = new Point(); Point bottomRight = new Point(); - Func delegateFunc; + Func delegateFunc; // Determine which channel to check against switch (channel) { case RgbaComponent.R: - delegateFunc = (imageBase, x, y, b) => Math.Abs(imageBase[x, y].R - b) > Epsilon; + delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].R - b) > Epsilon; break; case RgbaComponent.G: - delegateFunc = (imageBase, x, y, b) => Math.Abs(imageBase[x, y].G - b) > Epsilon; + delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].G - b) > Epsilon; break; case RgbaComponent.A: - delegateFunc = (imageBase, x, y, b) => Math.Abs(imageBase[x, y].A - b) > Epsilon; + delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].A - b) > Epsilon; break; default: - delegateFunc = (imageBase, x, y, b) => Math.Abs(imageBase[x, y].B - b) > Epsilon; + delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].B - b) > Epsilon; break; } - Func getMinY = imageBase => + Func getMinY = pixels => { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { - if (delegateFunc(imageBase, x, y, componentValue)) + if (delegateFunc(pixels, x, y, componentValue)) { return y; } @@ -209,13 +209,13 @@ namespace ImageProcessorCore return 0; }; - Func getMaxY = imageBase => + Func getMaxY = pixels => { for (int y = height - 1; y > -1; y--) { for (int x = 0; x < width; x++) { - if (delegateFunc(imageBase, x, y, componentValue)) + if (delegateFunc(pixels, x, y, componentValue)) { return y; } @@ -225,13 +225,13 @@ namespace ImageProcessorCore return height; }; - Func getMinX = imageBase => + Func getMinX = pixels => { for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { - if (delegateFunc(imageBase, x, y, componentValue)) + if (delegateFunc(pixels, x, y, componentValue)) { return x; } @@ -241,13 +241,13 @@ namespace ImageProcessorCore return 0; }; - Func getMaxX = imageBase => + Func getMaxX = pixels => { for (int x = width - 1; x > -1; x--) { for (int y = 0; y < height; y++) { - if (delegateFunc(imageBase, x, y, componentValue)) + if (delegateFunc(pixels, x, y, componentValue)) { return x; } @@ -257,10 +257,13 @@ namespace ImageProcessorCore return height; }; - topLeft.Y = getMinY(bitmap); - topLeft.X = getMinX(bitmap); - bottomRight.Y = (getMaxY(bitmap) + 1).Clamp(0, height); - bottomRight.X = (getMaxX(bitmap) + 1).Clamp(0, width); + using (PixelAccessor bitmapPixels = bitmap.Lock()) + { + topLeft.Y = getMinY(bitmapPixels); + topLeft.X = getMinX(bitmapPixels); + bottomRight.Y = (getMaxY(bitmapPixels) + 1).Clamp(0, height); + bottomRight.X = (getMaxX(bitmapPixels) + 1).Clamp(0, width); + } return GetBoundingRectangle(topLeft, bottomRight); } diff --git a/src/ImageProcessorCore/Filters/Alpha.cs b/src/ImageProcessorCore/Filters/Alpha.cs index efdd63ca71..738336def3 100644 --- a/src/ImageProcessorCore/Filters/Alpha.cs +++ b/src/ImageProcessorCore/Filters/Alpha.cs @@ -42,22 +42,28 @@ namespace ImageProcessorCore.Filters int endX = sourceRectangle.Right; Vector4 alphaVector = new Vector4(1, 1, 1, alpha); - Parallel.For( - startY, - endY, - y => - { - if (y >= sourceY && y < sourceBottom) + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => { - for (int x = startX; x < endX; x++) + if (y >= sourceY && y < sourceBottom) { - Vector4 color = Color.ToNonPremultiplied(source[x, y]).ToVector4(); - color *= alphaVector; - target[x, y] = Color.FromNonPremultiplied(new Color(color)); + for (int x = startX; x < endX; x++) + { + Vector4 color = Color.ToNonPremultiplied(sourcePixels[x, y]).ToVector4(); + color *= alphaVector; + targetPixels[x, y] = Color.FromNonPremultiplied(new Color(color)); + } + + this.OnRowProcessed(); } - this.OnRowProcessed(); - } - }); + }); + + } } } } diff --git a/src/ImageProcessorCore/Filters/BackgroundColor.cs b/src/ImageProcessorCore/Filters/BackgroundColor.cs index f0ad41d2e7..c8fa2f4e9f 100644 --- a/src/ImageProcessorCore/Filters/BackgroundColor.cs +++ b/src/ImageProcessorCore/Filters/BackgroundColor.cs @@ -41,33 +41,38 @@ namespace ImageProcessorCore.Filters int endX = sourceRectangle.Right; Color backgroundColor = this.Value; - Parallel.For( - startY, - endY, - y => - { - if (y >= sourceY && y < sourceBottom) - { - for (int x = startX; x < endX; x++) + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => { - Color color = source[x, y]; - float a = color.A; - - if (a < 1 && a > 0) + if (y >= sourceY && y < sourceBottom) { - color = Color.Lerp(color, backgroundColor, .5f); - } + for (int x = startX; x < endX; x++) + { + Color color = sourcePixels[x, y]; + float a = color.A; - if (Math.Abs(a) < Epsilon) - { - color = backgroundColor; - } + if (a < 1 && a > 0) + { + color = Color.Lerp(color, backgroundColor, .5f); + } - target[x, y] = color; - } - this.OnRowProcessed(); - } - }); + if (Math.Abs(a) < Epsilon) + { + color = backgroundColor; + } + + targetPixels[x, y] = color; + } + + this.OnRowProcessed(); + } + }); + } } } } diff --git a/src/ImageProcessorCore/Filters/Binarization/Threshold.cs b/src/ImageProcessorCore/Filters/Binarization/Threshold.cs index 6c71834d5a..e6f3e8b56b 100644 --- a/src/ImageProcessorCore/Filters/Binarization/Threshold.cs +++ b/src/ImageProcessorCore/Filters/Binarization/Threshold.cs @@ -60,7 +60,10 @@ namespace ImageProcessorCore.Filters int startX = sourceRectangle.X; int endX = sourceRectangle.Right; - Parallel.For( + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( startY, endY, y => @@ -69,14 +72,15 @@ namespace ImageProcessorCore.Filters { for (int x = startX; x < endX; x++) { - Color color = source[x, y]; + Color color = sourcePixels[x, y]; // Any channel will do since it's greyscale. - target[x, y] = color.B >= threshold ? upper : lower; + targetPixels[x, y] = color.B >= threshold ? upper : lower; } this.OnRowProcessed(); } }); + } } } } diff --git a/src/ImageProcessorCore/Filters/Blend.cs b/src/ImageProcessorCore/Filters/Blend.cs index 8c36c94f28..4b886cf3bc 100644 --- a/src/ImageProcessorCore/Filters/Blend.cs +++ b/src/ImageProcessorCore/Filters/Blend.cs @@ -15,7 +15,7 @@ namespace ImageProcessorCore.Filters /// /// The image to blend. /// - private readonly ImageBase toBlend; + private readonly ImageBase blend; /// /// Initializes a new instance of the class. @@ -28,7 +28,7 @@ namespace ImageProcessorCore.Filters public Blend(ImageBase image, int alpha = 100) { Guard.MustBeBetweenOrEqualTo(alpha, 0, 100, nameof(alpha)); - this.toBlend = image; + this.blend = image; this.Value = alpha; } @@ -44,38 +44,43 @@ namespace ImageProcessorCore.Filters int sourceBottom = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; - Rectangle bounds = this.toBlend.Bounds; + Rectangle bounds = this.blend.Bounds; float alpha = this.Value / 100f; - Parallel.For( - startY, - endY, - y => - { - if (y >= sourceY && y < sourceBottom) - { - for (int x = startX; x < endX; x++) + using (PixelAccessor toBlendPixels = this.blend.Lock()) + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => { - Color color = source[x, y]; - - if (bounds.Contains(x, y)) + if (y >= sourceY && y < sourceBottom) { - Color blendedColor = this.toBlend[x, y]; - - if (blendedColor.A > 0) + for (int x = startX; x < endX; x++) { - // Lerping colors is dependent on the alpha of the blended color - float alphaFactor = alpha > 0 ? alpha : blendedColor.A; - color = Color.Lerp(color, blendedColor, alphaFactor); - } - } + Color color = sourcePixels[x, y]; - target[x, y] = color; - } + if (bounds.Contains(x, y)) + { + Color blendedColor = toBlendPixels[x, y]; - this.OnRowProcessed(); - } - }); + if (blendedColor.A > 0) + { + // Lerping colors is dependent on the alpha of the blended color + float alphaFactor = alpha > 0 ? alpha : blendedColor.A; + color = Color.Lerp(color, blendedColor, alphaFactor); + } + } + + targetPixels[x, y] = color; + } + + this.OnRowProcessed(); + } + }); + } } } } diff --git a/src/ImageProcessorCore/Filters/Brightness.cs b/src/ImageProcessorCore/Filters/Brightness.cs index 4c21d50b54..7bc3c35a09 100644 --- a/src/ImageProcessorCore/Filters/Brightness.cs +++ b/src/ImageProcessorCore/Filters/Brightness.cs @@ -41,25 +41,29 @@ namespace ImageProcessorCore.Filters int startX = sourceRectangle.X; int endX = sourceRectangle.Right; - Parallel.For( - startY, - endY, - y => - { - if (y >= sourceY && y < sourceBottom) + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => { - for (int x = startX; x < endX; x++) + if (y >= sourceY && y < sourceBottom) { - Color color = Color.Expand(source[x, y]); + for (int x = startX; x < endX; x++) + { + Color color = Color.Expand(sourcePixels[x, y]); - Vector3 vector3 = color.ToVector3(); - vector3 += new Vector3(brightness); + Vector3 vector3 = color.ToVector3(); + vector3 += new Vector3(brightness); - target[x, y] = Color.Compress(new Color(vector3, color.A)); + targetPixels[x, y] = Color.Compress(new Color(vector3, color.A)); + } + this.OnRowProcessed(); } - this.OnRowProcessed(); - } - }); + }); + } } } } diff --git a/src/ImageProcessorCore/Filters/ColorMatrix/ColorMatrixFilter.cs b/src/ImageProcessorCore/Filters/ColorMatrix/ColorMatrixFilter.cs index 7de6fcac49..8397a07cc1 100644 --- a/src/ImageProcessorCore/Filters/ColorMatrix/ColorMatrixFilter.cs +++ b/src/ImageProcessorCore/Filters/ColorMatrix/ColorMatrixFilter.cs @@ -28,7 +28,10 @@ namespace ImageProcessorCore.Filters int endX = sourceRectangle.Right; Matrix4x4 matrix = this.Matrix; - Parallel.For( + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( startY, endY, y => @@ -37,11 +40,13 @@ namespace ImageProcessorCore.Filters { for (int x = startX; x < endX; x++) { - target[x, y] = this.ApplyMatrix(source[x, y], matrix); + targetPixels[x, y] = this.ApplyMatrix(sourcePixels[x, y], matrix); } + this.OnRowProcessed(); } }); + } } /// diff --git a/src/ImageProcessorCore/Filters/Contrast.cs b/src/ImageProcessorCore/Filters/Contrast.cs index 80c422c834..eeaa51ec12 100644 --- a/src/ImageProcessorCore/Filters/Contrast.cs +++ b/src/ImageProcessorCore/Filters/Contrast.cs @@ -42,24 +42,29 @@ namespace ImageProcessorCore.Filters int endX = sourceRectangle.Right; Vector4 contrastVector = new Vector4(contrast, contrast, contrast, 1); Vector4 shiftVector = new Vector4(.5f, .5f, .5f, 1); - Parallel.For( - startY, - endY, - y => - { - if (y >= sourceY && y < sourceBottom) + + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => { - for (int x = startX; x < endX; x++) + if (y >= sourceY && y < sourceBottom) { - Vector4 color = Color.Expand(source[x, y]).ToVector4(); - color -= shiftVector; - color *= contrastVector; - color += shiftVector; - target[x, y] = Color.Compress(new Color(color)); + for (int x = startX; x < endX; x++) + { + Vector4 color = Color.Expand(sourcePixels[x, y]).ToVector4(); + color -= shiftVector; + color *= contrastVector; + color += shiftVector; + targetPixels[x, y] = Color.Compress(new Color(color)); + } + this.OnRowProcessed(); } - this.OnRowProcessed(); - } - }); + }); + } } } } diff --git a/src/ImageProcessorCore/Filters/Convolution/Convolution2DFilter.cs b/src/ImageProcessorCore/Filters/Convolution/Convolution2DFilter.cs index 49537ac75f..2fd53f04c8 100644 --- a/src/ImageProcessorCore/Filters/Convolution/Convolution2DFilter.cs +++ b/src/ImageProcessorCore/Filters/Convolution/Convolution2DFilter.cs @@ -42,7 +42,10 @@ namespace ImageProcessorCore.Filters int maxY = sourceBottom - 1; int maxX = endX - 1; - Parallel.For( + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( startY, endY, y => @@ -58,8 +61,8 @@ namespace ImageProcessorCore.Filters float gY = 0; float bY = 0; - // Apply each matrix multiplier to the color components for each pixel. - for (int fy = 0; fy < kernelYHeight; fy++) + // Apply each matrix multiplier to the color components for each pixel. + for (int fy = 0; fy < kernelYHeight; fy++) { int fyr = fy - radiusY; int offsetY = y + fyr; @@ -73,7 +76,7 @@ namespace ImageProcessorCore.Filters offsetX = offsetX.Clamp(0, maxX); - Color currentColor = source[offsetX, offsetY]; + Color currentColor = sourcePixels[offsetX, offsetY]; float r = currentColor.R; float g = currentColor.G; float b = currentColor.B; @@ -98,12 +101,13 @@ namespace ImageProcessorCore.Filters float green = (float)Math.Sqrt((gX * gX) + (gY * gY)); float blue = (float)Math.Sqrt((bX * bX) + (bY * bY)); - Color targetColor = target[x, y]; - target[x, y] = new Color(red, green, blue, targetColor.A); + Color targetColor = targetPixels[x, y]; + targetPixels[x, y] = new Color(red, green, blue, targetColor.A); } this.OnRowProcessed(); } }); + } } } } diff --git a/src/ImageProcessorCore/Filters/Convolution/Convolution2PassFilter.cs b/src/ImageProcessorCore/Filters/Convolution/Convolution2PassFilter.cs index 8fbdb5e2b1..7d9bda6c06 100644 --- a/src/ImageProcessorCore/Filters/Convolution/Convolution2PassFilter.cs +++ b/src/ImageProcessorCore/Filters/Convolution/Convolution2PassFilter.cs @@ -71,7 +71,10 @@ namespace ImageProcessorCore.Filters int maxY = sourceBottom - 1; int maxX = endX - 1; - Parallel.For( + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( startY, endY, y => @@ -97,16 +100,17 @@ namespace ImageProcessorCore.Filters offsetX = offsetX.Clamp(0, maxX); - Color currentColor = source[offsetX, offsetY]; + Color currentColor = sourcePixels[offsetX, offsetY]; destination += kernel[fy, fx] * currentColor; } } - target[x, y] = destination; + targetPixels[x, y] = destination; } this.OnRowProcessed(); } }); + } } } } \ No newline at end of file diff --git a/src/ImageProcessorCore/Filters/Convolution/ConvolutionFilter.cs b/src/ImageProcessorCore/Filters/Convolution/ConvolutionFilter.cs index 0c34ab9022..5a5ee89d84 100644 --- a/src/ImageProcessorCore/Filters/Convolution/ConvolutionFilter.cs +++ b/src/ImageProcessorCore/Filters/Convolution/ConvolutionFilter.cs @@ -31,7 +31,10 @@ namespace ImageProcessorCore.Filters int maxY = sourceBottom - 1; int maxX = endX - 1; - Parallel.For( + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( startY, endY, y => @@ -59,7 +62,7 @@ namespace ImageProcessorCore.Filters offsetX = offsetX.Clamp(0, maxX); - Color currentColor = source[offsetX, offsetY]; + Color currentColor = sourcePixels[offsetX, offsetY]; float r = currentColor.R; float g = currentColor.G; float b = currentColor.B; @@ -74,11 +77,12 @@ namespace ImageProcessorCore.Filters float green = gX; float blue = bX; - target[x, y] = new Color(red, green, blue); + targetPixels[x, y] = new Color(red, green, blue); } this.OnRowProcessed(); } }); + } } } } diff --git a/src/ImageProcessorCore/Filters/Glow.cs b/src/ImageProcessorCore/Filters/Glow.cs index 2ad7a383ae..3cca427e8e 100644 --- a/src/ImageProcessorCore/Filters/Glow.cs +++ b/src/ImageProcessorCore/Filters/Glow.cs @@ -15,7 +15,7 @@ namespace ImageProcessorCore.Filters public class Glow : ParallelImageProcessor { /// - /// Gets or sets the vignette color to apply. + /// Gets or sets the glow color to apply. /// public Color Color { get; set; } = Color.White; @@ -40,19 +40,24 @@ namespace ImageProcessorCore.Filters float rY = this.RadiusY > 0 ? this.RadiusY : targetRectangle.Height / 2f; float maxDistance = (float)Math.Sqrt(rX * rX + rY * rY); - Parallel.For( - startY, - endY, - y => - { - for (int x = startX; x < endX; x++) + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => { - float distance = Vector2.Distance(centre, new Vector2(x, y)); - Color sourceColor = target[x, y]; - target[x, y] = Color.Lerp(glowColor, sourceColor, .5f * (distance / maxDistance)); - } - this.OnRowProcessed(); - }); + for (int x = startX; x < endX; x++) + { + float distance = Vector2.Distance(centre, new Vector2(x, y)); + Color sourceColor = sourcePixels[x, y]; + targetPixels[x, y] = Color.Lerp(glowColor, sourceColor, .5f * (distance / maxDistance)); + } + + this.OnRowProcessed(); + }); + } } } } diff --git a/src/ImageProcessorCore/Filters/Invert.cs b/src/ImageProcessorCore/Filters/Invert.cs index 249907abce..3399d99598 100644 --- a/src/ImageProcessorCore/Filters/Invert.cs +++ b/src/ImageProcessorCore/Filters/Invert.cs @@ -22,22 +22,27 @@ namespace ImageProcessorCore.Filters int endX = sourceRectangle.Right; Vector3 inverseVector = Vector3.One; - Parallel.For( - startY, - endY, - y => - { - if (y >= sourceY && y < sourceBottom) + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => { - for (int x = startX; x < endX; x++) + if (y >= sourceY && y < sourceBottom) { - Color color = source[x, y]; - Vector3 vector = inverseVector - color.ToVector3(); - target[x, y] = new Color(vector, color.A); + for (int x = startX; x < endX; x++) + { + Color color = sourcePixels[x, y]; + Vector3 vector = inverseVector - color.ToVector3(); + targetPixels[x, y] = new Color(vector, color.A); + } + + this.OnRowProcessed(); } - this.OnRowProcessed(); - } - }); + }); + } } } } diff --git a/src/ImageProcessorCore/Filters/Pixelate.cs b/src/ImageProcessorCore/Filters/Pixelate.cs index 00ef03349a..3a076a912c 100644 --- a/src/ImageProcessorCore/Filters/Pixelate.cs +++ b/src/ImageProcessorCore/Filters/Pixelate.cs @@ -48,46 +48,50 @@ namespace ImageProcessorCore.Filters // Get the range on the y-plane to choose from. IEnumerable range = EnumerableExtensions.SteppedRange(startY, i => i < endY, size); - Parallel.ForEach( - range, - y => - { - if (y >= sourceY && y < sourceBottom) - { - for (int x = startX; x < endX; x += size) + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.ForEach( + range, + y => { + if (y >= sourceY && y < sourceBottom) + { + for (int x = startX; x < endX; x += size) + { - int offsetX = offset; - int offsetY = offset; + int offsetX = offset; + int offsetY = offset; - // Make sure that the offset is within the boundary of the - // image. - while (y + offsetY >= sourceBottom) - { - offsetY--; - } + // Make sure that the offset is within the boundary of the + // image. + while (y + offsetY >= sourceBottom) + { + offsetY--; + } - while (x + offsetX >= endX) - { - offsetX--; - } + while (x + offsetX >= endX) + { + offsetX--; + } - // Get the pixel color in the centre of the soon to be pixelated area. - // ReSharper disable AccessToDisposedClosure - Color pixel = source[x + offsetX, y + offsetY]; + // Get the pixel color in the centre of the soon to be pixelated area. + // ReSharper disable AccessToDisposedClosure + Color pixel = sourcePixels[x + offsetX, y + offsetY]; - // For each pixel in the pixelate size, set it to the centre color. - for (int l = y; l < y + size && l < sourceBottom; l++) - { - for (int k = x; k < x + size && k < endX; k++) - { - target[k, l] = pixel; + // For each pixel in the pixelate size, set it to the centre color. + for (int l = y; l < y + size && l < sourceBottom; l++) + { + for (int k = x; k < x + size && k < endX; k++) + { + targetPixels[k, l] = pixel; + } + } } + this.OnRowProcessed(); } - } - this.OnRowProcessed(); - } - }); + }); + } } } } diff --git a/src/ImageProcessorCore/Filters/Vignette.cs b/src/ImageProcessorCore/Filters/Vignette.cs index af9c806b68..dcc2a18147 100644 --- a/src/ImageProcessorCore/Filters/Vignette.cs +++ b/src/ImageProcessorCore/Filters/Vignette.cs @@ -40,19 +40,23 @@ namespace ImageProcessorCore.Filters float rY = this.RadiusY > 0 ? this.RadiusY : targetRectangle.Height / 2f; float maxDistance = (float)Math.Sqrt(rX * rX + rY * rY); - Parallel.For( - startY, - endY, - y => - { - for (int x = startX; x < endX; x++) + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => { - float distance = Vector2.Distance(centre, new Vector2(x, y)); - Color sourceColor = target[x, y]; - target[x, y] = Color.Lerp(vignetteColor, sourceColor, 1 - .9f * distance / maxDistance); - } - this.OnRowProcessed(); - }); + for (int x = startX; x < endX; x++) + { + float distance = Vector2.Distance(centre, new Vector2(x, y)); + Color sourceColor = sourcePixels[x, y]; + targetPixels[x, y] = Color.Lerp(vignetteColor, sourceColor, 1 - .9f * distance / maxDistance); + } + this.OnRowProcessed(); + }); + } } } } diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs b/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs index 2c4ffd694a..8c16b4e40c 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs @@ -132,15 +132,18 @@ namespace ImageProcessorCore.Formats amount = 4 - amount; } - switch (this.bmpBitsPerPixel) + using (PixelAccessor pixels = image.Lock()) { - case BmpBitsPerPixel.Pixel32: - this.Write32bit(writer, image, amount); - break; + switch (this.bmpBitsPerPixel) + { + case BmpBitsPerPixel.Pixel32: + this.Write32bit(writer, pixels, amount); + break; - case BmpBitsPerPixel.Pixel24: - this.Write24bit(writer, image, amount); - break; + case BmpBitsPerPixel.Pixel24: + this.Write24bit(writer, pixels, amount); + break; + } } } @@ -148,18 +151,18 @@ namespace ImageProcessorCore.Formats /// Writes the 32bit color palette to the stream. /// /// The containing the stream to write to. - /// The containing pixel data. + /// The containing pixel data. /// The amount to pad each row by. - private void Write32bit(EndianBinaryWriter writer, ImageBase image, int amount) + private void Write32bit(EndianBinaryWriter writer, PixelAccessor pixels, int amount) { - for (int y = image.Height - 1; y >= 0; y--) + for (int y = pixels.Height - 1; y >= 0; y--) { - for (int x = 0; x < image.Width; x++) + for (int x = 0; x < pixels.Width; x++) { // Limit the output range and multiply out from our floating point. // Convert back to b-> g-> r-> a order. // Convert to non-premultiplied color. - Bgra32 color = Color.ToNonPremultiplied(image[x, y]); + Bgra32 color = Color.ToNonPremultiplied(pixels[x, y]); // We can take advantage of BGRA here writer.Write(color.Bgra); @@ -177,18 +180,18 @@ namespace ImageProcessorCore.Formats /// Writes the 24bit color palette to the stream. /// /// The containing the stream to write to. - /// The containing pixel data. + /// The containing pixel data. /// The amount to pad each row by. - private void Write24bit(EndianBinaryWriter writer, ImageBase image, int amount) + private void Write24bit(EndianBinaryWriter writer, PixelAccessor pixels, int amount) { - for (int y = image.Height - 1; y >= 0; y--) + for (int y = pixels.Height - 1; y >= 0; y--) { - for (int x = 0; x < image.Width; x++) + for (int x = 0; x < pixels.Width; x++) { // Limit the output range and multiply out from our floating point. // Convert back to b-> g-> r-> a order. // Convert to non-premultiplied color. - Bgra32 color = Color.ToNonPremultiplied(image[x, y]); + Bgra32 color = Color.ToNonPremultiplied(pixels[x, y]); // Allocate 1 array instead of allocating 3. writer.Write(new[] { color.B, color.G, color.R }); diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegEncoder.cs b/src/ImageProcessorCore/Formats/Jpg/JpegEncoder.cs index 87d9d6be19..32c4d5f288 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegEncoder.cs +++ b/src/ImageProcessorCore/Formats/Jpg/JpegEncoder.cs @@ -90,7 +90,7 @@ namespace ImageProcessorCore.Formats } else { - encode.Encode(stream, image, this.Quality, this.Quality >= 80 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420); + encode.Encode(stream, image, this.Quality, this.Quality >= 80 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420); } } } diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs index a8c18d264a..a1f49a0c83 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs @@ -423,15 +423,15 @@ namespace ImageProcessorCore.Formats // toYCbCr converts the 8x8 region of m whose top-left corner is p to its // YCbCr values. - private void toYCbCr(ImageBase m, int x, int y, Block yBlock, Block cbBlock, Block crBlock) + private void toYCbCr(PixelAccessor pixels, int x, int y, Block yBlock, Block cbBlock, Block crBlock) { - int xmax = m.Width - 1; - int ymax = m.Height - 1; + int xmax = pixels.Width - 1; + int ymax = pixels.Height - 1; for (int j = 0; j < 8; j++) { for (int i = 0; i < 8; i++) { - YCbCr color = m[Math.Min(x + i, xmax), Math.Min(y + j, ymax)]; + YCbCr color = pixels[Math.Min(x + i, xmax), Math.Min(y + j, ymax)]; int index = (8 * j) + i; yBlock[index] = (int)color.Y; cbBlock[index] = (int)color.Cb; @@ -486,17 +486,17 @@ namespace ImageProcessorCore.Formats // writeSOS writes the StartOfScan marker. - private void writeSOS(ImageBase m) + private void writeSOS(PixelAccessor pixels) { outputStream.Write(sosHeaderYCbCr, 0, sosHeaderYCbCr.Length); switch (subsample) { case JpegSubsample.Ratio444: - encode444(m); + encode444(pixels); break; case JpegSubsample.Ratio420: - encode420(m); + encode420(pixels); break; } @@ -504,18 +504,18 @@ namespace ImageProcessorCore.Formats emit(0x7f, 7); } - private void encode444(ImageBase m) + private void encode444(PixelAccessor pixels) { Block b = new Block(); Block cb = new Block(); Block cr = new Block(); int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; - for (int y = 0; y < m.Height; y += 8) + for (int y = 0; y < pixels.Height; y += 8) { - for (int x = 0; x < m.Width; x += 8) + for (int x = 0; x < pixels.Width; x += 8) { - toYCbCr(m, x, y, b, cb, cr); + toYCbCr(pixels, x, y, b, cb, cr); prevDCY = writeBlock(b, (quantIndex)0, prevDCY); prevDCCb = writeBlock(cb, (quantIndex)1, prevDCCb); prevDCCr = writeBlock(cr, (quantIndex)1, prevDCCr); @@ -523,7 +523,7 @@ namespace ImageProcessorCore.Formats } } - private void encode420(ImageBase m) + private void encode420(PixelAccessor pixels) { Block b = new Block(); Block[] cb = new Block[4]; @@ -533,16 +533,16 @@ namespace ImageProcessorCore.Formats for (int i = 0; i < 4; i++) cb[i] = new Block(); for (int i = 0; i < 4; i++) cr[i] = new Block(); - for (int y = 0; y < m.Height; y += 16) + for (int y = 0; y < pixels.Height; y += 16) { - for (int x = 0; x < m.Width; x += 16) + for (int x = 0; x < pixels.Width; x += 16) { for (int i = 0; i < 4; i++) { int xOff = (i & 1) * 8; int yOff = (i & 2) * 4; - toYCbCr(m, x + xOff, y + yOff, b, cb[i], cr[i]); + toYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]); prevDCY = writeBlock(b, (quantIndex)0, prevDCY); } scale_16x16_8x8(b, cb); @@ -555,7 +555,7 @@ namespace ImageProcessorCore.Formats // Encode writes the Image m to w in JPEG 4:2:0 baseline format with the given // options. Default parameters are used if a nil *Options is passed. - public void Encode(Stream stream, ImageBase m, int quality, JpegSubsample subsample) + public void Encode(Stream stream, ImageBase image, int quality, JpegSubsample subsample) { this.outputStream = stream; this.subsample = subsample; @@ -570,9 +570,9 @@ namespace ImageProcessorCore.Formats quant[i] = new byte[Block.blockSize]; } - if (m.Width >= (1 << 16) || m.Height >= (1 << 16)) + if (image.Width >= (1 << 16) || image.Height >= (1 << 16)) { - throw new ImageFormatException($"Image is too large to encode at {m.Width}x{m.Height}."); + throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}."); } if (quality < 1) quality = 1; @@ -614,13 +614,16 @@ namespace ImageProcessorCore.Formats writeDQT(); // Write the image dimensions. - writeSOF0(m.Width, m.Height, nComponent); + writeSOF0(image.Width, image.Height, nComponent); // Write the Huffman tables. writeDHT(nComponent); // Write the image data. - writeSOS(m); + using (PixelAccessor pixels = image.Lock()) + { + writeSOS(pixels); + } // Write the End Of Image marker. buf[0] = 0xff; diff --git a/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs b/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs index b2fdab0519..7e61f736ef 100644 --- a/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs @@ -123,7 +123,12 @@ namespace ImageProcessorCore.Formats this.WritePaletteChunk(stream, header, image); this.WritePhysicalChunk(stream, image); this.WriteGammaChunk(stream); - this.WriteDataChunks(stream, image); + + using (PixelAccessor pixels = image.Lock()) + { + this.WriteDataChunks(stream, pixels); + } + this.WriteEndChunk(stream); stream.Flush(); } @@ -292,12 +297,12 @@ namespace ImageProcessorCore.Formats /// Writes the pixel information to the stream. /// /// The containing image data. - /// The image base. - private void WriteDataChunks(Stream stream, ImageBase image) + /// The image pixels. + private void WriteDataChunks(Stream stream, PixelAccessor pixels) { byte[] data; - int imageWidth = image.Width; - int imageHeight = image.Height; + int imageWidth = pixels.Width; + int imageHeight = pixels.Height; // Indexed image. if (this.Quality <= 256) @@ -327,7 +332,7 @@ namespace ImageProcessorCore.Formats else { // TrueColor image. - data = new byte[(imageWidth * imageHeight * 4) + image.Height]; + data = new byte[(imageWidth * imageHeight * 4) + pixels.Height]; int rowLength = (imageWidth * 4) + 1; @@ -343,7 +348,7 @@ namespace ImageProcessorCore.Formats for (int x = 0; x < imageWidth; x++) { - Bgra32 color = Color.ToNonPremultiplied(image[x, y]); + Bgra32 color = Color.ToNonPremultiplied(pixels[x, y]); // Calculate the offset for the new array. int dataOffset = (y * rowLength) + (x * 4) + 1; @@ -354,7 +359,7 @@ namespace ImageProcessorCore.Formats if (y > 0) { - color = Color.ToNonPremultiplied(image[x, y - 1]); + color = Color.ToNonPremultiplied(pixels[x, y - 1]); data[dataOffset] -= color.R; data[dataOffset + 1] -= color.G; diff --git a/src/ImageProcessorCore/IImage.cs b/src/ImageProcessorCore/IImage.cs deleted file mode 100644 index 008be3ddb5..0000000000 --- a/src/ImageProcessorCore/IImage.cs +++ /dev/null @@ -1,103 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - using System.Collections.Generic; - using System.IO; - - using ImageProcessorCore.Formats; - - /// - /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. - /// - public interface IImage : IImageBase - { - /// - /// Gets or sets the resolution of the image in x- direction. It is defined as - /// number of dots per inch and should be an positive value. - /// - /// The density of the image in x- direction. - double HorizontalResolution { get; set; } - - /// - /// Gets or sets the resolution of the image in y- direction. It is defined as - /// number of dots per inch and should be an positive value. - /// - /// The density of the image in y- direction. - double VerticalResolution { get; set; } - - /// - /// Gets the width of the image in inches. It is calculated as the width of the image - /// in pixels multiplied with the density. When the density is equals or less than zero - /// the default value is used. - /// - /// The width of the image in inches. - double InchWidth { get; } - - /// - /// Gets the height of the image in inches. It is calculated as the height of the image - /// in pixels multiplied with the density. When the density is equals or less than zero - /// the default value is used. - /// - /// The height of the image in inches. - double InchHeight { get; } - - /// - /// Gets a value indicating whether this image is animated. - /// - /// - /// True if this image is animated; otherwise, false. - /// - bool IsAnimated { get; } - - /// - /// Gets or sets the number of times any animation is repeated. - /// 0 means to repeat indefinitely. - /// - ushort RepeatCount { get; set; } - - /// - /// Gets the currently loaded image format. - /// - IImageFormat CurrentImageFormat { get; } - - /// - /// Gets the other frames for the animation. - /// - /// The list of frame images. - IList Frames { get; } - - /// - /// Gets the list of properties for storing meta information about this image. - /// - /// A list of image properties. - IList Properties { get; } - - /// - /// Saves the image to the given stream using the currently loaded image format. - /// - /// The stream to save the image to. - /// Thrown if the stream is null. - void Save(Stream stream); - - /// - /// Saves the image to the given stream using the given image format. - /// - /// The stream to save the image to. - /// The format to save the image as. - /// Thrown if the stream is null. - void Save(Stream stream, IImageFormat format); - - /// - /// Saves the image to the given stream using the given image encoder. - /// - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - void Save(Stream stream, IImageEncoder encoder); - } -} \ No newline at end of file diff --git a/src/ImageProcessorCore/IImageBase.cs b/src/ImageProcessorCore/IImageBase.cs deleted file mode 100644 index c9bd8d958b..0000000000 --- a/src/ImageProcessorCore/IImageBase.cs +++ /dev/null @@ -1,106 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - using System.Runtime.CompilerServices; - - /// - /// Encapsulates the basic properties and methods required to manipulate images. - /// - public interface IImageBase - { - /// - /// Gets the image pixels as byte array. - /// - /// - /// The returned array has a length of Width * Height * 4 bytes - /// and stores the red, the green, the blue, and the alpha value for - /// each pixel in this order. - /// - float[] Pixels { get; } - - /// - /// Gets the width in pixels. - /// - int Width { get; } - - /// - /// Gets the height in pixels. - /// - int Height { get; } - - /// - /// Gets the pixel ratio made up of the width and height. - /// - double PixelRatio { get; } - - /// - /// Gets the representing the bounds of the image. - /// - Rectangle Bounds { get; } - - /// - /// Gets or sets th quality of the image. This affects the output quality of lossy image formats. - /// - 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. - /// - int FrameDelay { get; set; } - - /// - /// Gets or sets the color of a pixel at the specified position. - /// - /// - /// The x-coordinate of the pixel. Must be greater - /// than zero and smaller than the width of the pixel. - /// - /// - /// The y-coordinate of the pixel. Must be greater - /// than zero and smaller than the width of the pixel. - /// - /// The at the specified position. - Color this[int x, int y] { get; set; } - - /// - /// Sets the pixel array of the image to the given value. - /// - /// The new width of the image. Must be greater than zero. - /// The new height of the image. Must be greater than zero. - /// - /// The array with colors. Must be a multiple of four times the width and height. - /// - /// - /// Thrown if either or are less than or equal to 0. - /// - /// - /// Thrown if the length is not equal to Width * Height * 4. - /// - void SetPixels(int width, int height, float[] pixels); - - /// - /// Sets the pixel array of the image to the given value, creating a copy of - /// the original pixels. - /// - /// The new width of the image. Must be greater than zero. - /// The new height of the image. Must be greater than zero. - /// - /// The array with colors. Must be a multiple of four times the width and height. - /// - /// - /// Thrown if either or are less than or equal to 0. - /// - /// - /// Thrown if the length is not equal to Width * Height * 4. - /// - void ClonePixels(int width, int height, float[] pixels); - } -} diff --git a/src/ImageProcessorCore/Image.cs b/src/ImageProcessorCore/Image.cs index fce2543a87..3cde695432 100644 --- a/src/ImageProcessorCore/Image.cs +++ b/src/ImageProcessorCore/Image.cs @@ -19,10 +19,10 @@ namespace ImageProcessorCore /// /// /// The image data is always stored in RGBA format, where the red, green, blue, and - /// alpha values are floats. + /// alpha values are floating point numbers. /// [DebuggerDisplay("Image: {Width}x{Height}")] - public class Image : ImageBase, IImage + public class Image : ImageBase { /// /// The default horizontal resolution value (dots per inch) in x direction. @@ -97,13 +97,26 @@ namespace ImageProcessorCore /// public IReadOnlyCollection Formats { get; } = Bootstrapper.Instance.ImageFormats; - /// + /// + /// Gets or sets the resolution of the image in x- direction. It is defined as + /// number of dots per inch and should be an positive value. + /// + /// The density of the image in x- direction. public double HorizontalResolution { get; set; } = DefaultHorizontalResolution; - /// + /// + /// Gets or sets the resolution of the image in y- direction. It is defined as + /// number of dots per inch and should be an positive value. + /// + /// The density of the image in y- direction. public double VerticalResolution { get; set; } = DefaultVerticalResolution; - /// + /// + /// Gets the width of the image in inches. It is calculated as the width of the image + /// in pixels multiplied with the density. When the density is equals or less than zero + /// the default value is used. + /// + /// The width of the image in inches. public double InchWidth { get @@ -119,7 +132,12 @@ namespace ImageProcessorCore } } - /// + /// + /// Gets the height of the image in inches. It is calculated as the height of the image + /// in pixels multiplied with the density. When the density is equals or less than zero + /// the default value is used. + /// + /// The height of the image in inches. public double InchHeight { get @@ -135,36 +153,66 @@ namespace ImageProcessorCore } } - /// + /// + /// Gets a value indicating whether this image is animated. + /// + /// + /// True if this image is animated; otherwise, false. + /// public bool IsAnimated => this.Frames.Count > 0; - /// + /// + /// Gets or sets the number of times any animation is repeated. + /// 0 means to repeat indefinitely. + /// public ushort RepeatCount { get; set; } - /// + /// + /// Gets the other frames for the animation. + /// + /// The list of frame images. public IList Frames { get; } = new List(); - /// + /// + /// Gets the list of properties for storing meta information about this image. + /// + /// A list of image properties. public IList Properties { get; } = new List(); - /// + /// + /// Gets the currently loaded image format. + /// public IImageFormat CurrentImageFormat { get; internal set; } - /// + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The stream to save the image to. + /// Thrown if the stream is null. public void Save(Stream stream) { Guard.NotNull(stream, nameof(stream)); this.CurrentImageFormat.Encoder.Encode(this, stream); } - /// + /// + /// Saves the image to the given stream using the given image format. + /// + /// The stream to save the image to. + /// The format to save the image as. + /// Thrown if the stream is null. public void Save(Stream stream, IImageFormat format) { Guard.NotNull(stream, nameof(stream)); format.Encoder.Encode(this, stream); } - /// + /// + /// Saves the image to the given stream using the given image encoder. + /// + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. public void Save(Stream stream, IImageEncoder encoder) { Guard.NotNull(stream, nameof(stream)); @@ -186,27 +234,6 @@ namespace ImageProcessorCore } } - /// - protected override void Dispose(bool disposing) - { - if (this.IsDisposed) - { - return; - } - - // Dispose of the unmanaged resources for each frame 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 42fd85fd06..5b7ab65f83 100644 --- a/src/ImageProcessorCore/ImageBase.cs +++ b/src/ImageProcessorCore/ImageBase.cs @@ -6,43 +6,18 @@ namespace ImageProcessorCore { using System; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; /// /// The base class of all images. Encapsulates the basic properties and methods /// required to manipulate images. /// - public abstract unsafe class ImageBase : IImageBase, IDisposable + public abstract class ImageBase { - /// - /// 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. - /// - internal bool IsDisposed; - /// /// Initializes a new instance of the class. /// @@ -68,8 +43,6 @@ namespace ImageProcessorCore // 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(); } /// @@ -90,19 +63,11 @@ namespace ImageProcessorCore this.Quality = other.Quality; this.FrameDelay = other.FrameDelay; - // Assign the pointer and copy the pixels. + // 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); - } - /// /// Gets or sets the maximum allowable width in pixels. /// @@ -113,67 +78,65 @@ 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 red, the green, the blue, and the alpha value for + /// each pixel in this order. + /// 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); - /// + /// + /// Gets or sets th quality of the image. This affects the output quality of lossy image formats. + /// 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; } - /// - public Color this[int x, int y] - { - get - { -#if DEBUG - if ((x < 0) || (x >= this.Width)) - { - throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width."); - } - - if ((y < 0) || (y >= this.Height)) - { - throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); - } -#endif - return *((Color*)(this.pixelsBase + ((y * this.Width) + x) * 4)); - } - - set - { -#if DEBUG - if ((x < 0) || (x >= this.Width)) - { - throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width."); - } - - if ((y < 0) || (y >= this.Height)) - { - throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); - } -#endif - *(Color*)(this.pixelsBase + (((y * this.Width) + x) * 4)) = value; - } - } - - /// + /// + /// Sets the pixel array of the image to the given value. + /// + /// The new width of the image. Must be greater than zero. + /// The new height of the image. Must be greater than zero. + /// + /// The array with colors. Must be a multiple of four times the width and height. + /// + /// + /// Thrown if either or are less than or equal to 0. + /// + /// + /// Thrown if the length is not equal to Width * Height * 4. + /// public void SetPixels(int width, int height, float[] pixels) { -#if DEBUG if (width <= 0) { throw new ArgumentOutOfRangeException(nameof(width), "Width must be greater than or equals than zero."); @@ -188,28 +151,30 @@ namespace ImageProcessorCore { throw new ArgumentException("Pixel array must have the length of Width * Height * 4."); } -#endif + this.Width = width; this.Height = height; - - // Ensure nothing is preserved if previously allocated. - if (this.pixelsHandle.IsAllocated) - { - this.pixelsArray = null; - this.pixelsHandle.Free(); - this.pixelsBase = null; - } - this.pixelsArray = pixels; - this.pixelsHandle = GCHandle.Alloc(this.pixelsArray, GCHandleType.Pinned); - this.pixelsBase = (float*)this.pixelsHandle.AddrOfPinnedObject().ToPointer(); } - /// + /// + /// Sets the pixel array of the image to the given value, creating a copy of + /// the original pixels. + /// + /// The new width of the image. Must be greater than zero. + /// The new height of the image. Must be greater than zero. + /// + /// The array with colors. Must be a multiple of four times the width and height. + /// + /// + /// Thrown if either or are less than or equal to 0. + /// + /// + /// Thrown if the length is not equal to Width * Height * 4. + /// public void ClonePixels(int width, int height, float[] pixels) { -#if DEBUG - if (width <= 0) + if (width <= 0) { throw new ArgumentOutOfRangeException(nameof(width), "Width must be greater than or equals than zero."); } @@ -223,55 +188,25 @@ namespace ImageProcessorCore { throw new ArgumentException("Pixel array must have the length of Width * Height * 4."); } -#endif + this.Width = width; this.Height = height; - // Assign the pointer and copy the pixels. + // 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. + /// Locks the image providing access to the pixels. + /// + /// It is imperative that the accessor is correctly disposed off after use. + /// /// - /// If true, the object gets disposed. - protected virtual void Dispose(bool disposing) + /// The + public PixelAccessor Lock() { - 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; + return new PixelAccessor(this); } } } diff --git a/src/ImageProcessorCore/ImageExtensions.cs b/src/ImageProcessorCore/ImageExtensions.cs index 7379f20c81..cd5bea6242 100644 --- a/src/ImageProcessorCore/ImageExtensions.cs +++ b/src/ImageProcessorCore/ImageExtensions.cs @@ -128,11 +128,6 @@ namespace ImageProcessorCore /// Thrown if the has been disposed. private static Image PerformAction(Image source, bool clone, Action action) { - if (source.IsDisposed) - { - throw new ObjectDisposedException("Image"); - } - Image transformedImage = clone ? new Image(source) : new Image @@ -163,9 +158,6 @@ namespace ImageProcessorCore } } - // According to http://stackoverflow.com/questions/37921815/idisposable-unmanaged-fields-reference-types-and-assignments - // There's no need to dispose of the original image as the GC will get around to cleaning it up now there are no references to the original data. - // TODO: Investigate how long this is held onto and try to keep that to a minimum. source = transformedImage; return source; } diff --git a/src/ImageProcessorCore/PixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor.cs new file mode 100644 index 0000000000..3f7ac433dc --- /dev/null +++ b/src/ImageProcessorCore/PixelAccessor.cs @@ -0,0 +1,164 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.Runtime.InteropServices; + + /// + /// Provides per-pixel access to an images pixels. + /// + public sealed unsafe class PixelAccessor : IDisposable + { + /// + /// The position of the first pixel in the bitmap. + /// + private float* pixelsBase; + + /// + /// 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. + /// + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The image to provide pixel access for. + /// + public PixelAccessor(ImageBase image) + { + Guard.NotNull(image, nameof(image)); + Guard.MustBeGreaterThan(image.Width, 0, "image width"); + Guard.MustBeGreaterThan(image.Height, 0, "image height"); + + int size = image.Pixels.Length; + this.Width = image.Width; + this.Height = image.Height; + + // Assign the pointer. + // If buffer is allocated on Large Object Heap, then we are going to pin it instead of making a copy. + if (size > (85 * 1024)) + { + this.pixelsHandle = GCHandle.Alloc(image.Pixels, GCHandleType.Pinned); + this.pixelsBase = (float*)this.pixelsHandle.AddrOfPinnedObject().ToPointer(); + } + else + { + fixed (float* pbuffer = image.Pixels) + { + this.pixelsBase = pbuffer; + } + } + } + + /// + /// Finalizes an instance of the class. + /// + ~PixelAccessor() + { + this.Dispose(); + } + + /// + /// Gets the width of the image. + /// + public int Width { get; } + + /// + /// Gets the height of the image. + /// + public int Height { get; } + + /// + /// Gets or sets the color of a pixel at the specified position. + /// + /// + /// The x-coordinate of the pixel. Must be greater + /// than zero and smaller than the width of the pixel. + /// + /// + /// The y-coordinate of the pixel. Must be greater + /// than zero and smaller than the width of the pixel. + /// + /// The at the specified position. + public Color this[int x, int y] + { + get + { +#if DEBUG + if ((x < 0) || (x >= this.Width)) + { + throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width."); + } + + if ((y < 0) || (y >= this.Height)) + { + throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); + } +#endif + return *((Color*)(this.pixelsBase + ((y * this.Width) + x) * 4)); + } + + set + { +#if DEBUG + if ((x < 0) || (x >= this.Width)) + { + throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width."); + } + + if ((y < 0) || (y >= this.Height)) + { + throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); + } +#endif + *(Color*)(this.pixelsBase + (((y * this.Width) + x) * 4)) = value; + } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + if (this.isDisposed) + { + return; + } + + if (this.pixelsHandle.IsAllocated) + { + this.pixelsHandle.Free(); + } + + this.pixelsBase = null; + + // Note disposing is done. + this.isDisposed = 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); + } + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs b/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs index 268772cb8c..40d281015c 100644 --- a/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs +++ b/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs @@ -50,21 +50,24 @@ namespace ImageProcessorCore.Quantizers // Get the size of the source image int height = image.Height; int width = image.Width; - - // Call the FirstPass function if not a single pass algorithm. - // For something like an Octree quantizer, this will run through - // all image pixels, build a data structure, and create a palette. - if (!this.singlePass) - { - this.FirstPass(image, width, height); - } - byte[] quantizedPixels = new byte[width * height]; + List palette; - // Get the palette - List palette = this.GetPalette(); + using (PixelAccessor pixels = image.Lock()) + { + // Call the FirstPass function if not a single pass algorithm. + // For something like an Octree quantizer, this will run through + // all image pixels, build a data structure, and create a palette. + if (!this.singlePass) + { + this.FirstPass(pixels, width, height); + } + + // Get the palette + palette = this.GetPalette(); - this.SecondPass(image, quantizedPixels, width, height); + this.SecondPass(pixels, quantizedPixels, width, height); + } return new QuantizedImage(width, height, palette.ToArray(), quantizedPixels, this.TransparentIndex); } @@ -75,7 +78,7 @@ namespace ImageProcessorCore.Quantizers /// The source data /// The width in pixels of the image. /// The height in pixels of the image. - protected virtual void FirstPass(ImageBase source, int width, int height) + protected virtual void FirstPass(PixelAccessor source, int width, int height) { // Loop through each row for (int y = 0; y < height; y++) @@ -96,7 +99,7 @@ namespace ImageProcessorCore.Quantizers /// The output pixel array /// The width in pixels of the image /// The height in pixels of the image - protected virtual void SecondPass(ImageBase source, byte[] output, int width, int height) + protected virtual void SecondPass(PixelAccessor source, byte[] output, int width, int height) { Parallel.For( 0, diff --git a/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs b/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs index 55e0bdcc99..c06262503d 100644 --- a/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs +++ b/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs @@ -123,13 +123,16 @@ namespace ImageProcessorCore.Quantizers this.Clear(); - this.Build3DHistogram(image); - this.Get3DMoments(); + using (PixelAccessor imagePixels = image.Lock()) + { + this.Build3DHistogram(imagePixels); + this.Get3DMoments(); - Box[] cube; - this.BuildCube(out cube, ref colorCount); + Box[] cube; + this.BuildCube(out cube, ref colorCount); - return this.GenerateResult(image, colorCount, cube); + return this.GenerateResult(imagePixels, colorCount, cube); + } } /// @@ -318,7 +321,7 @@ namespace ImageProcessorCore.Quantizers /// Builds a 3-D color histogram of counts, r/g/b, c^2. /// /// The image. - private void Build3DHistogram(ImageBase image) + private void Build3DHistogram(PixelAccessor image) { for (int y = 0; y < image.Height; y++) { @@ -711,15 +714,17 @@ namespace ImageProcessorCore.Quantizers /// /// Generates the quantized result. /// - /// The image. + /// The image pixels. /// The color count. /// The cube. /// The result. - private QuantizedImage GenerateResult(ImageBase image, int colorCount, Box[] cube) + private QuantizedImage GenerateResult(PixelAccessor imagePixels, int colorCount, Box[] cube) { List pallette = new List(); - byte[] pixels = new byte[image.Width * image.Height]; + byte[] pixels = new byte[imagePixels.Width * imagePixels.Height]; int transparentIndex = -1; + int width = imagePixels.Width; + int height = imagePixels.Height; for (int k = 0; k < colorCount; k++) { @@ -752,12 +757,12 @@ namespace ImageProcessorCore.Quantizers Parallel.For( 0, - image.Height, + height, y => { - for (int x = 0; x < image.Width; x++) + for (int x = 0; x < width; x++) { - Bgra32 color = image[x, y]; + Bgra32 color = imagePixels[x, y]; int a = color.A >> (8 - IndexAlphaBits); int r = color.R >> (8 - IndexBits); int g = color.G >> (8 - IndexBits); @@ -765,16 +770,17 @@ namespace ImageProcessorCore.Quantizers if (transparentIndex > -1 && color.A <= this.Threshold) { - pixels[(y * image.Width) + x] = (byte)transparentIndex; + pixels[(y * width) + x] = (byte)transparentIndex; continue; } int ind = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1); - pixels[(y * image.Width) + x] = this.tag[ind]; + pixels[(y * width) + x] = this.tag[ind]; } }); - return new QuantizedImage(image.Width, image.Height, pallette.ToArray(), pixels, transparentIndex); + + return new QuantizedImage(width, height, pallette.ToArray(), pixels, transparentIndex); } } } \ No newline at end of file diff --git a/src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs index d6347ea8aa..c25cfe08d7 100644 --- a/src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs @@ -22,21 +22,25 @@ namespace ImageProcessorCore int sourceX = sourceRectangle.X; int sourceY = sourceRectangle.Y; - Parallel.For( - startY, - endY, - y => + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) { - if (y >= targetY && y < targetBottom) - { - for (int x = startX; x < endX; x++) - { - target[x, y] = source[x + sourceX, y + sourceY]; - } + Parallel.For( + startY, + endY, + y => + { + if (y >= targetY && y < targetBottom) + { + for (int x = startX; x < endX; x++) + { + targetPixels[x, y] = sourcePixels[x + sourceX, y + sourceY]; + } - this.OnRowProcessed(); - } - }); + this.OnRowProcessed(); + } + }); + } } } } diff --git a/src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs index 697148f091..0d4632472d 100644 --- a/src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs @@ -8,7 +8,7 @@ namespace ImageProcessorCore using System; using System.Threading.Tasks; - using ImageProcessorCore.Filters; + using Filters; /// /// Provides methods to allow the cropping of an image to preserve areas of highest @@ -42,21 +42,20 @@ namespace ImageProcessorCore /// protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { - using (ImageBase temp = new Image(source.Width, source.Height)) - { - // Detect the edges. - new Sobel().Apply(temp, source, sourceRectangle); + ImageBase temp = new Image(source.Width, source.Height); - // Apply threshold binarization filter. - new Threshold(.5f).Apply(temp, temp, sourceRectangle); + // Detect the edges. + new Sobel().Apply(temp, source, sourceRectangle); - // Search for the first white pixels - Rectangle rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0); + // Apply threshold binarization filter. + new Threshold(.5f).Apply(temp, temp, sourceRectangle); - // Reset the target pixel to the correct size. - target.SetPixels(rectangle.Width, rectangle.Height, new float[rectangle.Width * rectangle.Height * 4]); - this.cropRectangle = rectangle; - } + // 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; } /// @@ -73,21 +72,25 @@ namespace ImageProcessorCore int startX = this.cropRectangle.X; int endX = this.cropRectangle.Right; - Parallel.For( - startY, - endY, - y => + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) { - if (y >= targetY && y < targetBottom) - { - for (int x = startX; x < endX; x++) - { - target[x - startX, y - targetY] = source[x, y]; - } - } + Parallel.For( + startY, + endY, + y => + { + if (y >= targetY && y < targetBottom) + { + for (int x = startX; x < endX; x++) + { + targetPixels[x - startX, y - targetY] = sourcePixels[x, y]; + } + } - this.OnRowProcessed(); - }); + this.OnRowProcessed(); + }); + } } /// diff --git a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs index e72e303e69..0cc75822b1 100644 --- a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs @@ -62,7 +62,13 @@ namespace ImageProcessorCore } /// - protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + protected override void Apply( + ImageBase target, + ImageBase source, + Rectangle targetRectangle, + Rectangle sourceRectangle, + int startY, + int endY) { // Jump out, we'll deal with that later. if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) @@ -87,30 +93,34 @@ namespace ImageProcessorCore float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width; float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height; - Parallel.For( - startY, - endY, - y => - { - if (targetY <= y && y < targetBottom) - { - // Y coordinates of source points - int originY = (int)((y - startY) * heightFactor); - - for (int x = startX; x < endX; x++) + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => { - if (targetX <= x && x < targetRight) + if (targetY <= y && y < targetBottom) { - // X coordinates of source points - int originX = (int)((x - startX) * widthFactor); + // Y coordinates of source points + int originY = (int)((y - startY) * heightFactor); - target[x, y] = source[originX, originY]; - } - } + for (int x = startX; x < endX; x++) + { + if (targetX <= x && x < targetRight) + { + // X coordinates of source points + int originX = (int)((x - startX) * widthFactor); - this.OnRowProcessed(); - } - }); + targetPixels[x, y] = sourcePixels[originX, originY]; + } + } + + this.OnRowProcessed(); + } + }); + } // Break out now. return; @@ -120,78 +130,90 @@ namespace ImageProcessorCore // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm // First process the columns. Since we are not using multiple threads startY and endY // are the upper and lower bounds of the source rectangle. - Parallel.For( - 0, - sourceHeight, - y => - { - for (int x = startX; x < endX; x++) + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor firstPassPixels = this.firstPass.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + 0, + sourceHeight, + y => { - if (x >= 0 && x < width) + for (int x = startX; x < endX; x++) { - // Ensure offsets are normalised for cropping and padding. - int offsetX = x - startX; - float sum = this.HorizontalWeights[offsetX].Sum; - Weight[] horizontalValues = this.HorizontalWeights[offsetX].Values; - - // Destination color components - Color destination = new Color(); - - for (int i = 0; i < sum; i++) - { - Weight xw = horizontalValues[i]; - int originX = xw.Index; - Color sourceColor = compand ? Color.Expand(source[originX, y]) : source[originX, y]; - destination += sourceColor * xw.Value; - } - - if (compand) + if (x >= 0 && x < width) { - destination = Color.Compress(destination); + // Ensure offsets are normalised for cropping and padding. + int offsetX = x - startX; + float sum = this.HorizontalWeights[offsetX].Sum; + Weight[] horizontalValues = this.HorizontalWeights[offsetX].Values; + + // Destination color components + Color destination = new Color(); + + for (int i = 0; i < sum; i++) + { + Weight xw = horizontalValues[i]; + int originX = xw.Index; + Color sourceColor = compand + ? Color.Expand(sourcePixels[originX, y]) + : sourcePixels[originX, y]; + + destination += sourceColor * xw.Value; + } + + if (compand) + { + destination = Color.Compress(destination); + } + + firstPassPixels[x, y] = destination; } - - this.firstPass[x, y] = destination; } - } - }); + }); - // Now process the rows. - Parallel.For( - startY, - endY, - y => - { - if (y >= 0 && y < height) + // Now process the rows. + Parallel.For( + startY, + endY, + y => { - // Ensure offsets are normalised for cropping and padding. - int offsetY = y - startY; - float sum = this.VerticalWeights[offsetY].Sum; - Weight[] verticalValues = this.VerticalWeights[offsetY].Values; - - for (int x = 0; x < width; x++) + if (y >= 0 && y < height) { - // Destination color components - Color destination = new Color(); - - for (int i = 0; i < sum; i++) - { - Weight yw = verticalValues[i]; - int originY = yw.Index; - Color sourceColor = compand ? Color.Expand(this.firstPass[x, originY]) : this.firstPass[x, originY]; - destination += sourceColor * yw.Value; - } + // Ensure offsets are normalised for cropping and padding. + int offsetY = y - startY; + float sum = this.VerticalWeights[offsetY].Sum; + Weight[] verticalValues = this.VerticalWeights[offsetY].Values; - if (compand) + for (int x = 0; x < width; x++) { - destination = Color.Compress(destination); + // Destination color components + Color destination = new Color(); + + for (int i = 0; i < sum; i++) + { + Weight yw = verticalValues[i]; + int originY = yw.Index; + Color sourceColor = compand + ? Color.Expand(firstPassPixels[x, originY]) + : firstPassPixels[x, originY]; + + destination += sourceColor * yw.Value; + } + + if (compand) + { + destination = Color.Compress(destination); + } + + targetPixels[x, y] = destination; } - - target[x, y] = destination; } - } - this.OnRowProcessed(); - }); + this.OnRowProcessed(); + }); + + } } /// @@ -202,9 +224,6 @@ namespace ImageProcessorCore { target.ClonePixels(target.Width, target.Height, source.Pixels); } - - // Clean up - this.firstPass?.Dispose(); } /// diff --git a/src/ImageProcessorCore/Samplers/Processors/RotateFlip.cs b/src/ImageProcessorCore/Samplers/Processors/RotateFlip.cs index d732a8bb14..570ff9c4fc 100644 --- a/src/ImageProcessorCore/Samplers/Processors/RotateFlip.cs +++ b/src/ImageProcessorCore/Samplers/Processors/RotateFlip.cs @@ -78,20 +78,26 @@ namespace ImageProcessorCore int height = source.Height; Image temp = new Image(height, width); - Parallel.For(0, height, - y => - { - for (int x = 0; x < width; x++) - { - int newX = height - y - 1; - newX = height - newX - 1; - int newY = width - x - 1; - newY = width - newY - 1; - temp[newX, newY] = source[x, y]; - } - - this.OnRowProcessed(); - }); + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor tempPixels = temp.Lock()) + { + Parallel.For( + 0, + height, + y => + { + for (int x = 0; x < width; x++) + { + int newX = height - y - 1; + newX = height - newX - 1; + int newY = width - x - 1; + newY = width - newY - 1; + tempPixels[newX, newY] = sourcePixels[x, y]; + } + + this.OnRowProcessed(); + }); + } target.SetPixels(height, width, temp.Pixels); } @@ -106,18 +112,24 @@ namespace ImageProcessorCore int width = source.Width; int height = source.Height; - Parallel.For(0, height, - y => - { - for (int x = 0; x < width; x++) - { - int newX = width - x - 1; - int newY = height - y - 1; - target[newX, newY] = source[x, y]; - } - - this.OnRowProcessed(); - }); + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + 0, + height, + y => + { + for (int x = 0; x < width; x++) + { + int newX = width - x - 1; + int newY = height - y - 1; + targetPixels[newX, newY] = sourcePixels[x, y]; + } + + this.OnRowProcessed(); + }); + } } /// @@ -131,17 +143,23 @@ namespace ImageProcessorCore int height = source.Height; Image temp = new Image(height, width); - Parallel.For(0, height, - y => - { - for (int x = 0; x < width; x++) - { - int newX = height - y - 1; - temp[newX, x] = source[x, y]; - } - - this.OnRowProcessed(); - }); + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor tempPixels = temp.Lock()) + { + Parallel.For( + 0, + height, + y => + { + for (int x = 0; x < width; x++) + { + int newX = height - y - 1; + tempPixels[newX, x] = sourcePixels[x, y]; + } + + this.OnRowProcessed(); + }); + } target.SetPixels(height, width, temp.Pixels); } @@ -159,18 +177,24 @@ namespace ImageProcessorCore ImageBase temp = new Image(width, height); temp.ClonePixels(width, height, target.Pixels); - Parallel.For(0, halfHeight, - y => - { - for (int x = 0; x < width; x++) + using (PixelAccessor targetPixels = target.Lock()) + using (PixelAccessor tempPixels = temp.Lock()) + { + Parallel.For( + 0, + halfHeight, + y => { - int newY = height - y - 1; - target[x, y] = temp[x, newY]; - target[x, newY] = temp[x, y]; - } - - this.OnRowProcessed(); - }); + for (int x = 0; x < width; x++) + { + int newY = height - y - 1; + targetPixels[x, y] = tempPixels[x, newY]; + targetPixels[x, newY] = tempPixels[x, y]; + } + + this.OnRowProcessed(); + }); + } } /// @@ -186,18 +210,24 @@ namespace ImageProcessorCore ImageBase temp = new Image(width, height); temp.ClonePixels(width, height, target.Pixels); - Parallel.For(0, height, - y => - { - for (int x = 0; x < halfWidth; x++) - { - int newX = width - x - 1; - target[x, y] = temp[newX, y]; - target[newX, y] = temp[x, y]; - } - - this.OnRowProcessed(); - }); + using (PixelAccessor targetPixels = target.Lock()) + using (PixelAccessor tempPixels = temp.Lock()) + { + Parallel.For( + 0, + height, + y => + { + for (int x = 0; x < halfWidth; x++) + { + int newX = width - x - 1; + targetPixels[x, y] = tempPixels[newX, y]; + targetPixels[newX, y] = tempPixels[x, y]; + } + + this.OnRowProcessed(); + }); + } } } } diff --git a/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs index 33c31bf9c9..8b264a8fa9 100644 --- a/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs @@ -104,30 +104,27 @@ namespace ImageProcessorCore // Since we are not working in parallel we use full height and width // of the first pass image. - Parallel.For( - 0, - height, - y => - { - for (int x = startX; x < endX; x++) - { - // Rotate at the centre point - Point rotated = Point.Rotate(new Point(x, y), rotation); - if (this.firstPass.Bounds.Contains(rotated.X, rotated.Y)) + using (PixelAccessor firstPassPixels = this.firstPass.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + 0, + height, + y => { - target[x, y] = this.firstPass[rotated.X, rotated.Y]; - } - } - - this.OnRowProcessed(); - }); - } - - /// - protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) - { - // Cleanup. - this.firstPass.Dispose(); + for (int x = startX; x < endX; x++) + { + // Rotate at the centre point + Point rotated = Point.Rotate(new Point(x, y), rotation); + if (this.firstPass.Bounds.Contains(rotated.X, rotated.Y)) + { + targetPixels[x, y] = firstPassPixels[rotated.X, rotated.Y]; + } + } + + this.OnRowProcessed(); + }); + } } } } \ No newline at end of file diff --git a/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs index 1bfcfb7e2e..2d0ce233e7 100644 --- a/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs @@ -135,30 +135,27 @@ namespace ImageProcessorCore // Since we are not working in parallel we use full height and width // of the first pass image. - Parallel.For( - 0, - height, - y => - { - for (int x = startX; x < endX; x++) - { - // Skew at the centre point - Point skewed = Point.Skew(new Point(x, y), skew); - if (this.firstPass.Bounds.Contains(skewed.X, skewed.Y)) + using (PixelAccessor firstPassPixels = this.firstPass.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + 0, + height, + y => { - target[x, y] = this.firstPass[skewed.X, skewed.Y]; - } - } - - this.OnRowProcessed(); - }); - } - - /// - protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) - { - // Cleanup. - this.firstPass.Dispose(); + for (int x = startX; x < endX; x++) + { + // Skew at the centre point + Point skewed = Point.Skew(new Point(x, y), skew); + if (this.firstPass.Bounds.Contains(skewed.X, skewed.Y)) + { + targetPixels[x, y] = firstPassPixels[skewed.X, skewed.Y]; + } + } + + this.OnRowProcessed(); + }); + } } } } \ No newline at end of file diff --git a/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs b/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs index fce14beb7f..64d7896362 100644 --- a/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs +++ b/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs @@ -23,10 +23,11 @@ [Benchmark(Description = "ImageProcessorCore GetSet Pixel")] public CoreColor ResizeCore() { - using (CoreImage image = new CoreImage(400, 400)) + CoreImage image = new CoreImage(400, 400); + using (PixelAccessor imagePixels = image.Lock()) { - image[200, 200] = CoreColor.White; - return image[200, 200]; + imagePixels[200, 200] = CoreColor.White; + return imagePixels[200, 200]; } } } diff --git a/tests/ImageProcessorCore.Benchmarks/Samplers/Crop.cs b/tests/ImageProcessorCore.Benchmarks/Samplers/Crop.cs index 3c0cab4cab..4ffce9d48d 100644 --- a/tests/ImageProcessorCore.Benchmarks/Samplers/Crop.cs +++ b/tests/ImageProcessorCore.Benchmarks/Samplers/Crop.cs @@ -4,8 +4,6 @@ using System.Drawing.Drawing2D; using BenchmarkDotNet.Attributes; - - using ImageProcessorCore.Processors; using CoreImage = ImageProcessorCore.Image; using CoreSize = ImageProcessorCore.Size; @@ -34,11 +32,9 @@ [Benchmark(Description = "ImageProcessorCore Crop")] public CoreSize CropResizeCore() { - using (CoreImage image = new CoreImage(400, 400)) - { - image.Crop(100, 100); - return new CoreSize(image.Width, image.Height); - } + CoreImage image = new CoreImage(400, 400); + image.Crop(100, 100); + return new CoreSize(image.Width, image.Height); } } } diff --git a/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs b/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs index 97415f2fac..479392cc8e 100644 --- a/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs +++ b/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs @@ -4,8 +4,6 @@ using System.Drawing.Drawing2D; using BenchmarkDotNet.Attributes; - - using ImageProcessorCore.Processors; using CoreImage = ImageProcessorCore.Image; using CoreSize = ImageProcessorCore.Size; @@ -34,11 +32,9 @@ [Benchmark(Description = "ImageProcessorCore Resize")] public CoreSize ResizeCore() { - using (CoreImage image = new CoreImage(400, 400)) - { - image.Resize(100, 100); - return new CoreSize(image.Width, image.Height); - } + CoreImage image = new CoreImage(400, 400); + image.Resize(100, 100); + return new CoreSize(image.Width, image.Height); } } } diff --git a/tests/ImageProcessorCore.Tests/Formats/BitmapTests.cs b/tests/ImageProcessorCore.Tests/Formats/BitmapTests.cs index 68b4ea2b0c..aded05692d 100644 --- a/tests/ImageProcessorCore.Tests/Formats/BitmapTests.cs +++ b/tests/ImageProcessorCore.Tests/Formats/BitmapTests.cs @@ -27,23 +27,23 @@ namespace ImageProcessorCore.Tests using (FileStream stream = File.OpenRead(file)) { Stopwatch watch = Stopwatch.StartNew(); - using (Image image = new Image(stream)) - { - string encodeFilename = "TestOutput/Encode/Bitmap/" + "24-" + Path.GetFileNameWithoutExtension(file) + ".bmp"; - using (FileStream output = File.OpenWrite(encodeFilename)) - { - image.Save(output, new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel24 }); - } + Image image = new Image(stream); + string encodeFilename = "TestOutput/Encode/Bitmap/" + "24-" + Path.GetFileNameWithoutExtension(file) + ".bmp"; + + using (FileStream output = File.OpenWrite(encodeFilename)) + { + image.Save(output, new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel24 }); + } - encodeFilename = "TestOutput/Encode/Bitmap/" + "32-" + 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.Pixel32 }); - } + 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 05585284b4..0b2522d750 100644 --- a/tests/ImageProcessorCore.Tests/Formats/EncoderDecoderTests.cs +++ b/tests/ImageProcessorCore.Tests/Formats/EncoderDecoderTests.cs @@ -30,11 +30,10 @@ namespace ImageProcessorCore.Tests using (FileStream stream = File.OpenRead(file)) { Stopwatch watch = Stopwatch.StartNew(); - using (Image image = new Image(stream)) - { - string filename = "TestOutput/ToString/" + Path.GetFileNameWithoutExtension(file) + ".txt"; - File.WriteAllText(filename, image.ToString()); - } + + Image image = new Image(stream); + string filename = "TestOutput/ToString/" + Path.GetFileNameWithoutExtension(file) + ".txt"; + File.WriteAllText(filename, image.ToString()); Trace.WriteLine($"{watch.ElapsedMilliseconds}ms"); } @@ -54,14 +53,13 @@ namespace ImageProcessorCore.Tests using (FileStream stream = File.OpenRead(file)) { Stopwatch watch = Stopwatch.StartNew(); - using (Image image = new Image(stream)) - { - string encodeFilename = "TestOutput/Encode/" + Path.GetFileName(file); - using (FileStream output = File.OpenWrite(encodeFilename)) - { - image.Save(output); - } + Image image = new Image(stream); + string encodeFilename = "TestOutput/Encode/" + Path.GetFileName(file); + + using (FileStream output = File.OpenWrite(encodeFilename)) + { + image.Save(output); } Trace.WriteLine($"{file} : {watch.ElapsedMilliseconds}ms"); @@ -81,37 +79,32 @@ namespace ImageProcessorCore.Tests { using (FileStream stream = File.OpenRead(file)) { - using (Image image = new Image(stream)) + 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)}")) { - IQuantizer quantizer = new OctreeQuantizer(); - QuantizedImage quantizedImage = quantizer.Quantize(image, 256); + Image qi = quantizedImage.ToImage(); + qi.Save(output, image.CurrentImageFormat); - using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Octree-{Path.GetFileName(file)}")) - { - using (Image qi = quantizedImage.ToImage()) - { - qi.Save(output, image.CurrentImageFormat); - } - } + } - quantizer = new WuQuantizer(); - quantizedImage = quantizer.Quantize(image, 256); + quantizer = new WuQuantizer(); + 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/Wu-{Path.GetFileName(file)}")) + { + quantizedImage.ToImage().Save(output, image.CurrentImageFormat); + } - quantizer = new PaletteQuantizer(); - quantizedImage = quantizer.Quantize(image, 256); + 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); - } - } + using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Palette-{Path.GetFileName(file)}")) + { + Image qi = quantizedImage.ToImage(); + qi.Save(output, image.CurrentImageFormat); } } } @@ -129,27 +122,25 @@ namespace ImageProcessorCore.Tests { using (FileStream stream = File.OpenRead(file)) { - using (Image image = new Image(stream)) + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.gif")) { - using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.gif")) - { - image.SaveAsGif(output); - } + image.SaveAsGif(output); + } - 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); } } } @@ -167,25 +158,21 @@ namespace ImageProcessorCore.Tests { using (FileStream stream = File.OpenRead(file)) { - using (Image image = new Image(stream)) + Image image = new Image(stream); + byte[] serialized; + using (MemoryStream memoryStream = new MemoryStream()) { - byte[] serialized; - using (MemoryStream memoryStream = new MemoryStream()) - { - image.Save(memoryStream); - memoryStream.Flush(); - serialized = memoryStream.ToArray(); - } + image.Save(memoryStream); + memoryStream.Flush(); + serialized = memoryStream.ToArray(); + } - using (MemoryStream memoryStream = new MemoryStream(serialized)) + using (MemoryStream memoryStream = new MemoryStream(serialized)) + { + Image image2 = new Image(memoryStream); + using (FileStream output = File.OpenWrite($"TestOutput/Serialized/{Path.GetFileName(file)}")) { - using (Image image2 = new Image(memoryStream)) - { - using (FileStream output = File.OpenWrite($"TestOutput/Serialized/{Path.GetFileName(file)}")) - { - image2.Save(output); - } - } + image2.Save(output); } } } diff --git a/tests/ImageProcessorCore.Tests/Formats/PngTests.cs b/tests/ImageProcessorCore.Tests/Formats/PngTests.cs index 00e90b7942..e824a62f93 100644 --- a/tests/ImageProcessorCore.Tests/Formats/PngTests.cs +++ b/tests/ImageProcessorCore.Tests/Formats/PngTests.cs @@ -25,13 +25,11 @@ namespace ImageProcessorCore.Tests { using (FileStream stream = File.OpenRead(file)) { - using (Image image = new Image(stream)) + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"TestOutput/Encode/Png/{Path.GetFileNameWithoutExtension(file)}.png")) { - using (FileStream output = File.OpenWrite($"TestOutput/Encode/Png/{Path.GetFileNameWithoutExtension(file)}.png")) - { - image.Quality = 256; - image.Save(output, new PngFormat()); - } + 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 db48b7dd52..9c8d39ba8e 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Filters/FilterTests.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/FilterTests.cs @@ -70,16 +70,16 @@ namespace ImageProcessorCore.Tests using (FileStream stream = File.OpenRead(file)) { Stopwatch watch = Stopwatch.StartNew(); - using (Image image = new Image(stream)) + + Image image = new Image(stream); + string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); + using (FileStream output = File.OpenWrite($"TestOutput/Filter/{Path.GetFileName(filename)}")) { - 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; - } + 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 ac610746e9..5009d596b9 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs @@ -70,7 +70,6 @@ processor.OnProgress -= this.ProgressUpdate; } - image.Dispose(); Trace.WriteLine($"{ name }: { watch.ElapsedMilliseconds}ms"); } } @@ -92,7 +91,7 @@ string filename = Path.GetFileName(file); - using (Image image = new Image(stream)) + Image image = new Image(stream); using (FileStream output = File.OpenWrite($"TestOutput/Pad/{filename}")) { image.Pad(image.Width + 50, image.Height + 50, this.ProgressUpdate) @@ -120,7 +119,7 @@ Stopwatch watch = Stopwatch.StartNew(); string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); - using (Image image = new Image(stream)) + Image image = new Image(stream); using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}")) { image.Resize(image.Width / 2, image.Height / 2, sampler, false, this.ProgressUpdate) @@ -149,7 +148,7 @@ Stopwatch watch = Stopwatch.StartNew(); string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); - using (Image image = new Image(stream)) + Image image = new Image(stream); using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}")) { image.Resize(image.Width / 3, 0, new TriangleResampler(), false, this.ProgressUpdate) @@ -178,7 +177,7 @@ Stopwatch watch = Stopwatch.StartNew(); string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); - using (Image image = new Image(stream)) + Image image = new Image(stream); using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}")) { image.Resize(0, image.Height / 3, new TriangleResampler(), false, this.ProgressUpdate) @@ -205,7 +204,7 @@ Stopwatch watch = Stopwatch.StartNew(); string filename = Path.GetFileName(file); - using (Image image = new Image(stream)) + Image image = new Image(stream); using (FileStream output = File.OpenWrite($"TestOutput/ResizeCrop/{filename}")) { ResizeOptions options = new ResizeOptions() @@ -237,7 +236,7 @@ Stopwatch watch = Stopwatch.StartNew(); string filename = Path.GetFileName(file); - using (Image image = new Image(stream)) + Image image = new Image(stream); using (FileStream output = File.OpenWrite($"TestOutput/ResizePad/{filename}")) { ResizeOptions options = new ResizeOptions() @@ -270,7 +269,7 @@ Stopwatch watch = Stopwatch.StartNew(); string filename = Path.GetFileName(file); - using (Image image = new Image(stream)) + Image image = new Image(stream); using (FileStream output = File.OpenWrite($"TestOutput/ResizeBoxPad/{filename}")) { ResizeOptions options = new ResizeOptions() @@ -303,7 +302,7 @@ Stopwatch watch = Stopwatch.StartNew(); string filename = Path.GetFileName(file); - using (Image image = new Image(stream)) + Image image = new Image(stream); using (FileStream output = File.OpenWrite($"TestOutput/ResizeMax/{filename}")) { ResizeOptions options = new ResizeOptions() @@ -337,7 +336,7 @@ Stopwatch watch = Stopwatch.StartNew(); string filename = Path.GetFileName(file); - using (Image image = new Image(stream)) + Image image = new Image(stream); using (FileStream output = File.OpenWrite($"TestOutput/ResizeMin/{filename}")) { ResizeOptions options = new ResizeOptions() @@ -370,7 +369,7 @@ Stopwatch watch = Stopwatch.StartNew(); string filename = Path.GetFileName(file); - using (Image image = new Image(stream)) + Image image = new Image(stream); using (FileStream output = File.OpenWrite($"TestOutput/ResizeStretch/{filename}")) { ResizeOptions options = new ResizeOptions() @@ -417,8 +416,6 @@ .Save(output); } - image.Dispose(); - Trace.WriteLine($"{filename}: {watch.ElapsedMilliseconds}ms"); } } @@ -440,7 +437,7 @@ Stopwatch watch = Stopwatch.StartNew(); string filename = Path.GetFileNameWithoutExtension(file) + "-" + rotateType + flipType + Path.GetExtension(file); - using (Image image = new Image(stream)) + Image image = new Image(stream); using (FileStream output = File.OpenWrite($"TestOutput/RotateFlip/{filename}")) { image.RotateFlip(rotateType, flipType, this.ProgressUpdate) @@ -468,7 +465,7 @@ string filename = Path.GetFileName(file); - using (Image image = new Image(stream)) + Image image = new Image(stream); using (FileStream output = File.OpenWrite($"TestOutput/Rotate/{filename}")) { image.Rotate(63, this.ProgressUpdate) @@ -497,7 +494,7 @@ string filename = Path.GetFileName(file); - using (Image image = new Image(stream)) + Image image = new Image(stream); using (FileStream output = File.OpenWrite($"TestOutput/Skew/{filename}")) { image.Skew(20, 10, this.ProgressUpdate) @@ -523,7 +520,7 @@ { string filename = Path.GetFileNameWithoutExtension(file) + "-EntropyCrop" + Path.GetExtension(file); - using (Image image = new Image(stream)) + Image image = new Image(stream); using (FileStream output = File.OpenWrite($"TestOutput/EntropyCrop/{filename}")) { image.EntropyCrop(.5f, this.ProgressUpdate).Save(output); @@ -547,7 +544,7 @@ string filename = Path.GetFileNameWithoutExtension(file) + "-Crop" + Path.GetExtension(file); - using (Image image = new Image(stream)) + Image image = new Image(stream); using (FileStream output = File.OpenWrite($"TestOutput/Crop/{filename}")) { image.Crop(image.Width / 2, image.Height / 2, this.ProgressUpdate).Save(output);