mirror of https://github.com/SixLabors/ImageSharp
58 changed files with 1124 additions and 1067 deletions
@ -0,0 +1,29 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Reflection; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using SixLabors.ImageSharp; |
||||
|
|
||||
|
namespace SixLabors |
||||
|
{ |
||||
|
internal static partial class Guard |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Ensures that the value is a value type.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The target object, which cannot be null.</param>
|
||||
|
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
|
||||
|
/// <typeparam name="TValue">The type of the value.</typeparam>
|
||||
|
/// <exception cref="ArgumentException"><paramref name="value"/> is not a value type.</exception>
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
public static void MustBeValueType<TValue>(TValue value, string parameterName) |
||||
|
{ |
||||
|
if (!value.GetType().GetTypeInfo().IsValueType) |
||||
|
{ |
||||
|
ThrowArgumentException("Type must be a struct.", parameterName); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,188 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Numerics; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
using SixLabors.ImageSharp.Advanced; |
|
||||
using SixLabors.ImageSharp.Memory; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
|
|
||||
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 AffineTransformProcessor<TPixel> : TransformProcessor<TPixel> |
|
||||
where TPixel : unmanaged, IPixel<TPixel> |
|
||||
{ |
|
||||
private readonly Size targetSize; |
|
||||
private readonly Matrix3x2 transformMatrix; |
|
||||
private readonly IResampler resampler; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="AffineTransformProcessor{TPixel}"/> class.
|
|
||||
/// </summary>
|
|
||||
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
|
|
||||
/// <param name="definition">The <see cref="AffineTransformProcessor"/> defining the processor parameters.</param>
|
|
||||
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
|
|
||||
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
|
|
||||
public AffineTransformProcessor(Configuration configuration, AffineTransformProcessor definition, Image<TPixel> source, Rectangle sourceRectangle) |
|
||||
: base(configuration, source, sourceRectangle) |
|
||||
{ |
|
||||
this.targetSize = definition.TargetDimensions; |
|
||||
this.transformMatrix = definition.TransformMatrix; |
|
||||
this.resampler = definition.Sampler; |
|
||||
} |
|
||||
|
|
||||
protected override Size GetTargetSize() => this.targetSize; |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination) |
|
||||
{ |
|
||||
// Handle transforms that result in output identical to the original.
|
|
||||
if (this.transformMatrix.Equals(default) || this.transformMatrix.Equals(Matrix3x2.Identity)) |
|
||||
{ |
|
||||
// The clone will be blank here copy all the pixel data over
|
|
||||
source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
int width = this.targetSize.Width; |
|
||||
var targetBounds = new Rectangle(Point.Empty, this.targetSize); |
|
||||
Configuration configuration = this.Configuration; |
|
||||
|
|
||||
// Convert from screen to world space.
|
|
||||
Matrix3x2.Invert(this.transformMatrix, out Matrix3x2 matrix); |
|
||||
|
|
||||
if (this.resampler is NearestNeighborResampler) |
|
||||
{ |
|
||||
var nnOperation = new NearestNeighborRowIntervalOperation(this.SourceRectangle, ref matrix, width, source, destination); |
|
||||
ParallelRowIterator.IterateRows( |
|
||||
configuration, |
|
||||
targetBounds, |
|
||||
in nnOperation); |
|
||||
|
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); |
|
||||
|
|
||||
var operation = new RowIntervalOperation(configuration, kernelMap, ref matrix, width, source, destination); |
|
||||
ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>( |
|
||||
configuration, |
|
||||
targetBounds, |
|
||||
in operation); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// A <see langword="struct"/> implementing the nearest neighbor resampler logic for <see cref="AffineTransformProcessor{T}"/>.
|
|
||||
/// </summary>
|
|
||||
private readonly struct NearestNeighborRowIntervalOperation : IRowIntervalOperation |
|
||||
{ |
|
||||
private readonly Rectangle bounds; |
|
||||
private readonly Matrix3x2 matrix; |
|
||||
private readonly int maxX; |
|
||||
private readonly ImageFrame<TPixel> source; |
|
||||
private readonly ImageFrame<TPixel> destination; |
|
||||
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
|
||||
public NearestNeighborRowIntervalOperation( |
|
||||
Rectangle bounds, |
|
||||
ref Matrix3x2 matrix, |
|
||||
int maxX, |
|
||||
ImageFrame<TPixel> source, |
|
||||
ImageFrame<TPixel> destination) |
|
||||
{ |
|
||||
this.bounds = bounds; |
|
||||
this.matrix = matrix; |
|
||||
this.maxX = maxX; |
|
||||
this.source = source; |
|
||||
this.destination = destination; |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
/// <param name="rows"></param>
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
|
||||
public void Invoke(in RowInterval rows) |
|
||||
{ |
|
||||
for (int y = rows.Min; y < rows.Max; y++) |
|
||||
{ |
|
||||
Span<TPixel> destRow = this.destination.GetPixelRowSpan(y); |
|
||||
|
|
||||
for (int x = 0; x < this.maxX; x++) |
|
||||
{ |
|
||||
var point = Point.Transform(new Point(x, y), this.matrix); |
|
||||
if (this.bounds.Contains(point.X, point.Y)) |
|
||||
{ |
|
||||
destRow[x] = this.source[point.X, point.Y]; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// A <see langword="struct"/> implementing the transformation logic for <see cref="AffineTransformProcessor{T}"/>.
|
|
||||
/// </summary>
|
|
||||
private readonly struct RowIntervalOperation : IRowIntervalOperation<Vector4> |
|
||||
{ |
|
||||
private readonly Configuration configuration; |
|
||||
private readonly TransformKernelMap kernelMap; |
|
||||
private readonly Matrix3x2 matrix; |
|
||||
private readonly int maxX; |
|
||||
private readonly ImageFrame<TPixel> source; |
|
||||
private readonly ImageFrame<TPixel> destination; |
|
||||
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
|
||||
public RowIntervalOperation( |
|
||||
Configuration configuration, |
|
||||
TransformKernelMap kernelMap, |
|
||||
ref Matrix3x2 matrix, |
|
||||
int maxX, |
|
||||
ImageFrame<TPixel> source, |
|
||||
ImageFrame<TPixel> destination) |
|
||||
{ |
|
||||
this.configuration = configuration; |
|
||||
this.kernelMap = kernelMap; |
|
||||
this.matrix = matrix; |
|
||||
this.maxX = maxX; |
|
||||
this.source = source; |
|
||||
this.destination = destination; |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
|
||||
public void Invoke(in RowInterval rows, Span<Vector4> span) |
|
||||
{ |
|
||||
for (int y = rows.Min; y < rows.Max; y++) |
|
||||
{ |
|
||||
Span<TPixel> targetRowSpan = this.destination.GetPixelRowSpan(y); |
|
||||
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan, span); |
|
||||
ref float ySpanRef = ref this.kernelMap.GetYStartReference(y); |
|
||||
ref float xSpanRef = ref this.kernelMap.GetXStartReference(y); |
|
||||
|
|
||||
for (int x = 0; x < this.maxX; 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), this.matrix); |
|
||||
this.kernelMap.Convolve( |
|
||||
point, |
|
||||
x, |
|
||||
ref ySpanRef, |
|
||||
ref xSpanRef, |
|
||||
this.source.PixelBuffer, |
|
||||
span); |
|
||||
} |
|
||||
|
|
||||
PixelOperations<TPixel>.Instance.FromVector4Destructive( |
|
||||
this.configuration, |
|
||||
span, |
|
||||
targetRowSpan); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,23 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements an algorithm to alter the pixels of an image via resampling transforms.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
public interface IResamplingTransformImageProcessor<TPixel> : IImageProcessor<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Applies a resampling transform with the given sampler.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TResampler">The type of sampler.</typeparam>
|
||||
|
/// <param name="sampler">The sampler to use.</param>
|
||||
|
void ApplyTransform<TResampler>(in TResampler sampler) |
||||
|
where TResampler : struct, IResampler; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,234 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using SixLabors.ImageSharp.Advanced; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
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 AffineTransformProcessor<TPixel> : TransformProcessor<TPixel>, IResamplingTransformImageProcessor<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
private readonly Size destinationSize; |
||||
|
private readonly Matrix3x2 transformMatrix; |
||||
|
private readonly IResampler resampler; |
||||
|
private ImageFrame<TPixel> source; |
||||
|
private ImageFrame<TPixel> destination; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="AffineTransformProcessor{TPixel}"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
|
||||
|
/// <param name="definition">The <see cref="AffineTransformProcessor"/> defining the processor parameters.</param>
|
||||
|
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
|
||||
|
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
|
||||
|
public AffineTransformProcessor(Configuration configuration, AffineTransformProcessor definition, Image<TPixel> source, Rectangle sourceRectangle) |
||||
|
: base(configuration, source, sourceRectangle) |
||||
|
{ |
||||
|
this.destinationSize = definition.DestinationSize; |
||||
|
this.transformMatrix = definition.TransformMatrix; |
||||
|
this.resampler = definition.Sampler; |
||||
|
} |
||||
|
|
||||
|
protected override Size GetDestinationSize() => this.destinationSize; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination) |
||||
|
{ |
||||
|
this.source = source; |
||||
|
this.destination = destination; |
||||
|
this.resampler.ApplyTransform(this); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void ApplyTransform<TResampler>(in TResampler sampler) |
||||
|
where TResampler : struct, IResampler |
||||
|
{ |
||||
|
Configuration configuration = this.Configuration; |
||||
|
ImageFrame<TPixel> source = this.source; |
||||
|
ImageFrame<TPixel> destination = this.destination; |
||||
|
Matrix3x2 matrix = this.transformMatrix; |
||||
|
|
||||
|
// Handle transforms that result in output identical to the original.
|
||||
|
if (matrix.Equals(default) || matrix.Equals(Matrix3x2.Identity)) |
||||
|
{ |
||||
|
// The clone will be blank here copy all the pixel data over
|
||||
|
source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Convert from screen to world space.
|
||||
|
Matrix3x2.Invert(matrix, out matrix); |
||||
|
|
||||
|
if (sampler is NearestNeighborResampler) |
||||
|
{ |
||||
|
var nnOperation = new NNAffineOperation(source, destination, matrix); |
||||
|
ParallelRowIterator.IterateRows( |
||||
|
configuration, |
||||
|
destination.Bounds(), |
||||
|
in nnOperation); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
int yRadius = LinearTransformUtilities.GetSamplingRadius(in sampler, source.Height, destination.Height); |
||||
|
int xRadius = LinearTransformUtilities.GetSamplingRadius(in sampler, source.Width, destination.Width); |
||||
|
var radialExtents = new Vector2(xRadius, yRadius); |
||||
|
int yLength = (yRadius * 2) + 1; |
||||
|
int xLength = (xRadius * 2) + 1; |
||||
|
|
||||
|
// We use 2D buffers so that we can access the weight spans per row in parallel.
|
||||
|
using Buffer2D<float> yKernelBuffer = configuration.MemoryAllocator.Allocate2D<float>(yLength, destination.Height); |
||||
|
using Buffer2D<float> xKernelBuffer = configuration.MemoryAllocator.Allocate2D<float>(xLength, destination.Height); |
||||
|
|
||||
|
int maxX = source.Width - 1; |
||||
|
int maxY = source.Height - 1; |
||||
|
var maxSourceExtents = new Vector4(maxX, maxY, maxX, maxY); |
||||
|
|
||||
|
var operation = new AffineOperation<TResampler>( |
||||
|
configuration, |
||||
|
source, |
||||
|
destination, |
||||
|
yKernelBuffer, |
||||
|
xKernelBuffer, |
||||
|
in sampler, |
||||
|
matrix, |
||||
|
radialExtents, |
||||
|
maxSourceExtents); |
||||
|
|
||||
|
ParallelRowIterator.IterateRows<AffineOperation<TResampler>, Vector4>( |
||||
|
configuration, |
||||
|
destination.Bounds(), |
||||
|
in operation); |
||||
|
} |
||||
|
|
||||
|
private readonly struct NNAffineOperation : IRowIntervalOperation |
||||
|
{ |
||||
|
private readonly ImageFrame<TPixel> source; |
||||
|
private readonly ImageFrame<TPixel> destination; |
||||
|
private readonly Rectangle bounds; |
||||
|
private readonly Matrix3x2 matrix; |
||||
|
private readonly int maxX; |
||||
|
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
public NNAffineOperation( |
||||
|
ImageFrame<TPixel> source, |
||||
|
ImageFrame<TPixel> destination, |
||||
|
Matrix3x2 matrix) |
||||
|
{ |
||||
|
this.source = source; |
||||
|
this.destination = destination; |
||||
|
this.bounds = source.Bounds(); |
||||
|
this.matrix = matrix; |
||||
|
this.maxX = destination.Width; |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
public void Invoke(in RowInterval rows) |
||||
|
{ |
||||
|
for (int y = rows.Min; y < rows.Max; y++) |
||||
|
{ |
||||
|
Span<TPixel> destRow = this.destination.GetPixelRowSpan(y); |
||||
|
|
||||
|
for (int x = 0; x < this.maxX; x++) |
||||
|
{ |
||||
|
var point = Vector2.Transform(new Vector2(x, y), this.matrix); |
||||
|
int px = (int)MathF.Round(point.X); |
||||
|
int py = (int)MathF.Round(point.Y); |
||||
|
|
||||
|
if (this.bounds.Contains(px, py)) |
||||
|
{ |
||||
|
destRow[x] = this.source[px, py]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private readonly struct AffineOperation<TResampler> : IRowIntervalOperation<Vector4> |
||||
|
where TResampler : struct, IResampler |
||||
|
{ |
||||
|
private readonly Configuration configuration; |
||||
|
private readonly ImageFrame<TPixel> source; |
||||
|
private readonly ImageFrame<TPixel> destination; |
||||
|
private readonly Buffer2D<float> yKernelBuffer; |
||||
|
private readonly Buffer2D<float> xKernelBuffer; |
||||
|
private readonly TResampler sampler; |
||||
|
private readonly Matrix3x2 matrix; |
||||
|
private readonly Vector2 radialExtents; |
||||
|
private readonly Vector4 maxSourceExtents; |
||||
|
private readonly int maxX; |
||||
|
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
public AffineOperation( |
||||
|
Configuration configuration, |
||||
|
ImageFrame<TPixel> source, |
||||
|
ImageFrame<TPixel> destination, |
||||
|
Buffer2D<float> yKernelBuffer, |
||||
|
Buffer2D<float> xKernelBuffer, |
||||
|
in TResampler sampler, |
||||
|
Matrix3x2 matrix, |
||||
|
Vector2 radialExtents, |
||||
|
Vector4 maxSourceExtents) |
||||
|
{ |
||||
|
this.configuration = configuration; |
||||
|
this.source = source; |
||||
|
this.destination = destination; |
||||
|
this.yKernelBuffer = yKernelBuffer; |
||||
|
this.xKernelBuffer = xKernelBuffer; |
||||
|
this.sampler = sampler; |
||||
|
this.matrix = matrix; |
||||
|
this.radialExtents = radialExtents; |
||||
|
this.maxSourceExtents = maxSourceExtents; |
||||
|
this.maxX = destination.Width; |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
public void Invoke(in RowInterval rows, Span<Vector4> span) |
||||
|
{ |
||||
|
Buffer2D<TPixel> sourceBuffer = this.source.PixelBuffer; |
||||
|
for (int y = rows.Min; y < rows.Max; y++) |
||||
|
{ |
||||
|
PixelOperations<TPixel>.Instance.ToVector4( |
||||
|
this.configuration, |
||||
|
this.destination.GetPixelRowSpan(y), |
||||
|
span); |
||||
|
|
||||
|
ref float yKernelSpanRef = ref MemoryMarshal.GetReference(this.yKernelBuffer.GetRowSpan(y)); |
||||
|
ref float xKernelSpanRef = ref MemoryMarshal.GetReference(this.xKernelBuffer.GetRowSpan(y)); |
||||
|
|
||||
|
for (int x = 0; x < this.maxX; 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), this.matrix); |
||||
|
LinearTransformUtilities.Convolve( |
||||
|
in this.sampler, |
||||
|
point, |
||||
|
sourceBuffer, |
||||
|
span, |
||||
|
x, |
||||
|
ref yKernelSpanRef, |
||||
|
ref xKernelSpanRef, |
||||
|
this.radialExtents, |
||||
|
this.maxSourceExtents); |
||||
|
} |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.FromVector4Destructive( |
||||
|
this.configuration, |
||||
|
span, |
||||
|
this.destination.GetPixelRowSpan(y)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,104 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Utility methods for affine and projective transforms.
|
||||
|
/// </summary>
|
||||
|
internal static class LinearTransformUtilities |
||||
|
{ |
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
internal static int GetSamplingRadius<TResampler>(in TResampler sampler, int sourceSize, int destinationSize) |
||||
|
where TResampler : struct, IResampler |
||||
|
{ |
||||
|
double scale = sourceSize / destinationSize; |
||||
|
if (scale < 1) |
||||
|
{ |
||||
|
scale = 1; |
||||
|
} |
||||
|
|
||||
|
return (int)Math.Ceiling(scale * sampler.Radius); |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
internal static void Convolve<TResampler, TPixel>( |
||||
|
in TResampler sampler, |
||||
|
Vector2 transformedPoint, |
||||
|
Buffer2D<TPixel> sourcePixels, |
||||
|
Span<Vector4> targetRow, |
||||
|
int column, |
||||
|
ref float yKernelSpanRef, |
||||
|
ref float xKernelSpanRef, |
||||
|
Vector2 radialExtents, |
||||
|
Vector4 maxSourceExtents) |
||||
|
where TResampler : struct, IResampler |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
// Clamp sampling pixel radial extents to the source image edges
|
||||
|
Vector2 minXY = transformedPoint - radialExtents; |
||||
|
Vector2 maxXY = transformedPoint + radialExtents; |
||||
|
|
||||
|
// left, top, right, bottom
|
||||
|
var sourceExtents = new Vector4( |
||||
|
MathF.Ceiling(minXY.X), |
||||
|
MathF.Ceiling(minXY.Y), |
||||
|
MathF.Floor(maxXY.X), |
||||
|
MathF.Floor(maxXY.Y)); |
||||
|
|
||||
|
sourceExtents = Vector4.Clamp(sourceExtents, Vector4.Zero, maxSourceExtents); |
||||
|
|
||||
|
int left = (int)sourceExtents.X; |
||||
|
int top = (int)sourceExtents.Y; |
||||
|
int right = (int)sourceExtents.Z; |
||||
|
int bottom = (int)sourceExtents.W; |
||||
|
|
||||
|
if (left == right || top == bottom) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
CalculateWeights(in sampler, top, bottom, transformedPoint.Y, ref yKernelSpanRef); |
||||
|
CalculateWeights(in sampler, left, right, transformedPoint.X, ref xKernelSpanRef); |
||||
|
|
||||
|
Vector4 sum = Vector4.Zero; |
||||
|
for (int kernelY = 0, y = top; y <= bottom; y++, kernelY++) |
||||
|
{ |
||||
|
float yWeight = Unsafe.Add(ref yKernelSpanRef, kernelY); |
||||
|
|
||||
|
for (int kernelX = 0, x = left; x <= right; x++, kernelX++) |
||||
|
{ |
||||
|
float xWeight = Unsafe.Add(ref xKernelSpanRef, kernelX); |
||||
|
|
||||
|
// Values are first premultiplied to prevent darkening of edge pixels.
|
||||
|
var current = sourcePixels[x, y].ToVector4(); |
||||
|
Vector4Utils.Premultiply(ref current); |
||||
|
sum += current * xWeight * yWeight; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Reverse the premultiplication
|
||||
|
Vector4Utils.UnPremultiply(ref sum); |
||||
|
targetRow[column] = sum; |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
private static void CalculateWeights<TResampler>(in TResampler sampler, int min, int max, float point, ref float weightsRef) |
||||
|
where TResampler : struct, IResampler |
||||
|
{ |
||||
|
float sum = 0; |
||||
|
for (int x = 0, i = min; i <= max; i++, x++) |
||||
|
{ |
||||
|
float weight = sampler.GetValue(i - point); |
||||
|
sum += weight; |
||||
|
Unsafe.Add(ref weightsRef, x) = weight; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,26 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The Catmull-Rom filter is a well known standard Cubic Filter often used as a interpolation function.
|
|
||||
/// This filter produces a reasonably sharp edge, but without a the pronounced gradient change on large
|
|
||||
/// scale image enlargements that a 'Lagrange' filter can produce.
|
|
||||
/// <see href="http://www.imagemagick.org/Usage/filter/#cubic_bc"/>
|
|
||||
/// </summary>
|
|
||||
public class CatmullRomResampler : IResampler |
|
||||
{ |
|
||||
/// <inheritdoc/>
|
|
||||
public float Radius => 2; |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public float GetValue(float x) |
|
||||
{ |
|
||||
const float B = 0; |
|
||||
const float C = 0.5F; |
|
||||
|
|
||||
return ImageMaths.GetBcValue(x, B, C); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,112 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Cubic filters contain a collection of different filters of varying B-Spline and
|
||||
|
/// Cardinal values. With these two values you can generate any smoothly fitting
|
||||
|
/// (continuious first derivative) piece-wise cubic filter.
|
||||
|
/// <see href="http://www.imagemagick.org/Usage/filter/#cubic_bc"/>
|
||||
|
/// <see href="https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf"/>
|
||||
|
/// </summary>
|
||||
|
public readonly struct CubicResampler : IResampler |
||||
|
{ |
||||
|
private readonly float bspline; |
||||
|
private readonly float cardinal; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The Catmull-Rom filter is a well known standard Cubic Filter often used as a interpolation function.
|
||||
|
/// This filter produces a reasonably sharp edge, but without a the pronounced gradient change on large
|
||||
|
/// scale image enlargements that a 'Lagrange' filter can produce.
|
||||
|
/// </summary>
|
||||
|
public static CubicResampler CatmullRom = new CubicResampler(2, 0, .5F); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The Hermite filter is type of smoothed triangular interpolation Filter,
|
||||
|
/// This filter rounds off strong edges while preserving flat 'color levels' in the original image.
|
||||
|
/// </summary>
|
||||
|
public static CubicResampler Hermite = new CubicResampler(2, 0, 0); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The function implements the Mitchell-Netravali algorithm as described on
|
||||
|
/// <see href="https://de.wikipedia.org/wiki/Mitchell-Netravali-Filter">Wikipedia</see>
|
||||
|
/// </summary>
|
||||
|
public static CubicResampler MitchellNetravali = new CubicResampler(2, .3333333F, .3333333F); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The function implements the Robidoux algorithm.
|
||||
|
/// <see href="http://www.imagemagick.org/Usage/filter/#robidoux"/>
|
||||
|
/// </summary>
|
||||
|
public static CubicResampler Robidoux = new CubicResampler(2, .37821575509399867F, .31089212245300067F); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The function implements the Robidoux Sharp algorithm.
|
||||
|
/// <see href="http://www.imagemagick.org/Usage/filter/#robidoux"/>
|
||||
|
/// </summary>
|
||||
|
public static CubicResampler RobidouxSharp = new CubicResampler(2, .2620145123990142F, .3689927438004929F); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The function implements the spline algorithm.
|
||||
|
/// <see href="http://www.imagemagick.org/Usage/filter/#cubic_bc"/>
|
||||
|
/// </summary>
|
||||
|
/// <summary>
|
||||
|
/// The function implements the Robidoux Sharp algorithm.
|
||||
|
/// <see href="http://www.imagemagick.org/Usage/filter/#robidoux"/>
|
||||
|
/// </summary>
|
||||
|
public static CubicResampler Spline = new CubicResampler(2, 1, 0); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="CubicResampler"/> struct.
|
||||
|
/// </summary>
|
||||
|
/// <param name="radius">The sampling radius.</param>
|
||||
|
/// <param name="bspline">The B-Spline value.</param>
|
||||
|
/// <param name="cardinal">The Cardinal cubic value.</param>
|
||||
|
public CubicResampler(float radius, float bspline, float cardinal) |
||||
|
{ |
||||
|
this.Radius = radius; |
||||
|
this.bspline = bspline; |
||||
|
this.cardinal = cardinal; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public float Radius { get; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
public float GetValue(float x) |
||||
|
{ |
||||
|
float b = this.bspline; |
||||
|
float c = this.cardinal; |
||||
|
|
||||
|
if (x < 0F) |
||||
|
{ |
||||
|
x = -x; |
||||
|
} |
||||
|
|
||||
|
float temp = x * x; |
||||
|
if (x < 1F) |
||||
|
{ |
||||
|
x = ((12 - (9 * b) - (6 * c)) * (x * temp)) + ((-18 + (12 * b) + (6 * c)) * temp) + (6 - (2 * b)); |
||||
|
return x / 6F; |
||||
|
} |
||||
|
|
||||
|
if (x < 2F) |
||||
|
{ |
||||
|
x = ((-b - (6 * c)) * (x * temp)) + (((6 * b) + (30 * c)) * temp) + (((-12 * b) - (48 * c)) * x) + ((8 * b) + (24 * c)); |
||||
|
return x / 6F; |
||||
|
} |
||||
|
|
||||
|
return 0F; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
public void ApplyTransform<TPixel>(IResamplingTransformImageProcessor<TPixel> processor) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
=> processor.ApplyTransform(in this); |
||||
|
} |
||||
|
} |
||||
@ -1,25 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The Hermite filter is type of smoothed triangular interpolation Filter,
|
|
||||
/// This filter rounds off strong edges while preserving flat 'color levels' in the original image.
|
|
||||
/// <see href="http://www.imagemagick.org/Usage/filter/#cubic_bc"/>
|
|
||||
/// </summary>
|
|
||||
public class HermiteResampler : IResampler |
|
||||
{ |
|
||||
/// <inheritdoc/>
|
|
||||
public float Radius => 2; |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public float GetValue(float x) |
|
||||
{ |
|
||||
const float B = 0F; |
|
||||
const float C = 0F; |
|
||||
|
|
||||
return ImageMaths.GetBcValue(x, B, C); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,32 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The function implements the Lanczos kernel algorithm as described on
|
|
||||
/// <see href="https://en.wikipedia.org/wiki/Lanczos_resampling#Algorithm">Wikipedia</see>
|
|
||||
/// with a radius of 2 pixels.
|
|
||||
/// </summary>
|
|
||||
public class Lanczos2Resampler : IResampler |
|
||||
{ |
|
||||
/// <inheritdoc/>
|
|
||||
public float Radius => 2; |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public float GetValue(float x) |
|
||||
{ |
|
||||
if (x < 0F) |
|
||||
{ |
|
||||
x = -x; |
|
||||
} |
|
||||
|
|
||||
if (x < 2F) |
|
||||
{ |
|
||||
return ImageMaths.SinC(x) * ImageMaths.SinC(x / 2F); |
|
||||
} |
|
||||
|
|
||||
return 0F; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,32 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The function implements the Lanczos kernel algorithm as described on
|
|
||||
/// <see href="https://en.wikipedia.org/wiki/Lanczos_resampling#Algorithm">Wikipedia</see>
|
|
||||
/// with a radius of 3 pixels.
|
|
||||
/// </summary>
|
|
||||
public class Lanczos3Resampler : IResampler |
|
||||
{ |
|
||||
/// <inheritdoc/>
|
|
||||
public float Radius => 3; |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public float GetValue(float x) |
|
||||
{ |
|
||||
if (x < 0F) |
|
||||
{ |
|
||||
x = -x; |
|
||||
} |
|
||||
|
|
||||
if (x < 3F) |
|
||||
{ |
|
||||
return ImageMaths.SinC(x) * ImageMaths.SinC(x / 3F); |
|
||||
} |
|
||||
|
|
||||
return 0F; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,32 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The function implements the Lanczos kernel algorithm as described on
|
|
||||
/// <see href="https://en.wikipedia.org/wiki/Lanczos_resampling#Algorithm">Wikipedia</see>
|
|
||||
/// with a radius of 5 pixels.
|
|
||||
/// </summary>
|
|
||||
public class Lanczos5Resampler : IResampler |
|
||||
{ |
|
||||
/// <inheritdoc/>
|
|
||||
public float Radius => 5; |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public float GetValue(float x) |
|
||||
{ |
|
||||
if (x < 0F) |
|
||||
{ |
|
||||
x = -x; |
|
||||
} |
|
||||
|
|
||||
if (x < 5F) |
|
||||
{ |
|
||||
return ImageMaths.SinC(x) * ImageMaths.SinC(x / 5F); |
|
||||
} |
|
||||
|
|
||||
return 0F; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,32 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The function implements the Lanczos kernel algorithm as described on
|
|
||||
/// <see href="https://en.wikipedia.org/wiki/Lanczos_resampling#Algorithm">Wikipedia</see>
|
|
||||
/// with a radius of 8 pixels.
|
|
||||
/// </summary>
|
|
||||
public class Lanczos8Resampler : IResampler |
|
||||
{ |
|
||||
/// <inheritdoc/>
|
|
||||
public float Radius => 8; |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public float GetValue(float x) |
|
||||
{ |
|
||||
if (x < 0F) |
|
||||
{ |
|
||||
x = -x; |
|
||||
} |
|
||||
|
|
||||
if (x < 8F) |
|
||||
{ |
|
||||
return ImageMaths.SinC(x) * ImageMaths.SinC(x / 8F); |
|
||||
} |
|
||||
|
|
||||
return 0F; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,68 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The function implements the Lanczos kernel algorithm as described on
|
||||
|
/// <see href="https://en.wikipedia.org/wiki/Lanczos_resampling#Algorithm">Wikipedia</see>.
|
||||
|
/// </summary>
|
||||
|
public readonly struct LanczosResampler : IResampler |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements the Lanczos kernel algorithm with a radius of 2.
|
||||
|
/// </summary>
|
||||
|
public static LanczosResampler Lanczos2 = new LanczosResampler(2); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Implements the Lanczos kernel algorithm with a radius of 3.
|
||||
|
/// </summary>
|
||||
|
public static LanczosResampler Lanczos3 = new LanczosResampler(3); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Implements the Lanczos kernel algorithm with a radius of 5.
|
||||
|
/// </summary>
|
||||
|
public static LanczosResampler Lanczos5 = new LanczosResampler(5); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Implements the Lanczos kernel algorithm with a radius of 8.
|
||||
|
/// </summary>
|
||||
|
public static LanczosResampler Lanczos8 = new LanczosResampler(8); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="LanczosResampler"/> struct.
|
||||
|
/// </summary>
|
||||
|
/// <param name="radius">The sampling radius.</param>
|
||||
|
public LanczosResampler(float radius) => this.Radius = radius; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public float Radius { get; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
public float GetValue(float x) |
||||
|
{ |
||||
|
if (x < 0F) |
||||
|
{ |
||||
|
x = -x; |
||||
|
} |
||||
|
|
||||
|
float radius = this.Radius; |
||||
|
if (x < radius) |
||||
|
{ |
||||
|
return ImageMaths.SinC(x) * ImageMaths.SinC(x / radius); |
||||
|
} |
||||
|
|
||||
|
return 0F; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
public void ApplyTransform<TPixel>(IResamplingTransformImageProcessor<TPixel> processor) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
=> processor.ApplyTransform(in this); |
||||
|
} |
||||
|
} |
||||
@ -1,24 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The function implements the mitchell algorithm as described on
|
|
||||
/// <see href="https://de.wikipedia.org/wiki/Mitchell-Netravali-Filter">Wikipedia</see>
|
|
||||
/// </summary>
|
|
||||
public class MitchellNetravaliResampler : IResampler |
|
||||
{ |
|
||||
/// <inheritdoc/>
|
|
||||
public float Radius => 2; |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public float GetValue(float x) |
|
||||
{ |
|
||||
const float B = 0.3333333F; |
|
||||
const float C = 0.3333333F; |
|
||||
|
|
||||
return ImageMaths.GetBcValue(x, B, C); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,21 +1,28 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// The function implements the nearest neighbor algorithm. This uses an unscaled filter
|
/// The function implements the nearest neighbor algorithm. This uses an unscaled filter
|
||||
/// which will select the closest pixel to the new pixels position.
|
/// which will select the closest pixel to the new pixels position.
|
||||
/// </summary>
|
/// </summary>
|
||||
public class NearestNeighborResampler : IResampler |
public readonly struct NearestNeighborResampler : IResampler |
||||
{ |
{ |
||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||
public float Radius => 1; |
public float Radius => 1; |
||||
|
|
||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||
public float GetValue(float x) |
[MethodImpl(InliningOptions.ShortMethod)] |
||||
{ |
public float GetValue(float x) => x; |
||||
return x; |
|
||||
} |
/// <inheritdoc/>
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
public void ApplyTransform<TPixel>(IResamplingTransformImageProcessor<TPixel> processor) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
=> processor.ApplyTransform(in this); |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -1,24 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The function implements the Robidoux algorithm.
|
|
||||
/// <see href="http://www.imagemagick.org/Usage/filter/#robidoux"/>
|
|
||||
/// </summary>
|
|
||||
public class RobidouxResampler : IResampler |
|
||||
{ |
|
||||
/// <inheritdoc/>
|
|
||||
public float Radius => 2; |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public float GetValue(float x) |
|
||||
{ |
|
||||
const float B = 0.37821575509399867F; |
|
||||
const float C = 0.31089212245300067F; |
|
||||
|
|
||||
return ImageMaths.GetBcValue(x, B, C); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,24 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The function implements the Robidoux Sharp algorithm.
|
|
||||
/// <see href="http://www.imagemagick.org/Usage/filter/#robidoux"/>
|
|
||||
/// </summary>
|
|
||||
public class RobidouxSharpResampler : IResampler |
|
||||
{ |
|
||||
/// <inheritdoc/>
|
|
||||
public float Radius => 2; |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public float GetValue(float x) |
|
||||
{ |
|
||||
const float B = 0.2620145123990142F; |
|
||||
const float C = 0.3689927438004929F; |
|
||||
|
|
||||
return ImageMaths.GetBcValue(x, B, C); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,24 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The function implements the spline algorithm.
|
|
||||
/// <see href="http://www.imagemagick.org/Usage/filter/#cubic_bc"/>
|
|
||||
/// </summary>
|
|
||||
public class SplineResampler : IResampler |
|
||||
{ |
|
||||
/// <inheritdoc/>
|
|
||||
public float Radius => 2; |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public float GetValue(float x) |
|
||||
{ |
|
||||
const float B = 1F; |
|
||||
const float C = 0F; |
|
||||
|
|
||||
return ImageMaths.GetBcValue(x, B, C); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,160 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Numerics; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using SixLabors.ImageSharp.Memory; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Contains the methods required to calculate transform kernel convolution.
|
|
||||
/// </summary>
|
|
||||
internal class TransformKernelMap : IDisposable |
|
||||
{ |
|
||||
private readonly Buffer2D<float> yBuffer; |
|
||||
private readonly Buffer2D<float> xBuffer; |
|
||||
private readonly Vector2 extents; |
|
||||
private Vector4 maxSourceExtents; |
|
||||
private readonly IResampler sampler; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="TransformKernelMap"/> class.
|
|
||||
/// </summary>
|
|
||||
/// <param name="configuration">The configuration.</param>
|
|
||||
/// <param name="source">The source size.</param>
|
|
||||
/// <param name="destination">The destination size.</param>
|
|
||||
/// <param name="sampler">The sampler.</param>
|
|
||||
public TransformKernelMap(Configuration configuration, Size source, Size destination, IResampler sampler) |
|
||||
{ |
|
||||
this.sampler = sampler; |
|
||||
float yRadius = this.GetSamplingRadius(source.Height, destination.Height); |
|
||||
float xRadius = this.GetSamplingRadius(source.Width, destination.Width); |
|
||||
|
|
||||
this.extents = new Vector2(xRadius, yRadius); |
|
||||
int xLength = (int)MathF.Ceiling((this.extents.X * 2) + 2); |
|
||||
int yLength = (int)MathF.Ceiling((this.extents.Y * 2) + 2); |
|
||||
|
|
||||
// We use 2D buffers so that we can access the weight spans per row in parallel.
|
|
||||
this.yBuffer = configuration.MemoryAllocator.Allocate2D<float>(yLength, destination.Height); |
|
||||
this.xBuffer = configuration.MemoryAllocator.Allocate2D<float>(xLength, destination.Height); |
|
||||
|
|
||||
int maxX = source.Width - 1; |
|
||||
int maxY = source.Height - 1; |
|
||||
this.maxSourceExtents = new Vector4(maxX, maxY, maxX, maxY); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets a reference to the first item of the y window.
|
|
||||
/// </summary>
|
|
||||
/// <returns>The reference to the first item of the window.</returns>
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
|
||||
public ref float GetYStartReference(int y) |
|
||||
=> ref MemoryMarshal.GetReference(this.yBuffer.GetRowSpan(y)); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets a reference to the first item of the x window.
|
|
||||
/// </summary>
|
|
||||
/// <returns>The reference to the first item of the window.</returns>
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
|
||||
public ref float GetXStartReference(int y) |
|
||||
=> ref MemoryMarshal.GetReference(this.xBuffer.GetRowSpan(y)); |
|
||||
|
|
||||
public void Convolve<TPixel>( |
|
||||
Vector2 transformedPoint, |
|
||||
int column, |
|
||||
ref float ySpanRef, |
|
||||
ref float xSpanRef, |
|
||||
Buffer2D<TPixel> sourcePixels, |
|
||||
Span<Vector4> targetRow) |
|
||||
where TPixel : unmanaged, IPixel<TPixel> |
|
||||
{ |
|
||||
// Clamp sampling pixel radial extents to the source image edges
|
|
||||
Vector2 minXY = transformedPoint - this.extents; |
|
||||
Vector2 maxXY = transformedPoint + this.extents; |
|
||||
|
|
||||
// left, top, right, bottom
|
|
||||
var extents = new Vector4( |
|
||||
MathF.Ceiling(minXY.X - .5F), |
|
||||
MathF.Ceiling(minXY.Y - .5F), |
|
||||
MathF.Floor(maxXY.X + .5F), |
|
||||
MathF.Floor(maxXY.Y + .5F)); |
|
||||
|
|
||||
extents = Vector4.Clamp(extents, Vector4.Zero, this.maxSourceExtents); |
|
||||
|
|
||||
int left = (int)extents.X; |
|
||||
int top = (int)extents.Y; |
|
||||
int right = (int)extents.Z; |
|
||||
int bottom = (int)extents.W; |
|
||||
|
|
||||
if (left == right || top == bottom) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
this.CalculateWeights(top, bottom, transformedPoint.Y, ref ySpanRef); |
|
||||
this.CalculateWeights(left, right, transformedPoint.X, ref xSpanRef); |
|
||||
|
|
||||
Vector4 sum = Vector4.Zero; |
|
||||
for (int kernelY = 0, y = top; y <= bottom; y++, kernelY++) |
|
||||
{ |
|
||||
float yWeight = Unsafe.Add(ref ySpanRef, kernelY); |
|
||||
|
|
||||
for (int kernelX = 0, x = left; x <= right; x++, kernelX++) |
|
||||
{ |
|
||||
float xWeight = Unsafe.Add(ref xSpanRef, kernelX); |
|
||||
|
|
||||
// Values are first premultiplied to prevent darkening of edge pixels.
|
|
||||
var current = sourcePixels[x, y].ToVector4(); |
|
||||
Vector4Utils.Premultiply(ref current); |
|
||||
sum += current * xWeight * yWeight; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Reverse the premultiplication
|
|
||||
Vector4Utils.UnPremultiply(ref sum); |
|
||||
targetRow[column] = sum; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Calculated the normalized weights for the given point.
|
|
||||
/// </summary>
|
|
||||
/// <param name="min">The minimum sampling offset</param>
|
|
||||
/// <param name="max">The maximum sampling offset</param>
|
|
||||
/// <param name="point">The transformed point dimension</param>
|
|
||||
/// <param name="weightsRef">The reference to the collection of weights</param>
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
|
||||
private void CalculateWeights(int min, int max, float point, ref float weightsRef) |
|
||||
{ |
|
||||
float sum = 0; |
|
||||
for (int x = 0, i = min; i <= max; i++, x++) |
|
||||
{ |
|
||||
float weight = this.sampler.GetValue(i - point); |
|
||||
sum += weight; |
|
||||
Unsafe.Add(ref weightsRef, x) = weight; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
|
||||
private float GetSamplingRadius(int sourceSize, int destinationSize) |
|
||||
{ |
|
||||
float scale = (float)sourceSize / destinationSize; |
|
||||
|
|
||||
if (scale < 1F) |
|
||||
{ |
|
||||
scale = 1F; |
|
||||
} |
|
||||
|
|
||||
return MathF.Ceiling(scale * this.sampler.Radius); |
|
||||
} |
|
||||
|
|
||||
public void Dispose() |
|
||||
{ |
|
||||
this.yBuffer?.Dispose(); |
|
||||
this.xBuffer?.Dispose(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1 +1 @@ |
|||||
Subproject commit f9b4bfe42cacb3eefab02ada92ac771a9b93c080 |
Subproject commit f8a76fd3a900b90c98df67ac896574383a4d09f3 |
||||
Loading…
Reference in new issue