mirror of https://github.com/SixLabors/ImageSharp
Browse Source
* Attempt to use same weight generation algorithm as resize.
* tests pass
* Identical output
* Update LinearTransformKernelFactory{TResampler}.cs
* Use new low allocation iterator
* Migrate projective transforms.
* Optimizations
* Smaller kernel
* Fix sampling accuracy
* Finalize and update refs
* Revert unnecessary changes
* Remove enumerator
* Actually save output for debugging.
* Use custom test png encoder for reduced memory environments
* Convolution should use scaled vectors
* Update TestEnvironmentTests.cs
* Try using doubles
* Moar double precision
* Fix radius calculation
* Test if issue is SIMD related.
* Detect runtime to determine pipeline.
* Fix stack overflow
* fix condition
* Try simplified scalar run
* Simplify unpremultiply scalar
* Update Numerics.cs
* Fix runtime environment
* Update ImageSharp.csproj
* Duplicate the caller with scalar versions
* Update method name, exclude from coverage.
* Don't save output during coverage tests for perf.
* Update src/ImageSharp/Common/Helpers/RuntimeEnvironment.cs
Co-authored-by: Anton Firszov <antonfir@gmail.com>
Co-authored-by: Anton Firszov <antonfir@gmail.com>
pull/1600/head
committed by
GitHub
32 changed files with 604 additions and 351 deletions
@ -0,0 +1,32 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp |
|||
{ |
|||
/// <summary>
|
|||
/// Provides information about the .NET runtime installation.
|
|||
/// Many methods defer to <see cref="RuntimeInformation"/> when available.
|
|||
/// </summary>
|
|||
internal static class RuntimeEnvironment |
|||
{ |
|||
private static readonly Lazy<bool> IsNetCoreLazy = new Lazy<bool>(() => FrameworkDescription.StartsWith(".NET Core", StringComparison.OrdinalIgnoreCase)); |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the .NET installation is .NET Core 3.1 or lower.
|
|||
/// </summary>
|
|||
public static bool IsNetCore => IsNetCoreLazy.Value; |
|||
|
|||
/// <summary>
|
|||
/// Gets the name of the .NET installation on which an app is running.
|
|||
/// </summary>
|
|||
public static string FrameworkDescription => RuntimeInformation.FrameworkDescription; |
|||
|
|||
/// <summary>
|
|||
/// Indicates whether the current application is running on the specified platform.
|
|||
/// </summary>
|
|||
public static bool IsOSPlatform(OSPlatform osPlatform) => RuntimeInformation.IsOSPlatform(osPlatform); |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|||
{ |
|||
/// <summary>
|
|||
/// Utility methods for linear transforms.
|
|||
/// </summary>
|
|||
internal static class LinearTransformUtility |
|||
{ |
|||
/// <summary>
|
|||
/// Returns the sampling radius for the given sampler and dimensions.
|
|||
/// </summary>
|
|||
/// <typeparam name="TResampler">The type of resampler.</typeparam>
|
|||
/// <param name="sampler">The resampler sampler.</param>
|
|||
/// <param name="sourceSize">The source size.</param>
|
|||
/// <param name="destinationSize">The destination size.</param>
|
|||
/// <returns>The <see cref="float"/>.</returns>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static float GetSamplingRadius<TResampler>(in TResampler sampler, int sourceSize, int destinationSize) |
|||
where TResampler : struct, IResampler |
|||
{ |
|||
float scale = (float)sourceSize / destinationSize; |
|||
|
|||
if (scale < 1F) |
|||
{ |
|||
scale = 1F; |
|||
} |
|||
|
|||
return MathF.Ceiling(sampler.Radius * scale); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the start position (inclusive) for a sampling range given
|
|||
/// the radius, center position and max constraint.
|
|||
/// </summary>
|
|||
/// <param name="radius">The radius.</param>
|
|||
/// <param name="center">The center position.</param>
|
|||
/// <param name="max">The max allowed amouunt.</param>
|
|||
/// <returns>The <see cref="int"/>.</returns>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static int GetRangeStart(float radius, float center, int max) |
|||
=> Numerics.Clamp((int)MathF.Ceiling(center - radius), 0, max); |
|||
|
|||
/// <summary>
|
|||
/// Gets the end position (inclusive) for a sampling range given
|
|||
/// the radius, center position and max constraint.
|
|||
/// </summary>
|
|||
/// <param name="radius">The radius.</param>
|
|||
/// <param name="center">The center position.</param>
|
|||
/// <param name="max">The max allowed amouunt.</param>
|
|||
/// <returns>The <see cref="int"/>.</returns>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static int GetRangeEnd(float radius, float center, int max) |
|||
=> Numerics.Clamp((int)MathF.Floor(center + radius), 0, max); |
|||
} |
|||
} |
|||
@ -1,104 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// 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 LinearTransformUtils |
|||
{ |
|||
[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 : unmanaged, 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 = Numerics.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(); |
|||
Numerics.Premultiply(ref current); |
|||
sum += current * xWeight * yWeight; |
|||
} |
|||
} |
|||
|
|||
// Reverse the premultiplication
|
|||
Numerics.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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Runtime.InteropServices; |
|||
using Xunit; |
|||
|
|||
#pragma warning disable IDE0022 // Use expression body for methods
|
|||
namespace SixLabors.ImageSharp.Tests.Helpers |
|||
{ |
|||
public class RuntimeEnvironmentTests |
|||
{ |
|||
[Fact] |
|||
public void CanDetectNetCore() |
|||
{ |
|||
#if NET5_0_OR_GREATER
|
|||
Assert.False(RuntimeEnvironment.IsNetCore); |
|||
#elif NETCOREAPP
|
|||
Assert.True(RuntimeEnvironment.IsNetCore); |
|||
#else
|
|||
Assert.False(RuntimeEnvironment.IsNetCore); |
|||
#endif
|
|||
} |
|||
|
|||
[Fact] |
|||
public void CanDetectOSPlatform() |
|||
{ |
|||
if (TestEnvironment.IsLinux) |
|||
{ |
|||
Assert.True(RuntimeEnvironment.IsOSPlatform(OSPlatform.Linux)); |
|||
} |
|||
else if (TestEnvironment.IsOSX) |
|||
{ |
|||
Assert.True(RuntimeEnvironment.IsOSPlatform(OSPlatform.OSX)); |
|||
} |
|||
else if (TestEnvironment.IsWindows) |
|||
{ |
|||
Assert.True(RuntimeEnvironment.IsOSPlatform(OSPlatform.Windows)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using SixLabors.ImageSharp.Formats; |
|||
using SixLabors.ImageSharp.Formats.Png; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing.Processors.Quantization; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs |
|||
{ |
|||
/// <summary>
|
|||
/// A Png encoder that uses the ImageSharp core encoder but the default configuration.
|
|||
/// This allows encoding under environments with restricted memory.
|
|||
/// </summary>
|
|||
public sealed class ImageSharpPngEncoderWithDefaultConfiguration : IImageEncoder, IPngEncoderOptions |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public PngBitDepth? BitDepth { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public PngColorType? ColorType { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public PngFilterMethod? FilterMethod { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public PngCompressionLevel CompressionLevel { get; set; } = PngCompressionLevel.DefaultCompression; |
|||
|
|||
/// <inheritdoc/>
|
|||
public int TextCompressionThreshold { get; set; } = 1024; |
|||
|
|||
/// <inheritdoc/>
|
|||
public float? Gamma { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public IQuantizer Quantizer { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public byte Threshold { get; set; } = byte.MaxValue; |
|||
|
|||
/// <inheritdoc/>
|
|||
public PngInterlaceMode? InterlaceMethod { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public PngChunkFilter? ChunkFilter { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool IgnoreMetadata { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public PngTransparentColorMode TransparentColorMode { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
|
|||
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
|
|||
public void Encode<TPixel>(Image<TPixel> image, Stream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
Configuration configuration = Configuration.Default; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
|
|||
using var encoder = new PngEncoderCore(allocator, configuration, new PngEncoderOptions(this)); |
|||
encoder.Encode(image, stream); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
|
|||
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
|
|||
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
|
|||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
|||
public async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
Configuration configuration = Configuration.Default; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
|
|||
// The introduction of a local variable that refers to an object the implements
|
|||
// IDisposable means you must use async/await, where the compiler generates the
|
|||
// state machine and a continuation.
|
|||
using var encoder = new PngEncoderCore(allocator, configuration, new PngEncoderOptions(this)); |
|||
await encoder.EncodeAsync(image, stream, cancellationToken).ConfigureAwait(false); |
|||
} |
|||
} |
|||
} |
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:57376aa4446fc2d034d2a0cb627163ac416d1b6768b063b2c11ccf8517443bda |
|||
size 10135 |
|||
oid sha256:2ef489dc0837b382ad7c7ead6b7c7042dfbfba39902d4cc81b5f3805d5b03967 |
|||
size 9175 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:9c83b59471a50df9e1d7b8f0e35c50aa417cd3be1730d6369f47f5cc99b87cef |
|||
size 6405 |
|||
oid sha256:99d6c1d6b092a2feba2aebe2e09c521c3cc9682f3d748927cdc3cbaa38448b28 |
|||
size 710 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:f9f149093c5d9d487f3e16cb59e6807ea6ce581b5c307e57aeb4edaf921de9ca |
|||
size 15138 |
|||
oid sha256:04a3b84c668758b586f4d998f080ef96b09f726b0298b28f5dd3d739b0e90744 |
|||
size 13138 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:f9f149093c5d9d487f3e16cb59e6807ea6ce581b5c307e57aeb4edaf921de9ca |
|||
size 15138 |
|||
oid sha256:04a3b84c668758b586f4d998f080ef96b09f726b0298b28f5dd3d739b0e90744 |
|||
size 13138 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:0c7467702a784807a3a5813a75d5f643655ed5ad427e978d6ee079da67b05961 |
|||
size 15363 |
|||
oid sha256:6ea7ca66c31474c0bb9673a0d85c1c7465e387ebabf4e2d1e8f9daebfc7c8f34 |
|||
size 13956 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:a836c63c0912f69683bc9c8f59687b11e6b84f257816a01ff979ad0b6f4ab656 |
|||
size 19059 |
|||
oid sha256:6dd98ac441f3c20ea999f058c7b21601d5981d46e9b77709c25f2930a64edb93 |
|||
size 17148 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:05de30dc282a0b8a096a476f689a1d2b6bb298098692a2f665c59c3d14902aa6 |
|||
size 20426 |
|||
oid sha256:d5c4772d9b9dc57c4b5d47450ec9d02d96e40656cf2015f171b5425945f8a598 |
|||
size 18726 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:8892179d8edf96583900048bdd895eff24e57be0105e91efafe1de971414db0e |
|||
size 22457 |
|||
oid sha256:bb025c4470cec1b0d6544924e46b84cbdb90d75da5d0f879f2c7d7ec9875dee2 |
|||
size 20574 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:2f351ec6ae6df56537e383494984c2fbcf35a66c44a6ce53d6fd8d6d74a330f3 |
|||
size 15342 |
|||
oid sha256:c4abaa06827cb779026f8fbb655692bdd8adab37ae5b00c3ae18ebea456eb8d9 |
|||
size 13459 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:b0fb38dbdded32a1518d62ac79f4aa88133aaddad947f23c1066dc33d6938e0b |
|||
size 15372 |
|||
oid sha256:3435ade8f7988779280820342e16881b049f717735d2218ac5a971a1bd807db1 |
|||
size 13448 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:0be42a5fd4ff10680af74a99ed1e0561ae38181f4efe4641bd63891222dcdf3c |
|||
size 15283 |
|||
oid sha256:f0098aa45e820544dd16a58396fa70860886b7d79900916ed97376a8328a5ff2 |
|||
size 13367 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:1eabaf35e0dda3eccd5e8866ddd65e0c45557c8d5cc29423a99e2f377ee1bfa9 |
|||
size 16271 |
|||
oid sha256:7729495277b80a42e24dd8f40cdd8a280443575710fb4e199e4871c28b558271 |
|||
size 14253 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:5ab9bea8f45e7d29d7a8c5e3d0182c0f3f3aa7014aa358883dee53db6dfeb3f7 |
|||
size 14076 |
|||
oid sha256:a2304c234b93bdabaa018263dec70e62090ad1bbb7005ef62643b88600a863fb |
|||
size 12157 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:ea452bc46c508f6990870a34d908224a58aa350c4ccebabd4fa6ba138e8034a0 |
|||
size 18383 |
|||
oid sha256:54b0da9646b7f4cf83df784d69dfbec48e0bdc1788d70a9872817543f72f57c1 |
|||
size 16829 |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:2c83df7a2f70aec8f150799055ce42db09568b47b95216c91a79233ce69381d5 |
|||
size 191563 |
|||
oid sha256:233d8c6c9e101dddf5d210d47c7a20807f8f956738289068ea03b774258ef8c6 |
|||
size 182754 |
|||
|
|||
Loading…
Reference in new issue