From 5201a2238c174768780413b04ba4a208627f7dda Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 27 Nov 2017 19:37:06 +1100 Subject: [PATCH] Original maths was totally incorrect This, however, is no better if not, worse. --- src/ImageSharp/Common/Helpers/ImageMaths.cs | 2 +- .../Processors/Transforms/AffineProcessor.cs | 93 ++++++++++++------- .../Processors/Transforms/RotateProcessor.cs | 33 +++---- .../Processors/Transforms/SkewProcessor.cs | 2 +- 4 files changed, 71 insertions(+), 59 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs index 7d781e77fe..fa60f855ee 100644 --- a/src/ImageSharp/Common/Helpers/ImageMaths.cs +++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs @@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp Vector2[] allCorners = { leftTop, rightTop, leftBottom, rightBottom }; float extentX = allCorners.Select(v => v.X).Max() - allCorners.Select(v => v.X).Min(); float extentY = allCorners.Select(v => v.Y).Max() - allCorners.Select(v => v.Y).Min(); - return new Rectangle(0, 0, (int)extentX, (int)extentY); + return new Rectangle(0, 0, (int)MathF.Ceiling(extentX), (int)MathF.Ceiling(extentY)); } /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs index cffe6674d6..cc48cd91a3 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs @@ -39,8 +39,9 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// Returns the processing matrix used for transforming the image. /// + /// The rectangle bounds /// The - protected abstract Matrix3x2 CreateProcessingMatrix(); + protected abstract Matrix3x2 CreateProcessingMatrix(Rectangle rectangle); /// /// Creates a new target canvas to contain the results of the matrix transform. @@ -48,19 +49,9 @@ namespace SixLabors.ImageSharp.Processing.Processors /// The source rectangle. protected virtual void CreateNewCanvas(Rectangle sourceRectangle) { - if (this.ResizeRectangle == Rectangles.DefaultRectangle) - { - if (this.Expand) - { - this.ResizeRectangle = Matrix3x2.Invert(this.CreateProcessingMatrix(), out Matrix3x2 sizeMatrix) - ? ImageMaths.GetBoundingRectangle(sourceRectangle, sizeMatrix) - : sourceRectangle; - } - else - { - this.ResizeRectangle = sourceRectangle; - } - } + this.ResizeRectangle = Matrix3x2.Invert(this.CreateProcessingMatrix(sourceRectangle), out Matrix3x2 sizeMatrix) + ? ImageMaths.GetBoundingRectangle(sourceRectangle, sizeMatrix) + : sourceRectangle; this.Width = this.ResizeRectangle.Width; this.Height = this.ResizeRectangle.Height; @@ -84,8 +75,8 @@ namespace SixLabors.ImageSharp.Processing.Processors { int height = this.ResizeRectangle.Height; int width = this.ResizeRectangle.Width; - Matrix3x2 matrix = this.GetCenteredMatrix(source); Rectangle sourceBounds = source.Bounds(); + Matrix3x2 matrix = this.GetCenteredMatrix(source); if (this.Sampler is NearestNeighborResampler) { @@ -112,24 +103,24 @@ namespace SixLabors.ImageSharp.Processing.Processors int maxX = source.Width - 1; int maxY = source.Height - 1; - int radius = Math.Max((int)this.Sampler.Radius, 1); Parallel.For( 0, height, - configuration.ParallelOptions, + new ParallelOptions { MaxDegreeOfParallelism = 1 }, 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)) { WeightsWindow windowX = this.HorizontalWeights.Weights[transformedPoint.X]; WeightsWindow windowY = this.VerticalWeights.Weights[transformedPoint.Y]; - Vector4 dXY = this.ComputeWeightedSumAtPosition(source, maxX, maxY, radius, ref windowX, ref windowY, ref transformedPoint); + Vector4 dXY = this.ComputeWeightedSumAtPosition(source, maxX, maxY, ref windowX, ref windowY, ref transformedPoint); ref TPixel dest = ref destRow[x]; dest.PackFromVector4(dXY); } @@ -148,7 +139,7 @@ namespace SixLabors.ImageSharp.Processing.Processors { var translationToTargetCenter = Matrix3x2.CreateTranslation(-this.ResizeRectangle.Width * .5F, -this.ResizeRectangle.Height * .5F); var translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width * .5F, source.Height * .5F); - return (translationToTargetCenter * this.CreateProcessingMatrix()) * translateToSourceCenter; + return (translationToTargetCenter * this.CreateProcessingMatrix(this.ResizeRectangle)) * translateToSourceCenter; } /// @@ -157,55 +148,87 @@ namespace SixLabors.ImageSharp.Processing.Processors /// The source image /// The maximum x value /// The maximum y value - /// The radius of the current sampling window /// The horizontal weights /// The vertical weights /// The transformed position /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected Vector4 ComputeWeightedSumAtPosition(ImageFrame source, int maxX, int maxY, int radius, ref WeightsWindow windowX, ref WeightsWindow windowY, ref Point point) + protected Vector4 ComputeWeightedSumAtPosition( + ImageFrame source, + int maxX, + int maxY, + ref WeightsWindow windowX, + ref WeightsWindow windowY, + ref Point point) { + // 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 left = point.X - radius; - int right = point.X + radius; - int top = point.Y - radius; - int bottom = point.Y + radius; + 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; } - if (right > maxX) + if (right >= maxX) { - right = maxX; + // Trim the length of our weights iterator across the x-axis. + xLength -= right - maxX; } - if (bottom > maxY) + if (bottom >= maxY) { - 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. - // Ignoring the weight of outlying pixels is better for shape preservation on transforms such as skew with samplers that use larger radii. - // We don't offset our window index so that the weight compensates for the missing values - for (int y = top, yl = 0; y <= bottom; y++, yl++) + for (int y = top, yi = yIndex; yi < yLength; y++, yi++) { - float yweight = Unsafe.Add(ref verticalValues, yl); + float yweight = Unsafe.Add(ref verticalValues, yi); - for (int x = left, xl = 0; x <= right; x++, xl++) + for (int x = left, xi = xIndex; xi < xLength; x++, xi++) { - float xweight = Unsafe.Add(ref horizontalValues, xl); + float xweight = Unsafe.Add(ref horizontalValues, xi); float weight = yweight * xweight; result += source[x, y].ToVector4() * weight; diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs index eb6110fa14..1ad7263929 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Processing.Processors public float Angle { get; set; } /// - protected override Matrix3x2 CreateProcessingMatrix() + protected override Matrix3x2 CreateProcessingMatrix(Rectangle rectangle) { if (this.transformMatrix == default(Matrix3x2)) { @@ -54,25 +54,6 @@ namespace SixLabors.ImageSharp.Processing.Processors return this.transformMatrix; } - /// - protected override void CreateNewCanvas(Rectangle sourceRectangle) - { - if (MathF.Abs(this.Angle) < Constants.Epsilon || - MathF.Abs(this.Angle - 180) < Constants.Epsilon) - { - this.ResizeRectangle = sourceRectangle; - } - - if (MathF.Abs(this.Angle - 90) < Constants.Epsilon || - MathF.Abs(this.Angle - 270) < Constants.Epsilon) - { - // We always expand enumerated rectangle values - this.ResizeRectangle = new Rectangle(0, 0, sourceRectangle.Height, sourceRectangle.Width); - } - - base.CreateNewCanvas(sourceRectangle); - } - /// protected override void OnApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) { @@ -157,6 +138,7 @@ namespace SixLabors.ImageSharp.Processing.Processors { int width = source.Width; int height = source.Height; + Rectangle destinationBounds = destination.Bounds(); Parallel.For( 0, @@ -171,7 +153,10 @@ namespace SixLabors.ImageSharp.Processing.Processors newX = height - newX - 1; int newY = width - x - 1; - destination[newX, newY] = sourceRow[x]; + if (destinationBounds.Contains(newX, newY)) + { + destination[newX, newY] = sourceRow[x]; + } } }); } @@ -213,6 +198,7 @@ namespace SixLabors.ImageSharp.Processing.Processors { int width = source.Width; int height = source.Height; + Rectangle destinationBounds = destination.Bounds(); Parallel.For( 0, @@ -224,7 +210,10 @@ namespace SixLabors.ImageSharp.Processing.Processors int newX = height - y - 1; for (int x = 0; x < width; x++) { - destination[newX, x] = sourceRow[x]; + if (destinationBounds.Contains(newX, x)) + { + destination[newX, x] = sourceRow[x]; + } } }); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs index fc70330e81..61526747ea 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Processing.Processors public float AngleY { get; set; } /// - protected override Matrix3x2 CreateProcessingMatrix() + protected override Matrix3x2 CreateProcessingMatrix(Rectangle rectangle) { if (this.transformMatrix == default(Matrix3x2)) {