mirror of https://github.com/SixLabors/ImageSharp
14 changed files with 360 additions and 476 deletions
@ -0,0 +1,177 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Numerics; |
||||
|
using SixLabors.ImageSharp.Processing.Processors.Transforms; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A helper class for constructing <see cref="Matrix3x2"/> instances for use in affine transforms.
|
||||
|
/// </summary>
|
||||
|
public class AffineTransformBuilder |
||||
|
{ |
||||
|
private readonly List<Matrix3x2> matrices = new List<Matrix3x2>(); |
||||
|
private Rectangle rectangle; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="AffineTransformBuilder"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="sourceSize">The source image size.</param>
|
||||
|
public AffineTransformBuilder(Size sourceSize) => this.Size = sourceSize; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="AffineTransformBuilder"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="sourceRectangle">The source rectangle.</param>
|
||||
|
public AffineTransformBuilder(Rectangle sourceRectangle) |
||||
|
: this(sourceRectangle.Size) |
||||
|
=> this.rectangle = sourceRectangle; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Prepends a centered rotation matrix using the given rotation in degrees.
|
||||
|
/// </summary>
|
||||
|
/// <param name="degrees">The amount of rotation, in degrees.</param>
|
||||
|
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
|
||||
|
public AffineTransformBuilder PrependRotateMatrixDegrees(float degrees) |
||||
|
{ |
||||
|
this.matrices.Insert(0, TransformUtils.CreateRotationMatrixDegrees(degrees, this.Size)); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the source image size.
|
||||
|
/// </summary>
|
||||
|
internal Size Size { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Appends a centered rotation matrix using the given rotation in degrees.
|
||||
|
/// </summary>
|
||||
|
/// <param name="degrees">The amount of rotation, in degrees.</param>
|
||||
|
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
|
||||
|
public AffineTransformBuilder AppendRotateMatrixDegrees(float degrees) |
||||
|
{ |
||||
|
this.matrices.Add(TransformUtils.CreateRotationMatrixDegrees(degrees, this.Size)); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Prepends a scale matrix from the given vector scale.
|
||||
|
/// </summary>
|
||||
|
/// <param name="scales">The horizontal and vertical scale.</param>
|
||||
|
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
|
||||
|
public AffineTransformBuilder PrependScaleMatrix(SizeF scales) |
||||
|
{ |
||||
|
this.matrices.Insert(0, Matrix3x2Extensions.CreateScale(scales)); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Appends a scale matrix from the given vector scale.
|
||||
|
/// </summary>
|
||||
|
/// <param name="scales">The horizontal and vertical scale.</param>
|
||||
|
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
|
||||
|
public AffineTransformBuilder AppendScaleMatrix(SizeF scales) |
||||
|
{ |
||||
|
this.matrices.Add(Matrix3x2Extensions.CreateScale(scales)); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Prepends a centered skew matrix from the give angles in degrees.
|
||||
|
/// </summary>
|
||||
|
/// <param name="degreesX">The X angle, in degrees.</param>
|
||||
|
/// <param name="degreesY">The Y angle, in degrees.</param>
|
||||
|
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
|
||||
|
public AffineTransformBuilder PrependSkewMatrixDegrees(float degreesX, float degreesY) |
||||
|
{ |
||||
|
this.matrices.Insert(0, TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, this.Size)); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Appends a centered skew matrix from the give angles in degrees.
|
||||
|
/// </summary>
|
||||
|
/// <param name="degreesX">The X angle, in degrees.</param>
|
||||
|
/// <param name="degreesY">The Y angle, in degrees.</param>
|
||||
|
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
|
||||
|
public AffineTransformBuilder AppendSkewMatrixDegrees(float degreesX, float degreesY) |
||||
|
{ |
||||
|
this.matrices.Add(TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, this.Size)); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Prepends a translation matrix from the given vector.
|
||||
|
/// </summary>
|
||||
|
/// <param name="position">The translation position.</param>
|
||||
|
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
|
||||
|
public AffineTransformBuilder PrependTranslationMatrix(PointF position) |
||||
|
{ |
||||
|
this.matrices.Insert(0, Matrix3x2Extensions.CreateTranslation(position)); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Appends a translation matrix from the given vector.
|
||||
|
/// </summary>
|
||||
|
/// <param name="position">The translation position.</param>
|
||||
|
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
|
||||
|
public AffineTransformBuilder AppendTranslationMatrix(PointF position) |
||||
|
{ |
||||
|
this.matrices.Add(Matrix3x2Extensions.CreateTranslation(position)); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Prepends a raw matrix.
|
||||
|
/// </summary>
|
||||
|
/// <param name="matrix">The matrix to prepend.</param>
|
||||
|
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
|
||||
|
public AffineTransformBuilder PrependMatrix(Matrix3x2 matrix) |
||||
|
{ |
||||
|
this.matrices.Insert(0, matrix); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Appends a raw matrix.
|
||||
|
/// </summary>
|
||||
|
/// <param name="matrix">The matrix to append.</param>
|
||||
|
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
|
||||
|
public AffineTransformBuilder AppendMatrix(Matrix3x2 matrix) |
||||
|
{ |
||||
|
this.matrices.Add(matrix); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns the combined matrix.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The <see cref="Matrix3x2"/>.</returns>
|
||||
|
public Matrix3x2 BuildMatrix() |
||||
|
{ |
||||
|
Matrix3x2 matrix = Matrix3x2.Identity; |
||||
|
|
||||
|
// Translate the origin matrix to cater for source rectangle offsets.
|
||||
|
if (!this.rectangle.Equals(default)) |
||||
|
{ |
||||
|
matrix *= Matrix3x2.CreateTranslation(-this.rectangle.Location); |
||||
|
} |
||||
|
|
||||
|
foreach (Matrix3x2 m in this.matrices) |
||||
|
{ |
||||
|
matrix *= m; |
||||
|
} |
||||
|
|
||||
|
return matrix; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Removes all matrices from the builder.
|
||||
|
/// </summary>
|
||||
|
public void Clear() => this.matrices.Clear(); |
||||
|
} |
||||
|
} |
||||
@ -1,239 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using System.Numerics; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using SixLabors.ImageSharp.Advanced; |
|
||||
using SixLabors.ImageSharp.Memory; |
|
||||
using SixLabors.ImageSharp.ParallelUtils; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
using SixLabors.Memory; |
|
||||
using SixLabors.Primitives; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Provides the base methods to perform affine transforms on an image.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
||||
internal class AffineTransformProcessorOld<TPixel> : InterpolatedTransformProcessorBase<TPixel> |
|
||||
where TPixel : struct, IPixel<TPixel> |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="AffineTransformProcessorOld{TPixel}"/> class.
|
|
||||
/// </summary>
|
|
||||
/// <param name="matrix">The transform matrix</param>
|
|
||||
/// <param name="sampler">The sampler to perform the transform operation.</param>
|
|
||||
/// <param name="targetDimensions">The target dimensions to constrain the transformed image to.</param>
|
|
||||
public AffineTransformProcessorOld(Matrix3x2 matrix, IResampler sampler, Size targetDimensions) |
|
||||
: base(sampler) |
|
||||
{ |
|
||||
this.TransformMatrix = matrix; |
|
||||
this.TargetDimensions = targetDimensions; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the matrix used to supply the affine transform
|
|
||||
/// </summary>
|
|
||||
public Matrix3x2 TransformMatrix { get; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the target dimensions to constrain the transformed image to
|
|
||||
/// </summary>
|
|
||||
public Size TargetDimensions { get; } |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle) |
|
||||
{ |
|
||||
// We will always be creating the clone even for mutate because we may need to resize the canvas
|
|
||||
IEnumerable<ImageFrame<TPixel>> frames = |
|
||||
source.Frames.Select(x => new ImageFrame<TPixel>(source.GetConfiguration(), this.TargetDimensions, x.MetaData.DeepClone())); |
|
||||
|
|
||||
// Use the overload to prevent an extra frame being added
|
|
||||
return new Image<TPixel>(source.GetConfiguration(), source.MetaData.DeepClone(), frames); |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void OnFrameApply( |
|
||||
ImageFrame<TPixel> source, |
|
||||
ImageFrame<TPixel> destination, |
|
||||
Rectangle sourceRectangle, |
|
||||
Configuration configuration) |
|
||||
{ |
|
||||
int height = this.TargetDimensions.Height; |
|
||||
int width = this.TargetDimensions.Width; |
|
||||
|
|
||||
Rectangle sourceBounds = source.Bounds(); |
|
||||
var targetBounds = new Rectangle(0, 0, width, height); |
|
||||
|
|
||||
// Since could potentially be resizing the canvas we might need to re-calculate the matrix
|
|
||||
Matrix3x2 matrix = this.GetProcessingMatrix(sourceBounds, targetBounds); |
|
||||
|
|
||||
// Convert from screen to world space.
|
|
||||
Matrix3x2.Invert(matrix, out matrix); |
|
||||
|
|
||||
if (this.Sampler is NearestNeighborResampler) |
|
||||
{ |
|
||||
ParallelHelper.IterateRows( |
|
||||
targetBounds, |
|
||||
configuration, |
|
||||
rows => |
|
||||
{ |
|
||||
for (int y = rows.Min; y < rows.Max; y++) |
|
||||
{ |
|
||||
Span<TPixel> destRow = destination.GetPixelRowSpan(y); |
|
||||
|
|
||||
for (int x = 0; x < width; x++) |
|
||||
{ |
|
||||
var point = Point.Transform(new Point(x, y), matrix); |
|
||||
if (sourceBounds.Contains(point.X, point.Y)) |
|
||||
{ |
|
||||
destRow[x] = source[point.X, point.Y]; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
int maxSourceX = source.Width - 1; |
|
||||
int maxSourceY = source.Height - 1; |
|
||||
(float radius, float scale, float ratio) xRadiusScale = this.GetSamplingRadius(source.Width, destination.Width); |
|
||||
(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); |
|
||||
IResampler sampler = this.Sampler; |
|
||||
var maxSource = new Vector4(maxSourceX, maxSourceY, maxSourceX, maxSourceY); |
|
||||
int xLength = (int)MathF.Ceiling((radius.X * 2) + 2); |
|
||||
int yLength = (int)MathF.Ceiling((radius.Y * 2) + 2); |
|
||||
|
|
||||
MemoryAllocator memoryAllocator = configuration.MemoryAllocator; |
|
||||
|
|
||||
using (Buffer2D<float> yBuffer = memoryAllocator.Allocate2D<float>(yLength, height)) |
|
||||
using (Buffer2D<float> xBuffer = memoryAllocator.Allocate2D<float>(xLength, height)) |
|
||||
{ |
|
||||
ParallelHelper.IterateRows( |
|
||||
targetBounds, |
|
||||
configuration, |
|
||||
rows => |
|
||||
{ |
|
||||
for (int y = rows.Min; y < rows.Max; 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); |
|
||||
|
|
||||
// Clamp sampling pixel radial extents to the source image edges
|
|
||||
Vector2 maxXY = point + radius; |
|
||||
Vector2 minXY = point - radius; |
|
||||
|
|
||||
// max, maxY, minX, minY
|
|
||||
var extents = new Vector4( |
|
||||
MathF.Floor(maxXY.X + .5F), |
|
||||
MathF.Floor(maxXY.Y + .5F), |
|
||||
MathF.Ceiling(minXY.X - .5F), |
|
||||
MathF.Ceiling(minXY.Y - .5F)); |
|
||||
|
|
||||
int right = (int)extents.X; |
|
||||
int bottom = (int)extents.Y; |
|
||||
int left = (int)extents.Z; |
|
||||
int top = (int)extents.W; |
|
||||
|
|
||||
extents = Vector4.Clamp(extents, Vector4.Zero, maxSource); |
|
||||
|
|
||||
int maxX = (int)extents.X; |
|
||||
int maxY = (int)extents.Y; |
|
||||
int minX = (int)extents.Z; |
|
||||
int minY = (int)extents.W; |
|
||||
|
|
||||
if (minX == maxX || minY == maxY) |
|
||||
{ |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
// It appears these have to be calculated on-the-fly.
|
|
||||
// Precalculating transformed weights would require prior knowledge of every transformed pixel location
|
|
||||
// since they can be at sub-pixel positions on both axis.
|
|
||||
// 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, |
|
||||
ref ySpanRef, |
|
||||
yLength); |
|
||||
|
|
||||
CalculateWeightsDown( |
|
||||
left, |
|
||||
right, |
|
||||
minX, |
|
||||
maxX, |
|
||||
point.X, |
|
||||
sampler, |
|
||||
xScale, |
|
||||
ref xSpanRef, |
|
||||
xLength); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
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 = Unsafe.Add(ref ySpanRef, yy); |
|
||||
|
|
||||
for (int xx = 0, i = minX; i <= maxX; i++, xx++) |
|
||||
{ |
|
||||
float xWeight = Unsafe.Add(ref xSpanRef, xx); |
|
||||
|
|
||||
// Values are first premultiplied to prevent darkening of edge pixels
|
|
||||
var current = source[i, j].ToVector4(); |
|
||||
Vector4Utils.Premultiply(ref current); |
|
||||
sum += current * xWeight * yWeight; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
ref TPixel dest = ref Unsafe.Add(ref destRowRef, x); |
|
||||
|
|
||||
// Reverse the premultiplication
|
|
||||
Vector4Utils.UnPremultiply(ref sum); |
|
||||
dest.FromVector4(sum); |
|
||||
} |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets a transform matrix adjusted for final processing based upon the target image bounds.
|
|
||||
/// </summary>
|
|
||||
/// <param name="sourceRectangle">The source image bounds.</param>
|
|
||||
/// <param name="destinationRectangle">The destination image bounds.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Matrix3x2"/>.
|
|
||||
/// </returns>
|
|
||||
protected virtual Matrix3x2 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle) |
|
||||
=> this.TransformMatrix; |
|
||||
} |
|
||||
} |
|
||||
@ -1,38 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System.Numerics; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
using SixLabors.Primitives; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// A base class that provides methods to allow the automatic centering of affine transforms
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
||||
internal abstract class CenteredAffineTransformProcessor<TPixel> : AffineTransformProcessorOld<TPixel> |
|
||||
where TPixel : struct, IPixel<TPixel> |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="CenteredAffineTransformProcessor{TPixel}"/> class.
|
|
||||
/// </summary>
|
|
||||
/// <param name="matrix">The transform matrix</param>
|
|
||||
/// <param name="sampler">The sampler to perform the transform operation.</param>
|
|
||||
/// <param name="sourceSize">The source image size</param>
|
|
||||
protected CenteredAffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Size sourceSize) |
|
||||
: base(matrix, sampler, GetTransformedDimensions(sourceSize, matrix)) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override Matrix3x2 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle) |
|
||||
=> TransformHelpers.GetCenteredTransformMatrix(sourceRectangle, destinationRectangle, this.TransformMatrix); |
|
||||
|
|
||||
private static Size GetTransformedDimensions(Size sourceDimensions, Matrix3x2 matrix) |
|
||||
{ |
|
||||
var sourceRectangle = new Rectangle(0, 0, sourceDimensions.Width, sourceDimensions.Height); |
|
||||
return TransformHelpers.GetTransformedBoundingRectangle(sourceRectangle, matrix).Size; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,45 @@ |
|||||
|
using BenchmarkDotNet.Attributes; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.ImageSharp.Processing; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Benchmarks.Samplers |
||||
|
{ |
||||
|
[Config(typeof(Config.ShortClr))] |
||||
|
public class Skew |
||||
|
{ |
||||
|
[Benchmark] |
||||
|
public Size DoSkew() |
||||
|
{ |
||||
|
using (var image = new Image<Rgba32>(Configuration.Default, 400, 400, Rgba32.BlanchedAlmond)) |
||||
|
{ |
||||
|
image.Mutate(x => x.Skew(20, 10)); |
||||
|
|
||||
|
return image.Size(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Nov 7 2018
|
||||
|
//BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17763
|
||||
|
//Intel Core i7-6600U CPU 2.60GHz(Skylake), 1 CPU, 4 logical and 2 physical cores
|
||||
|
//.NET Core SDK = 2.1.403
|
||||
|
|
||||
|
// [Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT
|
||||
|
// Job-KKDIMW : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0
|
||||
|
// Job-IUZRFA : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT
|
||||
|
|
||||
|
//LaunchCount=1 TargetCount=3 WarmupCount=3
|
||||
|
|
||||
|
// #### BEFORE ####:
|
||||
|
//Method | Runtime | Mean | Error | StdDev | Allocated |
|
||||
|
//------- |-------- |---------:|---------:|----------:|----------:|
|
||||
|
// DoSkew | Clr | 78.14 ms | 8.383 ms | 0.4736 ms | 6 KB |
|
||||
|
// DoSkew | Core | 44.22 ms | 4.109 ms | 0.2322 ms | 4.28 KB |
|
||||
|
|
||||
|
// #### AFTER ####:
|
||||
|
//Method | Runtime | Mean | Error | StdDev | Allocated |
|
||||
|
//------- |-------- |---------:|----------:|----------:|----------:|
|
||||
|
// DoSkew | Clr | 71.63 ms | 25.589 ms | 1.4458 ms | 6 KB |
|
||||
|
// DoSkew | Core | 38.99 ms | 8.640 ms | 0.4882 ms | 4.36 KB |
|
||||
Loading…
Reference in new issue