From 889071abeb20f6e5e86d3dc2b703a679617b86f1 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 21 Oct 2015 08:20:15 +1100 Subject: [PATCH] Experimental resampler. Need y-axis fix Former-commit-id: 9c4e407244d5f3c4dca2acfdae80d4a1b09e91e2 Former-commit-id: df3578fe9a9da69ec0306cddc3427dd706dec866 Former-commit-id: 2142bb6dd2a6a2b959093598792335443f853651 --- src/ImageProcessor/ImageProcessor.csproj | 1 + src/ImageProcessor/Samplers/Resize - Copy.cs | 180 ++++++++++++++++++ src/ImageProcessor/Samplers/Resize.cs | 4 +- .../Processors/ProcessorTestBase.cs | 4 +- .../Processors/Samplers/SamplerTests.cs | 22 +-- 5 files changed, 196 insertions(+), 15 deletions(-) create mode 100644 src/ImageProcessor/Samplers/Resize - Copy.cs diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index 2cdf7b750..af3cae8eb 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -196,6 +196,7 @@ + diff --git a/src/ImageProcessor/Samplers/Resize - Copy.cs b/src/ImageProcessor/Samplers/Resize - Copy.cs new file mode 100644 index 000000000..65a07933f --- /dev/null +++ b/src/ImageProcessor/Samplers/Resize - Copy.cs @@ -0,0 +1,180 @@ +// +// 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 ce146186b..47125cfd3 100644 --- a/src/ImageProcessor/Samplers/Resize.cs +++ b/src/ImageProcessor/Samplers/Resize.cs @@ -10,7 +10,7 @@ namespace ImageProcessor.Samplers /// /// Provides methods that allow the resizing of images using various resampling algorithms. /// - public class Resize : ParallelImageProcessor + public class ResizeB : ParallelImageProcessor { /// /// The epsilon for comparing floating point numbers. @@ -23,7 +23,7 @@ namespace ImageProcessor.Samplers /// /// The sampler to perform the resize operation. /// - public Resize(IResampler sampler) + public ResizeB(IResampler sampler) { Guard.NotNull(sampler, nameof(sampler)); diff --git a/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs b/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs index 1804557fa..da88b7c94 100644 --- a/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs +++ b/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs @@ -19,8 +19,8 @@ namespace ImageProcessor.Tests /// public static readonly List Files = new List { - "../../TestImages/Formats/Jpg/Backdrop.jpg", - "../../TestImages/Formats/Jpg/Calliphora.jpg", + //"../../TestImages/Formats/Jpg/Backdrop.jpg", + //"../../TestImages/Formats/Jpg/Calliphora.jpg", //"../../TestImages/Formats/Bmp/Car.bmp", //"../../TestImages/Formats/Png/cmyk.png", //"../../TestImages/Formats/Gif/leaf.gif" diff --git a/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs index 9b15f8013..759095b33 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() }, + //{ "Bicubic", new BicubicResampler() }, + //{ "Bilinear", new TriangleResampler() }, { "NearestNeighbour", new BoxResampler() }, - { "Lanczos3", new Lanczos3Resampler() }, + //{ "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(100, 100, sampler).Save(output); + image.Resize(500, 500, sampler).Save(output); } Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms");