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);
+ }
+ }
+}