Browse Source

Merge pull request #546 from SixLabors/js/projective-transforms

Add Projective Transforms
pull/561/head
James Jackson-South 8 years ago
committed by GitHub
parent
commit
3cc7caa6a1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      src/ImageSharp/Processing/Transforms/Processors/AffineTransformProcessor.cs
  2. 19
      src/ImageSharp/Processing/Transforms/Processors/InterpolatedTransformProcessorBase.cs
  3. 49
      src/ImageSharp/Processing/Transforms/Processors/ProjectiveTransformProcessor.cs
  4. 166
      src/ImageSharp/Processing/Transforms/ProjectiveTransformHelper.cs
  5. 7
      src/ImageSharp/Processing/Transforms/TransformExtensions.cs
  6. 41
      tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs
  7. 137
      tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs
  8. 2
      tests/Images/External

22
src/ImageSharp/Processing/Transforms/Processors/AffineTransformProcessor.cs

@ -5,6 +5,8 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
@ -120,9 +122,9 @@ namespace SixLabors.ImageSharp.Processing.Transforms.Processors
configuration.ParallelOptions,
y =>
{
Span<TPixel> destRow = destination.GetPixelRowSpan(y);
Span<float> ySpan = yBuffer.GetRowSpan(y);
Span<float> xSpan = xBuffer.GetRowSpan(y);
ref TPixel destRowRef = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y));
ref float ySpanRef = ref MemoryMarshal.GetReference(yBuffer.GetRowSpan(y));
ref float xSpanRef = ref MemoryMarshal.GetReference(xBuffer.GetRowSpan(y));
for (int x = 0; x < width; x++)
{
@ -164,24 +166,24 @@ namespace SixLabors.ImageSharp.Processing.Transforms.Processors
// I've optimized where I can but am always open to suggestions.
if (yScale > 1 && xScale > 1)
{
CalculateWeightsDown(top, bottom, minY, maxY, point.Y, sampler, yScale, ySpan);
CalculateWeightsDown(left, right, minX, maxX, point.X, sampler, xScale, xSpan);
CalculateWeightsDown(top, bottom, minY, maxY, point.Y, sampler, yScale, ref ySpanRef, yLength);
CalculateWeightsDown(left, right, minX, maxX, point.X, sampler, xScale, ref xSpanRef, xLength);
}
else
{
CalculateWeightsScaleUp(minY, maxY, point.Y, sampler, ySpan);
CalculateWeightsScaleUp(minX, maxX, point.X, sampler, xSpan);
CalculateWeightsScaleUp(minY, maxY, point.Y, sampler, ref ySpanRef);
CalculateWeightsScaleUp(minX, maxX, point.X, sampler, ref xSpanRef);
}
// Now multiply the results against the offsets
Vector4 sum = Vector4.Zero;
for (int yy = 0, j = minY; j <= maxY; j++, yy++)
{
float yWeight = ySpan[yy];
float yWeight = Unsafe.Add(ref ySpanRef, yy);
for (int xx = 0, i = minX; i <= maxX; i++, xx++)
{
float xWeight = xSpan[xx];
float xWeight = Unsafe.Add(ref xSpanRef, xx);
var vector = source[i, j].ToVector4();
// Values are first premultiplied to prevent darkening of edge pixels
@ -190,7 +192,7 @@ namespace SixLabors.ImageSharp.Processing.Transforms.Processors
}
}
ref TPixel dest = ref destRow[x];
ref TPixel dest = ref Unsafe.Add(ref destRowRef, x);
// Reverse the premultiplication
dest.PackFromVector4(sum.UnPremultiply());

19
src/ImageSharp/Processing/Transforms/Processors/InterpolatedTransformProcessorBase.cs

@ -42,12 +42,12 @@ namespace SixLabors.ImageSharp.Processing.Transforms.Processors
/// <param name="point">The transformed point dimension</param>
/// <param name="sampler">The sampler</param>
/// <param name="scale">The transformed image scale relative to the source</param>
/// <param name="weights">The collection of weights</param>
/// <param name="weightsRef">The reference to the collection of weights</param>
/// <param name="length">The length of the weights collection</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected static void CalculateWeightsDown(int min, int max, int sourceMin, int sourceMax, float point, IResampler sampler, float scale, Span<float> weights)
protected static void CalculateWeightsDown(int min, int max, int sourceMin, int sourceMax, float point, IResampler sampler, float scale, ref float weightsRef, int length)
{
float sum = 0;
ref float weightsBaseRef = ref weights[0];
// Downsampling weights requires more edge sampling plus normalization of the weights
for (int x = 0, i = min; i <= max; i++, x++)
@ -65,14 +65,14 @@ namespace SixLabors.ImageSharp.Processing.Transforms.Processors
float weight = sampler.GetValue((index - point) / scale);
sum += weight;
Unsafe.Add(ref weightsBaseRef, x) = weight;
Unsafe.Add(ref weightsRef, x) = weight;
}
if (sum > 0)
{
for (int i = 0; i < weights.Length; i++)
for (int i = 0; i < length; i++)
{
ref float wRef = ref Unsafe.Add(ref weightsBaseRef, i);
ref float wRef = ref Unsafe.Add(ref weightsRef, i);
wRef = wRef / sum;
}
}
@ -85,15 +85,14 @@ namespace SixLabors.ImageSharp.Processing.Transforms.Processors
/// <param name="sourceMax">The maximum source bounds</param>
/// <param name="point">The transformed point dimension</param>
/// <param name="sampler">The sampler</param>
/// <param name="weights">The collection of weights</param>
/// <param name="weightsRef">The reference to the collection of weights</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected static void CalculateWeightsScaleUp(int sourceMin, int sourceMax, float point, IResampler sampler, Span<float> weights)
protected static void CalculateWeightsScaleUp(int sourceMin, int sourceMax, float point, IResampler sampler, ref float weightsRef)
{
ref float weightsBaseRef = ref weights[0];
for (int x = 0, i = sourceMin; i <= sourceMax; i++, x++)
{
float weight = sampler.GetValue(i - point);
Unsafe.Add(ref weightsBaseRef, x) = weight;
Unsafe.Add(ref weightsRef, x) = weight;
}
}

49
src/ImageSharp/Processing/Transforms/Processors/ProjectiveTransformProcessor.cs

@ -5,6 +5,8 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
@ -70,6 +72,7 @@ namespace SixLabors.ImageSharp.Processing.Transforms.Processors
// Convert from screen to world space.
Matrix4x4.Invert(matrix, out matrix);
const float Epsilon = 0.0000001F;
if (this.Sampler is NearestNeighborResampler)
{
@ -83,10 +86,15 @@ namespace SixLabors.ImageSharp.Processing.Transforms.Processors
for (int x = 0; x < width; x++)
{
var point = Point.Round(Vector2.Transform(new Vector2(x, y), matrix));
if (sourceBounds.Contains(point.X, point.Y))
var v3 = Vector3.Transform(new Vector3(x, y, 1), matrix);
float z = MathF.Max(v3.Z, Epsilon);
int px = (int)MathF.Round(v3.X / z);
int py = (int)MathF.Round(v3.Y / z);
if (sourceBounds.Contains(px, py))
{
destRow[x] = source[point.X, point.Y];
destRow[x] = source[px, py];
}
}
});
@ -100,7 +108,10 @@ namespace SixLabors.ImageSharp.Processing.Transforms.Processors
(float radius, float scale, float ratio) yRadiusScale = this.GetSamplingRadius(source.Height, destination.Height);
float xScale = xRadiusScale.scale;
float yScale = yRadiusScale.scale;
var radius = new Vector2(xRadiusScale.radius, yRadiusScale.radius);
// Using Vector4 with dummy 0-s, because Vector2 SIMD implementation is not reliable:
var radius = new Vector4(xRadiusScale.radius, yRadiusScale.radius, 0, 0);
IResampler sampler = this.Sampler;
var maxSource = new Vector4(maxSourceX, maxSourceY, maxSourceX, maxSourceY);
int xLength = (int)MathF.Ceiling((radius.X * 2) + 2);
@ -117,19 +128,23 @@ namespace SixLabors.ImageSharp.Processing.Transforms.Processors
configuration.ParallelOptions,
y =>
{
Span<TPixel> destRow = destination.GetPixelRowSpan(y);
Span<float> ySpan = yBuffer.GetRowSpan(y);
Span<float> xSpan = xBuffer.GetRowSpan(y);
ref TPixel destRowRef = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y));
ref float ySpanRef = ref MemoryMarshal.GetReference(yBuffer.GetRowSpan(y));
ref float xSpanRef = ref MemoryMarshal.GetReference(xBuffer.GetRowSpan(y));
for (int x = 0; x < width; x++)
{
// Use the single precision position to calculate correct bounding pixels
// otherwise we get rogue pixels outside of the bounds.
var point = Vector2.Transform(new Vector2(x, y), matrix);
var v3 = Vector3.Transform(new Vector3(x, y, 1), matrix);
float z = MathF.Max(v3.Z, Epsilon);
// Using Vector4 with dummy 0-s, because Vector2 SIMD implementation is not reliable:
Vector4 point = new Vector4(v3.X, v3.Y, 0, 0) / z;
// Clamp sampling pixel radial extents to the source image edges
Vector2 maxXY = point + radius;
Vector2 minXY = point - radius;
Vector4 maxXY = point + radius;
Vector4 minXY = point - radius;
// max, maxY, minX, minY
var extents = new Vector4(
@ -161,24 +176,24 @@ namespace SixLabors.ImageSharp.Processing.Transforms.Processors
// I've optimized where I can but am always open to suggestions.
if (yScale > 1 && xScale > 1)
{
CalculateWeightsDown(top, bottom, minY, maxY, point.Y, sampler, yScale, ySpan);
CalculateWeightsDown(left, right, minX, maxX, point.X, sampler, xScale, xSpan);
CalculateWeightsDown(top, bottom, minY, maxY, point.Y, sampler, yScale, ref ySpanRef, yLength);
CalculateWeightsDown(left, right, minX, maxX, point.X, sampler, xScale, ref xSpanRef, xLength);
}
else
{
CalculateWeightsScaleUp(minY, maxY, point.Y, sampler, ySpan);
CalculateWeightsScaleUp(minX, maxX, point.X, sampler, xSpan);
CalculateWeightsScaleUp(minY, maxY, point.Y, sampler, ref ySpanRef);
CalculateWeightsScaleUp(minX, maxX, point.X, sampler, ref xSpanRef);
}
// Now multiply the results against the offsets
Vector4 sum = Vector4.Zero;
for (int yy = 0, j = minY; j <= maxY; j++, yy++)
{
float yWeight = ySpan[yy];
float yWeight = Unsafe.Add(ref ySpanRef, yy);
for (int xx = 0, i = minX; i <= maxX; i++, xx++)
{
float xWeight = xSpan[xx];
float xWeight = Unsafe.Add(ref xSpanRef, xx);
var vector = source[i, j].ToVector4();
// Values are first premultiplied to prevent darkening of edge pixels
@ -187,7 +202,7 @@ namespace SixLabors.ImageSharp.Processing.Transforms.Processors
}
}
ref TPixel dest = ref destRow[x];
ref TPixel dest = ref Unsafe.Add(ref destRowRef, x);
// Reverse the premultiplication
dest.PackFromVector4(sum.UnPremultiply());

166
src/ImageSharp/Processing/Transforms/ProjectiveTransformHelper.cs

@ -0,0 +1,166 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Transforms
{
/// <summary>
/// Enumerates the various options which determine which side to taper
/// </summary>
public enum TaperSide
{
/// <summary>
/// Taper the left side
/// </summary>
Left,
/// <summary>
/// Taper the top side
/// </summary>
Top,
/// <summary>
/// Taper the right side
/// </summary>
Right,
/// <summary>
/// Taper the bottom side
/// </summary>
Bottom
}
/// <summary>
/// Enumerates the various options which determine how to taper corners
/// </summary>
public enum TaperCorner
{
/// <summary>
/// Taper the left or top corner
/// </summary>
LeftOrTop,
/// <summary>
/// Taper the right or bottom corner
/// </summary>
RightOrBottom,
/// <summary>
/// Taper the both sets of corners
/// </summary>
Both
}
/// <summary>
/// Provides helper methods for working with generalized projective transforms.
/// </summary>
public static class ProjectiveTransformHelper
{
/// <summary>
/// Creates a matrix that performs a tapering projective transform.
/// <see href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/transforms/non-affine"/>
/// </summary>
/// <param name="size">The rectangular size of the image being transformed.</param>
/// <param name="taperSide">An enumeration that indicates the side of the rectangle that tapers.</param>
/// <param name="taperCorner">An enumeration that indicates on which corners to taper the rectangle.</param>
/// <param name="taperFraction">The amount to taper.</param>
/// <returns>The <see cref="Matrix4x4"/></returns>
public static Matrix4x4 CreateTaperMatrix(Size size, TaperSide taperSide, TaperCorner taperCorner, float taperFraction)
{
Matrix4x4 matrix = Matrix4x4.Identity;
switch (taperSide)
{
case TaperSide.Left:
matrix.M11 = taperFraction;
matrix.M22 = taperFraction;
matrix.M13 = (taperFraction - 1) / size.Width;
switch (taperCorner)
{
case TaperCorner.RightOrBottom:
break;
case TaperCorner.LeftOrTop:
matrix.M12 = size.Height * matrix.M13;
matrix.M32 = size.Height * (1 - taperFraction);
break;
case TaperCorner.Both:
matrix.M12 = (size.Height * 0.5f) * matrix.M13;
matrix.M32 = size.Height * (1 - taperFraction) / 2;
break;
}
break;
case TaperSide.Top:
matrix.M11 = taperFraction;
matrix.M22 = taperFraction;
matrix.M23 = (taperFraction - 1) / size.Height;
switch (taperCorner)
{
case TaperCorner.RightOrBottom:
break;
case TaperCorner.LeftOrTop:
matrix.M21 = size.Width * matrix.M23;
matrix.M31 = size.Width * (1 - taperFraction);
break;
case TaperCorner.Both:
matrix.M21 = (size.Width * 0.5f) * matrix.M23;
matrix.M31 = size.Width * (1 - taperFraction) / 2;
break;
}
break;
case TaperSide.Right:
matrix.M11 = 1 / taperFraction;
matrix.M13 = (1 - taperFraction) / (size.Width * taperFraction);
switch (taperCorner)
{
case TaperCorner.RightOrBottom:
break;
case TaperCorner.LeftOrTop:
matrix.M12 = size.Height * matrix.M13;
break;
case TaperCorner.Both:
matrix.M12 = (size.Height * 0.5f) * matrix.M13;
break;
}
break;
case TaperSide.Bottom:
matrix.M22 = 1 / taperFraction;
matrix.M23 = (1 - taperFraction) / (size.Height * taperFraction);
switch (taperCorner)
{
case TaperCorner.RightOrBottom:
break;
case TaperCorner.LeftOrTop:
matrix.M21 = size.Width * matrix.M23;
break;
case TaperCorner.Both:
matrix.M21 = (size.Width * 0.5f) * matrix.M23;
break;
}
break;
}
return matrix;
}
}
}

7
src/ImageSharp/Processing/Transforms/TransformExtensions.cs

@ -86,26 +86,25 @@ namespace SixLabors.ImageSharp.Processing.Transforms
/// <param name="source">The image to transform.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <returns>The <see cref="Image{TPixel}"/></returns>
internal static IImageProcessingContext<TPixel> Transform<TPixel>(this IImageProcessingContext<TPixel> source, Matrix4x4 matrix)
public static IImageProcessingContext<TPixel> Transform<TPixel>(this IImageProcessingContext<TPixel> source, Matrix4x4 matrix)
where TPixel : struct, IPixel<TPixel>
=> Transform(source, matrix, KnownResamplers.Bicubic);
/// <summary>
/// Applies a projective transform to the image by the given matrix using the specified sampling algorithm.
/// TODO: Doesn't work yet! Implement tests + Finish implementation + Document Matrix4x4 behavior
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image to transform.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <param name="sampler">The <see cref="IResampler"/> to perform the resampling.</param>
/// <returns>The <see cref="Image{TPixel}"/></returns>
internal static IImageProcessingContext<TPixel> Transform<TPixel>(this IImageProcessingContext<TPixel> source, Matrix4x4 matrix, IResampler sampler)
public static IImageProcessingContext<TPixel> Transform<TPixel>(this IImageProcessingContext<TPixel> source, Matrix4x4 matrix, IResampler sampler)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new ProjectiveTransformProcessor<TPixel>(matrix, sampler, source.GetCurrentSize()));
/// <summary>
/// Applies a projective transform to the image by the given matrix using the specified sampling algorithm.
/// TODO: Doesn't work yet! Implement tests + Finish implementation + Document Matrix4x4 behavior
/// TODO: Should we be offsetting the matrix here?
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image to transform.</param>

41
tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs

@ -39,25 +39,24 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
{ 0, 1f, 2f, 0, 0 },
};
public static readonly TheoryData<string> ResamplerNames =
new TheoryData<string>
{
nameof(KnownResamplers.Bicubic),
nameof(KnownResamplers.Box),
nameof(KnownResamplers.CatmullRom),
nameof(KnownResamplers.Hermite),
nameof(KnownResamplers.Lanczos2),
nameof(KnownResamplers.Lanczos3),
nameof(KnownResamplers.Lanczos5),
nameof(KnownResamplers.Lanczos8),
nameof(KnownResamplers.MitchellNetravali),
nameof(KnownResamplers.NearestNeighbor),
nameof(KnownResamplers.Robidoux),
nameof(KnownResamplers.RobidouxSharp),
nameof(KnownResamplers.Spline),
nameof(KnownResamplers.Triangle),
nameof(KnownResamplers.Welch),
};
public static readonly TheoryData<string> ResamplerNames = new TheoryData<string>
{
nameof(KnownResamplers.Bicubic),
nameof(KnownResamplers.Box),
nameof(KnownResamplers.CatmullRom),
nameof(KnownResamplers.Hermite),
nameof(KnownResamplers.Lanczos2),
nameof(KnownResamplers.Lanczos3),
nameof(KnownResamplers.Lanczos5),
nameof(KnownResamplers.Lanczos8),
nameof(KnownResamplers.MitchellNetravali),
nameof(KnownResamplers.NearestNeighbor),
nameof(KnownResamplers.Robidoux),
nameof(KnownResamplers.RobidouxSharp),
nameof(KnownResamplers.Spline),
nameof(KnownResamplers.Triangle),
nameof(KnownResamplers.Welch),
};
public static readonly TheoryData<string> Transform_DoesNotCreateEdgeArtifacts_ResamplerNames =
new TheoryData<string>
@ -124,7 +123,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails);
}
}
[Theory]
[WithTestPatternImages(96, 96, PixelTypes.Rgba32, 50, 0.8f)]
public void Transform_RotateScale_ManuallyCentered<TPixel>(TestImageProvider<TPixel> provider, float angleDeg, float s)
@ -166,7 +165,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
using (Image<TPixel> image = provider.GetImage())
{
var m = Matrix3x2.CreateScale(2.0F, 1.5F);
image.Mutate(i => i.Transform(m, KnownResamplers.Spline, rectangle));
image.DebugSave(provider);

137
tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs

@ -0,0 +1,137 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Reflection;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Transforms;
using SixLabors.ImageSharp.Processing.Transforms.Resamplers;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Processing.Transforms
{
using Xunit.Abstractions;
public class ProjectiveTransformTests
{
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.03f, 3);
private static readonly ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.5f, 3);
private ITestOutputHelper Output { get; }
public static readonly TheoryData<string> ResamplerNames = new TheoryData<string>
{
nameof(KnownResamplers.Bicubic),
nameof(KnownResamplers.Box),
nameof(KnownResamplers.CatmullRom),
nameof(KnownResamplers.Hermite),
nameof(KnownResamplers.Lanczos2),
nameof(KnownResamplers.Lanczos3),
nameof(KnownResamplers.Lanczos5),
nameof(KnownResamplers.Lanczos8),
nameof(KnownResamplers.MitchellNetravali),
nameof(KnownResamplers.NearestNeighbor),
nameof(KnownResamplers.Robidoux),
nameof(KnownResamplers.RobidouxSharp),
nameof(KnownResamplers.Spline),
nameof(KnownResamplers.Triangle),
nameof(KnownResamplers.Welch),
};
public static readonly TheoryData<TaperSide, TaperCorner> TaperMatrixData = new TheoryData<TaperSide, TaperCorner>
{
{ TaperSide.Bottom, TaperCorner.Both },
{ TaperSide.Bottom, TaperCorner.LeftOrTop },
{ TaperSide.Bottom, TaperCorner.RightOrBottom },
{ TaperSide.Top, TaperCorner.Both },
{ TaperSide.Top, TaperCorner.LeftOrTop },
{ TaperSide.Top, TaperCorner.RightOrBottom },
{ TaperSide.Left, TaperCorner.Both },
{ TaperSide.Left, TaperCorner.LeftOrTop },
{ TaperSide.Left, TaperCorner.RightOrBottom },
{ TaperSide.Right, TaperCorner.Both },
{ TaperSide.Right, TaperCorner.LeftOrTop },
{ TaperSide.Right, TaperCorner.RightOrBottom },
};
public ProjectiveTransformTests(ITestOutputHelper output)
{
this.Output = output;
}
[Theory]
[WithTestPatternImages(nameof(ResamplerNames), 150, 150, PixelTypes.Rgba32)]
public void Transform_WithSampler<TPixel>(TestImageProvider<TPixel> provider, string resamplerName)
where TPixel : struct, IPixel<TPixel>
{
IResampler sampler = GetResampler(resamplerName);
using (Image<TPixel> image = provider.GetImage())
{
Matrix4x4 m = ProjectiveTransformHelper.CreateTaperMatrix(image.Size(), TaperSide.Right, TaperCorner.Both, .5F);
image.Mutate(i => { i.Transform(m, sampler); });
image.DebugSave(provider, resamplerName);
image.CompareToReferenceOutput(ValidatorComparer, provider, resamplerName);
}
}
[Theory]
[WithSolidFilledImages(nameof(TaperMatrixData), 30, 30, nameof(Rgba32.Red), PixelTypes.Rgba32)]
public void Transform_WithTaperMatrix<TPixel>(TestImageProvider<TPixel> provider, TaperSide taperSide, TaperCorner taperCorner)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
Matrix4x4 m = ProjectiveTransformHelper.CreateTaperMatrix(image.Size(), taperSide, taperCorner, .5F);
image.Mutate(i => { i.Transform(m); });
string testOutputDetails = $"{taperSide}-{taperCorner}";
image.DebugSave(provider, testOutputDetails);
image.CompareFirstFrameToReferenceOutput(TolerantComparer, provider, testOutputDetails);
}
}
[Theory]
[WithSolidFilledImages(100, 100, 0, 0, 255, PixelTypes.Rgba32)]
public void RawTransformMatchesDocumentedExample<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
// Printing some extra output to help investigating roundoff errors:
this.Output.WriteLine($"Vector.IsHardwareAccelerated: {Vector.IsHardwareAccelerated}");
// This test matches the output described in the example at
// https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/transforms/non-affine
using (Image<TPixel> image = provider.GetImage())
{
Matrix4x4 m = Matrix4x4.Identity;
m.M13 = 0.01F;
image.Mutate(i => { i.Transform(m); });
image.DebugSave(provider);
image.CompareToReferenceOutput(TolerantComparer, provider);
}
}
private static IResampler GetResampler(string name)
{
PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name);
if (property == null)
{
throw new Exception("Invalid property name!");
}
return (IResampler)property.GetValue(null);
}
}
}

2
tests/Images/External

@ -1 +1 @@
Subproject commit 5a9a88380166a87521d10048f53cda7f5f761d66
Subproject commit f641620eb5378db49d6153bbf1443ad13bda2379
Loading…
Cancel
Save