diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs index cc48cd91a..cea7f5953 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs @@ -90,10 +90,10 @@ namespace SixLabors.ImageSharp.Processing.Processors for (int x = 0; x < width; x++) { - var transformedPoint = Point.Transform(new Point(x, y), matrix); - if (sourceBounds.Contains(transformedPoint.X, transformedPoint.Y)) + var point = Point.Transform(new Point(x, y), matrix); + if (sourceBounds.Contains(point.X, point.Y)) { - destRow[x] = source[transformedPoint.X, transformedPoint.Y]; + destRow[x] = source[point.X, point.Y]; } } }); @@ -101,29 +101,58 @@ namespace SixLabors.ImageSharp.Processing.Processors return; } - int maxX = source.Width - 1; - int maxY = source.Height - 1; + int maxSourceX = source.Width - 1; + int maxSourceY = source.Height - 1; + (float radius, float scale) xRadiusScale = this.GetSamplingRadius(source.Width, destination.Width); + (float radius, float scale) yRadiusScale = this.GetSamplingRadius(source.Height, destination.Height); + float xScale = xRadiusScale.scale; + float yScale = yRadiusScale.scale; + float xRadius = xRadiusScale.radius; + float yRadius = yRadiusScale.radius; Parallel.For( 0, height, - new ParallelOptions { MaxDegreeOfParallelism = 1 }, + configuration.ParallelOptions, y => { Span destRow = destination.GetPixelRowSpan(y); for (int x = 0; x < width; x++) { - var transformedPoint = Point.Transform(new Point(x, y), matrix); - - if (sourceBounds.Contains(transformedPoint.X, transformedPoint.Y)) + // Use the single precision position to calculate correct bounding pixels + // otherwise we get rogue pixels outside of the bounds. + var point = PointF.Transform(new PointF(x, y), matrix); + int maxX = (int)MathF.Ceiling(point.X + xRadius); + int maxY = (int)MathF.Ceiling(point.Y + yRadius); + int minX = (int)MathF.Floor(point.X - xRadius); + int minY = (int)MathF.Floor(point.Y - yRadius); + + // Clamp sampling pixels to the source image edge + maxX = maxX.Clamp(0, maxSourceX); + minX = minX.Clamp(0, maxSourceX); + maxY = maxY.Clamp(0, maxSourceY); + minY = minY.Clamp(0, maxSourceY); + + if (minX == maxX || minY == maxY) { - WeightsWindow windowX = this.HorizontalWeights.Weights[transformedPoint.X]; - WeightsWindow windowY = this.VerticalWeights.Weights[transformedPoint.Y]; + continue; + } - Vector4 dXY = this.ComputeWeightedSumAtPosition(source, maxX, maxY, ref windowX, ref windowY, ref transformedPoint); - ref TPixel dest = ref destRow[x]; - dest.PackFromVector4(dXY); + // It appears these have to be calculated manually. + // Using the precalculated weights give the wrong values. + // TODO: Find a way to speed this up. + Vector4 sum = Vector4.Zero; + for (int i = minX; i <= maxX; i++) + { + float weightX = this.Sampler.GetValue((i - point.X) / xScale); + for (int j = minY; j <= maxY; j++) + { + float weightY = this.Sampler.GetValue((j - point.Y) / yScale); + sum += source[i, j].ToVector4() * weightX * weightY; + } } + ref TPixel dest = ref destRow[x]; + dest.PackFromVector4(sum); } }); } @@ -143,99 +172,22 @@ namespace SixLabors.ImageSharp.Processing.Processors } /// - /// Computes the weighted sum at the given XY position + /// Calculates the sampling radius for the current sampler /// - /// The source image - /// The maximum x value - /// The maximum y value - /// The horizontal weights - /// The vertical weights - /// The transformed position - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected Vector4 ComputeWeightedSumAtPosition( - ImageFrame source, - int maxX, - int maxY, - ref WeightsWindow windowX, - ref WeightsWindow windowY, - ref Point point) + /// The source dimension size + /// The destination dimension size + /// The radius, and scaling factor + private (float radius, float scale) GetSamplingRadius(int sourceSize, int destinationSize) { - // What, in theory, is supposed to happen here is the following... - // - // We identify the maximum possible pixel offsets allowable by the current sampler - // clamping values to ensure that we do not go outwith the bounds of our image. - // - // Then we get the weights of that offset value from our pre-calculated vaues. - // First we grab the weight on the y-axis, then the x-axis and then we multiply - // them together to get the final weight. - // - // Unfortunately this simply does not seem to work! - // The output is rubbish and I cannot see why :( - ref float horizontalValues = ref windowX.GetStartReference(); - ref float verticalValues = ref windowY.GetStartReference(); - - int yLength = windowY.Length; - int xLength = windowX.Length; - int yRadius = (int)MathF.Ceiling((yLength - 1) * .5F); - int xRadius = (int)MathF.Ceiling((xLength - 1) * .5F); - - int left = point.X - xRadius; - int right = point.X + xRadius; - int top = point.Y - yRadius; - int bottom = point.Y + yRadius; - - int yIndex = 0; - int xIndex = 0; - - // Faster than clamping + we know we are only looking in one direction - if (left < 0) - { - // Trim the length of our weights iterator across the x-axis. - // Offset our start index across the x-axis. - xIndex = ImageMaths.FastAbs(left); - xLength -= xIndex; - left = 0; - } - - if (top < 0) - { - // Trim the length of our weights iterator across the y-axis. - // Offset our start index across the y-axis. - yIndex = ImageMaths.FastAbs(top); - yLength -= yIndex; - top = 0; - } + float ratio = (float)sourceSize / destinationSize; + float scale = ratio; - if (right >= maxX) + if (scale < 1F) { - // Trim the length of our weights iterator across the x-axis. - xLength -= right - maxX; - } - - if (bottom >= maxY) - { - // Trim the length of our weights iterator across the y-axis. - yLength -= bottom - maxY; - } - - Vector4 result = Vector4.Zero; - - // We calculate our sample by iterating up-down/left-right from our transformed point. - for (int y = top, yi = yIndex; yi < yLength; y++, yi++) - { - float yweight = Unsafe.Add(ref verticalValues, yi); - - for (int x = left, xi = xIndex; xi < xLength; x++, xi++) - { - float xweight = Unsafe.Add(ref horizontalValues, xi); - float weight = yweight * xweight; - - result += source[x, y].ToVector4() * weight; - } + scale = 1F; } - return result; + return (MathF.Ceiling(scale * this.Sampler.Radius), scale); } } diff --git a/src/ImageSharp/Processing/Transforms/Resamplers/BicubicResampler.cs b/src/ImageSharp/Processing/Transforms/Resamplers/BicubicResampler.cs index be9de9eda..9fb1313d9 100644 --- a/src/ImageSharp/Processing/Transforms/Resamplers/BicubicResampler.cs +++ b/src/ImageSharp/Processing/Transforms/Resamplers/BicubicResampler.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Processing { /// @@ -14,6 +16,7 @@ namespace SixLabors.ImageSharp.Processing public float Radius => 2; /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public float GetValue(float x) { if (x < 0F) diff --git a/src/ImageSharp/Processing/Transforms/Resamplers/BoxResampler.cs b/src/ImageSharp/Processing/Transforms/Resamplers/BoxResampler.cs index 5aab0d07f..8255af4fe 100644 --- a/src/ImageSharp/Processing/Transforms/Resamplers/BoxResampler.cs +++ b/src/ImageSharp/Processing/Transforms/Resamplers/BoxResampler.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Processing { /// @@ -13,6 +15,7 @@ namespace SixLabors.ImageSharp.Processing public float Radius => 0.5F; /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public float GetValue(float x) { if (x > -0.5F && x <= 0.5F) diff --git a/src/ImageSharp/Processing/Transforms/Resamplers/CatmullRomResampler.cs b/src/ImageSharp/Processing/Transforms/Resamplers/CatmullRomResampler.cs index 1c8467618..19f466287 100644 --- a/src/ImageSharp/Processing/Transforms/Resamplers/CatmullRomResampler.cs +++ b/src/ImageSharp/Processing/Transforms/Resamplers/CatmullRomResampler.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Processing { /// @@ -15,6 +17,7 @@ namespace SixLabors.ImageSharp.Processing public float Radius => 2; /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public float GetValue(float x) { const float B = 0; diff --git a/src/ImageSharp/Processing/Transforms/Resamplers/HermiteResampler.cs b/src/ImageSharp/Processing/Transforms/Resamplers/HermiteResampler.cs index 33435059f..afc1427f4 100644 --- a/src/ImageSharp/Processing/Transforms/Resamplers/HermiteResampler.cs +++ b/src/ImageSharp/Processing/Transforms/Resamplers/HermiteResampler.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Processing { /// @@ -14,6 +16,7 @@ namespace SixLabors.ImageSharp.Processing public float Radius => 2; /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public float GetValue(float x) { const float B = 0F; diff --git a/src/ImageSharp/Processing/Transforms/Resamplers/Lanczos2Resampler.cs b/src/ImageSharp/Processing/Transforms/Resamplers/Lanczos2Resampler.cs index 29568db02..3d5af528e 100644 --- a/src/ImageSharp/Processing/Transforms/Resamplers/Lanczos2Resampler.cs +++ b/src/ImageSharp/Processing/Transforms/Resamplers/Lanczos2Resampler.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Processing { /// @@ -14,6 +16,7 @@ namespace SixLabors.ImageSharp.Processing public float Radius => 2; /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public float GetValue(float x) { if (x < 0F) diff --git a/src/ImageSharp/Processing/Transforms/Resamplers/Lanczos3Resampler.cs b/src/ImageSharp/Processing/Transforms/Resamplers/Lanczos3Resampler.cs index 492ef69e4..7e46b05f3 100644 --- a/src/ImageSharp/Processing/Transforms/Resamplers/Lanczos3Resampler.cs +++ b/src/ImageSharp/Processing/Transforms/Resamplers/Lanczos3Resampler.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Processing { /// @@ -14,6 +16,7 @@ namespace SixLabors.ImageSharp.Processing public float Radius => 3; /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public float GetValue(float x) { if (x < 0F) diff --git a/src/ImageSharp/Processing/Transforms/Resamplers/Lanczos5Resampler.cs b/src/ImageSharp/Processing/Transforms/Resamplers/Lanczos5Resampler.cs index cae152a53..d593dbcf4 100644 --- a/src/ImageSharp/Processing/Transforms/Resamplers/Lanczos5Resampler.cs +++ b/src/ImageSharp/Processing/Transforms/Resamplers/Lanczos5Resampler.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Processing { /// @@ -14,6 +16,7 @@ namespace SixLabors.ImageSharp.Processing public float Radius => 5; /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public float GetValue(float x) { if (x < 0F) diff --git a/src/ImageSharp/Processing/Transforms/Resamplers/Lanczos8Resampler.cs b/src/ImageSharp/Processing/Transforms/Resamplers/Lanczos8Resampler.cs index b390c5541..5d7c708f2 100644 --- a/src/ImageSharp/Processing/Transforms/Resamplers/Lanczos8Resampler.cs +++ b/src/ImageSharp/Processing/Transforms/Resamplers/Lanczos8Resampler.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Processing { /// @@ -14,6 +16,7 @@ namespace SixLabors.ImageSharp.Processing public float Radius => 8; /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public float GetValue(float x) { if (x < 0F) diff --git a/src/ImageSharp/Processing/Transforms/Resamplers/MitchellNetravaliResampler.cs b/src/ImageSharp/Processing/Transforms/Resamplers/MitchellNetravaliResampler.cs index df351d950..f6e9a9fa0 100644 --- a/src/ImageSharp/Processing/Transforms/Resamplers/MitchellNetravaliResampler.cs +++ b/src/ImageSharp/Processing/Transforms/Resamplers/MitchellNetravaliResampler.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Processing { /// @@ -13,6 +15,7 @@ namespace SixLabors.ImageSharp.Processing public float Radius => 2; /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public float GetValue(float x) { const float B = 0.3333333F; diff --git a/src/ImageSharp/Processing/Transforms/Resamplers/NearestNeighborResampler.cs b/src/ImageSharp/Processing/Transforms/Resamplers/NearestNeighborResampler.cs index 7a7785be3..b1cc8609e 100644 --- a/src/ImageSharp/Processing/Transforms/Resamplers/NearestNeighborResampler.cs +++ b/src/ImageSharp/Processing/Transforms/Resamplers/NearestNeighborResampler.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Processing { /// @@ -13,6 +15,7 @@ namespace SixLabors.ImageSharp.Processing public float Radius => 1; /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public float GetValue(float x) { return x; diff --git a/src/ImageSharp/Processing/Transforms/Resamplers/RobidouxSharpResampler.cs b/src/ImageSharp/Processing/Transforms/Resamplers/RobidouxSharpResampler.cs index a345da3f4..9db4b125f 100644 --- a/src/ImageSharp/Processing/Transforms/Resamplers/RobidouxSharpResampler.cs +++ b/src/ImageSharp/Processing/Transforms/Resamplers/RobidouxSharpResampler.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Processing { /// @@ -13,6 +15,7 @@ namespace SixLabors.ImageSharp.Processing public float Radius => 2; /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public float GetValue(float x) { const float B = 0.2620145123990142F; diff --git a/src/ImageSharp/Processing/Transforms/Resamplers/SplineResampler.cs b/src/ImageSharp/Processing/Transforms/Resamplers/SplineResampler.cs index ac5e2dedb..815fd9c3d 100644 --- a/src/ImageSharp/Processing/Transforms/Resamplers/SplineResampler.cs +++ b/src/ImageSharp/Processing/Transforms/Resamplers/SplineResampler.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Processing { /// @@ -13,6 +15,7 @@ namespace SixLabors.ImageSharp.Processing public float Radius => 2; /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public float GetValue(float x) { const float B = 1F; diff --git a/src/ImageSharp/Processing/Transforms/Resamplers/TriangleResampler.cs b/src/ImageSharp/Processing/Transforms/Resamplers/TriangleResampler.cs index 842da87e0..4b62c767b 100644 --- a/src/ImageSharp/Processing/Transforms/Resamplers/TriangleResampler.cs +++ b/src/ImageSharp/Processing/Transforms/Resamplers/TriangleResampler.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Processing { /// @@ -14,6 +16,7 @@ namespace SixLabors.ImageSharp.Processing public float Radius => 1; /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public float GetValue(float x) { if (x < 0F) diff --git a/src/ImageSharp/Processing/Transforms/Resamplers/WelchResampler.cs b/src/ImageSharp/Processing/Transforms/Resamplers/WelchResampler.cs index acb74a8ec..9bf19573a 100644 --- a/src/ImageSharp/Processing/Transforms/Resamplers/WelchResampler.cs +++ b/src/ImageSharp/Processing/Transforms/Resamplers/WelchResampler.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Processing { /// @@ -13,6 +15,7 @@ namespace SixLabors.ImageSharp.Processing public float Radius => 3; /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public float GetValue(float x) { if (x < 0F) @@ -28,4 +31,4 @@ namespace SixLabors.ImageSharp.Processing return 0F; } } -} +} \ No newline at end of file