From ded3de02c381fc358e0bf87604fbf7cf6d47cf0c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 22 Oct 2015 11:50:59 +1100 Subject: [PATCH] Fixed resize. :smile: Former-commit-id: b76ea5a37afec493edc37fa84af3f2e7407498a4 Former-commit-id: 1e557c3c26bd777ba0f16f2d2b87fe10b3aa1073 Former-commit-id: 53e49322bc6608b6d2dbb4a9a54473c093c69a5c --- src/ImageProcessor/ParallelImageProcessor.cs | 15 +- .../Samplers/ImageSampleExtensions.cs | 4 +- src/ImageProcessor/Samplers/Resize.cs | 155 ++++++------------ .../Processors/Samplers/SamplerTests.cs | 2 +- 4 files changed, 63 insertions(+), 113 deletions(-) diff --git a/src/ImageProcessor/ParallelImageProcessor.cs b/src/ImageProcessor/ParallelImageProcessor.cs index 72722dd91..8d7ba13dc 100644 --- a/src/ImageProcessor/ParallelImageProcessor.cs +++ b/src/ImageProcessor/ParallelImageProcessor.cs @@ -21,7 +21,7 @@ namespace ImageProcessor /// public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) { - this.OnApply(); + this.OnApply(target.Bounds, sourceRectangle); if (this.Parallelism > 1) { @@ -53,8 +53,6 @@ namespace ImageProcessor /// public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle)) { - this.OnApply(); - byte[] pixels = new byte[width * height * 4]; target.SetPixels(width, height, pixels); @@ -68,6 +66,8 @@ namespace ImageProcessor sourceRectangle = source.Bounds; } + this.OnApply(target.Bounds, sourceRectangle); + if (this.Parallelism > 1) { int partitionCount = this.Parallelism; @@ -98,7 +98,14 @@ namespace ImageProcessor /// /// This method is called before the process is applied to prepare the processor. /// - protected virtual void OnApply() + /// + /// 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. + /// + protected virtual void OnApply(Rectangle targetRectangle, Rectangle sourceRectangle) { } diff --git a/src/ImageProcessor/Samplers/ImageSampleExtensions.cs b/src/ImageProcessor/Samplers/ImageSampleExtensions.cs index ddd702c4a..a31355e6a 100644 --- a/src/ImageProcessor/Samplers/ImageSampleExtensions.cs +++ b/src/ImageProcessor/Samplers/ImageSampleExtensions.cs @@ -19,7 +19,7 @@ namespace ImageProcessor.Samplers /// The public static Image Resize(this Image source, int width, int height) { - return source.Process(width, height, default(Rectangle), default(Rectangle), new Resize(new BicubicResampler())); + return Resize(source, width, height, new RobidouxResampler()); } /// @@ -32,7 +32,7 @@ namespace ImageProcessor.Samplers /// The public static Image Resize(this Image source, int width, int height, IResampler sampler) { - return source.Process(width, height, default(Rectangle), default(Rectangle), new Resize(sampler)); + return Resize(source, width, height, sampler, source.Bounds, new Rectangle(0, 0, width, height)); } /// diff --git a/src/ImageProcessor/Samplers/Resize.cs b/src/ImageProcessor/Samplers/Resize.cs index da6207566..7e7e80557 100644 --- a/src/ImageProcessor/Samplers/Resize.cs +++ b/src/ImageProcessor/Samplers/Resize.cs @@ -10,12 +10,6 @@ namespace ImageProcessor.Samplers /// /// Provides methods that allow the resizing of images using various resampling algorithms. - /// - /// TODO: There is a bug in this class. Whenever the processor is set to use parallel processing, the output image becomes distorted - /// at the join points when startY is greater than 0. Uncomment the Parallelism overload and run the ImageShouldResize method in the SamplerTests - /// class to see the error manifest. - /// It is imperative that the issue is solved or resampling will be too slow to be practical and the project will have to cease. - /// /// public class Resize : ParallelImageProcessor { @@ -24,6 +18,16 @@ namespace ImageProcessor.Samplers /// private const float Epsilon = 0.0001f; + /// + /// The horizontal weights. + /// + private Weights[] horizontalWeights; + + /// + /// The vertical weights. + /// + private Weights[] verticalWeights; + /// /// Initializes a new instance of the class. /// @@ -37,97 +41,74 @@ namespace ImageProcessor.Samplers this.Sampler = sampler; } - /// - public override int Parallelism => 1; // Uncomment this to see bug. - /// /// 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) + protected override void OnApply(Rectangle targetRectangle, Rectangle sourceRectangle) { - int sourceWidth = source.Width; - int sourceHeight = source.Height; - - int width = target.Width; - int height = target.Height; + this.horizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width); + this.verticalWeights = this.PrecomputeWeights(targetRectangle.Height, sourceRectangle.Height); + } + /// + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { int targetY = targetRectangle.Y; + int targetBottom = targetRectangle.Bottom; int startX = targetRectangle.X; int endX = targetRectangle.Right; - // Scaling factors - double heightFactor = sourceHeight / (double)targetRectangle.Height; - int targetSectionHeight = endY - startY; - int sourceSectionHeight = (int)((targetSectionHeight * heightFactor) + .5); - - int offsetY = this.CalculateOffset(startY, targetSectionHeight, sourceSectionHeight); - int offsetX = this.CalculateOffset(startX, targetRectangle.Width, sourceRectangle.Width); - Weights[] horizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width); - Weights[] verticalWeights = this.PrecomputeWeights(targetSectionHeight, sourceSectionHeight); - - // 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) + if (y >= targetY && y < targetBottom) { - List verticalValues = verticalWeights[y - startY].Values; - double verticalSum = verticalWeights[y - startY].Sum; + List verticalValues = this.verticalWeights[y].Values; + double verticalSum = this.verticalWeights[y].Sum; for (int x = startX; x < endX; x++) { - if (x >= 0 && x < width) + List horizontalValues = this.horizontalWeights[x].Values; + double horizontalSum = this.horizontalWeights[x].Sum; + + // Destination color components + double r = 0; + double g = 0; + double b = 0; + double a = 0; + + foreach (Weight yw in verticalValues) { - List horizontalValues = horizontalWeights[x - startX].Values; - double horizontalSum = horizontalWeights[x - startX].Sum; + if (Math.Abs(yw.Value) < Epsilon) + { + continue; + } - // Destination color components - double r = 0; - double g = 0; - double b = 0; - double a = 0; + int originY = yw.Index; - foreach (Weight yw in verticalValues) + foreach (Weight xw in horizontalValues) { - if (Math.Abs(yw.Value) < Epsilon) + if (Math.Abs(xw.Value) < Epsilon) { continue; } - // TODO: This offset is wrong. - int originY = offsetY == 0 ? yw.Index : yw.Index + offsetY; - originY = originY.Clamp(0, maxHeight); + int originX = xw.Index; + Bgra sourceColor = source[originX, originY]; + sourceColor = PixelOperations.ToLinear(sourceColor); - foreach (Weight xw in horizontalValues) - { - if (Math.Abs(xw.Value) < Epsilon) - { - continue; - } - - // TODO: This offset is wrong. - int originX = xw.Index + offsetX; - originX = originX.Clamp(0, maxWidth); - - Bgra sourceColor = source[originX, originY]; - sourceColor = PixelOperations.ToLinear(sourceColor); - - 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); - } + 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; } + + Bgra destinationColor = new Bgra(b.ToByte(), g.ToByte(), r.ToByte(), a.ToByte()); + destinationColor = PixelOperations.ToSrgb(destinationColor); + target[x, y] = destinationColor; } } } @@ -192,44 +173,6 @@ namespace ImageProcessor.Samplers return result; } - /// - /// Calculates the scaled offset caused by parallelism. - /// - /// The offset position. - /// The destination size. - /// The source size. - /// - /// The . - /// - private int CalculateOffset(int offset, int destinationSize, int sourceSize) - { - if (offset == 0) - { - return 0; - } - - IResampler sampler = this.Sampler; - double du = sourceSize / (double)destinationSize; - double scale = du; - - if (scale < 1) - { - scale = 1; - } - - double ru = Math.Ceiling(scale * sampler.Radius); - - double fu = ((offset + .5) * du) - 0.5; - int result = (int)Math.Ceiling(fu - ru); - - if (result < 0) - { - return 0; - } - - return result; - } - /// /// Represents the weight to be added to a scaled pixel. /// diff --git a/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs index 0ce273346..9f5c9cccc 100644 --- a/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs +++ b/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs @@ -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(image.Width / 2, image.Height / 2, sampler).Save(output); } Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms");