diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index af3cae8ebc..a7ec6d89a8 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -196,8 +196,8 @@ - + diff --git a/src/ImageProcessor/ParallelImageProcessor.cs b/src/ImageProcessor/ParallelImageProcessor.cs index ea913776c9..fa224b44ae 100644 --- a/src/ImageProcessor/ParallelImageProcessor.cs +++ b/src/ImageProcessor/ParallelImageProcessor.cs @@ -16,7 +16,7 @@ namespace ImageProcessor /// /// Gets or sets the count of workers to run the process in parallel. /// - public int Parallelism { get; set; } = Environment.ProcessorCount; + public virtual int Parallelism { get; set; } = Environment.ProcessorCount; /// public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) @@ -67,7 +67,7 @@ namespace ImageProcessor { sourceRectangle = source.Bounds; } - + this.Parallelism = 1; if (this.Parallelism > 1) { int partitionCount = this.Parallelism; diff --git a/src/ImageProcessor/Samplers/Resize - Copy.cs b/src/ImageProcessor/Samplers/Resize - Copy.cs deleted file mode 100644 index 65a07933f3..0000000000 --- a/src/ImageProcessor/Samplers/Resize - Copy.cs +++ /dev/null @@ -1,180 +0,0 @@ -// -// Copyright © James South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessor.Samplers -{ - using System; - using System.Collections.Generic; - - /// - /// Provides methods that allow the resizing of images using various resampling algorithms. - /// - public class Resize : ParallelImageProcessor - { - /// - /// The epsilon for comparing floating point numbers. - /// - private const float Epsilon = 0.0001f; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The sampler to perform the resize operation. - /// - public Resize(IResampler sampler) - { - Guard.NotNull(sampler, nameof(sampler)); - - this.Sampler = sampler; - } - - /// - /// Gets the sampler to perform the resize operation. - /// - public IResampler Sampler { get; } - - /// - protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - int sourceWidth = source.Width; - int sourceHeight = source.Height; - - int width = target.Width; - int height = target.Height; - - int targetY = targetRectangle.Y; - int startX = targetRectangle.X; - int endX = targetRectangle.Right; - - // Scaling factors - double widthFactor = sourceWidth / (double)targetRectangle.Width; - double heightFactor = sourceHeight / (double)targetRectangle.Height; - int sectionHeight = endY - startY; - - Weights[] horizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width, this.Sampler); - Weights[] verticalWeights = this.PrecomputeWeights(sectionHeight, (int)((sectionHeight * heightFactor) + .5), this.Sampler); - - // Width and height decreased by 1 - int maxHeight = sourceHeight - 1; - int maxWidth = sourceWidth - 1; - - for (int y = startY; y < endY; y++) - { - List verticalValues = verticalWeights[y - startY].Values; - double verticalSum = verticalWeights[y - startY].Sum; - - for (int x = startX; x < endX; x++) - { - List horizontalValues = horizontalWeights[x - startX].Values; - double horizontalSum = horizontalWeights[x - startX].Sum; - - - // Destination color components - double r = 0; - double g = 0; - double b = 0; - double a = 0; - - foreach (Weight yw in verticalValues) - { - int originY = yw.Index + startY; //- targetY;//(int)(((y - targetY) * heightFactor) - 0.5); - originY = originY.Clamp(0, maxHeight); - - foreach (Weight xw in horizontalValues) - { - int originX = xw.Index;//(int)(((x - startX) * widthFactor) - 0.5); - originX = originX.Clamp(0, maxWidth); - - Bgra sourceColor = source[originX, originY]; - r += sourceColor.R * (yw.Value / verticalSum) * (xw.Value / horizontalSum); - g += sourceColor.G * (yw.Value / verticalSum) * (xw.Value / horizontalSum); - b += sourceColor.B * (yw.Value / verticalSum) * (xw.Value / horizontalSum); - a += sourceColor.A * (yw.Value / verticalSum) * (xw.Value / horizontalSum); - } - } - - Bgra destinationColor = new Bgra(b.ToByte(), g.ToByte(), r.ToByte(), a.ToByte()); - target[x, y] = destinationColor; - } - } - } - - private Weights[] PrecomputeWeights(int dstSize, int srcSize, IResampler sampler) - { - float du = srcSize / (float)dstSize; - float scale = du; - - if (scale < 1) - { - scale = 1; - } - - double ru = Math.Ceiling(scale * sampler.Radius); - Weights[] result = new Weights[dstSize]; - - for (int i = 0; i < dstSize; i++) - { - double fu = ((i + .5) * du) - 0.5; - int startU = (int)Math.Ceiling(fu - ru); - - if (startU < 0) - { - startU = 0; - } - - int endU = (int)Math.Floor(fu + ru); - - if (endU > srcSize - 1) - { - endU = srcSize - 1; - } - - double sum = 0; - result[i] = new Weights(); - - for (int a = startU; a <= endU; a++) - { - double w = 255 * sampler.GetValue((a - fu) / scale); - - if (Math.Abs(w) > Epsilon) - { - sum += w; - result[i].Values.Add(new Weight(a, w)); - } - } - - result[i].Sum = sum; - } - - return result; - } - - public struct Weight - { - public Weight(int index, double value) - { - this.Index = index; - this.Value = value; - } - - public readonly int Index; - - public readonly double Value; - } - - public class Weights - { - public Weights() - { - this.Values = new List(); - } - - public List Values { get; set; } - - public double Sum { get; set; } - } - } -} diff --git a/src/ImageProcessor/Samplers/Resize.cs b/src/ImageProcessor/Samplers/Resize.cs index 47125cfd3d..4ed61dc6a4 100644 --- a/src/ImageProcessor/Samplers/Resize.cs +++ b/src/ImageProcessor/Samplers/Resize.cs @@ -6,11 +6,12 @@ namespace ImageProcessor.Samplers { using System; + using System.Collections.Generic; /// /// Provides methods that allow the resizing of images using various resampling algorithms. /// - public class ResizeB : ParallelImageProcessor + public class Resize : ParallelImageProcessor { /// /// The epsilon for comparing floating point numbers. @@ -23,13 +24,16 @@ namespace ImageProcessor.Samplers /// /// The sampler to perform the resize operation. /// - public ResizeB(IResampler sampler) + public Resize(IResampler sampler) { Guard.NotNull(sampler, nameof(sampler)); this.Sampler = sampler; } + /// + public override int Parallelism => 1; // Uncomment this to see bug. + /// /// Gets the sampler to perform the resize operation. /// @@ -47,12 +51,14 @@ namespace ImageProcessor.Samplers int targetY = targetRectangle.Y; int startX = targetRectangle.X; int endX = targetRectangle.Right; - int right = (int)(this.Sampler.Radius + .5); - int left = (-right) + 1; // Scaling factors - double widthFactor = sourceWidth / (double)targetRectangle.Width; double heightFactor = sourceHeight / (double)targetRectangle.Height; + int targetSectionHeight = endY - startY; + int sourceSectionHeight = (int)((targetSectionHeight * heightFactor) + .5); + + Weights[] horizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width, this.Sampler); + Weights[] verticalWeights = this.PrecomputeWeights(targetSectionHeight, sourceSectionHeight, this.Sampler); // Width and height decreased by 1 int maxHeight = sourceHeight - 1; @@ -62,20 +68,15 @@ namespace ImageProcessor.Samplers { if (y >= 0 && y < height) { - // Y coordinates of source points. - double originY = ((y - targetY) * heightFactor) - 0.5; - int originY1 = (int)originY; - double dy = originY - originY1; + List verticalValues = verticalWeights[y - startY].Values; + double verticalSum = verticalWeights[y - startY].Sum; - // For each row. for (int x = startX; x < endX; x++) { if (x >= 0 && x < width) { - // X coordinates of source points. - double originX = ((x - startX) * widthFactor) - 0.5f; - int originX1 = (int)originX; - double dx = originX - originX1; + List horizontalValues = horizontalWeights[x - startX].Values; + double horizontalSum = horizontalWeights[x - startX].Sum; // Destination color components double r = 0; @@ -83,65 +84,132 @@ namespace ImageProcessor.Samplers double b = 0; double a = 0; - for (int yy = left; yy <= right; yy++) + foreach (Weight yw in verticalValues) { - // Get Y cooefficient - double kernel1 = this.Sampler.GetValue(yy - dy); - - if (Math.Abs(kernel1) < Epsilon) + if (Math.Abs(yw.Value) < Epsilon) { continue; } - int originY2 = originY1 + yy; - if (originY2 < 0) - { - originY2 = 0; - } - - if (originY2 > maxHeight) - { - originY2 = maxHeight; - } + // TODO: This is wrong. Adding (int)((startY * heightFactor) - .5) gets close but no cigar. + int originY = yw.Index + (int)((startY * heightFactor) - .5); + originY = originY.Clamp(0, maxHeight); - for (int xx = left; xx <= right; xx++) + foreach (Weight xw in horizontalValues) { - // Get X cooefficient - double kernel2 = kernel1 * this.Sampler.GetValue(xx - dx); - - if (Math.Abs(kernel2) < Epsilon) + if (Math.Abs(xw.Value) < Epsilon) { continue; } - int originX2 = originX1 + xx; - if (originX2 < 0) - { - originX2 = 0; - } - - if (originX2 > maxWidth) - { - originX2 = maxWidth; - } - - Bgra sourceColor = source[originX2, originY2]; - // sourceColor = PixelOperations.ToLinear(sourceColor); + // TODO: This need updating to take into account the target rectangle. + int originX = xw.Index; + originX = originX.Clamp(0, maxWidth); - r += kernel2 * sourceColor.R; - g += kernel2 * sourceColor.G; - b += kernel2 * sourceColor.B; - a += kernel2 * sourceColor.A; + Bgra sourceColor = source[originX, originY]; + r += sourceColor.R * (yw.Value / verticalSum) * (xw.Value / horizontalSum); + g += sourceColor.G * (yw.Value / verticalSum) * (xw.Value / horizontalSum); + b += sourceColor.B * (yw.Value / verticalSum) * (xw.Value / horizontalSum); + a += sourceColor.A * (yw.Value / verticalSum) * (xw.Value / horizontalSum); } } Bgra destinationColor = new Bgra(b.ToByte(), g.ToByte(), r.ToByte(), a.ToByte()); - // destinationColor = PixelOperations.ToSrgb(destinationColor); target[x, y] = destinationColor; } } } } } + + /// + /// Computes the weights to apply at each pixel when resizing. + /// + /// + /// The destination section size. + /// + /// + /// The source section size. + /// + /// + /// The containing the resampling algorithm. + /// + /// + /// The . + /// + private Weights[] PrecomputeWeights(int destinationSize, int sourceSize, IResampler sampler) + { + float du = sourceSize / (float)destinationSize; + float scale = du; + + if (scale < 1) + { + scale = 1; + } + + double ru = Math.Ceiling(scale * sampler.Radius); + Weights[] result = new Weights[destinationSize]; + + for (int i = 0; i < destinationSize; i++) + { + double fu = ((i + .5) * du) - 0.5; + int startU = (int)Math.Ceiling(fu - ru); + + if (startU < 0) + { + startU = 0; + } + + int endU = (int)Math.Floor(fu + ru); + + if (endU > sourceSize - 1) + { + endU = sourceSize - 1; + } + + double sum = 0; + result[i] = new Weights(); + + for (int a = startU; a <= endU; a++) + { + double w = 255 * sampler.GetValue((a - fu) / scale); + + if (Math.Abs(w) > Epsilon) + { + sum += w; + result[i].Values.Add(new Weight(a, w)); + } + } + + result[i].Sum = sum; + } + + return result; + } + + protected struct Weight + { + public Weight(int index, double value) + { + this.Index = index; + this.Value = value; + } + + public readonly int Index; + + public readonly double Value; + } + + protected class Weights + { + public Weights() + { + this.Values = new List(); + } + + public List Values { get; set; } + + public double Sum { get; set; } + } } } diff --git a/src/ImageProcessor/Samplers/ResizeOld.cs b/src/ImageProcessor/Samplers/ResizeOld.cs new file mode 100644 index 0000000000..47125cfd3d --- /dev/null +++ b/src/ImageProcessor/Samplers/ResizeOld.cs @@ -0,0 +1,147 @@ +// +// 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 ResizeB : ParallelImageProcessor + { + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.0001f; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The sampler to perform the resize operation. + /// + public ResizeB(IResampler sampler) + { + Guard.NotNull(sampler, nameof(sampler)); + + this.Sampler = sampler; + } + + /// + /// Gets the sampler to perform the resize operation. + /// + public IResampler Sampler { get; } + + /// + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + int sourceWidth = source.Width; + int sourceHeight = source.Height; + + int width = target.Width; + int height = target.Height; + + int targetY = targetRectangle.Y; + int startX = targetRectangle.X; + int endX = targetRectangle.Right; + int right = (int)(this.Sampler.Radius + .5); + int left = (-right) + 1; + + // Scaling factors + double widthFactor = sourceWidth / (double)targetRectangle.Width; + double heightFactor = sourceHeight / (double)targetRectangle.Height; + + // Width and height decreased by 1 + int maxHeight = sourceHeight - 1; + int maxWidth = sourceWidth - 1; + + for (int y = startY; y < endY; y++) + { + if (y >= 0 && y < height) + { + // Y coordinates of source points. + double originY = ((y - targetY) * heightFactor) - 0.5; + int originY1 = (int)originY; + double dy = originY - originY1; + + // For each row. + for (int x = startX; x < endX; x++) + { + if (x >= 0 && x < width) + { + // X coordinates of source points. + double originX = ((x - startX) * widthFactor) - 0.5f; + int originX1 = (int)originX; + double dx = originX - originX1; + + // Destination color components + double r = 0; + double g = 0; + double b = 0; + double a = 0; + + for (int yy = left; yy <= right; yy++) + { + // Get Y cooefficient + double kernel1 = this.Sampler.GetValue(yy - dy); + + if (Math.Abs(kernel1) < Epsilon) + { + continue; + } + + int originY2 = originY1 + yy; + if (originY2 < 0) + { + originY2 = 0; + } + + if (originY2 > maxHeight) + { + originY2 = maxHeight; + } + + for (int xx = left; xx <= right; xx++) + { + // Get X cooefficient + double kernel2 = kernel1 * this.Sampler.GetValue(xx - dx); + + if (Math.Abs(kernel2) < Epsilon) + { + continue; + } + + int originX2 = originX1 + xx; + if (originX2 < 0) + { + originX2 = 0; + } + + if (originX2 > maxWidth) + { + originX2 = maxWidth; + } + + Bgra sourceColor = source[originX2, originY2]; + // sourceColor = PixelOperations.ToLinear(sourceColor); + + r += kernel2 * sourceColor.R; + g += kernel2 * sourceColor.G; + b += kernel2 * sourceColor.B; + a += kernel2 * sourceColor.A; + } + } + + Bgra destinationColor = new Bgra(b.ToByte(), g.ToByte(), r.ToByte(), a.ToByte()); + // destinationColor = PixelOperations.ToSrgb(destinationColor); + target[x, y] = destinationColor; + } + } + } + } + } + } +} diff --git a/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs index 759095b336..881e45bdb3 100644 --- a/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs +++ b/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs @@ -13,18 +13,18 @@ namespace ImageProcessor.Tests public static readonly TheoryData Samplers = new TheoryData { - //{ "Bicubic", new BicubicResampler() }, - //{ "Bilinear", new TriangleResampler() }, - { "NearestNeighbour", new BoxResampler() }, - //{ "Lanczos3", new Lanczos3Resampler() }, + { "Bicubic", new BicubicResampler() }, + { "Triangle", new TriangleResampler() }, + { "Box", new BoxResampler() }, + { "Lanczos3", new Lanczos3Resampler() }, { "Lanczos8", new Lanczos8Resampler() }, - //{ "MitchellNetravali", new MitchellNetravaliResampler() }, - //{ "Hermite", new HermiteResampler() }, - //{ "Spline", new SplineResampler() }, - //{ "Robidoux", new RobidouxResampler() }, - //{ "RobidouxSharp", new RobidouxSharpResampler() }, - //{ "RobidouxSoft", new RobidouxSoftResampler() }, - //{ "Welch", new WelchResampler() } + { "MitchellNetravali", new MitchellNetravaliResampler() }, + { "Hermite", new HermiteResampler() }, + { "Spline", new SplineResampler() }, + { "Robidoux", new RobidouxResampler() }, + { "RobidouxSharp", new RobidouxSharpResampler() }, + { "RobidouxSoft", new RobidouxSoftResampler() }, + { "Welch", new WelchResampler() } }; [Theory] @@ -45,7 +45,7 @@ namespace ImageProcessor.Tests string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); using (FileStream output = File.OpenWrite($"Resized/{filename}")) { - image.Resize(500, 500, sampler).Save(output); + image.Resize(100, 100, sampler).Save(output); } Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms");