Browse Source

Original maths was totally incorrect

This, however, is no better if not, worse.
af/merge-core
James Jackson-South 9 years ago
parent
commit
5201a2238c
  1. 2
      src/ImageSharp/Common/Helpers/ImageMaths.cs
  2. 93
      src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs
  3. 33
      src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
  4. 2
      src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs

2
src/ImageSharp/Common/Helpers/ImageMaths.cs

@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp
Vector2[] allCorners = { leftTop, rightTop, leftBottom, rightBottom }; Vector2[] allCorners = { leftTop, rightTop, leftBottom, rightBottom };
float extentX = allCorners.Select(v => v.X).Max() - allCorners.Select(v => v.X).Min(); 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(); 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));
} }
/// <summary> /// <summary>

93
src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs

@ -39,8 +39,9 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <summary> /// <summary>
/// Returns the processing matrix used for transforming the image. /// Returns the processing matrix used for transforming the image.
/// </summary> /// </summary>
/// <param name="rectangle">The rectangle bounds</param>
/// <returns>The <see cref="Matrix3x2"/></returns> /// <returns>The <see cref="Matrix3x2"/></returns>
protected abstract Matrix3x2 CreateProcessingMatrix(); protected abstract Matrix3x2 CreateProcessingMatrix(Rectangle rectangle);
/// <summary> /// <summary>
/// Creates a new target canvas to contain the results of the matrix transform. /// Creates a new target canvas to contain the results of the matrix transform.
@ -48,19 +49,9 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <param name="sourceRectangle">The source rectangle.</param> /// <param name="sourceRectangle">The source rectangle.</param>
protected virtual void CreateNewCanvas(Rectangle sourceRectangle) protected virtual void CreateNewCanvas(Rectangle sourceRectangle)
{ {
if (this.ResizeRectangle == Rectangles.DefaultRectangle) this.ResizeRectangle = Matrix3x2.Invert(this.CreateProcessingMatrix(sourceRectangle), out Matrix3x2 sizeMatrix)
{ ? ImageMaths.GetBoundingRectangle(sourceRectangle, sizeMatrix)
if (this.Expand) : sourceRectangle;
{
this.ResizeRectangle = Matrix3x2.Invert(this.CreateProcessingMatrix(), out Matrix3x2 sizeMatrix)
? ImageMaths.GetBoundingRectangle(sourceRectangle, sizeMatrix)
: sourceRectangle;
}
else
{
this.ResizeRectangle = sourceRectangle;
}
}
this.Width = this.ResizeRectangle.Width; this.Width = this.ResizeRectangle.Width;
this.Height = this.ResizeRectangle.Height; this.Height = this.ResizeRectangle.Height;
@ -84,8 +75,8 @@ namespace SixLabors.ImageSharp.Processing.Processors
{ {
int height = this.ResizeRectangle.Height; int height = this.ResizeRectangle.Height;
int width = this.ResizeRectangle.Width; int width = this.ResizeRectangle.Width;
Matrix3x2 matrix = this.GetCenteredMatrix(source);
Rectangle sourceBounds = source.Bounds(); Rectangle sourceBounds = source.Bounds();
Matrix3x2 matrix = this.GetCenteredMatrix(source);
if (this.Sampler is NearestNeighborResampler) if (this.Sampler is NearestNeighborResampler)
{ {
@ -112,24 +103,24 @@ namespace SixLabors.ImageSharp.Processing.Processors
int maxX = source.Width - 1; int maxX = source.Width - 1;
int maxY = source.Height - 1; int maxY = source.Height - 1;
int radius = Math.Max((int)this.Sampler.Radius, 1);
Parallel.For( Parallel.For(
0, 0,
height, height,
configuration.ParallelOptions, new ParallelOptions { MaxDegreeOfParallelism = 1 },
y => y =>
{ {
Span<TPixel> destRow = destination.GetPixelRowSpan(y); Span<TPixel> destRow = destination.GetPixelRowSpan(y);
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
var transformedPoint = Point.Transform(new Point(x, y), matrix); var transformedPoint = Point.Transform(new Point(x, y), matrix);
if (sourceBounds.Contains(transformedPoint.X, transformedPoint.Y)) if (sourceBounds.Contains(transformedPoint.X, transformedPoint.Y))
{ {
WeightsWindow windowX = this.HorizontalWeights.Weights[transformedPoint.X]; WeightsWindow windowX = this.HorizontalWeights.Weights[transformedPoint.X];
WeightsWindow windowY = this.VerticalWeights.Weights[transformedPoint.Y]; 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]; ref TPixel dest = ref destRow[x];
dest.PackFromVector4(dXY); 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 translationToTargetCenter = Matrix3x2.CreateTranslation(-this.ResizeRectangle.Width * .5F, -this.ResizeRectangle.Height * .5F);
var translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width * .5F, source.Height * .5F); var translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width * .5F, source.Height * .5F);
return (translationToTargetCenter * this.CreateProcessingMatrix()) * translateToSourceCenter; return (translationToTargetCenter * this.CreateProcessingMatrix(this.ResizeRectangle)) * translateToSourceCenter;
} }
/// <summary> /// <summary>
@ -157,55 +148,87 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <param name="source">The source image</param> /// <param name="source">The source image</param>
/// <param name="maxX">The maximum x value</param> /// <param name="maxX">The maximum x value</param>
/// <param name="maxY">The maximum y value</param> /// <param name="maxY">The maximum y value</param>
/// <param name="radius">The radius of the current sampling window</param>
/// <param name="windowX">The horizontal weights</param> /// <param name="windowX">The horizontal weights</param>
/// <param name="windowY">The vertical weights</param> /// <param name="windowY">The vertical weights</param>
/// <param name="point">The transformed position</param> /// <param name="point">The transformed position</param>
/// <returns>The <see cref="Vector4"/></returns> /// <returns>The <see cref="Vector4"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
protected Vector4 ComputeWeightedSumAtPosition(ImageFrame<TPixel> source, int maxX, int maxY, int radius, ref WeightsWindow windowX, ref WeightsWindow windowY, ref Point point) protected Vector4 ComputeWeightedSumAtPosition(
ImageFrame<TPixel> 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 horizontalValues = ref windowX.GetStartReference();
ref float verticalValues = ref windowY.GetStartReference(); ref float verticalValues = ref windowY.GetStartReference();
int left = point.X - radius; int yLength = windowY.Length;
int right = point.X + radius; int xLength = windowX.Length;
int top = point.Y - radius; int yRadius = (int)MathF.Ceiling((yLength - 1) * .5F);
int bottom = point.Y + radius; 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 // Faster than clamping + we know we are only looking in one direction
if (left < 0) 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; left = 0;
} }
if (top < 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; 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; Vector4 result = Vector4.Zero;
// We calculate our sample by iterating up-down/left-right from our transformed point. // 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. for (int y = top, yi = yIndex; yi < yLength; y++, yi++)
// 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++)
{ {
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; float weight = yweight * xweight;
result += source[x, y].ToVector4() * weight; result += source[x, y].ToVector4() * weight;

33
src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs

@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
public float Angle { get; set; } public float Angle { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
protected override Matrix3x2 CreateProcessingMatrix() protected override Matrix3x2 CreateProcessingMatrix(Rectangle rectangle)
{ {
if (this.transformMatrix == default(Matrix3x2)) if (this.transformMatrix == default(Matrix3x2))
{ {
@ -54,25 +54,6 @@ namespace SixLabors.ImageSharp.Processing.Processors
return this.transformMatrix; return this.transformMatrix;
} }
/// <inheritdoc/>
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);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle sourceRectangle, Configuration configuration) protected override void OnApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle sourceRectangle, Configuration configuration)
{ {
@ -157,6 +138,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
{ {
int width = source.Width; int width = source.Width;
int height = source.Height; int height = source.Height;
Rectangle destinationBounds = destination.Bounds();
Parallel.For( Parallel.For(
0, 0,
@ -171,7 +153,10 @@ namespace SixLabors.ImageSharp.Processing.Processors
newX = height - newX - 1; newX = height - newX - 1;
int newY = width - x - 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 width = source.Width;
int height = source.Height; int height = source.Height;
Rectangle destinationBounds = destination.Bounds();
Parallel.For( Parallel.For(
0, 0,
@ -224,7 +210,10 @@ namespace SixLabors.ImageSharp.Processing.Processors
int newX = height - y - 1; int newX = height - y - 1;
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
destination[newX, x] = sourceRow[x]; if (destinationBounds.Contains(newX, x))
{
destination[newX, x] = sourceRow[x];
}
} }
}); });
} }

2
src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs

@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
public float AngleY { get; set; } public float AngleY { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
protected override Matrix3x2 CreateProcessingMatrix() protected override Matrix3x2 CreateProcessingMatrix(Rectangle rectangle)
{ {
if (this.transformMatrix == default(Matrix3x2)) if (this.transformMatrix == default(Matrix3x2))
{ {

Loading…
Cancel
Save