diff --git a/src/ImageProcessor/Filters/Contrast.cs b/src/ImageProcessor/Filters/Contrast.cs index 291b1c6d2..6c3336fca 100644 --- a/src/ImageProcessor/Filters/Contrast.cs +++ b/src/ImageProcessor/Filters/Contrast.cs @@ -30,19 +30,14 @@ namespace ImageProcessor.Filters /// public int Value { get; } - protected override void Apply(ImageBase source, ImageBase target, Rectangle sourceRectangle, Rectangle targetRectangle, int startY, int endY) - { - throw new NotImplementedException(); - } - /// - protected override void Apply(ImageBase target, ImageBase source, Rectangle rectangle, int startY, int endY) + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { double contrast = (100.0 + this.Value) / 100.0; for (int y = startY; y < endY; y++) { - for (int x = rectangle.X; x < rectangle.Right; x++) + for (int x = sourceRectangle.X; x < sourceRectangle.Right; x++) { Bgra color = source[x, y]; diff --git a/src/ImageProcessor/IImageProcessor.cs b/src/ImageProcessor/IImageProcessor.cs index a9eea7dbd..696348989 100644 --- a/src/ImageProcessor/IImageProcessor.cs +++ b/src/ImageProcessor/IImageProcessor.cs @@ -11,12 +11,12 @@ namespace ImageProcessor public interface IImageProcessor { /// - /// Apply a process to an image to alter the pixels at the area of the specified rectangle. + /// Applies the process to the specified portion of the specified . /// /// Target image to apply the process to. /// The source image. Cannot be null. - /// - /// The rectangle, which defines the area of the image where the process should be applied to. + /// + /// The structure that specifies the portion of the image object to draw. /// /// /// The method keeps the source image unchanged and returns the @@ -26,10 +26,29 @@ namespace ImageProcessor /// is null or is null. /// /// - /// doesnt fit the dimension of the image. + /// doesnt fit the dimension of the image. /// - void Apply(ImageBase target, ImageBase source, Rectangle rectangle); + void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle); + /// + /// Applies the process to the specified portion of the specified at the specified + /// location and with the specified size. + /// + /// Target image to apply the process to. + /// The source image. Cannot be null. + /// The target width. + /// The target height. + /// + /// The structure that specifies the location and size of the drawn image. + /// The image is scaled to fit the rectangle. + /// + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// + /// The method keeps the source image unchanged and returns the + /// the result of image process as new image. + /// void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle); } } diff --git a/src/ImageProcessor/ImageExtensions.cs b/src/ImageProcessor/ImageExtensions.cs index 1b5196580..bfdd7ace9 100644 --- a/src/ImageProcessor/ImageExtensions.cs +++ b/src/ImageProcessor/ImageExtensions.cs @@ -55,22 +55,26 @@ namespace ImageProcessor /// The image this method extends. /// Any processors to apply to the image. /// The . - public static Image Process(this Image source, params IImageProcessor[] processors) => Process(source, source.Bounds, processors); + public static Image Process(this Image source, params IImageProcessor[] processors) + { + return Process(source, source.Bounds, processors); + } /// /// Applies the collection of processors to the image. /// /// The image this method extends. - /// - /// The rectangle defining the bounds of the pixels the image filter with adjust. + /// + /// The structure that specifies the portion of the image object to draw. + /// /// Any processors to apply to the image. /// The . - public static Image Process(this Image source, Rectangle rectangle, params IImageProcessor[] processors) + public static Image Process(this Image source, Rectangle sourceRectangle, params IImageProcessor[] processors) { // ReSharper disable once LoopCanBeConvertedToQuery foreach (IImageProcessor filter in processors) { - source = PerformAction(source, true, (sourceImage, targetImage) => filter.Apply(targetImage, sourceImage, rectangle)); + source = PerformAction(source, true, (sourceImage, targetImage) => filter.Apply(targetImage, sourceImage, sourceRectangle)); } return source; @@ -79,13 +83,29 @@ namespace ImageProcessor /// /// Applies the collection of processors to the image. /// - /// The image this method extends. - /// - /// The rectangle defining the bounds of the pixels the image filter with adjust. - /// - /// - /// - /// + /// The source image. Cannot be null. + /// The target image width. + /// The target image width. + /// Any processors to apply to the image. + /// The . + public static Image Process(this Image source, int width, int height, params IImageProcessor[] processors) + { + return Process(source, width, height, source.Bounds, default(Rectangle), processors); + } + + /// + /// Applies the collection of processors to the image. + /// + /// The source image. Cannot be null. + /// The target image width. + /// The target image width. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// + /// The structure that specifies the location and size of the drawn image. + /// The image is scaled to fit the rectangle. + /// /// Any processors to apply to the image. /// The . public static Image Process(this Image source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, params IImageProcessor[] processors) diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index e8731d2fe..1207c97ff 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -180,7 +180,7 @@ - + @@ -196,6 +196,7 @@ + diff --git a/src/ImageProcessor/ParallelImageProcessor.cs b/src/ImageProcessor/ParallelImageProcessor.cs index b1533cf65..ea913776c 100644 --- a/src/ImageProcessor/ParallelImageProcessor.cs +++ b/src/ImageProcessor/ParallelImageProcessor.cs @@ -19,7 +19,7 @@ namespace ImageProcessor public int Parallelism { get; set; } = Environment.ProcessorCount; /// - public void Apply(ImageBase target, ImageBase source, Rectangle rectangle) + public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) { this.OnApply(); @@ -34,11 +34,11 @@ namespace ImageProcessor int current = p; tasks[p] = Task.Run(() => { - int batchSize = rectangle.Height / partitionCount; - int yStart = rectangle.Y + (current * batchSize); - int yEnd = current == partitionCount - 1 ? rectangle.Bottom : yStart + batchSize; + int batchSize = sourceRectangle.Height / partitionCount; + int yStart = sourceRectangle.Y + (current * batchSize); + int yEnd = current == partitionCount - 1 ? sourceRectangle.Bottom : yStart + batchSize; - this.Apply(target, source, rectangle, yStart, yEnd); + this.Apply(target, source, target.Bounds, sourceRectangle, yStart, yEnd); }); } @@ -46,7 +46,7 @@ namespace ImageProcessor } else { - this.Apply(target, source, rectangle, rectangle.Y, rectangle.Bottom); + this.Apply(target, source, target.Bounds, sourceRectangle, sourceRectangle.Y, sourceRectangle.Bottom); } } @@ -102,22 +102,25 @@ namespace ImageProcessor { } - protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY); - /// - /// Apply a process to an image to alter the pixels at the area of the specified rectangle. + /// Applies the process to the specified portion of the specified at the specified location + /// and with the specified size. /// /// Target image to apply the process to. /// The source image. Cannot be null. - /// - /// The rectangle, which defines the area of the image where the process should be applied to. + /// + /// The structure that specifies the location and size of the drawn image. + /// The image is scaled to fit the rectangle. /// - /// The index of the row within the image to start processing. - /// The index of the row within the image to end processing. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// The index of the row within the source image to start processing. + /// The index of the row within the source image to end processing. /// /// The method keeps the source image unchanged and returns the - /// the result of image processing filter as new image. + /// the result of image process as new image. /// - protected abstract void Apply(ImageBase target, ImageBase source, Rectangle rectangle, int startY, int endY); + protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY); } } diff --git a/src/ImageProcessor/Samplers/Lanczos3Resampler.cs b/src/ImageProcessor/Samplers/Lanczos5Resampler.cs similarity index 90% rename from src/ImageProcessor/Samplers/Lanczos3Resampler.cs rename to src/ImageProcessor/Samplers/Lanczos5Resampler.cs index 9593c1aea..1d7008d58 100644 --- a/src/ImageProcessor/Samplers/Lanczos3Resampler.cs +++ b/src/ImageProcessor/Samplers/Lanczos5Resampler.cs @@ -9,10 +9,10 @@ namespace ImageProcessor.Samplers /// The function implements the Lanczos kernel algorithm as described on /// Wikipedia /// - public class Lanczos3Resampler : IResampler + public class Lanczos5Resampler : IResampler { /// - public double Radius => 3; + public double Radius => 5; /// public double GetValue(double x) diff --git a/src/ImageProcessor/Samplers/Resize.cs b/src/ImageProcessor/Samplers/Resize.cs index 26dc44d7d..3dc3b166f 100644 --- a/src/ImageProcessor/Samplers/Resize.cs +++ b/src/ImageProcessor/Samplers/Resize.cs @@ -1,7 +1,15 @@ -using System; +// +// Copyright © James South and contributors. +// Licensed under the Apache License, Version 2.0. +// namespace ImageProcessor.Samplers { + using System; + + /// + /// Provides methods that allow the resizing of images using various resampling algorithms. + /// public class Resize : ParallelImageProcessor { /// @@ -23,13 +31,7 @@ namespace ImageProcessor.Samplers public IResampler Sampler { get; } /// - 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) { int sourceWidth = source.Width; int sourceHeight = source.Height; @@ -126,11 +128,5 @@ namespace ImageProcessor.Samplers } } } - - /// - protected override void Apply(ImageBase target, ImageBase source, Rectangle rectangle, int startY, int endY) - { - throw new NotImplementedException(); - } } } diff --git a/tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs b/tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs deleted file mode 100644 index 74c8ab304..000000000 --- a/tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace ImageProcessor.Tests -{ - using System.Diagnostics; - using System.IO; - - using Formats; - - using Xunit; - - public class EncoderDecoderTests - { - [Theory] - [InlineData("../../TestImages/Formats/Jpg/Backdrop.jpg")] - [InlineData("../../TestImages/Formats/Gif/a.gif")] - [InlineData("../../TestImages/Formats/Gif/leaf.gif")] - [InlineData("../../TestImages/Formats/Gif/ani.gif")] - [InlineData("../../TestImages/Formats/Gif/ani2.gif")] - [InlineData("../../TestImages/Formats/Gif/giphy.gif")] - [InlineData("../../TestImages/Formats/Bmp/Car.bmp")] - [InlineData("../../TestImages/Formats/Png/cmyk.png")] - public void DecodeThenEncodeImageFromStreamShouldSucceed(string filename) - { - if (!Directory.Exists("Encoded")) - { - Directory.CreateDirectory("Encoded"); - } - - FileStream stream = File.OpenRead(filename); - Stopwatch watch = Stopwatch.StartNew(); - Image image = new Image(stream); - - string encodedFilename = "Encoded/" + Path.GetFileName(filename); - - using (FileStream output = File.OpenWrite(encodedFilename)) - { - image.Save(output); - } - - Trace.WriteLine($"{filename} : {watch.ElapsedMilliseconds}ms"); - } - - [Theory] - [InlineData("../../TestImages/Formats/Jpg/Backdrop.jpg")] - [InlineData("../../TestImages/Formats/Bmp/Car.bmp")] - [InlineData("../../TestImages/Formats/Png/cmyk.png")] - public void QuantizedImageShouldPreserveMaximumColorPrecision(string filename) - { - if (!Directory.Exists("Quantized")) - { - Directory.CreateDirectory("Quantized"); - } - - Image image = new Image(File.OpenRead(filename)); - IQuantizer quantizer = new OctreeQuantizer(); - QuantizedImage quantizedImage = quantizer.Quantize(image); - - using (FileStream output = File.OpenWrite($"Quantized/{ Path.GetFileName(filename) }")) - { - quantizedImage.ToImage().Save(output); - } - } - } -} \ No newline at end of file diff --git a/tests/ImageProcessor.Tests/ImageProcessor.Tests.csproj b/tests/ImageProcessor.Tests/ImageProcessor.Tests.csproj index 477b1b909..fd13cac0e 100644 --- a/tests/ImageProcessor.Tests/ImageProcessor.Tests.csproj +++ b/tests/ImageProcessor.Tests/ImageProcessor.Tests.csproj @@ -53,14 +53,15 @@ - - + + + - + diff --git a/tests/ImageProcessor.Tests/ImageProcessor.Tests.csproj.DotSettings b/tests/ImageProcessor.Tests/ImageProcessor.Tests.csproj.DotSettings index 0b25413f0..cb4957f1e 100644 --- a/tests/ImageProcessor.Tests/ImageProcessor.Tests.csproj.DotSettings +++ b/tests/ImageProcessor.Tests/ImageProcessor.Tests.csproj.DotSettings @@ -1,4 +1,9 @@  True True - True \ No newline at end of file + True + True + True + True + True + True \ No newline at end of file diff --git a/tests/ImageProcessor.Tests/Filters/FilterTests.cs b/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs similarity index 78% rename from tests/ImageProcessor.Tests/Filters/FilterTests.cs rename to tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs index 9eed6a977..dac009328 100644 --- a/tests/ImageProcessor.Tests/Filters/FilterTests.cs +++ b/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs @@ -1,5 +1,5 @@  -namespace ImageProcessor.Tests.Filters +namespace ImageProcessor.Tests { using System.Collections.Generic; using System.Diagnostics; @@ -10,20 +10,8 @@ namespace ImageProcessor.Tests.Filters using Xunit; - public class FilterTests + public class FilterTests : ProcessorTestBase { - public static readonly List Files = new List - { - //{ "../../TestImages/Formats/Jpg/Backdrop.jpg"}, - //{ "../../TestImages/Formats/Bmp/Car.bmp" }, - //{ "../../TestImages/Formats/Png/cmyk.png" }, - //{ "../../TestImages/Formats/Gif/a.gif" }, - //{ "../../TestImages/Formats/Gif/leaf.gif" }, - { "../../TestImages/Formats/Gif/ani.gif" }, - //{ "../../TestImages/Formats/Gif/ani2.gif" }, - //{ "../../TestImages/Formats/Gif/giphy.gif" }, - }; - public static readonly TheoryData Filters = new TheoryData { { "Contrast-50", new Contrast(50) }, diff --git a/tests/ImageProcessor.Tests/Processors/Formats/EncoderDecoderTests.cs b/tests/ImageProcessor.Tests/Processors/Formats/EncoderDecoderTests.cs new file mode 100644 index 000000000..a1af332e8 --- /dev/null +++ b/tests/ImageProcessor.Tests/Processors/Formats/EncoderDecoderTests.cs @@ -0,0 +1,63 @@ +namespace ImageProcessor.Tests +{ + using System.Diagnostics; + using System.IO; + + using Formats; + + using Xunit; + + public class EncoderDecoderTests : ProcessorTestBase + { + [Fact] + public void DecodeThenEncodeImageFromStreamShouldSucceed() + { + if (!Directory.Exists("Encoded")) + { + Directory.CreateDirectory("Encoded"); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + Stopwatch watch = Stopwatch.StartNew(); + Image image = new Image(stream); + + string encodedFilename = "Encoded/" + Path.GetFileName(file); + + using (FileStream output = File.OpenWrite(encodedFilename)) + { + image.Save(output); + } + + Trace.WriteLine($"{file} : {watch.ElapsedMilliseconds}ms"); + } + } + } + + [Fact] + public void QuantizedImageShouldPreserveMaximumColorPrecision() + { + if (!Directory.Exists("Quantized")) + { + Directory.CreateDirectory("Quantized"); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + Image image = new Image(stream); + IQuantizer quantizer = new OctreeQuantizer(); + QuantizedImage quantizedImage = quantizer.Quantize(image); + + using (FileStream output = File.OpenWrite($"Quantized/{Path.GetFileName(file)}")) + { + quantizedImage.ToImage().Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs b/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs new file mode 100644 index 000000000..d8399d73f --- /dev/null +++ b/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs @@ -0,0 +1,32 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright © James South and contributors. +// Licensed under the Apache License, Version 2.0. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Tests +{ + using System.Collections.Generic; + + /// + /// The processor test base. + /// + public abstract class ProcessorTestBase + { + /// + /// The collection of image files to test against. + /// + public static readonly List Files = new List + { + "../../TestImages/Formats/Jpg/Backdrop.jpg", + "../../TestImages/Formats/Bmp/Car.bmp", + "../../TestImages/Formats/Png/cmyk.png", + "../../TestImages/Formats/Gif/leaf.gif" + + // { "../../TestImages/Formats/Gif/ani.gif" }, + // { "../../TestImages/Formats/Gif/ani2.gif" }, + // { "../../TestImages/Formats/Gif/giphy.gif" }, + }; + } +} diff --git a/tests/ImageProcessor.Tests/Samplers/SamplerTests.cs b/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs similarity index 61% rename from tests/ImageProcessor.Tests/Samplers/SamplerTests.cs rename to tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs index a9449fd9f..ab4511ad3 100644 --- a/tests/ImageProcessor.Tests/Samplers/SamplerTests.cs +++ b/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs @@ -1,38 +1,25 @@  -namespace ImageProcessor.Tests.Filters +namespace ImageProcessor.Tests { - using System.Collections.Generic; using System.Diagnostics; using System.IO; - using Samplers; + using ImageProcessor.Samplers; using Xunit; - public class SamplerTests + public class SamplerTests : ProcessorTestBase { - public static readonly List Files = new List - { - { "../../TestImages/Formats/Jpg/Backdrop.jpg" }, - { "../../TestImages/Formats/Bmp/Car.bmp" }, - { "../../TestImages/Formats/Png/cmyk.png" }, - //{ "../../TestImages/Formats/Gif/a.gif" }, - { "../../TestImages/Formats/Gif/leaf.gif" }, - //{ "../../TestImages/Formats/Gif/ani.gif" }, - //{ "../../TestImages/Formats/Gif/ani2.gif" }, - //{ "../../TestImages/Formats/Gif/giphy.gif" }, - }; - public static readonly TheoryData Samplers = new TheoryData { { "Bicubic", new BicubicResampler() }, - { "Lanczos3", new Lanczos3Resampler() } + //{ "Lanczos3", new Lanczos5Resampler() } }; [Theory] [MemberData("Samplers")] - public void ResizeImage(string name, IResampler sampler) + public void ImageShouldResize(string name, IResampler sampler) { if (!Directory.Exists("Resized")) { @@ -48,7 +35,7 @@ namespace ImageProcessor.Tests.Filters string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); using (FileStream output = File.OpenWrite($"Resized/{filename}")) { - image.Resize(900, 900, sampler).Save(output); + image.Resize(500, 500, sampler).Save(output); } Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms"); @@ -65,7 +52,7 @@ namespace ImageProcessor.Tests.Filters [InlineData(2, 0)] public static void Lanczos3WindowOscillatesCorrectly(double x, double expected) { - Lanczos3Resampler sampler = new Lanczos3Resampler(); + Lanczos5Resampler sampler = new Lanczos5Resampler(); double result = sampler.GetValue(x); Assert.Equal(result, expected); diff --git a/tests/ImageProcessor.Tests/TestImages/Formats/Gif/a.gif b/tests/ImageProcessor.Tests/TestImages/Formats/Gif/a.gif deleted file mode 100644 index d89e799a6..000000000 Binary files a/tests/ImageProcessor.Tests/TestImages/Formats/Gif/a.gif and /dev/null differ