diff --git a/.gitattributes b/.gitattributes index 70ced6903..355b64dce 100644 --- a/.gitattributes +++ b/.gitattributes @@ -87,7 +87,6 @@ *.eot binary *.exe binary *.otf binary -*.pbm binary *.pdf binary *.ppt binary *.pptx binary @@ -95,7 +94,6 @@ *.snk binary *.ttc binary *.ttf binary -*.wbmp binary *.woff binary *.woff2 binary *.xls binary @@ -126,3 +124,9 @@ *.dds filter=lfs diff=lfs merge=lfs -text *.ktx filter=lfs diff=lfs merge=lfs -text *.ktx2 filter=lfs diff=lfs merge=lfs -text +*.pam filter=lfs diff=lfs merge=lfs -text +*.pbm filter=lfs diff=lfs merge=lfs -text +*.pgm filter=lfs diff=lfs merge=lfs -text +*.ppm filter=lfs diff=lfs merge=lfs -text +*.pnm filter=lfs diff=lfs merge=lfs -text +*.wbmp filter=lfs diff=lfs merge=lfs -text diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 1326c72e8..62a8bf2b4 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,5 @@ blank_issues_enabled: false contact_links: - - name: Ask a Question - url: https://github.com/SixLabors/ImageSharp/discussions?discussions_q=category%3AQ%26A - about: Ask a question about this project. - name: Feature Request url: https://github.com/SixLabors/ImageSharp/discussions?discussions_q=category%3AIdeas about: Share ideas for new features for this project. diff --git a/.github/ISSUE_TEMPLATE/oss-bug-report.md b/.github/ISSUE_TEMPLATE/oss-bug-report.md index e0d37de53..9e9567a99 100644 --- a/.github/ISSUE_TEMPLATE/oss-bug-report.md +++ b/.github/ISSUE_TEMPLATE/oss-bug-report.md @@ -1,6 +1,6 @@ --- name: "OSS : Bug Report" -about: Create a report to help us improve the project. +about: Create a report to help us improve the project. OSS Issues are not guaranteed to be triaged. labels: needs triage --- diff --git a/Directory.Build.props b/Directory.Build.props index 3899ce939..26b3cc5af 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -13,6 +13,9 @@ $(MSBuildThisFileDirectory) + + + $(DefineConstants);DEBUG @@ -30,5 +33,4 @@ true - diff --git a/README.md b/README.md index ab16bbb76..fdf14b496 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ ImageSharp is a new, fully featured, fully managed, cross-platform, 2D graphics ImageSharp is designed from the ground up to be flexible and extensible. The library provides API endpoints for common image processing operations and the building blocks to allow for the development of additional operations. -Built against [.NET Standard 1.3](https://docs.microsoft.com/en-us/dotnet/standard/net-standard), ImageSharp can be used in device, cloud, and embedded/IoT scenarios. +Built against [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard), ImageSharp can be used in device, cloud, and embedded/IoT scenarios. ## License diff --git a/shared-infrastructure b/shared-infrastructure index a042aba17..59ce17f5a 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit a042aba176cdb840d800c6ed4cfe41a54fb7b1e3 +Subproject commit 59ce17f5a4e1f956811133f41add7638e74c2836 diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index 54a773be0..829c6155d 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -143,7 +143,7 @@ namespace SixLabors.ImageSharp.Advanced /// The source. /// The row. /// The - public static Memory GetPixelRowMemory(this ImageFrame source, int rowIndex) + public static Memory DangerousGetPixelRowMemory(this ImageFrame source, int rowIndex) where TPixel : unmanaged, IPixel { Guard.NotNull(source, nameof(source)); @@ -161,7 +161,7 @@ namespace SixLabors.ImageSharp.Advanced /// The source. /// The row. /// The - public static Memory GetPixelRowMemory(this Image source, int rowIndex) + public static Memory DangerousGetPixelRowMemory(this Image source, int rowIndex) where TPixel : unmanaged, IPixel { Guard.NotNull(source, nameof(source)); diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 3961cc6c5..82a146dc7 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -10,6 +10,7 @@ using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tiff; @@ -74,6 +75,7 @@ namespace SixLabors.ImageSharp.Advanced Seed(); Seed(); + Seed(); Seed(); Seed(); Seed(); @@ -148,6 +150,7 @@ namespace SixLabors.ImageSharp.Advanced Image img = default; img.CloneAs(default); img.CloneAs(default); + img.CloneAs(default); img.CloneAs(default); img.CloneAs(default); img.CloneAs(default); @@ -200,6 +203,7 @@ namespace SixLabors.ImageSharp.Advanced default(BmpEncoderCore).Encode(default, default, default); default(GifEncoderCore).Encode(default, default, default); default(JpegEncoderCore).Encode(default, default, default); + default(PbmEncoderCore).Encode(default, default, default); default(PngEncoderCore).Encode(default, default, default); default(TgaEncoderCore).Encode(default, default, default); default(TiffEncoderCore).Encode(default, default, default); @@ -217,6 +221,7 @@ namespace SixLabors.ImageSharp.Advanced default(BmpDecoderCore).Decode(default, default, default); default(GifDecoderCore).Decode(default, default, default); default(JpegDecoderCore).Decode(default, default, default); + default(PbmDecoderCore).Decode(default, default, default); default(PngDecoderCore).Decode(default, default, default); default(TgaDecoderCore).Decode(default, default, default); default(TiffDecoderCore).Decode(default, default, default); @@ -234,6 +239,7 @@ namespace SixLabors.ImageSharp.Advanced AotCompileImageEncoder(); AotCompileImageEncoder(); AotCompileImageEncoder(); + AotCompileImageEncoder(); AotCompileImageEncoder(); AotCompileImageEncoder(); AotCompileImageEncoder(); @@ -251,6 +257,7 @@ namespace SixLabors.ImageSharp.Advanced AotCompileImageDecoder(); AotCompileImageDecoder(); AotCompileImageDecoder(); + AotCompileImageDecoder(); AotCompileImageDecoder(); AotCompileImageDecoder(); AotCompileImageDecoder(); @@ -529,7 +536,7 @@ namespace SixLabors.ImageSharp.Advanced private static void AotCompileMemoryManagers() where TPixel : unmanaged, IPixel { - AotCompileMemoryManager(); + AotCompileMemoryManager(); AotCompileMemoryManager(); } diff --git a/src/ImageSharp/Color/Color.Conversions.cs b/src/ImageSharp/Color/Color.Conversions.cs index bf7869e53..5c10bfaa0 100644 --- a/src/ImageSharp/Color/Color.Conversions.cs +++ b/src/ImageSharp/Color/Color.Conversions.cs @@ -89,6 +89,17 @@ namespace SixLabors.ImageSharp this.boxedHighPrecisionPixel = null; } + /// + /// Initializes a new instance of the struct. + /// + /// The containing the color information. + [MethodImpl(InliningOptions.ShortMethod)] + public Color(Abgr32 pixel) + { + this.data = new Rgba64(pixel); + this.boxedHighPrecisionPixel = null; + } + /// /// Initializes a new instance of the struct. /// @@ -177,6 +188,19 @@ namespace SixLabors.ImageSharp return value; } + [MethodImpl(InliningOptions.ShortMethod)] + internal Abgr32 ToAbgr32() + { + if (this.boxedHighPrecisionPixel is null) + { + return this.data.ToAbgr32(); + } + + Abgr32 value = default; + value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); + return value; + } + [MethodImpl(InliningOptions.ShortMethod)] internal Rgb24 ToRgb24() { diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs index 7c21d62dd..cd0583361 100644 --- a/src/ImageSharp/Color/Color.cs +++ b/src/ImageSharp/Color/Color.cs @@ -270,8 +270,15 @@ namespace SixLabors.ImageSharp return pixel; } + if (this.boxedHighPrecisionPixel is null) + { + pixel = default; + pixel.FromRgba64(this.data); + return pixel; + } + pixel = default; - pixel.FromRgba64(this.data); + pixel.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); return pixel; } diff --git a/src/ImageSharp/Common/Helpers/DebugGuard.cs b/src/ImageSharp/Common/Helpers/DebugGuard.cs index f56cb37a8..f438ca9e2 100644 --- a/src/ImageSharp/Common/Helpers/DebugGuard.cs +++ b/src/ImageSharp/Common/Helpers/DebugGuard.cs @@ -26,6 +26,20 @@ namespace SixLabors } } + /// + /// Verifies whether a condition (indicating disposed state) is met, throwing an ObjectDisposedException if it's true. + /// + /// Whether the object is disposed. + /// The name of the object. + [Conditional("DEBUG")] + public static void NotDisposed(bool isDisposed, string objectName) + { + if (isDisposed) + { + throw new ObjectDisposedException(objectName); + } + } + /// /// Verifies, that the target span is of same size than the 'other' span. /// diff --git a/src/ImageSharp/Common/Helpers/Guard.cs b/src/ImageSharp/Common/Helpers/Guard.cs index 0b5cc21cb..0f6efcb3c 100644 --- a/src/ImageSharp/Common/Helpers/Guard.cs +++ b/src/ImageSharp/Common/Helpers/Guard.cs @@ -2,9 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -#if NETSTANDARD1_3 -using System.Reflection; -#endif using System.Runtime.CompilerServices; using SixLabors.ImageSharp; @@ -22,11 +19,7 @@ namespace SixLabors [MethodImpl(InliningOptions.ShortMethod)] public static void MustBeValueType(TValue value, string parameterName) { - if (value.GetType() -#if NETSTANDARD1_3 - .GetTypeInfo() -#endif - .IsValueType) + if (value.GetType().IsValueType) { return; } diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index fa0af823d..513db0c44 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -907,5 +907,61 @@ namespace SixLabors.ImageSharp /// Divisor value. /// Ceiled division result. public static uint DivideCeil(uint value, uint divisor) => (value + divisor - 1) / divisor; + + /// + /// Rotates the specified value left by the specified number of bits. + /// + /// The value to rotate. + /// The number of bits to rotate with. + /// The rotated value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint RotateLeft(uint value, int offset) + { +#if SUPPORTS_BITOPERATIONS + return BitOperations.RotateLeft(value, offset); +#else + return RotateLeftSoftwareFallback(value, offset); +#endif + } + +#if !SUPPORTS_BITOPERATIONS + /// + /// Rotates the specified value left by the specified number of bits. + /// + /// The value to rotate. + /// The number of bits to rotate with. + /// The rotated value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint RotateLeftSoftwareFallback(uint value, int offset) + => (value << offset) | (value >> (32 - offset)); +#endif + + /// + /// Rotates the specified value right by the specified number of bits. + /// + /// The value to rotate. + /// The number of bits to rotate with. + /// The rotated value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint RotateRight(uint value, int offset) + { +#if SUPPORTS_BITOPERATIONS + return BitOperations.RotateRight(value, offset); +#else + return RotateRightSoftwareFallback(value, offset); +#endif + } + +#if !SUPPORTS_BITOPERATIONS + /// + /// Rotates the specified value right by the specified number of bits. + /// + /// The value to rotate. + /// The number of bits to rotate with. + /// The rotated value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint RotateRightSoftwareFallback(uint value, int offset) + => (value >> offset) | (value << (32 - offset)); +#endif } } diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs index 7687a5b95..049c61185 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs @@ -28,6 +28,10 @@ namespace SixLabors.ImageSharp /// /// The source span of bytes. /// The destination span of bytes. + /// + /// Implementation can assume that source.Length is less or equal than dest.Length. + /// Loops should iterate using source.Length. + /// void RunFallbackShuffle(ReadOnlySpan source, Span dest); } @@ -153,7 +157,7 @@ namespace SixLabors.ImageSharp // packed = [W Z Y X] // ROTR(8, packedArgb) = [Y Z W X] - Unsafe.Add(ref dBase, i) = (packed >> 8) | (packed << 24); + Unsafe.Add(ref dBase, i) = Numerics.RotateRight(packed, 8); } } } @@ -184,7 +188,40 @@ namespace SixLabors.ImageSharp // tmp1 + tmp3 = [W X Y Z] uint tmp1 = packed & 0xFF00FF00; uint tmp2 = packed & 0x00FF00FF; - uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); + uint tmp3 = Numerics.RotateLeft(tmp2, 16); + + Unsafe.Add(ref dBase, i) = tmp1 + tmp3; + } + } + } + + internal readonly struct XWZYShuffle4 : IShuffle4 + { + public byte Control + { + [MethodImpl(InliningOptions.ShortMethod)] + get => SimdUtils.Shuffle.MmShuffle(1, 2, 3, 0); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void RunFallbackShuffle(ReadOnlySpan source, Span dest) + { + ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + int n = source.Length / 4; + + for (int i = 0; i < n; i++) + { + uint packed = Unsafe.Add(ref sBase, i); + + // packed = [W Z Y X] + // tmp1 = [0 Z 0 X] + // tmp2 = [W 0 Y 0] + // tmp3=ROTL(16, tmp2) = [Y 0 W 0] + // tmp1 + tmp3 = [Y Z W X] + uint tmp1 = packed & 0x00FF00FF; + uint tmp2 = packed & 0xFF00FF00; + uint tmp3 = Numerics.RotateLeft(tmp2, 16); Unsafe.Add(ref dBase, i) = tmp1 + tmp3; } diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs b/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs index 07744566a..abf9e9fed 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs @@ -77,6 +77,7 @@ namespace SixLabors.ImageSharp TShuffle shuffle) where TShuffle : struct, IShuffle3 { + // Source length should be smaller than dest length, and divisible by 3. VerifyShuffle3SpanInput(source, dest); #if SUPPORTS_RUNTIME_INTRINSICS @@ -182,9 +183,9 @@ namespace SixLabors.ImageSharp where T : struct { DebugGuard.IsTrue( - source.Length == dest.Length, + source.Length <= dest.Length, nameof(source), - "Input spans must be of same length!"); + "Source should fit into dest!"); DebugGuard.IsTrue( source.Length % 3 == 0, diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.cs b/src/ImageSharp/Common/Helpers/SimdUtils.cs index 6d82cfad0..29068a82c 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.cs @@ -33,18 +33,6 @@ namespace SixLabors.ImageSharp public static bool HasVector4 { get; } = Vector.IsHardwareAccelerated && Vector.Count == 4; - public static bool HasAvx2 - { - get - { -#if SUPPORTS_RUNTIME_INTRINSICS - return Avx2.IsSupported; -#else - return false; -#endif - } - } - /// /// Transform all scalars in 'v' in a way that converting them to would have rounding semantics. /// diff --git a/src/ImageSharp/Common/Tuples/Vector4Pair.cs b/src/ImageSharp/Common/Tuples/Vector4Pair.cs deleted file mode 100644 index 6294a6177..000000000 --- a/src/ImageSharp/Common/Tuples/Vector4Pair.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Tuples -{ - /// - /// Its faster to process multiple Vector4-s together, so let's pair them! - /// On AVX2 this pair should be convertible to of ! - /// TODO: Investigate defining this as union with an Octet.OfSingle type. - /// - [StructLayout(LayoutKind.Sequential)] - internal struct Vector4Pair - { - public Vector4 A; - - public Vector4 B; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void MultiplyInplace(float value) - { - this.A *= value; - this.B *= value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AddInplace(Vector4 value) - { - this.A += value; - this.B += value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AddInplace(ref Vector4Pair other) - { - this.A += other.A; - this.B += other.B; - } - - /// . - /// Downscale method, specific to Jpeg color conversion. Works only if Vector{float}.Count == 4! /// TODO: Move it somewhere else. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void RoundAndDownscalePreVector8(float downscaleFactor) - { - ref Vector a = ref Unsafe.As>(ref this.A); - a = a.FastRound(); - - ref Vector b = ref Unsafe.As>(ref this.B); - b = b.FastRound(); - - // Downscale by 1/factor - var scale = new Vector4(1 / downscaleFactor); - this.A *= scale; - this.B *= scale; - } - - /// - /// AVX2-only Downscale method, specific to Jpeg color conversion. - /// TODO: Move it somewhere else. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void RoundAndDownscaleVector8(float downscaleFactor) - { - ref Vector self = ref Unsafe.As>(ref this); - Vector v = self; - v = v.FastRound(); - - // Downscale by 1/factor - v *= new Vector(1 / downscaleFactor); - self = v; - } - - public override string ToString() - { - return $"{nameof(Vector4Pair)}({this.A}, {this.B})"; - } - } -} diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index ea9524827..ca83b0764 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tiff; @@ -26,10 +27,11 @@ namespace SixLabors.ImageSharp /// /// A lazily initialized configuration default instance. /// - private static readonly Lazy Lazy = new Lazy(CreateDefaultInstance); + private static readonly Lazy Lazy = new(CreateDefaultInstance); private const int DefaultStreamProcessingBufferSize = 8096; private int streamProcessingBufferSize = DefaultStreamProcessingBufferSize; private int maxDegreeOfParallelism = Environment.ProcessorCount; + private MemoryAllocator memoryAllocator = MemoryAllocator.Default; /// /// Initializes a new instance of the class. @@ -95,6 +97,14 @@ namespace SixLabors.ImageSharp } } + /// + /// Gets or sets a value indicating whether to force image buffers to be contiguous whenever possible. + /// + /// + /// Contiguous allocations are not possible, if the image needs a buffer larger than . + /// + public bool PreferContiguousImageBuffers { get; set; } + /// /// Gets a set of properties for the Configuration. /// @@ -117,9 +127,31 @@ namespace SixLabors.ImageSharp public ImageFormatManager ImageFormatsManager { get; set; } = new ImageFormatManager(); /// - /// Gets or sets the that is currently in use. + /// Gets or sets the that is currently in use. + /// Defaults to . + /// + /// Allocators are expensive, so it is strongly recommended to use only one busy instance per process. + /// In case you need to customize it, you can ensure this by changing /// - public MemoryAllocator MemoryAllocator { get; set; } = ArrayPoolMemoryAllocator.CreateDefault(); + /// + /// It's possible to reduce allocator footprint by assigning a custom instance created with + /// , but note that since the default pooling + /// allocators are expensive, it is strictly recommended to use a single process-wide allocator. + /// You can ensure this by altering the allocator of , or by implementing custom application logic that + /// manages allocator lifetime. + /// + /// If an allocator has to be dropped for some reason, + /// shall be invoked after disposing all associated instances. + /// + public MemoryAllocator MemoryAllocator + { + get => this.memoryAllocator; + set + { + Guard.NotNull(value, nameof(this.MemoryAllocator)); + this.memoryAllocator = value; + } + } /// /// Gets the maximum header size of all the formats. @@ -165,7 +197,7 @@ namespace SixLabors.ImageSharp MaxDegreeOfParallelism = this.MaxDegreeOfParallelism, StreamProcessingBufferSize = this.StreamProcessingBufferSize, ImageFormatsManager = this.ImageFormatsManager, - MemoryAllocator = this.MemoryAllocator, + memoryAllocator = this.memoryAllocator, ImageOperationsProvider = this.ImageOperationsProvider, ReadOrigin = this.ReadOrigin, FileSystem = this.FileSystem, @@ -178,6 +210,7 @@ namespace SixLabors.ImageSharp /// /// /// . + /// . /// . /// . /// . @@ -188,6 +221,7 @@ namespace SixLabors.ImageSharp new JpegConfigurationModule(), new GifConfigurationModule(), new BmpConfigurationModule(), + new PbmConfigurationModule(), new TgaConfigurationModule(), new TiffConfigurationModule(), new WebpConfigurationModule()); diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 8919befcb..41adc1cff 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -306,7 +306,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp int newY = Invert(y, height, inverted); int rowStartIdx = y * width; Span bufferRow = bufferSpan.Slice(rowStartIdx, width); - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.DangerousGetRowSpan(newY); bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y]; if (rowHasUndefinedPixels) @@ -377,7 +377,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp for (int y = 0; y < height; y++) { int newY = Invert(y, height, inverted); - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.DangerousGetRowSpan(newY); bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y]; if (rowHasUndefinedPixels) { @@ -826,7 +826,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp int newY = Invert(y, height, inverted); this.stream.Read(rowSpan); int offset = 0; - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.DangerousGetRowSpan(newY); for (int x = 0; x < arrayWidth; x++) { @@ -878,7 +878,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { this.stream.Read(bufferSpan); int newY = Invert(y, height, inverted); - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.DangerousGetRowSpan(newY); int offset = 0; for (int x = 0; x < width; x++) @@ -933,7 +933,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { this.stream.Read(rowSpan); int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpan(newY); + Span pixelSpan = pixels.DangerousGetRowSpan(newY); PixelOperations.Instance.FromBgr24Bytes( this.Configuration, rowSpan, @@ -961,7 +961,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { this.stream.Read(rowSpan); int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpan(newY); + Span pixelSpan = pixels.DangerousGetRowSpan(newY); PixelOperations.Instance.FromBgra32Bytes( this.Configuration, rowSpan, @@ -1031,7 +1031,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.stream.Read(rowSpan); int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpan(newY); + Span pixelSpan = pixels.DangerousGetRowSpan(newY); PixelOperations.Instance.FromBgra32Bytes( this.Configuration, @@ -1054,7 +1054,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp width); int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpan(newY); + Span pixelSpan = pixels.DangerousGetRowSpan(newY); for (int x = 0; x < width; x++) { @@ -1109,7 +1109,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { this.stream.Read(bufferSpan); int newY = Invert(y, height, inverted); - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.DangerousGetRowSpan(newY); int offset = 0; for (int x = 0; x < width; x++) diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index c6ca5b09d..6384074df 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -274,7 +274,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.ToBgra32Bytes( this.configuration, pixelSpan, @@ -300,7 +300,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.ToBgr24Bytes( this.configuration, pixelSpan, @@ -326,7 +326,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.ToBgra5551Bytes( this.configuration, @@ -379,7 +379,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp for (int y = image.Height - 1; y >= 0; y--) { - ReadOnlySpan pixelSpan = quantized.GetPixelRowSpan(y); + ReadOnlySpan pixelSpan = quantized.DangerousGetRowSpan(y); stream.Write(pixelSpan); for (int i = 0; i < this.padding; i++) @@ -413,10 +413,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp } stream.Write(colorPalette); - + Buffer2D imageBuffer = image.PixelBuffer; for (int y = image.Height - 1; y >= 0; y--) { - ReadOnlySpan inputPixelRow = image.GetPixelRowSpan(y); + ReadOnlySpan inputPixelRow = imageBuffer.DangerousGetRowSpan(y); ReadOnlySpan outputPixelRow = MemoryMarshal.AsBytes(inputPixelRow); stream.Write(outputPixelRow); @@ -447,11 +447,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); - ReadOnlySpan pixelRowSpan = quantized.GetPixelRowSpan(0); + ReadOnlySpan pixelRowSpan = quantized.DangerousGetRowSpan(0); int rowPadding = pixelRowSpan.Length % 2 != 0 ? this.padding - 1 : this.padding; for (int y = image.Height - 1; y >= 0; y--) { - pixelRowSpan = quantized.GetPixelRowSpan(y); + pixelRowSpan = quantized.DangerousGetRowSpan(y); int endIdx = pixelRowSpan.Length % 2 == 0 ? pixelRowSpan.Length : pixelRowSpan.Length - 1; for (int i = 0; i < endIdx; i += 2) @@ -491,11 +491,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); - ReadOnlySpan quantizedPixelRow = quantized.GetPixelRowSpan(0); + ReadOnlySpan quantizedPixelRow = quantized.DangerousGetRowSpan(0); int rowPadding = quantizedPixelRow.Length % 8 != 0 ? this.padding - 1 : this.padding; for (int y = image.Height - 1; y >= 0; y--) { - quantizedPixelRow = quantized.GetPixelRowSpan(y); + quantizedPixelRow = quantized.DangerousGetRowSpan(y); int endIdx = quantizedPixelRow.Length % 8 == 0 ? quantizedPixelRow.Length : quantizedPixelRow.Length - 8; for (int i = 0; i < endIdx; i += 8) diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 482a76153..3e33a6e37 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -445,7 +445,7 @@ namespace SixLabors.ImageSharp.Formats.Gif for (int y = descriptorTop; y < descriptorBottom && y < imageHeight; y++) { - ref byte indicesRowRef = ref MemoryMarshal.GetReference(indices.GetRowSpan(y - descriptorTop)); + ref byte indicesRowRef = ref MemoryMarshal.GetReference(indices.DangerousGetRowSpan(y - descriptorTop)); // Check if this image is interlaced. int writeY; // the target y offset to write to @@ -481,7 +481,7 @@ namespace SixLabors.ImageSharp.Formats.Gif writeY = y; } - ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.GetPixelRowSpan(writeY)); + ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.PixelBuffer.DangerousGetRowSpan(writeY)); if (!transFlag) { diff --git a/src/ImageSharp/Formats/Gif/LzwDecoder.cs b/src/ImageSharp/Formats/Gif/LzwDecoder.cs index 9eaa55566..68227db53 100644 --- a/src/ImageSharp/Formats/Gif/LzwDecoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwDecoder.cs @@ -115,14 +115,14 @@ namespace SixLabors.ImageSharp.Formats.Gif int y = 0; int x = 0; int rowMax = width; - ref byte pixelsRowRef = ref MemoryMarshal.GetReference(pixels.GetRowSpan(y)); + ref byte pixelsRowRef = ref MemoryMarshal.GetReference(pixels.DangerousGetRowSpan(y)); while (xyz < length) { // Reset row reference. if (xyz == rowMax) { x = 0; - pixelsRowRef = ref MemoryMarshal.GetReference(pixels.GetRowSpan(++y)); + pixelsRowRef = ref MemoryMarshal.GetReference(pixels.DangerousGetRowSpan(++y)); rowMax = (y * width) + width; } diff --git a/src/ImageSharp/Formats/Gif/LzwEncoder.cs b/src/ImageSharp/Formats/Gif/LzwEncoder.cs index e9fb7ab00..c52e34f96 100644 --- a/src/ImageSharp/Formats/Gif/LzwEncoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwEncoder.cs @@ -275,7 +275,7 @@ namespace SixLabors.ImageSharp.Formats.Gif for (int y = 0; y < indexedPixels.Height; y++) { - ref byte rowSpanRef = ref MemoryMarshal.GetReference(indexedPixels.GetRowSpan(y)); + ref byte rowSpanRef = ref MemoryMarshal.GetReference(indexedPixels.DangerousGetRowSpan(y)); int offsetX = y == 0 ? 1 : 0; for (int x = offsetX; x < indexedPixels.Width; x++) diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.cs b/src/ImageSharp/Formats/ImageExtensions.Save.cs index c5237f2bc..a6a65aef6 100644 --- a/src/ImageSharp/Formats/ImageExtensions.Save.cs +++ b/src/ImageSharp/Formats/ImageExtensions.Save.cs @@ -10,6 +10,7 @@ using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Webp; @@ -331,6 +332,109 @@ namespace SixLabors.ImageSharp encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance), cancellationToken); + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + public static void SaveAsPbm(this Image source, string path) => SaveAsPbm(source, path, null); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsPbmAsync(this Image source, string path) => SaveAsPbmAsync(source, path, null); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsPbmAsync(this Image source, string path, CancellationToken cancellationToken) + => SaveAsPbmAsync(source, path, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + public static void SaveAsPbm(this Image source, string path, PbmEncoder encoder) => + source.Save( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance)); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsPbmAsync(this Image source, string path, PbmEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsPbm(this Image source, Stream stream) + => SaveAsPbm(source, stream, null); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsPbmAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) + => SaveAsPbmAsync(source, stream, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static void SaveAsPbm(this Image source, Stream stream, PbmEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance)); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsPbmAsync(this Image source, Stream stream, PbmEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance), + cancellationToken); + /// /// Saves the image to the given stream with the Png format. /// diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.tt b/src/ImageSharp/Formats/ImageExtensions.Save.tt index 874f3ab0d..c4a00b37c 100644 --- a/src/ImageSharp/Formats/ImageExtensions.Save.tt +++ b/src/ImageSharp/Formats/ImageExtensions.Save.tt @@ -15,6 +15,7 @@ using SixLabors.ImageSharp.Advanced; "Bmp", "Gif", "Jpeg", + "Pbm", "Png", "Tga", "Webp", diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs new file mode 100644 index 000000000..002d382dc --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + internal unsafe partial struct Block8x8 + { + [FieldOffset(0)] + public Vector128 V0; + [FieldOffset(16)] + public Vector128 V1; + [FieldOffset(32)] + public Vector128 V2; + [FieldOffset(48)] + public Vector128 V3; + [FieldOffset(64)] + public Vector128 V4; + [FieldOffset(80)] + public Vector128 V5; + [FieldOffset(96)] + public Vector128 V6; + [FieldOffset(112)] + public Vector128 V7; + + [FieldOffset(0)] + public Vector256 V01; + [FieldOffset(32)] + public Vector256 V23; + [FieldOffset(64)] + public Vector256 V45; + [FieldOffset(96)] + public Vector256 V67; + } +} +#endif diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs index 27bb2fc3c..4b03f9f7b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// // ReSharper disable once InconsistentNaming [StructLayout(LayoutKind.Explicit)] - internal unsafe struct Block8x8 : IEquatable + internal unsafe partial struct Block8x8 { /// /// A number of scalar coefficients in a @@ -36,34 +36,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components private fixed short data[Size]; #pragma warning restore IDE0051 -#if SUPPORTS_RUNTIME_INTRINSICS - [FieldOffset(0)] - public Vector128 V0; - [FieldOffset(16)] - public Vector128 V1; - [FieldOffset(32)] - public Vector128 V2; - [FieldOffset(48)] - public Vector128 V3; - [FieldOffset(64)] - public Vector128 V4; - [FieldOffset(80)] - public Vector128 V5; - [FieldOffset(96)] - public Vector128 V6; - [FieldOffset(112)] - public Vector128 V7; - - [FieldOffset(0)] - public Vector256 V01; - [FieldOffset(32)] - public Vector256 V23; - [FieldOffset(64)] - public Vector256 V45; - [FieldOffset(96)] - public Vector256 V67; -#endif - /// /// Gets or sets a value at the given index /// @@ -102,74 +74,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components set => this[(y * 8) + x] = value; } - public static bool operator ==(Block8x8 left, Block8x8 right) => left.Equals(right); - - public static bool operator !=(Block8x8 left, Block8x8 right) => !left.Equals(right); - - /// - /// Multiply all elements by a given - /// - public static Block8x8 operator *(Block8x8 block, int value) - { - Block8x8 result = block; - for (int i = 0; i < Size; i++) - { - int val = result[i]; - val *= value; - result[i] = (short)val; - } - - return result; - } - - /// - /// Divide all elements by a given - /// - public static Block8x8 operator /(Block8x8 block, int value) - { - Block8x8 result = block; - for (int i = 0; i < Size; i++) - { - int val = result[i]; - val /= value; - result[i] = (short)val; - } - - return result; - } - - /// - /// Add an to all elements - /// - public static Block8x8 operator +(Block8x8 block, int value) - { - Block8x8 result = block; - for (int i = 0; i < Size; i++) - { - int val = result[i]; - val += value; - result[i] = (short)val; - } - - return result; - } - - /// - /// Subtract an from all elements - /// - public static Block8x8 operator -(Block8x8 block, int value) - { - Block8x8 result = block; - for (int i = 0; i < Size; i++) - { - int val = result[i]; - val -= value; - result[i] = (short)val; - } - - return result; - } - public static Block8x8 Load(Span data) { Unsafe.SkipInit(out Block8x8 result); @@ -260,26 +164,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components return sb.ToString(); } - /// - public bool Equals(Block8x8 other) - { - for (int i = 0; i < Size; i++) - { - if (this[i] != other[i]) - { - return false; - } - } - - return true; - } - - /// - public override bool Equals(object obj) => obj is Block8x8 other && this.Equals(other); - - /// - public override int GetHashCode() => (this[0] * 31) + this[1]; - /// /// Returns index of the last non-zero element in given matrix. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index 02f5a1324..f25286447 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -98,58 +97,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components set => this[(y * 8) + x] = value; } - public static Block8x8F operator *(Block8x8F block, float value) - { - Block8x8F result = block; - for (int i = 0; i < Size; i++) - { - float val = result[i]; - val *= value; - result[i] = val; - } - - return result; - } - - public static Block8x8F operator /(Block8x8F block, float value) - { - Block8x8F result = block; - for (int i = 0; i < Size; i++) - { - float val = result[i]; - val /= value; - result[i] = val; - } - - return result; - } - - public static Block8x8F operator +(Block8x8F block, float value) - { - Block8x8F result = block; - for (int i = 0; i < Size; i++) - { - float val = result[i]; - val += value; - result[i] = val; - } - - return result; - } - - public static Block8x8F operator -(Block8x8F block, float value) - { - Block8x8F result = block; - for (int i = 0; i < Size; i++) - { - float val = result[i]; - val -= value; - result[i] = val; - } - - return result; - } - public static Block8x8F Load(Span data) { Block8x8F result = default; @@ -157,13 +104,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components return result; } - public static Block8x8F Load(Span data) - { - Block8x8F result = default; - result.LoadFrom(data); - return result; - } - /// /// Load raw 32bit floating point data from source. /// @@ -177,15 +117,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components Unsafe.CopyBlock(ref d, ref s, Size * sizeof(float)); } - /// - /// Load raw 32bit floating point data from source. - /// - /// Block pointer - /// Source - [MethodImpl(InliningOptions.ShortMethod)] - public static unsafe void LoadFrom(Block8x8F* blockPtr, Span source) - => blockPtr->LoadFrom(source); - /// /// Load raw 32bit floating point data from source /// @@ -202,44 +133,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - /// - /// Copy raw 32bit floating point data to dest, - /// - /// Destination - [MethodImpl(InliningOptions.ShortMethod)] - public void ScaledCopyTo(Span dest) - { - ref byte d = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - ref byte s = ref Unsafe.As(ref this); - - Unsafe.CopyBlock(ref d, ref s, Size * sizeof(float)); - } - - /// - /// Convert scalars to byte-s and copy to dest, - /// - /// Pointer to block - /// Destination - [MethodImpl(InliningOptions.ShortMethod)] - public static unsafe void ScaledCopyTo(Block8x8F* blockPtr, Span dest) - { - float* fPtr = (float*)blockPtr; - for (int i = 0; i < Size; i++) - { - dest[i] = (byte)*fPtr; - fPtr++; - } - } - - /// - /// Copy raw 32bit floating point data to dest. - /// - /// The block pointer. - /// The destination. - [MethodImpl(InliningOptions.ShortMethod)] - public static unsafe void ScaledCopyTo(Block8x8F* blockPtr, Span dest) - => blockPtr->ScaledCopyTo(dest); - /// /// Copy raw 32bit floating point data to dest /// @@ -253,22 +146,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - /// - /// Copy raw 32bit floating point data to dest - /// - /// Destination - public unsafe void ScaledCopyTo(Span dest) - { - fixed (Vector4* ptr = &this.V0L) - { - var fp = (float*)ptr; - for (int i = 0; i < Size; i++) - { - dest[i] = (int)fp[i]; - } - } - } - public float[] ToArray() { float[] result = new float[Size]; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Avx2JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Avx2JpegColorConverter.cs deleted file mode 100644 index 90ebce3b8..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Avx2JpegColorConverter.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverter - { - internal abstract class Avx2JpegColorConverter : VectorizedJpegColorConverter - { - protected Avx2JpegColorConverter(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision, 8) - { - } - - protected sealed override bool IsAvailable => SimdUtils.HasAvx2; - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.BasicJpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.BasicJpegColorConverter.cs deleted file mode 100644 index ed2e2cd76..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.BasicJpegColorConverter.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverter - { - internal abstract class BasicJpegColorConverter : JpegColorConverter - { - protected BasicJpegColorConverter(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision) - { - } - - protected override bool IsAvailable => true; - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs new file mode 100644 index 000000000..7366ee30a --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs @@ -0,0 +1,52 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +{ + internal abstract partial class JpegColorConverterBase + { + internal sealed class FromCmykAvx : JpegColorConverterAvx + { + public FromCmykAvx(int precision) + : base(JpegColorSpace.Cmyk, precision) + { + } + + public override void ConvertToRgbInplace(in ComponentValues values) + { + ref Vector256 c0Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector256 c1Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector256 c2Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + ref Vector256 c3Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); + + // Used for the color conversion + var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); + + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) + { + ref Vector256 c = ref Unsafe.Add(ref c0Base, i); + ref Vector256 m = ref Unsafe.Add(ref c1Base, i); + ref Vector256 y = ref Unsafe.Add(ref c2Base, i); + Vector256 k = Unsafe.Add(ref c3Base, i); + + k = Avx.Multiply(k, scale); + c = Avx.Multiply(c, k); + m = Avx.Multiply(m, k); + y = Avx.Multiply(y, k); + } + } + } + } +} +#endif diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx2.cs deleted file mode 100644 index 216c12735..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx2.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -#if SUPPORTS_RUNTIME_INTRINSICS -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -using static SixLabors.ImageSharp.SimdUtils; -#endif - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverter - { - internal sealed class FromCmykAvx2 : Avx2JpegColorConverter - { - public FromCmykAvx2(int precision) - : base(JpegColorSpace.Cmyk, precision) - { - } - - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) - { -#if SUPPORTS_RUNTIME_INTRINSICS - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - var scale = Vector256.Create(1 / this.MaximumValue); - - nint n = values.Component0.Length / 8; - for (nint i = 0; i < n; i++) - { - ref Vector256 c = ref Unsafe.Add(ref c0Base, i); - ref Vector256 m = ref Unsafe.Add(ref c1Base, i); - ref Vector256 y = ref Unsafe.Add(ref c2Base, i); - Vector256 k = Unsafe.Add(ref c3Base, i); - - k = Avx.Multiply(k, scale); - c = Avx.Multiply(Avx.Multiply(c, k), scale); - m = Avx.Multiply(Avx.Multiply(m, k), scale); - y = Avx.Multiply(Avx.Multiply(y, k), scale); - } -#endif - } - - protected override void ConvertCoreInplace(in ComponentValues values) => - FromCmykBasic.ConvertCoreInplace(values, this.MaximumValue); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykBasic.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs similarity index 67% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykBasic.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs index b0ad50301..68dfa9bfb 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykBasic.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs @@ -1,16 +1,15 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; -using System.Numerics; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { - internal abstract partial class JpegColorConverter + internal abstract partial class JpegColorConverterBase { - internal sealed class FromCmykBasic : BasicJpegColorConverter + internal sealed class FromCmykScalar : JpegColorConverterScalar { - public FromCmykBasic(int precision) + public FromCmykScalar(int precision) : base(JpegColorSpace.Cmyk, precision) { } @@ -25,17 +24,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters Span c2 = values.Component2; Span c3 = values.Component3; - float scale = 1 / maxValue; + float scale = 1 / (maxValue * maxValue); for (int i = 0; i < c0.Length; i++) { float c = c0[i]; float m = c1[i]; float y = c2[i]; - float k = c3[i] / maxValue; + float k = c3[i]; - c0[i] = c * k * scale; - c1[i] = m * k * scale; - c2[i] = y * k * scale; + k *= scale; + c0[i] = c * k; + c1[i] = m * k; + c2[i] = y * k; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs new file mode 100644 index 000000000..6b7ed169e --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +{ + internal abstract partial class JpegColorConverterBase + { + internal sealed class FromCmykVector : JpegColorConverterVector + { + public FromCmykVector(int precision) + : base(JpegColorSpace.Cmyk, precision) + { + } + + protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + { + ref Vector cBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector mBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector yBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + ref Vector kBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); + + var scale = new Vector(1 / (this.MaximumValue * this.MaximumValue)); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) + { + ref Vector c = ref Unsafe.Add(ref cBase, i); + ref Vector m = ref Unsafe.Add(ref mBase, i); + ref Vector y = ref Unsafe.Add(ref yBase, i); + Vector k = Unsafe.Add(ref kBase, i); + + k *= scale; + c *= k; + m *= k; + y *= k; + } + } + + protected override void ConvertCoreInplace(in ComponentValues values) => + FromCmykScalar.ConvertCoreInplace(values, this.MaximumValue); + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector8.cs deleted file mode 100644 index 0da4c9ec2..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector8.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Tuples; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverter - { - internal sealed class FromCmykVector8 : Vector8JpegColorConverter - { - public FromCmykVector8(int precision) - : base(JpegColorSpace.Cmyk, precision) - { - } - - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) - { - ref Vector cBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector mBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector yBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector kBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - var scale = new Vector(1 / this.MaximumValue); - - // Walking 8 elements at one step: - nint n = values.Component0.Length / 8; - for (nint i = 0; i < n; i++) - { - ref Vector c = ref Unsafe.Add(ref cBase, i); - ref Vector m = ref Unsafe.Add(ref mBase, i); - ref Vector y = ref Unsafe.Add(ref yBase, i); - Vector k = Unsafe.Add(ref kBase, i) * scale; - - c = (c * k) * scale; - m = (m * k) * scale; - y = (y * k) * scale; - } - } - - protected override void ConvertCoreInplace(in ComponentValues values) => - FromCmykBasic.ConvertCoreInplace(values, this.MaximumValue); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs similarity index 59% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx2.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs index eca6b6292..963543ad4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx2.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs @@ -1,47 +1,39 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Numerics; +#if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -#if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -using static SixLabors.ImageSharp.SimdUtils; -#endif namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { - internal abstract partial class JpegColorConverter + internal abstract partial class JpegColorConverterBase { - internal sealed class FromGrayscaleAvx2 : Avx2JpegColorConverter + internal sealed class FromGrayscaleAvx : JpegColorConverterAvx { - public FromGrayscaleAvx2(int precision) + public FromGrayscaleAvx(int precision) : base(JpegColorSpace.Grayscale, precision) { } - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + public override void ConvertToRgbInplace(in ComponentValues values) { -#if SUPPORTS_RUNTIME_INTRINSICS ref Vector256 c0Base = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); // Used for the color conversion var scale = Vector256.Create(1 / this.MaximumValue); - nint n = values.Component0.Length / 8; + nint n = values.Component0.Length / Vector256.Count; for (nint i = 0; i < n; i++) { ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); c0 = Avx.Multiply(c0, scale); } -#endif } - - protected override void ConvertCoreInplace(in ComponentValues values) => - FromGrayscaleBasic.ScaleValues(values.Component0, this.MaximumValue); } } } +#endif diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleBasic.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleBasic.cs deleted file mode 100644 index 76d57bf06..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleBasic.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverter - { - internal sealed class FromGrayscaleBasic : BasicJpegColorConverter - { - public FromGrayscaleBasic(int precision) - : base(JpegColorSpace.Grayscale, precision) - { - } - - public override void ConvertToRgbInplace(in ComponentValues values) => - ScaleValues(values.Component0, this.MaximumValue); - - internal static void ScaleValues(Span values, float maxValue) - { - Span vecValues = MemoryMarshal.Cast(values); - - var scaleVector = new Vector4(1 / maxValue); - - for (int i = 0; i < vecValues.Length; i++) - { - vecValues[i] *= scaleVector; - } - - values = values.Slice(vecValues.Length * 4); - if (!values.IsEmpty) - { - float scaleValue = 1f / maxValue; - values[0] *= scaleValue; - - if ((uint)values.Length > 1) - { - values[1] *= scaleValue; - - if ((uint)values.Length > 2) - { - values[2] *= scaleValue; - } - } - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs new file mode 100644 index 000000000..3f6a6caa4 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +{ + internal abstract partial class JpegColorConverterBase + { + internal sealed class FromGrayscaleScalar : JpegColorConverterScalar + { + public FromGrayscaleScalar(int precision) + : base(JpegColorSpace.Grayscale, precision) + { + } + + public override void ConvertToRgbInplace(in ComponentValues values) => + ConvertCoreInplace(values.Component0, this.MaximumValue); + + internal static void ConvertCoreInplace(Span values, float maxValue) + { + ref float valuesRef = ref MemoryMarshal.GetReference(values); + float scale = 1 / maxValue; + + for (nint i = 0; i < values.Length; i++) + { + Unsafe.Add(ref valuesRef, i) *= scale; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs new file mode 100644 index 000000000..c484aac28 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +{ + internal abstract partial class JpegColorConverterBase + { + internal sealed class FromGrayScaleVector : JpegColorConverterVector + { + public FromGrayScaleVector(int precision) + : base(JpegColorSpace.Grayscale, precision) + { + } + + protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + { + ref Vector cBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + + var scale = new Vector(1 / this.MaximumValue); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) + { + ref Vector c0 = ref Unsafe.Add(ref cBase, i); + c0 *= scale; + } + } + + protected override void ConvertCoreInplace(in ComponentValues values) => + FromGrayscaleScalar.ConvertCoreInplace(values.Component0, this.MaximumValue); + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs similarity index 53% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx2.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs index 557e4e417..f017716e3 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx2.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs @@ -1,40 +1,35 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Numerics; +#if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -#if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -using static SixLabors.ImageSharp.SimdUtils; -#endif namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { - internal abstract partial class JpegColorConverter + internal abstract partial class JpegColorConverterBase { - internal sealed class FromRgbAvx2 : Avx2JpegColorConverter + internal sealed class FromRgbAvx : JpegColorConverterAvx { - public FromRgbAvx2(int precision) + public FromRgbAvx(int precision) : base(JpegColorSpace.RGB, precision) { } - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + public override void ConvertToRgbInplace(in ComponentValues values) { -#if SUPPORTS_RUNTIME_INTRINSICS ref Vector256 rBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); ref Vector256 gBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); ref Vector256 bBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); // Used for the color conversion var scale = Vector256.Create(1 / this.MaximumValue); - nint n = values.Component0.Length / 8; + nint n = values.Component0.Length / Vector256.Count; for (nint i = 0; i < n; i++) { ref Vector256 r = ref Unsafe.Add(ref rBase, i); @@ -44,11 +39,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters g = Avx.Multiply(g, scale); b = Avx.Multiply(b, scale); } -#endif } - - protected override void ConvertCoreInplace(in ComponentValues values) => - FromRgbBasic.ConvertCoreInplace(values, this.MaximumValue); } } } +#endif diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs deleted file mode 100644 index 1425e7b58..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverter - { - internal sealed class FromRgbBasic : BasicJpegColorConverter - { - public FromRgbBasic(int precision) - : base(JpegColorSpace.RGB, precision) - { - } - - public override void ConvertToRgbInplace(in ComponentValues values) - { - ConvertCoreInplace(values, this.MaximumValue); - } - - internal static void ConvertCoreInplace(ComponentValues values, float maxValue) - { - FromGrayscaleBasic.ScaleValues(values.Component0, maxValue); - FromGrayscaleBasic.ScaleValues(values.Component1, maxValue); - FromGrayscaleBasic.ScaleValues(values.Component2, maxValue); - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs new file mode 100644 index 000000000..24c59206d --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +{ + internal abstract partial class JpegColorConverterBase + { + internal sealed class FromRgbScalar : JpegColorConverterScalar + { + public FromRgbScalar(int precision) + : base(JpegColorSpace.RGB, precision) + { + } + + public override void ConvertToRgbInplace(in ComponentValues values) => + ConvertCoreInplace(values, this.MaximumValue); + + internal static void ConvertCoreInplace(ComponentValues values, float maxValue) + { + FromGrayscaleScalar.ConvertCoreInplace(values.Component0, maxValue); + FromGrayscaleScalar.ConvertCoreInplace(values.Component1, maxValue); + FromGrayscaleScalar.ConvertCoreInplace(values.Component2, maxValue); + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs similarity index 76% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs index a00361d97..ff3a2bee1 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs @@ -1,19 +1,17 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Tuples; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { - internal abstract partial class JpegColorConverter + internal abstract partial class JpegColorConverterBase { - internal sealed class FromRgbVector8 : Vector8JpegColorConverter + internal sealed class FromRgbVector : JpegColorConverterVector { - public FromRgbVector8(int precision) + public FromRgbVector(int precision) : base(JpegColorSpace.RGB, precision) { } @@ -29,8 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var scale = new Vector(1 / this.MaximumValue); - // Walking 8 elements at one step: - nint n = values.Component0.Length / 8; + nint n = values.Component0.Length / Vector.Count; for (nint i = 0; i < n; i++) { ref Vector r = ref Unsafe.Add(ref rBase, i); @@ -43,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } protected override void ConvertCoreInplace(in ComponentValues values) => - FromRgbBasic.ConvertCoreInplace(values, this.MaximumValue); + FromRgbScalar.ConvertCoreInplace(values, this.MaximumValue); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs similarity index 70% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx2.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs index 5aae1faa2..892bcc79e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx2.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs @@ -1,31 +1,27 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Numerics; +#if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -#if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; using static SixLabors.ImageSharp.SimdUtils; -#endif // ReSharper disable ImpureMethodCallOnReadonlyValueField namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { - internal abstract partial class JpegColorConverter + internal abstract partial class JpegColorConverterBase { - internal sealed class FromYCbCrAvx2 : Avx2JpegColorConverter + internal sealed class FromYCbCrAvx : JpegColorConverterAvx { - public FromYCbCrAvx2(int precision) + public FromYCbCrAvx(int precision) : base(JpegColorSpace.YCbCr, precision) { } - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + public override void ConvertToRgbInplace(in ComponentValues values) { -#if SUPPORTS_RUNTIME_INTRINSICS ref Vector256 c0Base = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); ref Vector256 c1Base = @@ -36,18 +32,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters // Used for the color conversion var chromaOffset = Vector256.Create(-this.HalfValue); var scale = Vector256.Create(1 / this.MaximumValue); - var rCrMult = Vector256.Create(1.402F); - var gCbMult = Vector256.Create(-0.344136F); - var gCrMult = Vector256.Create(-0.714136F); - var bCbMult = Vector256.Create(1.772F); - - // Used for packing. - var va = Vector256.Create(1F); - ref byte control = ref MemoryMarshal.GetReference(HwIntrinsics.PermuteMaskEvenOdd8x32); - Vector256 vcontrol = Unsafe.As>(ref control); + var rCrMult = Vector256.Create(FromYCbCrScalar.RCrMult); + var gCbMult = Vector256.Create(-FromYCbCrScalar.GCbMult); + var gCrMult = Vector256.Create(-FromYCbCrScalar.GCrMult); + var bCbMult = Vector256.Create(FromYCbCrScalar.BCbMult); // Walking 8 elements at one step: - nint n = values.Component0.Length / 8; + nint n = values.Component0.Length / Vector256.Count; for (nint i = 0; i < n; i++) { // y = yVals[i]; @@ -64,7 +55,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters // r = y + (1.402F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr); // b = y + (1.772F * cb); - // Adding & multiplying 8 elements at one time: Vector256 r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult); Vector256 g = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); Vector256 b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult); @@ -77,11 +67,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters c1 = g; c2 = b; } -#endif } - - protected override void ConvertCoreInplace(in ComponentValues values) => - FromYCbCrBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); } } } +#endif diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs deleted file mode 100644 index 990d29aa0..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverter - { - internal sealed class FromYCbCrBasic : BasicJpegColorConverter - { - public FromYCbCrBasic(int precision) - : base(JpegColorSpace.YCbCr, precision) - { - } - - public override void ConvertToRgbInplace(in ComponentValues values) - => ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); - - internal static void ConvertCoreInplace(in ComponentValues values, float maxValue, float halfValue) - { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - - var scale = 1 / maxValue; - - for (int i = 0; i < c0.Length; i++) - { - float y = c0[i]; - float cb = c1[i] - halfValue; - float cr = c2[i] - halfValue; - - c0[i] = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero) * scale; - c1[i] = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero) * scale; - c2[i] = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero) * scale; - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs new file mode 100644 index 000000000..4b6d88f72 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +{ + internal abstract partial class JpegColorConverterBase + { + internal sealed class FromYCbCrScalar : JpegColorConverterScalar + { + // TODO: comments, derived from ITU-T Rec. T.871 + internal const float RCrMult = 1.402f; + internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); + internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); + internal const float BCbMult = 1.772f; + + public FromYCbCrScalar(int precision) + : base(JpegColorSpace.YCbCr, precision) + { + } + + public override void ConvertToRgbInplace(in ComponentValues values) + => ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); + + internal static void ConvertCoreInplace(in ComponentValues values, float maxValue, float halfValue) + { + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + + float scale = 1 / maxValue; + + for (int i = 0; i < c0.Length; i++) + { + float y = c0[i]; + float cb = c1[i] - halfValue; + float cr = c2[i] - halfValue; + + // r = y + (1.402F * cr); + // g = y - (0.344136F * cb) - (0.714136F * cr); + // b = y + (1.772F * cb); + c0[i] = MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero) * scale; + c1[i] = MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero) * scale; + c2[i] = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scale; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs similarity index 73% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector8.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs index a077b9ed8..48e311d99 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs @@ -1,20 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Tuples; // ReSharper disable ImpureMethodCallOnReadonlyValueField namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { - internal abstract partial class JpegColorConverter + internal abstract partial class JpegColorConverterBase { - internal sealed class FromYCbCrVector8 : Vector8JpegColorConverter + internal sealed class FromYCbCrVector : JpegColorConverterVector { - public FromYCbCrVector8(int precision) + public FromYCbCrVector(int precision) : base(JpegColorSpace.YCbCr, precision) { } @@ -30,10 +28,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var chromaOffset = new Vector(-this.HalfValue); - // Walking 8 elements at one step: - nint n = values.Component0.Length / 8; var scale = new Vector(1 / this.MaximumValue); + var rCrMult = new Vector(FromYCbCrScalar.RCrMult); + var gCbMult = new Vector(-FromYCbCrScalar.GCbMult); + var gCrMult = new Vector(-FromYCbCrScalar.GCrMult); + var bCbMult = new Vector(FromYCbCrScalar.BCbMult); + nint n = values.Component0.Length / Vector.Count; for (nint i = 0; i < n; i++) { // y = yVals[i]; @@ -49,10 +50,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters // r = y + (1.402F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr); // b = y + (1.772F * cb); - // Adding & multiplying 8 elements at one time: - Vector r = y + (cr * new Vector(1.402F)); - Vector g = y - (cb * new Vector(0.344136F)) - (cr * new Vector(0.714136F)); - Vector b = y + (cb * new Vector(1.772F)); + Vector r = y + (cr * rCrMult); + Vector g = y + (cb * gCbMult) + (cr * gCrMult); + Vector b = y + (cb * bCbMult); r = r.FastRound(); g = g.FastRound(); @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } protected override void ConvertCoreInplace(in ComponentValues values) => - FromYCbCrBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); + FromYCbCrScalar.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector4.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector4.cs deleted file mode 100644 index 1ebc3e879..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector4.cs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Tuples; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverter - { - internal sealed class FromYCbCrVector4 : VectorizedJpegColorConverter - { - public FromYCbCrVector4(int precision) - : base(JpegColorSpace.YCbCr, precision, 8) - { - } - - protected override bool IsAvailable => SimdUtils.HasVector4; - - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) - { - DebugGuard.IsTrue(values.Component0.Length % 8 == 0, nameof(values), "Length should be divisible by 8!"); - - ref Vector4Pair c0Base = - ref Unsafe.As(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector4Pair c1Base = - ref Unsafe.As(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector4Pair c2Base = - ref Unsafe.As(ref MemoryMarshal.GetReference(values.Component2)); - - var chromaOffset = new Vector4(-this.HalfValue); - var maxValue = this.MaximumValue; - - // Walking 8 elements at one step: - nint n = values.Component0.Length / 8; - - for (nint i = 0; i < n; i++) - { - // y = yVals[i]; - ref Vector4Pair c0 = ref Unsafe.Add(ref c0Base, i); - - // cb = cbVals[i] - halfValue); - ref Vector4Pair c1 = ref Unsafe.Add(ref c1Base, i); - c1.AddInplace(chromaOffset); - - // cr = crVals[i] - halfValue; - ref Vector4Pair c2 = ref Unsafe.Add(ref c2Base, i); - c2.AddInplace(chromaOffset); - - // r = y + (1.402F * cr); - Vector4Pair r = c0; - Vector4Pair tmp = c2; - tmp.MultiplyInplace(1.402F); - r.AddInplace(ref tmp); - - // g = y - (0.344136F * cb) - (0.714136F * cr); - Vector4Pair g = c0; - tmp = c1; - tmp.MultiplyInplace(-0.344136F); - g.AddInplace(ref tmp); - tmp = c2; - tmp.MultiplyInplace(-0.714136F); - g.AddInplace(ref tmp); - - // b = y + (1.772F * cb); - Vector4Pair b = c0; - tmp = c1; - tmp.MultiplyInplace(1.772F); - b.AddInplace(ref tmp); - - r.RoundAndDownscalePreVector8(maxValue); - g.RoundAndDownscalePreVector8(maxValue); - b.RoundAndDownscalePreVector8(maxValue); - - c0 = r; - c1 = g; - c2 = b; - } - } - - protected override void ConvertCoreInplace(in ComponentValues values) - => FromYCbCrBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs similarity index 77% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx2.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs index a3500a096..1f18d5324 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx2.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs @@ -1,30 +1,26 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Numerics; +#if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -#if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; using static SixLabors.ImageSharp.SimdUtils; -#endif namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { - internal abstract partial class JpegColorConverter + internal abstract partial class JpegColorConverterBase { - internal sealed class FromYccKAvx2 : Avx2JpegColorConverter + internal sealed class FromYccKAvx : JpegColorConverterAvx { - public FromYccKAvx2(int precision) + public FromYccKAvx(int precision) : base(JpegColorSpace.Ycck, precision) { } - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + public override void ConvertToRgbInplace(in ComponentValues values) { -#if SUPPORTS_RUNTIME_INTRINSICS ref Vector256 c0Base = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); ref Vector256 c1Base = @@ -38,13 +34,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var chromaOffset = Vector256.Create(-this.HalfValue); var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); var max = Vector256.Create(this.MaximumValue); - var rCrMult = Vector256.Create(1.402F); - var gCbMult = Vector256.Create(-0.344136F); - var gCrMult = Vector256.Create(-0.714136F); - var bCbMult = Vector256.Create(1.772F); + var rCrMult = Vector256.Create(FromYCbCrScalar.RCrMult); + var gCbMult = Vector256.Create(-FromYCbCrScalar.GCbMult); + var gCrMult = Vector256.Create(-FromYCbCrScalar.GCrMult); + var bCbMult = Vector256.Create(FromYCbCrScalar.BCbMult); // Walking 8 elements at one step: - nint n = values.Component0.Length / 8; + nint n = values.Component0.Length / Vector256.Count; for (nint i = 0; i < n; i++) { // y = yVals[i]; @@ -62,7 +58,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters // r = y + (1.402F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr); // b = y + (1.772F * cb); - // Adding & multiplying 8 elements at one time: Vector256 r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult); Vector256 g = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); @@ -80,11 +75,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters c1 = g; c2 = b; } -#endif } - - protected override void ConvertCoreInplace(in ComponentValues values) => - FromYccKBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); } } } +#endif diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKBasic.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs similarity index 81% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKBasic.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs index 4833f4868..d6387ae71 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKBasic.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs @@ -1,16 +1,15 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; -using System.Numerics; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { - internal abstract partial class JpegColorConverter + internal abstract partial class JpegColorConverterBase { - internal sealed class FromYccKBasic : BasicJpegColorConverter + internal sealed class FromYccKScalar : JpegColorConverterScalar { - public FromYccKBasic(int precision) + public FromYccKScalar(int precision) : base(JpegColorSpace.Ycck, precision) { } @@ -25,9 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters Span c2 = values.Component2; Span c3 = values.Component3; - var v = new Vector4(0, 0, 0, 1F); - - var scale = 1 / (maxValue * maxValue); + float scale = 1 / (maxValue * maxValue); for (int i = 0; i < values.Component0.Length; i++) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs similarity index 71% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector8.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs index f830e5042..66c79ae7c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs @@ -1,19 +1,17 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Tuples; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { - internal abstract partial class JpegColorConverter + internal abstract partial class JpegColorConverterBase { - internal sealed class FromYccKVector8 : Vector8JpegColorConverter + internal sealed class FromYccKVector : JpegColorConverterVector { - public FromYccKVector8(int precision) + public FromYccKVector(int precision) : base(JpegColorSpace.Ycck, precision) { } @@ -30,13 +28,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); var chromaOffset = new Vector(-this.HalfValue); - - // Walking 8 elements at one step: - nint n = values.Component0.Length / 8; - + var scale = new Vector(1 / (this.MaximumValue * this.MaximumValue)); var max = new Vector(this.MaximumValue); - var scale = new Vector(1f) / (max * max); + var rCrMult = new Vector(FromYCbCrScalar.RCrMult); + var gCbMult = new Vector(-FromYCbCrScalar.GCbMult); + var gCrMult = new Vector(-FromYCbCrScalar.GCrMult); + var bCbMult = new Vector(FromYCbCrScalar.BCbMult); + nint n = values.Component0.Length / Vector.Count; for (nint i = 0; i < n; i++) { // y = yVals[i]; @@ -55,10 +54,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters // r = y + (1.402F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr); // b = y + (1.772F * cb); - // Adding & multiplying 8 elements at one time: - Vector r = y + (cr * new Vector(1.402F)); - Vector g = y - (cb * new Vector(0.344136F)) - (cr * new Vector(0.714136F)); - Vector b = y + (cb * new Vector(1.772F)); + Vector r = y + (cr * rCrMult); + Vector g = y + (cb * gCbMult) + (cr * gCrMult); + Vector b = y + (cb * bCbMult); r = (max - r.FastRound()) * scaledK; g = (max - g.FastRound()) * scaledK; @@ -71,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } protected override void ConvertCoreInplace(in ComponentValues values) => - FromYccKBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); + FromYccKScalar.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Vector8JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Vector8JpegColorConverter.cs deleted file mode 100644 index 3e9b889db..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Vector8JpegColorConverter.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverter - { - internal abstract class Vector8JpegColorConverter : VectorizedJpegColorConverter - { - protected Vector8JpegColorConverter(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision, 8) - { - } - - protected sealed override bool IsAvailable => SimdUtils.HasVector8; - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.VectorizedJpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.VectorizedJpegColorConverter.cs deleted file mode 100644 index fc4fb7786..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.VectorizedJpegColorConverter.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverter - { - internal abstract class VectorizedJpegColorConverter : JpegColorConverter - { - private readonly int vectorSize; - - protected VectorizedJpegColorConverter(JpegColorSpace colorSpace, int precision, int vectorSize) - : base(colorSpace, precision) - { - this.vectorSize = vectorSize; - } - - public override void ConvertToRgbInplace(in ComponentValues values) - { - int length = values.Component0.Length; - int remainder = values.Component0.Length % this.vectorSize; - int simdCount = length - remainder; - if (simdCount > 0) - { - // This implementation is actually AVX specific. - // An AVX register is capable of storing 8 float-s. - if (!this.IsAvailable) - { - throw new InvalidOperationException( - "This converter can be used only on architecture having 256 byte floating point SIMD registers!"); - } - - this.ConvertCoreVectorizedInplace(values.Slice(0, simdCount)); - } - - this.ConvertCoreInplace(values.Slice(simdCount, remainder)); - } - - protected virtual void ConvertCoreVectorizedInplace(in ComponentValues values) => throw new NotImplementedException(); - - protected virtual void ConvertCoreInplace(in ComponentValues values) => throw new NotImplementedException(); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs new file mode 100644 index 000000000..81c7c0764 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics.X86; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +{ + internal abstract partial class JpegColorConverterBase + { + /// + /// abstract base for implementations + /// based on instructions. + /// + /// + /// Converters of this family would expect input buffers lengths to be + /// divisible by 8 without a remainder. + /// This is guaranteed by real-life data as jpeg stores pixels via 8x8 blocks. + /// DO NOT pass test data of invalid size to these converters as they + /// potentially won't do a bound check and return a false positive result. + /// + internal abstract class JpegColorConverterAvx : JpegColorConverterBase + { + protected JpegColorConverterAvx(JpegColorSpace colorSpace, int precision) + : base(colorSpace, precision) + { + } + + public override bool IsAvailable => Avx.IsSupported; + } + } +} +#endif diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs similarity index 59% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs index 11ea4cda8..808ca687b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs @@ -4,26 +4,24 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Numerics; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Tuples; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { /// - /// Encapsulates the conversion of Jpeg channels to RGBA values packed in buffer. + /// Encapsulates the conversion of color channels from jpeg image to RGB channels. /// - internal abstract partial class JpegColorConverter + internal abstract partial class JpegColorConverterBase { /// /// The available converters /// - private static readonly JpegColorConverter[] Converters = CreateConverters(); + private static readonly JpegColorConverterBase[] Converters = CreateConverters(); /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - protected JpegColorConverter(JpegColorSpace colorSpace, int precision) + protected JpegColorConverterBase(JpegColorSpace colorSpace, int precision) { this.ColorSpace = colorSpace; this.Precision = precision; @@ -32,10 +30,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } /// - /// Gets a value indicating whether this is available + /// Gets a value indicating whether this is available /// on the current runtime and CPU architecture. /// - protected abstract bool IsAvailable { get; } + public abstract bool IsAvailable { get; } /// /// Gets the of this converter. @@ -58,11 +56,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters private float HalfValue { get; } /// - /// Returns the corresponding to the given + /// Returns the corresponding to the given /// - public static JpegColorConverter GetConverter(JpegColorSpace colorSpace, int precision) + public static JpegColorConverterBase GetConverter(JpegColorSpace colorSpace, int precision) { - JpegColorConverter converter = Array.Find( + JpegColorConverterBase converter = Array.Find( Converters, c => c.ColorSpace == colorSpace && c.Precision == precision); @@ -82,11 +80,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters public abstract void ConvertToRgbInplace(in ComponentValues values); /// - /// Returns the s for all supported colorspaces and precisions. + /// Returns the s for all supported colorspaces and precisions. /// - private static JpegColorConverter[] CreateConverters() + private static JpegColorConverterBase[] CreateConverters() { - var converters = new List(); + var converters = new List(); // 8-bit converters converters.AddRange(GetYCbCrConverters(8)); @@ -106,63 +104,63 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } /// - /// Returns the s for the YCbCr colorspace. + /// Returns the s for the YCbCr colorspace. /// - private static IEnumerable GetYCbCrConverters(int precision) + private static IEnumerable GetYCbCrConverters(int precision) { #if SUPPORTS_RUNTIME_INTRINSICS - yield return new FromYCbCrAvx2(precision); + yield return new FromYCbCrAvx(precision); #endif - yield return new FromYCbCrVector8(precision); - yield return new FromYCbCrVector4(precision); - yield return new FromYCbCrBasic(precision); + yield return new FromYCbCrVector(precision); + yield return new FromYCbCrScalar(precision); } /// - /// Returns the s for the YccK colorspace. + /// Returns the s for the YccK colorspace. /// - private static IEnumerable GetYccKConverters(int precision) + private static IEnumerable GetYccKConverters(int precision) { #if SUPPORTS_RUNTIME_INTRINSICS - yield return new FromYccKAvx2(precision); + yield return new FromYccKAvx(precision); #endif - yield return new FromYccKVector8(precision); - yield return new FromYccKBasic(precision); + yield return new FromYccKVector(precision); + yield return new FromYccKScalar(precision); } /// - /// Returns the s for the CMYK colorspace. + /// Returns the s for the CMYK colorspace. /// - private static IEnumerable GetCmykConverters(int precision) + private static IEnumerable GetCmykConverters(int precision) { #if SUPPORTS_RUNTIME_INTRINSICS - yield return new FromCmykAvx2(precision); + yield return new FromCmykAvx(precision); #endif - yield return new FromCmykVector8(precision); - yield return new FromCmykBasic(precision); + yield return new FromCmykVector(precision); + yield return new FromCmykScalar(precision); } /// - /// Returns the s for the gray scale colorspace. + /// Returns the s for the gray scale colorspace. /// - private static IEnumerable GetGrayScaleConverters(int precision) + private static IEnumerable GetGrayScaleConverters(int precision) { #if SUPPORTS_RUNTIME_INTRINSICS - yield return new FromGrayscaleAvx2(precision); + yield return new FromGrayscaleAvx(precision); #endif - yield return new FromGrayscaleBasic(precision); + yield return new FromGrayScaleVector(precision); + yield return new FromGrayscaleScalar(precision); } /// - /// Returns the s for the RGB colorspace. + /// Returns the s for the RGB colorspace. /// - private static IEnumerable GetRgbConverters(int precision) + private static IEnumerable GetRgbConverters(int precision) { #if SUPPORTS_RUNTIME_INTRINSICS - yield return new FromRgbAvx2(precision); + yield return new FromRgbAvx(precision); #endif - yield return new FromRgbVector8(precision); - yield return new FromRgbBasic(precision); + yield return new FromRgbVector(precision); + yield return new FromRgbScalar(precision); } /// @@ -200,18 +198,39 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// /// Initializes a new instance of the struct. /// - /// The 1-4 sized list of component buffers. - /// The row to convert + /// List of component buffers. + /// Row to convert public ComponentValues(IReadOnlyList> componentBuffers, int row) { + DebugGuard.MustBeGreaterThan(componentBuffers.Count, 0, nameof(componentBuffers)); + this.ComponentCount = componentBuffers.Count; - this.Component0 = componentBuffers[0].GetRowSpan(row); + this.Component0 = componentBuffers[0].DangerousGetRowSpan(row); + + // In case of grayscale, Component1 and Component2 point to Component0 memory area + this.Component1 = this.ComponentCount > 1 ? componentBuffers[1].DangerousGetRowSpan(row) : this.Component0; + this.Component2 = this.ComponentCount > 2 ? componentBuffers[2].DangerousGetRowSpan(row) : this.Component0; + this.Component3 = this.ComponentCount > 3 ? componentBuffers[3].DangerousGetRowSpan(row) : Span.Empty; + } + + /// + /// Initializes a new instance of the struct. + /// + /// List of component color processors. + /// Row to convert + public ComponentValues(IReadOnlyList processors, int row) + { + DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors)); + + this.ComponentCount = processors.Count; + + this.Component0 = processors[0].GetColorBufferRowSpan(row); // In case of grayscale, Component1 and Component2 point to Component0 memory area - this.Component1 = this.ComponentCount > 1 ? componentBuffers[1].GetRowSpan(row) : this.Component0; - this.Component2 = this.ComponentCount > 2 ? componentBuffers[2].GetRowSpan(row) : this.Component0; - this.Component3 = this.ComponentCount > 3 ? componentBuffers[3].GetRowSpan(row) : Span.Empty; + this.Component1 = this.ComponentCount > 1 ? processors[1].GetColorBufferRowSpan(row) : this.Component0; + this.Component2 = this.ComponentCount > 2 ? processors[2].GetColorBufferRowSpan(row) : this.Component0; + this.Component3 = this.ComponentCount > 3 ? processors[3].GetColorBufferRowSpan(row) : Span.Empty; } internal ComponentValues( diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs new file mode 100644 index 000000000..ff88ab969 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +{ + internal abstract partial class JpegColorConverterBase + { + /// + /// abstract base for implementations + /// based on scalar instructions. + /// + internal abstract class JpegColorConverterScalar : JpegColorConverterBase + { + protected JpegColorConverterScalar(JpegColorSpace colorSpace, int precision) + : base(colorSpace, precision) + { + } + + public override bool IsAvailable => true; + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs new file mode 100644 index 000000000..ca482d78d --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs @@ -0,0 +1,59 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +{ + internal abstract partial class JpegColorConverterBase + { + /// + /// abstract base for implementations + /// based on API. + /// + /// + /// Converters of this family can work with data of any size. + /// Even though real life data is guaranteed to be of size + /// divisible by 8 newer SIMD instructions like AVX512 won't work with + /// such data out of the box. These converters have fallback code + /// for 'remainder' data. + /// + internal abstract class JpegColorConverterVector : JpegColorConverterBase + { + protected JpegColorConverterVector(JpegColorSpace colorSpace, int precision) + : base(colorSpace, precision) + { + } + + public sealed override bool IsAvailable => Vector.IsHardwareAccelerated && Vector.Count % 4 == 0; + + public override void ConvertToRgbInplace(in ComponentValues values) + { + DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); + + int length = values.Component0.Length; + int remainder = (int)((uint)length % (uint)Vector.Count); + + // Jpeg images are guaranteed to have pixel strides at least 8 pixels wide + // Thus there's no need to check whether simdCount is greater than zero + int simdCount = length - remainder; + this.ConvertCoreVectorizedInplace(values.Slice(0, simdCount)); + + // Jpeg images width is always divisible by 8 without a remainder + // so it's safe to say SSE/AVX implementations would never have + // 'remainder' pixels + // But some exotic simd implementations e.g. AVX-512 can have + // remainder pixels + if (remainder > 0) + { + this.ConvertCoreInplace(values.Slice(simdCount, remainder)); + } + } + + protected virtual void ConvertCoreVectorizedInplace(in ComponentValues values) => throw new NotImplementedException(); + + protected virtual void ConvertCoreInplace(in ComponentValues values) => throw new NotImplementedException(); + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 6f104351c..ce5e5110b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -203,7 +203,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // by the basic H and V specified for the component for (int y = 0; y < v; y++) { - Span blockSpan = component.SpectralBlocks.GetRowSpan(y); + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); for (int x = 0; x < h; x++) @@ -254,7 +254,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int j = 0; j < h; j++) { this.cancellationToken.ThrowIfCancellationRequested(); - Span blockSpan = component.SpectralBlocks.GetRowSpan(j); + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); for (int i = 0; i < w; i++) @@ -377,7 +377,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int y = 0; y < v; y++) { int blockRow = (mcuRow * v) + y; - Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow); + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(blockRow); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); for (int x = 0; x < h; x++) @@ -422,7 +422,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { this.cancellationToken.ThrowIfCancellationRequested(); - Span blockSpan = component.SpectralBlocks.GetRowSpan(j); + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); for (int i = 0; i < w; i++) @@ -450,7 +450,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { this.cancellationToken.ThrowIfCancellationRequested(); - Span blockSpan = component.SpectralBlocks.GetRowSpan(j); + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); for (int i = 0; i < w; i++) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs index 0b80acc5d..33815e539 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs @@ -5,7 +5,6 @@ using System; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { - /// /// /// Represents decompressed, unprocessed jpeg data with spectral space -s. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs deleted file mode 100644 index 15f212b40..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder -{ - /// - /// Encapsulates the implementation of processing "raw" jpeg buffers into Jpeg image channels. - /// - [StructLayout(LayoutKind.Sequential)] - internal struct JpegBlockPostProcessor - { - /// - /// Source block - /// - public Block8x8F SourceBlock; - - /// - /// The quantization table as . - /// - public Block8x8F DequantiazationTable; - - /// - /// Defines the horizontal and vertical scale we need to apply to the 8x8 sized block. - /// - private Size subSamplingDivisors; - - /// - /// Initializes a new instance of the struct. - /// - /// The raw jpeg data. - /// The raw component. - public JpegBlockPostProcessor(IRawJpegData decoder, IJpegComponent component) - { - int qtIndex = component.QuantizationTableIndex; - this.DequantiazationTable = decoder.QuantizationTables[qtIndex]; - this.subSamplingDivisors = component.SubSamplingDivisors; - - this.SourceBlock = default; - } - - /// - /// Processes 'sourceBlock' producing Jpeg color channel values from spectral values: - /// - Dequantize - /// - Applying IDCT - /// - Level shift by +maximumValue/2, clip to [0, maximumValue] - /// - Copy the resulting color values into 'destArea' scaling up the block by amount defined in . - /// - /// The source block. - /// Reference to the origin of the destination pixel area. - /// The width of the destination pixel buffer. - /// The maximum value derived from the bitdepth. - public void ProcessBlockColorsInto( - ref Block8x8 sourceBlock, - ref float destAreaOrigin, - int destAreaStride, - float maximumValue) - { - ref Block8x8F block = ref this.SourceBlock; - block.LoadFrom(ref sourceBlock); - - // Dequantize: - block.MultiplyInPlace(ref this.DequantiazationTable); - - FastFloatingPointDCT.TransformIDCT(ref block); - - // To conform better to libjpeg we actually NEED TO loose precision here. - // This is because they store blocks as Int16 between all the operations. - // To be "more accurate", we need to emulate this by rounding! - block.NormalizeColorsAndRoundInPlace(maximumValue); - - block.ScaledCopyTo( - ref destAreaOrigin, - destAreaStride, - this.subSamplingDivisors.Width, - this.subSamplingDivisors.Height); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs index 90162aba3..7ef280932 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs @@ -10,14 +10,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { Undefined = 0, + /// + /// Color space with 1 component. + /// Grayscale, + /// + /// Color space with 4 components. + /// Ycck, + /// + /// Color space with 4 components. + /// Cmyk, + /// + /// Color space with 3 components. + /// RGB, + /// + /// Color space with 3 components. + /// YCbCr } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index 9a659d621..c3bf1cbdd 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -11,11 +11,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// internal class JpegComponentPostProcessor : IDisposable { - /// - /// Points to the current row in . - /// - private int currentComponentRowInBlocks; - /// /// The size of the area in corresponding to one 8x8 Jpeg block /// @@ -26,6 +21,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// private readonly JpegFrame frame; + /// + /// Gets the maximal number of block rows being processed in one step. + /// + private readonly int blockRowsPerStep; + + /// + /// Gets the component containing decoding meta information. + /// + private readonly IJpegComponent component; + + /// + /// Gets the instance containing decoding meta information. + /// + private readonly IRawJpegData rawJpeg; + /// /// Initializes a new instance of the class. /// @@ -33,98 +43,87 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { this.frame = frame; - this.Component = component; - this.RawJpeg = rawJpeg; - this.blockAreaSize = this.Component.SubSamplingDivisors * 8; + this.component = component; + this.rawJpeg = rawJpeg; + this.blockAreaSize = this.component.SubSamplingDivisors * 8; this.ColorBuffer = memoryAllocator.Allocate2DOveraligned( postProcessorBufferSize.Width, postProcessorBufferSize.Height, this.blockAreaSize.Height); - this.BlockRowsPerStep = postProcessorBufferSize.Height / 8 / this.Component.SubSamplingDivisors.Height; + this.blockRowsPerStep = postProcessorBufferSize.Height / 8 / this.component.SubSamplingDivisors.Height; } - public IRawJpegData RawJpeg { get; } - - /// - /// Gets the - /// - public IJpegComponent Component { get; } - /// /// Gets the temporary working buffer of color values. /// public Buffer2D ColorBuffer { get; } - /// - /// Gets - /// - public Size SizeInBlocks => this.Component.SizeInBlocks; - - /// - /// Gets the maximal number of block rows being processed in one step. - /// - public int BlockRowsPerStep { get; } - /// public void Dispose() => this.ColorBuffer.Dispose(); /// - /// Invoke for block rows, copy the result into . + /// Convert raw spectral DCT data to color data and copy it to the color buffer . /// - public void CopyBlocksToColorBuffer(int step) + public void CopyBlocksToColorBuffer(int spectralStep) { - Buffer2D spectralBuffer = this.Component.SpectralBlocks; - - var blockPp = new JpegBlockPostProcessor(this.RawJpeg, this.Component); + Buffer2D spectralBuffer = this.component.SpectralBlocks; float maximumValue = this.frame.MaxColorChannelValue; int destAreaStride = this.ColorBuffer.Width; - int yBlockStart = step * this.BlockRowsPerStep; + int yBlockStart = spectralStep * this.blockRowsPerStep; - for (int y = 0; y < this.BlockRowsPerStep; y++) - { - int yBlock = yBlockStart + y; + Size subSamplingDivisors = this.component.SubSamplingDivisors; - if (yBlock >= spectralBuffer.Height) - { - break; - } + Block8x8F dequantTable = this.rawJpeg.QuantizationTables[this.component.QuantizationTableIndex]; + Block8x8F workspaceBlock = default; + for (int y = 0; y < this.blockRowsPerStep; y++) + { int yBuffer = y * this.blockAreaSize.Height; - Span colorBufferRow = this.ColorBuffer.GetRowSpan(yBuffer); - Span blockRow = spectralBuffer.GetRowSpan(yBlock); + Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); + Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); - // see: https://github.com/SixLabors/ImageSharp/issues/824 - int widthInBlocks = Math.Min(spectralBuffer.Width, this.SizeInBlocks.Width); - - for (int xBlock = 0; xBlock < widthInBlocks; xBlock++) + for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) { - ref Block8x8 block = ref blockRow[xBlock]; - int xBuffer = xBlock * this.blockAreaSize.Width; - ref float destAreaOrigin = ref colorBufferRow[xBuffer]; - - blockPp.ProcessBlockColorsInto(ref block, ref destAreaOrigin, destAreaStride, maximumValue); + // Integer to float + workspaceBlock.LoadFrom(ref blockRow[xBlock]); + + // Dequantize + workspaceBlock.MultiplyInPlace(ref dequantTable); + + // Convert from spectral to color + FastFloatingPointDCT.TransformIDCT(ref workspaceBlock); + + // To conform better to libjpeg we actually NEED TO loose precision here. + // This is because they store blocks as Int16 between all the operations. + // To be "more accurate", we need to emulate this by rounding! + workspaceBlock.NormalizeColorsAndRoundInPlace(maximumValue); + + // Write to color buffer acording to sampling factors + int xColorBufferStart = xBlock * this.blockAreaSize.Width; + workspaceBlock.ScaledCopyTo( + ref colorBufferRow[xColorBufferStart], + destAreaStride, + subSamplingDivisors.Width, + subSamplingDivisors.Height); } } } public void ClearSpectralBuffers() { - Buffer2D spectralBlocks = this.Component.SpectralBlocks; + Buffer2D spectralBlocks = this.component.SpectralBlocks; for (int i = 0; i < spectralBlocks.Height; i++) { - spectralBlocks.GetRowSpan(i).Clear(); + spectralBlocks.DangerousGetRowSpan(i).Clear(); } } - public void CopyBlocksToColorBuffer() - { - this.CopyBlocksToColorBuffer(this.currentComponentRowInBlocks); - this.currentComponentRowInBlocks += this.BlockRowsPerStep; - } + public Span GetColorBufferRowSpan(int row) => + this.ColorBuffer.DangerousGetRowSpan(row); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs index 4e74f6226..acd98bcfc 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs @@ -6,11 +6,8 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { /// - /// Converter used to convert jpeg spectral data. + /// Converter used to convert jpeg spectral data to color pixels. /// - /// - /// This is tightly coupled with and . - /// internal abstract class SpectralConverter { /// @@ -30,12 +27,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); /// - /// Called once per spectral stride for each component in . - /// This is called only for baseline interleaved jpegs. + /// Converts single spectral jpeg stride to color stride in baseline + /// decoding mode. /// /// + /// Called once per decoded spectral stride in + /// only for baseline interleaved jpeg images. /// Spectral 'stride' doesn't particularly mean 'single stride'. - /// Actual stride height depends on the subsampling factor of the given component. + /// Actual stride height depends on the subsampling factor of the given image. /// public abstract void ConvertStrideBaseline(); @@ -58,6 +57,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// The jpeg frame with the color space to convert to. /// The raw JPEG data. /// The color converter. - protected virtual JpegColorConverter GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverter.GetConverter(jpegData.ColorSpace, frame.Precision); + protected virtual JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverterBase.GetConverter(jpegData.ColorSpace, frame.Precision); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 2e965e0ac..87f11a085 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -4,7 +4,6 @@ using System; using System.Buffers; using System.Linq; -using System.Numerics; using System.Threading; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; using SixLabors.ImageSharp.Memory; @@ -12,35 +11,78 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { + /// + /// + /// Color decoding scheme: + /// + /// + /// Decode spectral data to Jpeg color space + /// Convert from Jpeg color space to RGB + /// Convert from RGB to target pixel space + /// + /// + /// internal class SpectralConverter : SpectralConverter, IDisposable where TPixel : unmanaged, IPixel { + /// + /// instance associated with current + /// decoding routine. + /// private readonly Configuration configuration; - private readonly CancellationToken cancellationToken; - + /// + /// Jpeg component converters from decompressed spectral to color data. + /// private JpegComponentPostProcessor[] componentProcessors; - private JpegColorConverter colorConverter; + /// + /// Color converter from jpeg color space to target pixel color space. + /// + private JpegColorConverterBase colorConverter; - // private IMemoryOwner rgbaBuffer; + /// + /// Intermediate buffer of RGB components used in color conversion. + /// private IMemoryOwner rgbBuffer; + /// + /// Proxy buffer used in packing from RGB to target TPixel pixels. + /// private IMemoryOwner paddedProxyPixelRow; + /// + /// Resulting 2D pixel buffer. + /// private Buffer2D pixelBuffer; + /// + /// How many pixel rows are processed in one 'stride'. + /// private int pixelRowsPerStep; + /// + /// How many pixel rows were processed. + /// private int pixelRowCounter; - public SpectralConverter(Configuration configuration, CancellationToken cancellationToken) - { + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + public SpectralConverter(Configuration configuration) => this.configuration = configuration; - this.cancellationToken = cancellationToken; - } - public Buffer2D GetPixelBuffer() + /// + /// Gets converted pixel buffer. + /// + /// + /// For non-baseline interleaved jpeg this method does a 'lazy' spectral + /// conversion from spectral to color. + /// + /// Cancellation token. + /// Pixel buffer. + public Buffer2D GetPixelBuffer(CancellationToken cancellationToken) { if (!this.Converted) { @@ -48,7 +90,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int step = 0; step < steps; step++) { - this.cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); this.ConvertStride(step); } } @@ -102,30 +144,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } } - /// - public void Dispose() - { - if (this.componentProcessors != null) - { - foreach (JpegComponentPostProcessor cpp in this.componentProcessors) - { - cpp.Dispose(); - } - } - - this.rgbBuffer?.Dispose(); - this.paddedProxyPixelRow?.Dispose(); - } - + /// + /// Converts single spectral jpeg stride to color stride. + /// + /// Spectral stride index. private void ConvertStride(int spectralStep) { int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep); - var buffers = new Buffer2D[this.componentProcessors.Length]; for (int i = 0; i < this.componentProcessors.Length; i++) { this.componentProcessors[i].CopyBlocksToColorBuffer(spectralStep); - buffers[i] = this.componentProcessors[i].ColorBuffer; } int width = this.pixelBuffer.Width; @@ -134,7 +163,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { int y = yy - this.pixelRowCounter; - var values = new JpegColorConverter.ComponentValues(buffers, y); + var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y); this.colorConverter.ConvertToRgbInplace(values); values = values.Slice(0, width); // slice away Jpeg padding @@ -158,11 +187,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { Span proxyRow = this.paddedProxyPixelRow.GetSpan(); PixelOperations.Instance.PackFromRgbPlanes(this.configuration, r, g, b, proxyRow); - proxyRow.Slice(0, width).CopyTo(this.pixelBuffer.GetRowSpan(yy)); + proxyRow.Slice(0, width).CopyTo(this.pixelBuffer.DangerousGetRowSpan(yy)); } } this.pixelRowCounter += this.pixelRowsPerStep; } + + /// + public void Dispose() + { + if (this.componentProcessors != null) + { + foreach (JpegComponentPostProcessor cpp in this.componentProcessors) + { + cpp.Dispose(); + } + } + + this.rgbBuffer?.Dispose(); + this.paddedProxyPixelRow?.Dispose(); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index b3cdbf0a0..6acc6b6db 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -278,11 +278,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // ReSharper disable once InconsistentNaming int prevDCY = 0; - var pixelConverter = LuminanceForwardConverter.Create(); ImageFrame frame = pixels.Frames.RootFrame; Buffer2D pixelBuffer = frame.PixelBuffer; RowOctet currentRows = default; + var pixelConverter = new LuminanceForwardConverter(frame); + for (int y = 0; y < pixels.Height; y += 8) { cancellationToken.ThrowIfCancellationRequested(); @@ -290,7 +291,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder for (int x = 0; x < pixels.Width; x += 8) { - pixelConverter.Convert(frame, x, y, ref currentRows); + pixelConverter.Convert(x, y, ref currentRows); prevDCY = this.WriteBlock( QuantIndex.Luminance, @@ -649,6 +650,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } this.target.Write(this.streamWriteBuffer, 0, writeIdx); + this.emitWriteIndex = this.emitBuffer.Length; } /// @@ -659,11 +661,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// This must be called only if is true /// only during the macroblocks encoding routine. /// - private void FlushToStream() - { + private void FlushToStream() => this.FlushToStream(this.emitWriteIndex * 4); - this.emitWriteIndex = this.emitBuffer.Length; - } /// /// Flushes final cached bits to the stream padding 1's to @@ -680,10 +679,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // And writing only valuable count of bytes count we want to write to the output stream int valuableBytesCount = (int)Numerics.DivideCeil((uint)this.bitCount, 8); uint packedBytes = this.accumulatedBits | (uint.MaxValue >> this.bitCount); - this.emitBuffer[--this.emitWriteIndex] = packedBytes; + this.emitBuffer[this.emitWriteIndex - 1] = packedBytes; // Flush cached bytes to the output stream with padding bits - this.FlushToStream((this.emitWriteIndex * 4) - 4 + valuableBytesCount); + int lastByteIndex = (this.emitWriteIndex * 4) - valuableBytesCount; + this.FlushToStream(lastByteIndex); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs index fc5b9a868..6c402fcfd 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; @@ -15,39 +16,63 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder internal ref struct LuminanceForwardConverter where TPixel : unmanaged, IPixel { + /// + /// Number of pixels processed per single call + /// + private const int PixelsPerSample = 8 * 8; + /// /// The Y component /// public Block8x8F Y; /// - /// Temporal 8x8 block to hold TPixel data + /// Temporal 64-pixel span to hold unconverted TPixel data. + /// + private readonly Span pixelSpan; + + /// + /// Temporal 64-byte span to hold converted data. + /// + private readonly Span l8Span; + + /// + /// Sampled pixel buffer size. /// - private GenericBlock8x8 pixelBlock; + private readonly Size samplingAreaSize; /// - /// Temporal RGB block + /// for internal operations. /// - private GenericBlock8x8 l8Block; + private readonly Configuration config; - public static LuminanceForwardConverter Create() + public LuminanceForwardConverter(ImageFrame frame) { - var result = default(LuminanceForwardConverter); - return result; + this.Y = default; + + this.pixelSpan = new TPixel[PixelsPerSample].AsSpan(); + this.l8Span = new L8[PixelsPerSample].AsSpan(); + + this.samplingAreaSize = new Size(frame.Width, frame.Height); + this.config = frame.GetConfiguration(); } + /// + /// Gets size of sampling area from given frame pixel buffer. + /// + private static Size SampleSize => new(8, 8); + /// /// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure () /// - public void Convert(ImageFrame frame, int x, int y, ref RowOctet currentRows) + public void Convert(int x, int y, ref RowOctet currentRows) { - this.pixelBlock.LoadAndStretchEdges(frame.PixelBuffer, x, y, ref currentRows); + YCbCrForwardConverter.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize); - Span l8Span = this.l8Block.AsSpanUnsafe(); - PixelOperations.Instance.ToL8(frame.GetConfiguration(), this.pixelBlock.AsSpanUnsafe(), l8Span); + PixelOperations.Instance.ToL8(this.config, this.pixelSpan, this.l8Span); ref Block8x8F yBlock = ref this.Y; - ref L8 l8Start = ref l8Span[0]; + ref L8 l8Start = ref MemoryMarshal.GetReference(this.l8Span); for (int i = 0; i < Block8x8F.Size; i++) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs index 0be1076e2..789277d7d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs @@ -26,11 +26,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// private const int RgbSpanByteSize = PixelsPerSample * 3; - /// - /// of sampling area from given frame pixel buffer. - /// - private static readonly Size SampleSize = new Size(8, 8); - /// /// The Red component. /// @@ -81,6 +76,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder this.config = frame.GetConfiguration(); } + /// + /// Gets size of sampling area from given frame pixel buffer. + /// + private static Size SampleSize => new(8, 8); + /// /// Converts a 8x8 image area inside 'pixels' at position (x, y) to Rgb24. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs index bfeafcbb3..3a878f3c6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs @@ -25,11 +25,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// private const int RgbSpanByteSize = PixelsPerSample * 3; - /// - /// of sampling area from given frame pixel buffer - /// - private static readonly Size SampleSize = new Size(16, 8); - /// /// The left Y component /// @@ -102,6 +97,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } } + /// + /// Gets size of sampling area from given frame pixel buffer. + /// + private static Size SampleSize => new(16, 8); + public void Convert(int x, int y, ref RowOctet currentRows, int idx) { YCbCrForwardConverter.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs index 2dbd1a2dc..5f7725bdd 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs @@ -25,11 +25,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// private const int RgbSpanByteSize = PixelsPerSample * 3; - /// - /// of sampling area from given frame pixel buffer - /// - private static readonly Size SampleSize = new Size(8, 8); - /// /// The Y component /// @@ -96,6 +91,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } } + /// + /// Gets size of sampling area from given frame pixel buffer. + /// + private static Size SampleSize => new(8, 8); + /// /// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (, , ) /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.cs b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.cs deleted file mode 100644 index 213c48ff3..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -// -namespace SixLabors.ImageSharp.Formats.Jpeg.Components -{ - internal unsafe partial struct GenericBlock8x8 - { - #pragma warning disable 169 - - // It's not allowed use fix-sized buffers with generics, need to place all the fields manually: - private T _y0_x0, _y0_x1, _y0_x2, _y0_x3, _y0_x4, _y0_x5, _y0_x6, _y0_x7; - private T _y1_x0, _y1_x1, _y1_x2, _y1_x3, _y1_x4, _y1_x5, _y1_x6, _y1_x7; - private T _y2_x0, _y2_x1, _y2_x2, _y2_x3, _y2_x4, _y2_x5, _y2_x6, _y2_x7; - private T _y3_x0, _y3_x1, _y3_x2, _y3_x3, _y3_x4, _y3_x5, _y3_x6, _y3_x7; - private T _y4_x0, _y4_x1, _y4_x2, _y4_x3, _y4_x4, _y4_x5, _y4_x6, _y4_x7; - private T _y5_x0, _y5_x1, _y5_x2, _y5_x3, _y5_x4, _y5_x5, _y5_x6, _y5_x7; - private T _y6_x0, _y6_x1, _y6_x2, _y6_x3, _y6_x4, _y6_x5, _y6_x6, _y6_x7; - private T _y7_x0, _y7_x1, _y7_x2, _y7_x3, _y7_x4, _y7_x5, _y7_x6, _y7_x7; - - #pragma warning restore 169 - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.tt b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.tt deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs deleted file mode 100644 index 42c01d770..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Formats.Jpeg.Components -{ - /// - /// A generic 8x8 block implementation, useful for manipulating custom 8x8 pixel data. - /// - [StructLayout(LayoutKind.Sequential)] - internal unsafe partial struct GenericBlock8x8 - where T : unmanaged - { - public const int Size = 64; - - /// - /// FOR TESTING ONLY! - /// Gets or sets a value at the given index - /// - /// The index - /// The value - public T this[int idx] - { - get - { - ref T selfRef = ref Unsafe.As, T>(ref this); - return Unsafe.Add(ref selfRef, idx); - } - - set - { - ref T selfRef = ref Unsafe.As, T>(ref this); - Unsafe.Add(ref selfRef, idx) = value; - } - } - - /// - /// FOR TESTING ONLY! - /// Gets or sets a value in a row+column of the 8x8 block - /// - /// The x position index in the row - /// The column index - /// The value - public T this[int x, int y] - { - get => this[(y * 8) + x]; - set => this[(y * 8) + x] = value; - } - - /// - /// Load a 8x8 region of an image into the block. - /// The "outlying" area of the block will be stretched out with pixels on the right and bottom edge of the image. - /// - public void LoadAndStretchEdges(Buffer2D source, int sourceX, int sourceY, ref RowOctet currentRows) - { - int width = Math.Min(8, source.Width - sourceX); - int height = Math.Min(8, source.Height - sourceY); - - if (width <= 0 || height <= 0) - { - return; - } - - uint byteWidth = (uint)width * (uint)Unsafe.SizeOf(); - int remainderXCount = 8 - width; - - ref byte blockStart = ref Unsafe.As, byte>(ref this); - int blockRowSizeInBytes = 8 * Unsafe.SizeOf(); - - for (int y = 0; y < height; y++) - { - Span row = currentRows[y]; - - ref byte s = ref Unsafe.As(ref row[sourceX]); - ref byte d = ref Unsafe.Add(ref blockStart, y * blockRowSizeInBytes); - - Unsafe.CopyBlock(ref d, ref s, byteWidth); - - ref T last = ref Unsafe.Add(ref Unsafe.As(ref d), width - 1); - - for (int x = 1; x <= remainderXCount; x++) - { - Unsafe.Add(ref last, x) = last; - } - } - - int remainderYCount = 8 - height; - - if (remainderYCount == 0) - { - return; - } - - ref byte lastRowStart = ref Unsafe.Add(ref blockStart, (height - 1) * blockRowSizeInBytes); - - for (int y = 1; y <= remainderYCount; y++) - { - ref byte remStart = ref Unsafe.Add(ref lastRowStart, blockRowSizeInBytes * y); - Unsafe.CopyBlock(ref remStart, ref lastRowStart, (uint)blockRowSizeInBytes); - } - } - - /// - /// Only for on-stack instances! - /// - public Span AsSpanUnsafe() - { -#if SUPPORTS_CREATESPAN - Span> s = MemoryMarshal.CreateSpan(ref this, 1); - return MemoryMarshal.Cast, T>(s); -#else - return new Span(Unsafe.AsPointer(ref this), Size); -#endif - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs index 16d24cf81..d4a4c1cf4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components int i = 0; while (y < yEnd) { - this[i++] = buffer.GetRowSpan(y++); + this[i++] = buffer.DangerousGetRowSpan(y++); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index be03a7e7b..18212ffc7 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken) .ConfigureAwait(false); /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 73763f4ab..4be6731cc 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -175,7 +175,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - using var spectralConverter = new SpectralConverter(this.Configuration, cancellationToken); + using var spectralConverter = new SpectralConverter(this.Configuration); var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, cancellationToken); @@ -185,7 +185,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.InitIptcProfile(); this.InitDerivedMetadataProperties(); - return new Image(this.Configuration, spectralConverter.GetPixelBuffer(), this.Metadata); + return new Image( + this.Configuration, + spectralConverter.GetPixelBuffer(cancellationToken), + this.Metadata); } /// diff --git a/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs b/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs new file mode 100644 index 000000000..33af30434 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs @@ -0,0 +1,194 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Pixel decoding methods for the PBM binary encoding. + /// + internal class BinaryDecoder + { + private static L8 white = new(255); + private static L8 black = new(0); + + /// + /// Decode the specified pixels. + /// + /// The type of pixel to encode to. + /// The configuration. + /// The pixel array to encode into. + /// The stream to read the data from. + /// The ColorType to decode. + /// Data type of the pixles components. + /// + /// Thrown if an invalid combination of setting is requested. + /// + public static void Process(Configuration configuration, Buffer2D pixels, BufferedReadStream stream, PbmColorType colorType, PbmComponentType componentType) + where TPixel : unmanaged, IPixel + { + if (colorType == PbmColorType.Grayscale) + { + if (componentType == PbmComponentType.Byte) + { + ProcessGrayscale(configuration, pixels, stream); + } + else + { + ProcessWideGrayscale(configuration, pixels, stream); + } + } + else if (colorType == PbmColorType.Rgb) + { + if (componentType == PbmComponentType.Byte) + { + ProcessRgb(configuration, pixels, stream); + } + else + { + ProcessWideRgb(configuration, pixels, stream); + } + } + else + { + ProcessBlackAndWhite(configuration, pixels, stream); + } + } + + private static void ProcessGrayscale(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + const int bytesPerPixel = 1; + int width = pixels.Width; + int height = pixels.Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + stream.Read(rowSpan); + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromL8Bytes( + configuration, + rowSpan, + pixelSpan, + width); + } + } + + private static void ProcessWideGrayscale(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + const int bytesPerPixel = 2; + int width = pixels.Width; + int height = pixels.Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + stream.Read(rowSpan); + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromL16Bytes( + configuration, + rowSpan, + pixelSpan, + width); + } + } + + private static void ProcessRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + const int bytesPerPixel = 3; + int width = pixels.Width; + int height = pixels.Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + stream.Read(rowSpan); + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromRgb24Bytes( + configuration, + rowSpan, + pixelSpan, + width); + } + } + + private static void ProcessWideRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + const int bytesPerPixel = 6; + int width = pixels.Width; + int height = pixels.Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + stream.Read(rowSpan); + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromRgb48Bytes( + configuration, + rowSpan, + pixelSpan, + width); + } + } + + private static void ProcessBlackAndWhite(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int height = pixels.Height; + int startBit = 0; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width;) + { + int raw = stream.ReadByte(); + int bit = startBit; + startBit = 0; + for (; bit < 8; bit++) + { + bool bitValue = (raw & (0x80 >> bit)) != 0; + rowSpan[x] = bitValue ? black : white; + x++; + if (x == width) + { + startBit = (bit + 1) & 7; // Round off to below 8. + if (startBit != 0) + { + stream.Seek(-1, System.IO.SeekOrigin.Current); + } + + break; + } + } + } + + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromL8( + configuration, + rowSpan, + pixelSpan); + } + } + } +} diff --git a/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs b/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs new file mode 100644 index 000000000..332ab9b50 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs @@ -0,0 +1,208 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.IO; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Pixel encoding methods for the PBM binary encoding. + /// + internal class BinaryEncoder + { + /// + /// Decode pixels into the PBM binary encoding. + /// + /// The type of input pixel. + /// The configuration. + /// The bytestream to write to. + /// The input image. + /// The ColorType to use. + /// Data type of the pixles components. + /// + /// Thrown if an invalid combination of setting is requested. + /// + public static void WritePixels(Configuration configuration, Stream stream, ImageFrame image, PbmColorType colorType, PbmComponentType componentType) + where TPixel : unmanaged, IPixel + { + if (colorType == PbmColorType.Grayscale) + { + if (componentType == PbmComponentType.Byte) + { + WriteGrayscale(configuration, stream, image); + } + else + { + WriteWideGrayscale(configuration, stream, image); + } + } + else if (colorType == PbmColorType.Rgb) + { + if (componentType == PbmComponentType.Byte) + { + WriteRgb(configuration, stream, image); + } + else + { + WriteWideRgb(configuration, stream, image); + } + } + else + { + WriteBlackAndWhite(configuration, stream, image); + } + } + + private static void WriteGrayscale(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); + + PixelOperations.Instance.ToL8Bytes( + configuration, + pixelSpan, + rowSpan, + width); + + stream.Write(rowSpan); + } + } + + private static void WriteWideGrayscale(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + const int bytesPerPixel = 2; + int width = image.Width; + int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); + + PixelOperations.Instance.ToL16Bytes( + configuration, + pixelSpan, + rowSpan, + width); + + stream.Write(rowSpan); + } + } + + private static void WriteRgb(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + const int bytesPerPixel = 3; + int width = image.Width; + int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); + + PixelOperations.Instance.ToRgb24Bytes( + configuration, + pixelSpan, + rowSpan, + width); + + stream.Write(rowSpan); + } + } + + private static void WriteWideRgb(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + const int bytesPerPixel = 6; + int width = image.Width; + int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); + + PixelOperations.Instance.ToRgb48Bytes( + configuration, + pixelSpan, + rowSpan, + width); + + stream.Write(rowSpan); + } + } + + private static void WriteBlackAndWhite(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + + int previousValue = 0; + int startBit = 0; + for (int y = 0; y < height; y++) + { + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); + + PixelOperations.Instance.ToL8( + configuration, + pixelSpan, + rowSpan); + + for (int x = 0; x < width;) + { + int value = previousValue; + for (int i = startBit; i < 8; i++) + { + if (rowSpan[x].PackedValue < 128) + { + value |= 0x80 >> i; + } + + x++; + if (x == width) + { + previousValue = value; + startBit = (i + 1) & 7; // Round off to below 8. + break; + } + } + + if (startBit == 0) + { + stream.WriteByte((byte)value); + previousValue = 0; + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs new file mode 100644 index 000000000..581d3e592 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.IO; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Extensions methods for . + /// + internal static class BufferedReadStreamExtensions + { + /// + /// Skip over any whitespace or any comments. + /// + public static void SkipWhitespaceAndComments(this BufferedReadStream stream) + { + bool isWhitespace; + do + { + int val = stream.ReadByte(); + + // Comments start with '#' and end at the next new-line. + if (val == 0x23) + { + int innerValue; + do + { + innerValue = stream.ReadByte(); + } + while (innerValue != 0x0a); + + // Continue searching for whitespace. + val = innerValue; + } + + isWhitespace = val is 0x09 or 0x0a or 0x0d or 0x20; + } + while (isWhitespace); + stream.Seek(-1, SeekOrigin.Current); + } + + /// + /// Read a decimal text value. + /// + /// The integer value of the decimal. + public static int ReadDecimal(this BufferedReadStream stream) + { + int value = 0; + while (true) + { + int current = stream.ReadByte() - 0x30; + if ((uint)current > 9) + { + break; + } + + value = (value * 10) + current; + } + + return value; + } + } +} diff --git a/src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs b/src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs new file mode 100644 index 000000000..988d9e560 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Configuration options for use during PBM encoding. + /// + internal interface IPbmEncoderOptions + { + /// + /// Gets the encoding of the pixels. + /// + PbmEncoding? Encoding { get; } + + /// + /// Gets the Color type of the resulting image. + /// + PbmColorType? ColorType { get; } + + /// + /// Gets the Data Type of the pixel components. + /// + PbmComponentType? ComponentType { get; } + } +} diff --git a/src/ImageSharp/Formats/Pbm/MetadataExtensions.cs b/src/ImageSharp/Formats/Pbm/MetadataExtensions.cs new file mode 100644 index 000000000..cce8fb318 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/MetadataExtensions.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Pbm; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class MetadataExtensions + { + /// + /// Gets the pbm format specific metadata for the image. + /// + /// The metadata this method extends. + /// The . + public static PbmMetadata GetPbmMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(PbmFormat.Instance); + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmColorType.cs b/src/ImageSharp/Formats/Pbm/PbmColorType.cs new file mode 100644 index 000000000..827f19344 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmColorType.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Provides enumeration of available PBM color types. + /// + public enum PbmColorType : byte + { + /// + /// PBM + /// + BlackAndWhite = 0, + + /// + /// PGM - Greyscale. Single component. + /// + Grayscale = 1, + + /// + /// PPM - RGB Color. 3 components. + /// + Rgb = 2, + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmComponentType.cs b/src/ImageSharp/Formats/Pbm/PbmComponentType.cs new file mode 100644 index 000000000..26272021c --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmComponentType.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// The data type of the components of the pixels. + /// + public enum PbmComponentType : byte + { + /// + /// Single bit per pixel, exclusively for . + /// + Bit = 0, + + /// + /// 8 bits unsigned integer per component. + /// + Byte = 1, + + /// + /// 16 bits unsigned integer per component. + /// + Short = 2 + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmConfigurationModule.cs b/src/ImageSharp/Formats/Pbm/PbmConfigurationModule.cs new file mode 100644 index 000000000..172bda667 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmConfigurationModule.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Registers the image encoders, decoders and mime type detectors for the Pbm format. + /// + public sealed class PbmConfigurationModule : IConfigurationModule + { + /// + public void Configure(Configuration configuration) + { + configuration.ImageFormatsManager.SetEncoder(PbmFormat.Instance, new PbmEncoder()); + configuration.ImageFormatsManager.SetDecoder(PbmFormat.Instance, new PbmDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new PbmImageFormatDetector()); + } + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmConstants.cs b/src/ImageSharp/Formats/Pbm/PbmConstants.cs new file mode 100644 index 000000000..912ffaf85 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmConstants.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Contains PBM constant values defined in the specification. + /// + internal static class PbmConstants + { + /// + /// The maximum allowable pixel value of a ppm image. + /// + public const ushort MaxLength = 65535; + + /// + /// The list of mimetypes that equate to a ppm. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/x-portable-pixmap", "image/x-portable-anymap" }; + + /// + /// The list of file extensions that equate to a ppm. + /// + public static readonly IEnumerable FileExtensions = new[] { "ppm", "pbm", "pgm" }; + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs new file mode 100644 index 000000000..2eebbb1d9 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs @@ -0,0 +1,79 @@ +// 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.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Image decoder for reading PGM, PBM or PPM bitmaps from a stream. These images are from + /// the family of PNM images. + /// + /// + /// PBM + /// Black and white images. + /// + /// + /// PGM + /// Grayscale images. + /// + /// + /// PPM + /// Color images, with RGB pixels. + /// + /// + /// The specification of these images is found at . + /// + public sealed class PbmDecoder : IImageDecoder, IImageInfoDetector + { + /// + public Image Decode(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(stream, nameof(stream)); + + var decoder = new PbmDecoderCore(configuration); + return decoder.Decode(configuration, stream); + } + + /// + public Image Decode(Configuration configuration, Stream stream) + => this.Decode(configuration, stream); + + /// + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(stream, nameof(stream)); + + var decoder = new PbmDecoderCore(configuration); + return decoder.DecodeAsync(configuration, stream, cancellationToken); + } + + /// + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); + + /// + public IImageInfo Identify(Configuration configuration, Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + + var decoder = new PbmDecoderCore(configuration); + return decoder.Identify(configuration, stream); + } + + /// + public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(stream, nameof(stream)); + + var decoder = new PbmDecoderCore(configuration); + return decoder.IdentifyAsync(configuration, stream, cancellationToken); + } + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs new file mode 100644 index 000000000..749fc0292 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs @@ -0,0 +1,195 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Threading; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Performs the PBM decoding operation. + /// + internal sealed class PbmDecoderCore : IImageDecoderInternals + { + private int maxPixelValue; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + public PbmDecoderCore(Configuration configuration) => this.Configuration = configuration ?? Configuration.Default; + + /// + public Configuration Configuration { get; } + + /// + /// Gets the colortype to use + /// + public PbmColorType ColorType { get; private set; } + + /// + /// Gets the size of the pixel array + /// + public Size PixelSize { get; private set; } + + /// + /// Gets the component data type + /// + public PbmComponentType ComponentType { get; private set; } + + /// + /// Gets the Encoding of pixels + /// + public PbmEncoding Encoding { get; private set; } + + /// + /// Gets the decoded by this decoder instance. + /// + public ImageMetadata Metadata { get; private set; } + + /// + Size IImageDecoderInternals.Dimensions => this.PixelSize; + + private bool NeedsUpscaling => this.ColorType != PbmColorType.BlackAndWhite && this.maxPixelValue is not 255 and not 65535; + + /// + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + this.ProcessHeader(stream); + + var image = new Image(this.Configuration, this.PixelSize.Width, this.PixelSize.Height, this.Metadata); + + Buffer2D pixels = image.GetRootFramePixelBuffer(); + + this.ProcessPixels(stream, pixels); + if (this.NeedsUpscaling) + { + this.ProcessUpscaling(image); + } + + return image; + } + + /// + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + { + this.ProcessHeader(stream); + + // BlackAndWhite pixels are encoded into a byte. + int bitsPerPixel = this.ComponentType == PbmComponentType.Short ? 16 : 8; + return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.PixelSize.Width, this.PixelSize.Height, this.Metadata); + } + + /// + /// Processes the ppm header. + /// + /// The input stream. + private void ProcessHeader(BufferedReadStream stream) + { + Span buffer = stackalloc byte[2]; + + int bytesRead = stream.Read(buffer); + if (bytesRead != 2 || buffer[0] != 'P') + { + throw new InvalidImageContentException("Empty or not an PPM image."); + } + + switch ((char)buffer[1]) + { + case '1': + // Plain PBM format: 1 component per pixel, boolean value ('0' or '1'). + this.ColorType = PbmColorType.BlackAndWhite; + this.Encoding = PbmEncoding.Plain; + break; + case '2': + // Plain PGM format: 1 component per pixel, in decimal text. + this.ColorType = PbmColorType.Grayscale; + this.Encoding = PbmEncoding.Plain; + break; + case '3': + // Plain PPM format: 3 components per pixel, in decimal text. + this.ColorType = PbmColorType.Rgb; + this.Encoding = PbmEncoding.Plain; + break; + case '4': + // Binary PBM format: 1 component per pixel, 8 pixels per byte. + this.ColorType = PbmColorType.BlackAndWhite; + this.Encoding = PbmEncoding.Binary; + break; + case '5': + // Binary PGM format: 1 components per pixel, in binary integers. + this.ColorType = PbmColorType.Grayscale; + this.Encoding = PbmEncoding.Binary; + break; + case '6': + // Binary PPM format: 3 components per pixel, in binary integers. + this.ColorType = PbmColorType.Rgb; + this.Encoding = PbmEncoding.Binary; + break; + case '7': + // PAM image: sequence of images. + // Not implemented yet + default: + throw new InvalidImageContentException("Unknown of not implemented image type encountered."); + } + + stream.SkipWhitespaceAndComments(); + int width = stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + int height = stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + if (this.ColorType != PbmColorType.BlackAndWhite) + { + this.maxPixelValue = stream.ReadDecimal(); + if (this.maxPixelValue > 255) + { + this.ComponentType = PbmComponentType.Short; + } + else + { + this.ComponentType = PbmComponentType.Byte; + } + + stream.SkipWhitespaceAndComments(); + } + else + { + this.ComponentType = PbmComponentType.Bit; + } + + this.PixelSize = new Size(width, height); + this.Metadata = new ImageMetadata(); + PbmMetadata meta = this.Metadata.GetPbmMetadata(); + meta.Encoding = this.Encoding; + meta.ColorType = this.ColorType; + meta.ComponentType = this.ComponentType; + } + + private void ProcessPixels(BufferedReadStream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + if (this.Encoding == PbmEncoding.Binary) + { + BinaryDecoder.Process(this.Configuration, pixels, stream, this.ColorType, this.ComponentType); + } + else + { + PlainDecoder.Process(this.Configuration, pixels, stream, this.ColorType, this.ComponentType); + } + } + + private void ProcessUpscaling(Image image) + where TPixel : unmanaged, IPixel + { + int maxAllocationValue = this.ComponentType == PbmComponentType.Short ? 65535 : 255; + float factor = maxAllocationValue / this.maxPixelValue; + image.Mutate(x => x.Brightness(factor)); + } + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoder.cs b/src/ImageSharp/Formats/Pbm/PbmEncoder.cs new file mode 100644 index 000000000..75d666063 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmEncoder.cs @@ -0,0 +1,69 @@ +// 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.Advanced; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Image encoder for writing an image to a stream as PGM, PBM or PPM bitmap. These images are from + /// the family of PNM images. + /// + /// The PNM formats are a fairly simple image format. They share a plain text header, consisting of: + /// signature, width, height and max_pixel_value only. The pixels follow thereafter and can be in + /// plain text decimals separated by spaces, or binary encoded. + /// + /// + /// PBM + /// Black and white images, with 1 representing black and 0 representing white. + /// + /// + /// PGM + /// Grayscale images, scaling from 0 to max_pixel_value, 0 representing black and max_pixel_value representing white. + /// + /// + /// PPM + /// Color images, with RGB pixels (in that order), with 0 representing black and 2 representing full color. + /// + /// + /// + /// The specification of these images is found at . + /// + public sealed class PbmEncoder : IImageEncoder, IPbmEncoderOptions + { + /// + /// Gets or sets the Encoding of the pixels. + /// + public PbmEncoding? Encoding { get; set; } + + /// + /// Gets or sets the Color type of the resulting image. + /// + public PbmColorType? ColorType { get; set; } + + /// + /// Gets or sets the data type of the pixels components. + /// + public PbmComponentType? ComponentType { get; set; } + + /// + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + var encoder = new PbmEncoderCore(image.GetConfiguration(), this); + encoder.Encode(image, stream); + } + + /// + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + var encoder = new PbmEncoderCore(image.GetConfiguration(), this); + return encoder.EncodeAsync(image, stream, cancellationToken); + } + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs new file mode 100644 index 000000000..9d1f39edf --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs @@ -0,0 +1,187 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Text; +using System.IO; +using System.Threading; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Image encoder for writing an image to a stream as a PGM, PBM, PPM or PAM bitmap. + /// + internal sealed class PbmEncoderCore : IImageEncoderInternals + { + private const byte NewLine = (byte)'\n'; + private const byte Space = (byte)' '; + private const byte P = (byte)'P'; + + /// + /// The global configuration. + /// + private Configuration configuration; + + /// + /// The encoder options. + /// + private readonly IPbmEncoderOptions options; + + /// + /// The encoding for the pixels. + /// + private PbmEncoding encoding; + + /// + /// Gets the Color type of the resulting image. + /// + private PbmColorType colorType; + + /// + /// Gets the maximum pixel value, per component. + /// + private PbmComponentType componentType; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// The encoder options. + public PbmEncoderCore(Configuration configuration, IPbmEncoderOptions options) + { + this.configuration = configuration; + this.options = options; + } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The token to request cancellation. + public void Encode(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + this.DeduceOptions(image); + + byte signature = this.DeduceSignature(); + this.WriteHeader(stream, signature, image.Size()); + + this.WritePixels(stream, image.Frames.RootFrame); + + stream.Flush(); + } + + private void DeduceOptions(Image image) + where TPixel : unmanaged, IPixel + { + this.configuration = image.GetConfiguration(); + PbmMetadata metadata = image.Metadata.GetPbmMetadata(); + this.encoding = this.options.Encoding ?? metadata.Encoding; + this.colorType = this.options.ColorType ?? metadata.ColorType; + if (this.colorType != PbmColorType.BlackAndWhite) + { + this.componentType = this.options.ComponentType ?? metadata.ComponentType; + } + else + { + this.componentType = PbmComponentType.Bit; + } + } + + private byte DeduceSignature() + { + byte signature; + if (this.colorType == PbmColorType.BlackAndWhite) + { + if (this.encoding == PbmEncoding.Plain) + { + signature = (byte)'1'; + } + else + { + signature = (byte)'4'; + } + } + else if (this.colorType == PbmColorType.Grayscale) + { + if (this.encoding == PbmEncoding.Plain) + { + signature = (byte)'2'; + } + else + { + signature = (byte)'5'; + } + } + else + { + // RGB ColorType + if (this.encoding == PbmEncoding.Plain) + { + signature = (byte)'3'; + } + else + { + signature = (byte)'6'; + } + } + + return signature; + } + + private void WriteHeader(Stream stream, byte signature, Size pixelSize) + { + Span buffer = stackalloc byte[128]; + + int written = 3; + buffer[0] = P; + buffer[1] = signature; + buffer[2] = NewLine; + + Utf8Formatter.TryFormat(pixelSize.Width, buffer.Slice(written), out int bytesWritten); + written += bytesWritten; + buffer[written++] = Space; + Utf8Formatter.TryFormat(pixelSize.Height, buffer.Slice(written), out bytesWritten); + written += bytesWritten; + buffer[written++] = NewLine; + + if (this.colorType != PbmColorType.BlackAndWhite) + { + int maxPixelValue = this.componentType == PbmComponentType.Short ? 65535 : 255; + Utf8Formatter.TryFormat(maxPixelValue, buffer.Slice(written), out bytesWritten); + written += bytesWritten; + buffer[written++] = NewLine; + } + + stream.Write(buffer, 0, written); + } + + /// + /// Writes the pixel data to the binary stream. + /// + /// The pixel format. + /// The to write to. + /// + /// The containing pixel data. + /// + private void WritePixels(Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + if (this.encoding == PbmEncoding.Plain) + { + PlainEncoder.WritePixels(this.configuration, stream, image, this.colorType, this.componentType); + } + else + { + BinaryEncoder.WritePixels(this.configuration, stream, image, this.colorType, this.componentType); + } + } + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoding.cs b/src/ImageSharp/Formats/Pbm/PbmEncoding.cs new file mode 100644 index 000000000..be7fb909f --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmEncoding.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Provides enumeration of available PBM encodings. + /// + public enum PbmEncoding : byte + { + /// + /// Plain text decimal encoding. + /// + Plain = 0, + + /// + /// Binary integer encoding. + /// + Binary = 1, + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmFormat.cs b/src/ImageSharp/Formats/Pbm/PbmFormat.cs new file mode 100644 index 000000000..5ffb49652 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmFormat.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Registers the image encoders, decoders and mime type detectors for the PBM format. + /// + public sealed class PbmFormat : IImageFormat + { + private PbmFormat() + { + } + + /// + /// Gets the current instance. + /// + public static PbmFormat Instance { get; } = new(); + + /// + public string Name => "PBM"; + + /// + public string DefaultMimeType => "image/x-portable-pixmap"; + + /// + public IEnumerable MimeTypes => PbmConstants.MimeTypes; + + /// + public IEnumerable FileExtensions => PbmConstants.FileExtensions; + + /// + public PbmMetadata CreateDefaultFormatMetadata() => new(); + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs b/src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs new file mode 100644 index 000000000..15bacc4de --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Detects Pbm file headers. + /// + public sealed class PbmImageFormatDetector : IImageFormatDetector + { + private const byte P = (byte)'P'; + private const byte Zero = (byte)'0'; + private const byte Seven = (byte)'7'; + + /// + public int HeaderSize => 2; + + /// + public IImageFormat DetectFormat(ReadOnlySpan header) => this.IsSupportedFileFormat(header) ? PbmFormat.Instance : null; + + private bool IsSupportedFileFormat(ReadOnlySpan header) + { +#pragma warning disable SA1131 // Use readable conditions + if (1 < (uint)header.Length) +#pragma warning restore SA1131 // Use readable conditions + { + // Signature should be between P1 and P6. + return header[0] == P && (uint)(header[1] - Zero - 1) < (Seven - Zero - 1); + } + + return false; + } + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmMetadata.cs b/src/ImageSharp/Formats/Pbm/PbmMetadata.cs new file mode 100644 index 000000000..a00ae46de --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmMetadata.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Provides PBM specific metadata information for the image. + /// + public class PbmMetadata : IDeepCloneable + { + /// + /// Initializes a new instance of the class. + /// + public PbmMetadata() => + this.ComponentType = this.ColorType == PbmColorType.BlackAndWhite ? PbmComponentType.Bit : PbmComponentType.Byte; + + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private PbmMetadata(PbmMetadata other) + { + this.Encoding = other.Encoding; + this.ColorType = other.ColorType; + this.ComponentType = other.ComponentType; + } + + /// + /// Gets or sets the encoding of the pixels. + /// + public PbmEncoding Encoding { get; set; } = PbmEncoding.Plain; + + /// + /// Gets or sets the color type. + /// + public PbmColorType ColorType { get; set; } = PbmColorType.Grayscale; + + /// + /// Gets or sets the data type of the pixel components. + /// + public PbmComponentType ComponentType { get; set; } + + /// + public IDeepCloneable DeepClone() => new PbmMetadata(this); + } +} diff --git a/src/ImageSharp/Formats/Pbm/PlainDecoder.cs b/src/ImageSharp/Formats/Pbm/PlainDecoder.cs new file mode 100644 index 000000000..aeb527dd2 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PlainDecoder.cs @@ -0,0 +1,198 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Pixel decoding methods for the PBM plain encoding. + /// + internal class PlainDecoder + { + private static readonly L8 White = new(255); + private static readonly L8 Black = new(0); + + /// + /// Decode the specified pixels. + /// + /// The type of pixel to encode to. + /// The configuration. + /// The pixel array to encode into. + /// The stream to read the data from. + /// The ColorType to decode. + /// Data type of the pixles components. + public static void Process(Configuration configuration, Buffer2D pixels, BufferedReadStream stream, PbmColorType colorType, PbmComponentType componentType) + where TPixel : unmanaged, IPixel + { + if (colorType == PbmColorType.Grayscale) + { + if (componentType == PbmComponentType.Byte) + { + ProcessGrayscale(configuration, pixels, stream); + } + else + { + ProcessWideGrayscale(configuration, pixels, stream); + } + } + else if (colorType == PbmColorType.Rgb) + { + if (componentType == PbmComponentType.Byte) + { + ProcessRgb(configuration, pixels, stream); + } + else + { + ProcessWideRgb(configuration, pixels, stream); + } + } + else + { + ProcessBlackAndWhite(configuration, pixels, stream); + } + } + + private static void ProcessGrayscale(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int height = pixels.Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + byte value = (byte)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + rowSpan[x] = new L8(value); + } + + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromL8( + configuration, + rowSpan, + pixelSpan); + } + } + + private static void ProcessWideGrayscale(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int height = pixels.Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + ushort value = (ushort)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + rowSpan[x] = new L16(value); + } + + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromL16( + configuration, + rowSpan, + pixelSpan); + } + } + + private static void ProcessRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int height = pixels.Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + byte red = (byte)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + byte green = (byte)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + byte blue = (byte)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + rowSpan[x] = new Rgb24(red, green, blue); + } + + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromRgb24( + configuration, + rowSpan, + pixelSpan); + } + } + + private static void ProcessWideRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int height = pixels.Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + ushort red = (ushort)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + ushort green = (ushort)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + ushort blue = (ushort)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + rowSpan[x] = new Rgb48(red, green, blue); + } + + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromRgb48( + configuration, + rowSpan, + pixelSpan); + } + } + + private static void ProcessBlackAndWhite(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int height = pixels.Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + int value = stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + rowSpan[x] = value == 0 ? White : Black; + } + + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromL8( + configuration, + rowSpan, + pixelSpan); + } + } + } +} diff --git a/src/ImageSharp/Formats/Pbm/PlainEncoder.cs b/src/ImageSharp/Formats/Pbm/PlainEncoder.cs new file mode 100644 index 000000000..a64ae38a7 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PlainEncoder.cs @@ -0,0 +1,251 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Buffers.Text; +using System.IO; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Pixel encoding methods for the PBM plain encoding. + /// + internal class PlainEncoder + { + private const byte NewLine = 0x0a; + private const byte Space = 0x20; + private const byte Zero = 0x30; + private const byte One = 0x31; + + private const int MaxCharsPerPixelBlackAndWhite = 2; + private const int MaxCharsPerPixelGrayscale = 4; + private const int MaxCharsPerPixelGrayscaleWide = 6; + private const int MaxCharsPerPixelRgb = 4 * 3; + private const int MaxCharsPerPixelRgbWide = 6 * 3; + + private static readonly StandardFormat DecimalFormat = StandardFormat.Parse("D"); + + /// + /// Decode pixels into the PBM plain encoding. + /// + /// The type of input pixel. + /// The configuration. + /// The bytestream to write to. + /// The input image. + /// The ColorType to use. + /// Data type of the pixles components. + public static void WritePixels(Configuration configuration, Stream stream, ImageFrame image, PbmColorType colorType, PbmComponentType componentType) + where TPixel : unmanaged, IPixel + { + if (colorType == PbmColorType.Grayscale) + { + if (componentType == PbmComponentType.Byte) + { + WriteGrayscale(configuration, stream, image); + } + else + { + WriteWideGrayscale(configuration, stream, image); + } + } + else if (colorType == PbmColorType.Rgb) + { + if (componentType == PbmComponentType.Byte) + { + WriteRgb(configuration, stream, image); + } + else + { + WriteWideRgb(configuration, stream, image); + } + } + else + { + WriteBlackAndWhite(configuration, stream, image); + } + + // Write EOF indicator, as some encoders expect it. + stream.WriteByte(Space); + } + + private static void WriteGrayscale(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelGrayscale); + Span plainSpan = plainMemory.GetSpan(); + + for (int y = 0; y < height; y++) + { + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); + PixelOperations.Instance.ToL8( + configuration, + pixelSpan, + rowSpan); + + int written = 0; + for (int x = 0; x < width; x++) + { + Utf8Formatter.TryFormat(rowSpan[x].PackedValue, plainSpan.Slice(written), out int bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; + } + + plainSpan[written - 1] = NewLine; + stream.Write(plainSpan, 0, written); + } + } + + private static void WriteWideGrayscale(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelGrayscaleWide); + Span plainSpan = plainMemory.GetSpan(); + + for (int y = 0; y < height; y++) + { + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); + PixelOperations.Instance.ToL16( + configuration, + pixelSpan, + rowSpan); + + int written = 0; + for (int x = 0; x < width; x++) + { + Utf8Formatter.TryFormat(rowSpan[x].PackedValue, plainSpan.Slice(written), out int bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; + } + + plainSpan[written - 1] = NewLine; + stream.Write(plainSpan, 0, written); + } + } + + private static void WriteRgb(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelRgb); + Span plainSpan = plainMemory.GetSpan(); + + for (int y = 0; y < height; y++) + { + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); + PixelOperations.Instance.ToRgb24( + configuration, + pixelSpan, + rowSpan); + + int written = 0; + for (int x = 0; x < width; x++) + { + Utf8Formatter.TryFormat(rowSpan[x].R, plainSpan.Slice(written), out int bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; + Utf8Formatter.TryFormat(rowSpan[x].G, plainSpan.Slice(written), out bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; + Utf8Formatter.TryFormat(rowSpan[x].B, plainSpan.Slice(written), out bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; + } + + plainSpan[written - 1] = NewLine; + stream.Write(plainSpan, 0, written); + } + } + + private static void WriteWideRgb(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelRgbWide); + Span plainSpan = plainMemory.GetSpan(); + + for (int y = 0; y < height; y++) + { + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); + PixelOperations.Instance.ToRgb48( + configuration, + pixelSpan, + rowSpan); + + int written = 0; + for (int x = 0; x < width; x++) + { + Utf8Formatter.TryFormat(rowSpan[x].R, plainSpan.Slice(written), out int bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; + Utf8Formatter.TryFormat(rowSpan[x].G, plainSpan.Slice(written), out bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; + Utf8Formatter.TryFormat(rowSpan[x].B, plainSpan.Slice(written), out bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; + } + + plainSpan[written - 1] = NewLine; + stream.Write(plainSpan, 0, written); + } + } + + private static void WriteBlackAndWhite(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelBlackAndWhite); + Span plainSpan = plainMemory.GetSpan(); + + for (int y = 0; y < height; y++) + { + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); + PixelOperations.Instance.ToL8( + configuration, + pixelSpan, + rowSpan); + + int written = 0; + for (int x = 0; x < width; x++) + { + byte value = (rowSpan[x].PackedValue < 128) ? One : Zero; + plainSpan[written++] = value; + plainSpan[written++] = Space; + } + + plainSpan[written - 1] = NewLine; + stream.Write(plainSpan, 0, written); + } + } + } +} diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index 479c24b7e..04e70c51d 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -20,37 +20,137 @@ namespace SixLabors.ImageSharp.Formats.Png public Image Decode(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel { - var decoder = new PngDecoderCore(configuration, this); + PngDecoderCore decoder = new(configuration, this); return decoder.Decode(configuration, stream); } /// - public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + public Image Decode(Configuration configuration, Stream stream) + { + PngDecoderCore decoder = new(configuration, true); + IImageInfo info = decoder.Identify(configuration, stream); + stream.Position = 0; + + PngMetadata meta = info.Metadata.GetPngMetadata(); + PngColorType color = meta.ColorType.GetValueOrDefault(); + PngBitDepth bits = meta.BitDepth.GetValueOrDefault(); + switch (color) + { + case PngColorType.Grayscale: + if (bits == PngBitDepth.Bit16) + { + return !meta.HasTransparency + ? this.Decode(configuration, stream) + : this.Decode(configuration, stream); + } + + return !meta.HasTransparency + ? this.Decode(configuration, stream) + : this.Decode(configuration, stream); + + case PngColorType.Rgb: + if (bits == PngBitDepth.Bit16) + { + return !meta.HasTransparency + ? this.Decode(configuration, stream) + : this.Decode(configuration, stream); + } + + return !meta.HasTransparency + ? this.Decode(configuration, stream) + : this.Decode(configuration, stream); + + case PngColorType.Palette: + return this.Decode(configuration, stream); + + case PngColorType.GrayscaleWithAlpha: + return (bits == PngBitDepth.Bit16) + ? this.Decode(configuration, stream) + : this.Decode(configuration, stream); + + case PngColorType.RgbWithAlpha: + return (bits == PngBitDepth.Bit16) + ? this.Decode(configuration, stream) + : this.Decode(configuration, stream); + + default: + return this.Decode(configuration, stream); + } + } /// public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - var decoder = new PngDecoderCore(configuration, this); + PngDecoderCore decoder = new(configuration, this); return decoder.DecodeAsync(configuration, stream, cancellationToken); } /// public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken) - .ConfigureAwait(false); + { + PngDecoderCore decoder = new(configuration, true); + IImageInfo info = await decoder.IdentifyAsync(configuration, stream, cancellationToken).ConfigureAwait(false); + stream.Position = 0; + + PngMetadata meta = info.Metadata.GetPngMetadata(); + PngColorType color = meta.ColorType.GetValueOrDefault(); + PngBitDepth bits = meta.BitDepth.GetValueOrDefault(); + switch (color) + { + case PngColorType.Grayscale: + if (bits == PngBitDepth.Bit16) + { + return !meta.HasTransparency + ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) + : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); + } + + return !meta.HasTransparency + ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) + : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); + + case PngColorType.Rgb: + if (bits == PngBitDepth.Bit16) + { + return !meta.HasTransparency + ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) + : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); + } + + return !meta.HasTransparency + ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) + : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); + + case PngColorType.Palette: + return await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); + + case PngColorType.GrayscaleWithAlpha: + return (bits == PngBitDepth.Bit16) + ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) + : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); + + case PngColorType.RgbWithAlpha: + return (bits == PngBitDepth.Bit16) + ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) + : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); + + default: + return await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); + } + } /// public IImageInfo Identify(Configuration configuration, Stream stream) { - var decoder = new PngDecoderCore(configuration, this); + PngDecoderCore decoder = new(configuration, this); return decoder.Identify(configuration, stream); } /// public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) { - var decoder = new PngDecoderCore(configuration, this); + PngDecoderCore decoder = new(configuration, this); return decoder.IdentifyAsync(configuration, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index cf3cd7eb1..ffaa9d567 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -37,6 +37,11 @@ namespace SixLabors.ImageSharp.Formats.Png /// private readonly bool ignoreMetadata; + /// + /// Gets or sets a value indicating whether to read the IHDR and tRNS chunks only. + /// + private readonly bool colorMetadataOnly; + /// /// Used the manage memory allocations. /// @@ -77,11 +82,6 @@ namespace SixLabors.ImageSharp.Formats.Png /// private byte[] paletteAlpha; - /// - /// A value indicating whether the end chunk has been reached. - /// - private bool isEndChunkReached; - /// /// Previous scanline processed. /// @@ -124,13 +124,21 @@ namespace SixLabors.ImageSharp.Formats.Png this.ignoreMetadata = options.IgnoreMetadata; } + internal PngDecoderCore(Configuration configuration, bool colorMetadataOnly) + { + this.Configuration = configuration ?? Configuration.Default; + this.memoryAllocator = this.Configuration.MemoryAllocator; + this.colorMetadataOnly = colorMetadataOnly; + this.ignoreMetadata = true; + } + /// public Configuration Configuration { get; } /// /// Gets the dimensions of the image. /// - public Size Dimensions => new Size(this.header.Width, this.header.Height); + public Size Dimensions => new(this.header.Width, this.header.Height); /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) @@ -143,7 +151,7 @@ namespace SixLabors.ImageSharp.Formats.Png Image image = null; try { - while (!this.isEndChunkReached && this.TryReadChunk(out PngChunk chunk)) + while (this.TryReadChunk(out PngChunk chunk)) { try { @@ -168,12 +176,12 @@ namespace SixLabors.ImageSharp.Formats.Png break; case PngChunkType.Palette: - var pal = new byte[chunk.Length]; + byte[] pal = new byte[chunk.Length]; chunk.Data.GetSpan().CopyTo(pal); this.palette = pal; break; case PngChunkType.Transparency: - var alpha = new byte[chunk.Length]; + byte[] alpha = new byte[chunk.Length]; chunk.Data.GetSpan().CopyTo(alpha); this.paletteAlpha = alpha; this.AssignTransparentMarkers(alpha, pngMetadata); @@ -190,15 +198,14 @@ namespace SixLabors.ImageSharp.Formats.Png case PngChunkType.Exif: if (!this.ignoreMetadata) { - var exifData = new byte[chunk.Length]; + byte[] exifData = new byte[chunk.Length]; chunk.Data.GetSpan().CopyTo(exifData); metadata.ExifProfile = new ExifProfile(exifData); } break; case PngChunkType.End: - this.isEndChunkReached = true; - break; + goto EOF; case PngChunkType.ProprietaryApple: PngThrowHelper.ThrowInvalidChunkType("Proprietary Apple PNG detected! This PNG file is not conform to the specification and cannot be decoded."); break; @@ -210,6 +217,7 @@ namespace SixLabors.ImageSharp.Formats.Png } } + EOF: if (image is null) { PngThrowHelper.ThrowNoData(); @@ -233,7 +241,7 @@ namespace SixLabors.ImageSharp.Formats.Png this.currentStream.Skip(8); try { - while (!this.isEndChunkReached && this.TryReadChunk(out PngChunk chunk)) + while (this.TryReadChunk(out PngChunk chunk)) { try { @@ -243,35 +251,89 @@ namespace SixLabors.ImageSharp.Formats.Png this.ReadHeaderChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.Physical: + if (this.colorMetadataOnly) + { + this.SkipChunkDataAndCrc(chunk); + break; + } + this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan()); break; case PngChunkType.Gamma: + if (this.colorMetadataOnly) + { + this.SkipChunkDataAndCrc(chunk); + break; + } + this.ReadGammaChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.Data: + + // Spec says tRNS must be before IDAT so safe to exit. + if (this.colorMetadataOnly) + { + goto EOF; + } + this.SkipChunkDataAndCrc(chunk); + break; + case PngChunkType.Transparency: + byte[] alpha = new byte[chunk.Length]; + chunk.Data.GetSpan().CopyTo(alpha); + this.paletteAlpha = alpha; + this.AssignTransparentMarkers(alpha, pngMetadata); + + if (this.colorMetadataOnly) + { + goto EOF; + } + break; case PngChunkType.Text: + if (this.colorMetadataOnly) + { + this.SkipChunkDataAndCrc(chunk); + break; + } + this.ReadTextChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.CompressedText: + if (this.colorMetadataOnly) + { + this.SkipChunkDataAndCrc(chunk); + break; + } + this.ReadCompressedTextChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.InternationalText: + if (this.colorMetadataOnly) + { + this.SkipChunkDataAndCrc(chunk); + break; + } + this.ReadInternationalTextChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.Exif: + if (this.colorMetadataOnly) + { + this.SkipChunkDataAndCrc(chunk); + break; + } + if (!this.ignoreMetadata) { - var exifData = new byte[chunk.Length]; + byte[] exifData = new byte[chunk.Length]; chunk.Data.GetSpan().CopyTo(exifData); metadata.ExifProfile = new ExifProfile(exifData); } break; case PngChunkType.End: - this.isEndChunkReached = true; - break; + goto EOF; } } finally @@ -279,19 +341,20 @@ namespace SixLabors.ImageSharp.Formats.Png chunk.Data?.Dispose(); // Data is rented in ReadChunkData() } } + + EOF: + if (this.header.Width == 0 && this.header.Height == 0) + { + PngThrowHelper.ThrowNoHeader(); + } + + return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata); } finally { this.scanline?.Dispose(); this.previousScanline?.Dispose(); } - - if (this.header.Width == 0 && this.header.Height == 0) - { - PngThrowHelper.ThrowNoHeader(); - } - - return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata); } /// @@ -364,11 +427,10 @@ namespace SixLabors.ImageSharp.Formats.Png /// The metadata to read to. /// The data containing physical data. private void ReadGammaChunk(PngMetadata pngMetadata, ReadOnlySpan data) - { + // The value is encoded as a 4-byte unsigned integer, representing gamma times 100000. // For example, a gamma of 1/2.2 would be stored as 45455. - pngMetadata.Gamma = BinaryPrimitives.ReadUInt32BigEndian(data) / 100_000F; - } + => pngMetadata.Gamma = BinaryPrimitives.ReadUInt32BigEndian(data) * 1e-5F; /// /// Initializes the image and various buffers needed for processing @@ -477,19 +539,17 @@ namespace SixLabors.ImageSharp.Formats.Png private void ReadScanlines(PngChunk chunk, ImageFrame image, PngMetadata pngMetadata) where TPixel : unmanaged, IPixel { - using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk)) - { - deframeStream.AllocateNewBytes(chunk.Length, true); - DeflateStream dataStream = deframeStream.CompressedStream; + using var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk); + deframeStream.AllocateNewBytes(chunk.Length, true); + DeflateStream dataStream = deframeStream.CompressedStream; - if (this.header.InterlaceMethod == PngInterlaceMode.Adam7) - { - this.DecodeInterlacedPixelData(dataStream, image, pngMetadata); - } - else - { - this.DecodePixelData(dataStream, image, pngMetadata); - } + if (this.header.InterlaceMethod == PngInterlaceMode.Adam7) + { + this.DecodeInterlacedPixelData(dataStream, image, pngMetadata); + } + else + { + this.DecodePixelData(dataStream, image, pngMetadata); } } @@ -565,6 +625,7 @@ namespace SixLabors.ImageSharp.Formats.Png { int pass = 0; int width = this.header.Width; + Buffer2D imageBuffer = image.PixelBuffer; while (true) { int numColumns = Adam7.ComputeColumns(width, pass); @@ -623,7 +684,7 @@ namespace SixLabors.ImageSharp.Formats.Png break; } - Span rowSpan = image.GetPixelRowSpan(this.currentRow); + Span rowSpan = imageBuffer.DangerousGetRowSpan(this.currentRow); this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, pngMetadata, Adam7.FirstColumn[pass], Adam7.ColumnIncrement[pass]); this.SwapScanlineBuffers(); @@ -656,7 +717,7 @@ namespace SixLabors.ImageSharp.Formats.Png private void ProcessDefilteredScanline(ReadOnlySpan defilteredScanline, ImageFrame pixels, PngMetadata pngMetadata) where TPixel : unmanaged, IPixel { - Span rowSpan = pixels.GetPixelRowSpan(this.currentRow); + Span rowSpan = pixels.PixelBuffer.DangerousGetRowSpan(this.currentRow); // Trim the first marker byte from the buffer ReadOnlySpan trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1); @@ -918,7 +979,7 @@ namespace SixLabors.ImageSharp.Formats.Png int zeroIndex = data.IndexOf((byte)0); // Keywords are restricted to 1 to 79 bytes in length. - if (zeroIndex < PngConstants.MinTextKeywordLength || zeroIndex > PngConstants.MaxTextKeywordLength) + if (zeroIndex is < PngConstants.MinTextKeywordLength or > PngConstants.MaxTextKeywordLength) { return; } @@ -1145,8 +1206,10 @@ namespace SixLabors.ImageSharp.Formats.Png PngChunkType type = this.ReadChunkType(); - // NOTE: Reading the chunk data is the responsible of the caller - if (type == PngChunkType.Data) + // NOTE: Reading the Data chunk is the responsible of the caller + // If we're reading color metadata only we're only interested in the IHDR and tRNS chunks. + // We can skip all other chunk data in the stream for better performance. + if (type == PngChunkType.Data || (this.colorMetadataOnly && type != PngChunkType.Header && type != PngChunkType.Transparency)) { chunk = new PngChunk(length, type); diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index f10db7a6c..5e067aba5 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -163,23 +163,25 @@ namespace SixLabors.ImageSharp.Formats.Png /// The type of the pixel. /// The cloned image where the transparent pixels will be changed. private static void ClearTransparentPixels(Image image) - where TPixel : unmanaged, IPixel - { - Rgba32 rgba32 = default; - for (int y = 0; y < image.Height; y++) + where TPixel : unmanaged, IPixel => + image.ProcessPixelRows(accessor => { - Span span = image.GetPixelRowSpan(y); - for (int x = 0; x < image.Width; x++) + Rgba32 rgba32 = default; + Rgba32 transparent = Color.Transparent; + for (int y = 0; y < accessor.Height; y++) { - span[x].ToRgba32(ref rgba32); - - if (rgba32.A == 0) + Span span = accessor.GetRowSpan(y); + for (int x = 0; x < accessor.Width; x++) { - span[x].FromRgba32(Color.Transparent); + span[x].ToRgba32(ref rgba32); + + if (rgba32.A == 0) + { + span[x].FromRgba32(transparent); + } } } - } - } + }); /// /// Creates the quantized image and sets calculates and sets the bit depth. @@ -391,11 +393,11 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.bitDepth < 8) { - PngEncoderHelpers.ScaleDownFrom8BitArray(quantized.GetPixelRowSpan(row), this.currentScanline.GetSpan(), this.bitDepth); + PngEncoderHelpers.ScaleDownFrom8BitArray(quantized.DangerousGetRowSpan(row), this.currentScanline.GetSpan(), this.bitDepth); } else { - quantized.GetPixelRowSpan(row).CopyTo(this.currentScanline.GetSpan()); + quantized.DangerousGetRowSpan(row).CopyTo(this.currentScanline.GetSpan()); } break; @@ -914,27 +916,31 @@ namespace SixLabors.ImageSharp.Formats.Png using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); - Span filter = filterBuffer.GetSpan(); - Span attempt = attemptBuffer.GetSpan(); - for (int y = 0; y < this.height; y++) + pixels.ProcessPixelRows(accessor => { - this.CollectAndFilterPixelRow(pixels.GetPixelRowSpan(y), ref filter, ref attempt, quantized, y); - deflateStream.Write(filter); - this.SwapScanlineBuffers(); - } + Span filter = filterBuffer.GetSpan(); + Span attempt = attemptBuffer.GetSpan(); + for (int y = 0; y < this.height; y++) + { + this.CollectAndFilterPixelRow(accessor.GetRowSpan(y), ref filter, ref attempt, quantized, y); + deflateStream.Write(filter); + this.SwapScanlineBuffers(); + } + }); } /// /// Interlaced encoding the pixels. /// /// The type of the pixel. - /// The pixels. + /// The image. /// The deflate stream. - private void EncodeAdam7Pixels(Image pixels, ZlibDeflateStream deflateStream) + private void EncodeAdam7Pixels(Image image, ZlibDeflateStream deflateStream) where TPixel : unmanaged, IPixel { - int width = pixels.Width; - int height = pixels.Height; + int width = image.Width; + int height = image.Height; + Buffer2D pixelBuffer = image.Frames.RootFrame.PixelBuffer; for (int pass = 0; pass < 7; pass++) { int startRow = Adam7.FirstRow[pass]; @@ -959,7 +965,7 @@ namespace SixLabors.ImageSharp.Formats.Png for (int row = startRow; row < height; row += Adam7.RowIncrement[pass]) { // Collect pixel data - Span srcRow = pixels.GetPixelRowSpan(row); + Span srcRow = pixelBuffer.DangerousGetRowSpan(row); for (int col = startCol, i = 0; col < width; col += Adam7.ColumnIncrement[pass]) { block[i++] = srcRow[col]; @@ -1014,7 +1020,7 @@ namespace SixLabors.ImageSharp.Formats.Png row += Adam7.RowIncrement[pass]) { // Collect data - ReadOnlySpan srcRow = quantized.GetPixelRowSpan(row); + ReadOnlySpan srcRow = quantized.DangerousGetRowSpan(row); for (int col = startCol, i = 0; col < width; col += Adam7.ColumnIncrement[pass]) diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index 8f9786140..d101ccd94 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -234,7 +234,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.DangerousGetRowSpan(newY); switch (colorMapPixelSizeInBytes) { @@ -318,7 +318,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.DangerousGetRowSpan(newY); int rowStartIdx = y * width * bytesPerPixel; for (int x = 0; x < width; x++) { @@ -364,7 +364,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); - Span pixelSpan = pixels.GetRowSpan(newY); + Span pixelSpan = pixels.DangerousGetRowSpan(newY); for (int x = width - 1; x >= 0; x--) { this.ReadL8Pixel(color, x, pixelSpan); @@ -412,7 +412,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); - Span pixelSpan = pixels.GetRowSpan(newY); + Span pixelSpan = pixels.DangerousGetRowSpan(newY); if (invertX) { @@ -479,7 +479,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); - Span pixelSpan = pixels.GetRowSpan(newY); + Span pixelSpan = pixels.DangerousGetRowSpan(newY); for (int x = width - 1; x >= 0; x--) { this.ReadBgr24Pixel(color, x, pixelSpan); @@ -548,7 +548,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.DangerousGetRowSpan(newY); if (invertX) { for (int x = width - 1; x >= 0; x--) @@ -587,7 +587,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.DangerousGetRowSpan(newY); int rowStartIdx = y * width * bytesPerPixel; for (int x = 0; x < width; x++) { @@ -654,7 +654,7 @@ namespace SixLabors.ImageSharp.Formats.Tga where TPixel : unmanaged, IPixel { this.currentStream.Read(row); - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.FromL8Bytes(this.Configuration, row, pixelSpan, width); } @@ -681,7 +681,7 @@ namespace SixLabors.ImageSharp.Formats.Tga where TPixel : unmanaged, IPixel { this.currentStream.Read(row); - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.FromBgr24Bytes(this.Configuration, row, pixelSpan, width); } @@ -700,7 +700,7 @@ namespace SixLabors.ImageSharp.Formats.Tga where TPixel : unmanaged, IPixel { this.currentStream.Read(row); - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.FromBgra32Bytes(this.Configuration, row, pixelSpan, width); } diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs index 4bf4ca60a..1a1260a58 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -276,7 +276,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.ToL8Bytes( this.configuration, pixelSpan, @@ -300,7 +300,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.ToBgra5551Bytes( this.configuration, pixelSpan, @@ -324,7 +324,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.ToBgr24Bytes( this.configuration, pixelSpan, @@ -348,7 +348,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.ToBgra32Bytes( this.configuration, pixelSpan, diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs index 225036f90..c240c06ef 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs @@ -43,15 +43,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors } int size = (int)this.memoryStream.Position; - -#if !NETSTANDARD1_3 byte[] buffer = this.memoryStream.GetBuffer(); this.Output.Write(buffer, 0, size); -#else - this.memoryStream.SetLength(size); - this.memoryStream.Position = 0; - this.memoryStream.CopyTo(this.Output); -#endif } /// diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs index 9a0607584..ce7820ccf 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs @@ -55,17 +55,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors { using var jpegDecoder = new JpegDecoderCore(this.configuration, new JpegDecoder()); - // TODO: Should we pass through the CancellationToken from the tiff decoder? // If the PhotometricInterpretation is YCbCr we explicitly assume the JPEG data is in RGB color space. // There seems no other way to determine that the JPEG data is RGB colorspace (no APP14 marker, componentId's are not RGB). using SpectralConverter spectralConverter = this.photometricInterpretation == TiffPhotometricInterpretation.YCbCr ? - new RgbJpegSpectralConverter(this.configuration, CancellationToken.None) : new SpectralConverter(this.configuration, CancellationToken.None); + new RgbJpegSpectralConverter(this.configuration) : new SpectralConverter(this.configuration); var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, CancellationToken.None); jpegDecoder.LoadTables(this.jpegTables, scanDecoder); scanDecoder.ResetInterval = 0; jpegDecoder.ParseStream(stream, scanDecoder, CancellationToken.None); - CopyImageBytesToBuffer(buffer, spectralConverter.GetPixelBuffer()); + // TODO: Should we pass through the CancellationToken from the tiff decoder? + CopyImageBytesToBuffer(buffer, spectralConverter.GetPixelBuffer(CancellationToken.None)); } else { @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors int offset = 0; for (int y = 0; y < pixelBuffer.Height; y++) { - Span pixelRowSpan = pixelBuffer.GetRowSpan(y); + Span pixelRowSpan = pixelBuffer.DangerousGetRowSpan(y); Span rgbBytes = MemoryMarshal.AsBytes(pixelRowSpan); rgbBytes.CopyTo(buffer.Slice(offset)); offset += rgbBytes.Length; diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs index aefec7fa3..001480542 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs @@ -21,13 +21,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// This Spectral converter will always convert the pixel data to RGB color. /// /// The configuration. - /// The cancellation token. - public RgbJpegSpectralConverter(Configuration configuration, CancellationToken cancellationToken) - : base(configuration, cancellationToken) + public RgbJpegSpectralConverter(Configuration configuration) + : base(configuration) { } /// - protected override JpegColorConverter GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverter.GetConverter(JpegColorSpace.RGB, frame.Precision); + protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverterBase.GetConverter(JpegColorSpace.RGB, frame.Precision); } } diff --git a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs index 02035265d..cce1d278c 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs @@ -15,6 +15,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// internal class DirectoryReader { + private const int DirectoryMax = 65534; + private readonly Stream stream; private readonly MemoryAllocator allocator; @@ -58,7 +60,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff { return ByteOrder.LittleEndian; } - else if (headerBytes[0] == TiffConstants.ByteOrderBigEndian && headerBytes[1] == TiffConstants.ByteOrderBigEndian) + + if (headerBytes[0] == TiffConstants.ByteOrderBigEndian && headerBytes[1] == TiffConstants.ByteOrderBigEndian) { return ByteOrder.BigEndian; } @@ -87,6 +90,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff this.nextIfdOffset = reader.NextIfdOffset; readers.Add(reader); + + if (readers.Count >= DirectoryMax) + { + TiffThrowHelper.ThrowImageFormatException("TIFF image contains too many directories"); + } } var list = new List(readers.Count); diff --git a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs index 58b042e84..922b7bf07 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff : base(stream, allocator) => this.IsBigEndian = byteOrder == ByteOrder.BigEndian; - public List Values { get; } = new List(); + public List Values { get; } = new(); public ulong NextIfdOffset { get; private set; } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs index 459506843..5d910d16e 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { for (int x = 0; x < pixelRow.Length; x++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs index ec07abd5c..4cda95480 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { for (int x = 0; x < pixelRow.Length; x++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs index ff34a29eb..ee9bf8a9c 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { for (int x = 0; x < pixelRow.Length; x++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs index f54a79484..7367a78e3 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { for (int x = 0; x < pixelRow.Length; x++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs index f62cf2952..c06239a4d 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); int byteCount = pixelRow.Length; PixelOperations.Instance.FromL8Bytes( this.configuration, diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs index 9956db523..a40fa7667 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); for (int x = 0; x < pixelRow.Length; x++) { int value = bitReader.ReadBits(this.bitsPerSample0); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs index b392fe1a3..29e03c6c6 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); for (int x = 0; x < pixelRow.Length; x++) { int index = bitReader.ReadBits(this.bitsPerSample0); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs index e5d8c8da2..a4d725bcf 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs index 9a6d4631a..1c61b0991 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { for (int x = 0; x < pixelRow.Length; x++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs index 3be0540a0..985ffeb18 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation Span bufferSpan = buffer.AsSpan(bufferStartIdx); for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs index 9c3e57e2a..ac4435db6 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { for (int x = 0; x < pixelRow.Length; x++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs index e2ba085e1..bf1e65e1c 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs index a7432549c..cdc6942bd 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { for (int x = 0; x < pixelRow.Length; x++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs index daad50e98..3dfffe0ce 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation var bgra = default(Bgra4444); for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y); + Span pixelRow = pixels.DangerousGetRowSpan(y); for (int x = left; x < left + width; x += 2) { diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs index 2a86eb2ee..1b5432c28 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); int byteCount = pixelRow.Length * 3; PixelOperations.Instance.FromRgb24Bytes( this.configuration, diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs index f3f27d5c4..4dc3295a4 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs index b442c4ae4..54466e05b 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs @@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); for (int x = 0; x < pixelRow.Length; x++) { float r = rBitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs index 1377598cc..4a887c426 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); for (int x = 0; x < pixelRow.Length; x++) { float r = bitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs index 18b5300b2..038281c99 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { for (int x = 0; x < pixelRow.Length; x++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs index 10182f250..807023b6b 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { for (int x = 0; x < pixelRow.Length; x++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs index d532247fe..71323c7ba 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { for (int x = 0; x < pixelRow.Length; x++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs index ef62b4f44..e433956f0 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { for (int x = 0; x < pixelRow.Length; x++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs index 15ebed58f..8945e55f2 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation var l8 = default(L8); for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); for (int x = 0; x < pixelRow.Length; x++) { byte intensity = (byte)(byte.MaxValue - data[offset++]); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs index 912955964..d692fc789 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); for (int x = 0; x < pixelRow.Length; x++) { int value = bitReader.ReadBits(this.bitsPerSample0); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs index 70578a744..465c8fba3 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); for (int x = 0; x < pixelRow.Length; x++) { Rgba32 rgba = this.converter.ConvertToRgba32(yData[offset], cbData[offset], crData[offset]); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs index e31b4984d..52cc1f0f1 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); for (int x = 0; x < pixelRow.Length; x++) { Rgba32 rgba = this.converter.ConvertToRgba32(ycbcrData[offset], ycbcrData[offset + 1], ycbcrData[offset + 2]); diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs index bd20d644f..5fec09ef1 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs @@ -42,18 +42,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers // Special case for T4BitCompressor. int stripPixels = width * height; this.pixelsAsGray ??= this.MemoryAllocator.Allocate(stripPixels); - Span pixelAsGraySpan = this.pixelsAsGray.GetSpan(); - int lastRow = y + height; - int grayRowIdx = 0; - for (int row = y; row < lastRow; row++) + this.imageBlackWhite.ProcessPixelRows(accessor => { - Span pixelsBlackWhiteRow = this.imageBlackWhite.GetPixelRowSpan(row); - Span pixelAsGrayRow = pixelAsGraySpan.Slice(grayRowIdx * width, width); - PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGrayRow, width); - grayRowIdx++; - } + Span pixelAsGraySpan = this.pixelsAsGray.GetSpan(); + int lastRow = y + height; + int grayRowIdx = 0; + for (int row = y; row < lastRow; row++) + { + Span pixelsBlackWhiteRow = accessor.GetRowSpan(row); + Span pixelAsGrayRow = pixelAsGraySpan.Slice(grayRowIdx * width, width); + PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGrayRow, width); + grayRowIdx++; + } - compressor.CompressStrip(pixelAsGraySpan.Slice(0, stripPixels), height); + compressor.CompressStrip(pixelAsGraySpan.Slice(0, stripPixels), height); + }); } else { @@ -65,6 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers Span rows = this.bitStrip.Slice(0, bytesPerStrip); rows.Clear(); + Buffer2D blackWhiteBuffer = this.imageBlackWhite.Frames.RootFrame.PixelBuffer; int outputRowIdx = 0; int lastRow = y + height; @@ -73,7 +77,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers int bitIndex = 0; int byteIndex = 0; Span outputRow = rows.Slice(outputRowIdx * this.BytesPerRow); - Span pixelsBlackWhiteRow = this.imageBlackWhite.GetPixelRowSpan(row); + Span pixelsBlackWhiteRow = blackWhiteBuffer.DangerousGetRowSpan(row); PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGraySpan, width); for (int x = 0; x < this.Image.Width; x++) { diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs index 88c5f33dd..5d190e0af 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers int stripPixelsRowIdx = 0; for (int row = y; row < lastRow; row++) { - Span stripPixelsRow = this.Image.PixelBuffer.GetRowSpan(row); + Span stripPixelsRow = this.Image.PixelBuffer.DangerousGetRowSpan(row); stripPixelsRow.CopyTo(stripPixels.Slice(stripPixelsRowIdx * width, width)); stripPixelsRowIdx++; } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs index 6d517294d..900969a6c 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers int lastRow = y + height; for (int row = y; row < lastRow; row++) { - ReadOnlySpan indexedPixelRow = this.quantizedImage.GetPixelRowSpan(row); + ReadOnlySpan indexedPixelRow = this.quantizedImage.DangerousGetRowSpan(row); int idxPixels = 0; for (int x = 0; x < halfWidth; x++) { @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers int indexedPixelsRowIdx = 0; for (int row = y; row < lastRow; row++) { - ReadOnlySpan indexedPixelRow = this.quantizedImage.GetPixelRowSpan(row); + ReadOnlySpan indexedPixelRow = this.quantizedImage.DangerousGetRowSpan(row); indexedPixelRow.CopyTo(indexedPixels.Slice(indexedPixelsRowIdx * width, width)); indexedPixelsRowIdx++; } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index adabd0ac3..8566566f6 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -192,7 +192,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless public bool UseCrossColorTransform { get; set; } /// - /// Gets or sets a value indicating whether to use the substract green transform. + /// Gets or sets a value indicating whether to use the subtract green transform. /// public bool UseSubtractGreenTransform { get; set; } @@ -407,13 +407,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private bool ConvertPixelsToBgra(Image image, int width, int height) where TPixel : unmanaged, IPixel { + Buffer2D imageBuffer = image.Frames.RootFrame.PixelBuffer; bool nonOpaque = false; Span bgra = this.Bgra.GetSpan(); Span bgraBytes = MemoryMarshal.Cast(bgra); int widthBytes = width * 4; for (int y = 0; y < height; y++) { - Span rowSpan = image.GetPixelRowSpan(y); + Span rowSpan = imageBuffer.DangerousGetRowSpan(y); Span rowBytes = bgraBytes.Slice(y * widthBytes, widthBytes); PixelOperations.Instance.ToBgra32Bytes(this.configuration, rowSpan, rowBytes, width); if (!nonOpaque) @@ -1049,7 +1050,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return EntropyIx.Palette; } - using IMemoryOwner histoBuffer = this.memoryAllocator.Allocate((int)HistoIx.HistoTotal * 256); + using IMemoryOwner histoBuffer = this.memoryAllocator.Allocate((int)HistoIx.HistoTotal * 256, AllocationOptions.Clean); Span histo = histoBuffer.Memory.Span; uint pixPrev = bgra[0]; // Skip the first pixel. ReadOnlySpan prevRow = null; diff --git a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs index 82bd32a02..f517ad520 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs @@ -196,7 +196,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless for (int y = 0; y < height; y++) { Span rowAsBytes = pixelDataAsBytes.Slice(y * bytesPerRow, bytesPerRow); - Span pixelRow = pixels.GetRowSpan(y); + Span pixelRow = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.FromBgra32Bytes( this.configuration, rowAsBytes.Slice(0, bytesPerRow), diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index b8986f66f..8938ede0a 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -17,30 +17,114 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { #if SUPPORTS_RUNTIME_INTRINSICS private static readonly Vector128 Mean16x4Mask = Vector128.Create((short)0x00ff).AsByte(); + + private static readonly Vector128 SignBit = Vector128.Create((byte)0x80); + + private static readonly Vector128 Three = Vector128.Create((byte)3).AsSByte(); + + private static readonly Vector128 FourShort = Vector128.Create((short)4); + + private static readonly Vector128 FourSByte = Vector128.Create((byte)4).AsSByte(); + + private static readonly Vector128 Nine = Vector128.Create((short)0x0900).AsSByte(); + + private static readonly Vector128 SixtyThree = Vector128.Create((short)63).AsSByte(); + + private static readonly Vector128 SixtyFour = Vector128.Create((byte)64).AsSByte(); + + private static readonly Vector128 K1 = Vector128.Create((short)20091); + + private static readonly Vector128 K2 = Vector128.Create((short)-30068); + #endif // Note: method name in libwebp reference implementation is called VP8SSE16x16. [MethodImpl(InliningOptions.ShortMethod)] - public static int Vp8_Sse16X16(Span a, Span b) => Vp8_SseNxN(a, b, 16, 16); + public static int Vp8_Sse16X16(Span a, Span b) + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported) + { + return Vp8_Sse16xN_Avx2(a, b, 4); + } + + if (Sse2.IsSupported) + { + return Vp8_Sse16xN_Sse2(a, b, 8); + } +#endif + { + return Vp8_SseNxN(a, b, 16, 16); + } + } // Note: method name in libwebp reference implementation is called VP8SSE16x8. [MethodImpl(InliningOptions.ShortMethod)] - public static int Vp8_Sse16X8(Span a, Span b) => Vp8_SseNxN(a, b, 16, 8); + public static int Vp8_Sse16X8(Span a, Span b) + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported) + { + return Vp8_Sse16xN_Avx2(a, b, 2); + } + + if (Sse2.IsSupported) + { + return Vp8_Sse16xN_Sse2(a, b, 4); + } +#endif + { + return Vp8_SseNxN(a, b, 16, 8); + } + } // Note: method name in libwebp reference implementation is called VP8SSE4x4. [MethodImpl(InliningOptions.ShortMethod)] public static int Vp8_Sse4X4(Span a, Span b) { #if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported) + { + // Load values. + ref byte aRef = ref MemoryMarshal.GetReference(a); + ref byte bRef = ref MemoryMarshal.GetReference(b); + var a0 = Vector256.Create( + Unsafe.As>(ref aRef), + Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps))); + var a1 = Vector256.Create( + Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 2)), + Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 3))); + var b0 = Vector256.Create( + Unsafe.As>(ref bRef), + Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps))); + var b1 = Vector256.Create( + Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps * 2)), + Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps * 3))); + + // Combine pair of lines. + Vector256 a01 = Avx2.UnpackLow(a0.AsInt32(), a1.AsInt32()); + Vector256 b01 = Avx2.UnpackLow(b0.AsInt32(), b1.AsInt32()); + + // Convert to 16b. + Vector256 a01s = Avx2.UnpackLow(a01.AsByte(), Vector256.Zero); + Vector256 b01s = Avx2.UnpackLow(b01.AsByte(), Vector256.Zero); + + // subtract, square and accumulate. + Vector256 d0 = Avx2.SubtractSaturate(a01s, b01s); + Vector256 e0 = Avx2.MultiplyAddAdjacent(d0.AsInt16(), d0.AsInt16()); + + return Numerics.ReduceSum(e0); + } + if (Sse2.IsSupported) { // Load values. ref byte aRef = ref MemoryMarshal.GetReference(a); + ref byte bRef = ref MemoryMarshal.GetReference(b); Vector128 a0 = Unsafe.As>(ref aRef); Vector128 a1 = Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps)); Vector128 a2 = Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 2)); Vector128 a3 = Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 3)); - ref byte bRef = ref MemoryMarshal.GetReference(b); Vector128 b0 = Unsafe.As>(ref bRef); Vector128 b1 = Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps)); Vector128 b2 = Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps * 2)); @@ -67,7 +151,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return Numerics.ReduceSum(sum); } - else #endif { return Vp8_SseNxN(a, b, 4, 4); @@ -95,6 +178,104 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return count; } +#if SUPPORTS_RUNTIME_INTRINSICS + [MethodImpl(InliningOptions.ShortMethod)] + private static int Vp8_Sse16xN_Sse2(Span a, Span b, int numPairs) + { + Vector128 sum = Vector128.Zero; + nint offset = 0; + ref byte aRef = ref MemoryMarshal.GetReference(a); + ref byte bRef = ref MemoryMarshal.GetReference(b); + for (int i = 0; i < numPairs; i++) + { + // Load values. + Vector128 a0 = Unsafe.As>(ref Unsafe.Add(ref aRef, offset)); + Vector128 b0 = Unsafe.As>(ref Unsafe.Add(ref bRef, offset)); + Vector128 a1 = Unsafe.As>(ref Unsafe.Add(ref aRef, offset + WebpConstants.Bps)); + Vector128 b1 = Unsafe.As>(ref Unsafe.Add(ref bRef, offset + WebpConstants.Bps)); + + Vector128 sum1 = SubtractAndAccumulate(a0, b0); + Vector128 sum2 = SubtractAndAccumulate(a1, b1); + sum = Sse2.Add(sum, Sse2.Add(sum1, sum2)); + + offset += 2 * WebpConstants.Bps; + } + + return Numerics.ReduceSum(sum); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int Vp8_Sse16xN_Avx2(Span a, Span b, int numPairs) + { + Vector256 sum = Vector256.Zero; + nint offset = 0; + ref byte aRef = ref MemoryMarshal.GetReference(a); + ref byte bRef = ref MemoryMarshal.GetReference(b); + for (int i = 0; i < numPairs; i++) + { + // Load values. + var a0 = Vector256.Create( + Unsafe.As>(ref Unsafe.Add(ref aRef, offset)), + Unsafe.As>(ref Unsafe.Add(ref aRef, offset + WebpConstants.Bps))); + var b0 = Vector256.Create( + Unsafe.As>(ref Unsafe.Add(ref bRef, offset)), + Unsafe.As>(ref Unsafe.Add(ref bRef, offset + WebpConstants.Bps))); + var a1 = Vector256.Create( + Unsafe.As>(ref Unsafe.Add(ref aRef, offset + (2 * WebpConstants.Bps))), + Unsafe.As>(ref Unsafe.Add(ref aRef, offset + (3 * WebpConstants.Bps)))); + var b1 = Vector256.Create( + Unsafe.As>(ref Unsafe.Add(ref bRef, offset + (2 * WebpConstants.Bps))), + Unsafe.As>(ref Unsafe.Add(ref bRef, offset + (3 * WebpConstants.Bps)))); + + Vector256 sum1 = SubtractAndAccumulate(a0, b0); + Vector256 sum2 = SubtractAndAccumulate(a1, b1); + sum = Avx2.Add(sum, Avx2.Add(sum1, sum2)); + + offset += 4 * WebpConstants.Bps; + } + + return Numerics.ReduceSum(sum); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector128 SubtractAndAccumulate(Vector128 a, Vector128 b) + { + // Take abs(a-b) in 8b. + Vector128 ab = Sse2.SubtractSaturate(a, b); + Vector128 ba = Sse2.SubtractSaturate(b, a); + Vector128 absAb = Sse2.Or(ab, ba); + + // Zero-extend to 16b. + Vector128 c0 = Sse2.UnpackLow(absAb, Vector128.Zero); + Vector128 c1 = Sse2.UnpackHigh(absAb, Vector128.Zero); + + // Multiply with self. + Vector128 sum1 = Sse2.MultiplyAddAdjacent(c0.AsInt16(), c0.AsInt16()); + Vector128 sum2 = Sse2.MultiplyAddAdjacent(c1.AsInt16(), c1.AsInt16()); + + return Sse2.Add(sum1, sum2); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector256 SubtractAndAccumulate(Vector256 a, Vector256 b) + { + // Take abs(a-b) in 8b. + Vector256 ab = Avx2.SubtractSaturate(a, b); + Vector256 ba = Avx2.SubtractSaturate(b, a); + Vector256 absAb = Avx2.Or(ab, ba); + + // Zero-extend to 16b. + Vector256 c0 = Avx2.UnpackLow(absAb, Vector256.Zero); + Vector256 c1 = Avx2.UnpackHigh(absAb, Vector256.Zero); + + // Multiply with self. + Vector256 sum1 = Avx2.MultiplyAddAdjacent(c0.AsInt16(), c0.AsInt16()); + Vector256 sum2 = Avx2.MultiplyAddAdjacent(c1.AsInt16(), c1.AsInt16()); + + return Avx2.Add(sum1, sum2); + } +#endif + [MethodImpl(InliningOptions.ShortMethod)] public static void Vp8Copy4X4(Span src, Span dst) => Copy(src, dst, 4, 4); @@ -788,57 +969,325 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } #endif + // Transforms (Paragraph 14.4). + // Does two transforms. public static void TransformTwo(Span src, Span dst, Span scratch) { - TransformOne(src, dst, scratch); - TransformOne(src.Slice(16), dst.Slice(4), scratch); +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + // This implementation makes use of 16-bit fixed point versions of two + // multiply constants: + // K1 = sqrt(2) * cos (pi/8) ~= 85627 / 2^16 + // K2 = sqrt(2) * sin (pi/8) ~= 35468 / 2^16 + // + // To be able to use signed 16-bit integers, we use the following trick to + // have constants within range: + // - Associated constants are obtained by subtracting the 16-bit fixed point + // version of one: + // k = K - (1 << 16) => K = k + (1 << 16) + // K1 = 85267 => k1 = 20091 + // K2 = 35468 => k2 = -30068 + // - The multiplication of a variable by a constant become the sum of the + // variable and the multiplication of that variable by the associated + // constant: + // (x * K) >> 16 = (x * (k + (1 << 16))) >> 16 = ((x * k ) >> 16) + x + + // Load and concatenate the transform coefficients (we'll do two transforms + // in parallel). + ref short srcRef = ref MemoryMarshal.GetReference(src); + var in0 = Vector128.Create(Unsafe.As(ref srcRef), 0); + var in1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 4)), 0); + var in2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 8)), 0); + var in3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 12)), 0); + + // a00 a10 a20 a30 x x x x + // a01 a11 a21 a31 x x x x + // a02 a12 a22 a32 x x x x + // a03 a13 a23 a33 x x x x + var inb0 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 16)), 0); + var inb1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 20)), 0); + var inb2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 24)), 0); + var inb3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 28)), 0); + + in0 = Sse2.UnpackLow(in0, inb0); + in1 = Sse2.UnpackLow(in1, inb1); + in2 = Sse2.UnpackLow(in2, inb2); + in3 = Sse2.UnpackLow(in3, inb3); + + // a00 a10 a20 a30 b00 b10 b20 b30 + // a01 a11 a21 a31 b01 b11 b21 b31 + // a02 a12 a22 a32 b02 b12 b22 b32 + // a03 a13 a23 a33 b03 b13 b23 b33 + + // Vertical pass and subsequent transpose. + // First pass, c and d calculations are longer because of the "trick" multiplications. + Vector128 a = Sse2.Add(in0.AsInt16(), in2.AsInt16()); + Vector128 b = Sse2.Subtract(in0.AsInt16(), in2.AsInt16()); + + // c = MUL(in1, K2) - MUL(in3, K1) = MUL(in1, k2) - MUL(in3, k1) + in1 - in3 + Vector128 c1 = Sse2.MultiplyHigh(in1.AsInt16(), K2); + Vector128 c2 = Sse2.MultiplyHigh(in3.AsInt16(), K1); + Vector128 c3 = Sse2.Subtract(in1.AsInt16(), in3.AsInt16()); + Vector128 c4 = Sse2.Subtract(c1, c2); + Vector128 c = Sse2.Add(c3.AsInt16(), c4); + + // d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3 + Vector128 d1 = Sse2.MultiplyHigh(in1.AsInt16(), K1); + Vector128 d2 = Sse2.MultiplyHigh(in3.AsInt16(), K2); + Vector128 d3 = Sse2.Add(in1.AsInt16(), in3.AsInt16()); + Vector128 d4 = Sse2.Add(d1, d2); + Vector128 d = Sse2.Add(d3, d4); + + // Second pass. + Vector128 tmp0 = Sse2.Add(a.AsInt16(), d); + Vector128 tmp1 = Sse2.Add(b.AsInt16(), c); + Vector128 tmp2 = Sse2.Subtract(b.AsInt16(), c); + Vector128 tmp3 = Sse2.Subtract(a.AsInt16(), d); + + // Transpose the two 4x4. + Vp8Transpose_2_4x4_16b(tmp0, tmp1, tmp2, tmp3, out Vector128 t0, out Vector128 t1, out Vector128 t2, out Vector128 t3); + + // Horizontal pass and subsequent transpose. + // First pass, c and d calculations are longer because of the "trick" multiplications. + Vector128 dc = Sse2.Add(t0.AsInt16(), FourShort); + a = Sse2.Add(dc, t2.AsInt16()); + b = Sse2.Subtract(dc, t2.AsInt16()); + + // c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3 + c1 = Sse2.MultiplyHigh(t1.AsInt16(), K2); + c2 = Sse2.MultiplyHigh(t3.AsInt16(), K1); + c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16()); + c4 = Sse2.Subtract(c1, c2); + c = Sse2.Add(c3, c4); + + // d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3 + d1 = Sse2.MultiplyHigh(t1.AsInt16(), K1); + d2 = Sse2.MultiplyHigh(t3.AsInt16(), K2); + d3 = Sse2.Add(t1.AsInt16(), t3.AsInt16()); + d4 = Sse2.Add(d1, d2); + d = Sse2.Add(d3, d4); + + // Second pass. + tmp0 = Sse2.Add(a, d); + tmp1 = Sse2.Add(b, c); + tmp2 = Sse2.Subtract(b, c); + tmp3 = Sse2.Subtract(a, d); + Vector128 shifted0 = Sse2.ShiftRightArithmetic(tmp0, 3); + Vector128 shifted1 = Sse2.ShiftRightArithmetic(tmp1, 3); + Vector128 shifted2 = Sse2.ShiftRightArithmetic(tmp2, 3); + Vector128 shifted3 = Sse2.ShiftRightArithmetic(tmp3, 3); + + // Transpose the two 4x4. + Vp8Transpose_2_4x4_16b(shifted0, shifted1, shifted2, shifted3, out t0, out t1, out t2, out t3); + + // Add inverse transform to 'dst' and store. + // Load the reference(s). + // Load eight bytes/pixels per line. + ref byte dstRef = ref MemoryMarshal.GetReference(dst); + Vector128 dst0 = Vector128.Create(Unsafe.As(ref dstRef), 0).AsByte(); + Vector128 dst1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps)), 0).AsByte(); + Vector128 dst2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 2)), 0).AsByte(); + Vector128 dst3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 3)), 0).AsByte(); + + // Convert to 16b. + dst0 = Sse2.UnpackLow(dst0, Vector128.Zero); + dst1 = Sse2.UnpackLow(dst1, Vector128.Zero); + dst2 = Sse2.UnpackLow(dst2, Vector128.Zero); + dst3 = Sse2.UnpackLow(dst3, Vector128.Zero); + + // Add the inverse transform(s). + dst0 = Sse2.Add(dst0.AsInt16(), t0.AsInt16()).AsByte(); + dst1 = Sse2.Add(dst1.AsInt16(), t1.AsInt16()).AsByte(); + dst2 = Sse2.Add(dst2.AsInt16(), t2.AsInt16()).AsByte(); + dst3 = Sse2.Add(dst3.AsInt16(), t3.AsInt16()).AsByte(); + + // Unsigned saturate to 8b. + dst0 = Sse2.PackUnsignedSaturate(dst0.AsInt16(), dst0.AsInt16()); + dst1 = Sse2.PackUnsignedSaturate(dst1.AsInt16(), dst1.AsInt16()); + dst2 = Sse2.PackUnsignedSaturate(dst2.AsInt16(), dst2.AsInt16()); + dst3 = Sse2.PackUnsignedSaturate(dst3.AsInt16(), dst3.AsInt16()); + + // Store the results. + // Store eight bytes/pixels per line. + ref byte outputRef = ref MemoryMarshal.GetReference(dst); + Unsafe.As>(ref outputRef) = dst0.GetLower(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps)) = dst1.GetLower(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 2)) = dst2.GetLower(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 3)) = dst3.GetLower(); + } + else +#endif + { + TransformOne(src, dst, scratch); + TransformOne(src.Slice(16), dst.Slice(4), scratch); + } } public static void TransformOne(Span src, Span dst, Span scratch) { - Span tmp = scratch.Slice(0, 16); - int tmpOffset = 0; - for (int srcOffset = 0; srcOffset < 4; srcOffset++) - { - // vertical pass - int srcOffsetPlus4 = srcOffset + 4; - int srcOffsetPlus8 = srcOffset + 8; - int srcOffsetPlus12 = srcOffset + 12; - int a = src[srcOffset] + src[srcOffsetPlus8]; - int b = src[srcOffset] - src[srcOffsetPlus8]; - int c = Mul2(src[srcOffsetPlus4]) - Mul1(src[srcOffsetPlus12]); - int d = Mul1(src[srcOffsetPlus4]) + Mul2(src[srcOffsetPlus12]); - tmp[tmpOffset++] = a + d; - tmp[tmpOffset++] = b + c; - tmp[tmpOffset++] = b - c; - tmp[tmpOffset++] = a - d; - } - - // Each pass is expanding the dynamic range by ~3.85 (upper bound). - // The exact value is (2. + (20091 + 35468) / 65536). - // After the second pass, maximum interval is [-3794, 3794], assuming - // an input in [-2048, 2047] interval. We then need to add a dst value in the [0, 255] range. - // In the worst case scenario, the input to clip_8b() can be as large as [-60713, 60968]. - tmpOffset = 0; - int dstOffset = 0; - for (int i = 0; i < 4; i++) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + // Load and concatenate the transform coefficients. + ref short srcRef = ref MemoryMarshal.GetReference(src); + var in0 = Vector128.Create(Unsafe.As(ref srcRef), 0); + var in1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 4)), 0); + var in2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 8)), 0); + var in3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 12)), 0); + + // a00 a10 a20 a30 x x x x + // a01 a11 a21 a31 x x x x + // a02 a12 a22 a32 x x x x + // a03 a13 a23 a33 x x x x + + // Vertical pass and subsequent transpose. + // First pass, c and d calculations are longer because of the "trick" multiplications. + Vector128 a = Sse2.Add(in0.AsInt16(), in2.AsInt16()); + Vector128 b = Sse2.Subtract(in0.AsInt16(), in2.AsInt16()); + + // c = MUL(in1, K2) - MUL(in3, K1) = MUL(in1, k2) - MUL(in3, k1) + in1 - in3 + Vector128 c1 = Sse2.MultiplyHigh(in1.AsInt16(), K2); + Vector128 c2 = Sse2.MultiplyHigh(in3.AsInt16(), K1); + Vector128 c3 = Sse2.Subtract(in1.AsInt16(), in3.AsInt16()); + Vector128 c4 = Sse2.Subtract(c1, c2); + Vector128 c = Sse2.Add(c3.AsInt16(), c4); + + // d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3 + Vector128 d1 = Sse2.MultiplyHigh(in1.AsInt16(), K1); + Vector128 d2 = Sse2.MultiplyHigh(in3.AsInt16(), K2); + Vector128 d3 = Sse2.Add(in1.AsInt16(), in3.AsInt16()); + Vector128 d4 = Sse2.Add(d1, d2); + Vector128 d = Sse2.Add(d3, d4); + + // Second pass. + Vector128 tmp0 = Sse2.Add(a.AsInt16(), d); + Vector128 tmp1 = Sse2.Add(b.AsInt16(), c); + Vector128 tmp2 = Sse2.Subtract(b.AsInt16(), c); + Vector128 tmp3 = Sse2.Subtract(a.AsInt16(), d); + + // Transpose the two 4x4. + Vp8Transpose_2_4x4_16b(tmp0, tmp1, tmp2, tmp3, out Vector128 t0, out Vector128 t1, out Vector128 t2, out Vector128 t3); + + // Horizontal pass and subsequent transpose. + // First pass, c and d calculations are longer because of the "trick" multiplications. + Vector128 dc = Sse2.Add(t0.AsInt16(), FourShort); + a = Sse2.Add(dc, t2.AsInt16()); + b = Sse2.Subtract(dc, t2.AsInt16()); + + // c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3 + c1 = Sse2.MultiplyHigh(t1.AsInt16(), K2); + c2 = Sse2.MultiplyHigh(t3.AsInt16(), K1); + c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16()); + c4 = Sse2.Subtract(c1, c2); + c = Sse2.Add(c3, c4); + + // d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3 + d1 = Sse2.MultiplyHigh(t1.AsInt16(), K1); + d2 = Sse2.MultiplyHigh(t3.AsInt16(), K2); + d3 = Sse2.Add(t1.AsInt16(), t3.AsInt16()); + d4 = Sse2.Add(d1, d2); + d = Sse2.Add(d3, d4); + + // Second pass. + tmp0 = Sse2.Add(a, d); + tmp1 = Sse2.Add(b, c); + tmp2 = Sse2.Subtract(b, c); + tmp3 = Sse2.Subtract(a, d); + Vector128 shifted0 = Sse2.ShiftRightArithmetic(tmp0, 3); + Vector128 shifted1 = Sse2.ShiftRightArithmetic(tmp1, 3); + Vector128 shifted2 = Sse2.ShiftRightArithmetic(tmp2, 3); + Vector128 shifted3 = Sse2.ShiftRightArithmetic(tmp3, 3); + + // Transpose the two 4x4. + Vp8Transpose_2_4x4_16b(shifted0, shifted1, shifted2, shifted3, out t0, out t1, out t2, out t3); + + // Add inverse transform to 'dst' and store. + // Load the reference(s). + // Load four bytes/pixels per line. + ref byte dstRef = ref MemoryMarshal.GetReference(dst); + Vector128 dst0 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref dstRef)).AsByte(); + Vector128 dst1 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps))).AsByte(); + Vector128 dst2 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 2))).AsByte(); + Vector128 dst3 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 3))).AsByte(); + + // Convert to 16b. + dst0 = Sse2.UnpackLow(dst0, Vector128.Zero); + dst1 = Sse2.UnpackLow(dst1, Vector128.Zero); + dst2 = Sse2.UnpackLow(dst2, Vector128.Zero); + dst3 = Sse2.UnpackLow(dst3, Vector128.Zero); + + // Add the inverse transform(s). + dst0 = Sse2.Add(dst0.AsInt16(), t0.AsInt16()).AsByte(); + dst1 = Sse2.Add(dst1.AsInt16(), t1.AsInt16()).AsByte(); + dst2 = Sse2.Add(dst2.AsInt16(), t2.AsInt16()).AsByte(); + dst3 = Sse2.Add(dst3.AsInt16(), t3.AsInt16()).AsByte(); + + // Unsigned saturate to 8b. + dst0 = Sse2.PackUnsignedSaturate(dst0.AsInt16(), dst0.AsInt16()); + dst1 = Sse2.PackUnsignedSaturate(dst1.AsInt16(), dst1.AsInt16()); + dst2 = Sse2.PackUnsignedSaturate(dst2.AsInt16(), dst2.AsInt16()); + dst3 = Sse2.PackUnsignedSaturate(dst3.AsInt16(), dst3.AsInt16()); + + // Store the results. + // Store four bytes/pixels per line. + ref byte outputRef = ref MemoryMarshal.GetReference(dst); + int output0 = Sse2.ConvertToInt32(dst0.AsInt32()); + int output1 = Sse2.ConvertToInt32(dst1.AsInt32()); + int output2 = Sse2.ConvertToInt32(dst2.AsInt32()); + int output3 = Sse2.ConvertToInt32(dst3.AsInt32()); + Unsafe.As(ref outputRef) = output0; + Unsafe.As(ref Unsafe.Add(ref outputRef, WebpConstants.Bps)) = output1; + Unsafe.As(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 2)) = output2; + Unsafe.As(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 3)) = output3; + } + else +#endif { - // horizontal pass - int tmpOffsetPlus4 = tmpOffset + 4; - int tmpOffsetPlus8 = tmpOffset + 8; - int tmpOffsetPlus12 = tmpOffset + 12; - int dc = tmp[tmpOffset] + 4; - int a = dc + tmp[tmpOffsetPlus8]; - int b = dc - tmp[tmpOffsetPlus8]; - int c = Mul2(tmp[tmpOffsetPlus4]) - Mul1(tmp[tmpOffsetPlus12]); - int d = Mul1(tmp[tmpOffsetPlus4]) + Mul2(tmp[tmpOffsetPlus12]); - Store(dst.Slice(dstOffset), 0, 0, a + d); - Store(dst.Slice(dstOffset), 1, 0, b + c); - Store(dst.Slice(dstOffset), 2, 0, b - c); - Store(dst.Slice(dstOffset), 3, 0, a - d); - tmpOffset++; + Span tmp = scratch.Slice(0, 16); + int tmpOffset = 0; + for (int srcOffset = 0; srcOffset < 4; srcOffset++) + { + // vertical pass + int srcOffsetPlus4 = srcOffset + 4; + int srcOffsetPlus8 = srcOffset + 8; + int srcOffsetPlus12 = srcOffset + 12; + int a = src[srcOffset] + src[srcOffsetPlus8]; + int b = src[srcOffset] - src[srcOffsetPlus8]; + int c = Mul2(src[srcOffsetPlus4]) - Mul1(src[srcOffsetPlus12]); + int d = Mul1(src[srcOffsetPlus4]) + Mul2(src[srcOffsetPlus12]); + tmp[tmpOffset++] = a + d; + tmp[tmpOffset++] = b + c; + tmp[tmpOffset++] = b - c; + tmp[tmpOffset++] = a - d; + } - dstOffset += WebpConstants.Bps; + // Each pass is expanding the dynamic range by ~3.85 (upper bound). + // The exact value is (2. + (20091 + 35468) / 65536). + // After the second pass, maximum interval is [-3794, 3794], assuming + // an input in [-2048, 2047] interval. We then need to add a dst value in the [0, 255] range. + // In the worst case scenario, the input to clip_8b() can be as large as [-60713, 60968]. + tmpOffset = 0; + int dstOffset = 0; + for (int i = 0; i < 4; i++) + { + // horizontal pass + int tmpOffsetPlus4 = tmpOffset + 4; + int tmpOffsetPlus8 = tmpOffset + 8; + int tmpOffsetPlus12 = tmpOffset + 12; + int dc = tmp[tmpOffset] + 4; + int a = dc + tmp[tmpOffsetPlus8]; + int b = dc - tmp[tmpOffsetPlus8]; + int c = Mul2(tmp[tmpOffsetPlus4]) - Mul1(tmp[tmpOffsetPlus12]); + int d = Mul1(tmp[tmpOffsetPlus4]) + Mul2(tmp[tmpOffsetPlus12]); + Store(dst.Slice(dstOffset), 0, 0, a + d); + Store(dst.Slice(dstOffset), 1, 0, b + c); + Store(dst.Slice(dstOffset), 2, 0, b - c); + Store(dst.Slice(dstOffset), 3, 0, a - d); + tmpOffset++; + + dstOffset += WebpConstants.Bps; + } } } @@ -900,71 +1349,292 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Simple In-loop filtering (Paragraph 15.2) public static void SimpleVFilter16(Span p, int offset, int stride, int thresh) { - int thresh2 = (2 * thresh) + 1; - int end = 16 + offset; - for (int i = offset; i < end; i++) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) { - if (NeedsFilter(p, i, stride, thresh2)) + // Load. + ref byte pRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(p), offset); + + Vector128 p1 = Unsafe.As>(ref Unsafe.Subtract(ref pRef, 2 * stride)); + Vector128 p0 = Unsafe.As>(ref Unsafe.Subtract(ref pRef, stride)); + Vector128 q0 = Unsafe.As>(ref pRef); + Vector128 q1 = Unsafe.As>(ref Unsafe.Add(ref pRef, stride)); + + DoFilter2Sse2(ref p1, ref p0, ref q0, ref q1, thresh); + + // Store. + ref byte outputRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(p), offset); + Unsafe.As>(ref Unsafe.Subtract(ref outputRef, stride)) = p0.AsSByte(); + Unsafe.As>(ref outputRef) = q0.AsSByte(); + } + else +#endif + { + int thresh2 = (2 * thresh) + 1; + int end = 16 + offset; + for (int i = offset; i < end; i++) { - DoFilter2(p, i, stride); + if (NeedsFilter(p, i, stride, thresh2)) + { + DoFilter2(p, i, stride); + } } } } public static void SimpleHFilter16(Span p, int offset, int stride, int thresh) { - int thresh2 = (2 * thresh) + 1; - int end = offset + (16 * stride); - for (int i = offset; i < end; i += stride) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) { - if (NeedsFilter(p, i, 1, thresh2)) + // Beginning of p1 + ref byte pRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(p), offset - 2); + + Load16x4(ref pRef, ref Unsafe.Add(ref pRef, 8 * stride), stride, out Vector128 p1, out Vector128 p0, out Vector128 q0, out Vector128 q1); + DoFilter2Sse2(ref p1, ref p0, ref q0, ref q1, thresh); + Store16x4(p1, p0, q0, q1, ref pRef, ref Unsafe.Add(ref pRef, 8 * stride), stride); + } + else +#endif + { + int thresh2 = (2 * thresh) + 1; + int end = offset + (16 * stride); + for (int i = offset; i < end; i += stride) { - DoFilter2(p, i, 1); + if (NeedsFilter(p, i, 1, thresh2)) + { + DoFilter2(p, i, 1); + } } } } public static void SimpleVFilter16i(Span p, int offset, int stride, int thresh) { - for (int k = 3; k > 0; --k) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) { - offset += 4 * stride; - SimpleVFilter16(p, offset, stride, thresh); + for (int k = 3; k > 0; k--) + { + offset += 4 * stride; + SimpleVFilter16(p, offset, stride, thresh); + } + } + else +#endif + { + for (int k = 3; k > 0; k--) + { + offset += 4 * stride; + SimpleVFilter16(p, offset, stride, thresh); + } } } public static void SimpleHFilter16i(Span p, int offset, int stride, int thresh) { - for (int k = 3; k > 0; --k) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) { - offset += 4; - SimpleHFilter16(p, offset, stride, thresh); + for (int k = 3; k > 0; k--) + { + offset += 4; + SimpleHFilter16(p, offset, stride, thresh); + } + } + else +#endif + { + for (int k = 3; k > 0; k--) + { + offset += 4; + SimpleHFilter16(p, offset, stride, thresh); + } } } + // On macroblock edges. [MethodImpl(InliningOptions.ShortMethod)] public static void VFilter16(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) - => FilterLoop26(p, offset, stride, 1, 16, thresh, ithresh, hevThresh); + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + ref byte pRef = ref MemoryMarshal.GetReference(p); + Vector128 t1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset - (4 * stride))); + Vector128 p2 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset - (3 * stride))); + Vector128 p1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset - (2 * stride))); + Vector128 p0 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset - stride)); + + Vector128 mask = Abs(p1, p0); + mask = Sse2.Max(mask, Abs(t1, p2)); + mask = Sse2.Max(mask, Abs(p2, p1)); + + Vector128 q0 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset)); + Vector128 q1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + stride)); + Vector128 q2 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (2 * stride))); + t1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (3 * stride))); + + mask = Sse2.Max(mask, Abs(q1, q0)); + mask = Sse2.Max(mask, Abs(t1, q2)); + mask = Sse2.Max(mask, Abs(q2, q1)); + + ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); + DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); + + // Store. + ref byte outputRef = ref MemoryMarshal.GetReference(p); + Unsafe.As>(ref Unsafe.Add(ref outputRef, offset - (3 * stride))) = p2.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, offset - (2 * stride))) = p1.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, offset - stride)) = p0.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, offset)) = q0.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, offset + stride)) = q1.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, offset + (2 * stride))) = q2.AsInt32(); + } + else +#endif + { + FilterLoop26(p, offset, stride, 1, 16, thresh, ithresh, hevThresh); + } + } [MethodImpl(InliningOptions.ShortMethod)] public static void HFilter16(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) - => FilterLoop26(p, offset, 1, stride, 16, thresh, ithresh, hevThresh); + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + ref byte pRef = ref MemoryMarshal.GetReference(p); + ref byte bRef = ref Unsafe.Add(ref pRef, offset - 4); + Load16x4(ref bRef, ref Unsafe.Add(ref bRef, 8 * stride), stride, out Vector128 p3, out Vector128 p2, out Vector128 p1, out Vector128 p0); + + Vector128 mask = Abs(p1, p0); + mask = Sse2.Max(mask, Abs(p3, p2)); + mask = Sse2.Max(mask, Abs(p2, p1)); + + Load16x4(ref Unsafe.Add(ref pRef, offset), ref Unsafe.Add(ref pRef, offset + (8 * stride)), stride, out Vector128 q0, out Vector128 q1, out Vector128 q2, out Vector128 q3); + + mask = Sse2.Max(mask, Abs(q1, q0)); + mask = Sse2.Max(mask, Abs(q3, q2)); + mask = Sse2.Max(mask, Abs(q2, q1)); + + ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); + DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); + + Store16x4(p3, p2, p1, p0, ref bRef, ref Unsafe.Add(ref bRef, 8 * stride), stride); + Store16x4(q0, q1, q2, q3, ref Unsafe.Add(ref pRef, offset), ref Unsafe.Add(ref pRef, offset + (8 * stride)), stride); + } + else +#endif + { + FilterLoop26(p, offset, 1, stride, 16, thresh, ithresh, hevThresh); + } + } public static void VFilter16i(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) { - for (int k = 3; k > 0; --k) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) { - offset += 4 * stride; - FilterLoop24(p, offset, stride, 1, 16, thresh, ithresh, hevThresh); + ref byte pRef = ref MemoryMarshal.GetReference(p); + Vector128 p3 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset)); + Vector128 p2 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + stride)); + Vector128 p1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (2 * stride))); + Vector128 p0 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (3 * stride))); + + for (int k = 3; k > 0; k--) + { + // Beginning of p1. + Span b = p.Slice(offset + (2 * stride)); + offset += 4 * stride; + + Vector128 mask = Abs(p0, p1); + mask = Sse2.Max(mask, Abs(p3, p2)); + mask = Sse2.Max(mask, Abs(p2, p1)); + + p3 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset)); + p2 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + stride)); + Vector128 tmp1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (2 * stride))); + Vector128 tmp2 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (3 * stride))); + + mask = Sse2.Max(mask, Abs(tmp1, tmp2)); + mask = Sse2.Max(mask, Abs(p3, p2)); + mask = Sse2.Max(mask, Abs(p2, tmp1)); + + // p3 and p2 are not just temporary variables here: they will be + // re-used for next span. And q2/q3 will become p1/p0 accordingly. + ComplexMask(p1, p0, p3, p2, thresh, ithresh, ref mask); + DoFilter4Sse2(ref p1, ref p0, ref p3, ref p2, mask, hevThresh); + + // Store. + ref byte outputRef = ref MemoryMarshal.GetReference(b); + Unsafe.As>(ref outputRef) = p1.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, stride)) = p0.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, stride * 2)) = p3.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, stride * 3)) = p2.AsInt32(); + + // Rotate samples. + p1 = tmp1; + p0 = tmp2; + } + } + else +#endif + { + for (int k = 3; k > 0; k--) + { + offset += 4 * stride; + FilterLoop24(p, offset, stride, 1, 16, thresh, ithresh, hevThresh); + } } } public static void HFilter16i(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) { - for (int k = 3; k > 0; --k) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) { - offset += 4; - FilterLoop24(p, offset, 1, stride, 16, thresh, ithresh, hevThresh); + ref byte pRef = ref MemoryMarshal.GetReference(p); + Load16x4(ref Unsafe.Add(ref pRef, offset), ref Unsafe.Add(ref pRef, offset + (8 * stride)), stride, out Vector128 p3, out Vector128 p2, out Vector128 p1, out Vector128 p0); + + Vector128 mask; + for (int k = 3; k > 0; k--) + { + // Beginning of p1. + ref byte bRef = ref Unsafe.Add(ref pRef, offset + 2); + + // Beginning of q0 (and next span). + offset += 4; + + // Compute partial mask. + mask = Abs(p1, p0); + mask = Sse2.Max(mask, Abs(p3, p2)); + mask = Sse2.Max(mask, Abs(p2, p1)); + + Load16x4(ref Unsafe.Add(ref pRef, offset), ref Unsafe.Add(ref pRef, offset + (8 * stride)), stride, out p3, out p2, out Vector128 tmp1, out Vector128 tmp2); + + mask = Sse2.Max(mask, Abs(tmp1, tmp2)); + mask = Sse2.Max(mask, Abs(p3, p2)); + mask = Sse2.Max(mask, Abs(p2, tmp1)); + + ComplexMask(p1, p0, p3, p2, thresh, ithresh, ref mask); + DoFilter4Sse2(ref p1, ref p0, ref p3, ref p2, mask, hevThresh); + + Store16x4(p1, p0, p3, p2, ref bRef, ref Unsafe.Add(ref bRef, 8 * stride), stride); + + // Rotate samples. + p1 = tmp1; + p0 = tmp2; + } + } + else +#endif + { + for (int k = 3; k > 0; k--) + { + offset += 4; + FilterLoop24(p, offset, 1, stride, 16, thresh, ithresh, hevThresh); + } } } @@ -972,31 +1642,167 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy [MethodImpl(InliningOptions.ShortMethod)] public static void VFilter8(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) { - FilterLoop26(u, offset, stride, 1, 8, thresh, ithresh, hevThresh); - FilterLoop26(v, offset, stride, 1, 8, thresh, ithresh, hevThresh); +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + // Load uv h-edges. + ref byte uRef = ref MemoryMarshal.GetReference(u); + ref byte vRef = ref MemoryMarshal.GetReference(v); + Vector128 t1 = LoadUvEdge(ref uRef, ref vRef, offset - (4 * stride)); + Vector128 p2 = LoadUvEdge(ref uRef, ref vRef, offset - (3 * stride)); + Vector128 p1 = LoadUvEdge(ref uRef, ref vRef, offset - (2 * stride)); + Vector128 p0 = LoadUvEdge(ref uRef, ref vRef, offset - stride); + + Vector128 mask = Abs(p1, p0); + mask = Sse2.Max(mask, Abs(t1, p2)); + mask = Sse2.Max(mask, Abs(p2, p1)); + + Vector128 q0 = LoadUvEdge(ref uRef, ref vRef, offset); + Vector128 q1 = LoadUvEdge(ref uRef, ref vRef, offset + stride); + Vector128 q2 = LoadUvEdge(ref uRef, ref vRef, offset + (2 * stride)); + t1 = LoadUvEdge(ref uRef, ref vRef, offset + (3 * stride)); + + mask = Sse2.Max(mask, Abs(q1, q0)); + mask = Sse2.Max(mask, Abs(t1, q2)); + mask = Sse2.Max(mask, Abs(q2, q1)); + + ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); + DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); + + // Store. + StoreUv(p2, ref uRef, ref vRef, offset - (3 * stride)); + StoreUv(p1, ref uRef, ref vRef, offset - (2 * stride)); + StoreUv(p0, ref uRef, ref vRef, offset - stride); + StoreUv(q0, ref uRef, ref vRef, offset); + StoreUv(q1, ref uRef, ref vRef, offset + (1 * stride)); + StoreUv(q2, ref uRef, ref vRef, offset + (2 * stride)); + } + else +#endif + { + FilterLoop26(u, offset, stride, 1, 8, thresh, ithresh, hevThresh); + FilterLoop26(v, offset, stride, 1, 8, thresh, ithresh, hevThresh); + } } [MethodImpl(InliningOptions.ShortMethod)] public static void HFilter8(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) { - FilterLoop26(u, offset, 1, stride, 8, thresh, ithresh, hevThresh); - FilterLoop26(v, offset, 1, stride, 8, thresh, ithresh, hevThresh); +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + ref byte uRef = ref MemoryMarshal.GetReference(u); + ref byte vRef = ref MemoryMarshal.GetReference(v); + Load16x4(ref Unsafe.Add(ref uRef, offset - 4), ref Unsafe.Add(ref vRef, offset - 4), stride, out Vector128 p3, out Vector128 p2, out Vector128 p1, out Vector128 p0); + + Vector128 mask = Abs(p1, p0); + mask = Sse2.Max(mask, Abs(p3, p2)); + mask = Sse2.Max(mask, Abs(p2, p1)); + + Load16x4(ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride, out Vector128 q0, out Vector128 q1, out Vector128 q2, out Vector128 q3); + + mask = Sse2.Max(mask, Abs(q1, q0)); + mask = Sse2.Max(mask, Abs(q3, q2)); + mask = Sse2.Max(mask, Abs(q2, q1)); + + ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); + DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); + + Store16x4(p3, p2, p1, p0, ref Unsafe.Add(ref uRef, offset - 4), ref Unsafe.Add(ref vRef, offset - 4), stride); + Store16x4(q0, q1, q2, q3, ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride); + } + else +#endif + { + FilterLoop26(u, offset, 1, stride, 8, thresh, ithresh, hevThresh); + FilterLoop26(v, offset, 1, stride, 8, thresh, ithresh, hevThresh); + } } [MethodImpl(InliningOptions.ShortMethod)] public static void VFilter8i(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) { - int offset4mulstride = offset + (4 * stride); - FilterLoop24(u, offset4mulstride, stride, 1, 8, thresh, ithresh, hevThresh); - FilterLoop24(v, offset4mulstride, stride, 1, 8, thresh, ithresh, hevThresh); +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + // Load uv h-edges. + ref byte uRef = ref MemoryMarshal.GetReference(u); + ref byte vRef = ref MemoryMarshal.GetReference(v); + Vector128 t2 = LoadUvEdge(ref uRef, ref vRef, offset); + Vector128 t1 = LoadUvEdge(ref uRef, ref vRef, offset + stride); + Vector128 p1 = LoadUvEdge(ref uRef, ref vRef, offset + (stride * 2)); + Vector128 p0 = LoadUvEdge(ref uRef, ref vRef, offset + (stride * 3)); + + Vector128 mask = Abs(p1, p0); + mask = Sse2.Max(mask, Abs(t2, t1)); + mask = Sse2.Max(mask, Abs(t1, p1)); + + offset += 4 * stride; + + Vector128 q0 = LoadUvEdge(ref uRef, ref vRef, offset); + Vector128 q1 = LoadUvEdge(ref uRef, ref vRef, offset + stride); + t1 = LoadUvEdge(ref uRef, ref vRef, offset + (stride * 2)); + t2 = LoadUvEdge(ref uRef, ref vRef, offset + (stride * 3)); + + mask = Sse2.Max(mask, Abs(q1, q0)); + mask = Sse2.Max(mask, Abs(t2, t1)); + mask = Sse2.Max(mask, Abs(t1, q1)); + + ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); + DoFilter4Sse2(ref p1, ref p0, ref q0, ref q1, mask, hevThresh); + + // Store. + StoreUv(p1, ref uRef, ref vRef, offset + (-2 * stride)); + StoreUv(p0, ref uRef, ref vRef, offset + (-1 * stride)); + StoreUv(q0, ref uRef, ref vRef, offset); + StoreUv(q1, ref uRef, ref vRef, offset + stride); + } + else +#endif + { + int offset4mulstride = offset + (4 * stride); + FilterLoop24(u, offset4mulstride, stride, 1, 8, thresh, ithresh, hevThresh); + FilterLoop24(v, offset4mulstride, stride, 1, 8, thresh, ithresh, hevThresh); + } } [MethodImpl(InliningOptions.ShortMethod)] public static void HFilter8i(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) { - int offsetPlus4 = offset + 4; - FilterLoop24(u, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh); - FilterLoop24(v, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh); +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + ref byte uRef = ref MemoryMarshal.GetReference(u); + ref byte vRef = ref MemoryMarshal.GetReference(v); + Load16x4(ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride, out Vector128 t2, out Vector128 t1, out Vector128 p1, out Vector128 p0); + + Vector128 mask = Abs(p1, p0); + mask = Sse2.Max(mask, Abs(t2, t1)); + mask = Sse2.Max(mask, Abs(t1, p1)); + + // Beginning of q0. + offset += 4; + + Load16x4(ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride, out Vector128 q0, out Vector128 q1, out t1, out t2); + + mask = Sse2.Max(mask, Abs(q1, q0)); + mask = Sse2.Max(mask, Abs(t2, t1)); + mask = Sse2.Max(mask, Abs(t1, q1)); + + ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); + DoFilter4Sse2(ref p1, ref p0, ref q0, ref q1, mask, hevThresh); + + // Beginning of p1. + offset -= 2; + Store16x4(p1, p0, q0, q1, ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride); + } + else +#endif + { + int offsetPlus4 = offset + 4; + FilterLoop24(u, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh); + FilterLoop24(v, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh); + } } public static void Mean16x4(Span input, Span dc) @@ -1153,6 +1959,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } + // Applies filter on 2 pixels (p0 and q0) private static void DoFilter2(Span p, int offset, int step) { // 4 pixels in, 2 pixels out. @@ -1167,6 +1974,143 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy p[offset] = WebpLookupTables.Clip1(q0 - a1); } +#if SUPPORTS_RUNTIME_INTRINSICS + // Applies filter on 2 pixels (p0 and q0) + private static void DoFilter2Sse2(ref Vector128 p1, ref Vector128 p0, ref Vector128 q0, ref Vector128 q1, int thresh) + { + // Convert p1/q1 to byte (for GetBaseDelta). + Vector128 p1s = Sse2.Xor(p1, SignBit); + Vector128 q1s = Sse2.Xor(q1, SignBit); + Vector128 mask = NeedsFilter(p1, p0, q0, q1, thresh); + + // Flip sign. + p0 = Sse2.Xor(p0, SignBit); + q0 = Sse2.Xor(q0, SignBit); + + Vector128 a = GetBaseDelta(p1s.AsSByte(), p0.AsSByte(), q0.AsSByte(), q1s.AsSByte()).AsByte(); + + // Mask filter values we don't care about. + a = Sse2.And(a, mask); + + DoSimpleFilterSse2(ref p0, ref q0, a); + + // Flip sign. + p0 = Sse2.Xor(p0, SignBit); + q0 = Sse2.Xor(q0, SignBit); + } + + // Applies filter on 4 pixels (p1, p0, q0 and q1) + private static void DoFilter4Sse2(ref Vector128 p1, ref Vector128 p0, ref Vector128 q0, ref Vector128 q1, Vector128 mask, int tresh) + { + // Compute hev mask. + Vector128 notHev = GetNotHev(ref p1, ref p0, ref q0, ref q1, tresh); + + // Convert to signed values. + p1 = Sse2.Xor(p1, SignBit); + p0 = Sse2.Xor(p0, SignBit); + q0 = Sse2.Xor(q0, SignBit); + q1 = Sse2.Xor(q1, SignBit); + + Vector128 t1 = Sse2.SubtractSaturate(p1.AsSByte(), q1.AsSByte()); // p1 - q1 + t1 = Sse2.AndNot(notHev, t1.AsByte()).AsSByte(); // hev(p1 - q1) + Vector128 t2 = Sse2.SubtractSaturate(q0.AsSByte(), p0.AsSByte()); // q0 - p0 + t1 = Sse2.AddSaturate(t1, t2); // hev(p1 - q1) + 1 * (q0 - p0) + t1 = Sse2.AddSaturate(t1, t2); // hev(p1 - q1) + 2 * (q0 - p0) + t1 = Sse2.AddSaturate(t1, t2); // hev(p1 - q1) + 3 * (q0 - p0) + t1 = Sse2.And(t1.AsByte(), mask).AsSByte(); // mask filter values we don't care about. + + t2 = Sse2.AddSaturate(t1, Three); // 3 * (q0 - p0) + hev(p1 - q1) + 3 + Vector128 t3 = Sse2.AddSaturate(t1, FourSByte); // 3 * (q0 - p0) + hev(p1 - q1) + 4 + t2 = SignedShift8b(t2.AsByte()); // (3 * (q0 - p0) + hev(p1 - q1) + 3) >> 3 + t3 = SignedShift8b(t3.AsByte()); // (3 * (q0 - p0) + hev(p1 - q1) + 4) >> 3 + p0 = Sse2.AddSaturate(p0.AsSByte(), t2).AsByte(); // p0 += t2 + q0 = Sse2.SubtractSaturate(q0.AsSByte(), t3).AsByte(); // q0 -= t3 + p0 = Sse2.Xor(p0, SignBit); + q0 = Sse2.Xor(q0, SignBit); + + // This is equivalent to signed (a + 1) >> 1 calculation. + t2 = Sse2.Add(t3, SignBit.AsSByte()); + t3 = Sse2.Average(t2.AsByte(), Vector128.Zero).AsSByte(); + t3 = Sse2.Subtract(t3, SixtyFour); + + t3 = Sse2.And(notHev, t3.AsByte()).AsSByte(); // if !hev + q1 = Sse2.SubtractSaturate(q1.AsSByte(), t3).AsByte(); // q1 -= t3 + p1 = Sse2.AddSaturate(p1.AsSByte(), t3).AsByte(); // p1 += t3 + p1 = Sse2.Xor(p1.AsByte(), SignBit); + q1 = Sse2.Xor(q1.AsByte(), SignBit); + } + + // Applies filter on 6 pixels (p2, p1, p0, q0, q1 and q2) + private static void DoFilter6Sse2(ref Vector128 p2, ref Vector128 p1, ref Vector128 p0, ref Vector128 q0, ref Vector128 q1, ref Vector128 q2, Vector128 mask, int tresh) + { + // Compute hev mask. + Vector128 notHev = GetNotHev(ref p1, ref p0, ref q0, ref q1, tresh); + + // Convert to signed values. + p1 = Sse2.Xor(p1, SignBit); + p0 = Sse2.Xor(p0, SignBit); + q0 = Sse2.Xor(q0, SignBit); + q1 = Sse2.Xor(q1, SignBit); + p2 = Sse2.Xor(p2, SignBit); + q2 = Sse2.Xor(q2, SignBit); + + Vector128 a = GetBaseDelta(p1.AsSByte(), p0.AsSByte(), q0.AsSByte(), q1.AsSByte()); + + // Do simple filter on pixels with hev. + Vector128 m = Sse2.AndNot(notHev, mask); + Vector128 f = Sse2.And(a.AsByte(), m); + DoSimpleFilterSse2(ref p0, ref q0, f); + + // Do strong filter on pixels with not hev. + m = Sse2.And(notHev, mask); + f = Sse2.And(a.AsByte(), m); + Vector128 flow = Sse2.UnpackLow(Vector128.Zero, f); + Vector128 fhigh = Sse2.UnpackHigh(Vector128.Zero, f); + + Vector128 f9Low = Sse2.MultiplyHigh(flow.AsInt16(), Nine.AsInt16()); // Filter (lo) * 9 + Vector128 f9High = Sse2.MultiplyHigh(fhigh.AsInt16(), Nine.AsInt16()); // Filter (hi) * 9 + + Vector128 a2Low = Sse2.Add(f9Low, SixtyThree.AsInt16()); // Filter * 9 + 63 + Vector128 a2High = Sse2.Add(f9High, SixtyThree.AsInt16()); // Filter * 9 + 63 + + Vector128 a1Low = Sse2.Add(a2Low, f9Low); // Filter * 18 + 63 + Vector128 a1High = Sse2.Add(a2High, f9High); // // Filter * 18 + 63 + + Vector128 a0Low = Sse2.Add(a1Low, f9Low); // Filter * 27 + 63 + Vector128 a0High = Sse2.Add(a1High, f9High); // Filter * 27 + 63 + + Update2Pixels(ref p2, ref q2, a2Low, a2High); + Update2Pixels(ref p1, ref q1, a1Low, a1High); + Update2Pixels(ref p0, ref q0, a0Low, a0High); + } + + private static void DoSimpleFilterSse2(ref Vector128 p0, ref Vector128 q0, Vector128 fl) + { + Vector128 v3 = Sse2.AddSaturate(fl.AsSByte(), Three); + Vector128 v4 = Sse2.AddSaturate(fl.AsSByte(), FourSByte); + + v4 = SignedShift8b(v4.AsByte()).AsSByte(); // v4 >> 3 + v3 = SignedShift8b(v3.AsByte()).AsSByte(); // v3 >> 3 + q0 = Sse2.SubtractSaturate(q0.AsSByte(), v4).AsByte(); // q0 -= v4 + p0 = Sse2.AddSaturate(p0.AsSByte(), v3).AsByte(); // p0 += v3 + } + + private static Vector128 GetNotHev(ref Vector128 p1, ref Vector128 p0, ref Vector128 q0, ref Vector128 q1, int hevThresh) + { + Vector128 t1 = Abs(p1, p0); + Vector128 t2 = Abs(q1, q0); + + var h = Vector128.Create((byte)hevThresh); + Vector128 tMax = Sse2.Max(t1, t2); + + Vector128 tMaxH = Sse2.SubtractSaturate(tMax, h); + + // not_hev <= t1 && not_hev <= t2 + return Sse2.CompareEqual(tMaxH, Vector128.Zero); + } +#endif + + // Applies filter on 4 pixels (p1, p0, q0 and q1) private static void DoFilter4(Span p, int offset, int step) { // 4 pixels in, 4 pixels out. @@ -1185,6 +2129,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy p[offset + step] = WebpLookupTables.Clip1(q1 - a3); } + // Applies filter on 6 pixels (p2, p1, p0, q0, q1 and q2) private static void DoFilter6(Span p, int offset, int step) { // 6 pixels in, 6 pixels out. @@ -1243,6 +2188,201 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy WebpLookupTables.Abs0(q2 - q1) <= it && WebpLookupTables.Abs0(q1 - q0) <= it; } +#if SUPPORTS_RUNTIME_INTRINSICS + private static Vector128 NeedsFilter(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1, int thresh) + { + var mthresh = Vector128.Create((byte)thresh); + Vector128 t1 = Abs(p1, q1); // abs(p1 - q1) + var fe = Vector128.Create((byte)0xFE); + Vector128 t2 = Sse2.And(t1, fe); // set lsb of each byte to zero. + Vector128 t3 = Sse2.ShiftRightLogical(t2.AsInt16(), 1); // abs(p1 - q1) / 2 + + Vector128 t4 = Abs(p0, q0); // abs(p0 - q0) + Vector128 t5 = Sse2.AddSaturate(t4, t4); // abs(p0 - q0) * 2 + Vector128 t6 = Sse2.AddSaturate(t5.AsByte(), t3.AsByte()); // abs(p0-q0)*2 + abs(p1-q1)/2 + + Vector128 t7 = Sse2.SubtractSaturate(t6, mthresh.AsByte()); // mask <= m_thresh + + return Sse2.CompareEqual(t7, Vector128.Zero); + } + + private static void Load16x4(ref byte r0, ref byte r8, int stride, out Vector128 p1, out Vector128 p0, out Vector128 q0, out Vector128 q1) + { + // Assume the pixels around the edge (|) are numbered as follows + // 00 01 | 02 03 + // 10 11 | 12 13 + // ... | ... + // e0 e1 | e2 e3 + // f0 f1 | f2 f3 + // + // r0 is pointing to the 0th row (00) + // r8 is pointing to the 8th row (80) + + // Load + // p1 = 71 61 51 41 31 21 11 01 70 60 50 40 30 20 10 00 + // q0 = 73 63 53 43 33 23 13 03 72 62 52 42 32 22 12 02 + // p0 = f1 e1 d1 c1 b1 a1 91 81 f0 e0 d0 c0 b0 a0 90 80 + // q1 = f3 e3 d3 c3 b3 a3 93 83 f2 e2 d2 c2 b2 a2 92 82 + Load8x4(ref r0, stride, out Vector128 t1, out Vector128 t2); + Load8x4(ref r8, stride, out p0, out q1); + + // p1 = f0 e0 d0 c0 b0 a0 90 80 70 60 50 40 30 20 10 00 + // p0 = f1 e1 d1 c1 b1 a1 91 81 71 61 51 41 31 21 11 01 + // q0 = f2 e2 d2 c2 b2 a2 92 82 72 62 52 42 32 22 12 02 + // q1 = f3 e3 d3 c3 b3 a3 93 83 73 63 53 43 33 23 13 03 + p1 = Sse2.UnpackLow(t1.AsInt64(), p0.AsInt64()).AsByte(); + p0 = Sse2.UnpackHigh(t1.AsInt64(), p0.AsInt64()).AsByte(); + q0 = Sse2.UnpackLow(t2.AsInt64(), q1.AsInt64()).AsByte(); + q1 = Sse2.UnpackHigh(t2.AsInt64(), q1.AsInt64()).AsByte(); + } + + // Reads 8 rows across a vertical edge. + private static void Load8x4(ref byte bRef, int stride, out Vector128 p, out Vector128 q) + { + // A0 = 63 62 61 60 23 22 21 20 43 42 41 40 03 02 01 00 + // A1 = 73 72 71 70 33 32 31 30 53 52 51 50 13 12 11 10 + uint a00 = Unsafe.As(ref Unsafe.Add(ref bRef, 6 * stride)); + uint a01 = Unsafe.As(ref Unsafe.Add(ref bRef, 2 * stride)); + uint a02 = Unsafe.As(ref Unsafe.Add(ref bRef, 4 * stride)); + uint a03 = Unsafe.As(ref Unsafe.Add(ref bRef, 0 * stride)); + Vector128 a0 = Vector128.Create(a03, a02, a01, a00).AsByte(); + uint a10 = Unsafe.As(ref Unsafe.Add(ref bRef, 7 * stride)); + uint a11 = Unsafe.As(ref Unsafe.Add(ref bRef, 3 * stride)); + uint a12 = Unsafe.As(ref Unsafe.Add(ref bRef, 5 * stride)); + uint a13 = Unsafe.As(ref Unsafe.Add(ref bRef, 1 * stride)); + Vector128 a1 = Vector128.Create(a13, a12, a11, a10).AsByte(); + + // B0 = 53 43 52 42 51 41 50 40 13 03 12 02 11 01 10 00 + // B1 = 73 63 72 62 71 61 70 60 33 23 32 22 31 21 30 20 + Vector128 b0 = Sse2.UnpackLow(a0.AsSByte(), a1.AsSByte()); + Vector128 b1 = Sse2.UnpackHigh(a0.AsSByte(), a1.AsSByte()); + + // C0 = 33 23 13 03 32 22 12 02 31 21 11 01 30 20 10 00 + // C1 = 73 63 53 43 72 62 52 42 71 61 51 41 70 60 50 40 + Vector128 c0 = Sse2.UnpackLow(b0.AsInt16(), b1.AsInt16()); + Vector128 c1 = Sse2.UnpackHigh(b0.AsInt16(), b1.AsInt16()); + + // *p = 71 61 51 41 31 21 11 01 70 60 50 40 30 20 10 00 + // *q = 73 63 53 43 33 23 13 03 72 62 52 42 32 22 12 02 + p = Sse2.UnpackLow(c0.AsInt32(), c1.AsInt32()).AsByte(); + q = Sse2.UnpackHigh(c0.AsInt32(), c1.AsInt32()).AsByte(); + } + + // Transpose back and store + private static void Store16x4(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1, ref byte r0Ref, ref byte r8Ref, int stride) + { + // p0 = 71 70 61 60 51 50 41 40 31 30 21 20 11 10 01 00 + // p1 = f1 f0 e1 e0 d1 d0 c1 c0 b1 b0 a1 a0 91 90 81 80 + Vector128 p0s = Sse2.UnpackLow(p1, p0); + Vector128 p1s = Sse2.UnpackHigh(p1, p0); + + // q0 = 73 72 63 62 53 52 43 42 33 32 23 22 13 12 03 02 + // q1 = f3 f2 e3 e2 d3 d2 c3 c2 b3 b2 a3 a2 93 92 83 82 + Vector128 q0s = Sse2.UnpackLow(q0, q1); + Vector128 q1s = Sse2.UnpackHigh(q0, q1); + + // p0 = 33 32 31 30 23 22 21 20 13 12 11 10 03 02 01 00 + // q0 = 73 72 71 70 63 62 61 60 53 52 51 50 43 42 41 40 + Vector128 t1 = p0s; + p0s = Sse2.UnpackLow(t1.AsInt16(), q0s.AsInt16()).AsByte(); + q0s = Sse2.UnpackHigh(t1.AsInt16(), q0s.AsInt16()).AsByte(); + + // p1 = b3 b2 b1 b0 a3 a2 a1 a0 93 92 91 90 83 82 81 80 + // q1 = f3 f2 f1 f0 e3 e2 e1 e0 d3 d2 d1 d0 c3 c2 c1 c0 + t1 = p1s; + p1s = Sse2.UnpackLow(t1.AsInt16(), q1s.AsInt16()).AsByte(); + q1s = Sse2.UnpackHigh(t1.AsInt16(), q1s.AsInt16()).AsByte(); + + Store4x4(p0s, ref r0Ref, stride); + Store4x4(q0s, ref Unsafe.Add(ref r0Ref, 4 * stride), stride); + + Store4x4(p1s, ref r8Ref, stride); + Store4x4(q1s, ref Unsafe.Add(ref r8Ref, 4 * stride), stride); + } + + private static void Store4x4(Vector128 x, ref byte dstRef, int stride) + { + int offset = 0; + for (int i = 0; i < 4; i++) + { + Unsafe.As(ref Unsafe.Add(ref dstRef, offset)) = Sse2.ConvertToInt32(x.AsInt32()); + x = Sse2.ShiftRightLogical128BitLane(x, 4); + offset += stride; + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector128 GetBaseDelta(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1) + { + // Beware of addition order, for saturation! + Vector128 p1q1 = Sse2.SubtractSaturate(p1, q1); // p1 - q1 + Vector128 q0p0 = Sse2.SubtractSaturate(q0, p0); // q0 - p0 + Vector128 s1 = Sse2.AddSaturate(p1q1, q0p0); // p1 - q1 + 1 * (q0 - p0) + Vector128 s2 = Sse2.AddSaturate(q0p0, s1); // p1 - q1 + 2 * (q0 - p0) + Vector128 s3 = Sse2.AddSaturate(q0p0, s2); // p1 - q1 + 3 * (q0 - p0) + + return s3; + } + + // Shift each byte of "x" by 3 bits while preserving by the sign bit. + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector128 SignedShift8b(Vector128 x) + { + Vector128 low0 = Sse2.UnpackLow(Vector128.Zero, x); + Vector128 high0 = Sse2.UnpackHigh(Vector128.Zero, x); + Vector128 low1 = Sse2.ShiftRightArithmetic(low0.AsInt16(), 3 + 8); + Vector128 high1 = Sse2.ShiftRightArithmetic(high0.AsInt16(), 3 + 8); + + return Sse2.PackSignedSaturate(low1, high1); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void ComplexMask(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1, int thresh, int ithresh, ref Vector128 mask) + { + var it = Vector128.Create((byte)ithresh); + Vector128 diff = Sse2.SubtractSaturate(mask, it); + Vector128 threshMask = Sse2.CompareEqual(diff, Vector128.Zero); + Vector128 filterMask = NeedsFilter(p1, p0, q0, q1, thresh); + + mask = Sse2.And(threshMask, filterMask); + } + + // Updates values of 2 pixels at MB edge during complex filtering. + // Update operations: + // q = q - delta and p = p + delta; where delta = [(a_hi >> 7), (a_lo >> 7)] + // Pixels 'pi' and 'qi' are int8_t on input, uint8_t on output (sign flip). + private static void Update2Pixels(ref Vector128 pi, ref Vector128 qi, Vector128 a0Low, Vector128 a0High) + { + Vector128 a1Low = Sse2.ShiftRightArithmetic(a0Low, 7); + Vector128 a1High = Sse2.ShiftRightArithmetic(a0High, 7); + Vector128 delta = Sse2.PackSignedSaturate(a1Low, a1High); + pi = Sse2.AddSaturate(pi.AsSByte(), delta).AsByte(); + qi = Sse2.SubtractSaturate(qi.AsSByte(), delta).AsByte(); + pi = Sse2.Xor(pi, SignBit.AsByte()); + qi = Sse2.Xor(qi, SignBit.AsByte()); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector128 LoadUvEdge(ref byte uRef, ref byte vRef, int offset) + { + var uVec = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref uRef, offset)), 0); + var vVec = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref vRef, offset)), 0); + return Sse2.UnpackLow(uVec, vVec).AsByte(); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void StoreUv(Vector128 x, ref byte uRef, ref byte vRef, int offset) + { + Unsafe.As>(ref Unsafe.Add(ref uRef, offset)) = x.GetLower(); + Unsafe.As>(ref Unsafe.Add(ref vRef, offset)) = x.GetUpper(); + } + + // Compute abs(p - q) = subs(p - q) OR subs(q - p) + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector128 Abs(Vector128 p, Vector128 q) + => Sse2.Or(Sse2.SubtractSaturate(q, p), Sse2.SubtractSaturate(p, q)); +#endif + [MethodImpl(InliningOptions.ShortMethod)] private static bool Hev(Span p, int offset, int step, int thresh) { @@ -1287,14 +2427,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - private static void Memset(Span dst, byte value, int startIdx, int count) - { - int end = startIdx + count; - for (int i = startIdx; i < end; i++) - { - dst[i] = value; - } - } + private static void Memset(Span dst, byte value, int startIdx, int count) => dst.Slice(startIdx, count).Fill(value); [MethodImpl(InliningOptions.ShortMethod)] private static int Clamp255(int x) => x < 0 ? 0 : x > 255 ? 255 : x; diff --git a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs index 2fcea8cee..2a40cffdc 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs @@ -25,6 +25,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy #if SUPPORTS_RUNTIME_INTRINSICS private static readonly Vector128 MaxCoeff2047 = Vector128.Create((short)MaxLevel); + private static readonly Vector256 MaxCoeff2047Vec256 = Vector256.Create((short)MaxLevel); + + private static readonly Vector256 Cst256 = Vector256.Create(0, 1, 2, 3, 8, 9, 254, 255, 10, 11, 4, 5, 6, 7, 12, 13, 2, 3, 8, 9, 10, 11, 4, 5, 254, 255, 6, 7, 12, 13, 14, 15); + + private static readonly Vector256 Cst78 = Vector256.Create(254, 255, 254, 255, 254, 255, 254, 255, 14, 15, 254, 255, 254, 255, 254, 255, 254, 255, 254, 255, 254, 255, 0, 1, 254, 255, 254, 255, 254, 255, 254, 255); + private static readonly Vector128 CstLo = Vector128.Create(0, 1, 2, 3, 8, 9, 254, 255, 10, 11, 4, 5, 6, 7, 12, 13); private static readonly Vector128 Cst7 = Vector128.Create(254, 255, 254, 255, 254, 255, 254, 255, 14, 15, 254, 255, 254, 255, 254, 255); @@ -329,7 +335,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy LossyUtils.TransformWht(dcTmp, tmp, scratch); for (n = 0; n < 16; n += 2) { - Vp8Encoding.ITransform(reference.Slice(WebpLookupTables.Vp8Scan[n]), tmp.Slice(n * 16, 32), yuvOut.Slice(WebpLookupTables.Vp8Scan[n]), scratch); + Vp8Encoding.ITransformTwo(reference.Slice(WebpLookupTables.Vp8Scan[n]), tmp.Slice(n * 16, 32), yuvOut.Slice(WebpLookupTables.Vp8Scan[n]), scratch); } return nz; @@ -375,7 +381,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy for (n = 0; n < 8; n += 2) { - Vp8Encoding.ITransform(reference.Slice(WebpLookupTables.Vp8ScanUv[n]), tmp.Slice(n * 16, 32), yuvOut.Slice(WebpLookupTables.Vp8ScanUv[n]), scratch); + Vp8Encoding.ITransformTwo(reference.Slice(WebpLookupTables.Vp8ScanUv[n]), tmp.Slice(n * 16, 32), yuvOut.Slice(WebpLookupTables.Vp8ScanUv[n]), scratch); } return nz << 16; @@ -531,7 +537,70 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static int QuantizeBlock(Span input, Span output, ref Vp8Matrix mtx) { #if SUPPORTS_RUNTIME_INTRINSICS - if (Sse41.IsSupported) + if (Avx2.IsSupported) + { + // Load all inputs. + Vector256 input0 = Unsafe.As>(ref MemoryMarshal.GetReference(input)); + Vector256 iq0 = Unsafe.As>(ref mtx.IQ[0]); + Vector256 q0 = Unsafe.As>(ref mtx.Q[0]); + + // coeff = abs(in) + Vector256 coeff0 = Avx2.Abs(input0); + + // coeff = abs(in) + sharpen + Vector256 sharpen0 = Unsafe.As>(ref mtx.Sharpen[0]); + Avx2.Add(coeff0.AsInt16(), sharpen0); + + // out = (coeff * iQ + B) >> QFIX + // doing calculations with 32b precision (QFIX=17) + // out = (coeff * iQ) + Vector256 coeffiQ0H = Avx2.MultiplyHigh(coeff0, iq0); + Vector256 coeffiQ0L = Avx2.MultiplyLow(coeff0, iq0); + Vector256 out00 = Avx2.UnpackLow(coeffiQ0L, coeffiQ0H); + Vector256 out08 = Avx2.UnpackHigh(coeffiQ0L, coeffiQ0H); + + // out = (coeff * iQ + B) + Vector256 bias00 = Unsafe.As>(ref mtx.Bias[0]); + Vector256 bias08 = Unsafe.As>(ref mtx.Bias[8]); + out00 = Avx2.Add(out00.AsInt32(), bias00.AsInt32()).AsUInt16(); + out08 = Avx2.Add(out08.AsInt32(), bias08.AsInt32()).AsUInt16(); + + // out = QUANTDIV(coeff, iQ, B, QFIX) + out00 = Avx2.ShiftRightArithmetic(out00.AsInt32(), WebpConstants.QFix).AsUInt16(); + out08 = Avx2.ShiftRightArithmetic(out08.AsInt32(), WebpConstants.QFix).AsUInt16(); + + // Pack result as 16b. + Vector256 out0 = Avx2.PackSignedSaturate(out00.AsInt32(), out08.AsInt32()); + + // if (coeff > 2047) coeff = 2047 + out0 = Avx2.Min(out0, MaxCoeff2047Vec256); + + // Put the sign back. + out0 = Avx2.Sign(out0, input0); + + // in = out * Q + input0 = Avx2.MultiplyLow(out0, q0.AsInt16()); + ref short inputRef = ref MemoryMarshal.GetReference(input); + Unsafe.As>(ref inputRef) = input0; + + // zigzag the output before storing it. + Vector256 tmp256 = Avx2.Shuffle(out0.AsByte(), Cst256); + Vector256 tmp78 = Avx2.Shuffle(out0.AsByte(), Cst78); + + // Reverse the order of the 16-byte lanes. + Vector256 tmp87 = Avx2.Permute2x128(tmp78, tmp78, 1); + Vector256 outZ = Avx2.Or(tmp256, tmp87).AsInt16(); + + ref short outputRef = ref MemoryMarshal.GetReference(output); + Unsafe.As>(ref outputRef) = outZ; + + Vector256 packedOutput = Avx2.PackSignedSaturate(outZ, outZ); + + // Detect if all 'out' values are zeros or not. + Vector256 cmpeq = Avx2.CompareEqual(packedOutput, Vector256.Zero); + return Avx2.MoveMask(cmpeq) != -1 ? 1 : 0; + } + else if (Sse41.IsSupported) { // Load all inputs. Vector128 input0 = Unsafe.As>(ref MemoryMarshal.GetReference(input)); @@ -579,7 +648,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy out08 = Sse2.ShiftRightArithmetic(out08.AsInt32(), WebpConstants.QFix).AsUInt16(); out12 = Sse2.ShiftRightArithmetic(out12.AsInt32(), WebpConstants.QFix).AsUInt16(); - // pack result as 16b + // Pack result as 16b. Vector128 out0 = Sse2.PackSignedSaturate(out00.AsInt32(), out04.AsInt32()); Vector128 out8 = Sse2.PackSignedSaturate(out08.AsInt32(), out12.AsInt32()); @@ -587,7 +656,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy out0 = Sse2.Min(out0, MaxCoeff2047); out8 = Sse2.Min(out8, MaxCoeff2047); - // put sign back + // Put the sign back. out0 = Ssse3.Sign(out0, input0); out8 = Ssse3.Sign(out8, input8); @@ -726,7 +795,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { uint v = src[0] * 0x01010101u; Span vSpan = BitConverter.GetBytes(v).AsSpan(); - for (int i = 0; i < 16; i++) + for (nint i = 0; i < 16; i++) { if (!src.Slice(0, 4).SequenceEqual(vSpan) || !src.Slice(4, 4).SequenceEqual(vSpan) || !src.Slice(8, 4).SequenceEqual(vSpan) || !src.Slice(12, 4).SequenceEqual(vSpan)) @@ -744,19 +813,21 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private static bool IsFlat(Span levels, int numBlocks, int thresh) { int score = 0; + ref short levelsRef = ref MemoryMarshal.GetReference(levels); + int offset = 0; while (numBlocks-- > 0) { - for (int i = 1; i < 16; i++) + for (nint i = 1; i < 16; i++) { // omit DC, we're only interested in AC - score += levels[i] != 0 ? 1 : 0; + score += Unsafe.Add(ref levelsRef, offset) != 0 ? 1 : 0; if (score > thresh) { return false; } } - levels = levels.Slice(16); + offset += 16; } return true; diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs index fcd61f2c0..eb8d92110 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs @@ -358,7 +358,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int kThreshold = 8 + ((17 - 8) * q / 100); int k; Span dc = stackalloc uint[16]; - Span tmp = stackalloc ushort[16]; uint m; uint m2; for (k = 0; k < 16; k += 4) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs index aa4ab5767..65b1d07d3 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// /// Methods for encoding a VP8 frame. /// - internal static class Vp8Encoding + internal static unsafe class Vp8Encoding { private const int KC1 = 20091 + (1 << 16); @@ -66,11 +66,39 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static readonly int[] Vp8I4ModeOffsets = { I4DC4, I4TM4, I4VE4, I4HE4, I4RD4, I4VR4, I4LD4, I4VL4, I4HD4, I4HU4 }; #if SUPPORTS_RUNTIME_INTRINSICS - public static readonly Vector128 K1 = Vector128.Create((short)20091).AsInt16(); +#pragma warning disable SA1310 // Field names should not contain underscore + private static readonly Vector128 K1 = Vector128.Create((short)20091).AsInt16(); - public static readonly Vector128 K2 = Vector128.Create((short)-30068).AsInt16(); + private static readonly Vector128 K2 = Vector128.Create((short)-30068).AsInt16(); - public static readonly Vector128 Four = Vector128.Create((short)4); + private static readonly Vector128 Four = Vector128.Create((short)4); + + private static readonly Vector128 Seven = Vector128.Create((short)7); + + private static readonly Vector128 K88p = Vector128.Create(8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0).AsInt16(); + + private static readonly Vector128 K88m = Vector128.Create(8, 0, 248, 255, 8, 0, 248, 255, 8, 0, 248, 255, 8, 0, 248, 255).AsInt16(); + + private static readonly Vector128 K5352_2217p = Vector128.Create(232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8).AsInt16(); + + private static readonly Vector128 K5352_2217m = Vector128.Create(169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235).AsInt16(); + + private static readonly Vector128 K937 = Vector128.Create(937); + + private static readonly Vector128 K1812 = Vector128.Create(1812); + + private static readonly Vector128 K5352_2217 = Vector128.Create(169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20).AsInt16(); + + private static readonly Vector128 K2217_5352 = Vector128.Create(24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8).AsInt16(); + + private static readonly Vector128 K12000PlusOne = Vector128.Create(12000 + (1 << 16)); + + private static readonly Vector128 K51000 = Vector128.Create(51000); + + private static readonly byte MmShuffle2301 = SimdUtils.Shuffle.MmShuffle(2, 3, 0, 1); + + private static readonly byte MmShuffle1032 = SimdUtils.Shuffle.MmShuffle(1, 0, 3, 2); +#pragma warning restore SA1310 // Field names should not contain underscore #endif static Vp8Encoding() @@ -83,7 +111,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Transforms (Paragraph 14.4) // Does two inverse transforms. - public static void ITransform(Span reference, Span input, Span dst, Span scratch) + public static void ITransformTwo(Span reference, Span input, Span dst, Span scratch) { #if SUPPORTS_RUNTIME_INTRINSICS if (Sse2.IsSupported) @@ -180,10 +208,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy ref2 = Sse2.PackUnsignedSaturate(ref2InvAdded, ref2InvAdded); ref3 = Sse2.PackUnsignedSaturate(ref3InvAdded, ref3InvAdded); - // Unsigned saturate to 8b. - ref byte outputRef = ref MemoryMarshal.GetReference(dst); - // Store eight bytes/pixels per line. + ref byte outputRef = ref MemoryMarshal.GetReference(dst); Unsafe.As>(ref outputRef) = ref0.GetLower(); Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps)) = ref1.GetLower(); Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 2)) = ref2.GetLower(); @@ -376,49 +402,246 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static void FTransform2(Span src, Span reference, Span output, Span output2, Span scratch) { - FTransform(src, reference, output, scratch); - FTransform(src.Slice(4), reference.Slice(4), output2, scratch); +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + ref byte srcRef = ref MemoryMarshal.GetReference(src); + ref byte referenceRef = ref MemoryMarshal.GetReference(reference); + + // Load src. + var src0 = Vector128.Create(Unsafe.As(ref srcRef), 0); + var src1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps)), 0); + var src2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps * 2)), 0); + var src3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps * 3)), 0); + + // Load ref. + var ref0 = Vector128.Create(Unsafe.As(ref referenceRef), 0); + var ref1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps)), 0); + var ref2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 2)), 0); + var ref3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 3)), 0); + + // Convert both to 16 bit. + Vector128 srcLow0 = Sse2.UnpackLow(src0.AsByte(), Vector128.Zero); + Vector128 srcLow1 = Sse2.UnpackLow(src1.AsByte(), Vector128.Zero); + Vector128 srcLow2 = Sse2.UnpackLow(src2.AsByte(), Vector128.Zero); + Vector128 srcLow3 = Sse2.UnpackLow(src3.AsByte(), Vector128.Zero); + Vector128 refLow0 = Sse2.UnpackLow(ref0.AsByte(), Vector128.Zero); + Vector128 refLow1 = Sse2.UnpackLow(ref1.AsByte(), Vector128.Zero); + Vector128 refLow2 = Sse2.UnpackLow(ref2.AsByte(), Vector128.Zero); + Vector128 refLow3 = Sse2.UnpackLow(ref3.AsByte(), Vector128.Zero); + + // Compute difference. -> 00 01 02 03 00' 01' 02' 03' + Vector128 diff0 = Sse2.Subtract(srcLow0.AsInt16(), refLow0.AsInt16()); + Vector128 diff1 = Sse2.Subtract(srcLow1.AsInt16(), refLow1.AsInt16()); + Vector128 diff2 = Sse2.Subtract(srcLow2.AsInt16(), refLow2.AsInt16()); + Vector128 diff3 = Sse2.Subtract(srcLow3.AsInt16(), refLow3.AsInt16()); + + // Unpack and shuffle. + // 00 01 02 03 0 0 0 0 + // 10 11 12 13 0 0 0 0 + // 20 21 22 23 0 0 0 0 + // 30 31 32 33 0 0 0 0 + Vector128 shuf01l = Sse2.UnpackLow(diff0.AsInt32(), diff1.AsInt32()); + Vector128 shuf23l = Sse2.UnpackLow(diff2.AsInt32(), diff3.AsInt32()); + Vector128 shuf01h = Sse2.UnpackHigh(diff0.AsInt32(), diff1.AsInt32()); + Vector128 shuf23h = Sse2.UnpackHigh(diff2.AsInt32(), diff3.AsInt32()); + + // First pass. + FTransformPass1SSE2(shuf01l.AsInt16(), shuf23l.AsInt16(), out Vector128 v01l, out Vector128 v32l); + FTransformPass1SSE2(shuf01h.AsInt16(), shuf23h.AsInt16(), out Vector128 v01h, out Vector128 v32h); + + // Second pass. + FTransformPass2SSE2(v01l, v32l, output); + FTransformPass2SSE2(v01h, v32h, output2); + } + else +#endif + { + FTransform(src, reference, output, scratch); + FTransform(src.Slice(4), reference.Slice(4), output2, scratch); + } } public static void FTransform(Span src, Span reference, Span output, Span scratch) { - int i; - Span tmp = scratch.Slice(0, 16); - - int srcIdx = 0; - int refIdx = 0; - for (i = 0; i < 4; i++) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) { - int d0 = src[srcIdx] - reference[refIdx]; // 9bit dynamic range ([-255,255]) - int d1 = src[srcIdx + 1] - reference[refIdx + 1]; - int d2 = src[srcIdx + 2] - reference[refIdx + 2]; - int d3 = src[srcIdx + 3] - reference[refIdx + 3]; - int a0 = d0 + d3; // 10b [-510,510] - int a1 = d1 + d2; - int a2 = d1 - d2; - int a3 = d0 - d3; - tmp[0 + (i * 4)] = (a0 + a1) * 8; // 14b [-8160,8160] - tmp[1 + (i * 4)] = ((a2 * 2217) + (a3 * 5352) + 1812) >> 9; // [-7536,7542] - tmp[2 + (i * 4)] = (a0 - a1) * 8; - tmp[3 + (i * 4)] = ((a3 * 2217) - (a2 * 5352) + 937) >> 9; - - srcIdx += WebpConstants.Bps; - refIdx += WebpConstants.Bps; - } + ref byte srcRef = ref MemoryMarshal.GetReference(src); + ref byte referenceRef = ref MemoryMarshal.GetReference(reference); - for (i = 0; i < 4; i++) + // Load src. + var src0 = Vector128.Create(Unsafe.As(ref srcRef), 0); + var src1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps)), 0); + var src2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps * 2)), 0); + var src3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps * 3)), 0); + + // Load ref. + var ref0 = Vector128.Create(Unsafe.As(ref referenceRef), 0); + var ref1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps)), 0); + var ref2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 2)), 0); + var ref3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 3)), 0); + + // 00 01 02 03 * + // 10 11 12 13 * + // 20 21 22 23 * + // 30 31 32 33 * + // Shuffle. + Vector128 srcLow0 = Sse2.UnpackLow(src0.AsInt16(), src1.AsInt16()); + Vector128 srcLow1 = Sse2.UnpackLow(src2.AsInt16(), src3.AsInt16()); + Vector128 refLow0 = Sse2.UnpackLow(ref0.AsInt16(), ref1.AsInt16()); + Vector128 refLow1 = Sse2.UnpackLow(ref2.AsInt16(), ref3.AsInt16()); + + // 00 01 10 11 02 03 12 13 * * ... + // 20 21 30 31 22 22 32 33 * * ... + + // Convert both to 16 bit. + Vector128 src0_16b = Sse2.UnpackLow(srcLow0.AsByte(), Vector128.Zero); + Vector128 src1_16b = Sse2.UnpackLow(srcLow1.AsByte(), Vector128.Zero); + Vector128 ref0_16b = Sse2.UnpackLow(refLow0.AsByte(), Vector128.Zero); + Vector128 ref1_16b = Sse2.UnpackLow(refLow1.AsByte(), Vector128.Zero); + + // Compute the difference. + Vector128 row01 = Sse2.Subtract(src0_16b.AsInt16(), ref0_16b.AsInt16()); + Vector128 row23 = Sse2.Subtract(src1_16b.AsInt16(), ref1_16b.AsInt16()); + + // First pass. + FTransformPass1SSE2(row01, row23, out Vector128 v01, out Vector128 v32); + + // Second pass. + FTransformPass2SSE2(v01, v32, output); + } + else +#endif { - int a0 = tmp[0 + i] + tmp[12 + i]; // 15b - int a1 = tmp[4 + i] + tmp[8 + i]; - int a2 = tmp[4 + i] - tmp[8 + i]; - int a3 = tmp[0 + i] - tmp[12 + i]; - output[0 + i] = (short)((a0 + a1 + 7) >> 4); // 12b - output[4 + i] = (short)((((a2 * 2217) + (a3 * 5352) + 12000) >> 16) + (a3 != 0 ? 1 : 0)); - output[8 + i] = (short)((a0 - a1 + 7) >> 4); - output[12 + i] = (short)(((a3 * 2217) - (a2 * 5352) + 51000) >> 16); + int i; + Span tmp = scratch.Slice(0, 16); + + int srcIdx = 0; + int refIdx = 0; + for (i = 0; i < 4; i++) + { + int d3 = src[srcIdx + 3] - reference[refIdx + 3]; + int d2 = src[srcIdx + 2] - reference[refIdx + 2]; + int d1 = src[srcIdx + 1] - reference[refIdx + 1]; + int d0 = src[srcIdx] - reference[refIdx]; // 9bit dynamic range ([-255,255]) + int a0 = d0 + d3; // 10b [-510,510] + int a1 = d1 + d2; + int a2 = d1 - d2; + int a3 = d0 - d3; + tmp[3 + (i * 4)] = ((a3 * 2217) - (a2 * 5352) + 937) >> 9; + tmp[2 + (i * 4)] = (a0 - a1) * 8; + tmp[1 + (i * 4)] = ((a2 * 2217) + (a3 * 5352) + 1812) >> 9; // [-7536,7542] + tmp[0 + (i * 4)] = (a0 + a1) * 8; // 14b [-8160,8160] + + srcIdx += WebpConstants.Bps; + refIdx += WebpConstants.Bps; + } + + for (i = 0; i < 4; i++) + { + int t12 = tmp[12 + i]; // 15b + int t8 = tmp[8 + i]; + + int a1 = tmp[4 + i] + t8; + int a2 = tmp[4 + i] - t8; + int a0 = tmp[0 + i] + t12; // 15b + int a3 = tmp[0 + i] - t12; + + output[12 + i] = (short)(((a3 * 2217) - (a2 * 5352) + 51000) >> 16); + output[8 + i] = (short)((a0 - a1 + 7) >> 4); + output[4 + i] = (short)((((a2 * 2217) + (a3 * 5352) + 12000) >> 16) + (a3 != 0 ? 1 : 0)); + output[0 + i] = (short)((a0 + a1 + 7) >> 4); // 12b + } } } +#if SUPPORTS_RUNTIME_INTRINSICS + public static void FTransformPass1SSE2(Vector128 row01, Vector128 row23, out Vector128 out01, out Vector128 out32) + { + // *in01 = 00 01 10 11 02 03 12 13 + // *in23 = 20 21 30 31 22 23 32 33 + Vector128 shuf01_p = Sse2.ShuffleHigh(row01, MmShuffle2301); + Vector128 shuf32_p = Sse2.ShuffleHigh(row23, MmShuffle2301); + + // 00 01 10 11 03 02 13 12 + // 20 21 30 31 23 22 33 32 + Vector128 s01 = Sse2.UnpackLow(shuf01_p.AsInt64(), shuf32_p.AsInt64()); + Vector128 s32 = Sse2.UnpackHigh(shuf01_p.AsInt64(), shuf32_p.AsInt64()); + + // 00 01 10 11 20 21 30 31 + // 03 02 13 12 23 22 33 32 + Vector128 a01 = Sse2.Add(s01.AsInt16(), s32.AsInt16()); + Vector128 a32 = Sse2.Subtract(s01.AsInt16(), s32.AsInt16()); + + // [d0 + d3 | d1 + d2 | ...] = [a0 a1 | a0' a1' | ... ] + // [d0 - d3 | d1 - d2 | ...] = [a3 a2 | a3' a2' | ... ] + Vector128 tmp0 = Sse2.MultiplyAddAdjacent(a01, K88p); // [ (a0 + a1) << 3, ... ] + Vector128 tmp2 = Sse2.MultiplyAddAdjacent(a01, K88m); // [ (a0 - a1) << 3, ... ] + Vector128 tmp11 = Sse2.MultiplyAddAdjacent(a32, K5352_2217p); + Vector128 tmp31 = Sse2.MultiplyAddAdjacent(a32, K5352_2217m); + Vector128 tmp12 = Sse2.Add(tmp11, K1812); + Vector128 tmp32 = Sse2.Add(tmp31, K937); + Vector128 tmp1 = Sse2.ShiftRightArithmetic(tmp12, 9); + Vector128 tmp3 = Sse2.ShiftRightArithmetic(tmp32, 9); + Vector128 s03 = Sse2.PackSignedSaturate(tmp0, tmp2); + Vector128 s12 = Sse2.PackSignedSaturate(tmp1, tmp3); + Vector128 slo = Sse2.UnpackLow(s03, s12); // 0 1 0 1 0 1... + Vector128 shi = Sse2.UnpackHigh(s03, s12); // 2 3 2 3 2 3 + Vector128 v23 = Sse2.UnpackHigh(slo.AsInt32(), shi.AsInt32()); + out01 = Sse2.UnpackLow(slo.AsInt32(), shi.AsInt32()); + out32 = Sse2.Shuffle(v23, MmShuffle1032); + } + + public static void FTransformPass2SSE2(Vector128 v01, Vector128 v32, Span output) + { + // Same operations are done on the (0,3) and (1,2) pairs. + // a3 = v0 - v3 + // a2 = v1 - v2 + Vector128 a32 = Sse2.Subtract(v01.AsInt16(), v32.AsInt16()); + Vector128 a22 = Sse2.UnpackHigh(a32.AsInt64(), a32.AsInt64()); + + Vector128 b23 = Sse2.UnpackLow(a22.AsInt16(), a32.AsInt16()); + Vector128 c1 = Sse2.MultiplyAddAdjacent(b23, K5352_2217); + Vector128 c3 = Sse2.MultiplyAddAdjacent(b23, K2217_5352); + Vector128 d1 = Sse2.Add(c1, K12000PlusOne); + Vector128 d3 = Sse2.Add(c3, K51000); + Vector128 e1 = Sse2.ShiftRightArithmetic(d1, 16); + Vector128 e3 = Sse2.ShiftRightArithmetic(d3, 16); + + // f1 = ((b3 * 5352 + b2 * 2217 + 12000) >> 16) + // f3 = ((b3 * 2217 - b2 * 5352 + 51000) >> 16) + Vector128 f1 = Sse2.PackSignedSaturate(e1, e1); + Vector128 f3 = Sse2.PackSignedSaturate(e3, e3); + + // g1 = f1 + (a3 != 0); + // The compare will return (0xffff, 0) for (==0, !=0). To turn that into the + // desired (0, 1), we add one earlier through k12000_plus_one. + // -> g1 = f1 + 1 - (a3 == 0) + Vector128 g1 = Sse2.Add(f1, Sse2.CompareEqual(a32, Vector128.Zero)); + + // a0 = v0 + v3 + // a1 = v1 + v2 + Vector128 a01 = Sse2.Add(v01.AsInt16(), v32.AsInt16()); + Vector128 a01Plus7 = Sse2.Add(a01.AsInt16(), Seven); + Vector128 a11 = Sse2.UnpackHigh(a01.AsInt64(), a01.AsInt64()).AsInt16(); + Vector128 c0 = Sse2.Add(a01Plus7, a11); + Vector128 c2 = Sse2.Subtract(a01Plus7, a11); + + // d0 = (a0 + a1 + 7) >> 4; + // d2 = (a0 - a1 + 7) >> 4; + Vector128 d0 = Sse2.ShiftRightArithmetic(c0, 4); + Vector128 d2 = Sse2.ShiftRightArithmetic(c2, 4); + + Vector128 d0g1 = Sse2.UnpackLow(d0.AsInt64(), g1.AsInt64()); + Vector128 d2f3 = Sse2.UnpackLow(d2.AsInt64(), f3.AsInt64()); + + ref short outputRef = ref MemoryMarshal.GetReference(output); + Unsafe.As>(ref outputRef) = d0g1.AsInt16(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, 8)) = d2f3.AsInt16(); + } +#endif + public static void FTransformWht(Span input, Span output, Span scratch) { Span tmp = scratch.Slice(0, 16); @@ -427,32 +650,37 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int inputIdx = 0; for (i = 0; i < 4; i++) { - int a0 = input[inputIdx + (0 * 16)] + input[inputIdx + (2 * 16)]; // 13b int a1 = input[inputIdx + (1 * 16)] + input[inputIdx + (3 * 16)]; int a2 = input[inputIdx + (1 * 16)] - input[inputIdx + (3 * 16)]; + int a0 = input[inputIdx + (0 * 16)] + input[inputIdx + (2 * 16)]; // 13b int a3 = input[inputIdx + (0 * 16)] - input[inputIdx + (2 * 16)]; - tmp[0 + (i * 4)] = a0 + a1; // 14b - tmp[1 + (i * 4)] = a3 + a2; - tmp[2 + (i * 4)] = a3 - a2; tmp[3 + (i * 4)] = a0 - a1; + tmp[2 + (i * 4)] = a3 - a2; + tmp[1 + (i * 4)] = a3 + a2; + tmp[0 + (i * 4)] = a0 + a1; // 14b inputIdx += 64; } for (i = 0; i < 4; i++) { - int a0 = tmp[0 + i] + tmp[8 + i]; // 15b - int a1 = tmp[4 + i] + tmp[12 + i]; - int a2 = tmp[4 + i] - tmp[12 + i]; - int a3 = tmp[0 + i] - tmp[8 + i]; + int t12 = tmp[12 + i]; + int t8 = tmp[8 + i]; + + int a1 = tmp[4 + i] + t12; + int a2 = tmp[4 + i] - t12; + int a0 = tmp[0 + i] + t8; // 15b + int a3 = tmp[0 + i] - t8; + int b0 = a0 + a1; // 16b int b1 = a3 + a2; int b2 = a3 - a2; int b3 = a0 - a1; - output[0 + i] = (short)(b0 >> 1); // 15b - output[4 + i] = (short)(b1 >> 1); - output[8 + i] = (short)(b2 >> 1); + output[12 + i] = (short)(b3 >> 1); + output[8 + i] = (short)(b2 >> 1); + output[4 + i] = (short)(b1 >> 1); + output[0 + i] = (short)(b0 >> 1); // 15b } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs index d384302b9..f679fcb13 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs @@ -3,6 +3,11 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif namespace SixLabors.ImageSharp.Formats.Webp.Lossy { @@ -19,6 +24,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// private const int MaxCoeffThresh = 31; +#if SUPPORTS_RUNTIME_INTRINSICS + private static readonly Vector256 MaxCoeffThreshVec = Vector256.Create((short)MaxCoeffThresh); +#endif + private int maxValue; private int lastNonZero; @@ -52,11 +61,38 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vp8Encoding.FTransform(reference.Slice(WebpLookupTables.Vp8DspScan[j]), pred.Slice(WebpLookupTables.Vp8DspScan[j]), this.output, this.scratch); // Convert coefficients to bin. - for (int k = 0; k < 16; ++k) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported) { - int v = Math.Abs(this.output[k]) >> 3; - int clippedValue = ClipMax(v, MaxCoeffThresh); - ++this.distribution[clippedValue]; + // Load. + ref short outputRef = ref MemoryMarshal.GetReference(this.output); + Vector256 out0 = Unsafe.As>(ref outputRef); + + // v = abs(out) >> 3 + Vector256 abs0 = Avx2.Abs(out0.AsInt16()); + Vector256 v0 = Avx2.ShiftRightArithmetic(abs0.AsInt16(), 3); + + // bin = min(v, MAX_COEFF_THRESH) + Vector256 min0 = Avx2.Min(v0, MaxCoeffThreshVec); + + // Store. + Unsafe.As>(ref outputRef) = min0; + + // Convert coefficients to bin. + for (int k = 0; k < 16; ++k) + { + ++this.distribution[this.output[k]]; + } + } + else +#endif + { + for (int k = 0; k < 16; ++k) + { + int v = Math.Abs(this.output[k]) >> 3; + int clippedValue = ClipMax(v, MaxCoeffThresh); + ++this.distribution[clippedValue]; + } } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs index 202df9039..b74f6969e 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs @@ -128,7 +128,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy for (int y = 0; y < height; y++) { Span row = pixelData.Slice(y * widthMul3, widthMul3); - Span decodedPixelRow = decodedPixels.GetRowSpan(y); + Span decodedPixelRow = decodedPixels.DangerousGetRowSpan(y); PixelOperations.Instance.FromBgr24Bytes( this.configuration, row, @@ -146,7 +146,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy for (int y = 0; y < height; y++) { int yMulWidth = y * width; - Span decodedPixelRow = decodedPixels.GetRowSpan(y); + Span decodedPixelRow = decodedPixels.DangerousGetRowSpan(y); for (int x = 0; x < width; x++) { int offset = yMulWidth + x; diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index 16d458ed8..7a731f428 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -321,8 +321,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static void ConvertRgbToYuv(Image image, Configuration configuration, MemoryAllocator memoryAllocator, Span y, Span u, Span v) where TPixel : unmanaged, IPixel { - int width = image.Width; - int height = image.Height; + Buffer2D imageBuffer = image.Frames.RootFrame.PixelBuffer; + int width = imageBuffer.Width; + int height = imageBuffer.Height; int uvWidth = (width + 1) >> 1; // Temporary storage for accumulated R/G/B values during conversion to U/V. @@ -336,8 +337,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int rowIndex; for (rowIndex = 0; rowIndex < height - 1; rowIndex += 2) { - Span rowSpan = image.GetPixelRowSpan(rowIndex); - Span nextRowSpan = image.GetPixelRowSpan(rowIndex + 1); + Span rowSpan = imageBuffer.DangerousGetRowSpan(rowIndex); + Span nextRowSpan = imageBuffer.DangerousGetRowSpan(rowIndex + 1); PixelOperations.Instance.ToBgra32(configuration, rowSpan, bgraRow0); PixelOperations.Instance.ToBgra32(configuration, nextRowSpan, bgraRow1); @@ -363,7 +364,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Extra last row. if ((height & 1) != 0) { - Span rowSpan = image.GetPixelRowSpan(rowIndex); + Span rowSpan = imageBuffer.DangerousGetRowSpan(rowIndex); PixelOperations.Instance.ToBgra32(configuration, rowSpan, bgraRow0); ConvertRgbaToY(bgraRow0, y.Slice(rowIndex * width), width); diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index 3a4b459c5..fce0835fb 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -255,6 +255,7 @@ namespace SixLabors.ImageSharp /// /// The file path to the image. /// The decoder. + /// The token to monitor for cancellation requests. /// The configuration is null. /// The path is null. /// The decoder is null. @@ -262,14 +263,15 @@ namespace SixLabors.ImageSharp /// Image format is not supported. /// Image contains invalid content. /// A representing the asynchronous operation. - public static Task LoadAsync(string path, IImageDecoder decoder) - => LoadAsync(Configuration.Default, path, decoder, default); + public static Task LoadAsync(string path, IImageDecoder decoder, CancellationToken cancellationToken = default) + => LoadAsync(Configuration.Default, path, decoder, cancellationToken); /// /// Create a new instance of the class from the given file. /// /// The file path to the image. /// The decoder. + /// The token to monitor for cancellation requests. /// The configuration is null. /// The path is null. /// The decoder is null. @@ -278,9 +280,9 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// The pixel format. /// A representing the asynchronous operation. - public static Task> LoadAsync(string path, IImageDecoder decoder) + public static Task> LoadAsync(string path, IImageDecoder decoder, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel - => LoadAsync(Configuration.Default, path, decoder, default); + => LoadAsync(Configuration.Default, path, decoder, cancellationToken); /// /// Create a new instance of the class from the given file. @@ -342,6 +344,7 @@ namespace SixLabors.ImageSharp /// Create a new instance of the class from the given file. /// /// The file path to the image. + /// The token to monitor for cancellation requests. /// The configuration is null. /// The path is null. /// Image format not recognised. @@ -349,9 +352,9 @@ namespace SixLabors.ImageSharp /// Image format is not supported. /// The pixel format. /// A representing the asynchronous operation. - public static Task> LoadAsync(string path) + public static Task> LoadAsync(string path, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel - => LoadAsync(Configuration.Default, path, default(CancellationToken)); + => LoadAsync(Configuration.Default, path, cancellationToken); /// /// Create a new instance of the class from the given file. diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 291d6f7ca..f5e32d8ce 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -44,27 +44,29 @@ namespace SixLabors.ImageSharp /// By reading the header on the provided stream this calculates the images format type. /// /// The image stream to read the header from. + /// The token to monitor for cancellation requests. /// The stream is null. /// The stream is not readable. /// A representing the asynchronous operation or null if none is found. - public static Task DetectFormatAsync(Stream stream) - => DetectFormatAsync(Configuration.Default, stream); + public static Task DetectFormatAsync(Stream stream, CancellationToken cancellationToken = default) + => DetectFormatAsync(Configuration.Default, stream, cancellationToken); /// /// By reading the header on the provided stream this calculates the images format type. /// /// The configuration. /// The image stream to read the header from. + /// The token to monitor for cancellation requests. /// The configuration is null. /// The stream is null. /// The stream is not readable. /// A representing the asynchronous operation. - public static Task DetectFormatAsync(Configuration configuration, Stream stream) + public static Task DetectFormatAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) => WithSeekableStreamAsync( configuration, stream, (s, _) => InternalDetectFormatAsync(s, configuration), - default); + cancellationToken); /// /// Reads the raw image information from the specified stream without fully decoding it. @@ -83,6 +85,7 @@ namespace SixLabors.ImageSharp /// Reads the raw image information from the specified stream without fully decoding it. /// /// The image stream to read the header from. + /// The token to monitor for cancellation requests. /// The stream is null. /// The stream is not readable. /// Image contains invalid content. @@ -90,8 +93,8 @@ namespace SixLabors.ImageSharp /// A representing the asynchronous operation or null if /// a suitable detector is not found. /// - public static Task IdentifyAsync(Stream stream) - => IdentifyAsync(Configuration.Default, stream); + public static Task IdentifyAsync(Stream stream, CancellationToken cancellationToken = default) + => IdentifyAsync(Configuration.Default, stream, cancellationToken); /// /// Reads the raw image information from the specified stream without fully decoding it. @@ -227,13 +230,14 @@ namespace SixLabors.ImageSharp /// The pixel format is selected by the decoder. /// /// The stream containing image information. + /// The token to monitor for cancellation requests. /// The stream is null. /// The stream is not readable or the image format is not supported. /// Image format not recognised. /// Image contains invalid content. /// A representing the asynchronous operation. - public static Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Stream stream) - => LoadWithFormatAsync(Configuration.Default, stream); + public static Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Stream stream, CancellationToken cancellationToken = default) + => LoadWithFormatAsync(Configuration.Default, stream, cancellationToken); /// /// Decode a new instance of the class from the given stream. @@ -252,12 +256,14 @@ namespace SixLabors.ImageSharp /// The pixel format is selected by the decoder. /// /// The stream containing image information. + /// The token to monitor for cancellation requests. /// The stream is null. /// The stream is not readable or the image format is not supported. /// Image format not recognised. /// Image contains invalid content. /// A representing the asynchronous operation. - public static Task LoadAsync(Stream stream) => LoadAsync(Configuration.Default, stream); + public static Task LoadAsync(Stream stream, CancellationToken cancellationToken = default) + => LoadAsync(Configuration.Default, stream, cancellationToken); /// /// Decode a new instance of the class from the given stream. @@ -280,14 +286,15 @@ namespace SixLabors.ImageSharp /// /// The stream containing image information. /// The decoder. + /// The token to monitor for cancellation requests. /// The stream is null. /// The decoder is null. /// The stream is not readable or the image format is not supported. /// Image format not recognised. /// Image contains invalid content. /// A representing the asynchronous operation. - public static Task LoadAsync(Stream stream, IImageDecoder decoder) - => LoadAsync(Configuration.Default, stream, decoder); + public static Task LoadAsync(Stream stream, IImageDecoder decoder, CancellationToken cancellationToken = default) + => LoadAsync(Configuration.Default, stream, decoder, cancellationToken); /// /// Decode a new instance of the class from the given stream. @@ -388,15 +395,16 @@ namespace SixLabors.ImageSharp /// Create a new instance of the class from the given stream. /// /// The stream containing image information. + /// The token to monitor for cancellation requests. /// The stream is null. /// The stream is not readable or the image format is not supported. /// Image format not recognised. /// Image contains invalid content. /// The pixel format. /// A representing the asynchronous operation. - public static Task> LoadAsync(Stream stream) + public static Task> LoadAsync(Stream stream, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel - => LoadAsync(Configuration.Default, stream); + => LoadAsync(Configuration.Default, stream, cancellationToken); /// /// Create a new instance of the class from the given stream. @@ -417,15 +425,16 @@ namespace SixLabors.ImageSharp /// Create a new instance of the class from the given stream. /// /// The stream containing image information. + /// The token to monitor for cancellation requests. /// The stream is null. /// The stream is not readable or the image format is not supported. /// Image format not recognised. /// Image contains invalid content. /// The pixel format. /// A representing the asynchronous operation. - public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Stream stream) + public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Stream stream, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel - => await LoadWithFormatAsync(Configuration.Default, stream).ConfigureAwait(false); + => await LoadWithFormatAsync(Configuration.Default, stream, cancellationToken).ConfigureAwait(false); /// /// Create a new instance of the class from the given stream. diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index a336c9c59..4753e90e0 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -58,7 +58,11 @@ namespace SixLabors.ImageSharp Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(width, height, AllocationOptions.Clean); + this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D( + width, + height, + configuration.PreferContiguousImageBuffers, + AllocationOptions.Clean); } /// @@ -87,7 +91,10 @@ namespace SixLabors.ImageSharp Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(width, height); + this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D( + width, + height, + configuration.PreferContiguousImageBuffers); this.Clear(backgroundColor); } @@ -131,7 +138,10 @@ namespace SixLabors.ImageSharp Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(source, nameof(source)); - this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(source.PixelBuffer.Width, source.PixelBuffer.Height); + this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D( + source.PixelBuffer.Width, + source.PixelBuffer.Height, + configuration.PreferContiguousImageBuffers); source.PixelBuffer.FastMemoryGroup.CopyTo(this.PixelBuffer.FastMemoryGroup); } @@ -168,36 +178,128 @@ namespace SixLabors.ImageSharp } /// - /// Gets the representation of the pixels as a of contiguous memory - /// at row beginning from the first pixel on that row. + /// Execute to process image pixels in a safe and efficient manner. /// - /// The row. - /// The - /// Thrown when row index is out of range. - public Span GetPixelRowSpan(int rowIndex) + /// The defining the pixel operations. + public void ProcessPixelRows(PixelAccessorAction processPixels) { - Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); - Guard.MustBeLessThan(rowIndex, this.Height, nameof(rowIndex)); + Guard.NotNull(processPixels, nameof(processPixels)); - return this.PixelBuffer.GetRowSpan(rowIndex); + this.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + + try + { + var accessor = new PixelAccessor(this.PixelBuffer); + processPixels(accessor); + } + finally + { + this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); + } + } + + /// + /// Execute to process pixels of multiple image frames in a safe and efficient manner. + /// + /// The second image frame. + /// The defining the pixel operations. + /// The pixel type of the second image frame. + public void ProcessPixelRows( + ImageFrame frame2, + PixelAccessorAction processPixels) + where TPixel2 : unmanaged, IPixel + { + Guard.NotNull(frame2, nameof(frame2)); + Guard.NotNull(processPixels, nameof(processPixels)); + + this.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + frame2.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + + try + { + var accessor1 = new PixelAccessor(this.PixelBuffer); + var accessor2 = new PixelAccessor(frame2.PixelBuffer); + processPixels(accessor1, accessor2); + } + finally + { + frame2.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); + this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); + } } /// - /// Gets the representation of the pixels as a in the source image's pixel format + /// Execute to process pixels of multiple image frames in a safe and efficient manner. + /// + /// The second image frame. + /// The third image frame. + /// The defining the pixel operations. + /// The pixel type of the second image frame. + /// The pixel type of the third image frame. + public void ProcessPixelRows( + ImageFrame frame2, + ImageFrame frame3, + PixelAccessorAction processPixels) + where TPixel2 : unmanaged, IPixel + where TPixel3 : unmanaged, IPixel + { + Guard.NotNull(frame2, nameof(frame2)); + Guard.NotNull(frame3, nameof(frame3)); + Guard.NotNull(processPixels, nameof(processPixels)); + + this.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + frame2.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + frame3.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + + try + { + var accessor1 = new PixelAccessor(this.PixelBuffer); + var accessor2 = new PixelAccessor(frame2.PixelBuffer); + var accessor3 = new PixelAccessor(frame3.PixelBuffer); + processPixels(accessor1, accessor2, accessor3); + } + finally + { + frame3.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); + frame2.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); + this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); + } + } + + /// + /// Copy image pixels to . + /// + /// The to copy image pixels to. + public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(destination); + + /// + /// Copy image pixels to . + /// + /// The of to copy image pixels to. + public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(MemoryMarshal.Cast(destination)); + + /// + /// Gets the representation of the pixels as a in the source image's pixel format /// stored in row major order, if the backing buffer is contiguous. + /// + /// To ensure the memory is contiguous, should be set + /// to true, preferably on a non-global configuration instance (not ). + /// + /// WARNING: Disposing or leaking the underlying image while still working with the 's + /// might lead to memory corruption. /// - /// The . - /// The . - public bool TryGetSinglePixelSpan(out Span span) + /// The referencing the image buffer. + /// The indicating the success. + public bool DangerousTryGetSinglePixelMemory(out Memory memory) { IMemoryGroup mg = this.GetPixelMemoryGroup(); if (mg.Count > 1) { - span = default; + memory = default; return false; } - span = mg.Single().Span; + memory = mg.Single(); return true; } @@ -310,7 +412,7 @@ namespace SixLabors.ImageSharp } var target = new ImageFrame(configuration, this.Width, this.Height, this.Metadata.DeepClone()); - var operation = new RowIntervalOperation(this, target, configuration); + var operation = new RowIntervalOperation(this.PixelBuffer, target.PixelBuffer, configuration); ParallelRowIterator.IterateRowIntervals( configuration, @@ -364,14 +466,14 @@ namespace SixLabors.ImageSharp private readonly struct RowIntervalOperation : IRowIntervalOperation where TPixel2 : unmanaged, IPixel { - private readonly ImageFrame source; - private readonly ImageFrame target; + private readonly Buffer2D source; + private readonly Buffer2D target; private readonly Configuration configuration; [MethodImpl(InliningOptions.ShortMethod)] public RowIntervalOperation( - ImageFrame source, - ImageFrame target, + Buffer2D source, + Buffer2D target, Configuration configuration) { this.source = source; @@ -385,8 +487,8 @@ namespace SixLabors.ImageSharp { for (int y = rows.Min; y < rows.Max; y++) { - Span sourceRow = this.source.GetPixelRowSpan(y); - Span targetRow = this.target.GetPixelRowSpan(y); + Span sourceRow = this.source.DangerousGetRowSpan(y); + Span targetRow = this.target.DangerousGetRowSpan(y); PixelOperations.Instance.To(this.configuration, sourceRow, targetRow); } } diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 6ad20713d..1b3a8e27f 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -10,7 +10,7 @@ Apache-2.0 https://github.com/SixLabors/ImageSharp/ $(RepositoryUrl) - Image Resize Crop Gif Jpg Jpeg Bitmap Png Tga NetCore + Image Resize Crop Gif Jpg Jpeg Bitmap Pbm Png Tga NetCore A new, fully featured, fully managed, cross-platform, 2D graphics API for .NET Debug;Release;Debug-InnerLoop;Release-InnerLoop @@ -23,12 +23,12 @@ - net6.0;net5.0;netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472 + net6.0;net5.0;netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;net472 - net5.0;netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472 + net5.0;netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;net472 @@ -38,7 +38,7 @@ - netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472 + netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;net472 @@ -70,15 +70,15 @@ True Block8x8F.Generated.tt - + True True - GenericBlock8x8.Generated.tt + Block8x8F.Generated.tt - + True True - Block8x8F.Generated.tt + Abgr32.PixelOperations.Generated.tt True @@ -167,14 +167,14 @@ TextTemplatingFileGenerator Block8x8F.Generated.cs - - TextTemplatingFileGenerator - GenericBlock8x8.Generated.cs - TextTemplatingFileGenerator Block8x8F.Generated.cs + + TextTemplatingFileGenerator + Abgr32.PixelOperations.Generated.cs + TextTemplatingFileGenerator PixelOperations{TPixel}.Generated.cs diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 2aa9c5394..d01706b01 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -2,9 +2,11 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; @@ -204,39 +206,136 @@ namespace SixLabors.ImageSharp } /// - /// Gets the representation of the pixels as a of contiguous memory - /// at row beginning from the first pixel on that row. + /// Execute to process image pixels in a safe and efficient manner. /// - /// The row. - /// The - /// Thrown when row index is out of range. - public Span GetPixelRowSpan(int rowIndex) + /// The defining the pixel operations. + public void ProcessPixelRows(PixelAccessorAction processPixels) { - Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); - Guard.MustBeLessThan(rowIndex, this.Height, nameof(rowIndex)); + Guard.NotNull(processPixels, nameof(processPixels)); + Buffer2D buffer = this.Frames.RootFrame.PixelBuffer; + buffer.FastMemoryGroup.IncreaseRefCounts(); - this.EnsureNotDisposed(); + try + { + var accessor = new PixelAccessor(buffer); + processPixels(accessor); + } + finally + { + buffer.FastMemoryGroup.DecreaseRefCounts(); + } + } + + /// + /// Execute to process pixels of multiple images in a safe and efficient manner. + /// + /// The second image. + /// The defining the pixel operations. + /// The pixel type of the second image. + public void ProcessPixelRows( + Image image2, + PixelAccessorAction processPixels) + where TPixel2 : unmanaged, IPixel + { + Guard.NotNull(image2, nameof(image2)); + Guard.NotNull(processPixels, nameof(processPixels)); + + Buffer2D buffer1 = this.Frames.RootFrame.PixelBuffer; + Buffer2D buffer2 = image2.Frames.RootFrame.PixelBuffer; + + buffer1.FastMemoryGroup.IncreaseRefCounts(); + buffer2.FastMemoryGroup.IncreaseRefCounts(); + + try + { + var accessor1 = new PixelAccessor(buffer1); + var accessor2 = new PixelAccessor(buffer2); + processPixels(accessor1, accessor2); + } + finally + { + buffer2.FastMemoryGroup.DecreaseRefCounts(); + buffer1.FastMemoryGroup.DecreaseRefCounts(); + } + } + + /// + /// Execute to process pixels of multiple images in a safe and efficient manner. + /// + /// The second image. + /// The third image. + /// The defining the pixel operations. + /// The pixel type of the second image. + /// The pixel type of the third image. + public void ProcessPixelRows( + Image image2, + Image image3, + PixelAccessorAction processPixels) + where TPixel2 : unmanaged, IPixel + where TPixel3 : unmanaged, IPixel + { + Guard.NotNull(image2, nameof(image2)); + Guard.NotNull(image3, nameof(image3)); + Guard.NotNull(processPixels, nameof(processPixels)); - return this.PixelSourceUnsafe.PixelBuffer.GetRowSpan(rowIndex); + Buffer2D buffer1 = this.Frames.RootFrame.PixelBuffer; + Buffer2D buffer2 = image2.Frames.RootFrame.PixelBuffer; + Buffer2D buffer3 = image3.Frames.RootFrame.PixelBuffer; + + buffer1.FastMemoryGroup.IncreaseRefCounts(); + buffer2.FastMemoryGroup.IncreaseRefCounts(); + buffer3.FastMemoryGroup.IncreaseRefCounts(); + + try + { + var accessor1 = new PixelAccessor(buffer1); + var accessor2 = new PixelAccessor(buffer2); + var accessor3 = new PixelAccessor(buffer3); + processPixels(accessor1, accessor2, accessor3); + } + finally + { + buffer3.FastMemoryGroup.DecreaseRefCounts(); + buffer2.FastMemoryGroup.DecreaseRefCounts(); + buffer1.FastMemoryGroup.DecreaseRefCounts(); + } } /// - /// Gets the representation of the pixels as a in the source image's pixel format + /// Copy image pixels to . + /// + /// The to copy image pixels to. + public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(destination); + + /// + /// Copy image pixels to . + /// + /// The of to copy image pixels to. + public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(MemoryMarshal.Cast(destination)); + + /// + /// Gets the representation of the pixels as a in the source image's pixel format /// stored in row major order, if the backing buffer is contiguous. + /// + /// To ensure the memory is contiguous, should be set + /// to true, preferably on a non-global configuration instance (not ). + /// + /// WARNING: Disposing or leaking the underlying image while still working with the 's + /// might lead to memory corruption. /// - /// The . - /// The . - public bool TryGetSinglePixelSpan(out Span span) + /// The referencing the image buffer. + /// The indicating the success. + public bool DangerousTryGetSinglePixelMemory(out Memory memory) { IMemoryGroup mg = this.GetPixelMemoryGroup(); - if (mg.Count == 1) + if (mg.Count > 1) { - span = mg[0].Span; - return true; + memory = default; + return false; } - span = default; - return false; + memory = mg.Single(); + return true; } /// diff --git a/src/ImageSharp/IndexedImageFrame{TPixel}.cs b/src/ImageSharp/IndexedImageFrame{TPixel}.cs index 7668d7600..18b44de82 100644 --- a/src/ImageSharp/IndexedImageFrame{TPixel}.cs +++ b/src/ImageSharp/IndexedImageFrame{TPixel}.cs @@ -75,11 +75,14 @@ namespace SixLabors.ImageSharp /// /// Gets the representation of the pixels as a of contiguous memory /// at row beginning from the first pixel on that row. + /// + /// WARNING: Disposing or leaking the underlying while still working with it's + /// might lead to memory corruption. /// /// The row index in the pixel buffer. /// The pixel row as a . [MethodImpl(InliningOptions.ShortMethod)] - public ReadOnlySpan GetPixelRowSpan(int rowIndex) + public ReadOnlySpan DangerousGetRowSpan(int rowIndex) => this.GetWritablePixelRowSpanUnsafe(rowIndex); /// @@ -96,7 +99,7 @@ namespace SixLabors.ImageSharp /// The pixel row as a . [MethodImpl(InliningOptions.ShortMethod)] public Span GetWritablePixelRowSpanUnsafe(int rowIndex) - => this.pixelBuffer.GetRowSpan(rowIndex); + => this.pixelBuffer.DangerousGetRowSpan(rowIndex); /// public void Dispose() diff --git a/src/ImageSharp/Memory/Allocators/AllocationOptions.cs b/src/ImageSharp/Memory/Allocators/AllocationOptions.cs index 3c865f357..ae856c978 100644 --- a/src/ImageSharp/Memory/Allocators/AllocationOptions.cs +++ b/src/ImageSharp/Memory/Allocators/AllocationOptions.cs @@ -1,21 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; + namespace SixLabors.ImageSharp.Memory { /// /// Options for allocating buffers. /// + [Flags] public enum AllocationOptions { /// /// Indicates that the buffer should just be allocated. /// - None, + None = 0, /// /// Indicates that the allocated buffer should be cleaned following allocation. /// - Clean + Clean = 1 } } diff --git a/src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs b/src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs new file mode 100644 index 000000000..d3e5bca6e --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs @@ -0,0 +1,10 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Memory +{ + internal static class AllocationOptionsExtensions + { + public static bool Has(this AllocationOptions options, AllocationOptions flag) => (options & flag) == flag; + } +} diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs deleted file mode 100644 index 0c35c8828..000000000 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory.Internals; - -namespace SixLabors.ImageSharp.Memory -{ - /// - /// Contains and . - /// - public partial class ArrayPoolMemoryAllocator - { - /// - /// The buffer implementation of . - /// - private class Buffer : ManagedBufferBase - where T : struct - { - /// - /// The length of the buffer. - /// - private readonly int length; - - /// - /// A weak reference to the source pool. - /// - /// - /// By using a weak reference here, we are making sure that array pools and their retained arrays are always GC-ed - /// after a call to , regardless of having buffer instances still being in use. - /// - private WeakReference> sourcePoolReference; - - public Buffer(byte[] data, int length, ArrayPool sourcePool) - { - this.Data = data; - this.length = length; - this.sourcePoolReference = new WeakReference>(sourcePool); - } - - /// - /// Gets the buffer as a byte array. - /// - protected byte[] Data { get; private set; } - - /// - public override Span GetSpan() - { - if (this.Data is null) - { - ThrowObjectDisposedException(); - } -#if SUPPORTS_CREATESPAN - ref byte r0 = ref MemoryMarshal.GetReference(this.Data); - return MemoryMarshal.CreateSpan(ref Unsafe.As(ref r0), this.length); -#else - return MemoryMarshal.Cast(this.Data.AsSpan()).Slice(0, this.length); -#endif - - } - - /// - protected override void Dispose(bool disposing) - { - if (!disposing || this.Data is null || this.sourcePoolReference is null) - { - return; - } - - if (this.sourcePoolReference.TryGetTarget(out ArrayPool pool)) - { - pool.Return(this.Data); - } - - this.sourcePoolReference = null; - this.Data = null; - } - - protected override object GetPinnableObject() => this.Data; - - [MethodImpl(InliningOptions.ColdPath)] - private static void ThrowObjectDisposedException() - { - throw new ObjectDisposedException("ArrayPoolMemoryAllocator.Buffer"); - } - } - - /// - /// The implementation of . - /// - private sealed class ManagedByteBuffer : Buffer, IManagedByteBuffer - { - public ManagedByteBuffer(byte[] data, int length, ArrayPool sourcePool) - : base(data, length, sourcePool) - { - } - - /// - public byte[] Array => this.Data; - } - } -} diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs deleted file mode 100644 index 8aa1b9063..000000000 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Memory -{ - /// - /// Contains common factory methods and configuration constants. - /// - public partial class ArrayPoolMemoryAllocator - { - /// - /// The default value for: maximum size of pooled arrays in bytes. - /// Currently set to 24MB, which is equivalent to 8 megapixels of raw RGBA32 data. - /// - internal const int DefaultMaxPooledBufferSizeInBytes = 24 * 1024 * 1024; - - /// - /// The value for: The threshold to pool arrays in which has less buckets for memory safety. - /// - private const int DefaultBufferSelectorThresholdInBytes = 8 * 1024 * 1024; - - /// - /// The default bucket count for . - /// - private const int DefaultLargePoolBucketCount = 6; - - /// - /// The default bucket count for . - /// - private const int DefaultNormalPoolBucketCount = 16; - - // TODO: This value should be determined by benchmarking - private const int DefaultBufferCapacityInBytes = int.MaxValue / 4; - - /// - /// This is the default. Should be good for most use cases. - /// - /// The memory manager. - public static ArrayPoolMemoryAllocator CreateDefault() - { - return new ArrayPoolMemoryAllocator( - DefaultMaxPooledBufferSizeInBytes, - DefaultBufferSelectorThresholdInBytes, - DefaultLargePoolBucketCount, - DefaultNormalPoolBucketCount, - DefaultBufferCapacityInBytes); - } - - /// - /// For environments with very limited memory capabilities, only small buffers like image rows are pooled. - /// - /// The memory manager. - public static ArrayPoolMemoryAllocator CreateWithMinimalPooling() - { - return new ArrayPoolMemoryAllocator(64 * 1024, 32 * 1024, 8, 24); - } - - /// - /// For environments with limited memory capabilities, only small array requests are pooled, which can result in reduced throughput. - /// - /// The memory manager. - public static ArrayPoolMemoryAllocator CreateWithModeratePooling() - { - return new ArrayPoolMemoryAllocator(1024 * 1024, 32 * 1024, 16, 24); - } - - /// - /// For environments where memory capabilities are not an issue, the maximum amount of array requests are pooled which results in optimal throughput. - /// - /// The memory manager. - public static ArrayPoolMemoryAllocator CreateWithAggressivePooling() - { - return new ArrayPoolMemoryAllocator(128 * 1024 * 1024, 32 * 1024 * 1024, 16, 32); - } - } -} diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs deleted file mode 100644 index a79e042a3..000000000 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Memory -{ - /// - /// Implements by allocating memory from . - /// - public sealed partial class ArrayPoolMemoryAllocator : MemoryAllocator - { - private readonly int maxArraysPerBucketNormalPool; - - private readonly int maxArraysPerBucketLargePool; - - /// - /// The for small-to-medium buffers which is not kept clean. - /// - private ArrayPool normalArrayPool; - - /// - /// The for huge buffers, which is not kept clean. - /// - private ArrayPool largeArrayPool; - - /// - /// Initializes a new instance of the class. - /// - public ArrayPoolMemoryAllocator() - : this(DefaultMaxPooledBufferSizeInBytes, DefaultBufferSelectorThresholdInBytes) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated. - public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes) - : this(maxPoolSizeInBytes, GetLargeBufferThresholdInBytes(maxPoolSizeInBytes)) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated. - /// Arrays over this threshold will be pooled in which has less buckets for memory safety. - public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes, int poolSelectorThresholdInBytes) - : this(maxPoolSizeInBytes, poolSelectorThresholdInBytes, DefaultLargePoolBucketCount, DefaultNormalPoolBucketCount) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated. - /// The threshold to pool arrays in which has less buckets for memory safety. - /// Max arrays per bucket for the large array pool. - /// Max arrays per bucket for the normal array pool. - public ArrayPoolMemoryAllocator( - int maxPoolSizeInBytes, - int poolSelectorThresholdInBytes, - int maxArraysPerBucketLargePool, - int maxArraysPerBucketNormalPool) - : this( - maxPoolSizeInBytes, - poolSelectorThresholdInBytes, - maxArraysPerBucketLargePool, - maxArraysPerBucketNormalPool, - DefaultBufferCapacityInBytes) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated. - /// The threshold to pool arrays in which has less buckets for memory safety. - /// Max arrays per bucket for the large array pool. - /// Max arrays per bucket for the normal array pool. - /// The length of the largest contiguous buffer that can be handled by this allocator instance. - public ArrayPoolMemoryAllocator( - int maxPoolSizeInBytes, - int poolSelectorThresholdInBytes, - int maxArraysPerBucketLargePool, - int maxArraysPerBucketNormalPool, - int bufferCapacityInBytes) - { - Guard.MustBeGreaterThan(maxPoolSizeInBytes, 0, nameof(maxPoolSizeInBytes)); - Guard.MustBeLessThanOrEqualTo(poolSelectorThresholdInBytes, maxPoolSizeInBytes, nameof(poolSelectorThresholdInBytes)); - - this.MaxPoolSizeInBytes = maxPoolSizeInBytes; - this.PoolSelectorThresholdInBytes = poolSelectorThresholdInBytes; - this.BufferCapacityInBytes = bufferCapacityInBytes; - this.maxArraysPerBucketLargePool = maxArraysPerBucketLargePool; - this.maxArraysPerBucketNormalPool = maxArraysPerBucketNormalPool; - - this.InitArrayPools(); - } - - /// - /// Gets the maximum size of pooled arrays in bytes. - /// - public int MaxPoolSizeInBytes { get; } - - /// - /// Gets the threshold to pool arrays in which has less buckets for memory safety. - /// - public int PoolSelectorThresholdInBytes { get; } - - /// - /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance. - /// - public int BufferCapacityInBytes { get; internal set; } // Setter is internal for easy configuration in tests - - /// - public override void ReleaseRetainedResources() - { - this.InitArrayPools(); - } - - /// - protected internal override int GetBufferCapacityInBytes() => this.BufferCapacityInBytes; - - /// - public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) - { - Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); - int itemSizeBytes = Unsafe.SizeOf(); - int bufferSizeInBytes = length * itemSizeBytes; - - ArrayPool pool = this.GetArrayPool(bufferSizeInBytes); - byte[] byteArray = pool.Rent(bufferSizeInBytes); - - var buffer = new Buffer(byteArray, length, pool); - if (options == AllocationOptions.Clean) - { - buffer.GetSpan().Clear(); - } - - return buffer; - } - - /// - public override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None) - { - Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); - - ArrayPool pool = this.GetArrayPool(length); - byte[] byteArray = pool.Rent(length); - - var buffer = new ManagedByteBuffer(byteArray, length, pool); - if (options == AllocationOptions.Clean) - { - buffer.GetSpan().Clear(); - } - - return buffer; - } - - private static int GetLargeBufferThresholdInBytes(int maxPoolSizeInBytes) - { - return maxPoolSizeInBytes / 4; - } - - [MethodImpl(InliningOptions.ColdPath)] - private static void ThrowInvalidAllocationException(int length, int max) => - throw new InvalidMemoryOperationException( - $"Requested allocation: '{length}' elements of '{typeof(T).Name}' is over the capacity in bytes '{max}' of the MemoryAllocator."); - - private ArrayPool GetArrayPool(int bufferSizeInBytes) - { - return bufferSizeInBytes <= this.PoolSelectorThresholdInBytes ? this.normalArrayPool : this.largeArrayPool; - } - - private void InitArrayPools() - { - this.largeArrayPool = ArrayPool.Create(this.MaxPoolSizeInBytes, this.maxArraysPerBucketLargePool); - this.normalArrayPool = ArrayPool.Create(this.PoolSelectorThresholdInBytes, this.maxArraysPerBucketNormalPool); - } - } -} diff --git a/src/ImageSharp/Memory/Allocators/IManagedByteBuffer.cs b/src/ImageSharp/Memory/Allocators/IManagedByteBuffer.cs deleted file mode 100644 index b8298edcd..000000000 --- a/src/ImageSharp/Memory/Allocators/IManagedByteBuffer.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.Buffers; - -namespace SixLabors.ImageSharp.Memory -{ - /// - /// Represents a byte buffer backed by a managed array. Useful for interop with classic .NET API-s. - /// - public interface IManagedByteBuffer : IMemoryOwner - { - /// - /// Gets the managed array backing this buffer instance. - /// - byte[] Array { get; } - } -} diff --git a/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs b/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs index d70811600..7dbbabff3 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs @@ -2,11 +2,12 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; namespace SixLabors.ImageSharp.Memory.Internals { /// - /// Wraps an array as an instance. + /// Wraps an array as an instance. /// /// internal class BasicArrayBuffer : ManagedBufferBase diff --git a/src/ImageSharp/Memory/Allocators/Internals/BasicByteBuffer.cs b/src/ImageSharp/Memory/Allocators/Internals/BasicByteBuffer.cs deleted file mode 100644 index e21592a12..000000000 --- a/src/ImageSharp/Memory/Allocators/Internals/BasicByteBuffer.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Memory.Internals -{ - /// - /// Provides an based on . - /// - internal sealed class BasicByteBuffer : BasicArrayBuffer, IManagedByteBuffer - { - /// - /// Initializes a new instance of the class. - /// - /// The byte array. - internal BasicByteBuffer(byte[] array) - : base(array) - { - } - } -} diff --git a/src/ImageSharp/Memory/Allocators/Internals/Gen2GcCallback.cs b/src/ImageSharp/Memory/Allocators/Internals/Gen2GcCallback.cs new file mode 100644 index 000000000..b0552936e --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/Gen2GcCallback.cs @@ -0,0 +1,115 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +// Port of BCL internal utility: +// https://github.com/dotnet/runtime/blob/57bfe474518ab5b7cfe6bf7424a79ce3af9d6657/src/libraries/System.Private.CoreLib/src/System/Gen2GcCallback.cs +#if NETCOREAPP3_1_OR_GREATER +using System; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Memory.Internals +{ + /// + /// Schedules a callback roughly every gen 2 GC (you may see a Gen 0 an Gen 1 but only once) + /// (We can fix this by capturing the Gen 2 count at startup and testing, but I mostly don't care) + /// + internal sealed class Gen2GcCallback : CriticalFinalizerObject + { + private readonly Func callback0; + private readonly Func callback1; + private GCHandle weakTargetObj; + + private Gen2GcCallback(Func callback) + { + this.callback0 = callback; + } + + private Gen2GcCallback(Func callback, object targetObj) + { + this.callback1 = callback; + this.weakTargetObj = GCHandle.Alloc(targetObj, GCHandleType.Weak); + } + + ~Gen2GcCallback() + { + if (this.weakTargetObj.IsAllocated) + { + // Check to see if the target object is still alive. + object targetObj = this.weakTargetObj.Target; + if (targetObj == null) + { + // The target object is dead, so this callback object is no longer needed. + this.weakTargetObj.Free(); + return; + } + + // Execute the callback method. + try + { + if (!this.callback1(targetObj)) + { + // If the callback returns false, this callback object is no longer needed. + this.weakTargetObj.Free(); + return; + } + } + catch + { + // Ensure that we still get a chance to resurrect this object, even if the callback throws an exception. +#if DEBUG + // Except in DEBUG, as we really shouldn't be hitting any exceptions here. + throw; +#endif + } + } + else + { + // Execute the callback method. + try + { + if (!this.callback0()) + { + // If the callback returns false, this callback object is no longer needed. + return; + } + } + catch + { + // Ensure that we still get a chance to resurrect this object, even if the callback throws an exception. +#if DEBUG + // Except in DEBUG, as we really shouldn't be hitting any exceptions here. + throw; +#endif + } + } + + // Resurrect ourselves by re-registering for finalization. + GC.ReRegisterForFinalize(this); + } + + /// + /// Schedule 'callback' to be called in the next GC. If the callback returns true it is + /// rescheduled for the next Gen 2 GC. Otherwise the callbacks stop. + /// + public static void Register(Func callback) + { + // Create a unreachable object that remembers the callback function and target object. + _ = new Gen2GcCallback(callback); + } + + /// + /// Schedule 'callback' to be called in the next GC. If the callback returns true it is + /// rescheduled for the next Gen 2 GC. Otherwise the callbacks stop. + /// + /// NOTE: This callback will be kept alive until either the callback function returns false, + /// or the target object dies. + /// + public static void Register(Func callback, object targetObj) + { + // Create a unreachable object that remembers the callback function and target object. + _ = new Gen2GcCallback(callback, targetObj); + } + } +} +#endif diff --git a/src/ImageSharp/Memory/Allocators/Internals/IRefCounted.cs b/src/ImageSharp/Memory/Allocators/Internals/IRefCounted.cs new file mode 100644 index 000000000..363b68048 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/IRefCounted.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Memory.Internals +{ + /// + /// Defines an common interface for ref-counted objects. + /// + internal interface IRefCounted + { + /// + /// Increments the reference counter. + /// + void AddRef(); + + /// + /// Decrements the reference counter. + /// + void ReleaseRef(); + } +} diff --git a/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs b/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs index 296a8bd3a..7207e9f56 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Buffers; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Memory.Internals @@ -23,7 +24,7 @@ namespace SixLabors.ImageSharp.Memory.Internals this.pinHandle = GCHandle.Alloc(this.GetPinnableObject(), GCHandleType.Pinned); } - void* ptr = (void*)this.pinHandle.AddrOfPinnedObject(); + void* ptr = Unsafe.Add((void*)this.pinHandle.AddrOfPinnedObject(), elementIndex); // We should only pass pinnable:this, when GCHandle lifetime is managed by the MemoryManager instance. return new MemoryHandle(ptr, pinnable: this); diff --git a/src/ImageSharp/Memory/Allocators/Internals/RefCountedLifetimeGuard.cs b/src/ImageSharp/Memory/Allocators/Internals/RefCountedLifetimeGuard.cs new file mode 100644 index 000000000..61682aa56 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/RefCountedLifetimeGuard.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.InteropServices; +using System.Threading; + +namespace SixLabors.ImageSharp.Memory.Internals +{ + /// + /// Implements reference counting lifetime guard mechanism similar to the one provided by , + /// but without the restriction of the guarded object being a handle. + /// + internal abstract class RefCountedLifetimeGuard : IDisposable + { + private int refCount = 1; + private int disposed; + private int released; + + ~RefCountedLifetimeGuard() + { + Interlocked.Exchange(ref this.disposed, 1); + this.ReleaseRef(); + } + + public bool IsDisposed => this.disposed == 1; + + public void AddRef() => Interlocked.Increment(ref this.refCount); + + public void ReleaseRef() + { + Interlocked.Decrement(ref this.refCount); + if (this.refCount == 0) + { + int wasReleased = Interlocked.Exchange(ref this.released, 1); + + if (wasReleased == 0) + { + this.Release(); + } + } + } + + public void Dispose() + { + int wasDisposed = Interlocked.Exchange(ref this.disposed, 1); + if (wasDisposed == 0) + { + this.ReleaseRef(); + GC.SuppressFinalize(this); + } + } + + protected abstract void Release(); + } +} diff --git a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs new file mode 100644 index 000000000..21673215a --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Memory.Internals +{ + internal class SharedArrayPoolBuffer : ManagedBufferBase, IRefCounted + where T : struct + { + private readonly int lengthInBytes; + private byte[] array; + private LifetimeGuard lifetimeGuard; + + public SharedArrayPoolBuffer(int lengthInElements) + { + this.lengthInBytes = lengthInElements * Unsafe.SizeOf(); + this.array = ArrayPool.Shared.Rent(this.lengthInBytes); + this.lifetimeGuard = new LifetimeGuard(this.array); + } + + protected override void Dispose(bool disposing) + { + if (this.array == null) + { + return; + } + + this.lifetimeGuard.Dispose(); + this.array = null; + } + + public override Span GetSpan() + { + this.CheckDisposed(); + return MemoryMarshal.Cast(this.array.AsSpan(0, this.lengthInBytes)); + } + + protected override object GetPinnableObject() => this.array; + + public void AddRef() + { + this.CheckDisposed(); + this.lifetimeGuard.AddRef(); + } + + public void ReleaseRef() => this.lifetimeGuard.ReleaseRef(); + + [Conditional("DEBUG")] + private void CheckDisposed() + { + if (this.array == null) + { + throw new ObjectDisposedException("SharedArrayPoolBuffer"); + } + } + + private sealed class LifetimeGuard : RefCountedLifetimeGuard + { + private byte[] array; + + public LifetimeGuard(byte[] array) => this.array = array; + + protected override void Release() + { + // If this is called by a finalizer, we will end storing the first array of this bucket + // on the thread local storage of the finalizer thread. + // This is not ideal, but subsequent leaks will end up returning arrays to per-cpu buckets, + // meaning likely a different bucket than it was rented from, + // but this is PROBABLY better than not returning the arrays at all. + ArrayPool.Shared.Return(this.array); + this.array = null; + } + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs new file mode 100644 index 000000000..666b24855 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Memory.Internals +{ + internal partial class UniformUnmanagedMemoryPool + { + public UnmanagedBuffer CreateGuardedBuffer( + UnmanagedMemoryHandle handle, + int lengthInElements, + bool clear) + where T : struct + { + var buffer = new UnmanagedBuffer(lengthInElements, new ReturnToPoolBufferLifetimeGuard(this, handle)); + if (clear) + { + buffer.Clear(); + } + + return buffer; + } + + public RefCountedLifetimeGuard CreateGroupLifetimeGuard(UnmanagedMemoryHandle[] handles) => new GroupLifetimeGuard(this, handles); + + private sealed class GroupLifetimeGuard : RefCountedLifetimeGuard + { + private readonly UniformUnmanagedMemoryPool pool; + private readonly UnmanagedMemoryHandle[] handles; + + public GroupLifetimeGuard(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle[] handles) + { + this.pool = pool; + this.handles = handles; + } + + protected override void Release() + { + if (!this.pool.Return(this.handles)) + { + foreach (UnmanagedMemoryHandle handle in this.handles) + { + handle.Free(); + } + } + } + } + + private sealed class ReturnToPoolBufferLifetimeGuard : UnmanagedBufferLifetimeGuard + { + private readonly UniformUnmanagedMemoryPool pool; + + public ReturnToPoolBufferLifetimeGuard(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle handle) + : base(handle) => + this.pool = pool; + + protected override void Release() + { + if (!this.pool.Return(this.Handle)) + { + this.Handle.Free(); + } + } + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs new file mode 100644 index 000000000..6504787a8 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs @@ -0,0 +1,356 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; + +namespace SixLabors.ImageSharp.Memory.Internals +{ + internal partial class UniformUnmanagedMemoryPool +#if !NETSTANDARD1_3 + // In case UniformUnmanagedMemoryPool is finalized, we prefer to run its finalizer after the guard finalizers, + // but we should not rely on this. + : System.Runtime.ConstrainedExecution.CriticalFinalizerObject +#endif + { + private static int minTrimPeriodMilliseconds = int.MaxValue; + private static readonly List> AllPools = new(); + private static Timer trimTimer; + + private static readonly Stopwatch Stopwatch = Stopwatch.StartNew(); + + private readonly TrimSettings trimSettings; + private readonly UnmanagedMemoryHandle[] buffers; + private int index; + private long lastTrimTimestamp; + private int finalized; + + public UniformUnmanagedMemoryPool(int bufferLength, int capacity) + : this(bufferLength, capacity, TrimSettings.Default) + { + } + + public UniformUnmanagedMemoryPool(int bufferLength, int capacity, TrimSettings trimSettings) + { + this.trimSettings = trimSettings; + this.Capacity = capacity; + this.BufferLength = bufferLength; + this.buffers = new UnmanagedMemoryHandle[capacity]; + + if (trimSettings.Enabled) + { + UpdateTimer(trimSettings, this); +#if NETCOREAPP3_1_OR_GREATER + Gen2GcCallback.Register(s => ((UniformUnmanagedMemoryPool)s).Trim(), this); +#endif + this.lastTrimTimestamp = Stopwatch.ElapsedMilliseconds; + } + } + + // We don't want UniformUnmanagedMemoryPool and MemoryAllocator to be IDisposable, + // since the types don't really match Disposable semantics. + // If a user wants to drop a MemoryAllocator after they finished using it, they should call allocator.ReleaseRetainedResources(), + // which normally should free the already returned (!) buffers. + // However in case if this doesn't happen, we need the retained memory to be freed by the finalizer. + ~UniformUnmanagedMemoryPool() + { + Interlocked.Exchange(ref this.finalized, 1); + this.TrimAll(this.buffers); + } + + public int BufferLength { get; } + + public int Capacity { get; } + + private bool Finalized => this.finalized == 1; + + /// + /// Rent a single buffer. If the pool is full, return . + /// + public UnmanagedMemoryHandle Rent() + { + UnmanagedMemoryHandle[] buffersLocal = this.buffers; + + // Avoid taking the lock if the pool is is over it's limit: + if (this.index == buffersLocal.Length || this.Finalized) + { + return UnmanagedMemoryHandle.NullHandle; + } + + UnmanagedMemoryHandle buffer; + lock (buffersLocal) + { + // Check again after taking the lock: + if (this.index == buffersLocal.Length || this.Finalized) + { + return UnmanagedMemoryHandle.NullHandle; + } + + buffer = buffersLocal[this.index]; + buffersLocal[this.index++] = default; + } + + if (buffer.IsInvalid) + { + buffer = UnmanagedMemoryHandle.Allocate(this.BufferLength); + } + + return buffer; + } + + /// + /// Rent buffers or return 'null' if the pool is full. + /// + public UnmanagedMemoryHandle[] Rent(int bufferCount) + { + UnmanagedMemoryHandle[] buffersLocal = this.buffers; + + // Avoid taking the lock if the pool is is over it's limit: + if (this.index + bufferCount >= buffersLocal.Length + 1 || this.Finalized) + { + return null; + } + + UnmanagedMemoryHandle[] result; + lock (buffersLocal) + { + // Check again after taking the lock: + if (this.index + bufferCount >= buffersLocal.Length + 1 || this.Finalized) + { + return null; + } + + result = new UnmanagedMemoryHandle[bufferCount]; + for (int i = 0; i < bufferCount; i++) + { + result[i] = buffersLocal[this.index]; + buffersLocal[this.index++] = UnmanagedMemoryHandle.NullHandle; + } + } + + for (int i = 0; i < result.Length; i++) + { + if (result[i].IsInvalid) + { + result[i] = UnmanagedMemoryHandle.Allocate(this.BufferLength); + } + } + + return result; + } + + // The Return methods return false if and only if: + // (1) More buffers are returned than rented OR + // (2) The pool has been finalized. + // This is defensive programming, since neither of the cases should happen normally + // (case 1 would be a programming mistake in the library, case 2 should be prevented by the CriticalFinalizerObject contract), + // so we throw in Debug instead of returning false. + // In Release, the caller should Free() the handles if false is returned to avoid memory leaks. + public bool Return(UnmanagedMemoryHandle bufferHandle) + { + Guard.IsTrue(bufferHandle.IsValid, nameof(bufferHandle), "Returning NullHandle to the pool is not allowed."); + lock (this.buffers) + { + if (this.Finalized || this.index == 0) + { + this.DebugThrowInvalidReturn(); + return false; + } + + this.buffers[--this.index] = bufferHandle; + } + + return true; + } + + public bool Return(Span bufferHandles) + { + lock (this.buffers) + { + if (this.Finalized || this.index - bufferHandles.Length + 1 <= 0) + { + this.DebugThrowInvalidReturn(); + return false; + } + + for (int i = bufferHandles.Length - 1; i >= 0; i--) + { + ref UnmanagedMemoryHandle h = ref bufferHandles[i]; + Guard.IsTrue(h.IsValid, nameof(bufferHandles), "Returning NullHandle to the pool is not allowed."); + this.buffers[--this.index] = h; + } + } + + return true; + } + + public void Release() + { + lock (this.buffers) + { + for (int i = this.index; i < this.buffers.Length; i++) + { + ref UnmanagedMemoryHandle buffer = ref this.buffers[i]; + if (buffer.IsInvalid) + { + break; + } + + buffer.Free(); + } + } + } + + [Conditional("DEBUG")] + private void DebugThrowInvalidReturn() + { + if (this.Finalized) + { + throw new ObjectDisposedException( + nameof(UniformUnmanagedMemoryPool), + "Invalid handle return to the pool! The pool has been finalized."); + } + + throw new InvalidOperationException( + "Invalid handle return to the pool! Returning more buffers than rented."); + } + + private static void UpdateTimer(TrimSettings settings, UniformUnmanagedMemoryPool pool) + { + lock (AllPools) + { + AllPools.Add(new WeakReference(pool)); + + // Invoke the timer callback more frequently, than trimSettings.TrimPeriodMilliseconds. + // We are checking in the callback if enough time passed since the last trimming. If not, we do nothing. + int period = settings.TrimPeriodMilliseconds / 4; + if (trimTimer == null) + { + trimTimer = new Timer(_ => TimerCallback(), null, period, period); + } + else if (settings.TrimPeriodMilliseconds < minTrimPeriodMilliseconds) + { + trimTimer.Change(period, period); + } + + minTrimPeriodMilliseconds = Math.Min(minTrimPeriodMilliseconds, settings.TrimPeriodMilliseconds); + } + } + + private static void TimerCallback() + { + lock (AllPools) + { + // Remove lost references from the list: + for (int i = AllPools.Count - 1; i >= 0; i--) + { + if (!AllPools[i].TryGetTarget(out _)) + { + AllPools.RemoveAt(i); + } + } + + foreach (WeakReference weakPoolRef in AllPools) + { + if (weakPoolRef.TryGetTarget(out UniformUnmanagedMemoryPool pool)) + { + pool.Trim(); + } + } + } + } + + private bool Trim() + { + if (this.Finalized) + { + return false; + } + + UnmanagedMemoryHandle[] buffersLocal = this.buffers; + + bool isHighPressure = this.IsHighMemoryPressure(); + + if (isHighPressure) + { + this.TrimAll(buffersLocal); + return true; + } + + long millisecondsSinceLastTrim = Stopwatch.ElapsedMilliseconds - this.lastTrimTimestamp; + if (millisecondsSinceLastTrim > this.trimSettings.TrimPeriodMilliseconds) + { + return this.TrimLowPressure(buffersLocal); + } + + return true; + } + + private void TrimAll(UnmanagedMemoryHandle[] buffersLocal) + { + lock (buffersLocal) + { + // Trim all: + for (int i = this.index; i < buffersLocal.Length && buffersLocal[i].IsValid; i++) + { + buffersLocal[i].Free(); + } + } + } + + private bool TrimLowPressure(UnmanagedMemoryHandle[] buffersLocal) + { + lock (buffersLocal) + { + // Count the buffers in the pool: + int retainedCount = 0; + for (int i = this.index; i < buffersLocal.Length && buffersLocal[i].IsValid; i++) + { + retainedCount++; + } + + // Trim 'trimRate' of 'retainedCount': + int trimCount = (int)Math.Ceiling(retainedCount * this.trimSettings.Rate); + int trimStart = this.index + retainedCount - 1; + int trimStop = this.index + retainedCount - trimCount; + for (int i = trimStart; i >= trimStop; i--) + { + buffersLocal[i].Free(); + } + + this.lastTrimTimestamp = Stopwatch.ElapsedMilliseconds; + } + + return true; + } + + private bool IsHighMemoryPressure() + { +#if NETCOREAPP3_1_OR_GREATER + GCMemoryInfo memoryInfo = GC.GetGCMemoryInfo(); + return memoryInfo.MemoryLoadBytes >= memoryInfo.HighMemoryLoadThresholdBytes * this.trimSettings.HighPressureThresholdRate; +#else + // We don't have high pressure detection triggering full trimming on other platforms, + // to counterpart this, the maximum pool size is small. + return false; +#endif + } + + public class TrimSettings + { + // Trim half of the retained pool buffers every minute. + public int TrimPeriodMilliseconds { get; set; } = 60_000; + + public float Rate { get; set; } = 0.5f; + + // Be more strict about high pressure on 32 bit. + public unsafe float HighPressureThresholdRate { get; set; } = sizeof(IntPtr) == 8 ? 0.9f : 0.6f; + + public bool Enabled => this.Rate > 0; + + public static TrimSettings Default => new TrimSettings(); + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs new file mode 100644 index 000000000..5f0759f20 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Memory.Internals +{ + /// + /// Defines a strategy for managing unmanaged memory ownership. + /// + internal abstract class UnmanagedBufferLifetimeGuard : RefCountedLifetimeGuard + { + private UnmanagedMemoryHandle handle; + + protected UnmanagedBufferLifetimeGuard(UnmanagedMemoryHandle handle) => this.handle = handle; + + public ref UnmanagedMemoryHandle Handle => ref this.handle; + + public sealed class FreeHandle : UnmanagedBufferLifetimeGuard + { + public FreeHandle(UnmanagedMemoryHandle handle) + : base(handle) + { + } + + protected override void Release() => this.Handle.Free(); + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs new file mode 100644 index 000000000..5d0c6dd61 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace SixLabors.ImageSharp.Memory.Internals +{ + /// + /// Allocates and provides an implementation giving + /// access to unmanaged buffers allocated by . + /// + /// The element type. + internal sealed unsafe class UnmanagedBuffer : MemoryManager, IRefCounted + where T : struct + { + private readonly int lengthInElements; + + private readonly UnmanagedBufferLifetimeGuard lifetimeGuard; + + private int disposed; + + public UnmanagedBuffer(int lengthInElements, UnmanagedBufferLifetimeGuard lifetimeGuard) + { + DebugGuard.NotNull(lifetimeGuard, nameof(lifetimeGuard)); + + this.lengthInElements = lengthInElements; + this.lifetimeGuard = lifetimeGuard; + } + + private void* Pointer => this.lifetimeGuard.Handle.Pointer; + + public override Span GetSpan() + { + DebugGuard.NotDisposed(this.disposed == 1, this.GetType().Name); + DebugGuard.NotDisposed(this.lifetimeGuard.IsDisposed, this.lifetimeGuard.GetType().Name); + return new(this.Pointer, this.lengthInElements); + } + + /// + public override MemoryHandle Pin(int elementIndex = 0) + { + DebugGuard.NotDisposed(this.disposed == 1, this.GetType().Name); + DebugGuard.NotDisposed(this.lifetimeGuard.IsDisposed, this.lifetimeGuard.GetType().Name); + + // Will be released in Unpin + this.lifetimeGuard.AddRef(); + + void* pbData = Unsafe.Add(this.Pointer, elementIndex); + return new MemoryHandle(pbData, pinnable: this); + } + + /// + protected override void Dispose(bool disposing) + { + DebugGuard.IsTrue(disposing, nameof(disposing), "Unmanaged buffers should not have finalizer!"); + + if (Interlocked.Exchange(ref this.disposed, 1) == 1) + { + // Already disposed + return; + } + + this.lifetimeGuard.Dispose(); + } + + /// + public override void Unpin() => this.lifetimeGuard.ReleaseRef(); + + public void AddRef() => this.lifetimeGuard.AddRef(); + + public void ReleaseRef() => this.lifetimeGuard.ReleaseRef(); + + public static UnmanagedBuffer Allocate(int lengthInElements) => + new(lengthInElements, new UnmanagedBufferLifetimeGuard.FreeHandle(UnmanagedMemoryHandle.Allocate(lengthInElements * Unsafe.SizeOf()))); + } +} diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs new file mode 100644 index 000000000..59d4d5bda --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs @@ -0,0 +1,140 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace SixLabors.ImageSharp.Memory.Internals +{ + /// + /// Encapsulates the functionality around allocating and releasing unmanaged memory. NOT a . + /// + internal struct UnmanagedMemoryHandle : IEquatable + { + // Number of allocation re-attempts when detecting OutOfMemoryException. + private const int MaxAllocationAttempts = 1000; + + // Track allocations for testing purposes: + private static int totalOutstandingHandles; + + private static long totalOomRetries; + + // A Monitor to wait/signal when we are low on memory. + private static object lowMemoryMonitor; + + public static readonly UnmanagedMemoryHandle NullHandle = default; + + private IntPtr handle; + private int lengthInBytes; + + private UnmanagedMemoryHandle(IntPtr handle, int lengthInBytes) + { + this.handle = handle; + this.lengthInBytes = lengthInBytes; + + if (lengthInBytes > 0) + { + GC.AddMemoryPressure(lengthInBytes); + } + + Interlocked.Increment(ref totalOutstandingHandles); + } + + public IntPtr Handle => this.handle; + + public bool IsInvalid => this.Handle == IntPtr.Zero; + + public bool IsValid => this.Handle != IntPtr.Zero; + + public unsafe void* Pointer => (void*)this.Handle; + + /// + /// Gets the total outstanding handle allocations for testing purposes. + /// + internal static int TotalOutstandingHandles => totalOutstandingHandles; + + /// + /// Gets the total number -s retried. + /// + internal static long TotalOomRetries => totalOomRetries; + + public static bool operator ==(UnmanagedMemoryHandle a, UnmanagedMemoryHandle b) => a.Equals(b); + + public static bool operator !=(UnmanagedMemoryHandle a, UnmanagedMemoryHandle b) => !a.Equals(b); + + public static UnmanagedMemoryHandle Allocate(int lengthInBytes) + { + IntPtr handle = AllocateHandle(lengthInBytes); + return new UnmanagedMemoryHandle(handle, lengthInBytes); + } + + private static IntPtr AllocateHandle(int lengthInBytes) + { + int counter = 0; + IntPtr handle = IntPtr.Zero; + while (handle == IntPtr.Zero) + { + try + { + handle = Marshal.AllocHGlobal(lengthInBytes); + } + catch (OutOfMemoryException) + { + // We are low on memory, but expect some memory to be freed soon. + // Block the thread & retry to avoid OOM. + if (counter < MaxAllocationAttempts) + { + counter++; + Interlocked.Increment(ref totalOomRetries); + + Interlocked.CompareExchange(ref lowMemoryMonitor, new object(), null); + Monitor.Enter(lowMemoryMonitor); + Monitor.Wait(lowMemoryMonitor, millisecondsTimeout: 1); + Monitor.Exit(lowMemoryMonitor); + } + else + { + throw; + } + } + } + + return handle; + } + + public void Free() + { + IntPtr h = Interlocked.Exchange(ref this.handle, IntPtr.Zero); + + if (h == IntPtr.Zero) + { + return; + } + + Marshal.FreeHGlobal(h); + Interlocked.Decrement(ref totalOutstandingHandles); + if (this.lengthInBytes > 0) + { + GC.RemoveMemoryPressure(this.lengthInBytes); + } + + if (Volatile.Read(ref lowMemoryMonitor) != null) + { + // We are low on memory. Signal all threads waiting in AllocateHandle(). + Monitor.Enter(lowMemoryMonitor); + Monitor.PulseAll(lowMemoryMonitor); + Monitor.Exit(lowMemoryMonitor); + } + + this.lengthInBytes = 0; + } + + public bool Equals(UnmanagedMemoryHandle other) => this.handle.Equals(other.handle); + + public override bool Equals(object obj) => obj is UnmanagedMemoryHandle other && this.Equals(other); + + public override int GetHashCode() => this.handle.GetHashCode(); + } +} diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index af56b99a0..4df78d9d9 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -11,12 +11,37 @@ namespace SixLabors.ImageSharp.Memory /// public abstract class MemoryAllocator { + /// + /// Gets the default platform-specific global instance that + /// serves as the default value for . + /// + /// This is a get-only property, + /// you should set 's + /// to change the default allocator used by and it's operations. + /// + public static MemoryAllocator Default { get; } = Create(); + /// /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes. /// /// The length of the largest contiguous buffer that can be handled by this allocator instance. protected internal abstract int GetBufferCapacityInBytes(); + /// + /// Creates a default instance of a optimized for the executing platform. + /// + /// The . + public static MemoryAllocator Create() => + new UniformUnmanagedMemoryPoolMemoryAllocator(null); + + /// + /// Creates the default using the provided options. + /// + /// The . + /// The . + public static MemoryAllocator Create(MemoryAllocatorOptions options) => + new UniformUnmanagedMemoryPoolMemoryAllocator(options.MaximumPoolSizeMegabytes); + /// /// Allocates an , holding a of length . /// @@ -29,16 +54,6 @@ namespace SixLabors.ImageSharp.Memory public abstract IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) where T : struct; - /// - /// Allocates an . - /// - /// The requested buffer length. - /// The allocation options. - /// The . - /// When length is zero or negative. - /// When length is over the capacity of the allocator. - public abstract IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None); - /// /// Releases all retained resources not being in use. /// Eg: by resetting array pools and letting GC to free the arrays. @@ -46,5 +61,20 @@ namespace SixLabors.ImageSharp.Memory public virtual void ReleaseRetainedResources() { } + + /// + /// Allocates a . + /// + /// The total length of the buffer. + /// The expected alignment (eg. to make sure image rows fit into single buffers). + /// The . + /// A new . + /// Thrown when 'blockAlignment' converted to bytes is greater than the buffer capacity of the allocator. + internal virtual MemoryGroup AllocateGroup( + long totalLength, + int bufferAlignment, + AllocationOptions options = AllocationOptions.None) + where T : struct + => MemoryGroup.Allocate(this, totalLength, bufferAlignment, options); } } diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs new file mode 100644 index 000000000..22a041075 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Defines options for creating the default . + /// + public struct MemoryAllocatorOptions + { + private int? maximumPoolSizeMegabytes; + + /// + /// Gets or sets a value defining the maximum size of the 's internal memory pool + /// in Megabytes. means platform default. + /// + public int? MaximumPoolSizeMegabytes + { + get => this.maximumPoolSizeMegabytes; + set + { + if (value.HasValue) + { + Guard.MustBeGreaterThanOrEqualTo(value.Value, 0, nameof(this.MaximumPoolSizeMegabytes)); + } + + this.maximumPoolSizeMegabytes = value; + } + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs index 84494f685..a53ecbc66 100644 --- a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs @@ -21,13 +21,5 @@ namespace SixLabors.ImageSharp.Memory return new BasicArrayBuffer(new T[length]); } - - /// - public override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None) - { - Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); - - return new BasicByteBuffer(new byte[length]); - } } } diff --git a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs new file mode 100644 index 000000000..16a3cb73d --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs @@ -0,0 +1,164 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using SixLabors.ImageSharp.Memory.Internals; + +namespace SixLabors.ImageSharp.Memory +{ + internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocator + { + private const int OneMegabyte = 1 << 20; + + // 4 MB seemed to perform slightly better in benchmarks than 2MB or higher values: + private const int DefaultContiguousPoolBlockSizeBytes = 4 * OneMegabyte; + private const int DefaultNonPoolBlockSizeBytes = 32 * OneMegabyte; + private readonly int sharedArrayPoolThresholdInBytes; + private readonly int poolBufferSizeInBytes; + private readonly int poolCapacity; + private readonly UniformUnmanagedMemoryPool.TrimSettings trimSettings; + + private UniformUnmanagedMemoryPool pool; + private readonly UnmanagedMemoryAllocator nonPoolAllocator; + + public UniformUnmanagedMemoryPoolMemoryAllocator(int? maxPoolSizeMegabytes) + : this( + DefaultContiguousPoolBlockSizeBytes, + maxPoolSizeMegabytes.HasValue ? (long)maxPoolSizeMegabytes.Value * OneMegabyte : GetDefaultMaxPoolSizeBytes(), + DefaultNonPoolBlockSizeBytes) + { + } + + public UniformUnmanagedMemoryPoolMemoryAllocator( + int poolBufferSizeInBytes, + long maxPoolSizeInBytes, + int unmanagedBufferSizeInBytes) + : this( + OneMegabyte, + poolBufferSizeInBytes, + maxPoolSizeInBytes, + unmanagedBufferSizeInBytes) + { + } + + internal UniformUnmanagedMemoryPoolMemoryAllocator( + int sharedArrayPoolThresholdInBytes, + int poolBufferSizeInBytes, + long maxPoolSizeInBytes, + int unmanagedBufferSizeInBytes) + : this( + sharedArrayPoolThresholdInBytes, + poolBufferSizeInBytes, + maxPoolSizeInBytes, + unmanagedBufferSizeInBytes, + UniformUnmanagedMemoryPool.TrimSettings.Default) + { + } + + internal UniformUnmanagedMemoryPoolMemoryAllocator( + int sharedArrayPoolThresholdInBytes, + int poolBufferSizeInBytes, + long maxPoolSizeInBytes, + int unmanagedBufferSizeInBytes, + UniformUnmanagedMemoryPool.TrimSettings trimSettings) + { + this.sharedArrayPoolThresholdInBytes = sharedArrayPoolThresholdInBytes; + this.poolBufferSizeInBytes = poolBufferSizeInBytes; + this.poolCapacity = (int)(maxPoolSizeInBytes / poolBufferSizeInBytes); + this.trimSettings = trimSettings; + this.pool = new UniformUnmanagedMemoryPool(this.poolBufferSizeInBytes, this.poolCapacity, this.trimSettings); + this.nonPoolAllocator = new UnmanagedMemoryAllocator(unmanagedBufferSizeInBytes); + } + + /// + protected internal override int GetBufferCapacityInBytes() => this.poolBufferSizeInBytes; + + /// + public override IMemoryOwner Allocate( + int length, + AllocationOptions options = AllocationOptions.None) + { + Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); + int lengthInBytes = length * Unsafe.SizeOf(); + + if (lengthInBytes <= this.sharedArrayPoolThresholdInBytes) + { + var buffer = new SharedArrayPoolBuffer(length); + if (options.Has(AllocationOptions.Clean)) + { + buffer.GetSpan().Clear(); + } + + return buffer; + } + + if (lengthInBytes <= this.poolBufferSizeInBytes) + { + UnmanagedMemoryHandle mem = this.pool.Rent(); + if (mem.IsValid) + { + UnmanagedBuffer buffer = this.pool.CreateGuardedBuffer(mem, length, options.Has(AllocationOptions.Clean)); + return buffer; + } + } + + return this.nonPoolAllocator.Allocate(length, options); + } + + /// + internal override MemoryGroup AllocateGroup( + long totalLength, + int bufferAlignment, + AllocationOptions options = AllocationOptions.None) + { + long totalLengthInBytes = totalLength * Unsafe.SizeOf(); + if (totalLengthInBytes <= this.sharedArrayPoolThresholdInBytes) + { + var buffer = new SharedArrayPoolBuffer((int)totalLength); + return MemoryGroup.CreateContiguous(buffer, options.Has(AllocationOptions.Clean)); + } + + if (totalLengthInBytes <= this.poolBufferSizeInBytes) + { + // Optimized path renting single array from the pool + UnmanagedMemoryHandle mem = this.pool.Rent(); + if (mem.IsValid) + { + UnmanagedBuffer buffer = this.pool.CreateGuardedBuffer(mem, (int)totalLength, options.Has(AllocationOptions.Clean)); + return MemoryGroup.CreateContiguous(buffer, options.Has(AllocationOptions.Clean)); + } + } + + // Attempt to rent the whole group from the pool, allocate a group of unmanaged buffers if the attempt fails: + if (MemoryGroup.TryAllocate(this.pool, totalLength, bufferAlignment, options, out MemoryGroup poolGroup)) + { + return poolGroup; + } + + return MemoryGroup.Allocate(this.nonPoolAllocator, totalLength, bufferAlignment, options); + } + + public override void ReleaseRetainedResources() => this.pool.Release(); + + private static long GetDefaultMaxPoolSizeBytes() + { +#if NETCOREAPP3_1_OR_GREATER + // On 64 bit .NET Core 3.1+, set the pool size to a portion of the total available memory. + // There is a bug in GC.GetGCMemoryInfo() on .NET 5 + 32 bit, making TotalAvailableMemoryBytes unreliable: + // https://github.com/dotnet/runtime/issues/55126#issuecomment-876779327 + if (Environment.Is64BitProcess || !RuntimeInformation.FrameworkDescription.StartsWith(".NET 5.0")) + { + GCMemoryInfo info = GC.GetGCMemoryInfo(); + return info.TotalAvailableMemoryBytes / 8; + } +#endif + + // Stick to a conservative value of 128 Megabytes on other platforms and 32 bit .NET 5.0: + return 128 * OneMegabyte; + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs new file mode 100644 index 000000000..74197b0a1 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using SixLabors.ImageSharp.Memory.Internals; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// A implementation that allocates memory on the unmanaged heap + /// without any pooling. + /// + internal class UnmanagedMemoryAllocator : MemoryAllocator + { + private readonly int bufferCapacityInBytes; + + public UnmanagedMemoryAllocator(int bufferCapacityInBytes) => this.bufferCapacityInBytes = bufferCapacityInBytes; + + protected internal override int GetBufferCapacityInBytes() => this.bufferCapacityInBytes; + + public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) + { + var buffer = UnmanagedBuffer.Allocate(length); + if (options.Has(AllocationOptions.Clean)) + { + buffer.GetSpan().Clear(); + } + + return buffer; + } + } +} diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index 6458ad7e4..58143de4e 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -111,10 +111,16 @@ namespace SixLabors.ImageSharp.Memory /// The /// The of the buffer internal static Size Size(this Buffer2D buffer) - where T : struct - { - return new Size(buffer.Width, buffer.Height); - } + where T : struct => + new(buffer.Width, buffer.Height); + + /// + /// Gets the bounds of the buffer. + /// + /// The + internal static Rectangle Bounds(this Buffer2D buffer) + where T : struct => + new(0, 0, buffer.Width, buffer.Height); [Conditional("DEBUG")] private static void CheckColumnRegionsDoNotOverlap( diff --git a/src/ImageSharp/Memory/Buffer2DRegion{T}.cs b/src/ImageSharp/Memory/Buffer2DRegion{T}.cs index 8c5988944..d1c39ccbf 100644 --- a/src/ImageSharp/Memory/Buffer2DRegion{T}.cs +++ b/src/ImageSharp/Memory/Buffer2DRegion{T}.cs @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Memory int xx = this.Rectangle.X; int width = this.Rectangle.Width; - return this.Buffer.GetRowSpan(yy).Slice(xx, width); + return this.Buffer.DangerousGetRowSpan(yy).Slice(xx, width); } /// @@ -138,7 +138,7 @@ namespace SixLabors.ImageSharp.Memory { int y = this.Rectangle.Y; int x = this.Rectangle.X; - return ref this.Buffer.GetRowSpan(y)[x]; + return ref this.Buffer.DangerousGetRowSpan(y)[x]; } internal void Clear() diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index b62c9beb5..ba39a924e 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -19,8 +19,6 @@ namespace SixLabors.ImageSharp.Memory public sealed class Buffer2D : IDisposable where T : struct { - private Memory cachedMemory = default; - /// /// Initializes a new instance of the class. /// @@ -32,11 +30,6 @@ namespace SixLabors.ImageSharp.Memory this.FastMemoryGroup = memoryGroup; this.Width = width; this.Height = height; - - if (memoryGroup.Count == 1) - { - this.cachedMemory = memoryGroup[0]; - } } /// @@ -82,18 +75,14 @@ namespace SixLabors.ImageSharp.Memory DebugGuard.MustBeLessThan(x, this.Width, nameof(x)); DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); - return ref this.GetRowSpan(y)[x]; + return ref this.DangerousGetRowSpan(y)[x]; } } /// /// Disposes the instance /// - public void Dispose() - { - this.FastMemoryGroup.Dispose(); - this.cachedMemory = default; - } + public void Dispose() => this.FastMemoryGroup.Dispose(); /// /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. @@ -106,14 +95,12 @@ namespace SixLabors.ImageSharp.Memory /// The of the pixels in the row. /// Thrown when row index is out of range. [MethodImpl(InliningOptions.ShortMethod)] - public Span GetRowSpan(int y) + public Span DangerousGetRowSpan(int y) { DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); - return this.cachedMemory.Length > 0 - ? this.cachedMemory.Span.Slice(y * this.Width, this.Width) - : this.GetRowMemorySlow(y).Span; + return this.GetRowMemoryCore(y).Span; } internal bool TryGetPaddedRowSpan(int y, int padding, out Span paddedSpan) @@ -122,17 +109,6 @@ namespace SixLabors.ImageSharp.Memory DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); int stride = this.Width + padding; - if (this.cachedMemory.Length > 0) - { - paddedSpan = this.cachedMemory.Span.Slice(y * this.Width); - if (paddedSpan.Length < stride) - { - return false; - } - - paddedSpan = paddedSpan.Slice(0, stride); - return true; - } Memory memory = this.FastMemoryGroup.GetRemainingSliceOfBuffer(y * (long)this.Width); @@ -149,31 +125,8 @@ namespace SixLabors.ImageSharp.Memory [MethodImpl(InliningOptions.ShortMethod)] internal ref T GetElementUnsafe(int x, int y) { - if (this.cachedMemory.Length > 0) - { - Span span = this.cachedMemory.Span; - ref T start = ref MemoryMarshal.GetReference(span); - return ref Unsafe.Add(ref start, (y * this.Width) + x); - } - - return ref this.GetElementSlow(x, y); - } - - /// - /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. - /// This method is intended for internal use only, since it does not use the indirection provided by - /// . - /// - /// The y (row) coordinate. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - internal Memory GetFastRowMemory(int y) - { - DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); - DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); - return this.cachedMemory.Length > 0 - ? this.cachedMemory.Slice(y * this.Width, this.Width) - : this.GetRowMemorySlow(y); + Span span = this.GetRowMemoryCore(y).Span; + return ref span[x]; } /// @@ -198,11 +151,7 @@ namespace SixLabors.ImageSharp.Memory /// Thrown when the backing group is discontiguous. /// [MethodImpl(InliningOptions.ShortMethod)] - internal Span DangerousGetSingleSpan() - { - // TODO: If we need a public version of this method, we need to cache the non-fast Memory of this.MemoryGroup - return this.cachedMemory.Length != 0 ? this.cachedMemory.Span : this.DangerousGetSingleSpanSlow(); - } + internal Span DangerousGetSingleSpan() => this.FastMemoryGroup.Single().Span; /// /// Gets a to the backing data of if the backing group consists of a single contiguous memory buffer. @@ -213,11 +162,7 @@ namespace SixLabors.ImageSharp.Memory /// Thrown when the backing group is discontiguous. /// [MethodImpl(InliningOptions.ShortMethod)] - internal Memory DangerousGetSingleMemory() - { - // TODO: If we need a public version of this method, we need to cache the non-fast Memory of this.MemoryGroup - return this.cachedMemory.Length != 0 ? this.cachedMemory : this.DangerousGetSingleMemorySlow(); - } + internal Memory DangerousGetSingleMemory() => this.FastMemoryGroup.Single(); /// /// Swaps the contents of 'destination' with 'source' if the buffers are owned (1), @@ -225,27 +170,14 @@ namespace SixLabors.ImageSharp.Memory /// internal static void SwapOrCopyContent(Buffer2D destination, Buffer2D source) { - bool swap = MemoryGroup.SwapOrCopyContent(destination.FastMemoryGroup, source.FastMemoryGroup); - SwapOwnData(destination, source, swap); + MemoryGroup.SwapOrCopyContent(destination.FastMemoryGroup, source.FastMemoryGroup); + SwapOwnData(destination, source); } - [MethodImpl(InliningOptions.ColdPath)] - private Memory GetRowMemorySlow(int y) => this.FastMemoryGroup.GetBoundedSlice(y * (long)this.Width, this.Width); - - [MethodImpl(InliningOptions.ColdPath)] - private Memory DangerousGetSingleMemorySlow() => this.FastMemoryGroup.Single(); - - [MethodImpl(InliningOptions.ColdPath)] - private Span DangerousGetSingleSpanSlow() => this.FastMemoryGroup.Single().Span; - - [MethodImpl(InliningOptions.ColdPath)] - private ref T GetElementSlow(int x, int y) - { - Span span = this.GetRowMemorySlow(y).Span; - return ref span[x]; - } + [MethodImpl(InliningOptions.ShortMethod)] + private Memory GetRowMemoryCore(int y) => this.FastMemoryGroup.GetBoundedSlice(y * (long)this.Width, this.Width); - private static void SwapOwnData(Buffer2D a, Buffer2D b, bool swapCachedMemory) + private static void SwapOwnData(Buffer2D a, Buffer2D b) { Size aSize = a.Size(); Size bSize = b.Size(); @@ -255,13 +187,6 @@ namespace SixLabors.ImageSharp.Memory a.Width = bSize.Width; a.Height = bSize.Height; - - if (swapCachedMemory) - { - Memory aCached = a.cachedMemory; - a.cachedMemory = b.cachedMemory; - b.cachedMemory = aCached; - } } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs index cc2a2f17c..7c58c9c01 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs @@ -50,10 +50,7 @@ namespace SixLabors.ImageSharp.Memory return ((IList>)this.source).GetEnumerator(); } - public override void Dispose() - { - this.View.Invalidate(); - } + public override void Dispose() => this.View.Invalidate(); } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs index 35290c109..3b9241383 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -6,6 +6,7 @@ using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory.Internals; namespace SixLabors.ImageSharp.Memory { @@ -17,6 +18,7 @@ namespace SixLabors.ImageSharp.Memory public sealed class Owned : MemoryGroup, IEnumerable> { private IMemoryOwner[] memoryOwners; + private RefCountedLifetimeGuard groupLifetimeGuard; public Owned(IMemoryOwner[] memoryOwners, int bufferLength, long totalLength, bool swappable) : base(bufferLength, totalLength) @@ -26,6 +28,16 @@ namespace SixLabors.ImageSharp.Memory this.View = new MemoryGroupView(this); } + public Owned( + UniformUnmanagedMemoryPool pool, + UnmanagedMemoryHandle[] pooledHandles, + int bufferLength, + long totalLength, + int sizeOfLastBuffer, + AllocationOptions options) + : this(CreateBuffers(pooledHandles, bufferLength, sizeOfLastBuffer, options), bufferLength, totalLength, true) => + this.groupLifetimeGuard = pool.CreateGroupLifetimeGuard(pooledHandles); + public bool Swappable { get; } private bool IsDisposed => this.memoryOwners == null; @@ -49,11 +61,65 @@ namespace SixLabors.ImageSharp.Memory } } + private static IMemoryOwner[] CreateBuffers( + UnmanagedMemoryHandle[] pooledBuffers, + int bufferLength, + int sizeOfLastBuffer, + AllocationOptions options) + { + var result = new IMemoryOwner[pooledBuffers.Length]; + for (int i = 0; i < pooledBuffers.Length - 1; i++) + { + var currentBuffer = ObservedBuffer.Create(pooledBuffers[i], bufferLength, options); + result[i] = currentBuffer; + } + + var lastBuffer = ObservedBuffer.Create(pooledBuffers[pooledBuffers.Length - 1], sizeOfLastBuffer, options); + result[result.Length - 1] = lastBuffer; + return result; + } + /// [MethodImpl(InliningOptions.ShortMethod)] - public override MemoryGroupEnumerator GetEnumerator() + public override MemoryGroupEnumerator GetEnumerator() => new(this); + + public override void IncreaseRefCounts() { - return new MemoryGroupEnumerator(this); + this.EnsureNotDisposed(); + + if (this.groupLifetimeGuard != null) + { + this.groupLifetimeGuard.AddRef(); + } + else + { + foreach (IMemoryOwner memoryOwner in this.memoryOwners) + { + if (memoryOwner is IRefCounted unmanagedBuffer) + { + unmanagedBuffer.AddRef(); + } + } + } + } + + public override void DecreaseRefCounts() + { + this.EnsureNotDisposed(); + if (this.groupLifetimeGuard != null) + { + this.groupLifetimeGuard.ReleaseRef(); + } + else + { + foreach (IMemoryOwner memoryOwner in this.memoryOwners) + { + if (memoryOwner is IRefCounted unmanagedBuffer) + { + unmanagedBuffer.ReleaseRef(); + } + } + } } /// @@ -72,13 +138,21 @@ namespace SixLabors.ImageSharp.Memory this.View.Invalidate(); - foreach (IMemoryOwner memoryOwner in this.memoryOwners) + if (this.groupLifetimeGuard != null) + { + this.groupLifetimeGuard.Dispose(); + } + else { - memoryOwner.Dispose(); + foreach (IMemoryOwner memoryOwner in this.memoryOwners) + { + memoryOwner.Dispose(); + } } this.memoryOwners = null; this.IsValid = false; + this.groupLifetimeGuard = null; } [MethodImpl(InliningOptions.ShortMethod)] @@ -91,10 +165,7 @@ namespace SixLabors.ImageSharp.Memory } [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowObjectDisposedException() - { - throw new ObjectDisposedException(nameof(MemoryGroup)); - } + private static void ThrowObjectDisposedException() => throw new ObjectDisposedException(nameof(MemoryGroup)); internal static void SwapContents(Owned a, Owned b) { @@ -104,20 +175,69 @@ namespace SixLabors.ImageSharp.Memory IMemoryOwner[] tempOwners = a.memoryOwners; long tempTotalLength = a.TotalLength; int tempBufferLength = a.BufferLength; + RefCountedLifetimeGuard tempGroupOwner = a.groupLifetimeGuard; a.memoryOwners = b.memoryOwners; a.TotalLength = b.TotalLength; a.BufferLength = b.BufferLength; + a.groupLifetimeGuard = b.groupLifetimeGuard; b.memoryOwners = tempOwners; b.TotalLength = tempTotalLength; b.BufferLength = tempBufferLength; + b.groupLifetimeGuard = tempGroupOwner; a.View.Invalidate(); b.View.Invalidate(); a.View = new MemoryGroupView(a); b.View = new MemoryGroupView(b); } + + // When the MemoryGroup points to multiple buffers via `groupLifetimeGuard`, + // the lifetime of the individual buffers is managed by the guard. + // Group buffer IMemoryOwner-s d not manage ownership. + private sealed class ObservedBuffer : MemoryManager + { + private readonly UnmanagedMemoryHandle handle; + private readonly int lengthInElements; + + private ObservedBuffer(UnmanagedMemoryHandle handle, int lengthInElements) + { + this.handle = handle; + this.lengthInElements = lengthInElements; + } + + public static ObservedBuffer Create( + UnmanagedMemoryHandle handle, + int lengthInElements, + AllocationOptions options) + { + var buffer = new ObservedBuffer(handle, lengthInElements); + if (options.Has(AllocationOptions.Clean)) + { + buffer.GetSpan().Clear(); + } + + return buffer; + } + + protected override void Dispose(bool disposing) + { + // No-op. + } + + public override unsafe Span GetSpan() => new(this.handle.Pointer, this.lengthInElements); + + public override unsafe MemoryHandle Pin(int elementIndex = 0) + { + void* pbData = Unsafe.Add(this.handle.Pointer, elementIndex); + return new MemoryHandle(pbData); + } + + public override void Unpin() + { + } + } } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index 451a8f7e3..cdd8e6a75 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -6,6 +6,7 @@ using System.Buffers; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory.Internals; namespace SixLabors.ImageSharp.Memory { @@ -67,44 +68,45 @@ namespace SixLabors.ImageSharp.Memory /// Creates a new memory group, allocating it's buffers with the provided allocator. /// /// The to use. - /// The total length of the buffer. - /// The expected alignment (eg. to make sure image rows fit into single buffers). + /// The total length of the buffer. + /// The expected alignment (eg. to make sure image rows fit into single buffers). /// The . /// A new . /// Thrown when 'blockAlignment' converted to bytes is greater than the buffer capacity of the allocator. public static MemoryGroup Allocate( MemoryAllocator allocator, - long totalLength, - int bufferAlignment, + long totalLengthInElements, + int bufferAlignmentInElements, AllocationOptions options = AllocationOptions.None) { + int bufferCapacityInBytes = allocator.GetBufferCapacityInBytes(); Guard.NotNull(allocator, nameof(allocator)); - Guard.MustBeGreaterThanOrEqualTo(totalLength, 0, nameof(totalLength)); - Guard.MustBeGreaterThanOrEqualTo(bufferAlignment, 0, nameof(bufferAlignment)); + Guard.MustBeGreaterThanOrEqualTo(totalLengthInElements, 0, nameof(totalLengthInElements)); + Guard.MustBeGreaterThanOrEqualTo(bufferAlignmentInElements, 0, nameof(bufferAlignmentInElements)); - int blockCapacityInElements = allocator.GetBufferCapacityInBytes() / ElementSize; + int blockCapacityInElements = bufferCapacityInBytes / ElementSize; - if (bufferAlignment > blockCapacityInElements) + if (bufferAlignmentInElements > blockCapacityInElements) { throw new InvalidMemoryOperationException( - $"The buffer capacity of the provided MemoryAllocator is insufficient for the requested buffer alignment: {bufferAlignment}."); + $"The buffer capacity of the provided MemoryAllocator is insufficient for the requested buffer alignment: {bufferAlignmentInElements}."); } - if (totalLength == 0) + if (totalLengthInElements == 0) { var buffers0 = new IMemoryOwner[1] { allocator.Allocate(0, options) }; return new Owned(buffers0, 0, 0, true); } - int numberOfAlignedSegments = blockCapacityInElements / bufferAlignment; - int bufferLength = numberOfAlignedSegments * bufferAlignment; - if (totalLength > 0 && totalLength < bufferLength) + int numberOfAlignedSegments = blockCapacityInElements / bufferAlignmentInElements; + int bufferLength = numberOfAlignedSegments * bufferAlignmentInElements; + if (totalLengthInElements > 0 && totalLengthInElements < bufferLength) { - bufferLength = (int)totalLength; + bufferLength = (int)totalLengthInElements; } - int sizeOfLastBuffer = (int)(totalLength % bufferLength); - long bufferCount = totalLength / bufferLength; + int sizeOfLastBuffer = (int)(totalLengthInElements % bufferLength); + long bufferCount = totalLengthInElements / bufferLength; if (sizeOfLastBuffer == 0) { @@ -126,7 +128,75 @@ namespace SixLabors.ImageSharp.Memory buffers[buffers.Length - 1] = allocator.Allocate(sizeOfLastBuffer, options); } - return new Owned(buffers, bufferLength, totalLength, true); + return new Owned(buffers, bufferLength, totalLengthInElements, true); + } + + public static MemoryGroup CreateContiguous(IMemoryOwner buffer, bool clear) + { + if (clear) + { + buffer.GetSpan().Clear(); + } + + int length = buffer.Memory.Length; + var buffers = new IMemoryOwner[1] { buffer }; + return new Owned(buffers, length, length, true); + } + + public static bool TryAllocate( + UniformUnmanagedMemoryPool pool, + long totalLengthInElements, + int bufferAlignmentInElements, + AllocationOptions options, + out MemoryGroup memoryGroup) + { + Guard.NotNull(pool, nameof(pool)); + Guard.MustBeGreaterThanOrEqualTo(totalLengthInElements, 0, nameof(totalLengthInElements)); + Guard.MustBeGreaterThanOrEqualTo(bufferAlignmentInElements, 0, nameof(bufferAlignmentInElements)); + + int blockCapacityInElements = pool.BufferLength / ElementSize; + + if (bufferAlignmentInElements > blockCapacityInElements) + { + memoryGroup = null; + return false; + } + + if (totalLengthInElements == 0) + { + throw new InvalidMemoryOperationException("Allocating 0 length buffer from UniformByteArrayPool is disallowed"); + } + + int numberOfAlignedSegments = blockCapacityInElements / bufferAlignmentInElements; + int bufferLength = numberOfAlignedSegments * bufferAlignmentInElements; + if (totalLengthInElements > 0 && totalLengthInElements < bufferLength) + { + bufferLength = (int)totalLengthInElements; + } + + int sizeOfLastBuffer = (int)(totalLengthInElements % bufferLength); + int bufferCount = (int)(totalLengthInElements / bufferLength); + + if (sizeOfLastBuffer == 0) + { + sizeOfLastBuffer = bufferLength; + } + else + { + bufferCount++; + } + + UnmanagedMemoryHandle[] arrays = pool.Rent(bufferCount); + + if (arrays == null) + { + // Pool is full + memoryGroup = null; + return false; + } + + memoryGroup = new Owned(pool, arrays, bufferLength, totalLengthInElements, sizeOfLastBuffer, options); + return true; } public static MemoryGroup Wrap(params Memory[] source) @@ -196,5 +266,13 @@ namespace SixLabors.ImageSharp.Memory return false; } } + + public virtual void IncreaseRefCounts() + { + } + + public virtual void DecreaseRefCounts() + { + } } } diff --git a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs index 2f70ac05e..abcf078ac 100644 --- a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs +++ b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs @@ -20,20 +20,50 @@ namespace SixLabors.ImageSharp.Memory /// The memory allocator. /// The buffer width. /// The buffer height. + /// A value indicating whether the allocated buffer should be contiguous, unless bigger than . /// The allocation options. /// The . public static Buffer2D Allocate2D( this MemoryAllocator memoryAllocator, int width, int height, + bool preferContiguosImageBuffers, AllocationOptions options = AllocationOptions.None) where T : struct { long groupLength = (long)width * height; - MemoryGroup memoryGroup = memoryAllocator.AllocateGroup(groupLength, width, options); + MemoryGroup memoryGroup; + if (preferContiguosImageBuffers && groupLength < int.MaxValue) + { + IMemoryOwner buffer = memoryAllocator.Allocate((int)groupLength, options); + memoryGroup = MemoryGroup.CreateContiguous(buffer, false); + } + else + { + memoryGroup = memoryAllocator.AllocateGroup(groupLength, width, options); + } + return new Buffer2D(memoryGroup, width, height); } + /// + /// Allocates a buffer of value type objects interpreted as a 2D region + /// of x elements. + /// + /// The type of buffer items to allocate. + /// The memory allocator. + /// The buffer width. + /// The buffer height. + /// The allocation options. + /// The . + public static Buffer2D Allocate2D( + this MemoryAllocator memoryAllocator, + int width, + int height, + AllocationOptions options = AllocationOptions.None) + where T : struct => + Allocate2D(memoryAllocator, width, height, false, options); + /// /// Allocates a buffer of value type objects interpreted as a 2D region /// of width x height elements. @@ -41,14 +71,32 @@ namespace SixLabors.ImageSharp.Memory /// The type of buffer items to allocate. /// The memory allocator. /// The buffer size. + /// A value indicating whether the allocated buffer should be contiguous, unless bigger than . /// The allocation options. /// The . public static Buffer2D Allocate2D( this MemoryAllocator memoryAllocator, Size size, + bool preferContiguosImageBuffers, AllocationOptions options = AllocationOptions.None) where T : struct => - Allocate2D(memoryAllocator, size.Width, size.Height, options); + Allocate2D(memoryAllocator, size.Width, size.Height, preferContiguosImageBuffers, options); + + /// + /// Allocates a buffer of value type objects interpreted as a 2D region + /// of width x height elements. + /// + /// The type of buffer items to allocate. + /// The memory allocator. + /// The buffer size. + /// The allocation options. + /// The . + public static Buffer2D Allocate2D( + this MemoryAllocator memoryAllocator, + Size size, + AllocationOptions options = AllocationOptions.None) + where T : struct => + Allocate2D(memoryAllocator, size.Width, size.Height, false, options); internal static Buffer2D Allocate2DOveraligned( this MemoryAllocator memoryAllocator, @@ -83,22 +131,5 @@ namespace SixLabors.ImageSharp.Memory int length = (width * pixelSizeInBytes) + paddingInBytes; return memoryAllocator.Allocate(length); } - - /// - /// Allocates a . - /// - /// The to use. - /// The total length of the buffer. - /// The expected alignment (eg. to make sure image rows fit into single buffers). - /// The . - /// A new . - /// Thrown when 'blockAlignment' converted to bytes is greater than the buffer capacity of the allocator. - internal static MemoryGroup AllocateGroup( - this MemoryAllocator memoryAllocator, - long totalLength, - int bufferAlignment, - AllocationOptions options = AllocationOptions.None) - where T : struct - => MemoryGroup.Allocate(memoryAllocator, totalLength, bufferAlignment, options); } } diff --git a/src/ImageSharp/Memory/UnmanagedMemoryManager{T}.cs b/src/ImageSharp/Memory/UnmanagedMemoryManager{T}.cs index 58eaee320..8e8d1aa2f 100644 --- a/src/ImageSharp/Memory/UnmanagedMemoryManager{T}.cs +++ b/src/ImageSharp/Memory/UnmanagedMemoryManager{T}.cs @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Memory /// public override MemoryHandle Pin(int elementIndex = 0) { - return new MemoryHandle(((T*)this.pointer) + elementIndex); + return new MemoryHandle(((T*)this.pointer) + elementIndex, pinnable: this); } /// diff --git a/src/ImageSharp/PixelAccessor{TPixel}.cs b/src/ImageSharp/PixelAccessor{TPixel}.cs new file mode 100644 index 000000000..36671439e --- /dev/null +++ b/src/ImageSharp/PixelAccessor{TPixel}.cs @@ -0,0 +1,72 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp +{ + /// + /// A delegate to be executed on a . + /// + /// The pixel type. + public delegate void PixelAccessorAction(PixelAccessor pixelAccessor) + where TPixel : unmanaged, IPixel; + + /// + /// A delegate to be executed on two instances of . + /// + /// The first pixel type. + /// The second pixel type. + public delegate void PixelAccessorAction( + PixelAccessor pixelAccessor1, + PixelAccessor pixelAccessor2) + where TPixel1 : unmanaged, IPixel + where TPixel2 : unmanaged, IPixel; + + /// + /// A delegate to be executed on three instances of . + /// + /// The first pixel type. + /// The second pixel type. + /// The third pixel type. + public delegate void PixelAccessorAction( + PixelAccessor pixelAccessor1, + PixelAccessor pixelAccessor2, + PixelAccessor pixelAccessor3) + where TPixel1 : unmanaged, IPixel + where TPixel2 : unmanaged, IPixel + where TPixel3 : unmanaged, IPixel; + + /// + /// Provides efficient access the pixel buffers of an . + /// + /// The pixel type. + public ref struct PixelAccessor + where TPixel : unmanaged, IPixel + { + private Buffer2D buffer; + + internal PixelAccessor(Buffer2D buffer) => this.buffer = buffer; + + /// + /// Gets the width of the backing . + /// + public int Width => this.buffer.Width; + + /// + /// Gets the height of the backing . + /// + public int Height => this.buffer.Height; + + /// + /// Gets the representation of the pixels as a of contiguous memory + /// at row beginning from the first pixel on that row. + /// + /// The row index. + /// The . + /// Thrown when row index is out of range. + public Span GetRowSpan(int rowIndex) => this.buffer.DangerousGetRowSpan(rowIndex); + } +} diff --git a/src/ImageSharp/PixelFormats/IPixel.cs b/src/ImageSharp/PixelFormats/IPixel.cs index 12b5bc784..09f8cc955 100644 --- a/src/ImageSharp/PixelFormats/IPixel.cs +++ b/src/ImageSharp/PixelFormats/IPixel.cs @@ -79,6 +79,12 @@ namespace SixLabors.ImageSharp.PixelFormats /// The value. void FromBgra32(Bgra32 source); + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromAbgr32(Abgr32 source); + /// /// Initializes the pixel instance from an value. /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs index cca7ff7db..afb07433f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs @@ -87,6 +87,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.PackedValue = source.A; + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.PackedValue = source.A; + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs new file mode 100644 index 000000000..ca8f5c144 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs @@ -0,0 +1,396 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. + /// The color components are stored in alpha, red, green, and blue order (least significant to most significant byte). + /// + /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. + /// + /// + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + [StructLayout(LayoutKind.Sequential)] + public partial struct Abgr32 : IPixel, IPackedVector + { + /// + /// Gets or sets the alpha component. + /// + public byte A; + + /// + /// Gets or sets the blue component. + /// + public byte B; + + /// + /// Gets or sets the green component. + /// + public byte G; + + /// + /// Gets or sets the red component. + /// + public byte R; + + /// + /// The maximum byte value. + /// + private static readonly Vector4 MaxBytes = new(255); + + /// + /// The half vector value. + /// + private static readonly Vector4 Half = new(0.5F); + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + [MethodImpl(InliningOptions.ShortMethod)] + public Abgr32(byte r, byte g, byte b) + { + this.R = r; + this.G = g; + this.B = b; + this.A = byte.MaxValue; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(InliningOptions.ShortMethod)] + public Abgr32(byte r, byte g, byte b, byte a) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(InliningOptions.ShortMethod)] + public Abgr32(float r, float g, float b, float a = 1) + : this() => this.Pack(r, g, b, a); + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Abgr32(Vector3 vector) + : this() => this.Pack(ref vector); + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Abgr32(Vector4 vector) + : this() => this.Pack(ref vector); + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The packed value. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Abgr32(uint packed) + : this() => this.Abgr = packed; + + /// + /// Gets or sets the packed representation of the Abgrb32 struct. + /// + public uint Abgr + { + [MethodImpl(InliningOptions.ShortMethod)] + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); + + [MethodImpl(InliningOptions.ShortMethod)] + set => Unsafe.As(ref this) = value; + } + + /// + public uint PackedValue + { + [MethodImpl(InliningOptions.ShortMethod)] + readonly get => this.Abgr; + + [MethodImpl(InliningOptions.ShortMethod)] + set => this.Abgr = value; + } + + /// + /// Converts an to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Color(Abgr32 source) => new(source); + + /// + /// Converts a to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Abgr32(Color color) => color.ToAbgr32(); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Abgr32 left, Abgr32 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Abgr32 left, Abgr32 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this = source; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) + { + // We can assign the Bgr24 value directly to last three bytes of this instance. + ref byte thisRef = ref Unsafe.As(ref this); + ref byte thisRefFromB = ref Unsafe.AddByteOffset(ref thisRef, new IntPtr(1)); + Unsafe.As(ref thisRefFromB) = source; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) + { + this.R = source.PackedValue; + this.G = source.PackedValue; + this.B = source.PackedValue; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) + { + byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.PackedValue); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.L); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.R = this.R; + dest.G = this.G; + dest.B = this.B; + dest.A = this.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + { + this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); + this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); + this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) + { + this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); + this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); + this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); + this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); + } + + /// + public override readonly bool Equals(object obj) => obj is Abgr32 abgr32 && this.Equals(abgr32); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(Abgr32 other) => this.Abgr == other.Abgr; + + /// + /// Gets a string representation of the packed vector. + /// + /// A string representation of the packed vector. + public override readonly string ToString() => $"Abgr({this.A}, {this.B}, {this.G}, {this.R})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.Abgr.GetHashCode(); + + /// + /// Packs the four floats into a color. + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(float x, float y, float z, float w) + { + var value = new Vector4(x, y, z, w); + this.Pack(ref value); + } + + /// + /// Packs a into a uint. + /// + /// The vector containing the values to pack. + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(ref Vector3 vector) + { + var value = new Vector4(vector, 1); + this.Pack(ref value); + } + + /// + /// Packs a into a color. + /// + /// The vector containing the values to pack. + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(ref Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + + this.R = (byte)vector.X; + this.G = (byte)vector.Y; + this.B = (byte)vector.Z; + this.A = (byte)vector.W; + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs index 8c1b04ff1..2ec85de93 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs @@ -230,6 +230,16 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = source.A; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromL8(L8 source) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs index 22e983a65..81c178348 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs @@ -185,6 +185,16 @@ namespace SixLabors.ImageSharp.PixelFormats this.B = source.B; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + // We can assign this instances value directly to last three bytes of the Abgr32. + ref byte sourceRef = ref Unsafe.As(ref source); + ref byte sourceRefFromB = ref Unsafe.AddByteOffset(ref sourceRef, new IntPtr(1)); + this = Unsafe.As(ref sourceRefFromB); + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromRgba32(Rgba32 source) => this = source.Bgr; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs index 5585310b9..bd21d0425 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs @@ -99,6 +99,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromVector4(source.ToVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromVector4(source.ToVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs index be4e178c2..34769e32d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs @@ -165,6 +165,16 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = source.A; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgr24(Bgr24 source) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs index 3578f1dd3..059d61136 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs @@ -102,6 +102,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs index 0254397c3..b6cc1f878 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs @@ -124,6 +124,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs index 0995f8417..e1ed4577e 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs @@ -124,6 +124,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs index b0ef0f6a9..51b6af640 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs @@ -88,6 +88,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs index 8be826130..1fff37757 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs @@ -99,6 +99,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs index 955b274ac..94051e263 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs @@ -104,6 +104,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs index 6d1128dd2..c40e301de 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs @@ -91,6 +91,13 @@ namespace SixLabors.ImageSharp.PixelFormats ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.PackedValue = ColorNumerics.Get16BitBT709Luminance( + ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs index ffff60be5..70d031aa1 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs @@ -83,6 +83,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.PackedValue = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.PackedValue = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs index 877aaed81..72f188fa3 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs @@ -112,6 +112,14 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = source.A; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.L = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); + this.A = source.A; + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs index f19f22813..d9104aa4f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs @@ -127,6 +127,18 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.L = ColorNumerics.Get16BitBT709Luminance( + ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); + + this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs index 62eaf949d..78c83a543 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs @@ -107,6 +107,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs index 2e81b3e2d..432461f75 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs @@ -109,6 +109,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs index b97aaacec..60fe2368a 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs @@ -108,6 +108,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs index f2e8aedd8..01b9777b0 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs @@ -110,6 +110,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32.PixelOperations.cs new file mode 100644 index 000000000..439c5529a --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32.PixelOperations.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Abgr32 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal partial class PixelOperations : PixelOperations + { + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); + + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs new file mode 100644 index 000000000..026025f76 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs @@ -0,0 +1,346 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +// + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Abgr32 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal partial class PixelOperations : PixelOperations + { + /// + public override void FromAbgr32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + + source.CopyTo(destinationPixels.Slice(0, source.Length)); + } + + /// + public override void ToAbgr32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); + } + /// + public override void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span destinationPixels, + PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(PixelConversionModifiers.Scale)); + } + + /// + public override void ToVector4( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destVectors, + PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale)); + } + /// + public override void ToRgba32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToRgba32(source, dest); + } + + /// + public override void FromRgba32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgba32.ToAbgr32(source, dest); + } + /// + public override void ToArgb32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToArgb32(source, dest); + } + + /// + public override void FromArgb32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromArgb32.ToAbgr32(source, dest); + } + /// + public override void ToBgra32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToBgra32(source, dest); + } + + /// + public override void FromBgra32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgra32.ToAbgr32(source, dest); + } + /// + public override void ToRgb24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToRgb24(source, dest); + } + + /// + public override void FromRgb24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgb24.ToAbgr32(source, dest); + } + /// + public override void ToBgr24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToBgr24(source, dest); + } + + /// + public override void FromBgr24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgr24.ToAbgr32(source, dest); + } + /// + public override void ToL8( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) + { + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromAbgr32(sp); + } + } + /// + public override void ToL16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) + { + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromAbgr32(sp); + } + } + /// + public override void ToLa16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) + { + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromAbgr32(sp); + } + } + /// + public override void ToLa32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) + { + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromAbgr32(sp); + } + } + /// + public override void ToRgb48( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) + { + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromAbgr32(sp); + } + } + /// + public override void ToRgba64( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) + { + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromAbgr32(sp); + } + } + /// + public override void ToBgra5551( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) + { + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromAbgr32(sp); + } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToAbgr32(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); + } + + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.tt new file mode 100644 index 000000000..071c74fbb --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.tt @@ -0,0 +1,18 @@ +<#@include file="_Common.ttinclude" #> +<#@ output extension=".cs" #> +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Abgr32 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal partial class PixelOperations : PixelOperations + { + <# GenerateAllDefaultConversionMethods("Abgr32"); #> + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs index cedd1762d..4b9f68a68 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs @@ -85,6 +85,33 @@ namespace SixLabors.ImageSharp.PixelFormats PixelConverter.FromRgba32.ToArgb32(source, dest); } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromArgb32.ToAbgr32(source, dest); + } + + /// + public override void FromAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToArgb32(source, dest); + } + /// public override void ToBgra32( Configuration configuration, ReadOnlySpan sourcePixels, @@ -177,7 +204,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); ref L8 dp = ref Unsafe.Add(ref destRef, i); @@ -197,7 +224,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); ref L16 dp = ref Unsafe.Add(ref destRef, i); @@ -217,7 +244,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); ref La16 dp = ref Unsafe.Add(ref destRef, i); @@ -237,7 +264,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); ref La32 dp = ref Unsafe.Add(ref destRef, i); @@ -257,7 +284,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); @@ -277,7 +304,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); @@ -297,7 +324,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs index c98e35656..c85e4ee3b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs @@ -112,6 +112,33 @@ namespace SixLabors.ImageSharp.PixelFormats PixelConverter.FromArgb32.ToBgr24(source, dest); } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgr24.ToAbgr32(source, dest); + } + + /// + public override void FromAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToBgr24(source, dest); + } + /// public override void ToBgra32( Configuration configuration, ReadOnlySpan sourcePixels, @@ -177,7 +204,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); ref L8 dp = ref Unsafe.Add(ref destRef, i); @@ -197,7 +224,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); ref L16 dp = ref Unsafe.Add(ref destRef, i); @@ -217,7 +244,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); ref La16 dp = ref Unsafe.Add(ref destRef, i); @@ -237,7 +264,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); ref La32 dp = ref Unsafe.Add(ref destRef, i); @@ -257,7 +284,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); @@ -277,7 +304,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); @@ -297,7 +324,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs index 02bb67532..ed1366b0a 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs @@ -112,6 +112,33 @@ namespace SixLabors.ImageSharp.PixelFormats PixelConverter.FromArgb32.ToBgra32(source, dest); } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgra32.ToAbgr32(source, dest); + } + + /// + public override void FromAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToBgra32(source, dest); + } + /// public override void ToRgb24( Configuration configuration, ReadOnlySpan sourcePixels, @@ -177,7 +204,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); ref L8 dp = ref Unsafe.Add(ref destRef, i); @@ -197,7 +224,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); ref L16 dp = ref Unsafe.Add(ref destRef, i); @@ -217,7 +244,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); ref La16 dp = ref Unsafe.Add(ref destRef, i); @@ -237,7 +264,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); ref La32 dp = ref Unsafe.Add(ref destRef, i); @@ -257,7 +284,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); @@ -277,7 +304,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); @@ -297,7 +324,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.cs index a02ffc3a4..0dfa46c14 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.cs @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); ref Argb32 dp = ref Unsafe.Add(ref destRef, i); @@ -59,6 +59,26 @@ namespace SixLabors.ImageSharp.PixelFormats } } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + /// public override void ToBgr24( Configuration configuration, ReadOnlySpan sourcePixels, @@ -70,7 +90,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); @@ -90,7 +110,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); @@ -110,7 +130,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); ref L8 dp = ref Unsafe.Add(ref destRef, i); @@ -130,7 +150,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); ref L16 dp = ref Unsafe.Add(ref destRef, i); @@ -150,7 +170,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); ref La16 dp = ref Unsafe.Add(ref destRef, i); @@ -170,7 +190,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); ref La32 dp = ref Unsafe.Add(ref destRef, i); @@ -190,7 +210,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); @@ -210,7 +230,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); @@ -230,7 +250,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); @@ -250,7 +270,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.cs index 954ef2d98..19e0548ae 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.cs @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref L16 sp = ref Unsafe.Add(ref sourceRef, i); ref Argb32 dp = ref Unsafe.Add(ref destRef, i); @@ -59,6 +59,26 @@ namespace SixLabors.ImageSharp.PixelFormats } } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + /// public override void ToBgr24( Configuration configuration, ReadOnlySpan sourcePixels, @@ -70,7 +90,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref L16 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); @@ -90,7 +110,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref L16 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); @@ -110,7 +130,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref L16 sp = ref Unsafe.Add(ref sourceRef, i); ref L8 dp = ref Unsafe.Add(ref destRef, i); @@ -130,7 +150,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref L16 sp = ref Unsafe.Add(ref sourceRef, i); ref La16 dp = ref Unsafe.Add(ref destRef, i); @@ -150,7 +170,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref L16 sp = ref Unsafe.Add(ref sourceRef, i); ref La32 dp = ref Unsafe.Add(ref destRef, i); @@ -170,7 +190,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref L16 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); @@ -190,7 +210,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref L16 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); @@ -210,7 +230,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref L16 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); @@ -230,7 +250,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref L16 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); @@ -250,7 +270,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref L16 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.cs index b3d809de5..8997449d3 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.cs @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref L8 sp = ref Unsafe.Add(ref sourceRef, i); ref Argb32 dp = ref Unsafe.Add(ref destRef, i); @@ -59,6 +59,26 @@ namespace SixLabors.ImageSharp.PixelFormats } } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + /// public override void ToBgr24( Configuration configuration, ReadOnlySpan sourcePixels, @@ -70,7 +90,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref L8 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); @@ -90,7 +110,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref L8 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); @@ -110,7 +130,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref L8 sp = ref Unsafe.Add(ref sourceRef, i); ref L16 dp = ref Unsafe.Add(ref destRef, i); @@ -130,7 +150,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref L8 sp = ref Unsafe.Add(ref sourceRef, i); ref La16 dp = ref Unsafe.Add(ref destRef, i); @@ -150,7 +170,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref L8 sp = ref Unsafe.Add(ref sourceRef, i); ref La32 dp = ref Unsafe.Add(ref destRef, i); @@ -170,7 +190,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref L8 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); @@ -190,7 +210,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref L8 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); @@ -210,7 +230,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref L8 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); @@ -230,7 +250,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref L8 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); @@ -250,7 +270,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref L8 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.cs index 14618d026..8166862fe 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.cs @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref La16 sp = ref Unsafe.Add(ref sourceRef, i); ref Argb32 dp = ref Unsafe.Add(ref destRef, i); @@ -59,6 +59,26 @@ namespace SixLabors.ImageSharp.PixelFormats } } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + /// public override void ToBgr24( Configuration configuration, ReadOnlySpan sourcePixels, @@ -70,7 +90,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref La16 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); @@ -90,7 +110,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref La16 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); @@ -110,7 +130,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref La16 sp = ref Unsafe.Add(ref sourceRef, i); ref L8 dp = ref Unsafe.Add(ref destRef, i); @@ -130,7 +150,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref La16 sp = ref Unsafe.Add(ref sourceRef, i); ref L16 dp = ref Unsafe.Add(ref destRef, i); @@ -150,7 +170,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref La16 sp = ref Unsafe.Add(ref sourceRef, i); ref La32 dp = ref Unsafe.Add(ref destRef, i); @@ -170,7 +190,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref La16 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); @@ -190,7 +210,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref La16 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); @@ -210,7 +230,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref La16 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); @@ -230,7 +250,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref La16 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); @@ -250,7 +270,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref La16 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.cs index 9620a1df4..32a0f24e3 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.cs @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref La32 sp = ref Unsafe.Add(ref sourceRef, i); ref Argb32 dp = ref Unsafe.Add(ref destRef, i); @@ -59,6 +59,26 @@ namespace SixLabors.ImageSharp.PixelFormats } } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + /// public override void ToBgr24( Configuration configuration, ReadOnlySpan sourcePixels, @@ -70,7 +90,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref La32 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); @@ -90,7 +110,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref La32 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); @@ -110,7 +130,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref La32 sp = ref Unsafe.Add(ref sourceRef, i); ref L8 dp = ref Unsafe.Add(ref destRef, i); @@ -130,7 +150,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref La32 sp = ref Unsafe.Add(ref sourceRef, i); ref L16 dp = ref Unsafe.Add(ref destRef, i); @@ -150,7 +170,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref La32 sp = ref Unsafe.Add(ref sourceRef, i); ref La16 dp = ref Unsafe.Add(ref destRef, i); @@ -170,7 +190,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref La32 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); @@ -190,7 +210,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref La32 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); @@ -210,7 +230,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref La32 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); @@ -230,7 +250,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref La32 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); @@ -250,7 +270,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref La32 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs index 2fe7f3c20..53a82989d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs @@ -112,6 +112,33 @@ namespace SixLabors.ImageSharp.PixelFormats PixelConverter.FromArgb32.ToRgb24(source, dest); } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgb24.ToAbgr32(source, dest); + } + + /// + public override void FromAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToRgb24(source, dest); + } + /// public override void ToBgra32( Configuration configuration, ReadOnlySpan sourcePixels, @@ -177,7 +204,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); ref L8 dp = ref Unsafe.Add(ref destRef, i); @@ -197,7 +224,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); ref L16 dp = ref Unsafe.Add(ref destRef, i); @@ -217,7 +244,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); ref La16 dp = ref Unsafe.Add(ref destRef, i); @@ -237,7 +264,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); ref La32 dp = ref Unsafe.Add(ref destRef, i); @@ -257,7 +284,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); @@ -277,7 +304,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); @@ -297,7 +324,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.cs index 031008fe1..d1c5ab2e3 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.cs @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); ref Argb32 dp = ref Unsafe.Add(ref destRef, i); @@ -59,6 +59,26 @@ namespace SixLabors.ImageSharp.PixelFormats } } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) + { + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb48(sp); + } + } + /// public override void ToBgr24( Configuration configuration, ReadOnlySpan sourcePixels, @@ -70,7 +90,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); @@ -90,7 +110,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); @@ -110,7 +130,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); ref L8 dp = ref Unsafe.Add(ref destRef, i); @@ -130,7 +150,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); ref L16 dp = ref Unsafe.Add(ref destRef, i); @@ -150,7 +170,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); ref La16 dp = ref Unsafe.Add(ref destRef, i); @@ -170,7 +190,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); ref La32 dp = ref Unsafe.Add(ref destRef, i); @@ -190,7 +210,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); @@ -210,7 +230,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); @@ -230,7 +250,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); @@ -250,7 +270,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.cs index 16f96d2da..2608a74fc 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.cs @@ -66,6 +66,33 @@ namespace SixLabors.ImageSharp.PixelFormats PixelConverter.FromArgb32.ToRgba32(source, dest); } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgba32.ToAbgr32(source, dest); + } + + /// + public override void FromAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToRgba32(source, dest); + } + /// public override void ToBgra32( Configuration configuration, ReadOnlySpan sourcePixels, @@ -158,7 +185,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); ref L8 dp = ref Unsafe.Add(ref destRef, i); @@ -178,7 +205,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); ref L16 dp = ref Unsafe.Add(ref destRef, i); @@ -198,7 +225,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); ref La16 dp = ref Unsafe.Add(ref destRef, i); @@ -218,7 +245,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); ref La32 dp = ref Unsafe.Add(ref destRef, i); @@ -238,7 +265,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); @@ -258,7 +285,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); @@ -278,7 +305,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.cs index 1f1571e91..f6445039a 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.cs @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); ref Argb32 dp = ref Unsafe.Add(ref destRef, i); @@ -59,6 +59,26 @@ namespace SixLabors.ImageSharp.PixelFormats } } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) + { + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba64(sp); + } + } + /// public override void ToBgr24( Configuration configuration, ReadOnlySpan sourcePixels, @@ -70,7 +90,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); @@ -90,7 +110,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); @@ -110,7 +130,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); ref L8 dp = ref Unsafe.Add(ref destRef, i); @@ -130,7 +150,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); ref L16 dp = ref Unsafe.Add(ref destRef, i); @@ -150,7 +170,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); ref La16 dp = ref Unsafe.Add(ref destRef, i); @@ -170,7 +190,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); ref La32 dp = ref Unsafe.Add(ref destRef, i); @@ -190,7 +210,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); @@ -210,7 +230,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); @@ -230,7 +250,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); @@ -250,7 +270,7 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude index 784ecf6fb..9a6ddd7d4 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude @@ -17,6 +17,7 @@ using SixLabors.ImageSharp.PixelFormats.Utils; private static readonly string[] CommonPixelTypes = { "Argb32", + "Abgr32", "Bgr24", "Bgra32", "L8", @@ -34,6 +35,7 @@ using SixLabors.ImageSharp.PixelFormats.Utils; { "Rgba32", "Argb32", + "Abgr32", "Bgra32", "Rgb24", "Bgr24" @@ -43,6 +45,7 @@ using SixLabors.ImageSharp.PixelFormats.Utils; private static readonly string[] Rgba32CompatibleTypes = { "Argb32", + "Abgr32", "Bgra32", "Rgb24", "Bgr24" @@ -101,7 +104,7 @@ using SixLabors.ImageSharp.PixelFormats.Utils; ref <#=fromPixelType#> sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref <#=toPixelType#> destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { ref <#=fromPixelType#> sp = ref Unsafe.Add(ref sourceRef, i); ref <#=toPixelType#> dp = ref Unsafe.Add(ref destRef, i); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs index 12b6e153f..d408f301b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs @@ -93,6 +93,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs index 3b5bdb3d5..00f072268 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs @@ -153,6 +153,15 @@ namespace SixLabors.ImageSharp.PixelFormats this.B = source.B; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromL8(L8 source) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs index d16b7db7a..90c15ae26 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs @@ -186,6 +186,15 @@ namespace SixLabors.ImageSharp.PixelFormats this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void ToRgba32(ref Rgba32 dest) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs index e68726018..11e11bc6c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs @@ -96,6 +96,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs index 3dc6490f1..2d250f71d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs @@ -333,6 +333,16 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = source.A; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs index 4cfa0bf97..bf7452592 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs @@ -94,6 +94,19 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); } + /// + /// Initializes a new instance of the struct. + /// + /// A structure of 4 bytes in ABGR byte order. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba64(Abgr32 source) + { + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); + this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); + } + /// /// Initializes a new instance of the struct. /// @@ -250,6 +263,16 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); + this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); @@ -380,6 +403,20 @@ namespace SixLabors.ImageSharp.PixelFormats return new Argb32(r, g, b, a); } + /// + /// Convert to . + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Abgr32 ToAbgr32() + { + byte r = ColorNumerics.DownScaleFrom16BitTo8Bit(this.R); + byte g = ColorNumerics.DownScaleFrom16BitTo8Bit(this.G); + byte b = ColorNumerics.DownScaleFrom16BitTo8Bit(this.B); + byte a = ColorNumerics.DownScaleFrom16BitTo8Bit(this.A); + return new Abgr32(r, g, b, a); + } + /// /// Convert to . /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs index cd6f53c4e..e582e6166 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs @@ -134,6 +134,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs index 24f6b4d1d..f0117707c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs @@ -111,6 +111,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs index 86a519297..fd58477d9 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs @@ -116,6 +116,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs index cc36c7d13..ea107b35a 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs @@ -82,6 +82,78 @@ namespace SixLabors.ImageSharp.PixelFormats this.ToArgb32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); } + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations. + /// The source of data. + /// The to the destination pixels. + public virtual void FromAbgr32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + + ref Abgr32 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < source.Length; i++) + { + ref Abgr32 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromAbgr32(sp); + } + } + + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations. + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) + { + this.FromAbgr32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); + } + + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + public virtual void ToAbgr32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromScaledVector4(sp.ToScaledVector4()); + } + } + + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToAbgr32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToAbgr32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } + /// /// Converts all pixels in 'source` span of into a span of -s. /// diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt index 21ed328fa..e8cf6f9a5 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt @@ -112,6 +112,9 @@ namespace SixLabors.ImageSharp.PixelFormats GenerateFromMethods("Argb32"); GenerateToDestFormatMethods("Argb32"); + GenerateFromMethods("Abgr32"); + GenerateToDestFormatMethods("Abgr32"); + GenerateFromMethods("Bgr24"); GenerateToDestFormatMethods("Bgr24"); diff --git a/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs b/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs index 7215fa860..907399d5f 100644 --- a/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs +++ b/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs @@ -18,8 +18,13 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils /// internal static class PixelConverter { + /// + /// Optimized converters from . + /// public static class FromRgba32 { + // Input pixels have: X = R, Y = G, Z = B and W = A. + /// /// Converts a representing a collection of /// pixels to a representing @@ -38,6 +43,15 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils public static void ToBgra32(ReadOnlySpan source, Span dest) => SimdUtils.Shuffle4(source, dest, default); + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToAbgr32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + /// /// Converts a representing a collection of /// pixels to a representing @@ -57,8 +71,13 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(3, 0, 1, 2)); } + /// + /// Optimized converters from . + /// public static class FromArgb32 { + // Input pixels have: X = A, Y = R, Z = G and W = B. + /// /// Converts a representing a collection of /// pixels to a representing @@ -77,6 +96,15 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils public static void ToBgra32(ReadOnlySpan source, Span dest) => SimdUtils.Shuffle4(source, dest, default); + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToAbgr32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + /// /// Converts a representing a collection of /// pixels to a representing @@ -96,8 +124,13 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(0, 1, 2, 3)); } + /// + /// Optimized converters from . + /// public static class FromBgra32 { + // Input pixels have: X = B, Y = G, Z = R and W = A. + /// /// Converts a representing a collection of /// pixels to a representing @@ -110,12 +143,21 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils /// /// Converts a representing a collection of /// pixels to a representing - /// a collection of pixels. + /// a collection of pixels. /// [MethodImpl(InliningOptions.ShortMethod)] public static void ToRgba32(ReadOnlySpan source, Span dest) => SimdUtils.Shuffle4(source, dest, default); + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToAbgr32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + /// /// Converts a representing a collection of /// pixels to a representing @@ -135,8 +177,66 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils => SimdUtils.Shuffle4Slice3(source, dest, default); } + /// + /// Optimized converters from . + /// + public static class FromAbgr32 + { + // Input pixels have: X = A, Y = B, Z = G and W = R. + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToArgb32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToRgba32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToBgra32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToRgb24(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(0, 1, 2, 3)); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToBgr24(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(0, 3, 2, 1)); + } + + /// + /// Optimized converters from . + /// public static class FromRgb24 { + // Input pixels have: X = R, Y = G and Z = B. + /// /// Converts a representing a collection of /// pixels to a representing @@ -164,6 +264,15 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils public static void ToBgra32(ReadOnlySpan source, Span dest) => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(3, 0, 1, 2)); + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToAbgr32(ReadOnlySpan source, Span dest) + => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(0, 1, 2, 3)); + /// /// Converts a representing a collection of /// pixels to a representing @@ -174,8 +283,13 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils => SimdUtils.Shuffle3(source, dest, new DefaultShuffle3(0, 1, 2)); } + /// + /// Optimized converters from . + /// public static class FromBgr24 { + // Input pixels have: X = B, Y = G and Z = R. + /// /// Converts a representing a collection of /// pixels to a representing @@ -203,6 +317,15 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils public static void ToBgra32(ReadOnlySpan source, Span dest) => SimdUtils.Pad3Shuffle4(source, dest, default); + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToAbgr32(ReadOnlySpan source, Span dest) + => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(2, 1, 0, 3)); + /// /// Converts a representing a collection of /// pixels to a representing diff --git a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs index af6d32b21..a336cfec3 100644 --- a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs +++ b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs @@ -30,12 +30,13 @@ namespace SixLabors.ImageSharp.Processing Buffer2D intImage = configuration.MemoryAllocator.Allocate2D(source.Width, source.Height); ulong sumX0 = 0; + Buffer2D sourceBuffer = source.Frames.RootFrame.PixelBuffer; using (IMemoryOwner tempRow = configuration.MemoryAllocator.Allocate(source.Width)) { Span tempSpan = tempRow.GetSpan(); - Span sourceRow = source.GetPixelRowSpan(0); - Span destRow = intImage.GetRowSpan(0); + Span sourceRow = sourceBuffer.DangerousGetRowSpan(0); + Span destRow = intImage.DangerousGetRowSpan(0); PixelOperations.Instance.ToL8(configuration, sourceRow, tempSpan); @@ -51,8 +52,8 @@ namespace SixLabors.ImageSharp.Processing // All other rows for (int y = 1; y < endY; y++) { - sourceRow = source.GetPixelRowSpan(y); - destRow = intImage.GetRowSpan(y); + sourceRow = sourceBuffer.DangerousGetRowSpan(y); + destRow = intImage.DangerousGetRowSpan(y); PixelOperations.Instance.ToL8(configuration, sourceRow, tempSpan); diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs index 254ba5a7e..bf6690dcf 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs @@ -52,6 +52,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization // ClusterSize defines the size of cluster to used to check for average. Tweaked to support up to 4k wide pixels and not more. 4096 / 16 is 256 thus the '-1' byte clusterSize = (byte)Math.Truncate((width / 16f) - 1); + Buffer2D sourceBuffer = source.PixelBuffer; + // Using pooled 2d buffer for integer image table and temp memory to hold Rgb24 converted pixel data. using (Buffer2D intImage = this.Configuration.MemoryAllocator.Allocate2D(width, height)) { @@ -61,7 +63,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization ulong sum = 0; for (int y = startY; y < endY; y++) { - Span row = source.GetPixelRowSpan(y); + Span row = sourceBuffer.DangerousGetRowSpan(y); ref TPixel rowRef = ref MemoryMarshal.GetReference(row); ref TPixel color = ref Unsafe.Add(ref rowRef, x); color.ToRgba32(ref rgb); @@ -79,7 +81,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization } } - var operation = new RowOperation(intersect, source, intImage, upper, lower, thresholdLimit, clusterSize, startX, endX, startY); + var operation = new RowOperation(intersect, source.PixelBuffer, intImage, upper, lower, thresholdLimit, clusterSize, startX, endX, startY); ParallelRowIterator.IterateRows( configuration, intersect, @@ -90,7 +92,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization private readonly struct RowOperation : IRowOperation { private readonly Rectangle bounds; - private readonly ImageFrame source; + private readonly Buffer2D source; private readonly Buffer2D intImage; private readonly TPixel upper; private readonly TPixel lower; @@ -103,7 +105,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization [MethodImpl(InliningOptions.ShortMethod)] public RowOperation( Rectangle bounds, - ImageFrame source, + Buffer2D source, Buffer2D intImage, TPixel upper, TPixel lower, @@ -130,7 +132,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization public void Invoke(int y) { Rgba32 rgb = default; - Span pixelRow = this.source.GetPixelRowSpan(y); + Span pixelRow = this.source.DangerousGetRowSpan(y); for (int x = this.startX; x < this.endX; x++) { diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs index 5942c7164..00cb015bc 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Binarization @@ -41,7 +42,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); var operation = new RowOperation( interest.X, - source, + source.PixelBuffer, upper, lower, threshold, @@ -59,7 +60,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization /// private readonly struct RowOperation : IRowOperation { - private readonly ImageFrame source; + private readonly Buffer2D source; private readonly TPixel upper; private readonly TPixel lower; private readonly byte threshold; @@ -70,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization [MethodImpl(InliningOptions.ShortMethod)] public RowOperation( int startX, - ImageFrame source, + Buffer2D source, TPixel upper, TPixel lower, byte threshold, @@ -93,7 +94,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization TPixel upper = this.upper; TPixel lower = this.lower; - Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, span.Length); + Span rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.startX, span.Length); PixelOperations.Instance.ToRgb24(this.configuration, rowSpan, span); switch (this.mode) diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index 241ff9db2..a55ce91e3 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -231,11 +231,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution int kernelSize = this.kernel.Length; // Clear the target buffer for each row run - Span targetBuffer = this.targetValues.GetRowSpan(y); + Span targetBuffer = this.targetValues.DangerousGetRowSpan(y); targetBuffer.Clear(); // Execute the bulk pixel format conversion for the current row - Span sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + Span sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, span); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(span); @@ -295,7 +295,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) { - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span, PixelConversionModifiers.Premultiply); ref Vector4 baseRef = ref MemoryMarshal.GetReference(span); @@ -335,7 +335,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) { - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span, PixelConversionModifiers.Premultiply); @@ -378,8 +378,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Vector4 low = Vector4.Zero; var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - Span targetPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - Span sourceRowSpan = this.sourceValues.GetRowSpan(y).Slice(this.bounds.X); + Span targetPixelSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(this.bounds.X); + Span sourceRowSpan = this.sourceValues.DangerousGetRowSpan(y).Slice(this.bounds.X); ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); for (int x = 0; x < this.bounds.Width; x++) @@ -422,13 +422,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution [MethodImpl(InliningOptions.ShortMethod)] public unsafe void Invoke(int y) { - Span sourceRowSpan = this.sourceValues.GetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + Span sourceRowSpan = this.sourceValues.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); Numerics.Clamp(MemoryMarshal.Cast(sourceRowSpan), 0, float.PositiveInfinity); Numerics.CubeRootOnXYZ(sourceRowSpan); - Span targetPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + Span targetPixelSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan.Slice(0, this.bounds.Width), targetPixelSpan, PixelConversionModifiers.Premultiply); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs index 802d1809f..01288e80f 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { // Get the precalculated source sample row for this kernel row and copy to our buffer. int sampleY = Unsafe.Add(ref sampleRowBase, kY); - sourceRow = this.sourcePixels.GetRowSpan(sampleY).Slice(boundsX, boundsWidth); + sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleY).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); @@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution // Now we need to combine the values and copy the original alpha values // from the source row. - sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); for (int x = 0; x < sourceRow.Length; x++) @@ -123,7 +123,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution target.W = Unsafe.Add(ref MemoryMarshal.GetReference(sourceBuffer), x).W; } - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.FromVector4Destructive(this.configuration, targetYBuffer, targetRowSpan); } @@ -152,7 +152,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { // Get the precalculated source sample row for this kernel row and copy to our buffer. int sampleY = Unsafe.Add(ref sampleRowBase, kY); - Span sourceRow = this.sourcePixels.GetRowSpan(sampleY).Slice(boundsX, boundsWidth); + Span sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleY).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); Numerics.Premultiply(sourceBuffer); @@ -186,7 +186,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Numerics.UnPremultiply(targetYBuffer); - Span targetRow = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.FromVector4Destructive(this.configuration, targetYBuffer, targetRow); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 3f4809c11..fa58422dc 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution targetBuffer.Clear(); // Get the precalculated source sample row for this kernel row and copy to our buffer. - Span sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + Span sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); @@ -187,7 +187,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } // Now we need to copy the original alpha values from the source row. - sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); targetStart = ref MemoryMarshal.GetReference(targetBuffer); @@ -200,7 +200,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution sourceBase = ref Unsafe.Add(ref sourceBase, 1); } - Span targetRow = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); } @@ -219,7 +219,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution targetBuffer.Clear(); // Get the precalculated source sample row for this kernel row and copy to our buffer. - Span sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + Span sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); Numerics.Premultiply(sourceBuffer); @@ -252,7 +252,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Numerics.UnPremultiply(targetBuffer); - Span targetRow = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); } } @@ -327,7 +327,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd)) { // Get the precalculated source sample row for this kernel row and copy to our buffer. - sourceRow = this.sourcePixels.GetRowSpan(sampleRowBase).Slice(boundsX, boundsWidth); + sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleRowBase).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); @@ -349,7 +349,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } // Now we need to copy the original alpha values from the source row. - sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); { ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); @@ -364,7 +364,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } } - Span targetRow = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); } @@ -392,7 +392,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd)) { // Get the precalculated source sample row for this kernel row and copy to our buffer. - sourceRow = this.sourcePixels.GetRowSpan(sampleRowBase).Slice(boundsX, boundsWidth); + sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleRowBase).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); @@ -417,7 +417,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Numerics.UnPremultiply(targetBuffer); - Span targetRow = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index 924a1125b..82b731277 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -118,7 +118,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Span targetBuffer = span.Slice(this.bounds.Width); ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); var state = new ConvolutionState(in this.kernel, this.map); int row = y - this.bounds.Y; @@ -135,7 +135,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { // Get the precalculated source sample row for this kernel row and copy to our buffer. int offsetY = Unsafe.Add(ref sampleRowBase, kY); - sourceRow = this.sourcePixels.GetRowSpan(offsetY).Slice(boundsX, boundsWidth); + sourceRow = this.sourcePixels.DangerousGetRowSpan(offsetY).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); @@ -155,7 +155,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } // Now we need to copy the original alpha values from the source row. - sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); for (int x = 0; x < sourceRow.Length; x++) @@ -174,7 +174,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { // Get the precalculated source sample row for this kernel row and copy to our buffer. int offsetY = Unsafe.Add(ref sampleRowBase, kY); - Span sourceRow = this.sourcePixels.GetRowSpan(offsetY).Slice(boundsX, boundsWidth); + Span sourceRow = this.sourcePixels.DangerousGetRowSpan(offsetY).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); Numerics.Premultiply(sourceBuffer); diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index 27963613e..360b496c3 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -117,8 +117,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.GetRowSpan(y)); - ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.GetRowSpan(y)); + ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.DangerousGetRowSpan(y)); + ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.DangerousGetRowSpan(y)); for (int x = this.minX; x < this.maxX; x++) { diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs index 0fe2d4b2c..5b049e55a 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -5,6 +5,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -105,10 +106,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering int offsetY = bounds.Top; int offsetX = bounds.Left; float scale = quantizer.Options.DitherScale; + Buffer2D sourceBuffer = source.PixelBuffer; for (int y = bounds.Top; y < bounds.Bottom; y++) { - ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(sourceBuffer.DangerousGetRowSpan(y)); ref byte destinationRowRef = ref MemoryMarshal.GetReference(destination.GetWritablePixelRowSpanUnsafe(y - offsetY)); for (int x = bounds.Left; x < bounds.Right; x++) @@ -134,10 +136,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering ThrowDefaultInstance(); } + Buffer2D sourceBuffer = source.PixelBuffer; float scale = processor.DitherScale; for (int y = bounds.Top; y < bounds.Bottom; y++) { - ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(sourceBuffer.DangerousGetRowSpan(y)); for (int x = bounds.Left; x < bounds.Right; x++) { ref TPixel sourcePixel = ref Unsafe.Add(ref sourceRowRef, x); @@ -171,6 +174,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering int offset = this.offset; DenseMatrix matrix = this.matrix; + Buffer2D imageBuffer = image.PixelBuffer; // Loop through and distribute the error amongst neighboring pixels. for (int row = 0, targetY = y; row < matrix.Rows; row++, targetY++) @@ -180,7 +184,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering continue; } - Span rowSpan = image.GetPixelRowSpan(targetY); + Span rowSpan = imageBuffer.DangerousGetRowSpan(targetY); for (int col = 0; col < matrix.Columns; col++) { diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index 2f5a5cf85..da0a852b8 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -118,10 +119,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering int spread = CalculatePaletteSpread(destination.Palette.Length); float scale = quantizer.Options.DitherScale; + Buffer2D sourceBuffer = source.PixelBuffer; for (int y = bounds.Top; y < bounds.Bottom; y++) { - ReadOnlySpan sourceRow = source.GetPixelRowSpan(y).Slice(bounds.X, bounds.Width); + ReadOnlySpan sourceRow = sourceBuffer.DangerousGetRowSpan(y).Slice(bounds.X, bounds.Width); Span destRow = destination.GetWritablePixelRowSpanUnsafe(y - bounds.Y).Slice(0, sourceRow.Length); for (int x = 0; x < sourceRow.Length; x++) @@ -148,10 +150,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering int spread = CalculatePaletteSpread(processor.Palette.Length); float scale = processor.DitherScale; + Buffer2D sourceBuffer = source.PixelBuffer; for (int y = bounds.Top; y < bounds.Bottom; y++) { - Span row = source.GetPixelRowSpan(y).Slice(bounds.X, bounds.Width); + Span row = sourceBuffer.DangerousGetRowSpan(y).Slice(bounds.X, bounds.Width); for (int x = 0; x < row.Length; x++) { diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs index 9b3dbcaa3..86009b9d9 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs @@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing "Cannot draw image because the source image does not overlap the target image."); } - var operation = new RowOperation(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity); + var operation = new RowOperation(source.PixelBuffer, targetImage.Frames.RootFrame.PixelBuffer, blender, configuration, minX, width, locationY, targetX, this.Opacity); ParallelRowIterator.IterateRows( configuration, workingRect, @@ -111,8 +111,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing /// private readonly struct RowOperation : IRowOperation { - private readonly ImageFrame sourceFrame; - private readonly Image targetImage; + private readonly Buffer2D source; + private readonly Buffer2D target; private readonly PixelBlender blender; private readonly Configuration configuration; private readonly int minX; @@ -123,8 +123,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing [MethodImpl(InliningOptions.ShortMethod)] public RowOperation( - ImageFrame sourceFrame, - Image targetImage, + Buffer2D source, + Buffer2D target, PixelBlender blender, Configuration configuration, int minX, @@ -133,8 +133,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing int targetX, float opacity) { - this.sourceFrame = sourceFrame; - this.targetImage = targetImage; + this.source = source; + this.target = target; this.blender = blender; this.configuration = configuration; this.minX = minX; @@ -148,8 +148,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Span background = this.sourceFrame.GetPixelRowSpan(y).Slice(this.minX, this.width); - Span foreground = this.targetImage.GetPixelRowSpan(y - this.locationY).Slice(this.targetX, this.width); + Span background = this.source.DangerousGetRowSpan(y).Slice(this.minX, this.width); + Span foreground = this.target.DangerousGetRowSpan(y - this.locationY).Slice(this.targetX, this.width); this.blender.Blend(this.configuration, background, background, foreground, this.opacity); } } diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs index 42216417e..eb18c10f4 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects source.CopyTo(targetPixels); - var operation = new RowIntervalOperation(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels); + var operation = new RowIntervalOperation(this.SourceRectangle, targetPixels, source.PixelBuffer, this.Configuration, brushSize >> 1, this.definition.Levels); ParallelRowIterator.IterateRowIntervals( this.Configuration, this.SourceRectangle, @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects { private readonly Rectangle bounds; private readonly Buffer2D targetPixels; - private readonly ImageFrame source; + private readonly Buffer2D source; private readonly Configuration configuration; private readonly int radius; private readonly int levels; @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects public RowIntervalOperation( Rectangle bounds, Buffer2D targetPixels, - ImageFrame source, + Buffer2D source, Configuration configuration, int radius, int levels) @@ -120,7 +120,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects for (int y = rows.Min; y < rows.Max; y++) { - Span sourceRowPixelSpan = this.source.GetPixelRowSpan(y); + Span sourceRowPixelSpan = this.source.DangerousGetRowSpan(y); Span sourceRowAreaPixelSpan = sourceRowPixelSpan.Slice(this.bounds.X, this.bounds.Width); PixelOperations.Instance.ToVector4(this.configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span); @@ -139,7 +139,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects int offsetY = y + fyr; offsetY = Numerics.Clamp(offsetY, 0, maxY); - Span sourceOffsetRow = this.source.GetPixelRowSpan(offsetY); + Span sourceOffsetRow = this.source.DangerousGetRowSpan(offsetY); for (int fx = 0; fx <= this.radius; fx++) { @@ -176,7 +176,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects } } - Span targetRowAreaPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + Span targetRowAreaPixelSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); PixelOperations.Instance.FromVector4Destructive(this.configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan); } diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs index 6b63c885a..bc1445d89 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects protected override void OnFrameApply(ImageFrame source) { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - var operation = new RowOperation(interest.X, source, this.Configuration, this.modifiers, this.rowDelegate); + var operation = new RowOperation(interest.X, source.PixelBuffer, this.Configuration, this.modifiers, this.rowDelegate); ParallelRowIterator.IterateRows( this.Configuration, @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects private readonly struct RowOperation : IRowOperation { private readonly int startX; - private readonly ImageFrame source; + private readonly Buffer2D source; private readonly Configuration configuration; private readonly PixelConversionModifiers modifiers; private readonly TDelegate rowProcessor; @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects [MethodImpl(InliningOptions.ShortMethod)] public RowOperation( int startX, - ImageFrame source, + Buffer2D source, Configuration configuration, PixelConversionModifiers modifiers, in TDelegate rowProcessor) @@ -88,7 +88,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) { - Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, span.Length); + Span rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.startX, span.Length); PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, this.modifiers); // Run the user defined pixel shader to the current row of pixels diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs index 0f307f8f1..f6aecabe1 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Effects @@ -48,7 +49,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects Parallel.ForEach( range, this.Configuration.GetParallelOptions(), - new RowOperation(interest, size, source).Invoke); + new RowOperation(interest, size, source.PixelBuffer).Invoke); } private readonly struct RowOperation @@ -60,13 +61,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects private readonly int maxYIndex; private readonly int size; private readonly int radius; - private readonly ImageFrame source; + private readonly Buffer2D source; [MethodImpl(InliningOptions.ShortMethod)] public RowOperation( Rectangle bounds, int size, - ImageFrame source) + Buffer2D source) { this.minX = bounds.X; this.maxX = bounds.Right; @@ -81,7 +82,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Span rowSpan = this.source.GetPixelRowSpan(Math.Min(y + this.radius, this.maxYIndex)); + Span rowSpan = this.source.DangerousGetRowSpan(Math.Min(y + this.radius, this.maxYIndex)); for (int x = this.minX; x < this.maxX; x += this.size) { diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs index d0c8ff40d..e3323dd6c 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters protected override void OnFrameApply(ImageFrame source) { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - var operation = new RowOperation(interest.X, source, this.definition.Matrix, this.Configuration); + var operation = new RowOperation(interest.X, source.PixelBuffer, this.definition.Matrix, this.Configuration); ParallelRowIterator.IterateRows( this.Configuration, @@ -50,14 +50,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters private readonly struct RowOperation : IRowOperation { private readonly int startX; - private readonly ImageFrame source; + private readonly Buffer2D source; private readonly ColorMatrix matrix; private readonly Configuration configuration; [MethodImpl(InliningOptions.ShortMethod)] public RowOperation( int startX, - ImageFrame source, + Buffer2D source, ColorMatrix matrix, Configuration configuration) { @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) { - Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, span.Length); + Span rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.startX, span.Length); PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, PixelConversionModifiers.Scale); ColorNumerics.Transform(span, ref Unsafe.AsRef(this.matrix)); diff --git a/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs index 9bb364476..a230fc761 100644 --- a/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs @@ -6,6 +6,7 @@ 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.Filters @@ -25,20 +26,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - var operation = new OpaqueRowOperation(this.Configuration, source, interest); + var operation = new OpaqueRowOperation(this.Configuration, source.PixelBuffer, interest); ParallelRowIterator.IterateRows(this.Configuration, interest, in operation); } private readonly struct OpaqueRowOperation : IRowOperation { private readonly Configuration configuration; - private readonly ImageFrame target; + private readonly Buffer2D target; private readonly Rectangle bounds; [MethodImpl(InliningOptions.ShortMethod)] public OpaqueRowOperation( Configuration configuration, - ImageFrame target, + Buffer2D target, Rectangle bounds) { this.configuration = configuration; @@ -50,7 +51,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) { - Span targetRowSpan = this.target.GetPixelRowSpan(y).Slice(this.bounds.X); + Span targetRowSpan = this.target.DangerousGetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span, PixelConversionModifiers.Scale); ref Vector4 baseRef = ref MemoryMarshal.GetReference(span); diff --git a/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs index b0896636e..b0c81dbd7 100644 --- a/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs @@ -45,27 +45,14 @@ namespace SixLabors.ImageSharp.Processing.Processors /// void IImageProcessor.Execute() { - try - { - this.BeforeImageApply(); - - foreach (ImageFrame sourceFrame in this.Source.Frames) - { - this.Apply(sourceFrame); - } + this.BeforeImageApply(); - this.AfterImageApply(); - } -#if DEBUG - catch (Exception) + foreach (ImageFrame sourceFrame in this.Source.Frames) { - throw; -#else - catch (Exception ex) - { - throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); -#endif + this.Apply(sourceFrame); } + + this.AfterImageApply(); } /// @@ -74,22 +61,9 @@ namespace SixLabors.ImageSharp.Processing.Processors /// the source image. public void Apply(ImageFrame source) { - try - { - this.BeforeFrameApply(source); - this.OnFrameApply(source); - this.AfterFrameApply(source); - } -#if DEBUG - catch (Exception) - { - throw; -#else - catch (Exception ex) - { - throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); -#endif - } + this.BeforeFrameApply(source); + this.OnFrameApply(source); + this.AfterFrameApply(source); } /// diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs index 883f85be3..0afdef905 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs @@ -80,37 +80,37 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization yStart += tileHeight; } - var operation = new RowIntervalOperation(cdfData, tileYStartPositions, tileWidth, tileHeight, tileCount, halfTileWidth, luminanceLevels, source); + var operation = new RowIntervalOperation(cdfData, tileYStartPositions, tileWidth, tileHeight, tileCount, halfTileWidth, luminanceLevels, source.PixelBuffer); ParallelRowIterator.IterateRowIntervals( this.Configuration, new Rectangle(0, 0, sourceWidth, tileYStartPositions.Count), in operation); // Fix left column - ProcessBorderColumn(source, cdfData, 0, sourceHeight, this.Tiles, tileHeight, xStart: 0, xEnd: halfTileWidth, luminanceLevels); + ProcessBorderColumn(source.PixelBuffer, cdfData, 0, sourceHeight, this.Tiles, tileHeight, xStart: 0, xEnd: halfTileWidth, luminanceLevels); // Fix right column int rightBorderStartX = ((this.Tiles - 1) * tileWidth) + halfTileWidth; - ProcessBorderColumn(source, cdfData, this.Tiles - 1, sourceHeight, this.Tiles, tileHeight, xStart: rightBorderStartX, xEnd: sourceWidth, luminanceLevels); + ProcessBorderColumn(source.PixelBuffer, cdfData, this.Tiles - 1, sourceHeight, this.Tiles, tileHeight, xStart: rightBorderStartX, xEnd: sourceWidth, luminanceLevels); // Fix top row - ProcessBorderRow(source, cdfData, 0, sourceWidth, this.Tiles, tileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); + ProcessBorderRow(source.PixelBuffer, cdfData, 0, sourceWidth, this.Tiles, tileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); // Fix bottom row int bottomBorderStartY = ((this.Tiles - 1) * tileHeight) + halfTileHeight; - ProcessBorderRow(source, cdfData, this.Tiles - 1, sourceWidth, this.Tiles, tileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); + ProcessBorderRow(source.PixelBuffer, cdfData, this.Tiles - 1, sourceWidth, this.Tiles, tileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); // Left top corner - ProcessCornerTile(source, cdfData, 0, 0, xStart: 0, xEnd: halfTileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); + ProcessCornerTile(source.PixelBuffer, cdfData, 0, 0, xStart: 0, xEnd: halfTileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); // Left bottom corner - ProcessCornerTile(source, cdfData, 0, this.Tiles - 1, xStart: 0, xEnd: halfTileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); + ProcessCornerTile(source.PixelBuffer, cdfData, 0, this.Tiles - 1, xStart: 0, xEnd: halfTileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); // Right top corner - ProcessCornerTile(source, cdfData, this.Tiles - 1, 0, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); + ProcessCornerTile(source.PixelBuffer, cdfData, this.Tiles - 1, 0, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); // Right bottom corner - ProcessCornerTile(source, cdfData, this.Tiles - 1, this.Tiles - 1, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); + ProcessCornerTile(source.PixelBuffer, cdfData, this.Tiles - 1, this.Tiles - 1, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); } } @@ -130,7 +130,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// or 65536 for 16-bit grayscale images. /// private static void ProcessCornerTile( - ImageFrame source, + Buffer2D source, CdfTileData cdfData, int cdfX, int cdfY, @@ -142,7 +142,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization { for (int dy = yStart; dy < yEnd; dy++) { - Span rowSpan = source.GetPixelRowSpan(dy); + Span rowSpan = source.DangerousGetRowSpan(dy); for (int dx = xStart; dx < xEnd; dx++) { ref TPixel pixel = ref rowSpan[dx]; @@ -168,7 +168,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// or 65536 for 16-bit grayscale images. /// private static void ProcessBorderColumn( - ImageFrame source, + Buffer2D source, CdfTileData cdfData, int cdfX, int sourceHeight, @@ -188,7 +188,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization int tileY = 0; for (int dy = y; dy < yLimit; dy++) { - Span rowSpan = source.GetPixelRowSpan(dy); + Span rowSpan = source.DangerousGetRowSpan(dy); for (int dx = xStart; dx < xEnd; dx++) { ref TPixel pixel = ref rowSpan[dx]; @@ -220,7 +220,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// or 65536 for 16-bit grayscale images. /// private static void ProcessBorderRow( - ImageFrame source, + Buffer2D source, CdfTileData cdfData, int cdfY, int sourceWidth, @@ -238,7 +238,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization { for (int dy = yStart; dy < yEnd; dy++) { - Span rowSpan = source.GetPixelRowSpan(dy); + Span rowSpan = source.DangerousGetRowSpan(dy); int tileX = 0; int xLimit = Math.Min(x + tileWidth, sourceWidth - 1); for (int dx = x; dx < xLimit; dx++) @@ -373,7 +373,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization private readonly int tileCount; private readonly int halfTileWidth; private readonly int luminanceLevels; - private readonly ImageFrame source; + private readonly Buffer2D source; private readonly int sourceWidth; private readonly int sourceHeight; @@ -386,7 +386,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization int tileCount, int halfTileWidth, int luminanceLevels, - ImageFrame source) + Buffer2D source) { this.cdfData = cdfData; this.tileYStartPositions = tileYStartPositions; @@ -419,7 +419,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization int xEnd = Math.Min(x + this.tileWidth, this.sourceWidth); for (int dy = y; dy < yEnd; dy++) { - Span rowSpan = this.source.GetPixelRowSpan(dy); + Span rowSpan = this.source.DangerousGetRowSpan(dy); int tileX = 0; for (int dx = x; dx < xEnd; dx++) { @@ -516,7 +516,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization this.tileWidth, this.tileHeight, this.luminanceLevels, - source); + source.PixelBuffer); ParallelRowIterator.IterateRowIntervals( this.configuration, @@ -525,7 +525,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization } [MethodImpl(InliningOptions.ShortMethod)] - public Span GetCdfLutSpan(int tileX, int tileY) => this.cdfLutBuffer2D.GetRowSpan(tileY).Slice(tileX * this.luminanceLevels, this.luminanceLevels); + public Span GetCdfLutSpan(int tileX, int tileY) => this.cdfLutBuffer2D.DangerousGetRowSpan(tileY).Slice(tileX * this.luminanceLevels, this.luminanceLevels); /// /// Remaps the grey value with the cdf. @@ -560,7 +560,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization private readonly int tileWidth; private readonly int tileHeight; private readonly int luminanceLevels; - private readonly ImageFrame source; + private readonly Buffer2D source; private readonly int sourceWidth; private readonly int sourceHeight; @@ -574,7 +574,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization int tileWidth, int tileHeight, int luminanceLevels, - ImageFrame source) + Buffer2D source) { this.processor = processor; this.allocator = allocator; @@ -599,7 +599,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization int cdfY = this.tileYStartPositions[index].CdfY; int y = this.tileYStartPositions[index].Y; int endY = Math.Min(y + this.tileHeight, this.sourceHeight); - Span cdfMinSpan = this.cdfMinBuffer2D.GetRowSpan(cdfY); + Span cdfMinSpan = this.cdfMinBuffer2D.DangerousGetRowSpan(cdfY); cdfMinSpan.Clear(); using IMemoryOwner histogramBuffer = this.allocator.Allocate(this.luminanceLevels); @@ -609,13 +609,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization for (int x = 0; x < this.sourceWidth; x += this.tileWidth) { histogram.Clear(); - Span cdfLutSpan = this.cdfLutBuffer2D.GetRowSpan(index).Slice(cdfX * this.luminanceLevels, this.luminanceLevels); + Span cdfLutSpan = this.cdfLutBuffer2D.DangerousGetRowSpan(index).Slice(cdfX * this.luminanceLevels, this.luminanceLevels); ref int cdfBase = ref MemoryMarshal.GetReference(cdfLutSpan); int xlimit = Math.Min(x + this.tileWidth, this.sourceWidth); for (int dy = y; dy < endY; dy++) { - Span rowSpan = this.source.GetPixelRowSpan(dy); + Span rowSpan = this.source.DangerousGetRowSpan(dy); for (int dx = x; dx < xlimit; dx++) { int luminance = GetLuminance(rowSpan[dx], this.luminanceLevels); diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs index f17e0d1e4..5e1e016ee 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs @@ -189,7 +189,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization y = source.Height - diff - 1; } - // Special cases for the left and the right border where GetPixelRowSpan can not be used. + // Special cases for the left and the right border where DangerousGetRowSpan can not be used. if (x < 0) { rowPixels.Clear(); @@ -224,7 +224,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization return; } - this.CopyPixelRowFast(source, rowPixels, x, y, tileWidth, configuration); + this.CopyPixelRowFast(source.PixelBuffer, rowPixels, x, y, tileWidth, configuration); } /// @@ -238,13 +238,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// The configuration. [MethodImpl(InliningOptions.ShortMethod)] private void CopyPixelRowFast( - ImageFrame source, + Buffer2D source, Span rowPixels, int x, int y, int tileWidth, Configuration configuration) - => PixelOperations.Instance.ToVector4(configuration, source.GetPixelRowSpan(y).Slice(start: x, length: tileWidth), rowPixels); + => PixelOperations.Instance.ToVector4(configuration, source.DangerousGetRowSpan(y).Slice(start: x, length: tileWidth), rowPixels); /// /// Adds a column of grey values to the histogram. @@ -356,7 +356,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization { if (this.useFastPath) { - this.processor.CopyPixelRowFast(this.source, pixelRow, x - this.swInfos.HalfTileWidth, dy, this.swInfos.TileWidth, this.configuration); + this.processor.CopyPixelRowFast(this.source.PixelBuffer, pixelRow, x - this.swInfos.HalfTileWidth, dy, this.swInfos.TileWidth, this.configuration); } else { @@ -390,7 +390,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization // Remove top most row from the histogram, mirroring rows which exceeds the borders. if (this.useFastPath) { - this.processor.CopyPixelRowFast(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y - this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); + this.processor.CopyPixelRowFast(this.source.PixelBuffer, pixelRow, x - this.swInfos.HalfTileWidth, y - this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); } else { @@ -402,7 +402,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization // Add new bottom row to the histogram, mirroring rows which exceeds the borders. if (this.useFastPath) { - this.processor.CopyPixelRowFast(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y + this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); + this.processor.CopyPixelRowFast(this.source.PixelBuffer, pixelRow, x - this.swInfos.HalfTileWidth, y + this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); } else { diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs index 70d3e075d..67970821c 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization using IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean); // Build the histogram of the grayscale levels. - var grayscaleOperation = new GrayscaleLevelsRowOperation(interest, histogramBuffer, source, this.LuminanceLevels); + var grayscaleOperation = new GrayscaleLevelsRowOperation(interest, histogramBuffer, source.PixelBuffer, this.LuminanceLevels); ParallelRowIterator.IterateRows( this.Configuration, interest, @@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; // Apply the cdf to each pixel of the image - var cdfOperation = new CdfApplicationRowOperation(interest, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin); + var cdfOperation = new CdfApplicationRowOperation(interest, cdfBuffer, source.PixelBuffer, this.LuminanceLevels, numberOfPixelsMinusCdfMin); ParallelRowIterator.IterateRows( this.Configuration, interest, @@ -90,14 +90,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization { private readonly Rectangle bounds; private readonly IMemoryOwner histogramBuffer; - private readonly ImageFrame source; + private readonly Buffer2D source; private readonly int luminanceLevels; [MethodImpl(InliningOptions.ShortMethod)] public GrayscaleLevelsRowOperation( Rectangle bounds, IMemoryOwner histogramBuffer, - ImageFrame source, + Buffer2D source, int luminanceLevels) { this.bounds = bounds; @@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization public void Invoke(int y) { ref int histogramBase = ref MemoryMarshal.GetReference(this.histogramBuffer.GetSpan()); - Span pixelRow = this.source.GetPixelRowSpan(y); + Span pixelRow = this.source.DangerousGetRowSpan(y); int levels = this.luminanceLevels; for (int x = 0; x < this.bounds.Width; x++) @@ -136,7 +136,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization { private readonly Rectangle bounds; private readonly IMemoryOwner cdfBuffer; - private readonly ImageFrame source; + private readonly Buffer2D source; private readonly int luminanceLevels; private readonly float numberOfPixelsMinusCdfMin; @@ -144,7 +144,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization public CdfApplicationRowOperation( Rectangle bounds, IMemoryOwner cdfBuffer, - ImageFrame source, + Buffer2D source, int luminanceLevels, float numberOfPixelsMinusCdfMin) { @@ -165,7 +165,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization public void Invoke(int y) { ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan()); - Span pixelRow = this.source.GetPixelRowSpan(y); + Span pixelRow = this.source.DangerousGetRowSpan(y); int levels = this.luminanceLevels; float noOfPixelsMinusCdfMin = this.numberOfPixelsMinusCdfMin; diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs index 76dcc2194..636738ca7 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays PixelBlender blender = PixelOperations.Instance.GetPixelBlender(graphicsOptions); - var operation = new RowOperation(configuration, interest, blender, amount, colors, source); + var operation = new RowOperation(configuration, interest, blender, amount, colors, source.PixelBuffer); ParallelRowIterator.IterateRows( configuration, interest, @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays private readonly PixelBlender blender; private readonly IMemoryOwner amount; private readonly IMemoryOwner colors; - private readonly ImageFrame source; + private readonly Buffer2D source; [MethodImpl(InliningOptions.ShortMethod)] public RowOperation( @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays PixelBlender blender, IMemoryOwner amount, IMemoryOwner colors, - ImageFrame source) + Buffer2D source) { this.configuration = configuration; this.bounds = bounds; @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays public void Invoke(int y) { Span destination = - this.source.GetPixelRowSpan(y) + this.source.DangerousGetRowSpan(y) .Slice(this.bounds.X, this.bounds.Width); // Switch color & destination in the 2nd and 3rd places because we are diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs index 78cf7f3c6..331609089 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays using IMemoryOwner rowColors = allocator.Allocate(interest.Width); rowColors.GetSpan().Fill(glowColor); - var operation = new RowOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source); + var operation = new RowOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source.PixelBuffer); ParallelRowIterator.IterateRows( configuration, interest, @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays private readonly float maxDistance; private readonly float blendPercent; private readonly IMemoryOwner colors; - private readonly ImageFrame source; + private readonly Buffer2D source; [MethodImpl(InliningOptions.ShortMethod)] public RowOperation( @@ -82,7 +82,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays Vector2 center, float maxDistance, float blendPercent, - ImageFrame source) + Buffer2D source) { this.configuration = configuration; this.bounds = bounds; @@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays span[i] = Numerics.Clamp(this.blendPercent * (1 - (.95F * (distance / this.maxDistance))), 0, 1F); } - Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + Span destination = this.source.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); this.blender.Blend( this.configuration, diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs index c853377ad..800613eca 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays using IMemoryOwner rowColors = allocator.Allocate(interest.Width); rowColors.GetSpan().Fill(vignetteColor); - var operation = new RowOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source); + var operation = new RowOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source.PixelBuffer); ParallelRowIterator.IterateRows( configuration, interest, @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays private readonly float maxDistance; private readonly float blendPercent; private readonly IMemoryOwner colors; - private readonly ImageFrame source; + private readonly Buffer2D source; [MethodImpl(InliningOptions.ShortMethod)] public RowOperation( @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays Vector2 center, float maxDistance, float blendPercent, - ImageFrame source) + Buffer2D source) { this.configuration = configuration; this.bounds = bounds; @@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays span[i] = Numerics.Clamp(this.blendPercent * (.9F * (distance / this.maxDistance)), 0, 1F); } - Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + Span destination = this.source.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); this.blender.Blend( this.configuration, diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs index 311a8aa2e..e28de54c2 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs @@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization // Loop through each row for (int y = bounds.Top; y < bounds.Bottom; y++) { - Span row = source.GetRowSpan(y).Slice(bounds.Left, bounds.Width); + Span row = source.DangerousGetRowSpan(y).Slice(bounds.Left, bounds.Width); PixelOperations.Instance.ToRgba32(this.Configuration, row, bufferSpan); for (int x = 0; x < bufferSpan.Length; x++) diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs index 93bca6075..574b27475 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs @@ -44,11 +44,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization ReadOnlySpan paletteSpan = quantized.Palette.Span; int offsetY = interest.Top; int offsetX = interest.Left; + Buffer2D sourceBuffer = source.PixelBuffer; for (int y = interest.Y; y < interest.Height; y++) { - Span row = source.GetPixelRowSpan(y); - ReadOnlySpan quantizedRow = quantized.GetPixelRowSpan(y - offsetY); + Span row = sourceBuffer.DangerousGetRowSpan(y); + ReadOnlySpan quantizedRow = quantized.DangerousGetRowSpan(y - offsetY); for (int x = interest.Left; x < interest.Right; x++) { diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs index 6c963bfab..5aa79d732 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs @@ -122,6 +122,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization where TPixel : unmanaged, IPixel { IDither dither = quantizer.Options.Dither; + Buffer2D sourceBuffer = source.PixelBuffer; if (dither is null) { @@ -130,7 +131,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization for (int y = bounds.Y; y < bounds.Height; y++) { - Span sourceRow = source.GetPixelRowSpan(y); + Span sourceRow = sourceBuffer.DangerousGetRowSpan(y); Span destinationRow = destination.GetWritablePixelRowSpanUnsafe(y - offsetY); for (int x = bounds.Left; x < bounds.Right; x++) diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs index 4218810d7..cc5329952 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs @@ -384,7 +384,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization for (int y = bounds.Top; y < bounds.Bottom; y++) { - Span row = source.GetRowSpan(y).Slice(bounds.Left, bounds.Width); + Span row = source.DangerousGetRowSpan(y).Slice(bounds.Left, bounds.Width); PixelOperations.Instance.ToRgba32(this.Configuration, row, bufferSpan); for (int x = 0; x < bufferSpan.Length; x++) diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index df9c1146b..dfc6ba1c1 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(this.Configuration).MultiplyMinimumPixelsPerTask(4); - var operation = new RowOperation(bounds, source, destination); + var operation = new RowOperation(bounds, source.PixelBuffer, destination.PixelBuffer); ParallelRowIterator.IterateRows( bounds, @@ -65,8 +65,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly struct RowOperation : IRowOperation { private readonly Rectangle bounds; - private readonly ImageFrame source; - private readonly ImageFrame destination; + private readonly Buffer2D source; + private readonly Buffer2D destination; /// /// Initializes a new instance of the struct. @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The source for the current instance. /// The destination for the current instance. [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation(Rectangle bounds, ImageFrame source, ImageFrame destination) + public RowOperation(Rectangle bounds, Buffer2D source, Buffer2D destination) { this.bounds = bounds; this.source = source; @@ -86,8 +86,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Span sourceRow = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left); - Span targetRow = this.destination.GetPixelRowSpan(y - this.bounds.Top); + Span sourceRow = this.source.DangerousGetRowSpan(y).Slice(this.bounds.Left); + Span targetRow = this.destination.DangerousGetRowSpan(y - this.bounds.Top); sourceRow.Slice(0, this.bounds.Width).CopyTo(targetRow); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs index 5f04918e0..640527fe7 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (sampler is NearestNeighborResampler) { - var nnOperation = new NNAffineOperation(source, destination, matrix); + var nnOperation = new NNAffineOperation(source.PixelBuffer, destination.PixelBuffer, matrix); ParallelRowIterator.IterateRows( configuration, destination.Bounds(), @@ -84,8 +84,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms var operation = new AffineOperation( configuration, - source, - destination, + source.PixelBuffer, + destination.PixelBuffer, in sampler, matrix); @@ -97,15 +97,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly struct NNAffineOperation : IRowOperation { - private readonly ImageFrame source; - private readonly ImageFrame destination; + private readonly Buffer2D source; + private readonly Buffer2D destination; private readonly Rectangle bounds; private readonly Matrix3x2 matrix; [MethodImpl(InliningOptions.ShortMethod)] public NNAffineOperation( - ImageFrame source, - ImageFrame destination, + Buffer2D source, + Buffer2D destination, Matrix3x2 matrix) { this.source = source; @@ -117,8 +117,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Buffer2D sourceBuffer = this.source.PixelBuffer; - Span destRow = this.destination.GetPixelRowSpan(y); + Span destRow = this.destination.DangerousGetRowSpan(y); for (int x = 0; x < destRow.Length; x++) { @@ -128,7 +127,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (this.bounds.Contains(px, py)) { - destRow[x] = sourceBuffer.GetElementUnsafe(px, py); + destRow[x] = this.source.GetElementUnsafe(px, py); } } } @@ -138,8 +137,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms where TResampler : struct, IResampler { private readonly Configuration configuration; - private readonly ImageFrame source; - private readonly ImageFrame destination; + private readonly Buffer2D source; + private readonly Buffer2D destination; private readonly TResampler sampler; private readonly Matrix3x2 matrix; private readonly float yRadius; @@ -148,8 +147,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(InliningOptions.ShortMethod)] public AffineOperation( Configuration configuration, - ImageFrame source, - ImageFrame destination, + Buffer2D source, + Buffer2D destination, in TResampler sampler, Matrix3x2 matrix) { @@ -186,11 +185,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int maxY = this.source.Height - 1; int maxX = this.source.Width - 1; - Buffer2D sourceBuffer = this.source.PixelBuffer; - for (int y = rows.Min; y < rows.Max; y++) { - Span rowSpan = this.destination.GetPixelRowSpan(y); + Span rowSpan = this.destination.DangerousGetRowSpan(y); PixelOperations.Instance.ToVector4( this.configuration, rowSpan, @@ -222,7 +219,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { float xWeight = sampler.GetValue(xK - pX); - Vector4 current = sourceBuffer.GetElementUnsafe(xK, yK).ToScaledVector4(); + Vector4 current = this.source.GetElementUnsafe(xK, yK).ToScaledVector4(); Numerics.Premultiply(ref current); sum += current * xWeight * yWeight; } @@ -251,11 +248,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int maxY = this.source.Height - 1; int maxX = this.source.Width - 1; - Buffer2D sourceBuffer = this.source.PixelBuffer; - for (int y = rows.Min; y < rows.Max; y++) { - Span rowSpan = this.destination.GetPixelRowSpan(y); + Span rowSpan = this.destination.DangerousGetRowSpan(y); PixelOperations.Instance.ToVector4( this.configuration, rowSpan, @@ -287,7 +282,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { float xWeight = sampler.GetValue(xK - pX); - Vector4 current = sourceBuffer.GetElementUnsafe(xK, yK).ToScaledVector4(); + Vector4 current = this.source.GetElementUnsafe(xK, yK).ToScaledVector4(); Numerics.Premultiply(ref current); sum += current * xWeight * yWeight; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs index 840881b14..8d15a79e5 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs @@ -5,6 +5,7 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -38,7 +39,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { // No default needed as we have already set the pixels. case FlipMode.Vertical: - this.FlipX(source, this.Configuration); + this.FlipX(source.PixelBuffer, this.Configuration); break; case FlipMode.Horizontal: this.FlipY(source, this.Configuration); @@ -51,7 +52,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// The source image to apply the process to. /// The configuration. - private void FlipX(ImageFrame source, Configuration configuration) + private void FlipX(Buffer2D source, Configuration configuration) { int height = source.Height; using IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(source.Width); @@ -60,8 +61,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms for (int yTop = 0; yTop < height / 2; yTop++) { int yBottom = height - yTop - 1; - Span topRow = source.GetPixelRowSpan(yBottom); - Span bottomRow = source.GetPixelRowSpan(yTop); + Span topRow = source.DangerousGetRowSpan(yBottom); + Span bottomRow = source.DangerousGetRowSpan(yTop); topRow.CopyTo(temp); bottomRow.CopyTo(topRow); temp.CopyTo(bottomRow); @@ -75,7 +76,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void FlipY(ImageFrame source, Configuration configuration) { - var operation = new RowOperation(source); + var operation = new RowOperation(source.PixelBuffer); ParallelRowIterator.IterateRows( configuration, source.Bounds(), @@ -84,13 +85,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly struct RowOperation : IRowOperation { - private readonly ImageFrame source; + private readonly Buffer2D source; [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation(ImageFrame source) => this.source = source; + public RowOperation(Buffer2D source) => this.source = source; [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) => this.source.GetPixelRowSpan(y).Reverse(); + public void Invoke(int y) => this.source.DangerousGetRowSpan(y).Reverse(); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs index 9396a018d..cf6567629 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (sampler is NearestNeighborResampler) { - var nnOperation = new NNProjectiveOperation(source, destination, matrix); + var nnOperation = new NNProjectiveOperation(source.PixelBuffer, destination.PixelBuffer, matrix); ParallelRowIterator.IterateRows( configuration, destination.Bounds(), @@ -83,8 +83,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms var operation = new ProjectiveOperation( configuration, - source, - destination, + source.PixelBuffer, + destination.PixelBuffer, in sampler, matrix); @@ -96,15 +96,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly struct NNProjectiveOperation : IRowOperation { - private readonly ImageFrame source; - private readonly ImageFrame destination; + private readonly Buffer2D source; + private readonly Buffer2D destination; private readonly Rectangle bounds; private readonly Matrix4x4 matrix; [MethodImpl(InliningOptions.ShortMethod)] public NNProjectiveOperation( - ImageFrame source, - ImageFrame destination, + Buffer2D source, + Buffer2D destination, Matrix4x4 matrix) { this.source = source; @@ -116,8 +116,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Buffer2D sourceBuffer = this.source.PixelBuffer; - Span destRow = this.destination.GetPixelRowSpan(y); + Span destRow = this.destination.DangerousGetRowSpan(y); for (int x = 0; x < destRow.Length; x++) { @@ -127,7 +126,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (this.bounds.Contains(px, py)) { - destRow[x] = sourceBuffer.GetElementUnsafe(px, py); + destRow[x] = this.source.GetElementUnsafe(px, py); } } } @@ -137,8 +136,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms where TResampler : struct, IResampler { private readonly Configuration configuration; - private readonly ImageFrame source; - private readonly ImageFrame destination; + private readonly Buffer2D source; + private readonly Buffer2D destination; private readonly TResampler sampler; private readonly Matrix4x4 matrix; private readonly float yRadius; @@ -147,8 +146,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(InliningOptions.ShortMethod)] public ProjectiveOperation( Configuration configuration, - ImageFrame source, - ImageFrame destination, + Buffer2D source, + Buffer2D destination, in TResampler sampler, Matrix4x4 matrix) { @@ -185,11 +184,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int maxY = this.source.Height - 1; int maxX = this.source.Width - 1; - Buffer2D sourceBuffer = this.source.PixelBuffer; - for (int y = rows.Min; y < rows.Max; y++) { - Span rowSpan = this.destination.GetPixelRowSpan(y); + Span rowSpan = this.destination.DangerousGetRowSpan(y); PixelOperations.Instance.ToVector4( this.configuration, rowSpan, @@ -221,7 +218,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { float xWeight = sampler.GetValue(xK - pX); - Vector4 current = sourceBuffer.GetElementUnsafe(xK, yK).ToScaledVector4(); + Vector4 current = this.source.GetElementUnsafe(xK, yK).ToScaledVector4(); Numerics.Premultiply(ref current); sum += current * xWeight * yWeight; } @@ -250,11 +247,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int maxY = this.source.Height - 1; int maxX = this.source.Width - 1; - Buffer2D sourceBuffer = this.source.PixelBuffer; - for (int y = rows.Min; y < rows.Max; y++) { - Span rowSpan = this.destination.GetPixelRowSpan(y); + Span rowSpan = this.destination.DangerousGetRowSpan(y); PixelOperations.Instance.ToVector4( this.configuration, rowSpan, @@ -286,7 +281,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { float xWeight = sampler.GetValue(xK - pX); - Vector4 current = sourceBuffer.GetElementUnsafe(xK, yK).ToScaledVector4(); + Vector4 current = this.source.GetElementUnsafe(xK, yK).ToScaledVector4(); Numerics.Premultiply(ref current); sum += current * xWeight * yWeight; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs index cce6d6860..234f89a71 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs @@ -131,7 +131,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate180(ImageFrame source, ImageFrame destination, Configuration configuration) { - var operation = new Rotate180RowOperation(source.Width, source.Height, source, destination); + var operation = new Rotate180RowOperation(source.Width, source.Height, source.PixelBuffer, destination.PixelBuffer); ParallelRowIterator.IterateRows( configuration, source.Bounds(), @@ -146,7 +146,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate270(ImageFrame source, ImageFrame destination, Configuration configuration) { - var operation = new Rotate270RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination); + var operation = new Rotate270RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source.PixelBuffer, destination.PixelBuffer); ParallelRowIterator.IterateRowIntervals( configuration, source.Bounds(), @@ -161,7 +161,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate90(ImageFrame source, ImageFrame destination, Configuration configuration) { - var operation = new Rotate90RowOperation(destination.Bounds(), source.Width, source.Height, source, destination); + var operation = new Rotate90RowOperation(destination.Bounds(), source.Width, source.Height, source.PixelBuffer, destination.PixelBuffer); ParallelRowIterator.IterateRows( configuration, source.Bounds(), @@ -172,15 +172,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { private readonly int width; private readonly int height; - private readonly ImageFrame source; - private readonly ImageFrame destination; + private readonly Buffer2D source; + private readonly Buffer2D destination; [MethodImpl(InliningOptions.ShortMethod)] public Rotate180RowOperation( int width, int height, - ImageFrame source, - ImageFrame destination) + Buffer2D source, + Buffer2D destination) { this.width = width; this.height = height; @@ -191,8 +191,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Span sourceRow = this.source.GetPixelRowSpan(y); - Span targetRow = this.destination.GetPixelRowSpan(this.height - y - 1); + Span sourceRow = this.source.DangerousGetRowSpan(y); + Span targetRow = this.destination.DangerousGetRowSpan(this.height - y - 1); for (int x = 0; x < this.width; x++) { @@ -206,16 +206,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly Rectangle bounds; private readonly int width; private readonly int height; - private readonly ImageFrame source; - private readonly ImageFrame destination; + private readonly Buffer2D source; + private readonly Buffer2D destination; [MethodImpl(InliningOptions.ShortMethod)] public Rotate270RowIntervalOperation( Rectangle bounds, int width, int height, - ImageFrame source, - ImageFrame destination) + Buffer2D source, + Buffer2D destination) { this.bounds = bounds; this.width = width; @@ -229,7 +229,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { for (int y = rows.Min; y < rows.Max; y++) { - Span sourceRow = this.source.GetPixelRowSpan(y); + Span sourceRow = this.source.DangerousGetRowSpan(y); for (int x = 0; x < this.width; x++) { int newX = this.height - y - 1; @@ -250,16 +250,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly Rectangle bounds; private readonly int width; private readonly int height; - private readonly ImageFrame source; - private readonly ImageFrame destination; + private readonly Buffer2D source; + private readonly Buffer2D destination; [MethodImpl(InliningOptions.ShortMethod)] public Rotate90RowOperation( Rectangle bounds, int width, int height, - ImageFrame source, - ImageFrame destination) + Buffer2D source, + Buffer2D destination) { this.bounds = bounds; this.width = width; @@ -271,7 +271,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Span sourceRow = this.source.GetPixelRowSpan(y); + Span sourceRow = this.source.DangerousGetRowSpan(y); int newX = this.height - y - 1; for (int x = 0; x < this.width; x++) { diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index 9cc468060..c9dda5f6b 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.sourceLength = sourceLength; this.DestinationLength = destinationLength; this.MaxDiameter = (radius * 2) + 1; - this.data = memoryAllocator.Allocate2D(this.MaxDiameter, bufferHeight, AllocationOptions.Clean); + this.data = memoryAllocator.Allocate2D(this.MaxDiameter, bufferHeight, preferContiguosImageBuffers: true, AllocationOptions.Clean); this.pinHandle = this.data.DangerousGetSingleMemory().Pin(); this.kernels = new ResizeKernel[destinationLength]; this.tempValues = new double[this.MaxDiameter]; @@ -252,7 +252,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int length = right - left + 1; this.ValidateSizesForCreateKernel(length, dataRowIndex, left, right); - Span rowSpan = this.data.GetRowSpan(dataRowIndex); + Span rowSpan = this.data.DangerousGetRowSpan(dataRowIndex); ref float rowReference = ref MemoryMarshal.GetReference(rowSpan); float* rowPtr = (float*)Unsafe.AsPointer(ref rowReference); diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 1b93d01a1..b486e4225 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -155,8 +155,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms interest, widthFactor, heightFactor, - source, - destination); + source.PixelBuffer, + destination.PixelBuffer); ParallelRowIterator.IterateRows( configuration, @@ -223,8 +223,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly Rectangle interest; private readonly float widthFactor; private readonly float heightFactor; - private readonly ImageFrame source; - private readonly ImageFrame destination; + private readonly Buffer2D source; + private readonly Buffer2D destination; [MethodImpl(InliningOptions.ShortMethod)] public NNRowOperation( @@ -233,8 +233,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Rectangle interest, float widthFactor, float heightFactor, - ImageFrame source, - ImageFrame destination) + Buffer2D source, + Buffer2D destination) { this.sourceBounds = sourceBounds; this.destinationBounds = destinationBounds; @@ -256,8 +256,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int destRight = this.interest.Right; // Y coordinates of source points - Span sourceRow = this.source.GetPixelRowSpan((int)(((y - destOriginY) * this.heightFactor) + sourceY)); - Span targetRow = this.destination.GetPixelRowSpan(y); + Span sourceRow = this.source.DangerousGetRowSpan((int)(((y - destOriginY) * this.heightFactor) + sourceY)); + Span targetRow = this.destination.DangerousGetRowSpan(y); for (int x = destLeft; x < destRight; x++) { diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index 7ade3aeee..4e3a08c39 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(InliningOptions.ShortMethod)] public Span GetColumnSpan(int x, int startY) - => this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min); + => this.transposedFirstPassBuffer.DangerousGetRowSpan(x).Slice(startY - this.currentWindow.Min); public void Initialize() => this.CalculateFirstPassValues(this.currentWindow); @@ -140,7 +140,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Unsafe.Add(ref tempRowBase, x) = kernel.ConvolveCore(ref firstPassColumnBase); } - Span targetRowSpan = destination.GetRowSpan(y); + Span targetRowSpan = destination.DangerousGetRowSpan(y); PixelOperations.Instance.FromVector4Destructive(this.configuration, tempColSpan, targetRowSpan, this.conversionModifiers); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs index aab17d292..191b6fc3a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -27,9 +28,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { Point p = default; Point newPoint; + Buffer2D sourceBuffer = source.PixelBuffer; for (p.Y = 0; p.Y < source.Height; p.Y++) { - Span rowSpan = source.GetPixelRowSpan(p.Y); + Span rowSpan = sourceBuffer.DangerousGetRowSpan(p.Y); for (p.X = 0; p.X < source.Width; p.X++) { newPoint = this.swizzler.Transform(p); diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs index 490beec6f..0f791ed8e 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs @@ -17,25 +17,27 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true)] public void Scalar() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromCmykBasic(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromCmykScalar(8).ConvertToRgbInplace(values); } [Benchmark] public void SimdVector8() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromCmykVector8(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromCmykVector(8).ConvertToRgbInplace(values); } +#if SUPPORTS_RUNTIME_INTRINSICS [Benchmark] - public void SimdVectorAvx2() + public void SimdVectorAvx() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromCmykAvx2(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromCmykAvx(8).ConvertToRgbInplace(values); } +#endif } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs index 7b62e1434..2fdb47077 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using BenchmarkDotNet.Attributes; @@ -17,17 +17,19 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true)] public void Scalar() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromGrayscaleBasic(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromGrayscaleScalar(8).ConvertToRgbInplace(values); } +#if SUPPORTS_RUNTIME_INTRINSICS [Benchmark] - public void SimdVectorAvx2() + public void SimdVectorAvx() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromGrayscaleAvx2(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromGrayscaleAvx(8).ConvertToRgbInplace(values); } +#endif } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs index af03b31e5..987a93194 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using BenchmarkDotNet.Attributes; @@ -17,25 +17,27 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true)] public void Scalar() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromRgbBasic(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromRgbScalar(8).ConvertToRgbInplace(values); } [Benchmark] public void SimdVector8() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromRgbVector8(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromRgbVector(8).ConvertToRgbInplace(values); } +#if SUPPORTS_RUNTIME_INTRINSICS [Benchmark] - public void SimdVectorAvx2() + public void SimdVectorAvx() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromRgbAvx2(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromRgbAvx(8).ConvertToRgbInplace(values); } +#endif } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs index 18daa364c..8d6846033 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs @@ -17,33 +17,27 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark] public void Scalar() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYCbCrBasic(8).ConvertToRgbInplace(values); - } - - [Benchmark(Baseline = true)] - public void SimdVector() - { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); - - new JpegColorConverter.FromYCbCrVector4(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromYCbCrScalar(8).ConvertToRgbInplace(values); } [Benchmark] public void SimdVector8() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYCbCrVector8(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromYCbCrVector(8).ConvertToRgbInplace(values); } +#if SUPPORTS_RUNTIME_INTRINSICS [Benchmark] - public void SimdVectorAvx2() + public void SimdVectorAvx() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYCbCrAvx2(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromYCbCrAvx(8).ConvertToRgbInplace(values); } +#endif } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs index 08e5e50d1..7e9edc918 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using BenchmarkDotNet.Attributes; @@ -17,25 +17,27 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true)] public void Scalar() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYccKBasic(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromYccKScalar(8).ConvertToRgbInplace(values); } [Benchmark] public void SimdVector8() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYccKVector8(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromYccKVector(8).ConvertToRgbInplace(values); } +#if SUPPORTS_RUNTIME_INTRINSICS [Benchmark] public void SimdVectorAvx2() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYccKAvx2(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromYccKAvx(8).ConvertToRgbInplace(values); } +#endif } } diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 8f0b4a86f..9a9274199 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -43,9 +43,9 @@ - + - + diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs index b7ec95c4b..b998863e8 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs @@ -3,6 +3,7 @@ using System; using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave { @@ -37,9 +38,9 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public int[] ParallelismValues { get; } = { Environment.ProcessorCount, - Environment.ProcessorCount / 2, - Environment.ProcessorCount / 4, - 1 + // Environment.ProcessorCount / 2, + // Environment.ProcessorCount / 4, + // 1 }; [Benchmark] @@ -48,7 +49,10 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave [Benchmark(Baseline = true)] [ArgumentsSource(nameof(ParallelismValues))] - public void ImageSharp(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.ImageSharpResize, maxDegreeOfParallelism); + public void ImageSharp(int maxDegreeOfParallelism) + { + this.ForEachImage(this.runner.ImageSharpResize, maxDegreeOfParallelism); + } [Benchmark] [ArgumentsSource(nameof(ParallelismValues))] diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs index 7c57d691a..eda054968 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using ImageMagick; using PhotoSauce.MagicScaler; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests; using SkiaSharp; @@ -43,13 +44,15 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public double TotalProcessedMegapixels { get; private set; } + public Size LastProcessedImageSize { get; private set; } + private string outputDirectory; public int ImageCount { get; set; } = int.MaxValue; public int MaxDegreeOfParallelism { get; set; } = -1; - public JpegKind Filter { get; set; } + public JpegKind Filter { get; set; } = JpegKind.Any; public int ThumbnailSize { get; set; } = 150; @@ -121,8 +124,9 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave new ParallelOptions { MaxDegreeOfParallelism = this.MaxDegreeOfParallelism }, action); - private void IncreaseTotalMegapixels(int width, int height) + private void LogImageProcessed(int width, int height) { + this.LastProcessedImageSize = new Size(width, height); double pixels = width * (double)height; this.TotalProcessedMegapixels += pixels / 1_000_000.0; } @@ -152,7 +156,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public void SystemDrawingResize(string input) { using var image = SystemDrawingImage.FromFile(input, true); - this.IncreaseTotalMegapixels(image.Width, image.Height); + this.LogImageProcessed(image.Width, image.Height); (int Width, int Height) scaled = this.ScaledSize(image.Width, image.Height, this.ThumbnailSize); var resized = new Bitmap(scaled.Width, scaled.Height); @@ -178,7 +182,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave // Resize it to fit a 150x150 square using var image = ImageSharpImage.Load(input); - this.IncreaseTotalMegapixels(image.Width, image.Height); + this.LogImageProcessed(image.Width, image.Height); image.Mutate(i => i.Resize(new ResizeOptions { @@ -196,7 +200,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public void MagickResize(string input) { using var image = new MagickImage(input); - this.IncreaseTotalMegapixels(image.Width, image.Height); + this.LogImageProcessed(image.Width, image.Height); // Resize it to fit a 150x150 square image.Resize(this.ThumbnailSize, this.ThumbnailSize); @@ -231,7 +235,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public void SkiaCanvasResize(string input) { using var original = SKBitmap.Decode(input); - this.IncreaseTotalMegapixels(original.Width, original.Height); + this.LogImageProcessed(original.Width, original.Height); (int Width, int Height) scaled = this.ScaledSize(original.Width, original.Height, this.ThumbnailSize); using var surface = SKSurface.Create(new SKImageInfo(scaled.Width, scaled.Height, original.ColorType, original.AlphaType)); using var paint = new SKPaint() { FilterQuality = SKFilterQuality.High }; @@ -249,7 +253,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public void SkiaBitmapResize(string input) { using var original = SKBitmap.Decode(input); - this.IncreaseTotalMegapixels(original.Width, original.Height); + this.LogImageProcessed(original.Width, original.Height); (int Width, int Height) scaled = this.ScaledSize(original.Width, original.Height, this.ThumbnailSize); using var resized = original.Resize(new SKImageInfo(scaled.Width, scaled.Height), SKFilterQuality.High); if (resized == null) @@ -268,7 +272,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave using var codec = SKCodec.Create(input); SKImageInfo info = codec.Info; - this.IncreaseTotalMegapixels(info.Width, info.Height); + this.LogImageProcessed(info.Width, info.Height); (int Width, int Height) scaled = this.ScaledSize(info.Width, info.Height, this.ThumbnailSize); SKSizeI supportedScale = codec.GetScaledDimensions((float)scaled.Width / info.Width); diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs index cf7807837..76d077c76 100644 --- a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs +++ b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Benchmarks Buffer2D pixels = image.GetRootFramePixelBuffer(); for (int y = 0; y < image.Height; y++) { - Span span = pixels.GetRowSpan(y); + Span span = pixels.DangerousGetRowSpan(y); this.BulkVectorConvert(span, span, span, amounts.GetSpan()); } @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.Benchmarks Buffer2D pixels = image.GetRootFramePixelBuffer(); for (int y = 0; y < image.Height; y++) { - Span span = pixels.GetRowSpan(y); + Span span = pixels.DangerousGetRowSpan(y); this.BulkPixelConvert(span, span, span, amounts.GetSpan()); } diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj index 1a470fa31..6ff5a4cc7 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj @@ -48,6 +48,7 @@ + diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index 7fe91fe5f..c7484daa0 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -3,29 +3,133 @@ using System; using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Runtime.CompilerServices; using System.Text; +using System.Threading; +using CommandLine; +using CommandLine.Text; using SixLabors.ImageSharp.Benchmarks.LoadResizeSave; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Memory.Internals; namespace SixLabors.ImageSharp.Tests.ProfilingSandbox { // See ImageSharp.Benchmarks/LoadResizeSave/README.md internal class LoadResizeSaveParallelMemoryStress { - private readonly LoadResizeSaveStressRunner benchmarks; - private LoadResizeSaveParallelMemoryStress() { - this.benchmarks = new LoadResizeSaveStressRunner() + this.Benchmarks = new LoadResizeSaveStressRunner() { - // MaxDegreeOfParallelism = 10, Filter = JpegKind.Baseline, }; - this.benchmarks.Init(); + this.Benchmarks.Init(); + } + + private int gcFrequency; + + private int leakFrequency; + + private int imageCounter; + + public LoadResizeSaveStressRunner Benchmarks { get; } + + public static void Run(string[] args) + { + Console.WriteLine($"Running: {typeof(LoadResizeSaveParallelMemoryStress).Assembly.Location}"); + Console.WriteLine($"64 bit: {Environment.Is64BitProcess}"); + CommandLineOptions options = args.Length > 0 ? CommandLineOptions.Parse(args) : null; + + var lrs = new LoadResizeSaveParallelMemoryStress(); + if (options != null) + { + lrs.Benchmarks.MaxDegreeOfParallelism = options.MaxDegreeOfParallelism; + } + + Console.WriteLine($"\nEnvironment.ProcessorCount={Environment.ProcessorCount}"); + Stopwatch timer; + + if (options == null || !options.ImageSharp) + { + RunBenchmarkSwitcher(lrs, out timer); + } + else + { + Console.WriteLine("Running ImageSharp with options:"); + Console.WriteLine(options.ToString()); + + if (!options.KeepDefaultAllocator) + { + Configuration.Default.MemoryAllocator = options.CreateMemoryAllocator(); + } + + lrs.leakFrequency = options.LeakFrequency; + lrs.gcFrequency = options.GcFrequency; + + + timer = Stopwatch.StartNew(); + try + { + for (int i = 0; i < options.RepeatCount; i++) + { + lrs.ImageSharpBenchmarkParallel(); + } + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + + timer.Stop(); + + if (options.ReleaseRetainedResourcesAtEnd) + { + Configuration.Default.MemoryAllocator.ReleaseRetainedResources(); + } + + int finalGcCount = -Math.Min(0, options.GcFrequency); + + if (finalGcCount > 0) + { + Console.WriteLine($"TotalOutstandingHandles: {UnmanagedMemoryHandle.TotalOutstandingHandles}"); + Console.WriteLine($"GC x {finalGcCount}, with 3 seconds wait."); + for (int i = 0; i < finalGcCount; i++) + { + Thread.Sleep(3000); + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + } + } + + var stats = new Stats(timer, lrs.Benchmarks.TotalProcessedMegapixels); + Console.WriteLine($"Total Megapixels: {stats.TotalMegapixels}, TotalOomRetries: {UnmanagedMemoryHandle.TotalOomRetries}, TotalOutstandingHandles: {UnmanagedMemoryHandle.TotalOutstandingHandles}, Total Gen2 GC count: {GC.CollectionCount(2)}"); + Console.WriteLine(stats.GetMarkdown()); + if (options?.FileOutput != null) + { + PrintFileOutput(options.FileOutput, stats); + } + + if (options != null && options.PauseAtEnd) + { + Console.WriteLine("Press ENTER"); + Console.ReadLine(); + } } - private double TotalProcessedMegapixels => this.benchmarks.TotalProcessedMegapixels; + private static void PrintFileOutput(string fileOutput, Stats stats) + { + string[] ss = fileOutput.Split(';'); + string fileName = ss[0]; + string content = ss[1] + .Replace("TotalSeconds", stats.TotalSeconds.ToString(CultureInfo.InvariantCulture)) + .Replace("EOL", Environment.NewLine); + File.AppendAllText(fileName, content); + } - public static void Run() + private static void RunBenchmarkSwitcher(LoadResizeSaveParallelMemoryStress lrs, out Stopwatch timer) { Console.WriteLine(@"Choose a library for image resizing stress test: @@ -33,57 +137,46 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox 2. ImageSharp 3. MagicScaler 4. SkiaSharp -5. NetVips -6. ImageMagick +5. SkiaSharp - Decode to target size +6. NetVips +7. ImageMagick "); ConsoleKey key = Console.ReadKey().Key; if (key < ConsoleKey.D1 || key > ConsoleKey.D6) { Console.WriteLine("Unrecognized command."); - return; + Environment.Exit(-1); } - try - { - var lrs = new LoadResizeSaveParallelMemoryStress(); - - Console.WriteLine($"\nEnvironment.ProcessorCount={Environment.ProcessorCount}"); - Console.WriteLine($"Running with MaxDegreeOfParallelism={lrs.benchmarks.MaxDegreeOfParallelism} ..."); - var timer = Stopwatch.StartNew(); - - switch (key) - { - case ConsoleKey.D1: - lrs.SystemDrawingBenchmarkParallel(); - break; - case ConsoleKey.D2: - Console.WriteLine($"Images: {lrs.benchmarks.Images.Length}"); - lrs.ImageSharpBenchmarkParallel(); - break; - case ConsoleKey.D3: - lrs.MagicScalerBenchmarkParallel(); - break; - case ConsoleKey.D4: - lrs.SkiaBitmapBenchmarkParallel(); - break; - case ConsoleKey.D5: - lrs.NetVipsBenchmarkParallel(); - break; - case ConsoleKey.D6: - lrs.MagickBenchmarkParallel(); - break; - } + timer = Stopwatch.StartNew(); - timer.Stop(); - var stats = new Stats(timer, lrs.TotalProcessedMegapixels); - Console.WriteLine("Done. TotalProcessedMegapixels: " + lrs.TotalProcessedMegapixels); - Console.WriteLine(stats.GetMarkdown()); - } - catch (Exception ex) + switch (key) { - Console.WriteLine(ex.ToString()); + case ConsoleKey.D1: + lrs.SystemDrawingBenchmarkParallel(); + break; + case ConsoleKey.D2: + lrs.ImageSharpBenchmarkParallel(); + break; + case ConsoleKey.D3: + lrs.MagicScalerBenchmarkParallel(); + break; + case ConsoleKey.D4: + lrs.SkiaBitmapBenchmarkParallel(); + break; + case ConsoleKey.D5: + lrs.SkiaBitmapDecodeToTargetSizeBenchmarkParallel(); + break; + case ConsoleKey.D6: + lrs.NetVipsBenchmarkParallel(); + break; + case ConsoleKey.D7: + lrs.MagickBenchmarkParallel(); + break; } + + timer.Stop(); } private struct Stats @@ -126,18 +219,124 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox } } - private void ForEachImage(Action action) => this.benchmarks.ForEachImageParallel(action); + private class CommandLineOptions + { + [Option('i', "imagesharp", Required = false, Default = false, HelpText = "Test ImageSharp without benchmark switching")] + public bool ImageSharp { get; set; } + + [Option('d', "default-allocator", Required = false, Default = false, HelpText = "Keep default MemoryAllocator and ignore all settings")] + public bool KeepDefaultAllocator { get; set; } + + [Option('m', "max-contiguous", Required = false, Default = 4, HelpText = "Maximum size of contiguous pool buffers in MegaBytes")] + public int MaxContiguousPoolBufferMegaBytes { get; set; } = 4; + + [Option('s', "poolsize", Required = false, Default = 4096, HelpText = "The size of the pool in MegaBytes")] + public int MaxPoolSizeMegaBytes { get; set; } = 4096; + + [Option('u', "max-nonpool", Required = false, Default = 32, HelpText = "Maximum size of non-pooled contiguous blocks in MegaBytes")] + public int MaxCapacityOfNonPoolBuffersMegaBytes { get; set; } = 32; + + [Option('p', "parallelism", Required = false, Default = -1, HelpText = "Level of parallelism")] + public int MaxDegreeOfParallelism { get; set; } = -1; + + [Option('r', "repeat-count", Required = false, Default = 1, HelpText = "Times to run the whole benchmark")] + public int RepeatCount { get; set; } = 1; + + [Option('g', "gc-frequency", Required = false, Default = 0, HelpText = "Positive number: call GC every 'g'-th resize. Negative number: call GC '-g' times in the end.")] + public int GcFrequency { get; set; } + + [Option('e', "release-at-end", Required = false, Default = false, HelpText = "Specify to run ReleaseRetainedResources() after execution")] + public bool ReleaseRetainedResourcesAtEnd { get; set; } + + [Option('w', "pause", Required = false, Default = false, HelpText = "Specify to pause and wait for user input after the execution")] + public bool PauseAtEnd { get; set; } + + [Option('f', "file", Required = false, Default = null, HelpText = "Specify to print the execution time to a file. Format: ';' see the code for formatstr semantics.")] + public string FileOutput { get; set; } + + [Option('t', "trim-period", Required = false, Default = null, HelpText = "Trim period for the pool in seconds")] + public int? TrimTimeSeconds { get; set; } + + [Option('l', "leak-frequency", Required = false, Default = 0, HelpText = "Inject leaking memory allocations after every 'l'-th resize to stress test finalizer behavior.")] + public int LeakFrequency { get; set; } + + public static CommandLineOptions Parse(string[] args) + { + CommandLineOptions result = null; + ParserResult parserResult = Parser.Default.ParseArguments(args).WithParsed(o => + { + result = o; + }); + + if (result == null) + { + Console.WriteLine(HelpText.RenderUsageText(parserResult)); + } + + return result; + } + + public override string ToString() => + $"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_d({this.KeepDefaultAllocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.GcFrequency})_e({this.ReleaseRetainedResourcesAtEnd})_l({this.LeakFrequency})"; + + public MemoryAllocator CreateMemoryAllocator() + { + if (this.TrimTimeSeconds.HasValue) + { + return new UniformUnmanagedMemoryPoolMemoryAllocator( + 1024 * 1024, + (int)B(this.MaxContiguousPoolBufferMegaBytes), + B(this.MaxPoolSizeMegaBytes), + (int)B(this.MaxCapacityOfNonPoolBuffersMegaBytes), + new UniformUnmanagedMemoryPool.TrimSettings + { + TrimPeriodMilliseconds = this.TrimTimeSeconds.Value * 1000 + }); + } + else + { + return new UniformUnmanagedMemoryPoolMemoryAllocator( + 1024 * 1024, + (int)B(this.MaxContiguousPoolBufferMegaBytes), + B(this.MaxPoolSizeMegaBytes), + (int)B(this.MaxCapacityOfNonPoolBuffersMegaBytes)); + } + } + + private static long B(double megaBytes) => (long)(megaBytes * 1024 * 1024); + } + + private void ForEachImage(Action action) => this.Benchmarks.ForEachImageParallel(action); - private void SystemDrawingBenchmarkParallel() => this.ForEachImage(this.benchmarks.SystemDrawingResize); + private void SystemDrawingBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SystemDrawingResize); - private void ImageSharpBenchmarkParallel() => this.ForEachImage(this.benchmarks.ImageSharpResize); + private void ImageSharpBenchmarkParallel() => + this.ForEachImage(f => + { + int cnt = Interlocked.Increment(ref this.imageCounter); + this.Benchmarks.ImageSharpResize(f); + if (this.leakFrequency > 0 && cnt % this.leakFrequency == 0) + { + _ = Configuration.Default.MemoryAllocator.Allocate(1 << 16); + Size size = this.Benchmarks.LastProcessedImageSize; + _ = new Image(size.Width, size.Height); + } + + if (this.gcFrequency > 0 && cnt % this.gcFrequency == 0) + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + } + }); - private void MagickBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagickResize); + private void MagickBenchmarkParallel() => this.ForEachImage(this.Benchmarks.MagickResize); - private void MagicScalerBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagicScalerResize); + private void MagicScalerBenchmarkParallel() => this.ForEachImage(this.Benchmarks.MagicScalerResize); - private void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaBitmapResize); + private void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SkiaBitmapResize); + private void SkiaBitmapDecodeToTargetSizeBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SkiaBitmapDecodeToTargetSize); - private void NetVipsBenchmarkParallel() => this.ForEachImage(this.benchmarks.NetVipsResize); + private void NetVipsBenchmarkParallel() => this.ForEachImage(this.Benchmarks.NetVipsResize); } } diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index e6e82b981..bc0b40bad 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -2,6 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Reflection; +using System.Threading; +using SixLabors.ImageSharp.Memory.Internals; using SixLabors.ImageSharp.Tests.Formats.Jpg; using SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; using SixLabors.ImageSharp.Tests.ProfilingBenchmarks; @@ -31,7 +34,15 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox /// public static void Main(string[] args) { - LoadResizeSaveParallelMemoryStress.Run(); + try + { + LoadResizeSaveParallelMemoryStress.Run(args); + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + // RunJpegEncoderProfilingTests(); // RunJpegColorProfilingTests(); // RunDecodeJpegProfilingTests(); @@ -41,16 +52,24 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox // Console.ReadLine(); } - private static void RunJpegEncoderProfilingTests() + private static Version GetNetCoreVersion() { - var benchmarks = new JpegProfilingBenchmarks(new ConsoleOutput()); - benchmarks.EncodeJpeg_SingleMidSize(); + Assembly assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly; + Console.WriteLine(assembly.Location); + string[] assemblyPath = assembly.Location.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); + int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App"); + if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2) + { + return Version.Parse(assemblyPath[netCoreAppIndex + 1]); + } + + return null; } - private static void RunJpegColorProfilingTests() + private static void RunJpegEncoderProfilingTests() { - new JpegColorConverterTests(new ConsoleOutput()).BenchmarkYCbCr(false); - new JpegColorConverterTests(new ConsoleOutput()).BenchmarkYCbCr(true); + var benchmarks = new JpegProfilingBenchmarks(new ConsoleOutput()); + benchmarks.EncodeJpeg_SingleMidSize(); } private static void RunResizeProfilingTest() diff --git a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs index 6031227bd..2da0cbf83 100644 --- a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -62,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests.Advanced using Image image0 = provider.GetImage(); var targetBuffer = new TPixel[image0.Width * image0.Height]; - Assert.True(image0.TryGetSinglePixelSpan(out Span sourceBuffer)); + Assert.True(image0.DangerousTryGetSinglePixelMemory(out Memory sourceBuffer)); sourceBuffer.CopyTo(targetBuffer); @@ -106,7 +107,7 @@ namespace SixLabors.ImageSharp.Tests.Advanced [WithBasicTestPatternImages(1, 1, PixelTypes.Rgba32)] [WithBasicTestPatternImages(131, 127, PixelTypes.Rgba32)] [WithBasicTestPatternImages(333, 555, PixelTypes.Bgr24)] - public void GetPixelRowMemory_PixelDataIsCorrect(TestImageProvider provider) + public void DangerousGetPixelRowMemory_PixelDataIsCorrect(TestImageProvider provider) where TPixel : unmanaged, IPixel { provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); @@ -116,13 +117,18 @@ namespace SixLabors.ImageSharp.Tests.Advanced for (int y = 0; y < image.Height; y++) { // Act: - Memory rowMemory = image.GetPixelRowMemory(y); - Span span = rowMemory.Span; + Memory rowMemoryFromImage = image.DangerousGetPixelRowMemory(y); + Memory rowMemoryFromFrame = image.Frames.RootFrame.DangerousGetPixelRowMemory(y); + Span spanFromImage = rowMemoryFromImage.Span; + Span spanFromFrame = rowMemoryFromFrame.Span; + + Assert.Equal(spanFromFrame.Length, spanFromImage.Length); + Assert.True(Unsafe.AreSame(ref spanFromFrame[0], ref spanFromImage[0])); // Assert: for (int x = 0; x < image.Width; x++) { - Assert.Equal(provider.GetExpectedBasicTestPatternPixelAt(x, y), span[x]); + Assert.Equal(provider.GetExpectedBasicTestPatternPixelAt(x, y), spanFromImage[x]); } } } @@ -134,30 +140,13 @@ namespace SixLabors.ImageSharp.Tests.Advanced { using Image image = provider.GetImage(); - Memory memory3 = image.GetPixelRowMemory(3); - Memory memory10 = image.GetPixelRowMemory(10); + Memory memory3 = image.DangerousGetPixelRowMemory(3); + Memory memory10 = image.DangerousGetPixelRowMemory(10); image.Mutate(c => c.Resize(8, 8)); Assert.ThrowsAny(() => _ = memory3.Span); Assert.ThrowsAny(() => _ = memory10.Span); } - - [Theory] - [WithBlankImages(1, 1, PixelTypes.Rgba32)] - [WithBlankImages(100, 111, PixelTypes.Rgba32)] - [WithBlankImages(400, 600, PixelTypes.Rgba32)] - public void GetPixelRowSpan_ShouldReferenceSpanOfMemory(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); - - using Image image = provider.GetImage(); - - Memory memory = image.GetPixelRowMemory(image.Height - 1); - Span span = image.GetPixelRowSpan(image.Height - 1); - - Assert.True(span == memory.Span); - } } } diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs index 38b94f486..ec03847df 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs @@ -63,6 +63,19 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(source, data); } + [Fact] + public void Abgr32() + { + var source = new Abgr32(1, 22, 33, 231); + + // Act: + Color color = source; + + // Assert: + Abgr32 data = color.ToPixel(); + Assert.Equal(source, data); + } + [Fact] public void Rgb24() { diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs index 3003265ca..1997cc6b1 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Numerics; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -63,6 +64,19 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(source, data); } + [Fact] + public void Abgr32() + { + var source = new Abgr32(1, 22, 33, 231); + + // Act: + var color = new Color(source); + + // Assert: + Abgr32 data = color; + Assert.Equal(source, data); + } + [Fact] public void Rgb24() { @@ -90,17 +104,30 @@ namespace SixLabors.ImageSharp.Tests } [Fact] - public void GenericPixel() + public void Vector4Constructor() { - AssertGenericPixel(new RgbaVector(float.Epsilon, 2 * float.Epsilon, float.MaxValue, float.MinValue)); - AssertGenericPixel(new Rgba64(1, 2, ushort.MaxValue, ushort.MaxValue - 1)); - AssertGenericPixel(new Rgb48(1, 2, ushort.MaxValue - 1)); - AssertGenericPixel(new La32(1, ushort.MaxValue - 1)); - AssertGenericPixel(new L16(ushort.MaxValue - 1)); - AssertGenericPixel(new Rgba32(1, 2, 255, 254)); + // Act: + Color color = new(Vector4.One); + + // Assert: + Assert.Equal(new RgbaVector(1, 1, 1, 1), color.ToPixel()); + Assert.Equal(new Rgba64(65535, 65535, 65535, 65535), color.ToPixel()); + Assert.Equal(new Rgba32(255, 255, 255, 255), color.ToPixel()); + Assert.Equal(new L8(255), color.ToPixel()); } - private static void AssertGenericPixel(TPixel source) + [Fact] + public void GenericPixelRoundTrip() + { + AssertGenericPixelRoundTrip(new RgbaVector(float.Epsilon, 2 * float.Epsilon, float.MaxValue, float.MinValue)); + AssertGenericPixelRoundTrip(new Rgba64(1, 2, ushort.MaxValue, ushort.MaxValue - 1)); + AssertGenericPixelRoundTrip(new Rgb48(1, 2, ushort.MaxValue - 1)); + AssertGenericPixelRoundTrip(new La32(1, ushort.MaxValue - 1)); + AssertGenericPixelRoundTrip(new L16(ushort.MaxValue - 1)); + AssertGenericPixelRoundTrip(new Rgba32(1, 2, 255, 254)); + } + + private static void AssertGenericPixelRoundTrip(TPixel source) where TPixel : unmanaged, IPixel { // Act: @@ -110,6 +137,27 @@ namespace SixLabors.ImageSharp.Tests TPixel actual = color.ToPixel(); Assert.Equal(source, actual); } + + [Fact] + public void GenericPixelDifferentPrecision() + { + AssertGenericPixelDifferentPrecision(new RgbaVector(1, 1, 1, 1), new Rgba64(65535, 65535, 65535, 65535)); + AssertGenericPixelDifferentPrecision(new RgbaVector(1, 1, 1, 1), new Rgba32(255, 255, 255, 255)); + AssertGenericPixelDifferentPrecision(new Rgba64(65535, 65535, 65535, 65535), new Rgba32(255, 255, 255, 255)); + AssertGenericPixelDifferentPrecision(new Rgba32(255, 255, 255, 255), new L8(255)); + } + + private static void AssertGenericPixelDifferentPrecision(TPixel source, TPixel2 expected) + where TPixel : unmanaged, IPixel + where TPixel2 : unmanaged, IPixel + { + // Act: + var color = Color.FromPixel(source); + + // Assert: + TPixel2 actual = color.ToPixel(); + Assert.Equal(expected, actual); + } } } } diff --git a/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs b/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs index 89276014b..d61361c80 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs @@ -63,6 +63,19 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(source, data); } + [Fact] + public void Abgr32() + { + var source = new Abgr32(1, 22, 33, 231); + + // Act: + var color = new Color(source); + + // Assert: + Abgr32 data = color.ToPixel(); + Assert.Equal(source, data); + } + [Fact] public void Rgb24() { diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 803babdfa..7497e1b4c 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -3,10 +3,12 @@ using System; using System.Linq; +using Microsoft.DotNet.RemoteExecutor; using Moq; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.IO; - +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Tests.Memory; using Xunit; // ReSharper disable InconsistentNaming @@ -21,7 +23,7 @@ namespace SixLabors.ImageSharp.Tests public Configuration DefaultConfiguration { get; } - private readonly int expectedDefaultConfigurationCount = 7; + private readonly int expectedDefaultConfigurationCount = 8; public ConfigurationTests() { @@ -146,5 +148,45 @@ namespace SixLabors.ImageSharp.Tests Assert.Throws( () => config.StreamProcessingBufferSize = 0); } + + [Fact] + public void MemoryAllocator_Setter_Roundtrips() + { + MemoryAllocator customAllocator = new SimpleGcMemoryAllocator(); + var config = new Configuration() { MemoryAllocator = customAllocator }; + Assert.Same(customAllocator, config.MemoryAllocator); + } + + [Fact] + public void MemoryAllocator_SetNull_ThrowsArgumentNullException() + { + var config = new Configuration(); + Assert.Throws(() => config.MemoryAllocator = null); + } + + [Fact] + public void InheritsDefaultMemoryAllocatorInstance() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + var c1 = new Configuration(); + var c2 = new Configuration(new MockConfigurationModule()); + var c3 = Configuration.CreateDefaultInstance(); + + Assert.Same(MemoryAllocator.Default, Configuration.Default.MemoryAllocator); + Assert.Same(MemoryAllocator.Default, c1.MemoryAllocator); + Assert.Same(MemoryAllocator.Default, c2.MemoryAllocator); + Assert.Same(MemoryAllocator.Default, c3.MemoryAllocator); + } + } + + private class MockConfigurationModule : IConfigurationModule + { + public void Configure(Configuration configuration) + { + } + } } } diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs index b426f4404..d10549b40 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs @@ -128,8 +128,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing using (Image background = provider.GetImage()) using (var overlay = new Image(50, 50)) { - Assert.True(overlay.TryGetSinglePixelSpan(out Span overlaySpan)); - overlaySpan.Fill(Color.Black); + Assert.True(overlay.DangerousTryGetSinglePixelMemory(out Memory overlayMem)); + overlayMem.Span.Fill(Color.Black); background.Mutate(c => c.DrawImage(overlay, new Point(x, y), PixelColorBlendingMode.Normal, 1F)); diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 4e6dc36dc..f85bc78fc 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -19,7 +19,6 @@ using static SixLabors.ImageSharp.Tests.TestImages.Bmp; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Bmp { - [Collection("RunSerial")] [Trait("Format", "Bmp")] public class BmpDecoderTests { diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index d645f0b60..073cf5fcf 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -17,19 +17,18 @@ using static SixLabors.ImageSharp.Tests.TestImages.Bmp; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Bmp { - [Collection("RunSerial")] [Trait("Format", "Bmp")] public class BmpEncoderTests { public static readonly TheoryData BitsPerPixel = - new TheoryData + new() { BmpBitsPerPixel.Pixel24, BmpBitsPerPixel.Pixel32 }; public static readonly TheoryData RatioFiles = - new TheoryData + new() { { Car, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, { V5Header, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, @@ -37,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp }; public static readonly TheoryData BmpBitsPerPixelFiles = - new TheoryData + new() { { Bit1, BmpBitsPerPixel.Pixel1 }, { Bit4, BmpBitsPerPixel.Pixel4 }, diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index bf13a9097..5a8425c1f 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Reflection; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -16,7 +15,6 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Formats { - [Collection("RunSerial")] public class GeneralFormatTests { /// @@ -34,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Formats /// /// The collection of image files to test against. /// - protected static readonly List Files = new List + protected static readonly List Files = new() { TestFile.Create(TestImages.Jpeg.Baseline.Calliphora), TestFile.Create(TestImages.Bmp.Car), @@ -85,8 +83,8 @@ namespace SixLabors.ImageSharp.Tests.Formats } public static readonly TheoryData QuantizerNames = - new TheoryData - { + new() + { nameof(KnownQuantizers.Octree), nameof(KnownQuantizers.WebSafe), nameof(KnownQuantizers.Werner), @@ -99,8 +97,6 @@ namespace SixLabors.ImageSharp.Tests.Formats public void QuantizeImageShouldPreserveMaximumColorPrecision(TestImageProvider provider, string quantizerName) where TPixel : unmanaged, IPixel { - provider.Configuration.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling(); - IQuantizer quantizer = GetQuantizer(quantizerName); using (Image image = provider.GetImage()) @@ -136,6 +132,11 @@ namespace SixLabors.ImageSharp.Tests.Formats image.SaveAsJpeg(output); } + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.pbm"))) + { + image.SaveAsPbm(output); + } + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.png"))) { image.SaveAsPng(output); @@ -183,6 +184,10 @@ namespace SixLabors.ImageSharp.Tests.Formats } [Theory] + [InlineData(10, 10, "pbm")] + [InlineData(100, 100, "pbm")] + [InlineData(100, 10, "pbm")] + [InlineData(10, 100, "pbm")] [InlineData(10, 10, "png")] [InlineData(100, 100, "png")] [InlineData(100, 10, "png")] diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index c0df1e400..6bf606ac9 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -17,13 +17,12 @@ using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Gif { - [Collection("RunSerial")] [Trait("Format", "Gif")] public class GifDecoderTests { private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; - private static GifDecoder GifDecoder => new GifDecoder(); + private static GifDecoder GifDecoder => new(); public static readonly string[] MultiFrameTestFiles = { @@ -165,9 +164,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif ImageFrame first = kumin1.Frames[i]; ImageFrame second = kumin2.Frames[i]; - Assert.True(second.TryGetSinglePixelSpan(out Span secondSpan)); + Assert.True(second.DangerousTryGetSinglePixelMemory(out Memory secondMemory)); - first.ComparePixelBufferTo(secondSpan); + first.ComparePixelBufferTo(secondMemory.Span); } } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index cb0b1521d..cb24de81b 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -13,7 +13,6 @@ using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Gif { - [Collection("RunSerial")] [Trait("Format", "Gif")] public class GifEncoderTests { @@ -21,13 +20,22 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0015F); public static readonly TheoryData RatioFiles = - new TheoryData + new() { { TestImages.Gif.Rings, (int)ImageMetadata.DefaultHorizontalResolution, (int)ImageMetadata.DefaultVerticalResolution, PixelResolutionUnit.PixelsPerInch }, { TestImages.Gif.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, { TestImages.Gif.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } }; + public GifEncoderTests() + { + // Free the pool on 32 bit: + if (!TestEnvironment.Is64BitProcess) + { + Configuration.Default.MemoryAllocator.ReleaseRetainedResources(); + } + } + [Theory] [WithTestPatternImages(100, 100, TestPixelTypes, false)] [WithTestPatternImages(100, 100, TestPixelTypes, false)] diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs index 59f7ebb74..5699b4741 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs @@ -12,12 +12,11 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Gif { - [Collection("RunSerial")] [Trait("Format", "Gif")] public class GifMetadataTests { public static readonly TheoryData RatioFiles = - new TheoryData + new() { { TestImages.Gif.Rings, (int)ImageMetadata.DefaultHorizontalResolution, (int)ImageMetadata.DefaultVerticalResolution, PixelResolutionUnit.PixelsPerInch }, { TestImages.Gif.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, @@ -25,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif }; public static readonly TheoryData RepeatFiles = - new TheoryData + new() { { TestImages.Gif.Cheers, 0 }, { TestImages.Gif.Receipt, 1 }, diff --git a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs index 5cd70b100..05a7a3be8 100644 --- a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs +++ b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs @@ -9,6 +9,7 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tiff; @@ -33,6 +34,7 @@ namespace SixLabors.ImageSharp.Tests.Formats [Fact] public void IfAutoLoadWellKnownFormatsIsTrueAllFormatsAreLoaded() { + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); @@ -41,6 +43,7 @@ namespace SixLabors.ImageSharp.Tests.Formats Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 8f5f10f19..ae7e81254 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -114,56 +114,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // PrintLinearData((Span)mirror); } - [Fact] - public unsafe void Load_Store_FloatArray_Ptr() - { - float[] data = new float[Block8x8F.Size]; - float[] mirror = new float[Block8x8F.Size]; - - for (int i = 0; i < Block8x8F.Size; i++) - { - data[i] = i; - } - - this.Measure( - Times, - () => - { - var b = default(Block8x8F); - Block8x8F.LoadFrom(&b, data); - Block8x8F.ScaledCopyTo(&b, mirror); - }); - - Assert.Equal(data, mirror); - - // PrintLinearData((Span)mirror); - } - - [Fact] - public void Load_Store_IntArray() - { - int[] data = new int[Block8x8F.Size]; - int[] mirror = new int[Block8x8F.Size]; - - for (int i = 0; i < Block8x8F.Size; i++) - { - data[i] = i; - } - - this.Measure( - Times, - () => - { - var v = default(Block8x8F); - v.LoadFrom(data); - v.ScaledCopyTo(mirror); - }); - - Assert.Equal(data, mirror); - - // PrintLinearData((Span)mirror); - } - [Fact] public void TransposeInplace() { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs index b13a196cb..094622070 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs @@ -70,20 +70,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(data, result); } - [Fact] - public void Equality_WhenTrue() - { - short[] data = Create8x8ShortData(); - var block1 = Block8x8.Load(data); - var block2 = Block8x8.Load(data); - - block1[0] = 42; - block2[0] = 42; - - Assert.Equal(block1, block2); - Assert.Equal(block1.GetHashCode(), block2.GetHashCode()); - } - [Fact] public void Equality_WhenFalse() { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs b/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs deleted file mode 100644 index d5ee2a2b8..000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Formats.Jpg -{ - [Trait("Format", "Jpg")] - public class GenericBlock8x8Tests - { - public static Image CreateTestImage() - where TPixel : unmanaged, IPixel - { - var image = new Image(10, 10); - Buffer2D pixels = image.GetRootFramePixelBuffer(); - for (int i = 0; i < 10; i++) - { - for (int j = 0; j < 10; j++) - { - var rgba = new Rgba32((byte)(i + 1), (byte)(j + 1), 200, 255); - var color = default(TPixel); - color.FromRgba32(rgba); - - pixels[i, j] = color; - } - } - - return image; - } - - [Theory] - [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32 /* | PixelTypes.Rgba32 | PixelTypes.Argb32*/)] - public void LoadAndStretchCorners_FromOrigo(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image s = provider.GetImage()) - { - var d = default(GenericBlock8x8); - RowOctet rowOctet = default; - rowOctet.Update(s.GetRootFramePixelBuffer(), 0); - d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 0, 0, ref rowOctet); - - TPixel a = s.Frames.RootFrame[0, 0]; - TPixel b = d[0, 0]; - - Assert.Equal(s[0, 0], d[0, 0]); - Assert.Equal(s[1, 0], d[1, 0]); - Assert.Equal(s[7, 0], d[7, 0]); - Assert.Equal(s[0, 1], d[0, 1]); - Assert.Equal(s[1, 1], d[1, 1]); - Assert.Equal(s[7, 0], d[7, 0]); - Assert.Equal(s[0, 7], d[0, 7]); - Assert.Equal(s[7, 7], d[7, 7]); - } - } - - [Theory] - [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32)] - public void LoadAndStretchCorners_WithOffset(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image s = provider.GetImage()) - { - var d = default(GenericBlock8x8); - RowOctet rowOctet = default; - rowOctet.Update(s.GetRootFramePixelBuffer(), 7); - - d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 6, 7, ref rowOctet); - - Assert.Equal(s[6, 7], d[0, 0]); - Assert.Equal(s[6, 8], d[0, 1]); - Assert.Equal(s[7, 8], d[1, 1]); - - Assert.Equal(s[6, 9], d[0, 2]); - Assert.Equal(s[6, 9], d[0, 3]); - Assert.Equal(s[6, 9], d[0, 7]); - - Assert.Equal(s[7, 9], d[1, 2]); - Assert.Equal(s[7, 9], d[1, 3]); - Assert.Equal(s[7, 9], d[1, 7]); - - Assert.Equal(s[9, 9], d[3, 2]); - Assert.Equal(s[9, 9], d[3, 3]); - Assert.Equal(s[9, 9], d[3, 7]); - - Assert.Equal(s[9, 7], d[3, 0]); - Assert.Equal(s[9, 7], d[4, 0]); - Assert.Equal(s[9, 7], d[7, 0]); - - Assert.Equal(s[9, 9], d[3, 2]); - Assert.Equal(s[9, 9], d[4, 2]); - Assert.Equal(s[9, 9], d[7, 2]); - - Assert.Equal(s[9, 9], d[4, 3]); - Assert.Equal(s[9, 9], d[7, 7]); - } - } - - [Fact] - public void Indexer() - { - var block = default(GenericBlock8x8); - Span span = block.AsSpanUnsafe(); - Assert.Equal(64, span.Length); - - for (int i = 0; i < 64; i++) - { - span[i] = new Rgb24((byte)i, (byte)(2 * i), (byte)(3 * i)); - } - - var expected00 = new Rgb24(0, 0, 0); - var expected07 = new Rgb24(7, 14, 21); - var expected11 = new Rgb24(9, 18, 27); - var expected77 = new Rgb24(63, 126, 189); - var expected67 = new Rgb24(62, 124, 186); - - Assert.Equal(expected00, block[0, 0]); - Assert.Equal(expected07, block[7, 0]); - Assert.Equal(expected11, block[1, 1]); - Assert.Equal(expected67, block[6, 7]); - Assert.Equal(expected77, block[7, 7]); - } - } -} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 4e0970e3b..91f87610e 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -9,7 +9,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Tests.Colorspaces.Conversion; - +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; using Xunit.Abstractions; @@ -18,20 +18,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Trait("Format", "Jpg")] public class JpegColorConverterTests { + private const float MaxColorChannelValue = 255f; + private const float Precision = 0.1F / 255; - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(Precision); + private const int TestBufferLength = 40; - // int inputBufferLength, int resultBufferLength, int seed - public static readonly TheoryData CommonConversionData = - new TheoryData - { - { 40, 40, 1 }, - { 42, 40, 2 }, - { 42, 39, 3 } - }; +#if SUPPORTS_RUNTIME_INTRINSICS + private static readonly HwIntrinsics IntrinsicsConfig = HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX; +#else + private static readonly HwIntrinsics IntrinsicsConfig = HwIntrinsics.AllowAll; +#endif + + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new(epsilon: Precision); + + private static readonly ColorSpaceConverter ColorSpaceConverter = new(); - private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); + public static readonly TheoryData Seeds = new() { 1, 2, 3 }; public JpegColorConverterTests(ITestOutputHelper output) { @@ -40,349 +43,265 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private ITestOutputHelper Output { get; } - [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromYCbCrBasic(int inputBufferLength, int resultBufferLength, int seed) + [Fact] + public void GetConverterThrowsExceptionOnInvalidColorSpace() { - ValidateConversion( - new JpegColorConverter.FromYCbCrBasic(8), - 3, - inputBufferLength, - resultBufferLength, - seed); + Assert.Throws(() => JpegColorConverterBase.GetConverter(JpegColorSpace.Undefined, 8)); } - [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromYCbCrVector4(int inputBufferLength, int resultBufferLength, int seed) + [Fact] + public void GetConverterThrowsExceptionOnInvalidPrecision() { - if (!SimdUtils.HasVector4) - { - this.Output.WriteLine("No SSE present, skipping test!"); - return; - } - - ValidateConversion( - new JpegColorConverter.FromYCbCrVector4(8), - 3, - inputBufferLength, - resultBufferLength, - seed); + // Valid precisions: 8 & 12 bit + Assert.Throws(() => JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, 9)); } [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromYCbCrVector8(int inputBufferLength, int resultBufferLength, int seed) + [InlineData(JpegColorSpace.Grayscale, 8)] + [InlineData(JpegColorSpace.Grayscale, 12)] + [InlineData(JpegColorSpace.Ycck, 8)] + [InlineData(JpegColorSpace.Ycck, 12)] + [InlineData(JpegColorSpace.Cmyk, 8)] + [InlineData(JpegColorSpace.Cmyk, 12)] + [InlineData(JpegColorSpace.RGB, 8)] + [InlineData(JpegColorSpace.RGB, 12)] + [InlineData(JpegColorSpace.YCbCr, 8)] + [InlineData(JpegColorSpace.YCbCr, 12)] + internal void GetConverterReturnsValidConverter(JpegColorSpace colorSpace, int precision) { - if (!SimdUtils.HasVector8) - { - this.Output.WriteLine("No AVX2 present, skipping test!"); - return; - } + var converter = JpegColorConverterBase.GetConverter(colorSpace, precision); - ValidateConversion( - new JpegColorConverter.FromYCbCrVector8(8), - 3, - inputBufferLength, - resultBufferLength, - seed); + Assert.NotNull(converter); + Assert.True(converter.IsAvailable); + Assert.Equal(colorSpace, converter.ColorSpace); + Assert.Equal(precision, converter.Precision); } [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromYCbCrAvx2(int inputBufferLength, int resultBufferLength, int seed) + [InlineData(JpegColorSpace.Grayscale, 1)] + [InlineData(JpegColorSpace.Ycck, 4)] + [InlineData(JpegColorSpace.Cmyk, 4)] + [InlineData(JpegColorSpace.RGB, 3)] + [InlineData(JpegColorSpace.YCbCr, 3)] + internal void ConvertWithSelectedConverter(JpegColorSpace colorSpace, int componentCount) { - if (!SimdUtils.HasAvx2) - { - this.Output.WriteLine("No AVX2 present, skipping test!"); - return; - } - + var converter = JpegColorConverterBase.GetConverter(colorSpace, 8); ValidateConversion( - new JpegColorConverter.FromYCbCrAvx2(8), - 3, - inputBufferLength, - resultBufferLength, - seed); + converter, + componentCount, + 1); } [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromYCbCr_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed) - { - ValidateConversion( - JpegColorSpace.YCbCr, - 3, - inputBufferLength, - resultBufferLength, - seed); - } + [MemberData(nameof(Seeds))] + public void FromYCbCrBasic(int seed) => + this.TestConverter(new JpegColorConverterBase.FromYCbCrScalar(8), 3, seed); [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromCmykBasic(int inputBufferLength, int resultBufferLength, int seed) + [MemberData(nameof(Seeds))] + public void FromYCbCrVector(int seed) { - ValidateConversion( - new JpegColorConverter.FromCmykBasic(8), - 4, - inputBufferLength, - resultBufferLength, - seed); - } + var converter = new JpegColorConverterBase.FromYCbCrVector(8); - [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromCmykVector8(int inputBufferLength, int resultBufferLength, int seed) - { - if (!SimdUtils.HasVector8) + if (!converter.IsAvailable) { - this.Output.WriteLine("No AVX2 present, skipping test!"); + this.Output.WriteLine( + $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); return; } - ValidateConversion( - new JpegColorConverter.FromCmykVector8(8), - 4, - inputBufferLength, - resultBufferLength, - seed); + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + IntrinsicsConfig); + + static void RunTest(string arg) => + ValidateConversion( + new JpegColorConverterBase.FromYCbCrVector(8), + 3, + FeatureTestRunner.Deserialize(arg)); } [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromCmykAvx2(int inputBufferLength, int resultBufferLength, int seed) + [MemberData(nameof(Seeds))] + public void FromCmykBasic(int seed) => + this.TestConverter(new JpegColorConverterBase.FromCmykScalar(8), 4, seed); + + [Theory] + [MemberData(nameof(Seeds))] + public void FromCmykVector(int seed) { - if (!SimdUtils.HasAvx2) + var converter = new JpegColorConverterBase.FromCmykVector(8); + + if (!converter.IsAvailable) { - this.Output.WriteLine("No AVX2 present, skipping test!"); + this.Output.WriteLine( + $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); return; } - ValidateConversion( - new JpegColorConverter.FromCmykAvx2(8), - 4, - inputBufferLength, - resultBufferLength, - seed); - } + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + IntrinsicsConfig); - [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromCmyk_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed) - { - ValidateConversion( - JpegColorSpace.Cmyk, - 4, - inputBufferLength, - resultBufferLength, - seed); + static void RunTest(string arg) => + ValidateConversion( + new JpegColorConverterBase.FromCmykVector(8), + 4, + FeatureTestRunner.Deserialize(arg)); } [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromGrayscaleBasic(int inputBufferLength, int resultBufferLength, int seed) - { - ValidateConversion( - new JpegColorConverter.FromGrayscaleBasic(8), - 1, - inputBufferLength, - resultBufferLength, - seed); - } + [MemberData(nameof(Seeds))] + public void FromGrayscaleBasic(int seed) => + this.TestConverter(new JpegColorConverterBase.FromGrayscaleScalar(8), 1, seed); [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromGrayscaleAvx2(int inputBufferLength, int resultBufferLength, int seed) + [MemberData(nameof(Seeds))] + public void FromGrayscaleVector(int seed) { - if (!SimdUtils.HasAvx2) + var converter = new JpegColorConverterBase.FromGrayScaleVector(8); + + if (!converter.IsAvailable) { - this.Output.WriteLine("No AVX2 present, skipping test!"); + this.Output.WriteLine( + $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); return; } - ValidateConversion( - new JpegColorConverter.FromGrayscaleAvx2(8), - 1, - inputBufferLength, - resultBufferLength, - seed); - } + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + IntrinsicsConfig); - [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromGraysacle_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed) - { - ValidateConversion( - JpegColorSpace.Grayscale, - 1, - inputBufferLength, - resultBufferLength, - seed); + static void RunTest(string arg) => + ValidateConversion( + new JpegColorConverterBase.FromGrayScaleVector(8), + 1, + FeatureTestRunner.Deserialize(arg)); } [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromRgbBasic(int inputBufferLength, int resultBufferLength, int seed) - { - ValidateConversion( - new JpegColorConverter.FromRgbBasic(8), - 3, - inputBufferLength, - resultBufferLength, - seed); - } + [MemberData(nameof(Seeds))] + public void FromRgbBasic(int seed) => + this.TestConverter(new JpegColorConverterBase.FromRgbScalar(8), 3, seed); [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromRgbVector8(int inputBufferLength, int resultBufferLength, int seed) + [MemberData(nameof(Seeds))] + public void FromRgbVector(int seed) { - if (!SimdUtils.HasVector8) + var converter = new JpegColorConverterBase.FromRgbVector(8); + + if (!converter.IsAvailable) { - this.Output.WriteLine("No AVX2 present, skipping test!"); + this.Output.WriteLine( + $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); return; } - ValidateConversion( - new JpegColorConverter.FromRgbVector8(8), - 3, - inputBufferLength, - resultBufferLength, - seed); + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + IntrinsicsConfig); + + static void RunTest(string arg) => + ValidateConversion( + new JpegColorConverterBase.FromRgbVector(8), + 3, + FeatureTestRunner.Deserialize(arg)); } [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromRgbAvx2(int inputBufferLength, int resultBufferLength, int seed) + [MemberData(nameof(Seeds))] + public void FromYccKBasic(int seed) => + this.TestConverter(new JpegColorConverterBase.FromYccKScalar(8), 4, seed); + + [Theory] + [MemberData(nameof(Seeds))] + public void FromYccKVector(int seed) { - if (!SimdUtils.HasAvx2) + var converter = new JpegColorConverterBase.FromYccKVector(8); + + if (!converter.IsAvailable) { - this.Output.WriteLine("No AVX2 present, skipping test!"); + this.Output.WriteLine( + $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); return; } - ValidateConversion( - new JpegColorConverter.FromRgbAvx2(8), - 3, - inputBufferLength, - resultBufferLength, - seed); + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + IntrinsicsConfig); + + static void RunTest(string arg) => + ValidateConversion( + new JpegColorConverterBase.FromYccKVector(8), + 4, + FeatureTestRunner.Deserialize(arg)); } +#if SUPPORTS_RUNTIME_INTRINSICS [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromRgb_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed) - { - ValidateConversion( - JpegColorSpace.RGB, - 3, - inputBufferLength, - resultBufferLength, - seed); - } + [MemberData(nameof(Seeds))] + public void FromYCbCrAvx2(int seed) => + this.TestConverter(new JpegColorConverterBase.FromYCbCrAvx(8), 3, seed); [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromYccKBasic(int inputBufferLength, int resultBufferLength, int seed) - { - ValidateConversion( - new JpegColorConverter.FromYccKBasic(8), - 4, - inputBufferLength, - resultBufferLength, - seed); - } + [MemberData(nameof(Seeds))] + public void FromCmykAvx2(int seed) => + this.TestConverter(new JpegColorConverterBase.FromCmykAvx(8), 4, seed); [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromYccKVector8(int inputBufferLength, int resultBufferLength, int seed) - { - if (!SimdUtils.HasVector8) - { - this.Output.WriteLine("No AVX2 present, skipping test!"); - return; - } + [MemberData(nameof(Seeds))] + public void FromGrayscaleAvx2(int seed) => + this.TestConverter(new JpegColorConverterBase.FromGrayscaleAvx(8), 1, seed); - ValidateConversion( - new JpegColorConverter.FromYccKVector8(8), - 4, - inputBufferLength, - resultBufferLength, - seed); - } + [Theory] + [MemberData(nameof(Seeds))] + public void FromRgbAvx2(int seed) => + this.TestConverter(new JpegColorConverterBase.FromRgbAvx(8), 3, seed); [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromYccKAvx2(int inputBufferLength, int resultBufferLength, int seed) + [MemberData(nameof(Seeds))] + public void FromYccKAvx2(int seed) => + this.TestConverter(new JpegColorConverterBase.FromYccKAvx(8), 4, seed); +#endif + + private void TestConverter( + JpegColorConverterBase converter, + int componentCount, + int seed) { - if (!SimdUtils.HasAvx2) + if (!converter.IsAvailable) { - this.Output.WriteLine("No AVX2 present, skipping test!"); + this.Output.WriteLine( + $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); return; } ValidateConversion( - new JpegColorConverter.FromYccKAvx2(8), - 4, - inputBufferLength, - resultBufferLength, - seed); - } - - [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromYcck_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed) - { - ValidateConversion( - JpegColorSpace.Ycck, - 4, - inputBufferLength, - resultBufferLength, + converter, + componentCount, seed); } - // Benchmark, for local execution only - // [Theory] - // [InlineData(false)] - // [InlineData(true)] - public void BenchmarkYCbCr(bool simd) - { - int count = 2053; - int times = 50000; - - JpegColorConverter.ComponentValues values = CreateRandomValues(3, count, 1); - var result = new Vector4[count]; - - JpegColorConverter converter = simd ? (JpegColorConverter)new JpegColorConverter.FromYCbCrVector4(8) : new JpegColorConverter.FromYCbCrBasic(8); - - // Warm up: - converter.ConvertToRgbInplace(values); - - using (new MeasureGuard(this.Output, $"{converter.GetType().Name} x {times}")) - { - for (int i = 0; i < times; i++) - { - converter.ConvertToRgbInplace(values); - } - } - } - - private static JpegColorConverter.ComponentValues CreateRandomValues( + private static JpegColorConverterBase.ComponentValues CreateRandomValues( + int length, int componentCount, - int inputBufferLength, - int seed, - float minVal = 0f, - float maxVal = 255f) + int seed) { var rnd = new Random(seed); var buffers = new Buffer2D[componentCount]; for (int i = 0; i < componentCount; i++) { - var values = new float[inputBufferLength]; + float[] values = new float[length]; - for (int j = 0; j < inputBufferLength; j++) + for (int j = 0; j < values.Length; j++) { - values[j] = ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal; + values[j] = (float)rnd.NextDouble() * MaxColorChannelValue; } // no need to dispose when buffer is not array owner @@ -391,55 +310,34 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg buffers[i] = new Buffer2D(source, values.Length, 1); } - return new JpegColorConverter.ComponentValues(buffers, 0); + return new JpegColorConverterBase.ComponentValues(buffers, 0); } private static void ValidateConversion( - JpegColorSpace colorSpace, + JpegColorConverterBase converter, int componentCount, - int inputBufferLength, - int resultBufferLength, int seed) { - ValidateConversion( - JpegColorConverter.GetConverter(colorSpace, 8), - componentCount, - inputBufferLength, - resultBufferLength, - seed); - } - - private static void ValidateConversion( - JpegColorConverter converter, - int componentCount, - int inputBufferLength, - int resultBufferLength, - int seed) - { - JpegColorConverter.ComponentValues original = CreateRandomValues(componentCount, inputBufferLength, seed); - JpegColorConverter.ComponentValues values = Copy(original); + JpegColorConverterBase.ComponentValues original = CreateRandomValues(TestBufferLength, componentCount, seed); + JpegColorConverterBase.ComponentValues values = new( + original.ComponentCount, + original.Component0.ToArray(), + original.Component1.ToArray(), + original.Component2.ToArray(), + original.Component3.ToArray()); converter.ConvertToRgbInplace(values); - for (int i = 0; i < resultBufferLength; i++) + for (int i = 0; i < TestBufferLength; i++) { Validate(converter.ColorSpace, original, values, i); } - - static JpegColorConverter.ComponentValues Copy(JpegColorConverter.ComponentValues values) - { - Span c0 = values.Component0.ToArray(); - Span c1 = values.ComponentCount > 1 ? values.Component1.ToArray().AsSpan() : c0; - Span c2 = values.ComponentCount > 2 ? values.Component2.ToArray().AsSpan() : c0; - Span c3 = values.ComponentCount > 3 ? values.Component3.ToArray().AsSpan() : Span.Empty; - return new JpegColorConverter.ComponentValues(values.ComponentCount, c0, c1, c2, c3); - } } private static void Validate( JpegColorSpace colorSpace, - in JpegColorConverter.ComponentValues original, - in JpegColorConverter.ComponentValues result, + in JpegColorConverterBase.ComponentValues original, + in JpegColorConverterBase.ComponentValues result, int i) { switch (colorSpace) @@ -459,92 +357,90 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg case JpegColorSpace.YCbCr: ValidateYCbCr(original, result, i); break; + case JpegColorSpace.Undefined: default: - Assert.True(false, $"Colorspace {colorSpace} not supported!"); + Assert.True(false, $"Invalid Colorspace enum value: {colorSpace}."); break; } } - private static void ValidateYCbCr(in JpegColorConverter.ComponentValues values, in JpegColorConverter.ComponentValues result, int i) + private static void ValidateYCbCr(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) { float y = values.Component0[i]; float cb = values.Component1[i]; float cr = values.Component2[i]; - var ycbcr = new YCbCr(y, cb, cr); + var expected = ColorSpaceConverter.ToRgb(new YCbCr(y, cb, cr)); var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); - var expected = ColorSpaceConverter.ToRgb(ycbcr); - Assert.Equal(expected, actual, ColorSpaceComparer); + bool equal = ColorSpaceComparer.Equals(expected, actual); + Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); } - private static void ValidateCyyK(in JpegColorConverter.ComponentValues values, in JpegColorConverter.ComponentValues result, int i) + private static void ValidateCyyK(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) { - var v = new Vector4(0, 0, 0, 1F); - var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); - float y = values.Component0[i]; float cb = values.Component1[i] - 128F; float cr = values.Component2[i] - 128F; float k = values.Component3[i] / 255F; - v.X = (255F - (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; - v.Y = (255F - (float)Math.Round( + float r = (255F - (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; + float g = (255F - (float)Math.Round( y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k; - v.Z = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; - v.W = 1F; + float b = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; - v *= scale; + r /= MaxColorChannelValue; + g /= MaxColorChannelValue; + b /= MaxColorChannelValue; + var expected = new Rgb(r, g, b); var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); - var expected = new Rgb(v.X, v.Y, v.Z); - Assert.Equal(expected, actual, ColorSpaceComparer); + bool equal = ColorSpaceComparer.Equals(expected, actual); + Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); } - private static void ValidateRgb(in JpegColorConverter.ComponentValues values, in JpegColorConverter.ComponentValues result, int i) + private static void ValidateRgb(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) { - float r = values.Component0[i]; - float g = values.Component1[i]; - float b = values.Component2[i]; + float r = values.Component0[i] / MaxColorChannelValue; + float g = values.Component1[i] / MaxColorChannelValue; + float b = values.Component2[i] / MaxColorChannelValue; + var expected = new Rgb(r, g, b); var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); - var expected = new Rgb(r / 255F, g / 255F, b / 255F); - Assert.Equal(expected, actual, ColorSpaceComparer); + bool equal = ColorSpaceComparer.Equals(expected, actual); + Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); } - private static void ValidateGrayScale(in JpegColorConverter.ComponentValues values, in JpegColorConverter.ComponentValues result, int i) + private static void ValidateGrayScale(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) { - float y = values.Component0[i]; + float y = values.Component0[i] / MaxColorChannelValue; + var expected = new Rgb(y, y, y); + var actual = new Rgb(result.Component0[i], result.Component0[i], result.Component0[i]); - var expected = new Rgb(y / 255F, y / 255F, y / 255F); - Assert.Equal(expected, actual, ColorSpaceComparer); + bool equal = ColorSpaceComparer.Equals(expected, actual); + Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); } - private static void ValidateCmyk(in JpegColorConverter.ComponentValues values, in JpegColorConverter.ComponentValues result, int i) + private static void ValidateCmyk(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) { - var v = new Vector4(0, 0, 0, 1F); - var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); - float c = values.Component0[i]; float m = values.Component1[i]; float y = values.Component2[i]; - float k = values.Component3[i] / 255F; - - v.X = c * k; - v.Y = m * k; - v.Z = y * k; - v.W = 1F; + float k = values.Component3[i] / MaxColorChannelValue; - v *= scale; + float r = c * k / MaxColorChannelValue; + float g = m * k / MaxColorChannelValue; + float b = y * k / MaxColorChannelValue; + var expected = new Rgb(r, g, b); var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); - var expected = new Rgb(v.X, v.Y, v.Z); - Assert.Equal(expected, actual, ColorSpaceComparer); + bool equal = ColorSpaceComparer.Equals(expected, actual); + Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 2cbc29027..08d8d9038 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -21,8 +21,7 @@ using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Formats.Jpg { // TODO: Scatter test cases into multiple test classes - [Collection("RunSerial")] - [Trait("Format", "Jpg")] + [Trait("Format", "Jpg")] public partial class JpegDecoderTests { public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.Bgr24 | PixelTypes.RgbaVector; @@ -93,6 +92,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.IsType>(image); } + [Fact] + public async Task DecodeAsync_NonGeneric_CreatesRgb24Image() + { + string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); + using Image image = await Image.LoadAsync(file); + Assert.IsType>(image); + } + [Theory] [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes)] public void JpegDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 62952f537..18eae9fbd 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -19,23 +19,22 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - [Collection("RunSerial")] [Trait("Format", "Jpg")] public class JpegEncoderTests { - private static JpegEncoder JpegEncoder => new JpegEncoder(); + private static JpegEncoder JpegEncoder => new(); - private static JpegDecoder JpegDecoder => new JpegDecoder(); + private static JpegDecoder JpegDecoder => new(); public static readonly TheoryData QualityFiles = - new TheoryData + new() { { TestImages.Jpeg.Baseline.Calliphora, 80 }, { TestImages.Jpeg.Progressive.Fb, 75 } }; public static readonly TheoryData BitsPerPixel_Quality = - new TheoryData + new() { { JpegColorType.YCbCrRatio420, 40 }, { JpegColorType.YCbCrRatio420, 60 }, @@ -49,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg }; public static readonly TheoryData Grayscale_Quality = - new TheoryData + new() { { 40 }, { 60 }, @@ -57,7 +56,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg }; public static readonly TheoryData RatioFiles = - new TheoryData + new() { { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio }, { TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch }, diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs index 8920d42d4..9eb3c0103 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformFDCT(ref source); Block8x8F actual = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformFDCT_UpscaleBy8(ref source); - actual /= 8; + actual.MultiplyInPlace(0.125f); this.CompareBlocks(expected, actual, 1f); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs index 6e25334f2..02f8ba388 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs @@ -51,11 +51,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformFDCT(ref source); - source += 128; + source.AddInPlace(128f); Block8x8 temp = source.RoundAsInt16Block(); Block8x8 actual8 = ReferenceImplementations.StandardIntegerDCT.Subtract128_TransformFDCT_Upscale8(ref temp); Block8x8F actual = actual8.AsFloatBlock(); - actual /= 8; + actual.MultiplyInPlace(0.125f); this.CompareBlocks(expected, actual, 1f); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 1785f3dec..35113f14f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -122,7 +122,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.Output.WriteLine($"Component{i}: [total: {total} | average: {average}]"); averageDifference += average; totalDifference += total; - tolerance += libJpegComponent.SpectralBlocks.DangerousGetSingleSpan().Length; + Size s = libJpegComponent.SpectralBlocks.Size(); + tolerance += s.Width * s.Height; } averageDifference /= componentCount; @@ -182,7 +183,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Buffer2D spectralBlocks = component.SpectralBlocks; for (int i = 0; i < spectralBlocks.Height; i++) { - spectralBlocks.GetRowSpan(i).Clear(); + spectralBlocks.DangerousGetRowSpan(i).Clear(); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs index f0f92d763..0071c623c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; +using System.Threading; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.IO; @@ -43,7 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); // Decoding - using var converter = new SpectralConverter(Configuration.Default, cancellationToken: default); + using var converter = new SpectralConverter(Configuration.Default); var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); var scanDecoder = new HuffmanScanDecoder(bufferedStream, converter, cancellationToken: default); decoder.ParseStream(bufferedStream, scanDecoder, cancellationToken: default); @@ -53,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg provider.Utility.TestName = JpegDecoderTests.DecodeBaselineJpegOutputName; // Comparison - using (Image image = new Image(Configuration.Default, converter.GetPixelBuffer(), new ImageMetadata())) + using (Image image = new Image(Configuration.Default, converter.GetPixelBuffer(CancellationToken.None), new ImageMetadata())) using (Image referenceImage = provider.GetReferenceOutputImage(appendPixelTypeToFileName: false)) { ImageSimilarityReport report = ImageComparer.Exact.CompareImagesOrFrames(referenceImage, image); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs index 5c00b39af..a390212d1 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils for (int y = startIndex; y < endIndex; y++) { - Span blockRow = data.GetRowSpan(y - startIndex); + Span blockRow = data.DangerousGetRowSpan(y - startIndex); for (int x = 0; x < this.WidthInBlocks; x++) { this.MakeBlock(blockRow[x], y, x); @@ -82,7 +82,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils Buffer2D data = c.SpectralBlocks; for (int y = 0; y < this.HeightInBlocks; y++) { - Span blockRow = data.GetRowSpan(y); + Span blockRow = data.DangerousGetRowSpan(y); for (int x = 0; x < this.WidthInBlocks; x++) { this.MakeBlock(blockRow[x], y, x); diff --git a/tests/ImageSharp.Tests/Formats/Pbm/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Pbm/ImageExtensionsTest.cs new file mode 100644 index 000000000..6ab0cf22f --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Pbm/ImageExtensionsTest.cs @@ -0,0 +1,155 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Pbm; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Pbm +{ + public class ImageExtensionsTest + { + [Fact] + public void SaveAsPbm_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsPbm_Path.pbm"); + + using (var image = new Image(10, 10)) + { + image.SaveAsPbm(file); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsPbmAsync_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsPbmAsync_Path.pbm"); + + using (var image = new Image(10, 10)) + { + await image.SaveAsPbmAsync(file); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsPbm_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsPbm_Path_Encoder.pbm"); + + using (var image = new Image(10, 10)) + { + image.SaveAsPbm(file, new PbmEncoder()); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsPbmAsync_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsPbmAsync_Path_Encoder.pbm"); + + using (var image = new Image(10, 10)) + { + await image.SaveAsPbmAsync(file, new PbmEncoder()); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsPbm_Stream() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + image.SaveAsPbm(memoryStream); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsPbmAsync_StreamAsync() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + await image.SaveAsPbmAsync(memoryStream); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsPbm_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + image.SaveAsPbm(memoryStream, new PbmEncoder()); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsPbmAsync_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + await image.SaveAsPbmAsync(memoryStream, new PbmEncoder()); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs new file mode 100644 index 000000000..97237bca5 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.Formats.Pbm; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; +using static SixLabors.ImageSharp.Tests.TestImages.Pbm; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Pbm +{ + [Trait("Format", "Pbm")] + public class PbmDecoderTests + { + [Theory] + [InlineData(BlackAndWhitePlain, PbmColorType.BlackAndWhite, PbmComponentType.Bit)] + [InlineData(BlackAndWhiteBinary, PbmColorType.BlackAndWhite, PbmComponentType.Bit)] + [InlineData(GrayscalePlain, PbmColorType.Grayscale, PbmComponentType.Byte)] + [InlineData(GrayscalePlainMagick, PbmColorType.Grayscale, PbmComponentType.Byte)] + [InlineData(GrayscaleBinary, PbmColorType.Grayscale, PbmComponentType.Byte)] + [InlineData(GrayscaleBinaryWide, PbmColorType.Grayscale, PbmComponentType.Short)] + [InlineData(RgbPlain, PbmColorType.Rgb, PbmComponentType.Byte)] + [InlineData(RgbPlainMagick, PbmColorType.Rgb, PbmComponentType.Byte)] + [InlineData(RgbBinary, PbmColorType.Rgb, PbmComponentType.Byte)] + public void ImageLoadCanDecode(string imagePath, PbmColorType expectedColorType, PbmComponentType expectedComponentType) + { + // Arrange + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + + // Act + using var image = Image.Load(stream); + + // Assert + Assert.NotNull(image); + PbmMetadata metadata = image.Metadata.GetPbmMetadata(); + Assert.NotNull(metadata); + Assert.Equal(expectedColorType, metadata.ColorType); + Assert.Equal(expectedComponentType, metadata.ComponentType); + } + + [Theory] + [InlineData(BlackAndWhitePlain)] + [InlineData(BlackAndWhiteBinary)] + [InlineData(GrayscalePlain)] + [InlineData(GrayscalePlainMagick)] + [InlineData(GrayscaleBinary)] + [InlineData(GrayscaleBinaryWide)] + public void ImageLoadL8CanDecode(string imagePath) + { + // Arrange + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + + // Act + using var image = Image.Load(stream); + + // Assert + Assert.NotNull(image); + } + + [Theory] + [InlineData(RgbPlain)] + [InlineData(RgbPlainMagick)] + [InlineData(RgbBinary)] + public void ImageLoadRgb24CanDecode(string imagePath) + { + // Arrange + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + + // Act + using var image = Image.Load(stream); + + // Assert + Assert.NotNull(image); + } + + [Theory] + [WithFile(BlackAndWhitePlain, PixelTypes.L8, "pbm")] + [WithFile(BlackAndWhiteBinary, PixelTypes.L8, "pbm")] + [WithFile(GrayscalePlain, PixelTypes.L8, "pgm")] + [WithFile(GrayscalePlainNormalized, PixelTypes.L8, "pgm")] + [WithFile(GrayscaleBinary, PixelTypes.L8, "pgm")] + [WithFile(GrayscaleBinaryWide, PixelTypes.L16, "pgm")] + [WithFile(RgbPlain, PixelTypes.Rgb24, "ppm")] + [WithFile(RgbPlainNormalized, PixelTypes.Rgb24, "ppm")] + [WithFile(RgbBinary, PixelTypes.Rgb24, "ppm")] + public void DecodeReferenceImage(TestImageProvider provider, string extension) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + image.DebugSave(provider, extension: extension); + + bool isGrayscale = extension is "pgm" or "pbm"; + image.CompareToReferenceOutput(provider, grayscale: isGrayscale); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs new file mode 100644 index 000000000..e9b496ce4 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs @@ -0,0 +1,145 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.Formats.Pbm; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using Xunit; +using static SixLabors.ImageSharp.Tests.TestImages.Pbm; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Pbm +{ + [Collection("RunSerial")] + [Trait("Format", "Pbm")] + public class PbmEncoderTests + { + public static readonly TheoryData ColorType = + new() + { + PbmColorType.BlackAndWhite, + PbmColorType.Grayscale, + PbmColorType.Rgb + }; + + public static readonly TheoryData PbmColorTypeFiles = + new() + { + { BlackAndWhiteBinary, PbmColorType.BlackAndWhite }, + { BlackAndWhitePlain, PbmColorType.BlackAndWhite }, + { GrayscaleBinary, PbmColorType.Grayscale }, + { GrayscaleBinaryWide, PbmColorType.Grayscale }, + { GrayscalePlain, PbmColorType.Grayscale }, + { RgbBinary, PbmColorType.Rgb }, + { RgbPlain, PbmColorType.Rgb }, + }; + + [Theory] + [MemberData(nameof(PbmColorTypeFiles))] + public void PbmEncoder_PreserveColorType(string imagePath, PbmColorType pbmColorType) + { + var options = new PbmEncoder(); + + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + PbmMetadata meta = output.Metadata.GetPbmMetadata(); + Assert.Equal(pbmColorType, meta.ColorType); + } + } + } + } + + [Theory] + [MemberData(nameof(PbmColorTypeFiles))] + public void PbmEncoder_WithPlainEncoding_PreserveBitsPerPixel(string imagePath, PbmColorType pbmColorType) + { + var options = new PbmEncoder() + { + Encoding = PbmEncoding.Plain + }; + + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + + // EOF indicator for plain is a Space. + memStream.Seek(-1, SeekOrigin.End); + int lastByte = memStream.ReadByte(); + Assert.Equal(0x20, lastByte); + + memStream.Seek(0, SeekOrigin.Begin); + using (var output = Image.Load(memStream)) + { + PbmMetadata meta = output.Metadata.GetPbmMetadata(); + Assert.Equal(pbmColorType, meta.ColorType); + } + } + } + } + + [Theory] + [WithFile(BlackAndWhitePlain, PixelTypes.Rgb24)] + public void PbmEncoder_P1_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.BlackAndWhite, PbmEncoding.Plain); + + [Theory] + [WithFile(BlackAndWhiteBinary, PixelTypes.Rgb24)] + public void PbmEncoder_P4_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.BlackAndWhite, PbmEncoding.Binary); + + [Theory] + [WithFile(GrayscalePlainMagick, PixelTypes.Rgb24)] + public void PbmEncoder_P2_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Grayscale, PbmEncoding.Plain); + + [Theory] + [WithFile(GrayscaleBinary, PixelTypes.Rgb24)] + public void PbmEncoder_P5_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Grayscale, PbmEncoding.Binary); + + [Theory] + [WithFile(RgbPlainMagick, PixelTypes.Rgb24)] + public void PbmEncoder_P3_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Rgb, PbmEncoding.Plain); + + [Theory] + [WithFile(RgbBinary, PixelTypes.Rgb24)] + public void PbmEncoder_P6_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Rgb, PbmEncoding.Binary); + + private static void TestPbmEncoderCore( + TestImageProvider provider, + PbmColorType colorType, + PbmEncoding encoding, + bool useExactComparer = true, + float compareTolerance = 0.01f) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) + { + var encoder = new PbmEncoder { ColorType = colorType, Encoding = encoding }; + + using (var memStream = new MemoryStream()) + { + image.Save(memStream, encoder); + memStream.Position = 0; + using (var encodedImage = (Image)Image.Load(memStream)) + { + ImageComparingUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance); + } + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs new file mode 100644 index 000000000..7915d224a --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs @@ -0,0 +1,86 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.Formats.Pbm; + +using Xunit; +using static SixLabors.ImageSharp.Tests.TestImages.Pbm; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Pbm +{ + [Trait("Format", "Pbm")] + public class PbmMetadataTests + { + [Fact] + public void CloneIsDeep() + { + var meta = new PbmMetadata { ColorType = PbmColorType.Grayscale }; + var clone = (PbmMetadata)meta.DeepClone(); + + clone.ColorType = PbmColorType.Rgb; + clone.ComponentType = PbmComponentType.Short; + + Assert.False(meta.ColorType.Equals(clone.ColorType)); + Assert.False(meta.ComponentType.Equals(clone.ComponentType)); + } + + [Theory] + [InlineData(BlackAndWhitePlain, PbmEncoding.Plain)] + [InlineData(BlackAndWhiteBinary, PbmEncoding.Binary)] + [InlineData(GrayscaleBinary, PbmEncoding.Binary)] + [InlineData(GrayscaleBinaryWide, PbmEncoding.Binary)] + [InlineData(GrayscalePlain, PbmEncoding.Plain)] + [InlineData(RgbBinary, PbmEncoding.Binary)] + [InlineData(RgbPlain, PbmEncoding.Plain)] + public void Identify_DetectsCorrectEncoding(string imagePath, PbmEncoding expectedEncoding) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata(); + Assert.NotNull(bitmapMetadata); + Assert.Equal(expectedEncoding, bitmapMetadata.Encoding); + } + + [Theory] + [InlineData(BlackAndWhitePlain, PbmColorType.BlackAndWhite)] + [InlineData(BlackAndWhiteBinary, PbmColorType.BlackAndWhite)] + [InlineData(GrayscaleBinary, PbmColorType.Grayscale)] + [InlineData(GrayscaleBinaryWide, PbmColorType.Grayscale)] + [InlineData(GrayscalePlain, PbmColorType.Grayscale)] + [InlineData(RgbBinary, PbmColorType.Rgb)] + [InlineData(RgbPlain, PbmColorType.Rgb)] + public void Identify_DetectsCorrectColorType(string imagePath, PbmColorType expectedColorType) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata(); + Assert.NotNull(bitmapMetadata); + Assert.Equal(expectedColorType, bitmapMetadata.ColorType); + } + + [Theory] + [InlineData(BlackAndWhitePlain, PbmComponentType.Bit)] + [InlineData(BlackAndWhiteBinary, PbmComponentType.Bit)] + [InlineData(GrayscaleBinary, PbmComponentType.Byte)] + [InlineData(GrayscaleBinaryWide, PbmComponentType.Short)] + [InlineData(GrayscalePlain, PbmComponentType.Byte)] + [InlineData(RgbBinary, PbmComponentType.Byte)] + [InlineData(RgbPlain, PbmComponentType.Byte)] + public void Identify_DetectsCorrectComponentType(string imagePath, PbmComponentType expectedComponentType) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata(); + Assert.NotNull(bitmapMetadata); + Assert.Equal(expectedComponentType, bitmapMetadata.ComponentType); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs new file mode 100644 index 000000000..190972535 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using Xunit; +using static SixLabors.ImageSharp.Tests.TestImages.Pbm; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Pbm +{ + [Trait("Format", "Pbm")] + public class PbmRoundTripTests + { + [Theory] + [InlineData(BlackAndWhitePlain)] + [InlineData(BlackAndWhiteBinary)] + [InlineData(GrayscalePlain)] + [InlineData(GrayscalePlainNormalized)] + [InlineData(GrayscalePlainMagick)] + [InlineData(GrayscaleBinary)] + public void PbmGrayscaleImageCanRoundTrip(string imagePath) + { + // Arrange + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + + // Act + using var originalImage = Image.Load(stream); + using Image colorImage = originalImage.CloneAs(); + using Image encodedImage = this.RoundTrip(colorImage); + + // Assert + Assert.NotNull(encodedImage); + ImageComparer.Exact.VerifySimilarity(colorImage, encodedImage); + } + + [Theory] + [InlineData(RgbPlain)] + [InlineData(RgbPlainNormalized)] + [InlineData(RgbPlainMagick)] + [InlineData(RgbBinary)] + public void PbmColorImageCanRoundTrip(string imagePath) + { + // Arrange + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + + // Act + using var originalImage = Image.Load(stream); + using Image encodedImage = this.RoundTrip(originalImage); + + // Assert + Assert.NotNull(encodedImage); + ImageComparer.Exact.VerifySimilarity(originalImage, encodedImage); + } + + private Image RoundTrip(Image originalImage) + where TPixel : unmanaged, IPixel + { + using var decodedStream = new MemoryStream(); + originalImage.SaveAsPbm(decodedStream); + decodedStream.Seek(0, SeekOrigin.Begin); + var encodedImage = Image.Load(decodedStream); + return encodedImage; + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 9fc4d03dd..d63dad8d4 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; +using System.Threading.Tasks; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Formats.Png; @@ -16,13 +18,12 @@ using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Png { - [Collection("RunSerial")] [Trait("Format", "Png")] public partial class PngDecoderTests { - private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; + private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; - private static PngDecoder PngDecoder => new PngDecoder(); + private static PngDecoder PngDecoder => new(); public static readonly string[] CommonTestImages = { @@ -63,16 +64,51 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png TestImages.Png.Bad.ZlibZtxtBadHeader, }; + public static readonly TheoryData PixelFormatRange = new() + { + { TestImages.Png.Gray4Bpp, typeof(Image) }, + { TestImages.Png.L16Bit, typeof(Image) }, + { TestImages.Png.Gray1BitTrans, typeof(Image) }, + { TestImages.Png.Gray2BitTrans, typeof(Image) }, + { TestImages.Png.Gray4BitTrans, typeof(Image) }, + { TestImages.Png.GrayA8Bit, typeof(Image) }, + { TestImages.Png.GrayAlpha16Bit, typeof(Image) }, + { TestImages.Png.Palette8Bpp, typeof(Image) }, + { TestImages.Png.PalettedTwoColor, typeof(Image) }, + { TestImages.Png.Rainbow, typeof(Image) }, + { TestImages.Png.Rgb24BppTrans, typeof(Image) }, + { TestImages.Png.Kaboom, typeof(Image) }, + { TestImages.Png.Rgb48Bpp, typeof(Image) }, + { TestImages.Png.Rgb48BppTrans, typeof(Image) }, + { TestImages.Png.Rgba64Bpp, typeof(Image) }, + }; + + [Theory] + [MemberData(nameof(PixelFormatRange))] + public void Decode_NonGeneric_CreatesCorrectImageType(string path, Type type) + { + string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path); + using var image = Image.Load(file); + Assert.IsType(type, image); + } + + [Theory] + [MemberData(nameof(PixelFormatRange))] + public async Task DecodeAsync_NonGeneric_CreatesCorrectImageType(string path, Type type) + { + string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path); + using Image image = await Image.LoadAsync(file); + Assert.IsType(type, image); + } + [Theory] [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] public void Decode(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] @@ -81,11 +117,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_GrayWithAlpha(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] @@ -95,11 +129,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_Interlaced(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] @@ -112,11 +144,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_Indexed(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] @@ -125,11 +155,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_48Bpp(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] @@ -138,11 +166,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_64Bpp(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] @@ -153,11 +179,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decoder_L8bitInterlaced(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] @@ -165,11 +189,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_L16Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] @@ -178,23 +200,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_GrayAlpha16Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] - [WithFile(TestImages.Png.GrayA8BitInterlaced, PixelTypes)] + [WithFile(TestImages.Png.GrayA8BitInterlaced, TestPixelTypes)] public void Decoder_CanDecode_Grey8bitInterlaced_WithAlpha(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] @@ -202,23 +220,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decoder_CanDecode_CorruptedImages(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] - [WithFile(TestImages.Png.Splash, PixelTypes)] + [WithFile(TestImages.Png.Splash, TestPixelTypes)] public void Decoder_IsNotBoundToSinglePixelType(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] @@ -232,10 +246,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Identify(string imagePath, int expectedPixelSize) { var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel); - } + using var stream = new MemoryStream(testFile.Bytes, false); + Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel); } [Theory] @@ -246,10 +258,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png System.Exception ex = Record.Exception( () => { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); }); Assert.NotNull(ex); Assert.Contains("PNG Image does not contain a data chunk", ex.Message); @@ -264,10 +274,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png System.Exception ex = Record.Exception( () => { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); }); Assert.NotNull(ex); Assert.Contains("Invalid or unsupported bit depth", ex.Message); @@ -282,10 +290,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png System.Exception ex = Record.Exception( () => { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); }); Assert.NotNull(ex); Assert.Contains("Invalid or unsupported color type", ex.Message); @@ -300,11 +306,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png System.Exception ex = Record.Exception( () => { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); }); Assert.Null(ex); } @@ -318,11 +322,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png System.Exception ex = Record.Exception( () => { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); }); Assert.Null(ex); } @@ -336,11 +338,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png System.Exception ex = Record.Exception( () => { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); }); Assert.Null(ex); } @@ -354,15 +354,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png System.Exception ex = Record.Exception( () => { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); - // We don't have another x-plat reference decoder that can be compared for this image. - if (TestEnvironment.IsWindows) - { - image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Instance); - } + // We don't have another x-plat reference decoder that can be compared for this image. + if (TestEnvironment.IsWindows) + { + image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Instance); } }); Assert.Null(ex); @@ -377,11 +375,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png System.Exception ex = Record.Exception( () => { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); }); Assert.Null(ex); } @@ -395,15 +391,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png System.Exception ex = Record.Exception( () => { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); - // We don't have another x-plat reference decoder that can be compared for this image. - if (TestEnvironment.IsWindows) - { - image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Instance); - } + // We don't have another x-plat reference decoder that can be compared for this image. + if (TestEnvironment.IsWindows) + { + image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Instance); } }); Assert.NotNull(ex); diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 50bacfba4..3cc879d6b 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -15,21 +15,20 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Png { - [Collection("RunSerial")] [Trait("Format", "Png")] public partial class PngEncoderTests { - private static PngEncoder PngEncoder => new PngEncoder(); + private static PngEncoder PngEncoder => new(); public static readonly TheoryData PngBitDepthFiles = - new TheoryData + new() { { TestImages.Png.Rgb48Bpp, PngBitDepth.Bit16 }, { TestImages.Png.Bpp1, PngBitDepth.Bit1 } }; public static readonly TheoryData PngTrnsFiles = - new TheoryData + new() { { TestImages.Png.Gray1BitTrans, PngBitDepth.Bit1, PngColorType.Grayscale }, { TestImages.Png.Gray2BitTrans, PngBitDepth.Bit2, PngColorType.Grayscale }, @@ -43,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png /// /// All types except Palette /// - public static readonly TheoryData PngColorTypes = new TheoryData + public static readonly TheoryData PngColorTypes = new() { PngColorType.RgbWithAlpha, PngColorType.Rgb, @@ -51,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png PngColorType.GrayscaleWithAlpha, }; - public static readonly TheoryData PngFilterMethods = new TheoryData + public static readonly TheoryData PngFilterMethods = new() { PngFilterMethod.None, PngFilterMethod.Sub, @@ -65,7 +64,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png /// All types except Palette /// public static readonly TheoryData CompressionLevels - = new TheoryData + = new() { PngCompressionLevel.Level0, PngCompressionLevel.Level1, @@ -79,12 +78,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png PngCompressionLevel.Level9, }; - public static readonly TheoryData PaletteSizes = new TheoryData + public static readonly TheoryData PaletteSizes = new() { 30, 55, 100, 201, 255 }; - public static readonly TheoryData PaletteLargeOnly = new TheoryData + public static readonly TheoryData PaletteLargeOnly = new() { 80, 100, 120, 230 }; @@ -96,7 +95,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png }; public static readonly TheoryData RatioFiles = - new TheoryData + new() { { TestImages.Png.Splash, 11810, 11810, PixelResolutionUnit.PixelsPerMeter }, { TestImages.Png.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, @@ -411,21 +410,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png ColorType = colorType }; Rgba32 rgba32 = Color.Blue; - for (int y = 0; y < image.Height; y++) + image.ProcessPixelRows(accessor => { - System.Span rowSpan = image.GetPixelRowSpan(y); - - // Half of the test image should be transparent. - if (y > 25) + for (int y = 0; y < image.Height; y++) { - rgba32.A = 0; - } + System.Span rowSpan = accessor.GetRowSpan(y); - for (int x = 0; x < image.Width; x++) - { - rowSpan[x].FromRgba32(rgba32); + // Half of the test image should be transparent. + if (y > 25) + { + rgba32.A = 0; + } + + for (int x = 0; x < image.Width; x++) + { + rowSpan[x].FromRgba32(rgba32); + } } - } + }); // act using var memStream = new MemoryStream(); @@ -441,20 +443,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png expectedColor = new Rgba32(luminance, luminance, luminance); } - for (int y = 0; y < actual.Height; y++) + actual.ProcessPixelRows(accessor => { - System.Span rowSpan = actual.GetPixelRowSpan(y); - - if (y > 25) + for (int y = 0; y < accessor.Height; y++) { - expectedColor = Color.Transparent; - } + System.Span rowSpan = accessor.GetRowSpan(y); - for (int x = 0; x < actual.Width; x++) - { - Assert.Equal(expectedColor, rowSpan[x]); + if (y > 25) + { + expectedColor = Color.Transparent; + } + + for (int x = 0; x < accessor.Width; x++) + { + Assert.Equal(expectedColor, rowSpan[x]); + } } - } + }); } [Theory] diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs index 1c53ff6a1..55dc2ecdd 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs @@ -15,11 +15,10 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tga; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Tga { - [Collection("RunSerial")] [Trait("Format", "Tga")] public class TgaDecoderTests { - private static TgaDecoder TgaDecoder => new TgaDecoder(); + private static TgaDecoder TgaDecoder => new(); [Theory] [WithFile(Gray8BitTopLeft, PixelTypes.Rgba32)] @@ -29,7 +28,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -41,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -53,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -65,7 +64,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -77,7 +76,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -89,7 +88,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -101,7 +100,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -113,7 +112,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -245,7 +244,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -257,7 +256,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -269,7 +268,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -281,7 +280,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -293,7 +292,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -305,7 +304,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -317,7 +316,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -329,7 +328,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -341,7 +340,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -353,7 +352,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -365,7 +364,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -377,7 +376,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -389,7 +388,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -401,7 +400,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -413,7 +412,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -425,7 +424,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -437,7 +436,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -449,7 +448,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -461,7 +460,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -473,7 +472,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -485,7 +484,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -497,7 +496,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -509,7 +508,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -521,7 +520,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -533,7 +532,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -545,7 +544,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -557,7 +556,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -569,7 +568,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -581,7 +580,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -593,7 +592,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -605,7 +604,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -617,7 +616,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -629,7 +628,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -641,7 +640,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -653,7 +652,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -665,7 +664,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -677,7 +676,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -689,7 +688,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -701,7 +700,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -714,7 +713,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs index 4c768a1a5..0683f90fd 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs @@ -4,26 +4,25 @@ using System.IO; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Tga; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Tga { - [Collection("RunSerial")] [Trait("Format", "Tga")] public class TgaEncoderTests { public static readonly TheoryData BitsPerPixel = - new TheoryData + new() { TgaBitsPerPixel.Pixel24, TgaBitsPerPixel.Pixel32 }; public static readonly TheoryData TgaBitsPerPixelFiles = - new TheoryData + new() { { Gray8BitBottomLeft, TgaBitsPerPixel.Pixel8 }, { Bit16BottomLeft, TgaBitsPerPixel.Pixel16 }, @@ -150,7 +149,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga memStream.Position = 0; using (var encodedImage = (Image)Image.Load(memStream)) { - TgaTestUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance); + ImageComparingUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance); } } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs index b56c1e7c9..bbca2610e 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression using var stream = new BufferedReadStream(Configuration.Default, memoryStream); byte[] buffer = new byte[expectedResult.Length]; - using var decompressor = new PackBitsTiffCompression(new ArrayPoolMemoryAllocator(), default, default); + using var decompressor = new PackBitsTiffCompression(MemoryAllocator.Create(), default, default); decompressor.Decompress(stream, 0, (uint)inputData.Length, 1, buffer); Assert.Equal(expectedResult, buffer); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 111eaf781..ea0544acf 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -12,7 +12,6 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tiff; namespace SixLabors.ImageSharp.Tests.Formats.Tiff { - [Collection("RunSerial")] [Trait("Format", "Tiff")] public class TiffDecoderTests : TiffDecoderBaseTester { @@ -374,6 +373,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffDecoder_CanDecode_JpegCompressed(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false); + // https://github.com/SixLabors/ImageSharp/issues/1891 + [Theory] + [WithFile(Issues1891, PixelTypes.Rgba32)] + public void TiffDecoder_ThrowsException_WithTooManyDirectories(TestImageProvider provider) + where TPixel : unmanaged, IPixel => Assert.Throws( + () => + { + using (provider.GetImage(TiffDecoder)) + { + } + }); + [Theory] [WithFileCollection(nameof(MultiframeTestImages), PixelTypes.Rgba32)] public void DecodeMultiframe(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs index d05e37e2a..b68670f1f 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Trait("Format", "Tiff")] public class TiffEncoderHeaderTests { - private static readonly MemoryAllocator MemoryAllocator = new ArrayPoolMemoryAllocator(); + private static readonly MemoryAllocator MemoryAllocator = MemoryAllocator.Create(); private static readonly Configuration Configuration = Configuration.Default; private static readonly ITiffEncoderOptions Options = new TiffEncoder(); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index d85ed16a7..aded52cd9 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -11,7 +11,6 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tiff; namespace SixLabors.ImageSharp.Tests.Formats.Tiff { - [Collection("RunSerial")] [Trait("Format", "Tiff")] public class TiffEncoderTests : TiffEncoderBaseTester { diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index cdd9616a7..7715ac3a3 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Collections.Generic; using System.IO; using System.Linq; using SixLabors.ImageSharp.Common.Helpers; @@ -16,11 +17,17 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tiff; namespace SixLabors.ImageSharp.Tests.Formats.Tiff { - [Collection("RunSerial")] [Trait("Format", "Tiff")] public class TiffMetadataTests { - private static TiffDecoder TiffDecoder => new TiffDecoder(); + private static TiffDecoder TiffDecoder => new(); + + private class NumberComparer : IEqualityComparer + { + public bool Equals(Number x, Number y) => x.Equals(y); + + public int GetHashCode(Number obj) => obj.GetHashCode(); + } [Fact] public void TiffMetadata_CloneIsDeep() diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs deleted file mode 100644 index eacadae2b..000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Collections.Generic; -using System.IO; -using ImageMagick; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff -{ - public static class TiffTestUtils - { - public static void CompareWithReferenceDecoder( - string encodedImagePath, - Image image, - bool useExactComparer = true, - float compareTolerance = 0.01f) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel - { - var testFile = TestFile.Create(encodedImagePath); - Image magickImage = DecodeWithMagick(Configuration.Default, new FileInfo(testFile.FullPath)); - if (useExactComparer) - { - ImageComparer.Exact.VerifySimilarity(magickImage, image); - } - else - { - ImageComparer.Tolerant(compareTolerance).VerifySimilarity(magickImage, image); - } - } - - public static Image DecodeWithMagick(Configuration configuration, FileInfo fileInfo) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel - { - using var magickImage = new MagickImage(fileInfo); - magickImage.AutoOrient(); - var result = new Image(configuration, magickImage.Width, magickImage.Height); - - Assert.True(result.TryGetSinglePixelSpan(out Span resultPixels)); - - using IUnsafePixelCollection pixels = magickImage.GetPixelsUnsafe(); - byte[] data = pixels.ToByteArray(PixelMapping.RGBA); - - PixelOperations.Instance.FromRgba32Bytes( - configuration, - data, - resultPixels, - resultPixels.Length); - - return result; - } - } - - internal class NumberComparer : IEqualityComparer - { - public bool Equals(Number x, Number y) => x.Equals(y); - - public int GetHashCode(Number obj) => obj.GetHashCode(); - } -} diff --git a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs index 684d7791b..9c7a2f758 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs @@ -229,7 +229,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void TransformColorInverse_Works() => RunTransformColorInverseTest(); #if SUPPORTS_RUNTIME_INTRINSICS - [Fact] public void CombinedShannonEntropy_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCombinedShannonEntropyTest, HwIntrinsics.AllowAll); diff --git a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs index 907b18300..cc5f1b4c2 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs @@ -11,8 +11,192 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Trait("Format", "Webp")] public class LossyUtilsTests { + private static void RunTransformTwoTest() + { + // arrange + short[] src = { 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 23, 0, 0, -23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + byte[] dst = + { + 103, 103, 103, 103, 103, 103, 103, 103, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, + 171, 171, 171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, + 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0, + 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 0, 0, 0, 0, 169, 169, 169, 169, + 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 103, 103, 103, 103, + 103, 103, 103, 103, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, 171, 171, + 0, 0, 0, 0, 0, 0, 0, 0 + }; + byte[] expected = + { + 105, 105, 105, 105, 105, 103, 100, 98, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, + 171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 105, 105, 105, 105, 108, 105, 102, 100, 0, 0, 0, 0, 169, + 169, 169, 169, 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 105, 105, + 105, 105, 111, 109, 106, 103, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, 171, + 171, 0, 0, 0, 0, 103, 103, 103, 103, 105, 105, 105, 105, 113, 111, 108, 106, 0, 0, 0, 0, 169, 169, + 169, 169, 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0, 0, 0, 0, 0 + }; + int[] scratch = new int[16]; + + // act + LossyUtils.TransformTwo(src, dst, scratch); + + // assert + Assert.True(expected.SequenceEqual(dst)); + } + + private static void RunTransformOneTest() + { + // arrange + short[] src = { -176, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + byte[] dst = + { + 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, + 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, + 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, + 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, + 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, + 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, + 0, 0, 0, 0, 0, 0, 0, 129 + }; + byte[] expected = + { + 111, 111, 111, 111, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, + 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 108, 108, 108, 108, 128, 128, 128, 128, + 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, + 0, 0, 0, 129, 104, 104, 104, 104, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, + 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 101, 101, 101, 101, + 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, + 0, 0, 0, 0, 0, 0, 0, 129 + }; + int[] scratch = new int[16]; + + // act + LossyUtils.TransformOne(src, dst, scratch); + + // assert + Assert.True(expected.SequenceEqual(dst)); + } + + private static void RunVp8Sse16X16Test() + { + // arrange + byte[] a = + { + 154, 154, 151, 151, 149, 148, 151, 157, 163, 163, 154, 132, 102, 98, 104, 108, 107, 104, 104, 103, + 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, 147, 147, 146, 159, + 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, 172, 172, 172, 168, + 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, 93, 90, 102, 107, + 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, 150, 149, 152, 151, + 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, 102, 102, 121, 117, + 170, 170, 169, 171, 171, 179, 173, 175, 149, 151, 152, 151, 148, 154, 162, 157, 154, 154, 151, 132, + 92, 89, 101, 108, 104, 102, 101, 101, 103, 103, 123, 118, 171, 168, 177, 173, 171, 178, 172, 176, + 152, 152, 152, 151, 154, 162, 161, 155, 149, 157, 156, 129, 92, 87, 101, 107, 102, 100, 107, 100, + 101, 102, 123, 118, 170, 175, 182, 172, 171, 179, 173, 175, 152, 151, 154, 155, 160, 162, 161, 153, + 150, 156, 153, 129, 92, 91, 102, 106, 100, 109, 115, 99, 101, 102, 124, 120, 171, 179, 178, 172, + 171, 181, 171, 173, 154, 154, 154, 162, 160, 158, 156, 152, 153, 157, 151, 128, 86, 86, 102, 105, + 102, 122, 114, 99, 101, 102, 125, 120, 178, 173, 177, 172, 171, 180, 172, 173, 154, 152, 158, 163, + 150, 148, 148, 156, 151, 158, 152, 129, 87, 87, 101, 105, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 154, 151, 165, 156, 141, 137, 146, 158, 152, 159, 152, 133, + 90, 88, 99, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 154, 160, 164, 150, 126, 127, 149, 159, 155, 161, 153, 131, 84, 86, 97, 103, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 157, 167, 157, 137, 102, 128, 155, 161, + 157, 159, 154, 134, 84, 82, 97, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 163, 163, 150, 113, 78, 132, 156, 162, 159, 160, 154, 132, 83, 78, 91, 97, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 163, 157, 137, 80, 78, + 131, 154, 163, 157, 159, 149, 131, 82, 77, 94, 100, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 159, 151, 108, 72, 88, 132, 156, 162, 159, 157, 151, 130, 79, 78, + 95, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 151, 130, + 82, 82, 89, 134, 154, 161, 161, 157, 152, 129, 81, 77, 95, 102, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204 + }; + + byte[] b = + { + 150, 150, 150, 150, 146, 149, 152, 154, 164, 166, 154, 132, 99, 92, 106, 112, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 150, 150, 150, 150, 146, 149, 152, 154, + 161, 164, 151, 130, 93, 86, 100, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 150, 150, 150, 150, 146, 149, 152, 154, 158, 161, 148, 127, 93, 86, 100, 106, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 150, 150, 150, 150, + 146, 149, 152, 154, 156, 159, 146, 125, 99, 92, 106, 112, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 148, 148, 148, 148, 149, 158, 162, 159, 155, 155, 153, 129, + 94, 87, 101, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 151, 151, 151, 151, 152, 159, 161, 156, 155, 155, 153, 129, 94, 87, 101, 106, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 154, 154, 154, 154, 156, 161, 159, 152, + 155, 155, 153, 129, 94, 87, 101, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 156, 156, 156, 156, 159, 162, 158, 149, 155, 155, 153, 129, 94, 87, 101, 106, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 152, 153, 157, 162, + 150, 149, 149, 151, 155, 160, 150, 131, 91, 90, 104, 104, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 152, 156, 158, 157, 140, 137, 145, 159, 155, 160, 150, 131, + 89, 88, 102, 101, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 153, 161, 160, 149, 118, 128, 147, 162, 155, 160, 150, 131, 86, 85, 99, 98, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 154, 165, 161, 144, 96, 128, 154, 159, 155, + 160, 150, 131, 83, 82, 97, 96, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 161, 160, 149, 105, 78, 127, 156, 170, 156, 156, 154, 130, 81, 77, 95, 102, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 160, 160, 133, 85, 81, 129, 155, + 167, 156, 156, 154, 130, 81, 77, 95, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 156, 147, 109, 76, 85, 130, 153, 163, 156, 156, 154, 130, 81, 77, 95, 102, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 152, 128, 87, 83, + 88, 132, 152, 159, 156, 156, 154, 130, 81, 77, 95, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204 + }; + + int expected = 2063; + + // act + int actual = LossyUtils.Vp8_Sse16X16(a, b); + + // assert + Assert.Equal(expected, actual); + } + + private static void RunVp8Sse16X8Test() + { + // arrange + byte[] a = + { + 107, 104, 104, 103, 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, + 147, 147, 146, 159, 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, + 172, 172, 172, 168, 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, + 93, 90, 102, 107, 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, + 150, 149, 152, 151, 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, + 102, 102, 121, 117, 170, 170, 169, 171, 171, 179, 173, 175, 149, 151, 152, 151, 148, 154, 162, 157, + 154, 154, 151, 132, 92, 89, 101, 108, 104, 102, 101, 101, 103, 103, 123, 118, 171, 168, 177, 173, + 171, 178, 172, 176, 152, 152, 152, 151, 154, 162, 161, 155, 149, 157, 156, 129, 92, 87, 101, 107, + 102, 100, 107, 100, 101, 102, 123, 118, 170, 175, 182, 172, 171, 179, 173, 175, 152, 151, 154, 155, + 160, 162, 161, 153, 150, 156, 153, 129, 92, 91, 102, 106, 100, 109, 115, 99, 101, 102, 124, 120, + 171, 179, 178, 172, 171, 181, 171, 173, 154, 154, 154, 162, 160, 158, 156, 152, 153, 157, 151, 128, + 86, 86, 102, 105, 102, 122, 114, 99, 101, 102, 125, 120, 178, 173, 177, 172, 171, 180, 172, 173, + 154, 152, 158, 163, 150, 148, 148, 156, 151, 158, 152, 129, 87, 87, 101, 105 + }; + + byte[] b = + { + 103, 103, 103, 103, 101, 106, 122, 114, 171, 171, 171, 171, 171, 177, 169, 175, 150, 150, 150, 150, + 146, 149, 152, 154, 161, 164, 151, 130, 93, 86, 100, 106, 103, 103, 103, 103, 101, 106, 122, 114, + 171, 171, 171, 171, 171, 177, 169, 175, 150, 150, 150, 150, 146, 149, 152, 154, 158, 161, 148, 127, + 93, 86, 100, 106, 103, 103, 103, 103, 101, 106, 122, 114, 171, 171, 171, 171, 171, 177, 169, 175, + 150, 150, 150, 150, 146, 149, 152, 154, 156, 159, 146, 125, 99, 92, 106, 112, 103, 103, 103, 103, + 101, 106, 122, 114, 171, 171, 171, 171, 171, 177, 169, 175, 148, 148, 148, 148, 149, 158, 162, 159, + 155, 155, 153, 129, 94, 87, 101, 106, 102, 100, 100, 102, 100, 101, 120, 122, 170, 176, 176, 170, + 174, 180, 171, 177, 151, 151, 151, 151, 152, 159, 161, 156, 155, 155, 153, 129, 94, 87, 101, 106, + 102, 105, 105, 102, 100, 101, 120, 122, 170, 176, 176, 170, 174, 180, 171, 177, 154, 154, 154, 154, + 156, 161, 159, 152, 155, 155, 153, 129, 94, 87, 101, 106, 102, 112, 112, 102, 100, 101, 120, 122, + 170, 176, 176, 170, 174, 180, 171, 177, 156, 156, 156, 156, 159, 162, 158, 149, 155, 155, 153, 129, + 94, 87, 101, 106, 102, 117, 117, 102, 100, 101, 120, 122, 170, 176, 176, 170, 174, 180, 171, 177, + 152, 153, 157, 162, 150, 149, 149, 151, 155, 160, 150, 131, 91, 90, 104, 104 + }; + + int expected = 749; + + // act + int actual = LossyUtils.Vp8_Sse16X8(a, b); + + // assert + Assert.Equal(expected, actual); + } + private static void RunVp8Sse4X4Test() { + // arrange byte[] a = { 27, 27, 28, 29, 29, 28, 27, 27, 27, 28, 28, 29, 29, 28, 28, 27, 129, 129, 129, 129, 129, 129, 129, @@ -35,8 +219,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp int expected = 27; + // act int actual = LossyUtils.Vp8_Sse4X4(a, b); + // assert Assert.Equal(expected, actual); } @@ -65,6 +251,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp private static void RunHadamardTransformTest() { + // arrange byte[] a = { 27, 27, 28, 29, 29, 28, 27, 27, 27, 28, 28, 29, 29, 28, 28, 27, 129, 129, 129, 129, 129, 129, 129, @@ -86,10 +273,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp ushort[] w = { 38, 32, 20, 9, 32, 28, 17, 7, 20, 17, 10, 4, 9, 7, 4, 2 }; int expected = 2; + // act int actual = LossyUtils.Vp8Disto4X4(a, b, w, new int[16]); + + // assert Assert.Equal(expected, actual); } + [Fact] + public void RunTransformTwo_Works() => RunTransformTwoTest(); + + [Fact] + public void RunTransformOne_Works() => RunTransformOneTest(); + + [Fact] + public void Vp8Sse16X16_Works() => RunVp8Sse16X16Test(); + + [Fact] + public void Vp8Sse16X8_Works() => RunVp8Sse16X8Test(); + [Fact] public void Vp8Sse4X4_Works() => RunVp8Sse4X4Test(); @@ -100,11 +302,44 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void HadamardTransform_Works() => RunHadamardTransformTest(); #if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void TransformTwo_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformTwoTest, HwIntrinsics.AllowAll); + + [Fact] + public void TransformTwo_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformTwoTest, HwIntrinsics.DisableHWIntrinsic); + + [Fact] + public void TransformOne_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformOneTest, HwIntrinsics.AllowAll); + + [Fact] + public void TransformOne_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformOneTest, HwIntrinsics.DisableHWIntrinsic); + + [Fact] + public void Vp8Sse16X16_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X16Test, HwIntrinsics.AllowAll); + + [Fact] + public void Vp8Sse16X16_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X16Test, HwIntrinsics.DisableSSE2); + + [Fact] + public void Vp8Sse16X16_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X16Test, HwIntrinsics.DisableAVX2); + + [Fact] + public void Vp8Sse16X8_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X8Test, HwIntrinsics.AllowAll); + + [Fact] + public void Vp8Sse16X8_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X8Test, HwIntrinsics.DisableSSE2); + + [Fact] + public void Vp8Sse16X8_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X8Test, HwIntrinsics.DisableAVX2); + [Fact] public void Vp8Sse4X4_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.AllowAll); [Fact] - public void Vp8Sse4X4_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.DisableHWIntrinsic); + public void Vp8Sse4X4_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.DisableSSE2); + + [Fact] + public void Vp8Sse4X4_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.DisableAVX2); [Fact] public void Mean16x4_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunMean16x4Test, HwIntrinsics.AllowAll); diff --git a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs index 98c144a90..33d49a97d 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs @@ -139,15 +139,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp where TPixel : unmanaged, IPixel { uint[] bgra = new uint[image.Width * image.Height]; - int idx = 0; - for (int y = 0; y < image.Height; y++) + image.ProcessPixelRows(accessor => { - Span rowSpan = image.GetPixelRowSpan(y); - for (int x = 0; x < rowSpan.Length; x++) + int idx = 0; + for (int y = 0; y < accessor.Height; y++) { - bgra[idx++] = ToBgra32(rowSpan[x]).PackedValue; + Span rowSpan = accessor.GetRowSpan(y); + for (int x = 0; x < rowSpan.Length; x++) + { + bgra[idx++] = ToBgra32(rowSpan[x]).PackedValue; + } } - } + }); return bgra; } diff --git a/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs b/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs index 80b5f0a53..ef60a7b20 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs @@ -47,7 +47,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void QuantizeBlock_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.AllowAll); [Fact] - public void QuantizeBlock_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableHWIntrinsic); + public void QuantizeBlock_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableSSE2); + + [Fact] + public void QuantizeBlock_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableAVX2); #endif } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs index 6bcb4f21f..2a43cb38b 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs @@ -11,6 +11,57 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Trait("Format", "Webp")] public class Vp8EncodingTests { + private static void RunFTransform2Test() + { + // arrange + byte[] src = { 154, 154, 151, 151, 149, 148, 151, 157, 163, 163, 154, 132, 102, 98, 104, 108, 107, 104, 104, 103, 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, 147, 147, 146, 159, 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, 172, 172, 172, 168, 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, 93, 90, 102, 107, 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, 150, 149, 152, 151, 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, 102, 102, 121, 117, 170, 170, 169, 171, 171, 179, 173, 175 }; + byte[] reference = { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129 }; + short[] actualOutput1 = new short[16]; + short[] actualOutput2 = new short[16]; + short[] expectedOutput1 = { 182, 4, 1, 1, 6, 7, -1, -4, 5, 0, -2, 1, 2, 1, 1, 1 }; + short[] expectedOutput2 = { 192, -34, 10, 1, -11, 8, 10, -7, 6, 3, -8, 4, 5, -3, -2, 6 }; + + // act + Vp8Encoding.FTransform2(src, reference, actualOutput1, actualOutput2, new int[16]); + + // assert + Assert.True(expectedOutput1.SequenceEqual(actualOutput1)); + Assert.True(expectedOutput2.SequenceEqual(actualOutput2)); + } + + private static void RunFTransformTest() + { + // arrange + byte[] src = + { + 154, 154, 151, 151, 149, 148, 151, 157, 163, 163, 154, 132, 102, 98, 104, 108, 107, 104, 104, 103, + 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, 147, 147, 146, 159, + 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, 172, 172, 172, 168, + 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, 93, 90, 102, 107, + 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, 150, 149, 152, 151, + 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, 102, 102, 121, 117, + 170, 170, 169, 171, 171, 179, 173, 175 + }; + byte[] reference = + { + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129 + }; + short[] actualOutput = new short[16]; + short[] expectedOutput = { 182, 4, 1, 1, 6, 7, -1, -4, 5, 0, -2, 1, 2, 1, 1, 1 }; + + // act + Vp8Encoding.FTransform(src, reference, actualOutput, new int[16]); + + // assert + Assert.True(expectedOutput.SequenceEqual(actualOutput)); + } + private static void RunOneInverseTransformTest() { // arrange @@ -69,12 +120,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp int[] scratch = new int[16]; // act - Vp8Encoding.ITransform(reference, input, dst, scratch); + Vp8Encoding.ITransformTwo(reference, input, dst, scratch); // assert Assert.True(dst.SequenceEqual(expected)); } + [Fact] + public void FTransform2_Works() => RunFTransform2Test(); + + [Fact] + public void FTransform_Works() => RunFTransformTest(); + [Fact] public void OneInverseTransform_Works() => RunOneInverseTransformTest(); @@ -82,6 +139,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void TwoInverseTransform_Works() => RunTwoInverseTransformTest(); #if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void FTransform2_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunFTransform2Test, HwIntrinsics.AllowAll); + + [Fact] + public void FTransform2_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunFTransform2Test, HwIntrinsics.DisableHWIntrinsic); + + [Fact] + public void FTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunFTransformTest, HwIntrinsics.AllowAll); + + [Fact] + public void FTransform_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunFTransformTest, HwIntrinsics.DisableHWIntrinsic); + [Fact] public void OneInverseTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunOneInverseTransformTest, HwIntrinsics.AllowAll); diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs index 4ff42f4ee..693626755 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using SixLabors.ImageSharp.Formats.Webp.Lossy; +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Webp @@ -67,6 +68,108 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp } } + private static void RunCollectHistogramTest() + { + // arrange + var histogram = new Vp8Histogram(); + + byte[] reference = + { + 154, 154, 151, 151, 149, 148, 151, 157, 163, 163, 154, 132, 102, 98, 104, 108, 107, 104, 104, 103, + 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, 147, 147, 146, 159, + 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, 172, 172, 172, 168, + 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, 93, 90, 102, 107, + 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, 150, 149, 152, 151, + 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, 102, 102, 121, 117, + 170, 170, 169, 171, 171, 179, 173, 175, 149, 151, 152, 151, 148, 154, 162, 157, 154, 154, 151, 132, + 92, 89, 101, 108, 104, 102, 101, 101, 103, 103, 123, 118, 171, 168, 177, 173, 171, 178, 172, 176, + 152, 152, 152, 151, 154, 162, 161, 155, 149, 157, 156, 129, 92, 87, 101, 107, 102, 100, 107, 100, + 101, 102, 123, 118, 170, 175, 182, 172, 171, 179, 173, 175, 152, 151, 154, 155, 160, 162, 161, 153, + 150, 156, 153, 129, 92, 91, 102, 106, 100, 109, 115, 99, 101, 102, 124, 120, 171, 179, 178, 172, + 171, 181, 171, 173, 154, 154, 154, 162, 160, 158, 156, 152, 153, 157, 151, 128, 86, 86, 102, 105, + 102, 122, 114, 99, 101, 102, 125, 120, 178, 173, 177, 172, 171, 180, 172, 173, 154, 152, 158, 163, + 150, 148, 148, 156, 151, 158, 152, 129, 87, 87, 101, 105, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 154, 151, 165, 156, 141, 137, 146, 158, 152, 159, 152, 133, + 90, 88, 99, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 154, 160, 164, 150, 126, 127, 149, 159, 155, 161, 153, 131, 84, 86, 97, 103, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 157, 167, 157, 137, 102, 128, 155, 161, + 157, 159, 154, 134, 84, 82, 97, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 163, 163, 150, 113, 78, 132, 156, 162, 159, 160, 154, 132, 83, 78, 91, 97, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 163, 157, 137, 80, 78, + 131, 154, 163, 157, 159, 149, 131, 82, 77, 94, 100, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 159, 151, 108, 72, 88, 132, 156, 162, 159, 157, 151, 130, 79, 78, + 95, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 151, 130, + 82, 82, 89, 134, 154, 161, 161, 157, 152, 129, 81, 77, 95, 102, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204 + }; + byte[] pred = + { + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129 + }; + int expectedAlpha = 146; + + // act + histogram.CollectHistogram(reference, pred, 0, 10); + int actualAlpha = histogram.GetAlpha(); + + // assert + Assert.Equal(expectedAlpha, actualAlpha); + } + + [Fact] + public void RunCollectHistogramTest_Works() => RunCollectHistogramTest(); + [Fact] public void GetAlpha_WithEmptyHistogram_Works() { @@ -111,5 +214,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp // assert Assert.Equal(1054, alpha); } + +#if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void CollectHistogramTest_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectHistogramTest, HwIntrinsics.AllowAll); + + [Fact] + public void CollectHistogramTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectHistogramTest, HwIntrinsics.DisableHWIntrinsic); +#endif } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index 34fa72c63..22342e612 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -4,6 +4,7 @@ using System.IO; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Webp; @@ -11,7 +12,6 @@ using static SixLabors.ImageSharp.Tests.TestImages.Webp; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Webp { - [Collection("RunSerial")] [Trait("Format", "Webp")] public class WebpDecoderTests { @@ -19,6 +19,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp private static MagickReferenceDecoder ReferenceDecoder => new(); + private static string TestImageLossySimpleFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.SimpleFilter02); + + private static string TestImageLossyComplexFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.BikeComplexFilter); + [Theory] [InlineData(Lossless.GreenTransform1, 1000, 307, 32)] [InlineData(Lossless.BikeThreeTransforms, 250, 195, 32)] @@ -359,5 +363,33 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { } }); + +#if SUPPORTS_RUNTIME_INTRINSICS + private static void RunDecodeLossyWithSimpleFilterTest() + { + var provider = TestImageProvider.File(TestImageLossySimpleFilterPath); + using (Image image = provider.GetImage(WebpDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + } + + private static void RunDecodeLossyWithComplexFilterTest() + { + var provider = TestImageProvider.File(TestImageLossyComplexFilterPath); + using (Image image = provider.GetImage(WebpDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + } + + [Fact] + public void DecodeLossyWithSimpleFilterTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunDecodeLossyWithSimpleFilterTest, HwIntrinsics.DisableHWIntrinsic); + + [Fact] + public void DecodeLossyWithComplexFilterTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunDecodeLossyWithComplexFilterTest, HwIntrinsics.DisableHWIntrinsic); +#endif } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 70cc487bf..607d4c764 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -11,7 +11,6 @@ using static SixLabors.ImageSharp.Tests.TestImages.Webp; namespace SixLabors.ImageSharp.Tests.Formats.Webp { - [Collection("RunSerial")] [Trait("Format", "Webp")] public class WebpEncoderTests { @@ -101,6 +100,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); } + [Theory] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 15114)] + public void Encode_Lossless_WithBestQuality_HasExpectedSize(TestImageProvider provider, int expectedBytes) + where TPixel : unmanaged, IPixel + { + var encoder = new WebpEncoder() + { + FileFormat = WebpFileFormatType.Lossless, + Method = WebpEncodingMethod.BestQuality + }; + + using Image image = provider.GetImage(); + using var memoryStream = new MemoryStream(); + image.Save(memoryStream, encoder); + + Assert.Equal(memoryStream.Length, expectedBytes); + } + [Theory] [WithFile(RgbTestPattern100x100, PixelTypes.Rgba32, 85)] [WithFile(RgbTestPattern100x100, PixelTypes.Rgba32, 60)] diff --git a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs index 5cf5db523..f46c9519c 100644 --- a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs @@ -360,7 +360,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers in operation); // Assert: - TestImageExtensions.CompareBuffers(expected.DangerousGetSingleSpan(), actual.DangerousGetSingleSpan()); + TestImageExtensions.CompareBuffers(expected, actual); } } diff --git a/tests/ImageSharp.Tests/Image/ImageCloneTests.cs b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs index 5efbe2cba..9d978ee52 100644 --- a/tests/ImageSharp.Tests/Image/ImageCloneTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs @@ -32,15 +32,17 @@ namespace SixLabors.ImageSharp.Tests [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] public void CloneAs_ToBgra32(TestImageProvider provider) { - using (Image image = provider.GetImage()) - using (Image clone = image.CloneAs()) + using Image image = provider.GetImage(); + using Image clone = image.CloneAs(); + + image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => { - for (int y = 0; y < image.Height; y++) + for (int y = 0; y < imageAccessor.Height; y++) { - Span row = image.GetPixelRowSpan(y); - Span rowClone = clone.GetPixelRowSpan(y); + Span row = imageAccessor.GetRowSpan(y); + Span rowClone = cloneAccessor.GetRowSpan(y); - for (int x = 0; x < image.Width; x++) + for (int x = 0; x < imageAccessor.Width; x++) { Rgba32 expected = row[x]; Bgra32 actual = rowClone[x]; @@ -51,22 +53,51 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(expected.A, actual.A); } } - } + }); + } + + [Theory] + [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] + public void CloneAs_ToAbgr32(TestImageProvider provider) + { + using Image image = provider.GetImage(); + using Image clone = image.CloneAs(); + + image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => + { + for (int y = 0; y < imageAccessor.Height; y++) + { + Span row = imageAccessor.GetRowSpan(y); + Span rowClone = cloneAccessor.GetRowSpan(y); + + for (int x = 0; x < cloneAccessor.Width; x++) + { + Rgba32 expected = row[x]; + Abgr32 actual = rowClone[x]; + + Assert.Equal(expected.R, actual.R); + Assert.Equal(expected.G, actual.G); + Assert.Equal(expected.B, actual.B); + } + } + }); } [Theory] [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] public void CloneAs_ToBgr24(TestImageProvider provider) { - using (Image image = provider.GetImage()) - using (Image clone = image.CloneAs()) + using Image image = provider.GetImage(); + using Image clone = image.CloneAs(); + + image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => { - for (int y = 0; y < image.Height; y++) + for (int y = 0; y < imageAccessor.Height; y++) { - Span row = image.GetPixelRowSpan(y); - Span rowClone = clone.GetPixelRowSpan(y); + Span row = imageAccessor.GetRowSpan(y); + Span rowClone = cloneAccessor.GetRowSpan(y); - for (int x = 0; x < image.Width; x++) + for (int x = 0; x < cloneAccessor.Width; x++) { Rgba32 expected = row[x]; Bgr24 actual = rowClone[x]; @@ -76,22 +107,23 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(expected.B, actual.B); } } - } + }); } [Theory] [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] public void CloneAs_ToArgb32(TestImageProvider provider) { - using (Image image = provider.GetImage()) - using (Image clone = image.CloneAs()) + using Image image = provider.GetImage(); + using Image clone = image.CloneAs(); + image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => { - for (int y = 0; y < image.Height; y++) + for (int y = 0; y < imageAccessor.Height; y++) { - Span row = image.GetPixelRowSpan(y); - Span rowClone = clone.GetPixelRowSpan(y); + Span row = imageAccessor.GetRowSpan(y); + Span rowClone = cloneAccessor.GetRowSpan(y); - for (int x = 0; x < image.Width; x++) + for (int x = 0; x < cloneAccessor.Width; x++) { Rgba32 expected = row[x]; Argb32 actual = rowClone[x]; @@ -102,22 +134,23 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(expected.A, actual.A); } } - } + }); } [Theory] [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] public void CloneAs_ToRgb24(TestImageProvider provider) { - using (Image image = provider.GetImage()) - using (Image clone = image.CloneAs()) + using Image image = provider.GetImage(); + using Image clone = image.CloneAs(); + image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => { - for (int y = 0; y < image.Height; y++) + for (int y = 0; y < imageAccessor.Height; y++) { - Span row = image.GetPixelRowSpan(y); - Span rowClone = clone.GetPixelRowSpan(y); + Span row = imageAccessor.GetRowSpan(y); + Span rowClone = cloneAccessor.GetRowSpan(y); - for (int x = 0; x < image.Width; x++) + for (int x = 0; x < imageAccessor.Width; x++) { Rgba32 expected = row[x]; Rgb24 actual = rowClone[x]; @@ -127,7 +160,7 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(expected.B, actual.B); } } - } + }); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs index dd8275ee8..9ed276ebc 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs @@ -2,10 +2,11 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Collections.Generic; using System.Linq; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Tests.Memory; using Xunit; namespace SixLabors.ImageSharp.Tests @@ -210,9 +211,9 @@ namespace SixLabors.ImageSharp.Tests using (Image cloned = img.Frames.CloneFrame(0)) { Assert.Equal(2, img.Frames.Count); - Assert.True(img.TryGetSinglePixelSpan(out Span imgSpan)); + Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory imgMem)); - cloned.ComparePixelBufferTo(imgSpan); + cloned.ComparePixelBufferTo(imgMem); } } } @@ -224,15 +225,15 @@ namespace SixLabors.ImageSharp.Tests { using (Image img = provider.GetImage()) { - Assert.True(img.TryGetSinglePixelSpan(out Span imgSpan)); - TPixel[] sourcePixelData = imgSpan.ToArray(); + Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory imgMemory)); + TPixel[] sourcePixelData = imgMemory.ToArray(); using var imageFrame = new ImageFrame(Configuration.Default, 10, 10); using ImageFrame addedFrame = img.Frames.AddFrame(imageFrame); using (Image cloned = img.Frames.ExportFrame(0)) { Assert.Equal(1, img.Frames.Count); - cloned.ComparePixelBufferTo(sourcePixelData); + cloned.ComparePixelBufferTo(sourcePixelData.AsSpan()); } } } @@ -260,8 +261,8 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void AddFrameFromPixelData() { - Assert.True(this.Image.Frames.RootFrame.TryGetSinglePixelSpan(out Span imgSpan)); - Rgba32[] pixelData = imgSpan.ToArray(); + Assert.True(this.Image.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out Memory imgMem)); + Rgba32[] pixelData = imgMem.ToArray(); using ImageFrame addedFrame = this.Image.Frames.AddFrame(pixelData); Assert.Equal(2, this.Image.Frames.Count); } @@ -272,8 +273,8 @@ namespace SixLabors.ImageSharp.Tests using var otherFrame = new ImageFrame(Configuration.Default, 10, 10); using ImageFrame addedFrame = this.Image.Frames.AddFrame(otherFrame); - Assert.True(otherFrame.TryGetSinglePixelSpan(out Span otherFrameSpan)); - addedFrame.ComparePixelBufferTo(otherFrameSpan); + Assert.True(otherFrame.DangerousTryGetSinglePixelMemory(out Memory otherFrameMem)); + addedFrame.ComparePixelBufferTo(otherFrameMem.Span); Assert.NotEqual(otherFrame, addedFrame); } @@ -283,8 +284,8 @@ namespace SixLabors.ImageSharp.Tests using var otherFrame = new ImageFrame(Configuration.Default, 10, 10); using ImageFrame addedFrame = this.Image.Frames.InsertFrame(0, otherFrame); - Assert.True(otherFrame.TryGetSinglePixelSpan(out Span otherFrameSpan)); - addedFrame.ComparePixelBufferTo(otherFrameSpan); + Assert.True(otherFrame.DangerousTryGetSinglePixelMemory(out Memory otherFrameMem)); + addedFrame.ComparePixelBufferTo(otherFrameMem.Span); Assert.NotEqual(otherFrame, addedFrame); } @@ -339,6 +340,26 @@ namespace SixLabors.ImageSharp.Tests Assert.False(this.Image.Frames.Contains(frame)); } + [Fact] + public void PreferContiguousImageBuffers_True_AppliedToAllFrames() + { + Configuration configuration = Configuration.Default.Clone(); + configuration.MemoryAllocator = new TestMemoryAllocator { BufferCapacityInBytes = 1000 }; + configuration.PreferContiguousImageBuffers = true; + + using var image = new Image(configuration, 100, 100); + image.Frames.CreateFrame(); + image.Frames.InsertFrame(0, image.Frames[0]); + image.Frames.CreateFrame(Color.Red); + + Assert.Equal(4, image.Frames.Count); + IEnumerable> frames = image.Frames; + foreach (ImageFrame frame in frames) + { + Assert.True(frame.DangerousTryGetSinglePixelMemory(out Memory _)); + } + } + [Fact] public void DisposeCall_NoThrowIfCalledMultiple() { diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs index b65615121..843546439 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs @@ -158,8 +158,8 @@ namespace SixLabors.ImageSharp.Tests var expectedClone = (Image)cloned; - Assert.True(img.TryGetSinglePixelSpan(out Span imgSpan)); - expectedClone.ComparePixelBufferTo(imgSpan); + Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory imgMem)); + expectedClone.ComparePixelBufferTo(imgMem); } } } @@ -171,8 +171,8 @@ namespace SixLabors.ImageSharp.Tests { using (Image img = provider.GetImage()) { - Assert.True(img.TryGetSinglePixelSpan(out Span imgSpan)); - var sourcePixelData = imgSpan.ToArray(); + Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory imgMem)); + TPixel[] sourcePixelData = imgMem.ToArray(); ImageFrameCollection nonGenericFrameCollection = img.Frames; @@ -182,7 +182,7 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(1, img.Frames.Count); var expectedClone = (Image)cloned; - expectedClone.ComparePixelBufferTo(sourcePixelData); + expectedClone.ComparePixelBufferTo(sourcePixelData.AsSpan()); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageFrameTests.cs b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs index 766bbd138..4d01fd754 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs @@ -2,8 +2,11 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.Memory; using Xunit; namespace SixLabors.ImageSharp.Tests @@ -16,9 +19,9 @@ namespace SixLabors.ImageSharp.Tests private void LimitBufferCapacity(int bufferCapacityInBytes) { - var allocator = ArrayPoolMemoryAllocator.CreateDefault(); - this.configuration.MemoryAllocator = allocator; + var allocator = new TestMemoryAllocator(); allocator.BufferCapacityInBytes = bufferCapacityInBytes; + this.configuration.MemoryAllocator = allocator; } [Theory] @@ -92,6 +95,99 @@ namespace SixLabors.ImageSharp.Tests ArgumentOutOfRangeException ex = Assert.Throws(() => frame[3, y] = default); Assert.Equal("y", ex.ParamName); } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void CopyPixelDataTo_Success(bool disco, bool byteSpan) + { + if (disco) + { + this.LimitBufferCapacity(20); + } + + using var image = new Image(this.configuration, 10, 10); + if (disco) + { + Assert.True(image.GetPixelMemoryGroup().Count > 1); + } + + byte[] expected = TestUtils.FillImageWithRandomBytes(image); + byte[] actual = new byte[expected.Length]; + if (byteSpan) + { + image.Frames.RootFrame.CopyPixelDataTo(actual); + } + else + { + Span destination = MemoryMarshal.Cast(actual); + image.Frames.RootFrame.CopyPixelDataTo(destination); + } + + Assert.True(expected.AsSpan().SequenceEqual(actual)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CopyPixelDataTo_DestinationTooShort_Throws(bool byteSpan) + { + using var image = new Image(this.configuration, 10, 10); + + Assert.ThrowsAny(() => + { + if (byteSpan) + { + image.Frames.RootFrame.CopyPixelDataTo(new byte[199]); + } + else + { + image.Frames.RootFrame.CopyPixelDataTo(new La16[99]); + } + }); + } + } + + public class ProcessPixelRows : ProcessPixelRowsTestBase + { + protected override void ProcessPixelRowsImpl( + Image image, + PixelAccessorAction processPixels) => + image.Frames.RootFrame.ProcessPixelRows(processPixels); + + protected override void ProcessPixelRowsImpl( + Image image1, + Image image2, + PixelAccessorAction processPixels) => + image1.Frames.RootFrame.ProcessPixelRows(image2.Frames.RootFrame, processPixels); + + protected override void ProcessPixelRowsImpl( + Image image1, + Image image2, + Image image3, + PixelAccessorAction processPixels) => + image1.Frames.RootFrame.ProcessPixelRows( + image2.Frames.RootFrame, + image3.Frames.RootFrame, + processPixels); + + [Fact] + public void NullReference_Throws() + { + using var img = new Image(1, 1); + ImageFrame frame = img.Frames.RootFrame; + + Assert.Throws(() => frame.ProcessPixelRows(null)); + + Assert.Throws(() => frame.ProcessPixelRows((ImageFrame)null, (_, _) => { })); + Assert.Throws(() => frame.ProcessPixelRows(frame, frame, null)); + + Assert.Throws(() => frame.ProcessPixelRows((ImageFrame)null, frame, (_, _, _) => { })); + Assert.Throws(() => frame.ProcessPixelRows(frame, (ImageFrame)null, (_, _, _) => { })); + Assert.Throws(() => frame.ProcessPixelRows(frame, frame, null)); + } } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs index 8bb121349..f21f2c916 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs @@ -71,6 +71,7 @@ namespace SixLabors.ImageSharp.Tests } [Theory] + [InlineData("test.pbm", "image/x-portable-pixmap")] [InlineData("test.png", "image/png")] [InlineData("test.tga", "image/tga")] [InlineData("test.bmp", "image/bmp")] @@ -114,6 +115,7 @@ namespace SixLabors.ImageSharp.Tests } [Theory] + [InlineData("test.pbm")] [InlineData("test.png")] [InlineData("test.tga")] [InlineData("test.bmp")] diff --git a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs index 7fae29a85..ec9e3450f 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs @@ -5,12 +5,15 @@ using System; using System.Buffers; using System.Drawing; using System.Drawing.Imaging; +using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using Xunit; // ReSharper disable InconsistentNaming @@ -71,7 +74,7 @@ namespace SixLabors.ImageSharp.Tests public override unsafe MemoryHandle Pin(int elementIndex = 0) { void* ptr = (void*)this.bmpData.Scan0; - return new MemoryHandle(ptr); + return new MemoryHandle(ptr, pinnable: this); } public override void Unpin() @@ -132,8 +135,8 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(cfg, memory, 5, 5, metaData)) { - Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); - ref Rgba32 pixel0 = ref imageSpan[0]; + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); + ref Rgba32 pixel0 = ref imageMem.Span[0]; Assert.True(Unsafe.AreSame(ref array[0], ref pixel0)); Assert.Equal(cfg, image.GetConfiguration()); @@ -160,17 +163,25 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(memory, bmp.Width, bmp.Height)) { Assert.Equal(memory, image.GetRootFramePixelBuffer().DangerousGetSingleMemory()); - Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); - imageSpan.Fill(bg); - for (var i = 10; i < 20; i++) + image.GetPixelMemoryGroup().Fill(bg); + + image.ProcessPixelRows(accessor => { - image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg); - } + for (var i = 10; i < 20; i++) + { + accessor.GetRowSpan(i).Slice(10, 10).Fill(fg); + } + }); } Assert.False(memoryManager.IsDisposed); } + if (!Directory.Exists(TestEnvironment.ActualOutputDirectoryFullPath)) + { + Directory.CreateDirectory(TestEnvironment.ActualOutputDirectoryFullPath); + } + string fn = System.IO.Path.Combine( TestEnvironment.ActualOutputDirectoryFullPath, $"{nameof(this.WrapSystemDrawingBitmap_WhenObserved)}.bmp"); @@ -196,12 +207,14 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(memoryManager, bmp.Width, bmp.Height)) { Assert.Equal(memoryManager.Memory, image.GetRootFramePixelBuffer().DangerousGetSingleMemory()); - Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); - imageSpan.Fill(bg); - for (var i = 10; i < 20; i++) + image.GetPixelMemoryGroup().Fill(bg); + image.ProcessPixelRows(accessor => { - image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg); - } + for (var i = 10; i < 20; i++) + { + accessor.GetRowSpan(i).Slice(10, 10).Fill(fg); + } + }); } Assert.True(memoryManager.IsDisposed); @@ -225,8 +238,8 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(cfg, memory, 5, 5, metaData)) { - Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); - ref Rgba32 pixel0 = ref imageSpan[0]; + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); + ref Rgba32 pixel0 = ref imageMem.Span[0]; Assert.True(Unsafe.AreSame(ref Unsafe.As(ref array[0]), ref pixel0)); Assert.Equal(cfg, image.GetConfiguration()); @@ -262,12 +275,14 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(pixelSpan.Length, imageSpan.Length); Assert.True(Unsafe.AreSame(ref pixelSpan.GetPinnableReference(), ref imageSpan.GetPinnableReference())); - Assert.True(image.TryGetSinglePixelSpan(out imageSpan)); - imageSpan.Fill(bg); - for (var i = 10; i < 20; i++) + image.GetPixelMemoryGroup().Fill(bg); + image.ProcessPixelRows(accessor => { - image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg); - } + for (var i = 10; i < 20; i++) + { + accessor.GetRowSpan(i).Slice(10, 10).Fill(fg); + } + }); } Assert.False(memoryManager.IsDisposed); @@ -293,7 +308,8 @@ namespace SixLabors.ImageSharp.Tests { using (var image = Image.WrapMemory(cfg, ptr, 5, 5, metaData)) { - Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); + Span imageSpan = imageMem.Span; ref Rgba32 pixel0 = ref imageSpan[0]; Assert.True(Unsafe.AreSame(ref array[0], ref pixel0)); ref Rgba32 pixel_1 = ref imageSpan[imageSpan.Length - 1]; @@ -331,12 +347,14 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(pixelSpan.Length, imageSpan.Length); Assert.True(Unsafe.AreSame(ref pixelSpan.GetPinnableReference(), ref imageSpan.GetPinnableReference())); - Assert.True(image.TryGetSinglePixelSpan(out imageSpan)); - imageSpan.Fill(bg); - for (var i = 10; i < 20; i++) + image.GetPixelMemoryGroup().Fill(bg); + image.ProcessPixelRows(accessor => { - image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg); - } + for (var i = 10; i < 20; i++) + { + accessor.GetRowSpan(i).Slice(10, 10).Fill(fg); + } + }); } Assert.False(memoryManager.IsDisposed); @@ -414,16 +432,19 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(width, img.Width); Assert.Equal(height, img.Height); - for (int i = 0; i < height; ++i) + img.ProcessPixelRows(accessor => { - var arrayIndex = width * i; + for (int i = 0; i < height; ++i) + { + var arrayIndex = width * i; - Span rowSpan = img.GetPixelRowSpan(i); - ref Rgba32 r0 = ref rowSpan[0]; - ref Rgba32 r1 = ref array[arrayIndex]; + Span rowSpan = accessor.GetRowSpan(i); + ref Rgba32 r0 = ref rowSpan[0]; + ref Rgba32 r1 = ref array[arrayIndex]; - Assert.True(Unsafe.AreSame(ref r0, ref r1)); - } + Assert.True(Unsafe.AreSame(ref r0, ref r1)); + } + }); } Assert.True(memory.Disposed); @@ -458,16 +479,19 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(width, img.Width); Assert.Equal(height, img.Height); - for (int i = 0; i < height; ++i) + img.ProcessPixelRows(acccessor => { - var arrayIndex = pixelSize * width * i; + for (int i = 0; i < height; ++i) + { + var arrayIndex = pixelSize * width * i; - Span rowSpan = img.GetPixelRowSpan(i); - ref Rgba32 r0 = ref rowSpan[0]; - ref Rgba32 r1 = ref Unsafe.As(ref array[arrayIndex]); + Span rowSpan = acccessor.GetRowSpan(i); + ref Rgba32 r0 = ref rowSpan[0]; + ref Rgba32 r1 = ref Unsafe.As(ref array[arrayIndex]); - Assert.True(Unsafe.AreSame(ref r0, ref r1)); - } + Assert.True(Unsafe.AreSame(ref r0, ref r1)); + } + }); } Assert.True(memory.Disposed); diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index db0479484..0a9e2817a 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -3,8 +3,11 @@ using System; using System.IO; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -29,8 +32,8 @@ namespace SixLabors.ImageSharp.Tests { Assert.Equal(11, image.Width); Assert.Equal(23, image.Height); - Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); - Assert.Equal(11 * 23, imageSpan.Length); + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); + Assert.Equal(11 * 23, imageMem.Length); image.ComparePixelBufferTo(default(Rgba32)); Assert.Equal(Configuration.Default, image.GetConfiguration()); @@ -46,8 +49,8 @@ namespace SixLabors.ImageSharp.Tests { Assert.Equal(11, image.Width); Assert.Equal(23, image.Height); - Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); - Assert.Equal(11 * 23, imageSpan.Length); + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); + Assert.Equal(11 * 23, imageMem.Length); image.ComparePixelBufferTo(default(Rgba32)); Assert.Equal(configuration, image.GetConfiguration()); @@ -64,8 +67,8 @@ namespace SixLabors.ImageSharp.Tests { Assert.Equal(11, image.Width); Assert.Equal(23, image.Height); - Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); - Assert.Equal(11 * 23, imageSpan.Length); + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); + Assert.Equal(11 * 23, imageMem.Length); image.ComparePixelBufferTo(color); Assert.Equal(configuration, image.GetConfiguration()); @@ -97,12 +100,8 @@ namespace SixLabors.ImageSharp.Tests { private readonly Configuration configuration = Configuration.CreateDefaultInstance(); - private void LimitBufferCapacity(int bufferCapacityInBytes) - { - var allocator = ArrayPoolMemoryAllocator.CreateDefault(); - this.configuration.MemoryAllocator = allocator; - allocator.BufferCapacityInBytes = bufferCapacityInBytes; - } + private void LimitBufferCapacity(int bufferCapacityInBytes) => + this.configuration.MemoryAllocator = new TestMemoryAllocator { BufferCapacityInBytes = bufferCapacityInBytes }; [Theory] [InlineData(false)] @@ -171,6 +170,95 @@ namespace SixLabors.ImageSharp.Tests ArgumentOutOfRangeException ex = Assert.Throws(() => image[3, y] = default); Assert.Equal("y", ex.ParamName); } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void CopyPixelDataTo_Success(bool disco, bool byteSpan) + { + if (disco) + { + this.LimitBufferCapacity(20); + } + + using var image = new Image(this.configuration, 10, 10); + if (disco) + { + Assert.True(image.GetPixelMemoryGroup().Count > 1); + } + + byte[] expected = TestUtils.FillImageWithRandomBytes(image); + byte[] actual = new byte[expected.Length]; + if (byteSpan) + { + image.CopyPixelDataTo(actual); + } + else + { + Span destination = MemoryMarshal.Cast(actual); + image.CopyPixelDataTo(destination); + } + + Assert.True(expected.AsSpan().SequenceEqual(actual)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CopyPixelDataTo_DestinationTooShort_Throws(bool byteSpan) + { + using var image = new Image(this.configuration, 10, 10); + + Assert.ThrowsAny(() => + { + if (byteSpan) + { + image.CopyPixelDataTo(new byte[199]); + } + else + { + image.CopyPixelDataTo(new La16[99]); + } + }); + } + } + + public class ProcessPixelRows : ProcessPixelRowsTestBase + { + protected override void ProcessPixelRowsImpl( + Image image, + PixelAccessorAction processPixels) => + image.ProcessPixelRows(processPixels); + + protected override void ProcessPixelRowsImpl( + Image image1, + Image image2, + PixelAccessorAction processPixels) => + image1.ProcessPixelRows(image2, processPixels); + + protected override void ProcessPixelRowsImpl( + Image image1, + Image image2, + Image image3, + PixelAccessorAction processPixels) => + image1.ProcessPixelRows(image2, image3, processPixels); + + [Fact] + public void NullReference_Throws() + { + using var img = new Image(1, 1); + + Assert.Throws(() => img.ProcessPixelRows(null)); + + Assert.Throws(() => img.ProcessPixelRows((Image)null, (_, _) => { })); + Assert.Throws(() => img.ProcessPixelRows(img, img, null)); + + Assert.Throws(() => img.ProcessPixelRows((Image)null, img, (_, _, _) => { })); + Assert.Throws(() => img.ProcessPixelRows(img, (Image)null, (_, _, _) => { })); + Assert.Throws(() => img.ProcessPixelRows(img, img, null)); + } } public class Dispose @@ -232,12 +320,42 @@ namespace SixLabors.ImageSharp.Tests // Image Assert.Throws(() => { var res = image.Clone(this.configuration); }); Assert.Throws(() => { var res = image.CloneAs(this.configuration); }); - Assert.Throws(() => { var res = image.GetPixelRowSpan(default); }); - Assert.Throws(() => { var res = image.TryGetSinglePixelSpan(out var _); }); + Assert.Throws(() => { var res = image.DangerousTryGetSinglePixelMemory(out Memory _); }); // Image Assert.Throws(() => { var res = genericImage.CloneAs(this.configuration); }); } } + + public class DetectEncoder + { + [Fact] + public void KnownExtension_ReturnsEncoder() + { + using var image = new Image(1, 1); + IImageEncoder encoder = image.DetectEncoder("dummy.png"); + Assert.NotNull(encoder); + Assert.IsType(encoder); + } + + [Fact] + public void UnknownExtension_ThrowsNotSupportedException() + { + using var image = new Image(1, 1); + Assert.Throws(() => image.DetectEncoder("dummy.yolo")); + } + + [Fact] + public void NoDetectorRegisteredForKnownExtension_ThrowsNotSupportedException() + { + var configuration = new Configuration(); + var format = new TestFormat(); + configuration.ImageFormatsManager.AddImageFormat(format); + configuration.ImageFormatsManager.AddImageFormatDetector(new MockImageFormatDetector(format)); + + using var image = new Image(configuration, 1, 1); + Assert.Throws(() => image.DetectEncoder($"dummy.{format.Extension}")); + } + } } } diff --git a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs index afa217bbc..b2ee9d673 100644 --- a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs +++ b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using Xunit; @@ -19,14 +21,31 @@ namespace SixLabors.ImageSharp.Tests image.DebugSave(provider); } + [Fact] + public void PreferContiguousImageBuffers_CreateImage_BufferIsContiguous() + { + // Run remotely to avoid large allocation in the test process: + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + Configuration configuration = Configuration.Default.Clone(); + configuration.PreferContiguousImageBuffers = true; + + using var image = new Image(configuration, 8192, 4096); + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory mem)); + Assert.Equal(8192 * 4096, mem.Length); + } + } + [Theory] [WithBasicTestPatternImages(width: 10, height: 10, PixelTypes.Rgba32)] - public void GetSingleSpan(TestImageProvider provider) + public void DangerousTryGetSinglePixelMemory_WhenImageTooLarge_ReturnsFalse(TestImageProvider provider) { provider.LimitAllocatorBufferCapacity().InPixels(10); using Image image = provider.GetImage(); - Assert.False(image.TryGetSinglePixelSpan(out Span imageSpan)); - Assert.False(image.Frames.RootFrame.TryGetSinglePixelSpan(out Span imageFrameSpan)); + Assert.False(image.DangerousTryGetSinglePixelMemory(out Memory mem)); + Assert.False(image.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out Memory _)); } } } diff --git a/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs b/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs new file mode 100644 index 000000000..fa0752e77 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs @@ -0,0 +1,323 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections.Generic; +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Memory.Internals; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public abstract class ProcessPixelRowsTestBase + { + protected abstract void ProcessPixelRowsImpl( + Image image, + PixelAccessorAction processPixels) + where TPixel : unmanaged, IPixel; + + protected abstract void ProcessPixelRowsImpl( + Image image1, + Image image2, + PixelAccessorAction processPixels) + where TPixel : unmanaged, IPixel; + + protected abstract void ProcessPixelRowsImpl( + Image image1, + Image image2, + Image image3, + PixelAccessorAction processPixels) + where TPixel : unmanaged, IPixel; + + [Fact] + public void PixelAccessorDimensionsAreCorrect() + { + using var image = new Image(123, 456); + this.ProcessPixelRowsImpl(image, accessor => + { + Assert.Equal(123, accessor.Width); + Assert.Equal(456, accessor.Height); + }); + } + + [Fact] + public void WriteImagePixels_SingleImage() + { + using var image = new Image(256, 256); + this.ProcessPixelRowsImpl(image, accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span row = accessor.GetRowSpan(y); + for (int x = 0; x < row.Length; x++) + { + row[x] = new L16((ushort)(x * y)); + } + } + }); + + Buffer2D buffer = image.Frames.RootFrame.PixelBuffer; + for (int y = 0; y < 256; y++) + { + Span row = buffer.DangerousGetRowSpan(y); + for (int x = 0; x < 256; x++) + { + int actual = row[x].PackedValue; + Assert.Equal(x * y, actual); + } + } + } + + [Fact] + public void WriteImagePixels_MultiImage2() + { + using var img1 = new Image(256, 256); + Buffer2D buffer = img1.Frames.RootFrame.PixelBuffer; + for (int y = 0; y < 256; y++) + { + Span row = buffer.DangerousGetRowSpan(y); + for (int x = 0; x < 256; x++) + { + row[x] = new L16((ushort)(x * y)); + } + } + + using var img2 = new Image(256, 256); + + this.ProcessPixelRowsImpl(img1, img2, (accessor1, accessor2) => + { + for (int y = 0; y < accessor1.Height; y++) + { + Span row1 = accessor1.GetRowSpan(y); + Span row2 = accessor2.GetRowSpan(accessor2.Height - y - 1); + row1.CopyTo(row2); + } + }); + + buffer = img2.Frames.RootFrame.PixelBuffer; + for (int y = 0; y < 256; y++) + { + Span row = buffer.DangerousGetRowSpan(y); + for (int x = 0; x < 256; x++) + { + int actual = row[x].PackedValue; + Assert.Equal(x * (256 - y - 1), actual); + } + } + } + + [Fact] + public void WriteImagePixels_MultiImage3() + { + using var img1 = new Image(256, 256); + Buffer2D buffer2 = img1.Frames.RootFrame.PixelBuffer; + for (int y = 0; y < 256; y++) + { + Span row = buffer2.DangerousGetRowSpan(y); + for (int x = 0; x < 256; x++) + { + row[x] = new L16((ushort)(x * y)); + } + } + + using var img2 = new Image(256, 256); + using var img3 = new Image(256, 256); + + this.ProcessPixelRowsImpl(img1, img2, img3, (accessor1, accessor2, accessor3) => + { + for (int y = 0; y < accessor1.Height; y++) + { + Span row1 = accessor1.GetRowSpan(y); + Span row2 = accessor2.GetRowSpan(accessor2.Height - y - 1); + Span row3 = accessor3.GetRowSpan(y); + row1.CopyTo(row2); + row1.CopyTo(row3); + } + }); + + buffer2 = img2.Frames.RootFrame.PixelBuffer; + Buffer2D buffer3 = img3.Frames.RootFrame.PixelBuffer; + for (int y = 0; y < 256; y++) + { + Span row2 = buffer2.DangerousGetRowSpan(y); + Span row3 = buffer3.DangerousGetRowSpan(y); + for (int x = 0; x < 256; x++) + { + int actual2 = row2[x].PackedValue; + int actual3 = row3[x].PackedValue; + Assert.Equal(x * (256 - y - 1), actual2); + Assert.Equal(x * y, actual3); + } + } + } + + [Fact] + public void Disposed_ThrowsObjectDisposedException() + { + using var nonDisposed = new Image(1, 1); + var disposed = new Image(1, 1); + disposed.Dispose(); + + Assert.Throws(() => this.ProcessPixelRowsImpl(disposed, _ => { })); + + Assert.Throws(() => this.ProcessPixelRowsImpl(disposed, nonDisposed, (_, _) => { })); + Assert.Throws(() => this.ProcessPixelRowsImpl(nonDisposed, disposed, (_, _) => { })); + + Assert.Throws(() => this.ProcessPixelRowsImpl(disposed, nonDisposed, nonDisposed, (_, _, _) => { })); + Assert.Throws(() => this.ProcessPixelRowsImpl(nonDisposed, disposed, nonDisposed, (_, _, _) => { })); + Assert.Throws(() => this.ProcessPixelRowsImpl(nonDisposed, nonDisposed, disposed, (_, _, _) => { })); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void RetainsUnmangedBuffers1(bool throwException) + { + RemoteExecutor.Invoke(RunTest, this.GetType().FullName, throwException.ToString()).Dispose(); + + static void RunTest(string testTypeName, string throwExceptionStr) + { + bool throwExceptionInner = bool.Parse(throwExceptionStr); + var buffer = UnmanagedBuffer.Allocate(100); + var allocator = new MockUnmanagedMemoryAllocator(buffer); + Configuration.Default.MemoryAllocator = allocator; + + var image = new Image(10, 10); + + Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); + try + { + GetTest(testTypeName).ProcessPixelRowsImpl(image, _ => + { + ((IDisposable)buffer).Dispose(); + Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); + if (throwExceptionInner) + { + throw new NonFatalException(); + } + }); + } + catch (NonFatalException) + { + } + + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void RetainsUnmangedBuffers2(bool throwException) + { + RemoteExecutor.Invoke(RunTest, this.GetType().FullName, throwException.ToString()).Dispose(); + + static void RunTest(string testTypeName, string throwExceptionStr) + { + bool throwExceptionInner = bool.Parse(throwExceptionStr); + var buffer1 = UnmanagedBuffer.Allocate(100); + var buffer2 = UnmanagedBuffer.Allocate(100); + var allocator = new MockUnmanagedMemoryAllocator(buffer1, buffer2); + Configuration.Default.MemoryAllocator = allocator; + + var image1 = new Image(10, 10); + var image2 = new Image(10, 10); + + Assert.Equal(2, UnmanagedMemoryHandle.TotalOutstandingHandles); + try + { + GetTest(testTypeName).ProcessPixelRowsImpl(image1, image2, (_, _) => + { + ((IDisposable)buffer1).Dispose(); + ((IDisposable)buffer2).Dispose(); + Assert.Equal(2, UnmanagedMemoryHandle.TotalOutstandingHandles); + if (throwExceptionInner) + { + throw new NonFatalException(); + } + }); + } + catch (NonFatalException) + { + } + + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void RetainsUnmangedBuffers3(bool throwException) + { + RemoteExecutor.Invoke(RunTest, this.GetType().FullName, throwException.ToString()).Dispose(); + + static void RunTest(string testTypeName, string throwExceptionStr) + { + bool throwExceptionInner = bool.Parse(throwExceptionStr); + var buffer1 = UnmanagedBuffer.Allocate(100); + var buffer2 = UnmanagedBuffer.Allocate(100); + var buffer3 = UnmanagedBuffer.Allocate(100); + var allocator = new MockUnmanagedMemoryAllocator(buffer1, buffer2, buffer3); + Configuration.Default.MemoryAllocator = allocator; + + var image1 = new Image(10, 10); + var image2 = new Image(10, 10); + var image3 = new Image(10, 10); + + Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); + try + { + GetTest(testTypeName).ProcessPixelRowsImpl(image1, image2, image3, (_, _, _) => + { + ((IDisposable)buffer1).Dispose(); + ((IDisposable)buffer2).Dispose(); + ((IDisposable)buffer3).Dispose(); + Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); + if (throwExceptionInner) + { + throw new NonFatalException(); + } + }); + } + catch (NonFatalException) + { + } + + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + } + + private static ProcessPixelRowsTestBase GetTest(string testTypeName) + { + Type type = typeof(ProcessPixelRowsTestBase).Assembly.GetType(testTypeName); + return (ProcessPixelRowsTestBase)Activator.CreateInstance(type); + } + + private class NonFatalException : Exception + { + } + + private class MockUnmanagedMemoryAllocator : MemoryAllocator + where T1 : struct + { + private Stack> buffers = new(); + + public MockUnmanagedMemoryAllocator(params UnmanagedBuffer[] buffers) + { + foreach (UnmanagedBuffer buffer in buffers) + { + this.buffers.Push(buffer); + } + } + + protected internal override int GetBufferCapacityInBytes() => int.MaxValue; + + public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) => + this.buffers.Pop() as IMemoryOwner; + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs deleted file mode 100644 index dd53b0b56..000000000 --- a/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using Microsoft.DotNet.RemoteExecutor; -using SixLabors.ImageSharp.Memory; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Memory.Allocators -{ - [Collection("RunSerial")] - public class ArrayPoolMemoryAllocatorTests - { - private const int MaxPooledBufferSizeInBytes = 2048; - - private const int PoolSelectorThresholdInBytes = MaxPooledBufferSizeInBytes / 2; - - /// - /// Gets the SUT for in-process tests. - /// - private MemoryAllocatorFixture LocalFixture { get; } = new MemoryAllocatorFixture(); - - /// - /// Gets the SUT for tests executed by , - /// recreated in each external process. - /// - private static MemoryAllocatorFixture StaticFixture { get; } = new MemoryAllocatorFixture(); - - public class BufferTests : BufferTestSuite - { - public BufferTests() - : base(new ArrayPoolMemoryAllocator(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes)) - { - } - } - - public class Constructor - { - [Fact] - public void WhenBothParametersPassedByUser() - { - var mgr = new ArrayPoolMemoryAllocator(1111, 666); - Assert.Equal(1111, mgr.MaxPoolSizeInBytes); - Assert.Equal(666, mgr.PoolSelectorThresholdInBytes); - } - - [Fact] - public void WhenPassedOnly_MaxPooledBufferSizeInBytes_SmallerThresholdValueIsAutoCalculated() - { - var mgr = new ArrayPoolMemoryAllocator(5000); - Assert.Equal(5000, mgr.MaxPoolSizeInBytes); - Assert.True(mgr.PoolSelectorThresholdInBytes < mgr.MaxPoolSizeInBytes); - } - - [Fact] - public void When_PoolSelectorThresholdInBytes_IsGreaterThan_MaxPooledBufferSizeInBytes_ExceptionIsThrown() - => Assert.ThrowsAny(() => new ArrayPoolMemoryAllocator(100, 200)); - } - - [Theory] - [InlineData(32)] - [InlineData(512)] - [InlineData(MaxPooledBufferSizeInBytes - 1)] - public void SmallBuffersArePooled_OfByte(int size) => Assert.True(this.LocalFixture.CheckIsRentingPooledBuffer(size)); - - [Theory] - [InlineData(128 * 1024 * 1024)] - [InlineData(MaxPooledBufferSizeInBytes + 1)] - public void LargeBuffersAreNotPooled_OfByte(int size) - { - static void RunTest(string sizeStr) - { - int size = int.Parse(sizeStr); - StaticFixture.CheckIsRentingPooledBuffer(size); - } - - RemoteExecutor.Invoke(RunTest, size.ToString()).Dispose(); - } - - [Fact] - public unsafe void SmallBuffersArePooled_OfBigValueType() - { - int count = (MaxPooledBufferSizeInBytes / sizeof(LargeStruct)) - 1; - - Assert.True(this.LocalFixture.CheckIsRentingPooledBuffer(count)); - } - - [Fact] - public unsafe void LaregeBuffersAreNotPooled_OfBigValueType() - { - int count = (MaxPooledBufferSizeInBytes / sizeof(LargeStruct)) + 1; - - Assert.False(this.LocalFixture.CheckIsRentingPooledBuffer(count)); - } - - [Theory] - [InlineData(AllocationOptions.None)] - [InlineData(AllocationOptions.Clean)] - public void CleaningRequests_AreControlledByAllocationParameter_Clean(AllocationOptions options) - { - MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator; - using (IMemoryOwner firstAlloc = memoryAllocator.Allocate(42)) - { - BufferExtensions.GetSpan(firstAlloc).Fill(666); - } - - using (IMemoryOwner secondAlloc = memoryAllocator.Allocate(42, options)) - { - int expected = options == AllocationOptions.Clean ? 0 : 666; - Assert.Equal(expected, BufferExtensions.GetSpan(secondAlloc)[0]); - } - } - - [Fact] - public unsafe void Allocate_MemoryIsPinnableMultipleTimes() - { - ArrayPoolMemoryAllocator allocator = this.LocalFixture.MemoryAllocator; - using IMemoryOwner memoryOwner = allocator.Allocate(100); - - using (MemoryHandle pin = memoryOwner.Memory.Pin()) - { - Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer); - } - - using (MemoryHandle pin = memoryOwner.Memory.Pin()) - { - Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer); - } - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void ReleaseRetainedResources_ReplacesInnerArrayPool(bool keepBufferAlive) - { - MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator; - IMemoryOwner buffer = memoryAllocator.Allocate(32); - ref int ptrToPrev0 = ref MemoryMarshal.GetReference(BufferExtensions.GetSpan(buffer)); - - if (!keepBufferAlive) - { - buffer.Dispose(); - } - - memoryAllocator.ReleaseRetainedResources(); - - buffer = memoryAllocator.Allocate(32); - - Assert.False(Unsafe.AreSame(ref ptrToPrev0, ref BufferExtensions.GetReference(buffer))); - } - - [Fact] - public void ReleaseRetainedResources_DisposingPreviouslyAllocatedBuffer_IsAllowed() - { - MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator; - IMemoryOwner buffer = memoryAllocator.Allocate(32); - memoryAllocator.ReleaseRetainedResources(); - buffer.Dispose(); - } - - [Fact] - public void AllocationOverLargeArrayThreshold_UsesDifferentPool() - { - static void RunTest() - { - const int ArrayLengthThreshold = PoolSelectorThresholdInBytes / sizeof(int); - - IMemoryOwner small = StaticFixture.MemoryAllocator.Allocate(ArrayLengthThreshold - 1); - ref int ptr2Small = ref BufferExtensions.GetReference(small); - small.Dispose(); - - IMemoryOwner large = StaticFixture.MemoryAllocator.Allocate(ArrayLengthThreshold + 1); - - Assert.False(Unsafe.AreSame(ref ptr2Small, ref BufferExtensions.GetReference(large))); - } - - RemoteExecutor.Invoke(RunTest).Dispose(); - } - - [Fact] - public void CreateWithAggressivePooling() - { - static void RunTest() - { - StaticFixture.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithAggressivePooling(); - Assert.True(StaticFixture.CheckIsRentingPooledBuffer(4096 * 4096)); - } - - RemoteExecutor.Invoke(RunTest).Dispose(); - } - - [Fact] - public void CreateDefault() - { - static void RunTest() - { - StaticFixture.MemoryAllocator = ArrayPoolMemoryAllocator.CreateDefault(); - - Assert.False(StaticFixture.CheckIsRentingPooledBuffer(2 * 4096 * 4096)); - Assert.True(StaticFixture.CheckIsRentingPooledBuffer(2048 * 2048)); - } - - RemoteExecutor.Invoke(RunTest).Dispose(); - } - - [Fact] - public void CreateWithModeratePooling() - { - static void RunTest() - { - StaticFixture.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling(); - Assert.False(StaticFixture.CheckIsRentingPooledBuffer(2048 * 2048)); - Assert.True(StaticFixture.CheckIsRentingPooledBuffer(1024 * 16)); - } - - RemoteExecutor.Invoke(RunTest).Dispose(); - } - - [Theory] - [InlineData(-1)] - [InlineData(-111)] - public void Allocate_Negative_Throws_ArgumentOutOfRangeException(int length) - { - ArgumentOutOfRangeException ex = Assert.Throws(() => - this.LocalFixture.MemoryAllocator.Allocate(length)); - Assert.Equal("length", ex.ParamName); - } - - [Fact] - public void AllocateZero() - { - using IMemoryOwner buffer = this.LocalFixture.MemoryAllocator.Allocate(0); - Assert.Equal(0, buffer.Memory.Length); - } - - [Theory] - [InlineData(-1)] - public void AllocateManagedByteBuffer_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) - { - ArgumentOutOfRangeException ex = Assert.Throws(() => - this.LocalFixture.MemoryAllocator.AllocateManagedByteBuffer(length)); - Assert.Equal("length", ex.ParamName); - } - - private class MemoryAllocatorFixture - { - public ArrayPoolMemoryAllocator MemoryAllocator { get; set; } = - new ArrayPoolMemoryAllocator(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes); - - /// - /// Rent a buffer -> return it -> re-rent -> verify if it's span points to the previous location. - /// - public bool CheckIsRentingPooledBuffer(int length) - where T : struct - { - IMemoryOwner buffer = this.MemoryAllocator.Allocate(length); - ref T ptrToPrevPosition0 = ref BufferExtensions.GetReference(buffer); - buffer.Dispose(); - - buffer = this.MemoryAllocator.Allocate(length); - bool sameBuffers = Unsafe.AreSame(ref ptrToPrevPosition0, ref BufferExtensions.GetReference(buffer)); - buffer.Dispose(); - - return sameBuffers; - } - } - - [StructLayout(LayoutKind.Sequential)] - private struct SmallStruct - { - private readonly uint dummy; - } - - private const int SizeOfLargeStruct = MaxPooledBufferSizeInBytes / 5; - - [StructLayout(LayoutKind.Explicit, Size = SizeOfLargeStruct)] - private struct LargeStruct - { - } - } -} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs b/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs index 1cadf1653..c3c3d40b9 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs @@ -96,8 +96,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators [MemberData(nameof(LengthValues))] public void CanAllocateCleanBuffer_byte(int desiredLength) { - this.TestCanAllocateCleanBuffer(desiredLength, false); - this.TestCanAllocateCleanBuffer(desiredLength, true); + this.TestCanAllocateCleanBuffer(desiredLength); } [Theory] @@ -114,30 +113,14 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators this.TestCanAllocateCleanBuffer(desiredLength); } - private IMemoryOwner Allocate(int desiredLength, AllocationOptions options, bool managedByteBuffer) - where T : struct - { - if (managedByteBuffer) - { - if (!(this.MemoryAllocator.AllocateManagedByteBuffer(desiredLength, options) is IMemoryOwner buffer)) - { - throw new InvalidOperationException("typeof(T) != typeof(byte)"); - } - - return buffer; - } - - return this.MemoryAllocator.Allocate(desiredLength, options); - } - - private void TestCanAllocateCleanBuffer(int desiredLength, bool testManagedByteBuffer = false) + private void TestCanAllocateCleanBuffer(int desiredLength) where T : struct, IEquatable { ReadOnlySpan expected = new T[desiredLength]; for (int i = 0; i < 10; i++) { - using (IMemoryOwner buffer = this.Allocate(desiredLength, AllocationOptions.Clean, testManagedByteBuffer)) + using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength, AllocationOptions.Clean)) { Assert.True(buffer.GetSpan().SequenceEqual(expected)); } @@ -155,14 +138,13 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators [MemberData(nameof(LengthValues))] public void SpanPropertyIsAlwaysTheSame_byte(int desiredLength) { - this.TestSpanPropertyIsAlwaysTheSame(desiredLength, false); - this.TestSpanPropertyIsAlwaysTheSame(desiredLength, true); + this.TestSpanPropertyIsAlwaysTheSame(desiredLength); } - private void TestSpanPropertyIsAlwaysTheSame(int desiredLength, bool testManagedByteBuffer = false) + private void TestSpanPropertyIsAlwaysTheSame(int desiredLength) where T : struct { - using (IMemoryOwner buffer = this.Allocate(desiredLength, AllocationOptions.None, testManagedByteBuffer)) + using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength, AllocationOptions.None)) { ref T a = ref MemoryMarshal.GetReference(buffer.GetSpan()); ref T b = ref MemoryMarshal.GetReference(buffer.GetSpan()); @@ -184,14 +166,13 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators [MemberData(nameof(LengthValues))] public void WriteAndReadElements_byte(int desiredLength) { - this.TestWriteAndReadElements(desiredLength, x => (byte)(x + 1), false); - this.TestWriteAndReadElements(desiredLength, x => (byte)(x + 1), true); + this.TestWriteAndReadElements(desiredLength, x => (byte)(x + 1)); } - private void TestWriteAndReadElements(int desiredLength, Func getExpectedValue, bool testManagedByteBuffer = false) + private void TestWriteAndReadElements(int desiredLength, Func getExpectedValue) where T : struct { - using (IMemoryOwner buffer = this.Allocate(desiredLength, AllocationOptions.None, testManagedByteBuffer)) + using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength)) { var expectedVals = new T[buffer.Length()]; @@ -214,8 +195,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators [MemberData(nameof(LengthValues))] public void IndexingSpan_WhenOutOfRange_Throws_byte(int desiredLength) { - this.TestIndexOutOfRangeShouldThrow(desiredLength, false); - this.TestIndexOutOfRangeShouldThrow(desiredLength, true); + this.TestIndexOutOfRangeShouldThrow(desiredLength); } [Theory] @@ -232,12 +212,12 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators this.TestIndexOutOfRangeShouldThrow(desiredLength); } - private T TestIndexOutOfRangeShouldThrow(int desiredLength, bool testManagedByteBuffer = false) + private T TestIndexOutOfRangeShouldThrow(int desiredLength) where T : struct, IEquatable { var dummy = default(T); - using (IMemoryOwner buffer = this.Allocate(desiredLength, AllocationOptions.None, testManagedByteBuffer)) + using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength)) { Assert.ThrowsAny( () => @@ -264,23 +244,6 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators return dummy; } - [Theory] - [InlineData(1)] - [InlineData(7)] - [InlineData(1024)] - [InlineData(6666)] - public void ManagedByteBuffer_ArrayIsCorrect(int desiredLength) - { - using (IManagedByteBuffer buffer = this.MemoryAllocator.AllocateManagedByteBuffer(desiredLength)) - { - ref byte array0 = ref buffer.Array[0]; - ref byte span0 = ref buffer.GetReference(); - - Assert.True(Unsafe.AreSame(ref span0, ref array0)); - Assert.True(buffer.Array.Length >= buffer.GetSpan().Length); - } - } - [Fact] public void GetMemory_ReturnsValidMemory() { diff --git a/tests/ImageSharp.Tests/Memory/Allocators/RefCountedLifetimeGuardTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/RefCountedLifetimeGuardTests.cs new file mode 100644 index 000000000..7fb3b7b7b --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Allocators/RefCountedLifetimeGuardTests.cs @@ -0,0 +1,126 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory.Internals; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.Allocators +{ + public class RefCountedLifetimeGuardTests + { + [Theory] + [InlineData(1)] + [InlineData(3)] + public void Dispose_ResultsInSingleRelease(int disposeCount) + { + var guard = new MockLifetimeGuard(); + Assert.Equal(0, guard.ReleaseInvocationCount); + + for (int i = 0; i < disposeCount; i++) + { + guard.Dispose(); + } + + Assert.Equal(1, guard.ReleaseInvocationCount); + } + + [Fact] + public void Finalize_ResultsInSingleRelease() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + Assert.Equal(0, MockLifetimeGuard.GlobalReleaseInvocationCount); + LeakGuard(false); + GC.Collect(); + GC.WaitForPendingFinalizers(); + Assert.Equal(1, MockLifetimeGuard.GlobalReleaseInvocationCount); + } + } + + [Theory] + [InlineData(1)] + [InlineData(3)] + public void AddRef_PreventsReleaseOnDispose(int addRefCount) + { + var guard = new MockLifetimeGuard(); + + for (int i = 0; i < addRefCount; i++) + { + guard.AddRef(); + } + + guard.Dispose(); + + for (int i = 0; i < addRefCount; i++) + { + Assert.Equal(0, guard.ReleaseInvocationCount); + guard.ReleaseRef(); + } + + Assert.Equal(1, guard.ReleaseInvocationCount); + } + + [Fact] + public void AddRef_PreventsReleaseOnFinalize() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + LeakGuard(true); + GC.Collect(); + GC.WaitForPendingFinalizers(); + Assert.Equal(0, MockLifetimeGuard.GlobalReleaseInvocationCount); + } + } + + [Fact] + public void AddRefReleaseRefMisuse_DoesntLeadToMultipleReleases() + { + var guard = new MockLifetimeGuard(); + guard.Dispose(); + guard.AddRef(); + guard.ReleaseRef(); + + Assert.Equal(1, guard.ReleaseInvocationCount); + } + + [Fact] + public void UnmanagedBufferLifetimeGuard_Handle_IsReturnedByRef() + { + var h = UnmanagedMemoryHandle.Allocate(10); + using var guard = new UnmanagedBufferLifetimeGuard.FreeHandle(h); + Assert.True(guard.Handle.IsValid); + guard.Handle.Free(); + Assert.False(guard.Handle.IsValid); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void LeakGuard(bool addRef) + { + var guard = new MockLifetimeGuard(); + if (addRef) + { + guard.AddRef(); + } + } + + private class MockLifetimeGuard : RefCountedLifetimeGuard + { + public int ReleaseInvocationCount { get; private set; } + + public static int GlobalReleaseInvocationCount { get; private set; } + + protected override void Release() + { + this.ReleaseInvocationCount++; + GlobalReleaseInvocationCount++; + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/SharedArrayPoolBufferTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/SharedArrayPoolBufferTests.cs new file mode 100644 index 000000000..fab520e19 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Allocators/SharedArrayPoolBufferTests.cs @@ -0,0 +1,60 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Linq; +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory.Internals; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.Allocators +{ + public class SharedArrayPoolBufferTests + { + [Fact] + public void AllocatesArrayPoolArray() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + using (var buffer = new SharedArrayPoolBuffer(900)) + { + Assert.Equal(900, buffer.GetSpan().Length); + buffer.GetSpan().Fill(42); + } + + byte[] array = ArrayPool.Shared.Rent(900); + byte[] expected = Enumerable.Repeat((byte)42, 900).ToArray(); + + Assert.True(expected.AsSpan().SequenceEqual(array.AsSpan(0, 900))); + } + } + + [Fact] + public void OutstandingReferences_RetainArrays() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + var buffer = new SharedArrayPoolBuffer(900); + Span span = buffer.GetSpan(); + + buffer.AddRef(); + ((IDisposable)buffer).Dispose(); + span.Fill(42); + byte[] array = ArrayPool.Shared.Rent(900); + Assert.NotEqual(42, array[0]); + ArrayPool.Shared.Return(array); + + buffer.ReleaseRef(); + array = ArrayPool.Shared.Rent(900); + byte[] expected = Enumerable.Repeat((byte)42, 900).ToArray(); + Assert.True(expected.AsSpan().SequenceEqual(array.AsSpan(0, 900))); + ArrayPool.Shared.Return(array); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs index 0e1f99725..3ab45be82 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs @@ -29,14 +29,6 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators Assert.Equal("length", ex.ParamName); } - [Theory] - [InlineData(-1)] - public void AllocateManagedByteBuffer_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) - { - ArgumentOutOfRangeException ex = Assert.Throws(() => this.MemoryAllocator.AllocateManagedByteBuffer(length)); - Assert.Equal("length", ex.ParamName); - } - [Fact] public unsafe void Allocate_MemoryIsPinnableMultipleTimes() { diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs new file mode 100644 index 000000000..f748b7feb --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs @@ -0,0 +1,169 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory.Internals; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.Allocators +{ + public partial class UniformUnmanagedMemoryPoolTests + { + [Collection(nameof(NonParallelTests))] + public class Trim + { + [CollectionDefinition(nameof(NonParallelTests), DisableParallelization = true)] + public class NonParallelTests + { + } + + [Fact] + public void TrimPeriodElapsed_TrimsHalfOfUnusedArrays() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + static void RunTest() + { + var trimSettings = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 5_000 }; + var pool = new UniformUnmanagedMemoryPool(128, 256, trimSettings); + + UnmanagedMemoryHandle[] a = pool.Rent(64); + UnmanagedMemoryHandle[] b = pool.Rent(64); + pool.Return(a); + Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles); + Thread.Sleep(15_000); + + // We expect at least 2 Trim actions, first trim 32, then 16 arrays. + // 128 - 32 - 16 = 80 + Assert.True( + UnmanagedMemoryHandle.TotalOutstandingHandles <= 80, + $"UnmanagedMemoryHandle.TotalOutstandingHandles={UnmanagedMemoryHandle.TotalOutstandingHandles} > 80"); + pool.Return(b); + } + } + + // Complicated Xunit ceremony to disable parallel execution of an individual test, + // MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed, + // which is strongly timing-sensitive, and might be flaky under high load. + [CollectionDefinition(nameof(NonParallelCollection), DisableParallelization = true)] + public class NonParallelCollection + { + } + + [Collection(nameof(NonParallelCollection))] + public class NonParallel + { + public static readonly bool IsNotMacOs = !TestEnvironment.IsOSX; + + // TODO: Investigate failures on MacOS. All handles are released after GC. + // (It seems to happen more consistently on .NET 6.) + [ConditionalFact(nameof(IsNotMacOs))] + public void MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed() + { + if (!TestEnvironment.RunsOnCI) + { + // This may fail in local runs resulting in high memory load. + // Remove the condition for local debugging! + return; + } + + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + var trimSettings1 = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 6_000 }; + var pool1 = new UniformUnmanagedMemoryPool(128, 256, trimSettings1); + Thread.Sleep(8_000); // Let some callbacks fire already + var trimSettings2 = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 3_000 }; + var pool2 = new UniformUnmanagedMemoryPool(128, 256, trimSettings2); + + pool1.Return(pool1.Rent(64)); + pool2.Return(pool2.Rent(64)); + Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles); + + // This exercises pool weak reference list trimming: + LeakPoolInstance(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles); + + Thread.Sleep(15_000); + Assert.True( + UnmanagedMemoryHandle.TotalOutstandingHandles <= 64, + $"UnmanagedMemoryHandle.TotalOutstandingHandles={UnmanagedMemoryHandle.TotalOutstandingHandles} > 64"); + GC.KeepAlive(pool1); + GC.KeepAlive(pool2); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void LeakPoolInstance() + { + var trimSettings = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 4_000 }; + _ = new UniformUnmanagedMemoryPool(128, 256, trimSettings); + } + } + } + +#if NETCOREAPP3_1_OR_GREATER + public static readonly bool Is32BitProcess = !Environment.Is64BitProcess; + private static readonly List PressureArrays = new(); + + [ConditionalFact(nameof(Is32BitProcess))] + public static void GC_Collect_OnHighLoad_TrimsEntirePool() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + Assert.False(Environment.Is64BitProcess); + const int OneMb = 1 << 20; + + var trimSettings = new UniformUnmanagedMemoryPool.TrimSettings { HighPressureThresholdRate = 0.2f }; + + GCMemoryInfo memInfo = GC.GetGCMemoryInfo(); + int highLoadThreshold = (int)(memInfo.HighMemoryLoadThresholdBytes / OneMb); + highLoadThreshold = (int)(trimSettings.HighPressureThresholdRate * highLoadThreshold); + + var pool = new UniformUnmanagedMemoryPool(OneMb, 16, trimSettings); + pool.Return(pool.Rent(16)); + Assert.Equal(16, UnmanagedMemoryHandle.TotalOutstandingHandles); + + for (int i = 0; i < highLoadThreshold; i++) + { + byte[] array = new byte[OneMb]; + TouchPage(array); + PressureArrays.Add(array); + } + + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); // The pool should be fully trimmed after this point + + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + + // Prevent eager collection of the pool: + GC.KeepAlive(pool); + + static void TouchPage(byte[] b) + { + uint size = (uint)b.Length; + const uint pageSize = 4096; + uint numPages = size / pageSize; + + for (uint i = 0; i < numPages; i++) + { + b[i * pageSize] = (byte)(i % 256); + } + } + } + } +#endif + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs new file mode 100644 index 000000000..00acce64e --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs @@ -0,0 +1,379 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Memory.Internals; +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.Memory.Allocators +{ + public partial class UniformUnmanagedMemoryPoolTests + { + private readonly ITestOutputHelper output; + + public UniformUnmanagedMemoryPoolTests(ITestOutputHelper output) => this.output = output; + + private class CleanupUtil : IDisposable + { + private readonly UniformUnmanagedMemoryPool pool; + private readonly List handlesToDestroy = new(); + private readonly List ptrsToDestroy = new(); + + public CleanupUtil(UniformUnmanagedMemoryPool pool) + { + this.pool = pool; + } + + public void Register(UnmanagedMemoryHandle handle) => this.handlesToDestroy.Add(handle); + + public void Register(IEnumerable handles) => this.handlesToDestroy.AddRange(handles); + + public void Register(IntPtr memoryPtr) => this.ptrsToDestroy.Add(memoryPtr); + + public void Register(IEnumerable memoryPtrs) => this.ptrsToDestroy.AddRange(memoryPtrs); + + public void Dispose() + { + foreach (UnmanagedMemoryHandle handle in this.handlesToDestroy) + { + handle.Free(); + } + + this.pool.Release(); + + foreach (IntPtr ptr in this.ptrsToDestroy) + { + Marshal.FreeHGlobal(ptr); + } + } + } + + [Theory] + [InlineData(3, 11)] + [InlineData(7, 4)] + public void Constructor_InitializesProperties(int arrayLength, int capacity) + { + var pool = new UniformUnmanagedMemoryPool(arrayLength, capacity); + Assert.Equal(arrayLength, pool.BufferLength); + Assert.Equal(capacity, pool.Capacity); + } + + [Theory] + [InlineData(1, 3)] + [InlineData(8, 10)] + public void Rent_SingleBuffer_ReturnsCorrectBuffer(int length, int capacity) + { + var pool = new UniformUnmanagedMemoryPool(length, capacity); + using var cleanup = new CleanupUtil(pool); + + for (int i = 0; i < capacity; i++) + { + UnmanagedMemoryHandle h = pool.Rent(); + CheckBuffer(length, pool, h); + cleanup.Register(h); + } + } + + [Fact] + public void Return_DoesNotDeallocateMemory() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + var pool = new UniformUnmanagedMemoryPool(16, 16); + UnmanagedMemoryHandle a = pool.Rent(); + UnmanagedMemoryHandle[] b = pool.Rent(2); + + Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); + pool.Return(a); + pool.Return(b); + Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + } + + private static void CheckBuffer(int length, UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle h) + { + Assert.False(h.IsInvalid); + Span span = GetSpan(h, pool.BufferLength); + span.Fill(123); + + byte[] expected = new byte[length]; + expected.AsSpan().Fill(123); + Assert.True(span.SequenceEqual(expected)); + } + + private static unsafe Span GetSpan(UnmanagedMemoryHandle h, int length) => new Span(h.Pointer, length); + + [Theory] + [InlineData(1, 1)] + [InlineData(1, 5)] + [InlineData(42, 7)] + [InlineData(5, 10)] + public void Rent_MultiBuffer_ReturnsCorrectBuffers(int length, int bufferCount) + { + var pool = new UniformUnmanagedMemoryPool(length, 10); + using var cleanup = new CleanupUtil(pool); + UnmanagedMemoryHandle[] handles = pool.Rent(bufferCount); + cleanup.Register(handles); + + Assert.NotNull(handles); + Assert.Equal(bufferCount, handles.Length); + + foreach (UnmanagedMemoryHandle h in handles) + { + CheckBuffer(length, pool, h); + } + } + + [Fact] + public void Rent_MultipleTimesWithoutReturn_ReturnsDifferentHandles() + { + var pool = new UniformUnmanagedMemoryPool(128, 10); + using var cleanup = new CleanupUtil(pool); + UnmanagedMemoryHandle[] a = pool.Rent(2); + cleanup.Register(a); + UnmanagedMemoryHandle b = pool.Rent(); + cleanup.Register(b); + + Assert.NotEqual(a[0].Handle, a[1].Handle); + Assert.NotEqual(a[0].Handle, b.Handle); + Assert.NotEqual(a[1].Handle, b.Handle); + } + + [Theory] + [InlineData(4, 2, 10)] + [InlineData(5, 1, 6)] + [InlineData(12, 4, 12)] + public void RentReturnRent_SameBuffers(int totalCount, int rentUnit, int capacity) + { + var pool = new UniformUnmanagedMemoryPool(128, capacity); + using var cleanup = new CleanupUtil(pool); + var allHandles = new HashSet(); + var handleUnits = new List(); + + UnmanagedMemoryHandle[] handles; + for (int i = 0; i < totalCount; i += rentUnit) + { + handles = pool.Rent(rentUnit); + Assert.NotNull(handles); + handleUnits.Add(handles); + foreach (UnmanagedMemoryHandle array in handles) + { + allHandles.Add(array); + } + + // Allocate some memory, so potential new pool allocation wouldn't allocated the same memory: + cleanup.Register(Marshal.AllocHGlobal(128)); + } + + foreach (UnmanagedMemoryHandle[] arrayUnit in handleUnits) + { + if (arrayUnit.Length == 1) + { + // Test single-array return: + pool.Return(arrayUnit.Single()); + } + else + { + pool.Return(arrayUnit); + } + } + + handles = pool.Rent(totalCount); + + Assert.NotNull(handles); + + foreach (UnmanagedMemoryHandle array in handles) + { + Assert.Contains(array, allHandles); + } + + cleanup.Register(allHandles); + } + + [Fact] + public void Rent_SingleBuffer_OverCapacity_ReturnsInvalidBuffer() + { + var pool = new UniformUnmanagedMemoryPool(7, 1000); + using var cleanup = new CleanupUtil(pool); + UnmanagedMemoryHandle[] initial = pool.Rent(1000); + Assert.NotNull(initial); + cleanup.Register(initial); + UnmanagedMemoryHandle b1 = pool.Rent(); + Assert.True(b1.IsInvalid); + } + + [Theory] + [InlineData(0, 6, 5)] + [InlineData(5, 1, 5)] + [InlineData(4, 7, 10)] + public void Rent_MultiBuffer_OverCapacity_ReturnsNull(int initialRent, int attempt, int capacity) + { + var pool = new UniformUnmanagedMemoryPool(128, capacity); + using var cleanup = new CleanupUtil(pool); + UnmanagedMemoryHandle[] initial = pool.Rent(initialRent); + Assert.NotNull(initial); + cleanup.Register(initial); + UnmanagedMemoryHandle[] b1 = pool.Rent(attempt); + Assert.Null(b1); + } + + [Theory] + [InlineData(0, 5, 5)] + [InlineData(5, 1, 6)] + [InlineData(4, 7, 11)] + [InlineData(3, 3, 7)] + public void Rent_MultiBuff_BelowCapacity_Succeeds(int initialRent, int attempt, int capacity) + { + var pool = new UniformUnmanagedMemoryPool(128, capacity); + using var cleanup = new CleanupUtil(pool); + UnmanagedMemoryHandle[] b0 = pool.Rent(initialRent); + Assert.NotNull(b0); + cleanup.Register(b0); + UnmanagedMemoryHandle[] b1 = pool.Rent(attempt); + Assert.NotNull(b1); + cleanup.Register(b1); + } + + public static readonly bool IsNotMacOS = !TestEnvironment.IsOSX; + + // TODO: Investigate MacOS failures + [ConditionalTheory(nameof(IsNotMacOS))] + [InlineData(false)] + [InlineData(true)] + public void RentReturnRelease_SubsequentRentReturnsDifferentHandles(bool multiple) + { + var pool = new UniformUnmanagedMemoryPool(16, 16); + using var cleanup = new CleanupUtil(pool); + UnmanagedMemoryHandle b0 = pool.Rent(); + IntPtr h0 = b0.Handle; + UnmanagedMemoryHandle b1 = pool.Rent(); + IntPtr h1 = b1.Handle; + pool.Return(b0); + pool.Return(b1); + pool.Release(); + + // Do some unmanaged allocations to make sure new pool buffers are different: + IntPtr[] dummy = Enumerable.Range(0, 100).Select(_ => Marshal.AllocHGlobal(16)).ToArray(); + cleanup.Register(dummy); + + if (multiple) + { + UnmanagedMemoryHandle b = pool.Rent(); + cleanup.Register(b); + Assert.NotEqual(h0, b.Handle); + Assert.NotEqual(h1, b.Handle); + } + else + { + UnmanagedMemoryHandle[] b = pool.Rent(2); + cleanup.Register(b); + Assert.NotEqual(h0, b[0].Handle); + Assert.NotEqual(h1, b[0].Handle); + Assert.NotEqual(h0, b[1].Handle); + Assert.NotEqual(h1, b[1].Handle); + } + } + + [Fact] + public void Release_ShouldFreeRetainedMemory() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + var pool = new UniformUnmanagedMemoryPool(16, 16); + UnmanagedMemoryHandle a = pool.Rent(); + UnmanagedMemoryHandle[] b = pool.Rent(2); + pool.Return(a); + pool.Return(b); + + Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); + pool.Release(); + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + } + + [Fact] + public void RentReturn_IsThreadSafe() + { + int count = Environment.ProcessorCount * 200; + var pool = new UniformUnmanagedMemoryPool(8, count); + using var cleanup = new CleanupUtil(pool); + var rnd = new Random(0); + + Parallel.For(0, Environment.ProcessorCount, (int i) => + { + var allHandles = new List(); + int pauseAt = rnd.Next(100); + for (int j = 0; j < 100; j++) + { + UnmanagedMemoryHandle[] data = pool.Rent(2); + + GetSpan(data[0], pool.BufferLength).Fill((byte)i); + GetSpan(data[1], pool.BufferLength).Fill((byte)i); + allHandles.Add(data[0]); + allHandles.Add(data[1]); + + if (j == pauseAt) + { + Thread.Sleep(15); + } + } + + Span expected = new byte[8]; + expected.Fill((byte)i); + + foreach (UnmanagedMemoryHandle h in allHandles) + { + Assert.True(expected.SequenceEqual(GetSpan(h, pool.BufferLength))); + pool.Return(new[] { h }); + } + }); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void LeakPool_FinalizerShouldFreeRetainedHandles(bool withGuardedBuffers) + { + RemoteExecutor.Invoke(RunTest, withGuardedBuffers.ToString()).Dispose(); + + static void RunTest(string withGuardedBuffersInner) + { + LeakPoolInstance(bool.Parse(withGuardedBuffersInner)); + Assert.Equal(20, UnmanagedMemoryHandle.TotalOutstandingHandles); + GC.Collect(); + GC.WaitForPendingFinalizers(); + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void LeakPoolInstance(bool withGuardedBuffers) + { + var pool = new UniformUnmanagedMemoryPool(16, 128); + if (withGuardedBuffers) + { + UnmanagedMemoryHandle h = pool.Rent(); + _ = pool.CreateGuardedBuffer(h, 10, false); + UnmanagedMemoryHandle[] g = pool.Rent(19); + _ = pool.CreateGroupLifetimeGuard(g); + } + else + { + pool.Return(pool.Rent(20)); + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs new file mode 100644 index 000000000..45a7cc278 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -0,0 +1,383 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Memory.Internals; +using SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.Allocators +{ + public class UniformUnmanagedPoolMemoryAllocatorTests + { + public class BufferTests1 : BufferTestSuite + { + private static MemoryAllocator CreateMemoryAllocator() => + new UniformUnmanagedMemoryPoolMemoryAllocator( + sharedArrayPoolThresholdInBytes: 1024, + poolBufferSizeInBytes: 2048, + maxPoolSizeInBytes: 2048 * 4, + unmanagedBufferSizeInBytes: 4096); + + public BufferTests1() + : base(CreateMemoryAllocator()) + { + } + } + + public class BufferTests2 : BufferTestSuite + { + private static MemoryAllocator CreateMemoryAllocator() => + new UniformUnmanagedMemoryPoolMemoryAllocator( + sharedArrayPoolThresholdInBytes: 512, + poolBufferSizeInBytes: 1024, + maxPoolSizeInBytes: 1024 * 4, + unmanagedBufferSizeInBytes: 2048); + + public BufferTests2() + : base(CreateMemoryAllocator()) + { + } + } + + public static TheoryData AllocateData = + new TheoryData() + { + { default(S4), 16, 256, 256, 1024, 64, 64, 1, -1, 64 }, + { default(S4), 16, 256, 256, 1024, 256, 256, 1, -1, 256 }, + { default(S4), 16, 256, 256, 1024, 250, 256, 1, -1, 250 }, + { default(S4), 16, 256, 256, 1024, 248, 250, 1, -1, 248 }, + { default(S4), 16, 1024, 2048, 4096, 512, 256, 2, 256, 256 }, + { default(S4), 16, 1024, 1024, 1024, 511, 256, 2, 256, 255 }, + }; + + [Theory] + [MemberData(nameof(AllocateData))] + public void AllocateGroup_BufferSizesAreCorrect( + T dummy, + int sharedArrayPoolThresholdInBytes, + int maxContiguousPoolBufferInBytes, + int maxPoolSizeInBytes, + int maxCapacityOfUnmanagedBuffers, + long allocationLengthInElements, + int bufferAlignmentInElements, + int expectedNumberOfBuffers, + int expectedBufferSize, + int expectedSizeOfLastBuffer) + where T : struct + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator( + sharedArrayPoolThresholdInBytes, + maxContiguousPoolBufferInBytes, + maxPoolSizeInBytes, + maxCapacityOfUnmanagedBuffers); + + using MemoryGroup g = allocator.AllocateGroup(allocationLengthInElements, bufferAlignmentInElements); + MemoryGroupTests.Allocate.ValidateAllocateMemoryGroup( + expectedNumberOfBuffers, + expectedBufferSize, + expectedSizeOfLastBuffer, + g); + } + + [Fact] + public void AllocateGroup_MultipleTimes_ExceedPoolLimit() + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator( + 64, + 128, + 1024, + 1024); + + var groups = new List>(); + for (int i = 0; i < 16; i++) + { + int lengthInElements = 128 / Unsafe.SizeOf(); + MemoryGroup g = allocator.AllocateGroup(lengthInElements, 32); + MemoryGroupTests.Allocate.ValidateAllocateMemoryGroup(1, -1, lengthInElements, g); + groups.Add(g); + } + + foreach (MemoryGroup g in groups) + { + g.Dispose(); + } + } + + [Fact] + public unsafe void Allocate_MemoryIsPinnableMultipleTimes() + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(null); + using IMemoryOwner memoryOwner = allocator.Allocate(100); + + using (MemoryHandle pin = memoryOwner.Memory.Pin()) + { + Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer); + } + + using (MemoryHandle pin = memoryOwner.Memory.Pin()) + { + Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer); + } + } + + [Fact] + public void MemoryAllocator_Create_WithoutSettings_AllocatesDiscontiguousMemory() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + var allocator = MemoryAllocator.Create(); + long sixteenMegabytes = 16 * (1 << 20); + + // Should allocate 4 times 4MB discontiguos blocks: + MemoryGroup g = allocator.AllocateGroup(sixteenMegabytes, 1024); + Assert.Equal(4, g.Count); + } + } + + [Fact] + public void MemoryAllocator_Create_LimitPoolSize() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + var allocator = MemoryAllocator.Create(new MemoryAllocatorOptions() + { + MaximumPoolSizeMegabytes = 8 + }); + + MemoryGroup g0 = allocator.AllocateGroup(B(8), 1024); + MemoryGroup g1 = allocator.AllocateGroup(B(8), 1024); + ref byte r0 = ref MemoryMarshal.GetReference(g0[0].Span); + ref byte r1 = ref MemoryMarshal.GetReference(g1[0].Span); + g0.Dispose(); + g1.Dispose(); + + // Do some unmanaged allocations to make sure new non-pooled unmanaged allocations will grab different memory: + IntPtr dummy1 = Marshal.AllocHGlobal((IntPtr)B(8)); + IntPtr dummy2 = Marshal.AllocHGlobal((IntPtr)B(8)); + + using MemoryGroup g2 = allocator.AllocateGroup(B(8), 1024); + using MemoryGroup g3 = allocator.AllocateGroup(B(8), 1024); + ref byte r2 = ref MemoryMarshal.GetReference(g2[0].Span); + ref byte r3 = ref MemoryMarshal.GetReference(g3[0].Span); + + Assert.True(Unsafe.AreSame(ref r0, ref r2)); + Assert.False(Unsafe.AreSame(ref r1, ref r3)); + + Marshal.FreeHGlobal(dummy1); + Marshal.FreeHGlobal(dummy2); + } + + static long B(int value) => value << 20; + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void BufferDisposal_ReturnsToPool(bool shared) + { + RemoteExecutor.Invoke(RunTest, shared.ToString()).Dispose(); + + static void RunTest(string sharedStr) + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(512, 1024, 16 * 1024, 1024); + IMemoryOwner buffer0 = allocator.Allocate(bool.Parse(sharedStr) ? 300 : 600); + buffer0.GetSpan()[0] = 42; + buffer0.Dispose(); + using IMemoryOwner buffer1 = allocator.Allocate(bool.Parse(sharedStr) ? 300 : 600); + Assert.Equal(42, buffer1.GetSpan()[0]); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void MemoryGroupDisposal_ReturnsToPool(bool shared) + { + RemoteExecutor.Invoke(RunTest, shared.ToString()).Dispose(); + + static void RunTest(string sharedStr) + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(512, 1024, 16 * 1024, 1024); + MemoryGroup g0 = allocator.AllocateGroup(bool.Parse(sharedStr) ? 300 : 600, 100); + g0.Single().Span[0] = 42; + g0.Dispose(); + using MemoryGroup g1 = allocator.AllocateGroup(bool.Parse(sharedStr) ? 300 : 600, 100); + Assert.Equal(42, g1.Single().Span[0]); + } + } + + [Fact] + public void ReleaseRetainedResources_ShouldFreePooledMemory() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + static void RunTest() + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(128, 512, 16 * 512, 1024); + MemoryGroup g = allocator.AllocateGroup(2048, 128); + g.Dispose(); + Assert.Equal(4, UnmanagedMemoryHandle.TotalOutstandingHandles); + allocator.ReleaseRetainedResources(); + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + } + + [Fact] + public void ReleaseRetainedResources_DoesNotFreeOutstandingBuffers() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + static void RunTest() + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(128, 512, 16 * 512, 1024); + IMemoryOwner b = allocator.Allocate(256); + MemoryGroup g = allocator.AllocateGroup(2048, 128); + Assert.Equal(5, UnmanagedMemoryHandle.TotalOutstandingHandles); + allocator.ReleaseRetainedResources(); + Assert.Equal(5, UnmanagedMemoryHandle.TotalOutstandingHandles); + b.Dispose(); + g.Dispose(); + Assert.Equal(5, UnmanagedMemoryHandle.TotalOutstandingHandles); + allocator.ReleaseRetainedResources(); + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + } + + [Theory] + [InlineData(300)] // Group of single SharedArrayPoolBuffer + [InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer + [InlineData(1200)] // Group of two UniformUnmanagedMemoryPool buffers + public void AllocateMemoryGroup_Finalization_ReturnsToPool(int length) + { + if (TestEnvironment.IsOSX) + { + // Skip on OSX: https://github.com/SixLabors/ImageSharp/issues/1887 + return; + } + + if (!TestEnvironment.RunsOnCI) + { + // This may fail in local runs resulting in high memory load. + // Remove the condition for local debugging! + return; + } + + // RunTest(length.ToString()); + RemoteExecutor.Invoke(RunTest, length.ToString()).Dispose(); + + static void RunTest(string lengthStr) + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(512, 1024, 16 * 1024, 1024); + int lengthInner = int.Parse(lengthStr); + + AllocateGroupAndForget(allocator, lengthInner); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + + AllocateGroupAndForget(allocator, lengthInner, true); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + + using MemoryGroup g = allocator.AllocateGroup(lengthInner, 100); + Assert.Equal(42, g.First().Span[0]); + } + } + + private static void AllocateGroupAndForget(UniformUnmanagedMemoryPoolMemoryAllocator allocator, int length, bool check = false) + { + MemoryGroup g = allocator.AllocateGroup(length, 100); + if (check) + { + Assert.Equal(42, g.First().Span[0]); + } + + g.First().Span[0] = 42; + + if (length < 512) + { + // For ArrayPool.Shared, first array will be returned to the TLS storage of the finalizer thread, + // repeat rental to make sure per-core buckets are also utilized. + MemoryGroup g1 = allocator.AllocateGroup(length, 100); + g1.First().Span[0] = 42; + } + } + + [Theory] + [InlineData(300)] // Group of single SharedArrayPoolBuffer + [InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer + public void AllocateSingleMemoryOwner_Finalization_ReturnsToPool(int length) + { + if (TestEnvironment.IsOSX) + { + // Skip on OSX: https://github.com/SixLabors/ImageSharp/issues/1887 + return; + } + + if (!TestEnvironment.RunsOnCI) + { + // This may fail in local runs resulting in high memory load. + // Remove the condition for local debugging! + return; + } + + // RunTest(length.ToString()); + RemoteExecutor.Invoke(RunTest, length.ToString()).Dispose(); + + static void RunTest(string lengthStr) + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(512, 1024, 16 * 1024, 1024); + int lengthInner = int.Parse(lengthStr); + + AllocateSingleAndForget(allocator, lengthInner); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + + AllocateSingleAndForget(allocator, lengthInner, true); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + + using IMemoryOwner g = allocator.Allocate(lengthInner); + Assert.Equal(42, g.GetSpan()[0]); + GC.KeepAlive(allocator); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void AllocateSingleAndForget(UniformUnmanagedMemoryPoolMemoryAllocator allocator, int length, bool check = false) + { + IMemoryOwner g = allocator.Allocate(length); + if (check) + { + Assert.Equal(42, g.GetSpan()[0]); + } + + g.GetSpan()[0] = 42; + + if (length < 512) + { + // For ArrayPool.Shared, first array will be returned to the TLS storage of the finalizer thread, + // repeat rental to make sure per-core buckets are also utilized. + IMemoryOwner g1 = allocator.Allocate(length); + g1.GetSpan()[0] = 42; + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedBufferTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedBufferTests.cs new file mode 100644 index 000000000..68251be86 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedBufferTests.cs @@ -0,0 +1,93 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections.Generic; +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Memory.Internals; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.Allocators +{ + public class UnmanagedBufferTests + { + public class AllocatorBufferTests : BufferTestSuite + { + public AllocatorBufferTests() + : base(new UnmanagedMemoryAllocator(1024 * 64)) + { + } + } + + [Fact] + public void Allocate_CreatesValidBuffer() + { + using var buffer = UnmanagedBuffer.Allocate(10); + Span span = buffer.GetSpan(); + Assert.Equal(10, span.Length); + span[9] = 123; + Assert.Equal(123, span[9]); + } + + [Fact] + public unsafe void Dispose_DoesNotReleaseOutstandingReferences() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + var buffer = UnmanagedBuffer.Allocate(10); + Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); + Span span = buffer.GetSpan(); + + // Pin should AddRef + using (MemoryHandle h = buffer.Pin()) + { + int* ptr = (int*)h.Pointer; + ((IDisposable)buffer).Dispose(); + Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); + ptr[3] = 13; + Assert.Equal(13, span[3]); + } // Unpin should ReleaseRef + + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + } + + [Theory] + [InlineData(2)] + [InlineData(12)] + public void BufferFinalization_TracksAllocations(int count) + { + RemoteExecutor.Invoke(RunTest, count.ToString()).Dispose(); + + static void RunTest(string countStr) + { + int countInner = int.Parse(countStr); + List> l = FillList(countInner); + + l.RemoveRange(0, l.Count / 2); + + GC.Collect(); + GC.WaitForPendingFinalizers(); + + Assert.Equal(countInner / 2, l.Count); // This is here to prevent eager finalization of the list's elements + Assert.Equal(countInner / 2, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + + static List> FillList(int countInner) + { + var l = new List>(); + for (int i = 0; i < countInner; i++) + { + var h = UnmanagedBuffer.Allocate(42); + l.Add(h); + } + + return l; + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs new file mode 100644 index 000000000..a3f827355 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory.Internals; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.Allocators +{ + public class UnmanagedMemoryHandleTests + { + [Fact] + public unsafe void Allocate_AllocatesReadWriteMemory() + { + var h = UnmanagedMemoryHandle.Allocate(128); + Assert.False(h.IsInvalid); + Assert.True(h.IsValid); + byte* ptr = (byte*)h.Handle; + for (int i = 0; i < 128; i++) + { + ptr[i] = (byte)i; + } + + for (int i = 0; i < 128; i++) + { + Assert.Equal((byte)i, ptr[i]); + } + + h.Free(); + } + + [Fact] + public void Free_ClosesHandle() + { + var h = UnmanagedMemoryHandle.Allocate(128); + h.Free(); + Assert.True(h.IsInvalid); + Assert.Equal(IntPtr.Zero, h.Handle); + } + + [Theory] + [InlineData(1)] + [InlineData(13)] + public void Create_Free_AllocationsAreTracked(int count) + { + RemoteExecutor.Invoke(RunTest, count.ToString()).Dispose(); + + static void RunTest(string countStr) + { + int countInner = int.Parse(countStr); + var l = new List(); + for (int i = 0; i < countInner; i++) + { + Assert.Equal(i, UnmanagedMemoryHandle.TotalOutstandingHandles); + var h = UnmanagedMemoryHandle.Allocate(42); + Assert.Equal(i + 1, UnmanagedMemoryHandle.TotalOutstandingHandles); + l.Add(h); + } + + for (int i = 0; i < countInner; i++) + { + Assert.Equal(countInner - i, UnmanagedMemoryHandle.TotalOutstandingHandles); + l[i].Free(); + Assert.Equal(countInner - i - 1, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + } + } + + [Fact] + public void Equality_WhenTrue() + { + var h1 = UnmanagedMemoryHandle.Allocate(10); + UnmanagedMemoryHandle h2 = h1; + + Assert.True(h1.Equals(h2)); + Assert.True(h2.Equals(h1)); + Assert.True(h1 == h2); + Assert.False(h1 != h2); + Assert.True(h1.GetHashCode() == h2.GetHashCode()); + h1.Free(); + } + + [Fact] + public void Equality_WhenFalse() + { + var h1 = UnmanagedMemoryHandle.Allocate(10); + var h2 = UnmanagedMemoryHandle.Allocate(10); + + Assert.False(h1.Equals(h2)); + Assert.False(h2.Equals(h1)); + Assert.False(h1 == h2); + Assert.True(h1 != h2); + + h1.Free(); + h2.Free(); + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 9092cbb08..73a0f4d60 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -68,6 +68,25 @@ namespace SixLabors.ImageSharp.Tests.Memory } } + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Construct_PreferContiguousImageBuffers_AllocatesContiguousRegardlessOfCapacity(bool useSizeOverload) + { + this.MemoryAllocator.BufferCapacityInBytes = 10_000; + + using Buffer2D buffer = useSizeOverload ? + this.MemoryAllocator.Allocate2D( + new Size(200, 200), + preferContiguosImageBuffers: true) : + this.MemoryAllocator.Allocate2D( + 200, + 200, + preferContiguosImageBuffers: true); + Assert.Equal(1, buffer.FastMemoryGroup.Count); + Assert.Equal(200 * 200, buffer.FastMemoryGroup.TotalLength); + } + [Theory] [InlineData(50, 10, 20, 4)] public void Allocate2DOveraligned(int bufferCapacity, int width, int height, int alignmentMultiplier) @@ -108,7 +127,7 @@ namespace SixLabors.ImageSharp.Tests.Memory using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) { - Span span = buffer.GetRowSpan(y); + Span span = buffer.DangerousGetRowSpan(y); Assert.Equal(width, span.Length); @@ -158,7 +177,7 @@ namespace SixLabors.ImageSharp.Tests.Memory this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity; using Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height); - Exception ex = Assert.ThrowsAny(() => buffer.GetRowSpan(y)); + Exception ex = Assert.ThrowsAny(() => buffer.DangerousGetRowSpan(y)); Assert.True(ex is ArgumentOutOfRangeException || ex is IndexOutOfRangeException); } @@ -249,16 +268,14 @@ namespace SixLabors.ImageSharp.Tests.Memory Buffer2D.SwapOrCopyContent(dest, source); } - int actual1 = dest.GetRowSpan(0)[0]; - int actual2 = dest.GetRowSpan(0)[0]; + int actual1 = dest.DangerousGetRowSpan(0)[0]; + int actual2 = dest.DangerousGetRowSpan(0)[0]; int actual3 = dest.GetSafeRowMemory(0).Span[0]; - int actual4 = dest.GetFastRowMemory(0).Span[0]; int actual5 = dest[0, 0]; Assert.Equal(1, actual1); Assert.Equal(1, actual2); Assert.Equal(1, actual3); - Assert.Equal(1, actual4); Assert.Equal(1, actual5); } @@ -280,7 +297,7 @@ namespace SixLabors.ImageSharp.Tests.Memory for (int y = 0; y < b.Height; y++) { - Span row = b.GetRowSpan(y); + Span row = b.DangerousGetRowSpan(y); Span s = row.Slice(startIndex, columnCount); Span d = row.Slice(destIndex, columnCount); @@ -303,7 +320,7 @@ namespace SixLabors.ImageSharp.Tests.Memory for (int y = 0; y < b.Height; y++) { - Span row = b.GetRowSpan(y); + Span row = b.DangerousGetRowSpan(y); Span s = row.Slice(0, 22); Span d = row.Slice(50, 22); diff --git a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs index 0dfc5f36b..76e55aa3a 100644 --- a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs +++ b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs @@ -127,7 +127,7 @@ namespace SixLabors.ImageSharp.Tests.Memory for (int y = 0; y < 13; y++) { - Span row = buffer.GetRowSpan(y); + Span row = buffer.DangerousGetRowSpan(y); Assert.True(row.SequenceEqual(emptyRow)); } } @@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Tests.Memory for (int y = region.Rectangle.Y; y < region.Rectangle.Bottom; y++) { - Span span = buffer.GetRowSpan(y).Slice(region.Rectangle.X, region.Width); + Span span = buffer.DangerousGetRowSpan(y).Slice(region.Rectangle.X, region.Width); Assert.True(span.SequenceEqual(new int[region.Width])); } } diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs index e9094fcca..adfafcb89 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs @@ -2,10 +2,15 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Memory.Internals; using Xunit; +using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers { @@ -14,8 +19,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers public class Allocate : MemoryGroupTestsBase { #pragma warning disable SA1509 - public static TheoryData AllocateData = - new TheoryData() + public static TheoryData AllocateData = new() { { default(S5), 22, 4, 4, 1, 4, 4 }, { default(S5), 22, 4, 7, 2, 4, 3 }, @@ -39,6 +43,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers { default(S4), 50, 7, 21, 3, 7, 7 }, { default(S4), 50, 7, 23, 4, 7, 2 }, { default(S4), 50, 6, 13, 2, 12, 1 }, + { default(S4), 1024, 20, 800, 4, 240, 80 }, { default(short), 200, 50, 49, 1, 49, 49 }, { default(short), 200, 50, 1, 1, 1, 1 }, @@ -47,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers [Theory] [MemberData(nameof(AllocateData))] - public void BufferSizesAreCorrect( + public void Allocate_FromMemoryAllocator_BufferSizesAreCorrect( T dummy, int bufferCapacity, int bufferAlignment, @@ -63,6 +68,96 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers using var g = MemoryGroup.Allocate(this.MemoryAllocator, totalLength, bufferAlignment); // Assert: + ValidateAllocateMemoryGroup(expectedNumberOfBuffers, expectedBufferSize, expectedSizeOfLastBuffer, g); + } + + [Theory] + [MemberData(nameof(AllocateData))] + public void Allocate_FromPool_BufferSizesAreCorrect( + T dummy, + int bufferCapacity, + int bufferAlignment, + long totalLength, + int expectedNumberOfBuffers, + int expectedBufferSize, + int expectedSizeOfLastBuffer) + where T : struct + { + if (totalLength == 0) + { + // Invalid case for UniformByteArrayPool allocations + return; + } + + var pool = new UniformUnmanagedMemoryPool(bufferCapacity, expectedNumberOfBuffers); + + // Act: + Assert.True(MemoryGroup.TryAllocate(pool, totalLength, bufferAlignment, AllocationOptions.None, out MemoryGroup g)); + + // Assert: + ValidateAllocateMemoryGroup(expectedNumberOfBuffers, expectedBufferSize, expectedSizeOfLastBuffer, g); + g.Dispose(); + } + + [Theory] + [InlineData(AllocationOptions.None)] + [InlineData(AllocationOptions.Clean)] + public unsafe void Allocate_FromPool_AllocationOptionsAreApplied(AllocationOptions options) + { + var pool = new UniformUnmanagedMemoryPool(10, 5); + UnmanagedMemoryHandle[] buffers = pool.Rent(5); + foreach (UnmanagedMemoryHandle b in buffers) + { + new Span(b.Pointer, pool.BufferLength).Fill(42); + } + + pool.Return(buffers); + + Assert.True(MemoryGroup.TryAllocate(pool, 50, 10, options, out MemoryGroup g)); + Span expected = stackalloc byte[10]; + expected.Fill((byte)(options == AllocationOptions.Clean ? 0 : 42)); + foreach (Memory memory in g) + { + Assert.True(expected.SequenceEqual(memory.Span)); + } + + g.Dispose(); + } + + [Theory] + [InlineData(64, 4, 60, 240, true)] + [InlineData(64, 4, 60, 244, false)] + public void Allocate_FromPool_AroundLimit( + int bufferCapacityBytes, + int poolCapacity, + int alignmentBytes, + int requestBytes, + bool shouldSucceed) + { + var pool = new UniformUnmanagedMemoryPool(bufferCapacityBytes, poolCapacity); + int alignmentElements = alignmentBytes / Unsafe.SizeOf(); + int requestElements = requestBytes / Unsafe.SizeOf(); + + Assert.Equal(shouldSucceed, MemoryGroup.TryAllocate(pool, requestElements, alignmentElements, AllocationOptions.None, out MemoryGroup g)); + if (shouldSucceed) + { + Assert.NotNull(g); + } + else + { + Assert.Null(g); + } + + g?.Dispose(); + } + + internal static void ValidateAllocateMemoryGroup( + int expectedNumberOfBuffers, + int expectedBufferSize, + int expectedSizeOfLastBuffer, + MemoryGroup g) + where T : struct + { Assert.Equal(expectedNumberOfBuffers, g.Count); if (expectedBufferSize >= 0) @@ -100,6 +195,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers public void MemoryAllocatorIsUtilizedCorrectly(AllocationOptions allocationOptions) { this.MemoryAllocator.BufferCapacityInBytes = 200; + this.MemoryAllocator.EnableNonThreadSafeLogging(); HashSet bufferHashes; @@ -125,4 +221,16 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers } } } + + [StructLayout(LayoutKind.Sequential, Size = 5)] + internal struct S5 + { + public override string ToString() => "S5"; + } + + [StructLayout(LayoutKind.Sequential, Size = 4)] + internal struct S4 + { + public override string ToString() => "S4"; + } } diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs index 3ab5797dd..13e47bdee 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs @@ -119,6 +119,14 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers Assert.True(group[0].Span.SequenceEqual(data0)); Assert.True(group[1].Span.SequenceEqual(data1)); Assert.True(group[2].Span.SequenceEqual(data2)); + + int cnt = 0; + int[][] allData = { data0, data1, data2 }; + foreach (Memory memory in group) + { + Assert.True(memory.Span.SequenceEqual(allData[cnt])); + cnt++; + } } public static TheoryData GetBoundedSlice_SuccessData = new TheoryData() @@ -229,17 +237,5 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers target[k] = source[k] * 2; } } - - [StructLayout(LayoutKind.Sequential, Size = 5)] - private struct S5 - { - public override string ToString() => "S5"; - } - - [StructLayout(LayoutKind.Sequential, Size = 4)] - private struct S4 - { - public override string ToString() => "S4"; - } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Abgr32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Abgr32Tests.cs new file mode 100644 index 000000000..278de3357 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/Abgr32Tests.cs @@ -0,0 +1,148 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.PixelFormats +{ + [Trait("Category", "PixelFormats")] + public class Abgr32Tests + { + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var color1 = new Abgr32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); + var color2 = new Abgr32(byte.MaxValue, byte.MaxValue, byte.MaxValue); + + Assert.Equal(color1, color2); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new Abgr32(0, 0, byte.MaxValue, byte.MaxValue); + var color2 = new Abgr32(byte.MaxValue, byte.MaxValue, byte.MaxValue); + + Assert.NotEqual(color1, color2); + } + + public static readonly TheoryData ColorData = + new() + { + { 1, 2, 3, 4 }, + { 4, 5, 6, 7 }, + { 0, 255, 42, 0 }, + { 1, 2, 3, 255 } + }; + + [Theory] + [MemberData(nameof(ColorData))] + public void Constructor(byte b, byte g, byte r, byte a) + { + var p = new Abgr32(r, g, b, a); + + Assert.Equal(r, p.R); + Assert.Equal(g, p.G); + Assert.Equal(b, p.B); + Assert.Equal(a, p.A); + } + + [Fact] + public unsafe void ByteLayoutIsSequentialBgra() + { + var color = new Abgr32(1, 2, 3, 4); + byte* ptr = (byte*)&color; + + Assert.Equal(4, ptr[0]); + Assert.Equal(3, ptr[1]); + Assert.Equal(2, ptr[2]); + Assert.Equal(1, ptr[3]); + } + + [Theory] + [MemberData(nameof(ColorData))] + public void Equality_WhenTrue(byte r, byte g, byte b, byte a) + { + var x = new Abgr32(r, g, b, a); + var y = new Abgr32(r, g, b, a); + + Assert.True(x.Equals(y)); + Assert.True(x.Equals((object)y)); + Assert.Equal(x.GetHashCode(), y.GetHashCode()); + } + + [Theory] + [InlineData(1, 2, 3, 4, 1, 2, 3, 5)] + [InlineData(0, 0, 255, 0, 0, 0, 244, 0)] + [InlineData(0, 255, 0, 0, 0, 244, 0, 0)] + [InlineData(1, 255, 0, 0, 0, 255, 0, 0)] + public void Equality_WhenFalse(byte b1, byte g1, byte r1, byte a1, byte b2, byte g2, byte r2, byte a2) + { + var x = new Abgr32(r1, g1, b1, a1); + var y = new Abgr32(r2, g2, b2, a2); + + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + } + + [Fact] + public void FromRgba32() + { + var abgr = default(Abgr32); + abgr.FromRgba32(new Rgba32(1, 2, 3, 4)); + + Assert.Equal(1, abgr.R); + Assert.Equal(2, abgr.G); + Assert.Equal(3, abgr.B); + Assert.Equal(4, abgr.A); + } + + private static Vector4 Vec(byte r, byte g, byte b, byte a = 255) => new Vector4( + r / 255f, + g / 255f, + b / 255f, + a / 255f); + + [Fact] + public void FromVector4() + { + var c = default(Abgr32); + c.FromVector4(Vec(1, 2, 3, 4)); + + Assert.Equal(1, c.R); + Assert.Equal(2, c.G); + Assert.Equal(3, c.B); + Assert.Equal(4, c.A); + } + + [Fact] + public void ToVector4() + { + var abgr = new Abgr32(1, 2, 3, 4); + + Assert.Equal(Vec(1, 2, 3, 4), abgr.ToVector4()); + } + + [Fact] + public void Abgr32_FromBgra5551() + { + // arrange + var abgr = default(Abgr32); + uint expected = uint.MaxValue; + + // act + abgr.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, abgr.PackedValue); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs index 4b8f4c2ea..7a2f29e2c 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs @@ -96,12 +96,13 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats [Fact] public void FromRgba32() { - var rgb = default(Rgb24); - rgb.FromRgba32(new Rgba32(1, 2, 3, 4)); + var bgra = default(Bgra32); + bgra.FromRgba32(new Rgba32(1, 2, 3, 4)); - Assert.Equal(1, rgb.R); - Assert.Equal(2, rgb.G); - Assert.Equal(3, rgb.B); + Assert.Equal(1, bgra.R); + Assert.Equal(2, bgra.G); + Assert.Equal(3, bgra.B); + Assert.Equal(4, bgra.A); } private static Vector4 Vec(byte r, byte g, byte b, byte a = 255) => new Vector4( diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs index 8b3483145..e51ee233c 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs @@ -60,6 +60,21 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats return buffer; } + public static byte[] MakeAbgr32ByteArray(byte r, byte g, byte b, byte a) + { + var buffer = new byte[256]; + + for (int i = 0; i < buffer.Length; i += 4) + { + buffer[i] = a; + buffer[i + 1] = b; + buffer[i + 2] = g; + buffer[i + 3] = r; + } + + return buffer; + } + internal static void To( Configuration configuration, ReadOnlySpan sourcePixels, diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs index 315f9f776..986f4189b 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs @@ -56,6 +56,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(expected, actual); } + + [Theory] + [MemberData(nameof(RgbaData))] + public void ToAbgr32(byte r, byte g, byte b, byte a) + { + byte[] source = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a); + byte[] actual = new byte[source.Length]; + + PixelConverter.FromRgba32.ToAbgr32(source, actual); + + byte[] expected = ReferenceImplementations.MakeAbgr32ByteArray(r, g, b, a); + + Assert.Equal(expected, actual); + } } public class FromArgb32 : PixelConverterTests @@ -119,5 +133,50 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(expected, actual); } } + + public class FromAbgr32 : PixelConverterTests + { + [Theory] + [MemberData(nameof(RgbaData))] + public void ToArgb32(byte r, byte g, byte b, byte a) + { + byte[] source = ReferenceImplementations.MakeAbgr32ByteArray(r, g, b, a); + byte[] actual = new byte[source.Length]; + + PixelConverter.FromAbgr32.ToArgb32(source, actual); + + byte[] expected = ReferenceImplementations.MakeArgb32ByteArray(r, g, b, a); + + Assert.Equal(expected, actual); + } + + [Theory] + [MemberData(nameof(RgbaData))] + public void ToRgba32(byte r, byte g, byte b, byte a) + { + byte[] source = ReferenceImplementations.MakeAbgr32ByteArray(r, g, b, a); + byte[] actual = new byte[source.Length]; + + PixelConverter.FromAbgr32.ToRgba32(source, actual); + + byte[] expected = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a); + + Assert.Equal(expected, actual); + } + + [Theory] + [MemberData(nameof(RgbaData))] + public void ToBgra32(byte r, byte g, byte b, byte a) + { + byte[] source = ReferenceImplementations.MakeAbgr32ByteArray(r, g, b, a); + byte[] actual = new byte[source.Length]; + + PixelConverter.FromAbgr32.ToBgra32(source, actual); + + byte[] expected = ReferenceImplementations.MakeBgra32ByteArray(r, g, b, a); + + Assert.Equal(expected, actual); + } + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs index 1069eb9ac..10b06df3b 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs @@ -12,8 +12,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { public partial class PixelOperationsTests { - - public partial class A8_OperationsTests : PixelOperationsTests + public partial class A8_OperationsTests : PixelOperationsTests { public A8_OperationsTests(ITestOutputHelper output) : base(output) @@ -32,7 +31,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class Argb32_OperationsTests : PixelOperationsTests { public Argb32_OperationsTests(ITestOutputHelper output) @@ -52,7 +50,25 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } + public partial class Abgr32_OperationsTests : PixelOperationsTests + { + public Abgr32_OperationsTests(ITestOutputHelper output) + : base(output) + { + } + protected override PixelOperations Operations => Abgr32.PixelOperations.Instance; + + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); + } + } public partial class Bgr24_OperationsTests : PixelOperationsTests { public Bgr24_OperationsTests(ITestOutputHelper output) @@ -72,7 +88,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class Bgr565_OperationsTests : PixelOperationsTests { public Bgr565_OperationsTests(ITestOutputHelper output) @@ -92,7 +107,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class Bgra32_OperationsTests : PixelOperationsTests { public Bgra32_OperationsTests(ITestOutputHelper output) @@ -112,7 +126,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class Bgra4444_OperationsTests : PixelOperationsTests { public Bgra4444_OperationsTests(ITestOutputHelper output) @@ -132,7 +145,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class Bgra5551_OperationsTests : PixelOperationsTests { public Bgra5551_OperationsTests(ITestOutputHelper output) @@ -152,7 +164,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class Byte4_OperationsTests : PixelOperationsTests { public Byte4_OperationsTests(ITestOutputHelper output) @@ -172,7 +183,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class HalfSingle_OperationsTests : PixelOperationsTests { public HalfSingle_OperationsTests(ITestOutputHelper output) @@ -192,7 +202,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class HalfVector2_OperationsTests : PixelOperationsTests { public HalfVector2_OperationsTests(ITestOutputHelper output) @@ -212,7 +221,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class HalfVector4_OperationsTests : PixelOperationsTests { public HalfVector4_OperationsTests(ITestOutputHelper output) @@ -232,7 +240,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class L16_OperationsTests : PixelOperationsTests { public L16_OperationsTests(ITestOutputHelper output) @@ -252,7 +259,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class L8_OperationsTests : PixelOperationsTests { public L8_OperationsTests(ITestOutputHelper output) @@ -272,7 +278,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class La16_OperationsTests : PixelOperationsTests { public La16_OperationsTests(ITestOutputHelper output) @@ -292,7 +297,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class La32_OperationsTests : PixelOperationsTests { public La32_OperationsTests(ITestOutputHelper output) @@ -312,7 +316,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class NormalizedByte2_OperationsTests : PixelOperationsTests { public NormalizedByte2_OperationsTests(ITestOutputHelper output) @@ -332,7 +335,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class NormalizedByte4_OperationsTests : PixelOperationsTests { public NormalizedByte4_OperationsTests(ITestOutputHelper output) @@ -352,7 +354,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class NormalizedShort2_OperationsTests : PixelOperationsTests { public NormalizedShort2_OperationsTests(ITestOutputHelper output) @@ -372,7 +373,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class NormalizedShort4_OperationsTests : PixelOperationsTests { public NormalizedShort4_OperationsTests(ITestOutputHelper output) @@ -392,7 +392,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class Rg32_OperationsTests : PixelOperationsTests { public Rg32_OperationsTests(ITestOutputHelper output) @@ -412,7 +411,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class Rgb24_OperationsTests : PixelOperationsTests { public Rgb24_OperationsTests(ITestOutputHelper output) @@ -432,7 +430,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class Rgb48_OperationsTests : PixelOperationsTests { public Rgb48_OperationsTests(ITestOutputHelper output) @@ -452,7 +449,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class Rgba1010102_OperationsTests : PixelOperationsTests { public Rgba1010102_OperationsTests(ITestOutputHelper output) @@ -472,7 +468,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class Rgba32_OperationsTests : PixelOperationsTests { public Rgba32_OperationsTests(ITestOutputHelper output) @@ -492,7 +487,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class Rgba64_OperationsTests : PixelOperationsTests { public Rgba64_OperationsTests(ITestOutputHelper output) @@ -512,7 +506,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class RgbaVector_OperationsTests : PixelOperationsTests { public RgbaVector_OperationsTests(ITestOutputHelper output) @@ -532,7 +525,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class Short2_OperationsTests : PixelOperationsTests { public Short2_OperationsTests(ITestOutputHelper output) @@ -552,7 +544,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class Short4_OperationsTests : PixelOperationsTests { public Short4_OperationsTests(ITestOutputHelper output) diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude index 8c436eecc..6ef3e914f 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude @@ -16,6 +16,7 @@ using Xunit.Abstractions; { "A8", "Argb32", + "Abgr32", "Bgra32", "Bgra4444", "Bgra5551", @@ -38,6 +39,7 @@ using Xunit.Abstractions; { "A8", "Argb32", + "Abgr32", "Bgr24", "Bgr565", "Bgra32", diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs index a2688359f..2a89c1d39 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs @@ -322,6 +322,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations public static readonly TheoryData Generic_To_Data = new TheoryData { + new TestPixel(), new TestPixel(), new TestPixel(), new TestPixel(), @@ -586,6 +587,50 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations (s, d) => this.Operations.ToBgra32Bytes(this.Configuration, s, d.GetSpan(), count)); } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromAbgr32Bytes(int count) + { + byte[] source = CreateByteTestData(count * 4); + var expected = new TPixel[count]; + + for (int i = 0; i < count; i++) + { + int i4 = i * 4; + + expected[i].FromAbgr32(new Abgr32(source[i4 + 3], source[i4 + 2], source[i4 + 1], source[i4 + 0])); + } + + TestOperation( + source, + expected, + (s, d) => this.Operations.FromAbgr32Bytes(this.Configuration, s, d.GetSpan(), count)); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToAbgr32Bytes(int count) + { + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 4]; + var abgr = default(Abgr32); + + for (int i = 0; i < count; i++) + { + int i4 = i * 4; + abgr.FromScaledVector4(source[i].ToScaledVector4()); + expected[i4] = abgr.A; + expected[i4 + 1] = abgr.B; + expected[i4 + 2] = abgr.G; + expected[i4 + 3] = abgr.R; + } + + TestOperation( + source, + expected, + (s, d) => this.Operations.ToAbgr32Bytes(this.Configuration, s, d.GetSpan(), count)); + } + [Theory] [MemberData(nameof(ArraySizesData))] public void FromBgra5551Bytes(int count) diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs index bc66d404b..aff16d6d8 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs @@ -230,6 +230,22 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(expected, actual); } + [Fact] + public void Rgba32_FromAbgr32_ToRgba32() + { + // arrange + var rgba = default(Rgba32); + var actual = default(Abgr32); + var expected = new Abgr32(0x1a, 0, 0x80, 0); + + // act + rgba.FromAbgr32(expected); + actual.FromRgba32(rgba); + + // assert + Assert.Equal(expected, actual); + } + [Fact] public void Rgba32_FromArgb32_ToArgb32() { diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs index 8daf960d6..e08cd2950 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs @@ -183,6 +183,16 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(expected, actual); } + [Fact] + public void ConstructFrom_Abgr32() + { + var expected = new Rgba64(5140, 9766, 19532, 29555); + var source = new Abgr32(20, 38, 76, 115); + var actual = new Rgba64(source); + + Assert.Equal(expected, actual); + } + [Fact] public void ConstructFrom_Rgb24() { @@ -257,6 +267,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(expected, actual); } + [Fact] + public void ToAbgr32_Retval() + { + // arrange + var source = new Rgba64(5140, 9766, 19532, 29555); + var expected = new Abgr32(20, 38, 76, 115); + + // act + var actual = source.ToAbgr32(); + + // assert + Assert.Equal(expected, actual); + } + [Fact] public void ToRgb24_Retval() { diff --git a/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs index 3709205ac..a95ac9ab9 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs @@ -150,6 +150,24 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(expected, actual); } + [Fact] + public void Short4_FromAbgrb32_ToRgba32() + { + // arrange + var short4 = default(Short4); + var actual = default(Abgr32); + var expected = new Abgr32(20, 38, 0, 255); + + // act + short4.FromAbgr32(expected); + Rgba32 temp = default; + short4.ToRgba32(ref temp); + actual.FromRgba32(temp); + + // assert + Assert.Equal(expected, actual); + } + [Fact] public void Short4_FromRgb48_ToRgb48() { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs index 0465cae94..7ae85c197 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Dithering /// but it is very different because of floating point inaccuracies. /// private static readonly bool SkipAllDitherTests = - !TestEnvironment.Is64BitProcess && string.IsNullOrEmpty(TestEnvironment.NetCoreVersion); + !TestEnvironment.Is64BitProcess && TestEnvironment.NetCoreVersion == null; [Theory] [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs index d2d2fcc1f..3cd8cec15 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs @@ -239,9 +239,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms private static void VerifyAllPixelsAreWhiteOrTransparent(Image image) where TPixel : unmanaged, IPixel { - Assert.True(image.Frames.RootFrame.TryGetSinglePixelSpan(out Span data)); + Assert.True(image.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out Memory data)); var white = new Rgb24(255, 255, 255); - foreach (TPixel pixel in data) + foreach (TPixel pixel in data.Span) { Rgba32 rgba = default; pixel.ToRgba32(ref rgba); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 3c0faf499..022bb224c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -117,6 +117,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms int workingBufferSizeHintInBytes = workingBufferLimitInRows * destSize.Width * SizeOfVector4; var allocator = new TestMemoryAllocator(); + allocator.EnableNonThreadSafeLogging(); configuration.MemoryAllocator = allocator; configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInBytes; @@ -269,8 +270,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { using (Image image0 = provider.GetImage()) { - Assert.True(image0.TryGetSinglePixelSpan(out Span imageSpan)); - var mmg = TestMemoryManager.CreateAsCopyOf(imageSpan); + Assert.True(image0.DangerousTryGetSinglePixelMemory(out Memory imageMem)); + var mmg = TestMemoryManager.CreateAsCopyOf(imageMem.Span); using (var image1 = Image.WrapMemory(mmg.Memory, image0.Width, image0.Height)) { @@ -340,7 +341,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms // Resize_WorksWithAllResamplers_TestPattern301x1180_NearestNeighbor-300x480.png // TODO: Should we investigate this? bool allowHigherInaccuracy = !TestEnvironment.Is64BitProcess - && string.IsNullOrEmpty(TestEnvironment.NetCoreVersion) + && TestEnvironment.NetCoreVersion == null && sampler is NearestNeighborResampler; var comparer = ImageComparer.TolerantPercentage(allowHigherInaccuracy ? 0.3f : 0.017f); diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs index c9e5d3aa7..77b114fd2 100644 --- a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs +++ b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests using (IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds())) { int index = this.GetTransparentIndex(quantized); - Assert.Equal(index, quantized.GetPixelRowSpan(0)[0]); + Assert.Equal(index, quantized.DangerousGetRowSpan(0)[0]); } } } @@ -103,7 +103,7 @@ namespace SixLabors.ImageSharp.Tests using (IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds())) { int index = this.GetTransparentIndex(quantized); - Assert.Equal(index, quantized.GetPixelRowSpan(0)[0]); + Assert.Equal(index, quantized.DangerousGetRowSpan(0)[0]); } } } diff --git a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs index 71a8702c7..b835aa63e 100644 --- a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization Assert.Equal(1, result.Height); Assert.Equal(Color.Black, (Color)result.Palette.Span[0]); - Assert.Equal(0, result.GetPixelRowSpan(0)[0]); + Assert.Equal(0, result.DangerousGetRowSpan(0)[0]); } [Fact] @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization Assert.Equal(1, result.Height); Assert.Equal(default, result.Palette.Span[0]); - Assert.Equal(0, result.GetPixelRowSpan(0)[0]); + Assert.Equal(0, result.DangerousGetRowSpan(0)[0]); } [Fact] @@ -93,25 +93,31 @@ namespace SixLabors.ImageSharp.Tests.Quantization Assert.Equal(1, result.Width); Assert.Equal(256, result.Height); - var actualImage = new Image(1, 256); + using var actualImage = new Image(1, 256); - ReadOnlySpan paletteSpan = result.Palette.Span; - int paletteCount = paletteSpan.Length - 1; - for (int y = 0; y < actualImage.Height; y++) + actualImage.ProcessPixelRows(accessor => { - Span row = actualImage.GetPixelRowSpan(y); - ReadOnlySpan quantizedPixelSpan = result.GetPixelRowSpan(y); - - for (int x = 0; x < actualImage.Width; x++) + ReadOnlySpan paletteSpan = result.Palette.Span; + int paletteCount = paletteSpan.Length - 1; + for (int y = 0; y < accessor.Height; y++) { - row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])]; + Span row = accessor.GetRowSpan(y); + ReadOnlySpan quantizedPixelSpan = result.DangerousGetRowSpan(y); + + for (int x = 0; x < accessor.Width; x++) + { + row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])]; + } } - } + }); - for (int y = 0; y < image.Height; y++) + image.ProcessPixelRows(actualImage, static (imageAccessor, actualImageAccessor) => { - Assert.True(image.GetPixelRowSpan(y).SequenceEqual(actualImage.GetPixelRowSpan(y))); - } + for (int y = 0; y < imageAccessor.Height; y++) + { + Assert.True(imageAccessor.GetRowSpan(y).SequenceEqual(actualImageAccessor.GetRowSpan(y))); + } + }); } [Theory] @@ -162,24 +168,30 @@ namespace SixLabors.ImageSharp.Tests.Quantization Assert.Equal(1, result.Width); Assert.Equal(256, result.Height); - ReadOnlySpan paletteSpan = result.Palette.Span; - int paletteCount = paletteSpan.Length - 1; - for (int y = 0; y < actualImage.Height; y++) + actualImage.ProcessPixelRows(accessor => { - Span row = actualImage.GetPixelRowSpan(y); - ReadOnlySpan quantizedPixelSpan = result.GetPixelRowSpan(y); - - for (int x = 0; x < actualImage.Width; x++) + ReadOnlySpan paletteSpan = result.Palette.Span; + int paletteCount = paletteSpan.Length - 1; + for (int y = 0; y < accessor.Height; y++) { - row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])]; + Span row = accessor.GetRowSpan(y); + ReadOnlySpan quantizedPixelSpan = result.DangerousGetRowSpan(y); + + for (int x = 0; x < accessor.Width; x++) + { + row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])]; + } } - } + }); } - for (int y = 0; y < expectedImage.Height; y++) + expectedImage.ProcessPixelRows(actualImage, static (expectedAccessor, actualAccessor) => { - Assert.True(expectedImage.GetPixelRowSpan(y).SequenceEqual(actualImage.GetPixelRowSpan(y))); - } + for (int y = 0; y < expectedAccessor.Height; y++) + { + Assert.True(expectedAccessor.GetRowSpan(y).SequenceEqual(actualAccessor.GetRowSpan(y))); + } + }); } } } diff --git a/tests/ImageSharp.Tests/RunTestsInLoop.ps1 b/tests/ImageSharp.Tests/RunTestsInLoop.ps1 new file mode 100644 index 000000000..c7c5c9ac5 --- /dev/null +++ b/tests/ImageSharp.Tests/RunTestsInLoop.ps1 @@ -0,0 +1,22 @@ +# This script can be used to collect logs from sporadic bugs +Param( + [int]$TestRunCount=10, + [string]$TargetFramework="netcoreapp3.1", + [string]$Configuration="Release" +) + +$runId = Get-Random -Minimum 0 -Maximum 9999 + +dotnet build -c $Configuration -f $TargetFramework +for ($i = 0; $i -lt $TestRunCount; $i++) { + $logFile = ".\_testlog-" + $runId.ToString("d4") + "-run-" + $i.ToString("d3") + ".log" + Write-Host "Test run $i ..." + & dotnet test --no-build -c $Configuration -f $TargetFramework 3>&1 2>&1 > $logFile + if ($LastExitCode -eq 0) { + Write-Host "Success!" + Remove-Item $logFile + } + else { + Write-Host "Failed: $logFile" + } +} diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index c8d0633d7..6c2b97eb6 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -310,6 +310,10 @@ namespace SixLabors.ImageSharp.Tests { } + public void FromAbgr32(Abgr32 source) + { + } + public void FromL8(L8 source) { } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 0042df9be..898c031c2 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -840,6 +840,7 @@ namespace SixLabors.ImageSharp.Tests public const string Flower32BitGrayPredictorLittleEndian = "Tiff/flower-minisblack-32_lsb_deflate_predictor.tiff"; public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff"; + public const string Issues1891 = "Tiff/Issues/Issue1891.tiff"; public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff"; public const string SmallRgbLzw = "Tiff/rgb_small_lzw.tiff"; @@ -889,5 +890,20 @@ namespace SixLabors.ImageSharp.Tests public const string Damaged_MinIsWhite_RLE = Base + "BigTIFF_MinIsWhite_RLE.tif"; public const string Damaged_MinIsBlack_RLE = Base + "BigTIFF_MinIsBlack_RLE.tif"; } + + public static class Pbm + { + public const string BlackAndWhitePlain = "Pbm/blackandwhite_plain.pbm"; + public const string BlackAndWhiteBinary = "Pbm/blackandwhite_binary.pbm"; + public const string GrayscaleBinary = "Pbm/rings.pgm"; + public const string GrayscaleBinaryWide = "Pbm/Gene-UP WebSocket RunImageMask.pgm"; + public const string GrayscalePlain = "Pbm/grayscale_plain.pgm"; + public const string GrayscalePlainNormalized = "Pbm/grayscale_plain_normalized.pgm"; + public const string GrayscalePlainMagick = "Pbm/grayscale_plain_magick.pgm"; + public const string RgbBinary = "Pbm/00000_00000.ppm"; + public const string RgbPlain = "Pbm/rgb_plain.ppm"; + public const string RgbPlainNormalized = "Pbm/rgb_plain_normalized.ppm"; + public const string RgbPlainMagick = "Pbm/rgb_plain_magick.ppm"; + } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs index c6bcef461..5c5392260 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison @@ -29,11 +30,13 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison var differences = new List(); Configuration configuration = expected.GetConfiguration(); + Buffer2D expectedBuffer = expected.PixelBuffer; + Buffer2D actualBuffer = actual.PixelBuffer; for (int y = 0; y < actual.Height; y++) { - Span aSpan = expected.GetPixelRowSpan(y); - Span bSpan = actual.GetPixelRowSpan(y); + Span aSpan = expectedBuffer.DangerousGetRowSpan(y); + Span bSpan = actualBuffer.DangerousGetRowSpan(y); PixelOperations.Instance.ToRgba64(configuration, aSpan, aBuffer); PixelOperations.Instance.ToRgba64(configuration, bSpan, bBuffer); diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs similarity index 79% rename from tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs rename to tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs index c96777031..1801d6b59 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs @@ -8,9 +8,9 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Tga +namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { - public static class TgaTestUtils + public static class ImageComparingUtils { public static void CompareWithReferenceDecoder( TestImageProvider provider, @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga } var testFile = TestFile.Create(path); - Image magickImage = DecodeWithMagick(Configuration.Default, new FileInfo(testFile.FullPath)); + Image magickImage = DecodeWithMagick(new FileInfo(testFile.FullPath)); if (useExactComparer) { ImageComparer.Exact.VerifySimilarity(magickImage, image); @@ -37,15 +37,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga } } - public static Image DecodeWithMagick(Configuration configuration, FileInfo fileInfo) + public static Image DecodeWithMagick(FileInfo fileInfo) where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel { + Configuration configuration = Configuration.Default.Clone(); + configuration.PreferContiguousImageBuffers = true; using (var magickImage = new MagickImage(fileInfo)) { magickImage.AutoOrient(); var result = new Image(configuration, magickImage.Width, magickImage.Height); - Assert.True(result.TryGetSinglePixelSpan(out Span resultPixels)); + Assert.True(result.DangerousTryGetSinglePixelMemory(out Memory resultPixels)); using (IUnsafePixelCollection pixels = magickImage.GetPixelsUnsafe()) { @@ -54,7 +56,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga PixelOperations.Instance.FromRgba32Bytes( configuration, data, - resultPixels, + resultPixels.Span, resultPixels.Length); } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs index 38fb4026d..771d4baf9 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison @@ -74,11 +75,13 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison var differences = new List(); Configuration configuration = expected.GetConfiguration(); + Buffer2D expectedBuffer = expected.PixelBuffer; + Buffer2D actualBuffer = actual.PixelBuffer; for (int y = 0; y < actual.Height; y++) { - Span aSpan = expected.GetPixelRowSpan(y); - Span bSpan = actual.GetPixelRowSpan(y); + Span aSpan = expectedBuffer.DangerousGetRowSpan(y); + Span bSpan = actualBuffer.DangerousGetRowSpan(y); PixelOperations.Instance.ToRgba64(configuration, aSpan, aBuffer); PixelOperations.Instance.ToRgba64(configuration, bSpan, bBuffer); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs index f5e1f238e..2d1c6e224 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs @@ -39,25 +39,27 @@ namespace SixLabors.ImageSharp.Tests public override Image GetImage() { var result = new Image(this.Configuration, this.Width, this.Height); - - int midY = this.Height / 2; - int midX = this.Width / 2; - - for (int y = 0; y < midY; y++) + result.ProcessPixelRows(accessor => { - Span row = result.GetPixelRowSpan(y); + int midY = this.Height / 2; + int midX = this.Width / 2; - row.Slice(0, midX).Fill(TopLeftColor); - row.Slice(midX, this.Width - midX).Fill(TopRightColor); - } + for (int y = 0; y < midY; y++) + { + Span row = accessor.GetRowSpan(y); - for (int y = midY; y < this.Height; y++) - { - Span row = result.GetPixelRowSpan(y); + row.Slice(0, midX).Fill(TopLeftColor); + row.Slice(midX, this.Width - midX).Fill(TopRightColor); + } - row.Slice(0, midX).Fill(BottomLeftColor); - row.Slice(midX, this.Width - midX).Fill(BottomRightColor); - } + for (int y = midY; y < this.Height; y++) + { + Span row = accessor.GetRowSpan(y); + + row.Slice(0, midX).Fill(BottomLeftColor); + row.Slice(midX, this.Width - midX).Fill(BottomRightColor); + } + }); return result; } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index f57c19f12..e2e7d73bc 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -8,6 +8,7 @@ using System.IO; using System.Reflection; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using Xunit.Abstractions; @@ -23,18 +24,17 @@ namespace SixLabors.ImageSharp.Tests // are shared between PixelTypes.Color & PixelTypes.Rgba32 private class Key : IEquatable { - private readonly Tuple commonValues; + private readonly Tuple commonValues; private readonly Dictionary decoderParameters; - public Key(PixelTypes pixelType, string filePath, int allocatorBufferCapacity, IImageDecoder customDecoder) + public Key(PixelTypes pixelType, string filePath, IImageDecoder customDecoder) { Type customType = customDecoder?.GetType(); - this.commonValues = new Tuple( + this.commonValues = new Tuple( pixelType, filePath, - customType, - allocatorBufferCapacity); + customType); this.decoderParameters = GetDecoderParameters(customDecoder); } @@ -152,13 +152,13 @@ namespace SixLabors.ImageSharp.Tests { Guard.NotNull(decoder, nameof(decoder)); - if (!TestEnvironment.Is64BitProcess) + // Do not cache with 64 bits or if image has been created with non-default MemoryAllocator + if (!TestEnvironment.Is64BitProcess || this.Configuration.MemoryAllocator != MemoryAllocator.Default) { return this.LoadImage(decoder); } - int bufferCapacity = this.Configuration.MemoryAllocator.GetBufferCapacityInBytes(); - var key = new Key(this.PixelType, this.FilePath, bufferCapacity, decoder); + var key = new Key(this.PixelType, this.FilePath, decoder); Image cachedImage = Cache.GetOrAdd(key, _ => this.LoadImage(decoder)); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index 4860524b3..700c40b72 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -21,20 +21,11 @@ namespace SixLabors.ImageSharp.Tests public abstract partial class TestImageProvider : ITestImageProvider, IXunitSerializable where TPixel : unmanaged, IPixel { - // Create a Configuration with Configuration.CreateDefaultInstance(), - // but use the shared MemoryAllocator from Configuration.Default.MemoryAllocator - private static Configuration CreateDefaultConfiguration() - { - var configuration = Configuration.CreateDefaultInstance(); - configuration.MemoryAllocator = ImageSharp.Configuration.Default.MemoryAllocator; - return configuration; - } - public PixelTypes PixelType { get; private set; } = typeof(TPixel).GetPixelType(); public virtual string SourceFileOrDescription => string.Empty; - public Configuration Configuration { get; set; } = CreateDefaultConfiguration(); + public Configuration Configuration { get; set; } = Configuration.CreateDefaultInstance(); /// /// Gets the utility instance to provide information about the test image & manage input/output. diff --git a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs index eb840231c..8ba383125 100644 --- a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs +++ b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs @@ -69,6 +69,8 @@ namespace SixLabors.ImageSharp.Tests La32 = 1 << 26, + Abgr32 = 1 << 27, + // TODO: Add multi-flag entries by rules defined in PackedPixelConverterHelper // "All" is handled as a separate, individual case instead of using bitwise OR diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs index 157748bdd..28f0dba06 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs @@ -47,14 +47,14 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs long destRowByteCount = w * sizeof(Bgra32); Configuration configuration = image.GetConfiguration(); - - using (IMemoryOwner workBuffer = Configuration.Default.MemoryAllocator.Allocate(w)) + image.ProcessPixelRows(accessor => { + using IMemoryOwner workBuffer = Configuration.Default.MemoryAllocator.Allocate(w); fixed (Bgra32* destPtr = &workBuffer.GetReference()) { for (int y = 0; y < h; y++) { - Span row = image.Frames.RootFrame.GetPixelRowSpan(y); + Span row = accessor.GetRowSpan(y); byte* sourcePtr = sourcePtrBase + (data.Stride * y); @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs row); } } - } + }); } finally { @@ -106,6 +106,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs long destRowByteCount = w * sizeof(Bgr24); Configuration configuration = image.GetConfiguration(); + Buffer2D imageBuffer = image.Frames.RootFrame.PixelBuffer; using (IMemoryOwner workBuffer = Configuration.Default.MemoryAllocator.Allocate(w)) { @@ -113,7 +114,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { for (int y = 0; y < h; y++) { - Span row = image.Frames.RootFrame.GetPixelRowSpan(y); + Span row = imageBuffer.DangerousGetRowSpan(y); byte* sourcePtr = sourcePtrBase + (data.Stride * y); @@ -144,24 +145,23 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs try { byte* destPtrBase = (byte*)data.Scan0; - long destRowByteCount = data.Stride; long sourceRowByteCount = w * sizeof(Bgra32); - - using (IMemoryOwner workBuffer = image.GetConfiguration().MemoryAllocator.Allocate(w)) + image.ProcessPixelRows(accessor => { + using IMemoryOwner workBuffer = image.GetConfiguration().MemoryAllocator.Allocate(w); fixed (Bgra32* sourcePtr = &workBuffer.GetReference()) { for (int y = 0; y < h; y++) { - Span row = image.Frames.RootFrame.GetPixelRowSpan(y); + Span row = accessor.GetRowSpan(y); PixelOperations.Instance.ToBgra32(configuration, row, workBuffer.GetSpan()); byte* destPtr = destPtrBase + (data.Stride * y); Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); } } - } + }); } finally { diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index 4b374b21f..fe512f9dc 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -7,6 +7,7 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tiff; @@ -57,6 +58,7 @@ namespace SixLabors.ImageSharp.Tests var cfg = new Configuration( new JpegConfigurationModule(), new GifConfigurationModule(), + new PbmConfigurationModule(), new TgaConfigurationModule(), new WebpConfigurationModule(), new TiffConfigurationModule()); diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs index b14c2bf78..3ccaf2ba3 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs @@ -24,14 +24,14 @@ namespace SixLabors.ImageSharp.Tests private static readonly Lazy SolutionDirectoryFullPathLazy = new Lazy(GetSolutionDirectoryFullPathImpl); - private static readonly Lazy NetCoreVersionLazy = new Lazy(GetNetCoreVersion); + private static readonly Lazy NetCoreVersionLazy = new Lazy(GetNetCoreVersion); static TestEnvironment() => PrepareRemoteExecutor(); /// /// Gets the .NET Core version, if running on .NET Core, otherwise returns an empty string. /// - internal static string NetCoreVersion => NetCoreVersionLazy.Value; + internal static Version NetCoreVersion => NetCoreVersionLazy.Value; // ReSharper disable once InconsistentNaming @@ -118,7 +118,7 @@ namespace SixLabors.ImageSharp.Tests internal static bool Is64BitProcess => IntPtr.Size == 8; - internal static bool IsFramework => string.IsNullOrEmpty(NetCoreVersion); + internal static bool IsFramework => NetCoreVersion == null; /// /// A dummy operation to enforce the execution of the static constructor. @@ -159,7 +159,7 @@ namespace SixLabors.ImageSharp.Tests /// private static void PrepareRemoteExecutor() { - if (!IsFramework) + if (!IsFramework || !Environment.Is64BitProcess) { return; } @@ -262,17 +262,24 @@ namespace SixLabors.ImageSharp.Tests /// Solution borrowed from: /// https://github.com/dotnet/BenchmarkDotNet/issues/448#issuecomment-308424100 /// - private static string GetNetCoreVersion() + private static Version GetNetCoreVersion() { Assembly assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly; string[] assemblyPath = assembly.Location.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App"); if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2) { - return assemblyPath[netCoreAppIndex + 1]; + string runtimeFolderStr = assemblyPath[netCoreAppIndex + 1]; + int previewSuffix = runtimeFolderStr.IndexOf('-'); + if (previewSuffix > 0) + { + runtimeFolderStr = runtimeFolderStr.Substring(0, previewSuffix); + } + + return Version.Parse(runtimeFolderStr); } - return string.Empty; + return null; } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 3f41281d0..719e52946 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -12,6 +12,7 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.ImageSharp.Tests.Memory; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; @@ -396,12 +397,18 @@ namespace SixLabors.ImageSharp.Tests Span expectedPixels) where TPixel : unmanaged, IPixel { - Assert.True(image.TryGetSinglePixelSpan(out Span actualPixels)); - CompareBuffers(expectedPixels, actualPixels); + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory actualPixels)); + CompareBuffers(expectedPixels, actualPixels.Span); return image; } + public static Image ComparePixelBufferTo( + this Image image, + Memory expectedPixels) + where TPixel : unmanaged, IPixel => + ComparePixelBufferTo(image, expectedPixels.Span); + public static void CompareBuffers(Span expected, Span actual) where T : struct, IEquatable { @@ -416,6 +423,27 @@ namespace SixLabors.ImageSharp.Tests } } + public static void CompareBuffers(Buffer2D expected, Buffer2D actual) + where T : struct, IEquatable + { + Assert.True(expected.Size() == actual.Size(), "Buffer sizes are not equal!"); + + for (int y = 0; y < expected.Height; y++) + { + Span expectedRow = expected.DangerousGetRowSpan(y); + Span actualRow = actual.DangerousGetRowSpan(y); + for (int x = 0; x < expectedRow.Length; x++) + { + T expectedVal = expectedRow[x]; + T actualVal = actualRow[x]; + + Assert.True( + expectedVal.Equals(actualVal), + $"Buffers differ at position ({x},{y})! Expected: {expectedVal} | Actual: {actualVal}"); + } + } + } + /// /// All pixels in all frames should be exactly equal to 'expectedPixel'. /// @@ -456,7 +484,8 @@ namespace SixLabors.ImageSharp.Tests public static ImageFrame ComparePixelBufferTo(this ImageFrame imageFrame, TPixel expectedPixel) where TPixel : unmanaged, IPixel { - Assert.True(imageFrame.TryGetSinglePixelSpan(out Span actualPixels)); + Assert.True(imageFrame.DangerousTryGetSinglePixelMemory(out Memory actualPixelMem)); + Span actualPixels = actualPixelMem.Span; for (int i = 0; i < actualPixels.Length; i++) { @@ -471,7 +500,8 @@ namespace SixLabors.ImageSharp.Tests Span expectedPixels) where TPixel : unmanaged, IPixel { - Assert.True(image.TryGetSinglePixelSpan(out Span actual)); + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory actualMem)); + Span actual = actualMem.Span; Assert.True(expectedPixels.Length == actual.Length, "Buffer sizes are not equal!"); for (int i = 0; i < expectedPixels.Length; i++) @@ -663,7 +693,7 @@ namespace SixLabors.ImageSharp.Tests this TestImageProvider provider) where TPixel : unmanaged, IPixel { - var allocator = ArrayPoolMemoryAllocator.CreateDefault(); + var allocator = new TestMemoryAllocator(); provider.Configuration.MemoryAllocator = allocator; return new AllocatorBufferCapacityConfigurator(allocator, Unsafe.SizeOf()); } @@ -672,7 +702,8 @@ namespace SixLabors.ImageSharp.Tests { var image = new Image(buffer.Width, buffer.Height); - Assert.True(image.Frames.RootFrame.TryGetSinglePixelSpan(out Span pixels)); + Assert.True(image.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out Memory pixelMem)); + Span pixels = pixelMem.Span; Span bufferSpan = buffer.DangerousGetSingleSpan(); for (int i = 0; i < bufferSpan.Length; i++) @@ -705,7 +736,7 @@ namespace SixLabors.ImageSharp.Tests Rectangle sourceRectangle = this.SourceRectangle; Configuration configuration = this.Configuration; - var operation = new RowOperation(configuration, sourceRectangle, source); + var operation = new RowOperation(configuration, sourceRectangle, source.PixelBuffer); ParallelRowIterator.IterateRowIntervals( configuration, @@ -717,9 +748,9 @@ namespace SixLabors.ImageSharp.Tests { private readonly Configuration configuration; private readonly Rectangle bounds; - private readonly ImageFrame source; + private readonly Buffer2D source; - public RowOperation(Configuration configuration, Rectangle bounds, ImageFrame source) + public RowOperation(Configuration configuration, Rectangle bounds, Buffer2D source) { this.configuration = configuration; this.bounds = bounds; @@ -730,7 +761,7 @@ namespace SixLabors.ImageSharp.Tests { for (int y = rows.Min; y < rows.Max; y++) { - Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left, this.bounds.Width); + Span rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.bounds.Left, this.bounds.Width); PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, PixelConversionModifiers.Scale); for (int i = 0; i < span.Length; i++) { @@ -747,14 +778,16 @@ namespace SixLabors.ImageSharp.Tests internal class AllocatorBufferCapacityConfigurator { - private readonly ArrayPoolMemoryAllocator allocator; +#pragma warning disable CS0618 // 'ArrayPoolMemoryAllocator' is obsolete + private readonly TestMemoryAllocator allocator; private readonly int pixelSizeInBytes; - public AllocatorBufferCapacityConfigurator(ArrayPoolMemoryAllocator allocator, int pixelSizeInBytes) + public AllocatorBufferCapacityConfigurator(TestMemoryAllocator allocator, int pixelSizeInBytes) { this.allocator = allocator; this.pixelSizeInBytes = pixelSizeInBytes; } +#pragma warning restore CS0618 public void InBytes(int totalBytes) => this.allocator.BufferCapacityInBytes = totalBytes; diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs index eab0d5776..5fb6d873a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs @@ -12,8 +12,8 @@ namespace SixLabors.ImageSharp.Tests.Memory { internal class TestMemoryAllocator : MemoryAllocator { - private readonly List allocationLog = new List(); - private readonly List returnLog = new List(); + private List allocationLog; + private List returnLog; public TestMemoryAllocator(byte dirtyValue = 42) { @@ -27,29 +27,29 @@ namespace SixLabors.ImageSharp.Tests.Memory public int BufferCapacityInBytes { get; set; } = int.MaxValue; - public IReadOnlyList AllocationLog => this.allocationLog; + public IReadOnlyList AllocationLog => this.allocationLog ?? throw new InvalidOperationException("Call TestMemoryAllocator.EnableLogging() first!"); - public IReadOnlyList ReturnLog => this.returnLog; + public IReadOnlyList ReturnLog => this.returnLog ?? throw new InvalidOperationException("Call TestMemoryAllocator.EnableLogging() first!"); protected internal override int GetBufferCapacityInBytes() => this.BufferCapacityInBytes; - public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) + public void EnableNonThreadSafeLogging() { - T[] array = this.AllocateArray(length, options); - return new BasicArrayBuffer(array, length, this); + this.allocationLog = new List(); + this.returnLog = new List(); } - public override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None) + public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) { - byte[] array = this.AllocateArray(length, options); - return new ManagedByteBuffer(array, this); + T[] array = this.AllocateArray(length, options); + return new BasicArrayBuffer(array, length, this); } private T[] AllocateArray(int length, AllocationOptions options) where T : struct { var array = new T[length + 42]; - this.allocationLog.Add(AllocationRequest.Create(options, length, array)); + this.allocationLog?.Add(AllocationRequest.Create(options, length, array)); if (options == AllocationOptions.None) { @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests.Memory private void Return(BasicArrayBuffer buffer) where T : struct { - this.returnLog.Add(new ReturnRequest(buffer.Array.GetHashCode())); + this.returnLog?.Add(new ReturnRequest(buffer.Array.GetHashCode())); } public struct AllocationRequest @@ -152,12 +152,12 @@ namespace SixLabors.ImageSharp.Tests.Memory } void* ptr = (void*)this.pinHandle.AddrOfPinnedObject(); - return new MemoryHandle(ptr, this.pinHandle); + return new MemoryHandle(ptr, pinnable: this); } public override void Unpin() { - throw new NotImplementedException(); + this.pinHandle.Free(); } /// @@ -170,7 +170,7 @@ namespace SixLabors.ImageSharp.Tests.Memory } } - private class ManagedByteBuffer : BasicArrayBuffer, IManagedByteBuffer + private class ManagedByteBuffer : BasicArrayBuffer, IMemoryOwner { public ManagedByteBuffer(byte[] array, TestMemoryAllocator allocator) : base(array, allocator) diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs index 32b5eaf18..3c27b60fe 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs @@ -12,6 +12,7 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.ImageSharp.Tests.Memory; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; @@ -53,6 +54,32 @@ namespace SixLabors.ImageSharp.Tests public static bool HasFlag(this PixelTypes pixelTypes, PixelTypes flag) => (pixelTypes & flag) == flag; + public static byte[] GetRandomBytes(int length, int seed = 42) + { + var rnd = new Random(42); + byte[] bytes = new byte[length]; + rnd.NextBytes(bytes); + return bytes; + } + + internal static byte[] FillImageWithRandomBytes(Image image) + { + byte[] expected = TestUtils.GetRandomBytes(image.Width * image.Height * 2); + image.ProcessPixelRows(accessor => + { + int cnt = 0; + for (int y = 0; y < accessor.Height; y++) + { + Span row = accessor.GetRowSpan(y); + for (int x = 0; x < row.Length; x++) + { + row[x] = new La16(expected[cnt++], expected[cnt++]); + } + } + }); + return expected; + } + public static bool IsEquivalentTo(this Image a, Image b, bool compareAlpha = true) where TPixel : unmanaged, IPixel { @@ -165,7 +192,7 @@ namespace SixLabors.ImageSharp.Tests int width = expected.Width; expected.Mutate(process); - var allocator = ArrayPoolMemoryAllocator.CreateDefault(); + var allocator = new TestMemoryAllocator(); provider.Configuration.MemoryAllocator = allocator; allocator.BufferCapacityInBytes = bufferCapacityInPixelRows * width * Unsafe.SizeOf(); @@ -277,8 +304,8 @@ namespace SixLabors.ImageSharp.Tests using (Image image0 = provider.GetImage()) { - Assert.True(image0.TryGetSinglePixelSpan(out Span imageSpan)); - var mmg = TestMemoryManager.CreateAsCopyOf(imageSpan); + Assert.True(image0.DangerousTryGetSinglePixelMemory(out Memory imageMem)); + var mmg = TestMemoryManager.CreateAsCopyOf(imageMem.Span); using (var image1 = Image.WrapMemory(mmg.Memory, image0.Width, image0.Height)) { diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs index bb9ed8260..1a46f91e5 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs @@ -112,14 +112,15 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void ExpandAllTypes_2() { - PixelTypes pixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; + PixelTypes pixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Abgr32 | PixelTypes.RgbaVector; IEnumerable> expanded = pixelTypes.ExpandAllTypes(); - Assert.Equal(3, expanded.Count()); + Assert.Equal(4, expanded.Count()); AssertContainsPixelType(PixelTypes.Rgba32, expanded); AssertContainsPixelType(PixelTypes.Bgra32, expanded); + AssertContainsPixelType(PixelTypes.Abgr32, expanded); AssertContainsPixelType(PixelTypes.RgbaVector, expanded); } diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png new file mode 100644 index 000000000..09bb074a3 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78fc668be9f82c01c277cb2560253b04a1ff74a5af4daaf19327591420a71fec +size 4521 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png new file mode 100644 index 000000000..d1f1515bb --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1339a8170408a7bcde261617cc599587c8f25c4dc94f780976ee1638879888e9 +size 147 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png new file mode 100644 index 000000000..372261923 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82d0397f38971cf90d7c064db332093e686196e244ece1196cca2071d27f0a6f +size 147 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain.png new file mode 100644 index 000000000..9c86c2fc1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8e8b8a1a05e76b1eeb577373c3a6f492e356f0dd58489afded248415cec4a07 +size 145 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png new file mode 100644 index 000000000..9c86c2fc1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8e8b8a1a05e76b1eeb577373c3a6f492e356f0dd58489afded248415cec4a07 +size 145 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png new file mode 100644 index 000000000..acf751c28 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:388c86b3dd472ef17fb911ae424b81baeeeff74c4161cf5825eab50698d54348 +size 27884 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png new file mode 100644 index 000000000..49cc74f3f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e3fc46b9f0546941ef95be7b750fb29376a679a921f2581403882b0e76e9caf +size 2250 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain.png new file mode 100644 index 000000000..421a59849 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c44322c4bf461acea27053057f5241afb029d9a1e66e94dcf1be6f86f7f97727 +size 152 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png new file mode 100644 index 000000000..421a59849 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c44322c4bf461acea27053057f5241afb029d9a1e66e94dcf1be6f86f7f97727 +size 152 diff --git a/tests/Images/Input/Pbm/00000_00000.ppm b/tests/Images/Input/Pbm/00000_00000.ppm new file mode 100644 index 000000000..f6e085787 --- /dev/null +++ b/tests/Images/Input/Pbm/00000_00000.ppm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b291c0d3a0747c7425a3445bea1de1fa7c112a183d2f78bb9fc96ec5ae9804e +size 2623 diff --git a/tests/Images/Input/Pbm/Gene-UP WebSocket RunImageMask.pgm b/tests/Images/Input/Pbm/Gene-UP WebSocket RunImageMask.pgm new file mode 100644 index 000000000..efd46a2c8 --- /dev/null +++ b/tests/Images/Input/Pbm/Gene-UP WebSocket RunImageMask.pgm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38c6aadba17548dbe6496de2c81c3cb719d2330499c3cf7d4237e78dec098e53 +size 614417 diff --git a/tests/Images/Input/Pbm/blackandwhite_binary.pbm b/tests/Images/Input/Pbm/blackandwhite_binary.pbm new file mode 100644 index 000000000..d07976894 --- /dev/null +++ b/tests/Images/Input/Pbm/blackandwhite_binary.pbm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0313a99c2acdd34d6ba67815d1daa25f2452bfada71a1828dbcbb3cc48a20b20 +size 48 diff --git a/tests/Images/Input/Pbm/blackandwhite_plain.pbm b/tests/Images/Input/Pbm/blackandwhite_plain.pbm new file mode 100644 index 000000000..9c92a99cc --- /dev/null +++ b/tests/Images/Input/Pbm/blackandwhite_plain.pbm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12ccfacadea1c97c15b6d192ee3ae3b6a1d79bdca30fddbe597390f71e86d59c +size 367 diff --git a/tests/Images/Input/Pbm/grayscale_plain.pgm b/tests/Images/Input/Pbm/grayscale_plain.pgm new file mode 100644 index 000000000..fa521b5da --- /dev/null +++ b/tests/Images/Input/Pbm/grayscale_plain.pgm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08068b4d30f19024e716176033f13f7203a45513e6ae73e79dc824509c92621a +size 507 diff --git a/tests/Images/Input/Pbm/grayscale_plain_magick.pgm b/tests/Images/Input/Pbm/grayscale_plain_magick.pgm new file mode 100644 index 000000000..fe1bb28b3 --- /dev/null +++ b/tests/Images/Input/Pbm/grayscale_plain_magick.pgm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec652ee7ea1a82d8ea2fd344670ab9aee2c2f52af86458d9991754204e1fc2bb +size 464 diff --git a/tests/Images/Input/Pbm/grayscale_plain_normalized.pgm b/tests/Images/Input/Pbm/grayscale_plain_normalized.pgm new file mode 100644 index 000000000..96497d605 --- /dev/null +++ b/tests/Images/Input/Pbm/grayscale_plain_normalized.pgm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e39342751c2a57a060a029213fd7d83cb9a72881b8b01dd6d5b0e897df5077de +size 599 diff --git a/tests/Images/Input/Pbm/rgb_plain.ppm b/tests/Images/Input/Pbm/rgb_plain.ppm new file mode 100644 index 000000000..32472d0ce --- /dev/null +++ b/tests/Images/Input/Pbm/rgb_plain.ppm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:895cd889f6723a5357936e852308cff25b74ead01618bf8efa0f876a86dc18c1 +size 205 diff --git a/tests/Images/Input/Pbm/rgb_plain_magick.ppm b/tests/Images/Input/Pbm/rgb_plain_magick.ppm new file mode 100644 index 000000000..ee88eb7f3 --- /dev/null +++ b/tests/Images/Input/Pbm/rgb_plain_magick.ppm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f38a31162f31e77f5ad80da968a386b2cbccc6998a88a4c6b311b48919119a1 +size 149 diff --git a/tests/Images/Input/Pbm/rgb_plain_normalized.ppm b/tests/Images/Input/Pbm/rgb_plain_normalized.ppm new file mode 100644 index 000000000..3d7fbe241 --- /dev/null +++ b/tests/Images/Input/Pbm/rgb_plain_normalized.ppm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59be6295e3983708ffba811a408acd83df8e9736b487a94d30132dee0edd6cb6 +size 234 diff --git a/tests/Images/Input/Pbm/rings.pgm b/tests/Images/Input/Pbm/rings.pgm new file mode 100644 index 000000000..4f2c8d2c7 --- /dev/null +++ b/tests/Images/Input/Pbm/rings.pgm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b5382e745e0447f13387ac02636b37baf3b4bbd3bc545177d407fd98a7cbe17 +size 40038 diff --git a/tests/Images/Input/Tiff/Issues/Issue1891.tiff b/tests/Images/Input/Tiff/Issues/Issue1891.tiff new file mode 100644 index 000000000..df2a5e798 --- /dev/null +++ b/tests/Images/Input/Tiff/Issues/Issue1891.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2779c7fb2c2ad858e0d7efcfffd594cc6fb2846e0475a2998a3cda50f289b9b +size 307