From cb6e3145ed0ad92f8a85f5e27b978e16df0ba1fd Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 8 Aug 2016 15:04:26 +1000 Subject: [PATCH] Fix resampler accuracy Former-commit-id: 35f3471c93647fb9cf7927cd758321154f06cdbb Former-commit-id: d90a5442769e89c655a4ada4a6ca36d07fa4bac5 Former-commit-id: 813a7f137bc4badeae043123dd51d02c8812e217 --- .../Processors/CompandingResizeProcessor.cs | 6 +- .../Processors/ResamplingWeightedProcessor.cs | 124 +++++++----------- .../Samplers/Processors/ResizeProcessor.cs | 6 +- .../ImageProcessorCore.Tests/FileTestBase.cs | 10 +- .../Processors/Samplers/ResizeTests.cs | 4 +- 5 files changed, 55 insertions(+), 95 deletions(-) diff --git a/src/ImageProcessorCore/Samplers/Processors/CompandingResizeProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/CompandingResizeProcessor.cs index 23887af23..9ebdd8917 100644 --- a/src/ImageProcessorCore/Samplers/Processors/CompandingResizeProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/CompandingResizeProcessor.cs @@ -112,13 +112,12 @@ namespace ImageProcessorCore.Processors { // Ensure offsets are normalised for cropping and padding. int offsetX = x - startX; - double sum = this.HorizontalWeights[offsetX].Sum; Weight[] horizontalValues = this.HorizontalWeights[offsetX].Values; // Destination color components Vector4 destination = Vector4.Zero; - for (int i = 0; i < sum; i++) + for (int i = 0; i < horizontalValues.Length; i++) { Weight xw = horizontalValues[i]; int originX = xw.Index; @@ -145,7 +144,6 @@ namespace ImageProcessorCore.Processors { // Ensure offsets are normalised for cropping and padding. int offsetY = y - startY; - double sum = this.VerticalWeights[offsetY].Sum; Weight[] verticalValues = this.VerticalWeights[offsetY].Values; for (int x = 0; x < width; x++) @@ -153,7 +151,7 @@ namespace ImageProcessorCore.Processors // Destination color components Vector4 destination = Vector4.Zero; - for (int i = 0; i < sum; i++) + for (int i = 0; i < verticalValues.Length; i++) { Weight yw = verticalValues[i]; int originY = yw.Index; diff --git a/src/ImageProcessorCore/Samplers/Processors/ResamplingWeightedProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/ResamplingWeightedProcessor.cs index ab41466cd..534e96aa2 100644 --- a/src/ImageProcessorCore/Samplers/Processors/ResamplingWeightedProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/ResamplingWeightedProcessor.cs @@ -6,6 +6,8 @@ namespace ImageProcessorCore.Processors { using System; + using System.Collections.Generic; + using System.Linq; /// /// Provides methods that allow the resizing of images using various algorithms. @@ -65,6 +67,7 @@ namespace ImageProcessorCore.Processors } } + /// /// Computes the weights to apply at each pixel when resizing. /// @@ -75,90 +78,56 @@ namespace ImageProcessorCore.Processors /// protected Weights[] PrecomputeWeights(int destinationSize, int sourceSize) { - float scale = (float)destinationSize / sourceSize; - IResampler sampler = this.Sampler; - float radius = sampler.Radius; - int left; - int right; - float weight; - int index; - int sum; + float ratio = (float)sourceSize / destinationSize; + float scale = ratio; + if (scale < 1F) + { + scale = 1F; + } + + IResampler sampler = this.Sampler; + float radius = (float)Math.Ceiling(scale * sampler.Radius); Weights[] result = new Weights[destinationSize]; - // When shrinking, broaden the effective kernel support so that we still - // visit every source pixel. - if (scale < 1F) + for (int i = 0; i < destinationSize; i++) { - float width = radius / scale; - float filterScale = 1F / scale; + float center = ((i + .5F) * ratio) - .5F; - // Make the weights slices, one source for each column or row. - for (int i = 0; i < destinationSize; i++) + // Keep inside bounds. + int left = (int)Math.Ceiling(center - radius); + if (left < 0) { - float centre = i / scale; - left = (int)Math.Ceiling(centre - width); - right = (int)Math.Floor(centre + width); - - result[i] = new Weights - { - Values = new Weight[(right - left + 1)] - }; + left = 0; + } - for (int j = left; j <= right; j++) - { - weight = sampler.GetValue((centre - j) / filterScale) / filterScale; - if (j < 0) - { - index = -j; - } - else if (j >= sourceSize) - { - index = (int)((sourceSize - (float)j) + sourceSize - 1); - } - else - { - index = j; - } - - sum = (int)result[i].Sum++; - result[i].Values[sum] = new Weight(index, weight); - } + int right = (int)Math.Floor(center + radius); + if (right > sourceSize - 1) + { + right = sourceSize - 1; } - } - else - { - // Make the weights slices, one source for each column or row. - for (int i = 0; i < destinationSize; i++) + + float sum = 0; + result[i] = new Weights(); + Weight[] weights = new Weight[right - left + 1]; + + for (int j = left; j <= right; j++) { - float centre = i / scale; - left = (int)Math.Ceiling(centre - radius); - right = (int)Math.Floor(centre + radius); - result[i] = new Weights - { - Values = new Weight[(right - left + 1)] - }; + float weight = sampler.GetValue((j - center) / scale); + sum += weight; + weights[j - left] = new Weight(j, weight); + } - for (int j = left; j <= right; j++) + // Normalise, best to do it here rather than in the pixel loop later on. + if (sum > 0) + { + for (int w = 0; w < weights.Length; w++) { - weight = sampler.GetValue(centre - (float)j); - if (j < 0) - { - index = -j; - } - else if (j >= sourceSize) - { - index = (sourceSize - j) + sourceSize - 1; - } - else - { - index = j; - } - - sum = (int)result[i].Sum++; - result[i].Values[sum] = new Weight(index, weight); + weights[w].Value = weights[w].Value / sum; } } + + result[i].Values = weights; } return result; @@ -167,10 +136,10 @@ namespace ImageProcessorCore.Processors /// /// Represents the weight to be added to a scaled pixel. /// - protected struct Weight + protected class Weight { /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the class. /// /// The index. /// The value. @@ -186,9 +155,9 @@ namespace ImageProcessorCore.Processors public int Index { get; } /// - /// Gets the result of the interpolation algorithm. + /// Gets or sets the result of the interpolation algorithm. /// - public float Value { get; } + public float Value { get; set; } } /// @@ -200,11 +169,6 @@ namespace ImageProcessorCore.Processors /// Gets or sets the values. /// public Weight[] Values { get; set; } - - /// - /// Gets or sets the sum. - /// - public float Sum { get; set; } } } } \ No newline at end of file diff --git a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs index da588c997..eae8bf1b9 100644 --- a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs @@ -110,13 +110,12 @@ namespace ImageProcessorCore.Processors { // Ensure offsets are normalised for cropping and padding. int offsetX = x - startX; - double sum = this.HorizontalWeights[offsetX].Sum; Weight[] horizontalValues = this.HorizontalWeights[offsetX].Values; // Destination color components Vector4 destination = Vector4.Zero; - for (int i = 0; i < sum; i++) + for (int i = 0; i < horizontalValues.Length; i++) { Weight xw = horizontalValues[i]; int originX = xw.Index; @@ -143,7 +142,6 @@ namespace ImageProcessorCore.Processors { // Ensure offsets are normalised for cropping and padding. int offsetY = y - startY; - double sum = this.VerticalWeights[offsetY].Sum; Weight[] verticalValues = this.VerticalWeights[offsetY].Values; for (int x = 0; x < width; x++) @@ -151,7 +149,7 @@ namespace ImageProcessorCore.Processors // Destination color components Vector4 destination = Vector4.Zero; - for (int i = 0; i < sum; i++) + for (int i = 0; i < verticalValues.Length; i++) { Weight yw = verticalValues[i]; int originY = yw.Index; diff --git a/tests/ImageProcessorCore.Tests/FileTestBase.cs b/tests/ImageProcessorCore.Tests/FileTestBase.cs index 48758f4d9..3eb5559ed 100644 --- a/tests/ImageProcessorCore.Tests/FileTestBase.cs +++ b/tests/ImageProcessorCore.Tests/FileTestBase.cs @@ -19,20 +19,20 @@ namespace ImageProcessorCore.Tests /// protected static readonly List Files = new List { - "TestImages/Formats/Png/pl.png", - "TestImages/Formats/Png/pd.png", + //"TestImages/Formats/Png/pl.png", + //"TestImages/Formats/Png/pd.png", //"TestImages/Formats/Jpg/Floorplan.jpeg", // Perf: Enable for local testing only - //"TestImages/Formats/Jpg/Calliphora.jpg", + "TestImages/Formats/Jpg/Calliphora.jpg", //"TestImages/Formats/Jpg/turtle.jpg", //"TestImages/Formats/Jpg/fb.jpg", // Perf: Enable for local testing only //"TestImages/Formats/Jpg/progress.jpg", // Perf: Enable for local testing only //"TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg", // Perf: Enable for local testing only - //"TestImages/Formats/Bmp/Car.bmp", + "TestImages/Formats/Bmp/Car.bmp", // "TestImages/Formats/Bmp/neg_height.bmp", // Perf: Enable for local testing only //"TestImages/Formats/Png/blur.png", // Perf: Enable for local testing only //"TestImages/Formats/Png/indexed.png", // Perf: Enable for local testing only //TestImages/Formats/Png/splash.png", - //"TestImages/Formats/Gif/rings.gif", + "TestImages/Formats/Gif/rings.gif", //"TestImages/Formats/Gif/giphy.gif" // Perf: Enable for local testing only }; diff --git a/tests/ImageProcessorCore.Tests/Processors/Samplers/ResizeTests.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/ResizeTests.cs index fdd5496cb..03812a886 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Samplers/ResizeTests.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/ResizeTests.cs @@ -51,8 +51,8 @@ namespace ImageProcessorCore.Tests Image image = new Image(stream); using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - //image.Resize(image.Width / 2, image.Height / 2, sampler, true, this.ProgressUpdate) - image.Resize(555, 15, sampler, true, this.ProgressUpdate) + image.Resize(image.Width / 2, image.Height / 2, sampler, true, this.ProgressUpdate) + //image.Resize(555, 275, sampler, false, this.ProgressUpdate) .Save(output); } }