From 8b3afb334b8cbb708d3c113bbf1bf6b4cc0fd437 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 7 Oct 2015 18:41:58 +1100 Subject: [PATCH] Add Lanczos3 Former-commit-id: ffc9b65f1a7643d7a1581dc702d507ee247aab91 Former-commit-id: e079559f8b9293a40b86a946825d4cd734ef0e69 Former-commit-id: 7b795cc9803c78b1d2b434b4d7c1479ebd42809f --- .../Common/Helpers/ImageMaths.cs | 56 ++++++++++++++ src/ImageProcessor/ImageExtensions.cs | 2 +- src/ImageProcessor/ImageProcessor.csproj | 2 + .../Samplers/Lanczos3Resampler.cs | 33 +++++++++ .../Filters/FilterTests.cs | 4 +- .../ImageProcessor.Tests.csproj | 1 + .../Samplers/SamplerTests.cs | 74 +++++++++++++++++++ 7 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 src/ImageProcessor/Common/Helpers/ImageMaths.cs create mode 100644 src/ImageProcessor/Samplers/Lanczos3Resampler.cs create mode 100644 tests/ImageProcessor.Tests/Samplers/SamplerTests.cs diff --git a/src/ImageProcessor/Common/Helpers/ImageMaths.cs b/src/ImageProcessor/Common/Helpers/ImageMaths.cs new file mode 100644 index 0000000000..2e2f49bee4 --- /dev/null +++ b/src/ImageProcessor/Common/Helpers/ImageMaths.cs @@ -0,0 +1,56 @@ +// +// Copyright © James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor +{ + using System; + + /// + /// Provides common mathematical methods. + /// + internal static class ImageMaths + { + /// + /// Gets the result of a sine cardinal function for the given value. + /// + /// + /// The value to calculate the result for. + /// + /// + /// The . + /// + public static double SinC(double x) + { + const double Epsilon = .0001; + + if (Math.Abs(x) > Epsilon) + { + x *= Math.PI; + return Clean(Math.Sin(x) / x); + } + + return 1.0; + } + + /// + /// Ensures that any passed double is correctly rounded to zero + /// + /// The value to clean. + /// + /// The + /// . + private static double Clean(double x) + { + const double Epsilon = .0001; + + if (Math.Abs(x) < Epsilon) + { + return 0.0; + } + + return x; + } + } +} diff --git a/src/ImageProcessor/ImageExtensions.cs b/src/ImageProcessor/ImageExtensions.cs index 3544f00c6d..1b51965809 100644 --- a/src/ImageProcessor/ImageExtensions.cs +++ b/src/ImageProcessor/ImageExtensions.cs @@ -119,7 +119,7 @@ namespace ImageProcessor for (int i = 0; i < source.Frames.Count; i++) { ImageFrame sourceFrame = source.Frames[i]; - ImageFrame tranformedFrame = clone ? new ImageFrame(sourceFrame) : new ImageFrame(); + ImageFrame tranformedFrame = clone ? new ImageFrame(sourceFrame) : new ImageFrame { FrameDelay = sourceFrame.FrameDelay }; action(sourceFrame, tranformedFrame); if (!clone) diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index 7067bcc024..e8731d2fef 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -40,6 +40,7 @@ + @@ -179,6 +180,7 @@ + diff --git a/src/ImageProcessor/Samplers/Lanczos3Resampler.cs b/src/ImageProcessor/Samplers/Lanczos3Resampler.cs new file mode 100644 index 0000000000..9593c1aea8 --- /dev/null +++ b/src/ImageProcessor/Samplers/Lanczos3Resampler.cs @@ -0,0 +1,33 @@ +// +// Copyright © James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Samplers +{ + /// + /// The function implements the Lanczos kernel algorithm as described on + /// Wikipedia + /// + public class Lanczos3Resampler : IResampler + { + /// + public double Radius => 3; + + /// + public double GetValue(double x) + { + if (x < 0) + { + x = -x; + } + + if (x < 3) + { + return ImageMaths.SinC(x) * ImageMaths.SinC(x / 3f); + } + + return 0; + } + } +} diff --git a/tests/ImageProcessor.Tests/Filters/FilterTests.cs b/tests/ImageProcessor.Tests/Filters/FilterTests.cs index 675bc3815d..9eed6a9778 100644 --- a/tests/ImageProcessor.Tests/Filters/FilterTests.cs +++ b/tests/ImageProcessor.Tests/Filters/FilterTests.cs @@ -14,12 +14,12 @@ namespace ImageProcessor.Tests.Filters { public static readonly List Files = new List { - { "../../TestImages/Formats/Jpg/Backdrop.jpg"}, + //{ "../../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/ani.gif" }, //{ "../../TestImages/Formats/Gif/ani2.gif" }, //{ "../../TestImages/Formats/Gif/giphy.gif" }, }; diff --git a/tests/ImageProcessor.Tests/ImageProcessor.Tests.csproj b/tests/ImageProcessor.Tests/ImageProcessor.Tests.csproj index 7d79e2e92a..477b1b9092 100644 --- a/tests/ImageProcessor.Tests/ImageProcessor.Tests.csproj +++ b/tests/ImageProcessor.Tests/ImageProcessor.Tests.csproj @@ -60,6 +60,7 @@ + diff --git a/tests/ImageProcessor.Tests/Samplers/SamplerTests.cs b/tests/ImageProcessor.Tests/Samplers/SamplerTests.cs new file mode 100644 index 0000000000..a9449fd9f3 --- /dev/null +++ b/tests/ImageProcessor.Tests/Samplers/SamplerTests.cs @@ -0,0 +1,74 @@ + +namespace ImageProcessor.Tests.Filters +{ + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + + using Samplers; + + using Xunit; + + public class SamplerTests + { + 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() } + }; + + [Theory] + [MemberData("Samplers")] + public void ResizeImage(string name, IResampler sampler) + { + if (!Directory.Exists("Resized")) + { + Directory.CreateDirectory("Resized"); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + Stopwatch watch = Stopwatch.StartNew(); + Image image = new Image(stream); + string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); + using (FileStream output = File.OpenWrite($"Resized/{filename}")) + { + image.Resize(900, 900, sampler).Save(output); + } + + Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms"); + } + } + } + + [Theory] + [InlineData(-2, 0)] + [InlineData(-1, 0)] + [InlineData(0, 1)] + [InlineData(1, 0)] + [InlineData(2, 0)] + [InlineData(2, 0)] + public static void Lanczos3WindowOscillatesCorrectly(double x, double expected) + { + Lanczos3Resampler sampler = new Lanczos3Resampler(); + double result = sampler.GetValue(x); + + Assert.Equal(result, expected); + } + } +}