diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs index 2d29e23fe..320c94c96 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs @@ -122,24 +122,27 @@ namespace SixLabors.ImageSharp.Drawing.Brushes internal override void Apply(Span scanline, int x, int y) { // Create a span for colors - using (var amountBuffer = new Buffer(scanline.Length)) - using (var overlay = new Buffer(scanline.Length)) + using (IBuffer amountBuffer = this.Target.MemoryManager.Allocate(scanline.Length)) + using (IBuffer overlay = this.Target.MemoryManager.Allocate(scanline.Length)) { + Span amountSpan = amountBuffer.Span; + Span overlaySpan = overlay.Span; + int sourceY = (y - this.offsetY) % this.yLength; int offsetX = x - this.offsetX; Span sourceRow = this.source.GetPixelRowSpan(sourceY); for (int i = 0; i < scanline.Length; i++) { - amountBuffer[i] = scanline[i] * this.Options.BlendPercentage; + amountSpan[i] = scanline[i] * this.Options.BlendPercentage; int sourceX = (i + offsetX) % this.xLength; TPixel pixel = sourceRow[sourceX]; - overlay[i] = pixel; + overlaySpan[i] = pixel; } Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); - this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer); + this.Blender.Blend(this.source.MemoryManager, destinationRow, destinationRow, overlaySpan, amountSpan); } } } diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs index 844df0e0e..cc22b2639 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs @@ -152,19 +152,24 @@ namespace SixLabors.ImageSharp.Drawing.Brushes internal override void Apply(Span scanline, int x, int y) { int patternY = y % this.pattern.Height; - using (var amountBuffer = new Buffer(scanline.Length)) - using (var overlay = new Buffer(scanline.Length)) + MemoryManager memoryManager = this.Target.MemoryManager; + + using (IBuffer amountBuffer = memoryManager.Allocate(scanline.Length)) + using (IBuffer overlay = memoryManager.Allocate(scanline.Length)) { + Span amountSpan = amountBuffer.Span; + Span overlaySpan = overlay.Span; + for (int i = 0; i < scanline.Length; i++) { - amountBuffer[i] = (scanline[i] * this.Options.BlendPercentage).Clamp(0, 1); + amountSpan[i] = (scanline[i] * this.Options.BlendPercentage).Clamp(0, 1); int patternX = (x + i) % this.pattern.Width; - overlay[i] = this.pattern[patternY, patternX]; + overlaySpan[i] = this.pattern[patternY, patternX]; } Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); - this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer); + this.Blender.Blend(memoryManager, destinationRow, destinationRow, overlaySpan, amountSpan); } } } diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index ca6f7630d..d8ea43558 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -65,21 +65,26 @@ namespace SixLabors.ImageSharp.Drawing.Brushes.Processors /// scanlineBuffer will be > scanlineWidth but provide and offset in case we want to share a larger buffer across runs. internal virtual void Apply(Span scanline, int x, int y) { - using (var amountBuffer = new Buffer(scanline.Length)) - using (var overlay = new Buffer(scanline.Length)) + MemoryManager memoryManager = this.Target.MemoryManager; + + using (IBuffer amountBuffer = memoryManager.Allocate(scanline.Length)) + using (IBuffer overlay = memoryManager.Allocate(scanline.Length)) { + Span amountSpan = amountBuffer.Span; + Span overlaySpan = overlay.Span; + for (int i = 0; i < scanline.Length; i++) { if (this.Options.BlendPercentage < 1) { - amountBuffer[i] = scanline[i] * this.Options.BlendPercentage; + amountSpan[i] = scanline[i] * this.Options.BlendPercentage; } - overlay[i] = this[x + i, y]; + overlaySpan[i] = this[x + i, y]; } Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); - this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer); + this.Blender.Blend(memoryManager, destinationRow, destinationRow, overlaySpan, amountSpan); } } } diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs index ba2fca4e4..39afd965c 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs @@ -144,22 +144,27 @@ namespace SixLabors.ImageSharp.Drawing.Brushes /// internal override void Apply(Span scanline, int x, int y) { - using (var amountBuffer = new Buffer(scanline.Length)) - using (var overlay = new Buffer(scanline.Length)) + MemoryManager memoryManager = this.Target.MemoryManager; + + using (IBuffer amountBuffer = memoryManager.Allocate(scanline.Length)) + using (IBuffer overlay = memoryManager.Allocate(scanline.Length)) { + Span amountSpan = amountBuffer.Span; + Span overlaySpan = overlay.Span; + for (int i = 0; i < scanline.Length; i++) { - amountBuffer[i] = scanline[i] * this.Options.BlendPercentage; + amountSpan[i] = scanline[i] * this.Options.BlendPercentage; int offsetX = x + i; // no doubt this one can be optermised further but I can't imagine its // actually being used and can probably be removed/interalised for now - overlay[i] = this[offsetX, y]; + overlaySpan[i] = this[offsetX, y]; } Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); - this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer); + this.Blender.Blend(memoryManager, destinationRow, destinationRow, overlaySpan, amountSpan); } } } diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs index 658164339..9630c707e 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs @@ -61,17 +61,14 @@ namespace SixLabors.ImageSharp.Drawing.Brushes public SolidBrushApplicator(ImageFrame source, TPixel color, GraphicsOptions options) : base(source, options) { - this.Colors = new Buffer(source.Width); - for (int i = 0; i < this.Colors.Length; i++) - { - this.Colors[i] = color; - } + this.Colors = source.MemoryManager.Allocate(source.Width); + this.Colors.Span.Fill(color); } /// /// Gets the colors. /// - protected Buffer Colors { get; } + protected IBuffer Colors { get; } /// /// Gets the color for a single pixel. @@ -81,7 +78,7 @@ namespace SixLabors.ImageSharp.Drawing.Brushes /// /// The color /// - internal override TPixel this[int x, int y] => this.Colors[x]; + internal override TPixel this[int x, int y] => this.Colors.Span[x]; /// public override void Dispose() @@ -92,23 +89,20 @@ namespace SixLabors.ImageSharp.Drawing.Brushes /// internal override void Apply(Span scanline, int x, int y) { - try + Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); + + MemoryManager memoryManager = this.Target.MemoryManager; + + using (IBuffer amountBuffer = memoryManager.Allocate(scanline.Length)) { - Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); + Span amountSpan = amountBuffer.Span; - using (var amountBuffer = new Buffer(scanline.Length)) + for (int i = 0; i < scanline.Length; i++) { - for (int i = 0; i < scanline.Length; i++) - { - amountBuffer[i] = scanline[i] * this.Options.BlendPercentage; - } - - this.Blender.Blend(destinationRow, destinationRow, this.Colors, amountBuffer); + amountSpan[i] = scanline[i] * this.Options.BlendPercentage; } - } - catch (Exception) - { - throw; + + this.Blender.Blend(memoryManager, destinationRow, destinationRow, this.Colors.Span, amountSpan); } } } diff --git a/src/ImageSharp.Drawing/Paths/ShapePath.cs b/src/ImageSharp.Drawing/Paths/ShapePath.cs index 61f1291c4..4c2278719 100644 --- a/src/ImageSharp.Drawing/Paths/ShapePath.cs +++ b/src/ImageSharp.Drawing/Paths/ShapePath.cs @@ -4,6 +4,8 @@ using System; using System.Buffers; using System.Numerics; + +using SixLabors.ImageSharp.Memory; using SixLabors.Shapes; namespace SixLabors.ImageSharp.Drawing diff --git a/src/ImageSharp.Drawing/Paths/ShapeRegion.cs b/src/ImageSharp.Drawing/Paths/ShapeRegion.cs index a96b03dd0..cc27f7fbb 100644 --- a/src/ImageSharp.Drawing/Paths/ShapeRegion.cs +++ b/src/ImageSharp.Drawing/Paths/ShapeRegion.cs @@ -46,18 +46,17 @@ namespace SixLabors.ImageSharp.Drawing { var start = new PointF(this.Bounds.Left - 1, y); var end = new PointF(this.Bounds.Right + 1, y); - using (var innerBuffer = new Buffer(buffer.Length)) - { - PointF[] array = innerBuffer.Array; - int count = this.Shape.FindIntersections(start, end, array, 0); - for (int i = 0; i < count; i++) - { - buffer[i + offset] = array[i].X; - } + // TODO: This is a temporary workaround because of the lack of Span API-s on IPath. We should use MemoryManager.Allocate() here! + PointF[] innerBuffer = new PointF[buffer.Length]; + int count = this.Shape.FindIntersections(start, end, innerBuffer, 0); - return count; + for (int i = 0; i < count; i++) + { + buffer[i + offset] = innerBuffer[i].X; } + + return count; } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs index 42d15035a..632b4d449 100644 --- a/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs @@ -73,12 +73,12 @@ namespace SixLabors.ImageSharp.Drawing.Processors int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); int width = maxX - minX; - using (var amount = new Buffer(width)) + + MemoryManager memoryManager = this.Image.GetConfiguration().MemoryManager; + + using (IBuffer amount = memoryManager.Allocate(width)) { - for (int i = 0; i < width; i++) - { - amount[i] = this.Opacity; - } + amount.Span.Fill(this.Opacity); Parallel.For( minY, @@ -88,7 +88,7 @@ namespace SixLabors.ImageSharp.Drawing.Processors { Span background = source.GetPixelRowSpan(y).Slice(minX, width); Span foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width); - blender.Blend(background, background, foreground, amount); + blender.Blend(memoryManager, background, background, foreground, amount.Span); }); } } diff --git a/src/ImageSharp.Drawing/Processors/FillProcessor.cs b/src/ImageSharp.Drawing/Processors/FillProcessor.cs index 679ca6a22..3bf18a37b 100644 --- a/src/ImageSharp.Drawing/Processors/FillProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillProcessor.cs @@ -66,25 +66,25 @@ namespace SixLabors.ImageSharp.Drawing.Processors int width = maxX - minX; - using (var amount = new Buffer(width)) - using (BrushApplicator applicator = this.brush.CreateApplicator(source, sourceRectangle, this.options)) + using (IBuffer amount = source.MemoryManager.Allocate(width)) + using (BrushApplicator applicator = this.brush.CreateApplicator( + source, + sourceRectangle, + this.options)) { - for (int i = 0; i < width; i++) - { - amount[i] = this.options.BlendPercentage; - } + amount.Span.Fill(this.options.BlendPercentage); - Parallel.For( + Parallel.For( minY, maxY, configuration.ParallelOptions, y => - { - int offsetY = y - startY; - int offsetX = minX - startX; + { + int offsetY = y - startY; + int offsetX = minX - startX; - applicator.Apply(amount, offsetX, offsetY); - }); + applicator.Apply(amount.Span, offsetX, offsetY); + }); } } } diff --git a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index b6ef4be21..076785526 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -78,8 +78,6 @@ namespace SixLabors.ImageSharp.Drawing.Processors return; // no effect inside image; } - ArrayPool arrayPool = ArrayPool.Shared; - int maxIntersections = region.MaxIntersections; float subpixelCount = 4; @@ -100,101 +98,94 @@ namespace SixLabors.ImageSharp.Drawing.Processors using (BrushApplicator applicator = this.Brush.CreateApplicator(source, rect, this.Options)) { - float[] buffer = arrayPool.Rent(maxIntersections); int scanlineWidth = maxX - minX; - using (var scanline = new Buffer(scanlineWidth)) + using (BasicArrayBuffer buffer = source.MemoryManager.AllocateFake(maxIntersections)) + using (BasicArrayBuffer scanline = source.MemoryManager.AllocateFake(scanlineWidth)) { - try + bool scanlineDirty = true; + for (int y = minY; y < maxY; y++) { - bool scanlineDirty = true; - for (int y = minY; y < maxY; y++) + if (scanlineDirty) { - if (scanlineDirty) + // clear the buffer + for (int x = 0; x < scanlineWidth; x++) { - // clear the buffer - for (int x = 0; x < scanlineWidth; x++) - { - scanline[x] = 0; - } - - scanlineDirty = false; + scanline[x] = 0; } - float subpixelFraction = 1f / subpixelCount; - float subpixelFractionPoint = subpixelFraction / subpixelCount; - for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction) - { - int pointsFound = region.Scan(subPixel + offset, buffer, 0); - if (pointsFound == 0) - { - // nothing on this line skip - continue; - } + scanlineDirty = false; + } - QuickSort(new Span(buffer, 0, pointsFound)); + float subpixelFraction = 1f / subpixelCount; + float subpixelFractionPoint = subpixelFraction / subpixelCount; + for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction) + { + int pointsFound = region.Scan(subPixel + offset, buffer.Array, 0); + if (pointsFound == 0) + { + // nothing on this line skip + continue; + } - for (int point = 0; point < pointsFound; point += 2) - { - // points will be paired up - float scanStart = buffer[point] - minX; - float scanEnd = buffer[point + 1] - minX; - int startX = (int)MathF.Floor(scanStart + offset); - int endX = (int)MathF.Floor(scanEnd + offset); + QuickSort(new Span(buffer.Array, 0, pointsFound)); - if (startX >= 0 && startX < scanline.Length) - { - for (float x = scanStart; x < startX + 1; x += subpixelFraction) - { - scanline[startX] += subpixelFractionPoint; - scanlineDirty = true; - } - } + for (int point = 0; point < pointsFound; point += 2) + { + // points will be paired up + float scanStart = buffer[point] - minX; + float scanEnd = buffer[point + 1] - minX; + int startX = (int)MathF.Floor(scanStart + offset); + int endX = (int)MathF.Floor(scanEnd + offset); - if (endX >= 0 && endX < scanline.Length) + if (startX >= 0 && startX < scanline.Length) + { + for (float x = scanStart; x < startX + 1; x += subpixelFraction) { - for (float x = endX; x < scanEnd; x += subpixelFraction) - { - scanline[endX] += subpixelFractionPoint; - scanlineDirty = true; - } + scanline[startX] += subpixelFractionPoint; + scanlineDirty = true; } + } - int nextX = startX + 1; - endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge - nextX = Math.Max(nextX, 0); - for (int x = nextX; x < endX; x++) + if (endX >= 0 && endX < scanline.Length) + { + for (float x = endX; x < scanEnd; x += subpixelFraction) { - scanline[x] += subpixelFraction; + scanline[endX] += subpixelFractionPoint; scanlineDirty = true; } } + + int nextX = startX + 1; + endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge + nextX = Math.Max(nextX, 0); + for (int x = nextX; x < endX; x++) + { + scanline[x] += subpixelFraction; + scanlineDirty = true; + } } + } - if (scanlineDirty) + if (scanlineDirty) + { + if (!this.Options.Antialias) { - if (!this.Options.Antialias) + for (int x = 0; x < scanlineWidth; x++) { - for (int x = 0; x < scanlineWidth; x++) + if (scanline[x] >= 0.5) + { + scanline[x] = 1; + } + else { - if (scanline[x] >= 0.5) - { - scanline[x] = 1; - } - else - { - scanline[x] = 0; - } + scanline[x] = 0; } } - - applicator.Apply(scanline, minX, y); } + + applicator.Apply(scanline.Span, minX, y); } } - finally - { - arrayPool.Return(buffer); - } } } } diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index 0acb846c5..612ced5d8 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -88,6 +88,14 @@ namespace SixLabors.ImageSharp.Advanced where TPixel : struct, IPixel => source.Frames.RootFrame.GetPixelRowSpan(row); + /// + /// Gets the assigned to 'source'. + /// + /// The source image + /// Returns the configuration. + internal static MemoryManager GetMemoryManager(this IConfigurable source) + => GetConfiguration(source).MemoryManager; + /// /// Gets the span to the backing buffer. /// diff --git a/src/ImageSharp/ApplyProcessors.cs b/src/ImageSharp/ApplyProcessors.cs index 58a952c40..c4954ef0d 100644 --- a/src/ImageSharp/ApplyProcessors.cs +++ b/src/ImageSharp/ApplyProcessors.cs @@ -4,7 +4,6 @@ using System; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; namespace SixLabors.ImageSharp { diff --git a/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs b/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs index d9767d45e..487f464d8 100644 --- a/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs +++ b/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs @@ -6,6 +6,7 @@ using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; +// ReSharper disable CompareOfFloatsByEqualityOperator namespace SixLabors.ImageSharp.ColorSpaces { /// @@ -143,7 +144,8 @@ namespace SixLabors.ImageSharp.ColorSpaces [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(CieXyChromaticityCoordinates other) { - return this.backingVector.Equals(other.backingVector); + // The memberwise comparison here is a workaround for https://github.com/dotnet/coreclr/issues/16443 + return this.X == other.X && this.Y == other.Y; } /// diff --git a/src/ImageSharp/Common/Extensions/SimdUtils.cs b/src/ImageSharp/Common/Extensions/SimdUtils.cs index 0188bc03c..7f46b7a84 100644 --- a/src/ImageSharp/Common/Extensions/SimdUtils.cs +++ b/src/ImageSharp/Common/Extensions/SimdUtils.cs @@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp Vector magick = new Vector(32768.0f); Vector scale = new Vector(255f) / new Vector(256f); - // need to copy to a temporal struct, because + // need to copy to a temporary struct, because // SimdUtils.Octet.OfUInt32 temp = Unsafe.As, SimdUtils.Octet.OfUInt32>(ref x) // does not work. TODO: This might be a CoreClr bug, need to ask/report var temp = default(Octet.OfUInt32); @@ -124,7 +124,7 @@ namespace SixLabors.ImageSharp Vector magick = new Vector(32768.0f); Vector scale = new Vector(255f) / new Vector(256f); - // need to copy to a temporal struct, because + // need to copy to a temporary struct, because // SimdUtils.Octet.OfUInt32 temp = Unsafe.As, SimdUtils.Octet.OfUInt32>(ref x) // does not work. TODO: This might be a CoreClr bug, need to ask/report var temp = default(Octet.OfUInt32); diff --git a/src/ImageSharp/Common/Extensions/StreamExtensions.cs b/src/ImageSharp/Common/Extensions/StreamExtensions.cs index b717abab1..7a9a34ac1 100644 --- a/src/ImageSharp/Common/Extensions/StreamExtensions.cs +++ b/src/ImageSharp/Common/Extensions/StreamExtensions.cs @@ -29,23 +29,16 @@ namespace SixLabors.ImageSharp } else { - byte[] foo = ArrayPool.Shared.Rent(count); - try + byte[] foo = new byte[count]; + while (count > 0) { - while (count > 0) + int bytesRead = stream.Read(foo, 0, count); + if (bytesRead == 0) { - int bytesRead = stream.Read(foo, 0, count); - if (bytesRead == 0) - { - break; - } - - count -= bytesRead; + break; } - } - finally - { - ArrayPool.Shared.Return(foo); + + count -= bytesRead; } } } diff --git a/src/ImageSharp/Common/Helpers/ParallelFor.cs b/src/ImageSharp/Common/Helpers/ParallelFor.cs new file mode 100644 index 000000000..da9125905 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/ParallelFor.cs @@ -0,0 +1,60 @@ +using System; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp +{ + /// + /// Utility methods for Parallel.For() execution. Use this instead of raw calls! + /// + internal static class ParallelFor + { + /// + /// Helper method to execute Parallel.For using the settings in + /// + public static void WithConfiguration(int fromInclusive, int toExclusive, Configuration configuration, Action body) + { + Parallel.For(fromInclusive, toExclusive, configuration.ParallelOptions, body); + } + + /// + /// Helper method to execute Parallel.For with temporary worker buffer shared between executing tasks. + /// The buffer is not guaranteed to be clean! + /// + /// The value type of the buffer + /// The start index, inclusive. + /// The end index, exclusive. + /// The used for getting the and + /// The length of the requested parallel buffer + /// The delegate that is invoked once per iteration. + public static void WithTemporaryBuffer( + int fromInclusive, + int toExclusive, + Configuration configuration, + int bufferLength, + Action> body) + where T : struct + { + MemoryManager memoryManager = configuration.MemoryManager; + ParallelOptions parallelOptions = configuration.ParallelOptions; + + IBuffer InitBuffer() + { + return memoryManager.Allocate(bufferLength); + } + + void CleanUpBuffer(IBuffer buffer) + { + buffer.Dispose(); + } + + IBuffer BodyFunc(int i, ParallelLoopState state, IBuffer buffer) + { + body(i, buffer); + return buffer; + } + + Parallel.For(fromInclusive, toExclusive, parallelOptions, InitBuffer, BodyFunc, CleanUpBuffer); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 740103533..9a627eeb7 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -12,6 +12,7 @@ using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp { @@ -82,6 +83,11 @@ namespace SixLabors.ImageSharp /// public IEnumerable ImageFormats => this.imageFormats; + /// + /// Gets or sets the that is currently in use. + /// + public MemoryManager MemoryManager { get; set; } = ArrayPoolMemoryManager.CreateDefault(); + /// /// Gets the maximum header size of all the formats. /// diff --git a/src/ImageSharp/DefaultInternalImageProcessorContext.cs b/src/ImageSharp/DefaultInternalImageProcessorContext.cs index 6e6feed84..5aba9ae69 100644 --- a/src/ImageSharp/DefaultInternalImageProcessorContext.cs +++ b/src/ImageSharp/DefaultInternalImageProcessorContext.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Helpers; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.Primitives; @@ -34,6 +36,9 @@ namespace SixLabors.ImageSharp } } + /// + public MemoryManager MemoryManager => this.source.GetConfiguration().MemoryManager; + /// public Image Apply() { diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index e552ac104..9f4dba5b4 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -69,7 +69,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// private BmpInfoHeader infoHeader; - private Configuration configuration; + private readonly Configuration configuration; + + private readonly MemoryManager memoryManager; /// /// Initializes a new instance of the class. @@ -79,6 +81,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp public BmpDecoderCore(Configuration configuration, IBmpDecoderOptions options) { this.configuration = configuration; + this.memoryManager = configuration.MemoryManager; } /// @@ -221,9 +224,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp var color = default(TPixel); var rgba = new Rgba32(0, 0, 0, 255); - using (var buffer = Buffer2D.CreateClean(width, height)) + using (var buffer = this.memoryManager.AllocateClean2D(width, height)) { - this.UncompressRle8(width, buffer); + this.UncompressRle8(width, buffer.Span); for (int y = 0; y < height; y++) { @@ -343,15 +346,17 @@ namespace SixLabors.ImageSharp.Formats.Bmp padding = 4 - padding; } - using (var row = Buffer.CreateClean(arrayWidth + padding)) + using (IManagedByteBuffer row = this.memoryManager.AllocateCleanManagedByteBuffer(arrayWidth + padding)) { var color = default(TPixel); var rgba = new Rgba32(0, 0, 0, 255); + Span rowSpan = row.Span; + for (int y = 0; y < height; y++) { int newY = Invert(y, height, inverted); - this.currentStream.Read(row.Array, 0, row.Length); + this.currentStream.Read(row.Array, 0, row.Length()); int offset = 0; Span pixelRow = pixels.GetRowSpan(newY); @@ -362,7 +367,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp for (int shift = 0; shift < ppb && (x + shift) < width; shift++) { - int colorIndex = ((row[offset] >> (8 - bits - (shift * bits))) & mask) * 4; + int colorIndex = ((rowSpan[offset] >> (8 - bits - (shift * bits))) & mask) * 4; int newX = colOffset + shift; // Stored in b-> g-> r order. @@ -393,7 +398,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp var color = default(TPixel); var rgba = new Rgba32(0, 0, 0, 255); - using (var buffer = new Buffer(stride)) + using (var buffer = this.memoryManager.AllocateManagedByteBuffer(stride)) { for (int y = 0; y < height; y++) { @@ -430,14 +435,15 @@ namespace SixLabors.ImageSharp.Formats.Bmp where TPixel : struct, IPixel { int padding = CalculatePadding(width, 3); - using (var row = new PixelArea(width, ComponentOrder.Zyx, padding)) + + using (IManagedByteBuffer row = this.memoryManager.AllocatePaddedPixelRowBuffer(width, 3, padding)) { for (int y = 0; y < height; y++) { - row.Read(this.currentStream); - + this.currentStream.Read(row); int newY = Invert(y, height, inverted); - pixels.CopyFrom(row, newY); + Span pixelSpan = pixels.GetRowSpan(newY); + PixelOperations.Instance.PackFromBgr24Bytes(row.Span, pixelSpan, width); } } } @@ -454,14 +460,15 @@ namespace SixLabors.ImageSharp.Formats.Bmp where TPixel : struct, IPixel { int padding = CalculatePadding(width, 4); - using (var row = new PixelArea(width, ComponentOrder.Zyxw, padding)) + + using (IManagedByteBuffer row = this.memoryManager.AllocatePaddedPixelRowBuffer(width, 4, padding)) { for (int y = 0; y < height; y++) { - row.Read(this.currentStream); - + this.currentStream.Read(row); int newY = Invert(y, height, inverted); - pixels.CopyFrom(row, newY); + Span pixelSpan = pixels.GetRowSpan(newY); + PixelOperations.Instance.PackFromBgra32Bytes(row.Span, pixelSpan, width); } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index 366afceb5..d80e43c63 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Bmp @@ -23,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp public void Encode(Image image, Stream stream) where TPixel : struct, IPixel { - var encoder = new BmpEncoderCore(this); + var encoder = new BmpEncoderCore(this, image.GetMemoryManager()); encoder.Encode(image, stream); } } diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index d34d17084..66c8b6c08 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -4,6 +4,7 @@ using System; using System.IO; using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Bmp @@ -21,14 +22,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Gets or sets the number of bits per pixel. /// - private BmpBitsPerPixel bitsPerPixel; + private readonly BmpBitsPerPixel bitsPerPixel; + + private readonly MemoryManager memoryManager; /// /// Initializes a new instance of the class. /// /// The encoder options - public BmpEncoderCore(IBmpEncoderOptions options) + /// The memory manager + public BmpEncoderCore(IBmpEncoderOptions options, MemoryManager memoryManager) { + this.memoryManager = memoryManager; this.bitsPerPixel = options.BitsPerPixel; } @@ -145,6 +150,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp } } + private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) + { + return this.memoryManager.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding); + } + /// /// Writes the 32bit color palette to the stream. /// @@ -154,12 +164,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp private void Write32Bit(EndianBinaryWriter writer, PixelAccessor pixels) where TPixel : struct, IPixel { - using (PixelArea row = new PixelArea(pixels.Width, ComponentOrder.Zyxw, this.padding)) + using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4)) { for (int y = pixels.Height - 1; y >= 0; y--) { - pixels.CopyTo(row, y); - writer.Write(row.Bytes, 0, row.Length); + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToBgra32Bytes(pixelSpan, row.Span, pixelSpan.Length); + writer.Write(row.Array, 0, row.Length()); } } } @@ -173,12 +184,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp private void Write24Bit(EndianBinaryWriter writer, PixelAccessor pixels) where TPixel : struct, IPixel { - using (PixelArea row = new PixelArea(pixels.Width, ComponentOrder.Zyx, this.padding)) + using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 3)) { for (int y = pixels.Height - 1; y >= 0; y--) { - pixels.CopyTo(row, y); - writer.Write(row.Bytes, 0, row.Length); + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToBgr24Bytes(pixelSpan, row.Span, pixelSpan.Length); + writer.Write(row.Array, 0, row.Length()); } } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 3c2251805..48c8ceb8c 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The global color table. /// - private Buffer globalColorTable; + private IManagedByteBuffer globalColorTable; /// /// The global color table length @@ -92,6 +92,8 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public FrameDecodingMode DecodingMode { get; } + private MemoryManager MemoryManager => this.configuration.MemoryManager; + /// /// Decodes the stream to the image. /// @@ -333,18 +335,12 @@ namespace SixLabors.ImageSharp.Formats.Gif continue; } - byte[] commentsBuffer = ArrayPool.Shared.Rent(length); - - try + using (IManagedByteBuffer commentsBuffer = this.MemoryManager.AllocateManagedByteBuffer(length)) { - this.currentStream.Read(commentsBuffer, 0, length); - string comments = this.TextEncoding.GetString(commentsBuffer, 0, length); + this.currentStream.Read(commentsBuffer.Array, 0, length); + string comments = this.TextEncoding.GetString(commentsBuffer.Array, 0, length); this.metaData.Properties.Add(new ImageProperty(GifConstants.Comments, comments)); } - finally - { - ArrayPool.Shared.Return(commentsBuffer); - } } } @@ -359,22 +355,23 @@ namespace SixLabors.ImageSharp.Formats.Gif { GifImageDescriptor imageDescriptor = this.ReadImageDescriptor(); - Buffer localColorTable = null; - Buffer indices = null; + IManagedByteBuffer localColorTable = null; + IManagedByteBuffer indices = null; try { // Determine the color table for this frame. If there is a local one, use it otherwise use the global color table. if (imageDescriptor.LocalColorTableFlag) { int length = imageDescriptor.LocalColorTableSize * 3; - localColorTable = Buffer.CreateClean(length); + localColorTable = this.configuration.MemoryManager.AllocateManagedByteBuffer(length, true); this.currentStream.Read(localColorTable.Array, 0, length); } - indices = Buffer.CreateClean(imageDescriptor.Width * imageDescriptor.Height); + indices = this.configuration.MemoryManager.AllocateManagedByteBuffer(imageDescriptor.Width * imageDescriptor.Height, true); - this.ReadFrameIndices(imageDescriptor, indices); - this.ReadFrameColors(ref image, ref previousFrame, indices, localColorTable ?? this.globalColorTable, imageDescriptor); + this.ReadFrameIndices(imageDescriptor, indices.Span); + IManagedByteBuffer colorTable = localColorTable ?? this.globalColorTable; + this.ReadFrameColors(ref image, ref previousFrame, indices.Span, colorTable.Span, imageDescriptor); // Skip any remaining blocks this.Skip(0); @@ -395,7 +392,7 @@ namespace SixLabors.ImageSharp.Formats.Gif private void ReadFrameIndices(GifImageDescriptor imageDescriptor, Span indices) { int dataSize = this.currentStream.ReadByte(); - using (var lzwDecoder = new LzwDecoder(this.currentStream)) + using (var lzwDecoder = new LzwDecoder(this.configuration.MemoryManager, this.currentStream)) { lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize, indices); } @@ -445,7 +442,7 @@ namespace SixLabors.ImageSharp.Formats.Gif imageFrame = currentFrame; - this.RestoreToBackground(imageFrame, image.Width, image.Height); + this.RestoreToBackground(imageFrame); } int i = 0; @@ -535,9 +532,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The pixel format. /// The frame. - /// Width of the image. - /// Height of the image. - private void RestoreToBackground(ImageFrame frame, int imageWidth, int imageHeight) + private void RestoreToBackground(ImageFrame frame) where TPixel : struct, IPixel { if (this.restoreArea == null) @@ -545,28 +540,8 @@ namespace SixLabors.ImageSharp.Formats.Gif return; } - // Optimization for when the size of the frame is the same as the image size. - if (this.restoreArea.Value.Width == imageWidth && - this.restoreArea.Value.Height == imageHeight) - { - using (PixelAccessor pixelAccessor = frame.Lock()) - { - pixelAccessor.Reset(); - } - } - else - { - using (var emptyRow = new PixelArea(this.restoreArea.Value.Width, ComponentOrder.Xyzw)) - { - using (PixelAccessor pixelAccessor = frame.Lock()) - { - for (int y = this.restoreArea.Value.Top; y < this.restoreArea.Value.Top + this.restoreArea.Value.Height; y++) - { - pixelAccessor.CopyFrom(emptyRow, y, this.restoreArea.Value.Left); - } - } - } - } + BufferArea pixelArea = frame.PixelBuffer.GetArea(this.restoreArea.Value); + pixelArea.Clear(); this.restoreArea = null; } @@ -606,7 +581,8 @@ namespace SixLabors.ImageSharp.Formats.Gif if (this.logicalScreenDescriptor.GlobalColorTableFlag) { this.globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3; - this.globalColorTable = Buffer.CreateClean(this.globalColorTableLength); + + this.globalColorTable = this.MemoryManager.AllocateManagedByteBuffer(this.globalColorTableLength, true); // Read the global color table from the stream stream.Read(this.globalColorTable.Array, 0, this.globalColorTableLength); diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index ccf46a17d..b548098be 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -5,6 +5,8 @@ using System; using System.Collections.Generic; using System.IO; using System.Text; + +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Quantizers; @@ -44,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Gif public void Encode(Image image, Stream stream) where TPixel : struct, IPixel { - var encoder = new GifEncoderCore(this); + var encoder = new GifEncoderCore(image.GetConfiguration().MemoryManager, this); encoder.Encode(image, stream); } } diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 41c8e944d..13ca5f2c6 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Text; using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Quantizers; @@ -18,6 +19,8 @@ namespace SixLabors.ImageSharp.Formats.Gif /// internal sealed class GifEncoderCore { + private readonly MemoryManager memoryManager; + /// /// The temp buffer used to reduce allocations. /// @@ -61,9 +64,11 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The options for the encoder. - public GifEncoderCore(IGifEncoderOptions options) + public GifEncoderCore(MemoryManager memoryManager, IGifEncoderOptions options) { + this.memoryManager = memoryManager; this.textEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding; this.quantizer = options.Quantizer; @@ -350,24 +355,21 @@ namespace SixLabors.ImageSharp.Formats.Gif // Get max colors for bit depth. int colorTableLength = (int)Math.Pow(2, this.bitDepth) * 3; - byte[] colorTable = ArrayPool.Shared.Rent(colorTableLength); var rgb = default(Rgb24); - try + using (IManagedByteBuffer colorTable = this.memoryManager.AllocateManagedByteBuffer(colorTableLength)) { + Span colorTableSpan = colorTable.Span; + for (int i = 0; i < pixelCount; i++) { int offset = i * 3; image.Palette[i].ToRgb24(ref rgb); - colorTable[offset] = rgb.R; - colorTable[offset + 1] = rgb.G; - colorTable[offset + 2] = rgb.B; + colorTableSpan[offset] = rgb.R; + colorTableSpan[offset + 1] = rgb.G; + colorTableSpan[offset + 2] = rgb.B; } - writer.Write(colorTable, 0, colorTableLength); - } - finally - { - ArrayPool.Shared.Return(colorTable); + writer.Write(colorTable.Array, 0, colorTableLength); } } @@ -380,7 +382,7 @@ namespace SixLabors.ImageSharp.Formats.Gif private void WriteImageData(QuantizedImage image, EndianBinaryWriter writer) where TPixel : struct, IPixel { - using (var encoder = new LzwEncoder(image.Pixels, (byte)this.bitDepth)) + using (var encoder = new LzwEncoder(this.memoryManager, image.Pixels, (byte)this.bitDepth)) { encoder.Encode(writer.BaseStream); } diff --git a/src/ImageSharp/Formats/Gif/LzwDecoder.cs b/src/ImageSharp/Formats/Gif/LzwDecoder.cs index 3284dad65..0c73efea4 100644 --- a/src/ImageSharp/Formats/Gif/LzwDecoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwDecoder.cs @@ -5,6 +5,8 @@ using System; using System.Buffers; using System.IO; +using SixLabors.ImageSharp.Memory; + namespace SixLabors.ImageSharp.Formats.Gif { /// @@ -30,17 +32,17 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The prefix buffer. /// - private readonly int[] prefix; + private readonly IBuffer prefix; /// /// The suffix buffer. /// - private readonly int[] suffix; + private readonly IBuffer suffix; /// /// The pixel stack buffer. /// - private readonly int[] pixelStack; + private readonly IBuffer pixelStack; /// /// A value indicating whether this instance of the given entity has been disposed. @@ -59,21 +61,18 @@ namespace SixLabors.ImageSharp.Formats.Gif /// Initializes a new instance of the class /// and sets the stream, where the compressed data should be read from. /// + /// The to use for buffer allocations. /// The stream to read from. /// is null. - public LzwDecoder(Stream stream) + public LzwDecoder(MemoryManager memoryManager, Stream stream) { Guard.NotNull(stream, nameof(stream)); this.stream = stream; - this.prefix = ArrayPool.Shared.Rent(MaxStackSize); - this.suffix = ArrayPool.Shared.Rent(MaxStackSize); - this.pixelStack = ArrayPool.Shared.Rent(MaxStackSize + 1); - - Array.Clear(this.prefix, 0, MaxStackSize); - Array.Clear(this.suffix, 0, MaxStackSize); - Array.Clear(this.pixelStack, 0, MaxStackSize + 1); + this.prefix = memoryManager.Allocate(MaxStackSize, true); + this.suffix = memoryManager.Allocate(MaxStackSize, true); + this.pixelStack = memoryManager.Allocate(MaxStackSize + 1, true); } /// @@ -116,10 +115,14 @@ namespace SixLabors.ImageSharp.Formats.Gif int data = 0; int first = 0; + Span prefixSpan = this.prefix.Span; + Span suffixSpan = this.suffix.Span; + Span pixelStackSpan = this.pixelStack.Span; + for (code = 0; code < clearCode; code++) { - this.prefix[code] = 0; - this.suffix[code] = (byte)code; + prefixSpan[code] = 0; + suffixSpan[code] = (byte)code; } byte[] buffer = new byte[255]; @@ -173,7 +176,7 @@ namespace SixLabors.ImageSharp.Formats.Gif if (oldCode == NullCode) { - this.pixelStack[top++] = this.suffix[code]; + pixelStackSpan[top++] = suffixSpan[code]; oldCode = code; first = code; continue; @@ -182,27 +185,27 @@ namespace SixLabors.ImageSharp.Formats.Gif int inCode = code; if (code == availableCode) { - this.pixelStack[top++] = (byte)first; + pixelStackSpan[top++] = (byte)first; code = oldCode; } while (code > clearCode) { - this.pixelStack[top++] = this.suffix[code]; - code = this.prefix[code]; + pixelStackSpan[top++] = suffixSpan[code]; + code = prefixSpan[code]; } - first = this.suffix[code]; + first = suffixSpan[code]; - this.pixelStack[top++] = this.suffix[code]; + pixelStackSpan[top++] = suffixSpan[code]; // Fix for Gifs that have "deferred clear code" as per here : // https://bugzilla.mozilla.org/show_bug.cgi?id=55918 if (availableCode < MaxStackSize) { - this.prefix[availableCode] = oldCode; - this.suffix[availableCode] = first; + prefixSpan[availableCode] = oldCode; + suffixSpan[availableCode] = first; availableCode++; if (availableCode == codeMask + 1 && availableCode < MaxStackSize) { @@ -218,7 +221,7 @@ namespace SixLabors.ImageSharp.Formats.Gif top--; // Clear missing pixels - pixels[xyz++] = (byte)this.pixelStack[top]; + pixels[xyz++] = (byte)pixelStackSpan[top]; } } @@ -262,9 +265,9 @@ namespace SixLabors.ImageSharp.Formats.Gif if (disposing) { - ArrayPool.Shared.Return(this.prefix); - ArrayPool.Shared.Return(this.suffix); - ArrayPool.Shared.Return(this.pixelStack); + this.prefix?.Dispose(); + this.suffix?.Dispose(); + this.pixelStack?.Dispose(); } this.isDisposed = true; diff --git a/src/ImageSharp/Formats/Gif/LzwEncoder.cs b/src/ImageSharp/Formats/Gif/LzwEncoder.cs index b7bfd0fd2..35c414896 100644 --- a/src/ImageSharp/Formats/Gif/LzwEncoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwEncoder.cs @@ -5,6 +5,8 @@ using System; using System.Buffers; using System.IO; +using SixLabors.ImageSharp.Memory; + namespace SixLabors.ImageSharp.Formats.Gif { /// @@ -69,12 +71,12 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The hash table. /// - private readonly int[] hashTable; + private readonly IBuffer hashTable; /// /// The code table. /// - private readonly int[] codeTable; + private readonly IBuffer codeTable; /// /// Define the storage for the packet accumulator. @@ -189,17 +191,16 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The array of indexed pixels. /// The color depth in bits. - public LzwEncoder(byte[] indexedPixels, int colorDepth) + public LzwEncoder(MemoryManager memoryManager, byte[] indexedPixels, int colorDepth) { this.pixelArray = indexedPixels; this.initialCodeSize = Math.Max(2, colorDepth); - this.hashTable = ArrayPool.Shared.Rent(HashSize); - this.codeTable = ArrayPool.Shared.Rent(HashSize); - Array.Clear(this.hashTable, 0, HashSize); - Array.Clear(this.codeTable, 0, HashSize); + this.hashTable = memoryManager.Allocate(HashSize, true); + this.codeTable = memoryManager.Allocate(HashSize, true); } /// @@ -258,7 +259,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The output stream. private void ClearBlock(Stream stream) { - this.ResetCodeTable(this.hsize); + this.ResetCodeTable(); this.freeEntry = this.clearCode + 2; this.clearFlag = true; @@ -268,13 +269,15 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Reset the code table. /// - /// The hash size. - private void ResetCodeTable(int size) + private void ResetCodeTable() { - for (int i = 0; i < size; ++i) - { - this.hashTable[i] = -1; - } + this.hashTable.Span.Fill(-1); + + // Original code: + // for (int i = 0; i < size; ++i) + // { + // this.hashTable[i] = -1; + // } } /// @@ -316,23 +319,26 @@ namespace SixLabors.ImageSharp.Formats.Gif hsizeReg = this.hsize; - this.ResetCodeTable(hsizeReg); // clear hash table + this.ResetCodeTable(); // clear hash table this.Output(this.clearCode, stream); + Span hashTableSpan = this.hashTable.Span; + Span codeTableSpan = this.codeTable.Span; + while ((c = this.NextPixel()) != Eof) { fcode = (c << this.maxbits) + ent; int i = (c << hshift) ^ ent /* = 0 */; - if (this.hashTable[i] == fcode) + if (hashTableSpan[i] == fcode) { - ent = this.codeTable[i]; + ent = codeTableSpan[i]; continue; } // Non-empty slot - if (this.hashTable[i] >= 0) + if (hashTableSpan[i] >= 0) { int disp = hsizeReg - i; if (i == 0) @@ -347,15 +353,15 @@ namespace SixLabors.ImageSharp.Formats.Gif i += hsizeReg; } - if (this.hashTable[i] == fcode) + if (hashTableSpan[i] == fcode) { - ent = this.codeTable[i]; + ent = codeTableSpan[i]; break; } } - while (this.hashTable[i] >= 0); + while (hashTableSpan[i] >= 0); - if (this.hashTable[i] == fcode) + if (hashTableSpan[i] == fcode) { continue; } @@ -365,8 +371,8 @@ namespace SixLabors.ImageSharp.Formats.Gif ent = c; if (this.freeEntry < this.maxmaxcode) { - this.codeTable[i] = this.freeEntry++; // code -> hashtable - this.hashTable[i] = fcode; + codeTableSpan[i] = this.freeEntry++; // code -> hashtable + hashTableSpan[i] = fcode; } else { @@ -483,8 +489,8 @@ namespace SixLabors.ImageSharp.Formats.Gif if (disposing) { - ArrayPool.Shared.Return(this.hashTable); - ArrayPool.Shared.Return(this.codeTable); + this.hashTable?.Dispose(); + this.codeTable?.Dispose(); } this.isDisposed = true; diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs index 39a6bee2e..d8963a8b6 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs @@ -26,6 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common return; } + ref float destBase = ref area.GetReferenceToOrigin(); + // TODO: Optimize: implement all the cases with loopless special code! (T4?) for (int y = 0; y < 8; y++) { @@ -40,9 +42,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common for (int i = 0; i < verticalScale; i++) { + int baseIdx = ((yy + i) * area.Stride) + xx; + for (int j = 0; j < horizontalScale; j++) { - area[xx + j, yy + i] = value; + // area[xx + j, yy + i] = value; + Unsafe.Add(ref destBase, baseIdx + j) = value; } } } @@ -53,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common public void CopyTo(BufferArea area) { ref byte selfBase = ref Unsafe.As(ref this); - ref byte destBase = ref Unsafe.As(ref area.GetReferenceToOrigo()); + ref byte destBase = ref Unsafe.As(ref area.GetReferenceToOrigin()); int destStride = area.Stride * sizeof(float); CopyRowImpl(ref selfBase, ref destBase, destStride, 0); @@ -76,7 +81,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common private void CopyTo2x2(BufferArea area) { - ref float destBase = ref area.GetReferenceToOrigo(); + ref float destBase = ref area.GetReferenceToOrigin(); int destStride = area.Stride; this.WidenCopyImpl2x2(ref destBase, 0, destStride); diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs index 574967b6b..5e8e8fa2c 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs @@ -8,7 +8,7 @@ using SixLabors.Primitives; namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder { /// - /// Encapsulates the implementation of processing "raw" -s into Jpeg image channels. + /// Encapsulates the implementation of processing "raw" -s into Jpeg image channels. /// [StructLayout(LayoutKind.Sequential)] internal struct JpegBlockPostProcessor diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs index 87c1431e0..1be637b6d 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs @@ -22,11 +22,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder /// /// Initializes a new instance of the class. /// - public JpegComponentPostProcessor(JpegImagePostProcessor imagePostProcessor, IJpegComponent component) + public JpegComponentPostProcessor(MemoryManager memoryManager, JpegImagePostProcessor imagePostProcessor, IJpegComponent component) { this.Component = component; this.ImagePostProcessor = imagePostProcessor; - this.ColorBuffer = new Buffer2D(imagePostProcessor.PostProcessorBufferSize); + this.ColorBuffer = memoryManager.Allocate2D( + imagePostProcessor.PostProcessorBufferSize.Width, + imagePostProcessor.PostProcessorBufferSize.Height); this.BlockRowsPerStep = JpegImagePostProcessor.BlockRowsPerStep / this.Component.SubSamplingDivisors.Height; this.blockAreaSize = this.Component.SubSamplingDivisors * 8; @@ -43,7 +45,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder public IJpegComponent Component { get; } /// - /// Gets the temporal working buffer of color values. + /// Gets the temporary working buffer of color values. /// public Buffer2D ColorBuffer { get; } diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs index 125ec5272..2adf3e02d 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder /// /// Temporal buffer to store a row of colors. /// - private readonly Buffer rgbaBuffer; + private readonly IBuffer rgbaBuffer; /// /// The corresponding to the current determined by . @@ -44,16 +44,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The representing the uncompressed spectral Jpeg data - public JpegImagePostProcessor(IRawJpegData rawJpeg) + public JpegImagePostProcessor(MemoryManager memoryManager, IRawJpegData rawJpeg) { this.RawJpeg = rawJpeg; IJpegComponent c0 = rawJpeg.Components.First(); this.NumberOfPostProcessorSteps = c0.SizeInBlocks.Height / BlockRowsPerStep; this.PostProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, PixelRowsPerStep); - this.ComponentProcessors = rawJpeg.Components.Select(c => new JpegComponentPostProcessor(this, c)).ToArray(); - this.rgbaBuffer = new Buffer(rawJpeg.ImageSizeInPixels.Width); + this.ComponentProcessors = rawJpeg.Components.Select(c => new JpegComponentPostProcessor(memoryManager, this, c)).ToArray(); + this.rgbaBuffer = memoryManager.Allocate(rawJpeg.ImageSizeInPixels.Width); this.colorConverter = ColorConverters.JpegColorConverter.GetConverter(rawJpeg.ColorSpace); } @@ -73,7 +74,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder public int NumberOfPostProcessorSteps { get; } /// - /// Gets the size of the temporal buffers we need to allocate into . + /// Gets the size of the temporary buffers we need to allocate into . /// public Size PostProcessorBufferSize { get; } @@ -149,11 +150,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder int y = yy - this.PixelRowCounter; var values = new ColorConverters.JpegColorConverter.ComponentValues(buffers, y); - this.colorConverter.ConvertToRGBA(values, this.rgbaBuffer); + this.colorConverter.ConvertToRGBA(values, this.rgbaBuffer.Span); Span destRow = destination.GetPixelRowSpan(yy); - PixelOperations.Instance.PackFromVector4(this.rgbaBuffer, destRow, destination.Width); + PixelOperations.Instance.PackFromVector4(this.rgbaBuffer.Span, destRow, destination.Width); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.cs b/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.cs new file mode 100644 index 000000000..1bb37a7d3 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; + +// +namespace SixLabors.ImageSharp.Formats.Jpeg.Common +{ + 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/Common/GenericBlock8x8.Generated.tt b/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.tt new file mode 100644 index 000000000..d9b15b34f --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.tt @@ -0,0 +1,43 @@ +<# +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +#> +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; + +// +namespace SixLabors.ImageSharp.Formats.Jpeg.Common +{ + 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: + <# + PushIndent(" "); + Write(" "); + for (int y = 0; y < 8; y++) + { + Write("private T "); + for (int x = 0; x < 8; x++) + { + Write($"_y{y}_x{x}"); + if (x < 7) Write(", "); + } + WriteLine(";"); + } + PopIndent(); + #> + + #pragma warning restore 169 + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.cs b/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.cs new file mode 100644 index 000000000..e20e850d7 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.cs @@ -0,0 +1,129 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Formats.Jpeg.Common +{ + /// + /// A generic 8x8 block implementation, useful for manipulating custom 8x8 pixel data. + /// + [StructLayout(LayoutKind.Sequential)] + internal unsafe partial struct GenericBlock8x8 + where T : struct + { + public const int Size = 64; + + public const int SizeInBytes = Size * 3; + + /// + /// 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+coulumn 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; + } + + public void LoadAndStretchEdges(IPixelSource source, int sourceX, int sourceY) + where TPixel : struct, IPixel + { + var buffer = source.PixelBuffer as Buffer2D; + if (buffer == null) + { + throw new InvalidOperationException("LoadAndStretchEdges() is only valid for TPixel == T !"); + } + + this.LoadAndStretchEdges(buffer, sourceX, sourceY); + } + + /// + /// 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) + { + 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); + ref byte imageStart = ref Unsafe.As( + ref Unsafe.Add(ref source.GetRowSpan(sourceY).DangerousGetPinnableReference(), sourceX)); + + int blockRowSizeInBytes = 8 * Unsafe.SizeOf(); + int imageRowSizeInBytes = source.Width * Unsafe.SizeOf(); + + for (int y = 0; y < height; y++) + { + ref byte s = ref Unsafe.Add(ref imageStart, y * imageRowSizeInBytes); + 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() => new Span(Unsafe.AsPointer(ref this), Size); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs index 38507d58c..2a3817400 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; using System.IO; using System.Runtime.CompilerServices; @@ -12,6 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// Bytes is a byte buffer, similar to a stream, except that it /// has to be able to unread more than 1 byte, due to byte stuffing. /// Byte stuffing is specified in section F.1.2.3. + /// TODO: Optimize buffer management inside this class! /// internal struct Bytes : IDisposable { @@ -48,20 +48,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// public int UnreadableBytes; - private static readonly ArrayPool BytePool = ArrayPool.Create(BufferSize, 50); - - private static readonly ArrayPool IntPool = ArrayPool.Create(BufferSize, 50); - /// /// Creates a new instance of the , and initializes it's buffer. /// /// The bytes created public static Bytes Create() { + // DO NOT bother with buffers and array pooling here! + // It only makes things worse! return new Bytes { - Buffer = BytePool.Rent(BufferSize), - BufferAsInt = IntPool.Rent(BufferSize) + Buffer = new byte[BufferSize], + BufferAsInt = new int[BufferSize] }; } @@ -70,12 +68,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// public void Dispose() { - if (this.Buffer != null) - { - BytePool.Return(this.Buffer); - IntPool.Return(this.BufferAsInt); - } - this.Buffer = null; this.BufferAsInt = null; } diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/InputProcessor.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/InputProcessor.cs index a7a5fcd98..d88481ba4 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/InputProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/InputProcessor.cs @@ -5,6 +5,8 @@ using System; using System.IO; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder { /// @@ -43,7 +45,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder public Stream InputStream { get; } /// - /// Gets the temporal buffer, same instance as + /// Gets the temporary buffer, same instance as /// public byte[] Temp { get; } diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs index c87752b37..e83dd75a5 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs @@ -54,8 +54,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// /// Initializes /// + /// The to use for buffer allocations. /// The instance - public void InitializeDerivedData(OrigJpegDecoderCore decoder) + public void InitializeDerivedData(MemoryManager memoryManager, OrigJpegDecoderCore decoder) { // For 4-component images (either CMYK or YCbCrK), we only support two // hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22]. @@ -78,7 +79,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors); } - this.SpectralBlocks = Buffer2D.CreateClean(this.SizeInBlocks); + this.SpectralBlocks = memoryManager.Allocate2D(this.SizeInBlocks.Width, this.SizeInBlocks.Height, true); } /// diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigHuffmanTree.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigHuffmanTree.cs index 4c97d5741..dbc7bb0f7 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigHuffmanTree.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigHuffmanTree.cs @@ -3,13 +3,16 @@ using System; using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder { /// /// Represents a Huffman tree /// - internal struct OrigHuffmanTree : IDisposable + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct OrigHuffmanTree { /// /// The index of the AC table row @@ -67,35 +70,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// are 1 plus the code length, or 0 if the value is too large to fit in /// lutSize bits. /// - public int[] Lut; + public FixedInt32Buffer256 Lut; /// /// Gets the the decoded values, sorted by their encoding. /// - public int[] Values; + public FixedInt32Buffer256 Values; /// /// Gets the array of minimum codes. /// MinCodes[i] is the minimum code of length i, or -1 if there are no codes of that length. /// - public int[] MinCodes; + public FixedInt32Buffer16 MinCodes; /// /// Gets the array of maximum codes. /// MaxCodes[i] is the maximum code of length i, or -1 if there are no codes of that length. /// - public int[] MaxCodes; + public FixedInt32Buffer16 MaxCodes; /// /// Gets the array of indices. Indices[i] is the index into Values of MinCodes[i]. /// - public int[] Indices; - - private static readonly ArrayPool IntPool256 = ArrayPool.Create(MaxNCodes, 50); - - private static readonly ArrayPool BytePool256 = ArrayPool.Create(MaxNCodes, 50); - - private static readonly ArrayPool CodesPool16 = ArrayPool.Create(MaxCodeLength, 50); + public FixedInt32Buffer16 Indices; /// /// Creates and initializes an array of instances of size @@ -103,35 +100,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// An array of instances representing the Huffman tables public static OrigHuffmanTree[] CreateHuffmanTrees() { - OrigHuffmanTree[] result = new OrigHuffmanTree[NumberOfTrees]; - for (int i = 0; i < MaxTc + 1; i++) - { - for (int j = 0; j < MaxTh + 1; j++) - { - result[(i * ThRowSize) + j].Init(); - } - } - - return result; - } - - /// - /// Disposes the underlying buffers - /// - public void Dispose() - { - IntPool256.Return(this.Lut, true); - IntPool256.Return(this.Values, true); - CodesPool16.Return(this.MinCodes, true); - CodesPool16.Return(this.MaxCodes, true); - CodesPool16.Return(this.Indices, true); + return new OrigHuffmanTree[NumberOfTrees]; } /// /// Internal part of the DHT processor, whatever does it mean /// /// The decoder instance - /// The temporal buffer that holds the data that has been read from the Jpeg stream + /// The temporary buffer that holds the data that has been read from the Jpeg stream /// Remaining bits public void ProcessDefineHuffmanTablesMarkerLoop( ref InputProcessor inputProcessor, @@ -166,75 +142,76 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder throw new ImageFormatException("DHT has wrong length"); } - byte[] values = null; - try - { - values = BytePool256.Rent(MaxNCodes); - inputProcessor.ReadFull(values, 0, this.Length); + byte[] values = new byte[MaxNCodes]; + inputProcessor.ReadFull(values, 0, this.Length); + fixed (int* valuesPtr = this.Values.Data) + fixed (int* lutPtr = this.Lut.Data) + { for (int i = 0; i < values.Length; i++) { - this.Values[i] = values[i]; + valuesPtr[i] = values[i]; } - } - finally - { - BytePool256.Return(values, true); - } - - // Derive the look-up table. - for (int i = 0; i < this.Lut.Length; i++) - { - this.Lut[i] = 0; - } - int x = 0, code = 0; + // Derive the look-up table. + for (int i = 0; i < MaxNCodes; i++) + { + lutPtr[i] = 0; + } - for (int i = 0; i < LutSizeLog2; i++) - { - code <<= 1; + int x = 0, code = 0; - for (int j = 0; j < ncodes[i]; j++) + for (int i = 0; i < LutSizeLog2; i++) { - // The codeLength is 1+i, so shift code by 8-(1+i) to - // calculate the high bits for every 8-bit sequence - // whose codeLength's high bits matches code. - // The high 8 bits of lutValue are the encoded value. - // The low 8 bits are 1 plus the codeLength. - int base2 = code << (7 - i); - int lutValue = (this.Values[x] << 8) | (2 + i); - - for (int k = 0; k < 1 << (7 - i); k++) + code <<= 1; + + for (int j = 0; j < ncodes[i]; j++) { - this.Lut[base2 | k] = lutValue; + // The codeLength is 1+i, so shift code by 8-(1+i) to + // calculate the high bits for every 8-bit sequence + // whose codeLength's high bits matches code. + // The high 8 bits of lutValue are the encoded value. + // The low 8 bits are 1 plus the codeLength. + int base2 = code << (7 - i); + int lutValue = (valuesPtr[x] << 8) | (2 + i); + + for (int k = 0; k < 1 << (7 - i); k++) + { + lutPtr[base2 | k] = lutValue; + } + + code++; + x++; } - - code++; - x++; } } - // Derive minCodes, maxCodes, and indices. - int c = 0, index = 0; - for (int i = 0; i < ncodes.Length; i++) + fixed (int* minCodesPtr = this.MinCodes.Data) + fixed (int* maxCodesPtr = this.MaxCodes.Data) + fixed (int* indicesPtr = this.Indices.Data) { - int nc = ncodes[i]; - if (nc == 0) - { - this.MinCodes[i] = -1; - this.MaxCodes[i] = -1; - this.Indices[i] = -1; - } - else + // Derive minCodes, maxCodes, and indices. + int c = 0, index = 0; + for (int i = 0; i < ncodes.Length; i++) { - this.MinCodes[i] = c; - this.MaxCodes[i] = c + nc - 1; - this.Indices[i] = index; - c += nc; - index += nc; - } + int nc = ncodes[i]; + if (nc == 0) + { + minCodesPtr[i] = -1; + maxCodesPtr[i] = -1; + indicesPtr[i] = -1; + } + else + { + minCodesPtr[i] = c; + maxCodesPtr[i] = c + nc - 1; + indicesPtr[i] = index; + c += nc; + index += nc; + } - c <<= 1; + c <<= 1; + } } } @@ -244,21 +221,42 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// The code /// The code length /// The value + [MethodImpl(MethodImplOptions.AggressiveInlining)] public int GetValue(int code, int codeLength) { return this.Values[this.Indices[codeLength] + code - this.MinCodes[codeLength]]; } - /// - /// Initializes the Huffman tree - /// - private void Init() + [StructLayout(LayoutKind.Sequential)] + internal struct FixedInt32Buffer256 { - this.Lut = IntPool256.Rent(MaxNCodes); - this.Values = IntPool256.Rent(MaxNCodes); - this.MinCodes = CodesPool16.Rent(MaxCodeLength); - this.MaxCodes = CodesPool16.Rent(MaxCodeLength); - this.Indices = CodesPool16.Rent(MaxCodeLength); + public fixed int Data[256]; + + public int this[int idx] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + ref int self = ref Unsafe.As(ref this); + return Unsafe.Add(ref self, idx); + } + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct FixedInt32Buffer16 + { + public fixed int Data[16]; + + public int this[int idx] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + ref int self = ref Unsafe.As(ref this); + return Unsafe.Add(ref self, idx); + } + } } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/RgbToYCbCrTables.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/RgbToYCbCrTables.cs index 02bd451b9..923fe244e 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/RgbToYCbCrTables.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/RgbToYCbCrTables.cs @@ -3,11 +3,14 @@ using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder { /// /// Provides 8-bit lookup tables for converting from Rgb to YCbCr colorspace. /// Methods to build the tables are based on libjpeg implementation. + /// TODO: Replace this logic with SIMD conversion (similar to the one in the decoder)! /// internal unsafe struct RgbToYCbCrTables { @@ -91,27 +94,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder } /// + /// TODO: Replace this logic with SIMD conversion (similar to the one in the decoder)! /// Optimized method to allocates the correct y, cb, and cr values to the DCT blocks from the given r, g, b values. /// - /// The The luminance block. - /// The red chroma block. - /// The blue chroma block. - /// The reference to the tables instance. - /// The current index. - /// The red value. - /// The green value. - /// The blue value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Allocate(ref float* yBlockRaw, ref float* cbBlockRaw, ref float* crBlockRaw, ref RgbToYCbCrTables* tables, int index, int r, int g, int b) + public void ConvertPixelInto(int r, int g, int b, ref float yResult, ref float cbResult, ref float crResult) { - // float y = (0.299F * r) + (0.587F * g) + (0.114F * b); - yBlockRaw[index] = (tables->YRTable[r] + tables->YGTable[g] + tables->YBTable[b]) >> ScaleBits; + ref int start = ref Unsafe.As(ref this); + + ref int yR = ref start; + ref int yG = ref Unsafe.Add(ref start, 256 * 1); + ref int yB = ref Unsafe.Add(ref start, 256 * 2); + + ref int cbR = ref Unsafe.Add(ref start, 256 * 3); + ref int cbG = ref Unsafe.Add(ref start, 256 * 4); + ref int cbB = ref Unsafe.Add(ref start, 256 * 5); - // float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); - cbBlockRaw[index] = (tables->CbRTable[r] + tables->CbGTable[g] + tables->CbBTable[b]) >> ScaleBits; + ref int crG = ref Unsafe.Add(ref start, 256 * 6); + ref int crB = ref Unsafe.Add(ref start, 256 * 7); - // float b = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero); - crBlockRaw[index] = (tables->CbBTable[r] + tables->CrGTable[g] + tables->CrBTable[b]) >> ScaleBits; + yResult = (Unsafe.Add(ref yR, r) + Unsafe.Add(ref yG, g) + Unsafe.Add(ref yB, b)) >> ScaleBits; + cbResult = (Unsafe.Add(ref cbR, r) + Unsafe.Add(ref cbG, g) + Unsafe.Add(ref cbB, b)) >> ScaleBits; + crResult = (Unsafe.Add(ref cbB, r) + Unsafe.Add(ref crG, g) + Unsafe.Add(ref crB, b)) >> ScaleBits; } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/YCbCrForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/YCbCrForwardConverter{TPixel}.cs new file mode 100644 index 000000000..3c95a8508 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/YCbCrForwardConverter{TPixel}.cs @@ -0,0 +1,82 @@ +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats.Jpeg.Common; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder +{ + /// + /// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks. + /// + /// The pixel type to work on + internal struct YCbCrForwardConverter + where TPixel : struct, IPixel + { + /// + /// The Y component + /// + public Block8x8F Y; + + /// + /// The Cb component + /// + public Block8x8F Cb; + + /// + /// The Cr component + /// + public Block8x8F Cr; + + /// + /// The color conversion tables + /// + private RgbToYCbCrTables colorTables; + + /// + /// Temporal 8x8 block to hold TPixel data + /// + private GenericBlock8x8 pixelBlock; + + /// + /// Temporal RGB block + /// + private GenericBlock8x8 rgbBlock; + + public static YCbCrForwardConverter Create() + { + var result = default(YCbCrForwardConverter); + result.colorTables = RgbToYCbCrTables.Create(); + return result; + } + + /// + /// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (, , ) + /// + public void Convert(IPixelSource pixels, int x, int y) + { + this.pixelBlock.LoadAndStretchEdges(pixels, x, y); + + Span rgbSpan = this.rgbBlock.AsSpanUnsafe(); + PixelOperations.Instance.ToRgb24(this.pixelBlock.AsSpanUnsafe(), rgbSpan, 64); + + ref float yBlockStart = ref Unsafe.As(ref this.Y); + ref float cbBlockStart = ref Unsafe.As(ref this.Cb); + ref float crBlockStart = ref Unsafe.As(ref this.Cr); + ref Rgb24 rgbStart = ref rgbSpan[0]; + + for (int i = 0; i < 64; i++) + { + ref Rgb24 c = ref Unsafe.Add(ref rgbStart, i); + + this.colorTables.ConvertPixelInto( + c.R, + c.G, + c.B, + ref Unsafe.Add(ref yBlockStart, i), + ref Unsafe.Add(ref cbBlockStart, i), + ref Unsafe.Add(ref crBlockStart, i)); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs index 2912a8719..ba40ef72b 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs @@ -7,7 +7,7 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Jpeg.Common; using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components; using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder; -using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.MetaData.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; @@ -231,10 +231,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort this.WriteDefineHuffmanTables(componentCount); // Write the image data. - using (PixelAccessor pixels = image.Lock()) - { - this.WriteStartOfScan(pixels); - } + this.WriteStartOfScan(image); // Write the End Of Image marker. this.buffer[0] = OrigJpegConstants.Markers.XFF; @@ -285,58 +282,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort } } - /// - /// Converts the 8x8 region of the image whose top-left corner is x,y to its YCbCr values. - /// - /// The pixel format. - /// The pixel accessor. - /// The reference to the tables instance. - /// The x-position within the image. - /// The y-position within the image. - /// The luminance block. - /// The red chroma block. - /// The blue chroma block. - /// Temporal provided by the caller - private static void ToYCbCr( - PixelAccessor pixels, - RgbToYCbCrTables* tables, - int x, - int y, - Block8x8F* yBlock, - Block8x8F* cbBlock, - Block8x8F* crBlock, - PixelArea rgbBytes) - where TPixel : struct, IPixel - { - float* yBlockRaw = (float*)yBlock; - float* cbBlockRaw = (float*)cbBlock; - float* crBlockRaw = (float*)crBlock; - - rgbBytes.Reset(); - pixels.CopyRGBBytesStretchedTo(rgbBytes, y, x); - - ref byte data0 = ref rgbBytes.Bytes[0]; - int dataIdx = 0; - - for (int j = 0; j < 8; j++) - { - int j8 = j * 8; - for (int i = 0; i < 8; i++) - { - // Convert returned bytes into the YCbCr color space. Assume RGBA - int r = Unsafe.Add(ref data0, dataIdx); - int g = Unsafe.Add(ref data0, dataIdx + 1); - int b = Unsafe.Add(ref data0, dataIdx + 2); - - int index = j8 + i; - - RgbToYCbCrTables.Allocate(ref yBlockRaw, ref cbBlockRaw, ref crBlockRaw, ref tables, index, r, g, b); - - dataIdx += 3; - } - } - } - /// /// Emits the least significant count of bits of bits to the bit-stream. /// The precondition is bits @@ -432,14 +377,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// /// The pixel format. /// The pixel accessor providing access to the image pixels. - private void Encode444(PixelAccessor pixels) + private void Encode444(Image pixels) where TPixel : struct, IPixel { // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) - Block8x8F b = default(Block8x8F); - Block8x8F cb = default(Block8x8F); - Block8x8F cr = default(Block8x8F); - + // (Partially done with YCbCrForwardConverter) Block8x8F temp1 = default(Block8x8F); Block8x8F temp2 = default(Block8x8F); @@ -451,42 +393,38 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort // ReSharper disable once InconsistentNaming int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; - fixed (RgbToYCbCrTables* tables = &rgbToYCbCrTables) + YCbCrForwardConverter pixelConverter = YCbCrForwardConverter.Create(); + + for (int y = 0; y < pixels.Height; y += 8) { - using (PixelArea rgbBytes = new PixelArea(8, 8, ComponentOrder.Xyz)) + for (int x = 0; x < pixels.Width; x += 8) { - for (int y = 0; y < pixels.Height; y += 8) - { - for (int x = 0; x < pixels.Width; x += 8) - { - ToYCbCr(pixels, tables, x, y, &b, &cb, &cr, rgbBytes); - - prevDCY = this.WriteBlock( - QuantIndex.Luminance, - prevDCY, - &b, - &temp1, - &temp2, - &onStackLuminanceQuantTable, - unzig.Data); - prevDCCb = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCb, - &cb, - &temp1, - &temp2, - &onStackChrominanceQuantTable, - unzig.Data); - prevDCCr = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCr, - &cr, - &temp1, - &temp2, - &onStackChrominanceQuantTable, - unzig.Data); - } - } + pixelConverter.Convert(pixels.Frames.RootFrame, x, y); + + prevDCY = this.WriteBlock( + QuantIndex.Luminance, + prevDCY, + &pixelConverter.Y, + &temp1, + &temp2, + &onStackLuminanceQuantTable, + unzig.Data); + prevDCCb = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCb, + &pixelConverter.Cb, + &temp1, + &temp2, + &onStackChrominanceQuantTable, + unzig.Data); + prevDCCr = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCr, + &pixelConverter.Cr, + &temp1, + &temp2, + &onStackChrominanceQuantTable, + unzig.Data); } } } @@ -657,14 +595,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort // Loop through and collect the tables as one array. // This allows us to reduce the number of writes to the stream. int dqtCount = (QuantizationTableCount * Block8x8F.Size) + QuantizationTableCount; - byte[] dqt = ArrayPool.Shared.Rent(dqtCount); + byte[] dqt = new byte[dqtCount]; int offset = 0; WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref this.luminanceQuantTable); WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref this.chrominanceQuantTable); this.outputStream.Write(dqt, 0, dqtCount); - ArrayPool.Shared.Return(dqt); } /// @@ -858,8 +795,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// Writes the StartOfScan marker. /// /// The pixel format. - /// The pixel accessor providing access to the image pixels. - private void WriteStartOfScan(PixelAccessor pixels) + /// The pixel accessor providing access to the image pixels. + private void WriteStartOfScan(Image image) where TPixel : struct, IPixel { // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) @@ -869,10 +806,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort switch (this.subsample) { case JpegSubsample.Ratio444: - this.Encode444(pixels); + this.Encode444(image); break; case JpegSubsample.Ratio420: - this.Encode420(pixels); + this.Encode420(image); break; } @@ -886,7 +823,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// /// The pixel format. /// The pixel accessor providing access to the image pixels. - private void Encode420(PixelAccessor pixels) + private void Encode420(Image pixels) where TPixel : struct, IPixel { // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) @@ -905,54 +842,54 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort ZigZag unzig = ZigZag.CreateUnzigTable(); + var pixelConverter = YCbCrForwardConverter.Create(); + // ReSharper disable once InconsistentNaming int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; - fixed (RgbToYCbCrTables* tables = &rgbToYCbCrTables) + + for (int y = 0; y < pixels.Height; y += 16) { - using (PixelArea rgbBytes = new PixelArea(8, 8, ComponentOrder.Xyz)) + for (int x = 0; x < pixels.Width; x += 16) { - for (int y = 0; y < pixels.Height; y += 16) + for (int i = 0; i < 4; i++) { - for (int x = 0; x < pixels.Width; x += 16) - { - for (int i = 0; i < 4; i++) - { - int xOff = (i & 1) * 8; - int yOff = (i & 2) * 4; - - ToYCbCr(pixels, tables, x + xOff, y + yOff, &b, cbPtr + i, crPtr + i, rgbBytes); - - prevDCY = this.WriteBlock( - QuantIndex.Luminance, - prevDCY, - &b, - &temp1, - &temp2, - &onStackLuminanceQuantTable, - unzig.Data); - } - - Block8x8F.Scale16X16To8X8(&b, cbPtr); - prevDCCb = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCb, - &b, - &temp1, - &temp2, - &onStackChrominanceQuantTable, - unzig.Data); - - Block8x8F.Scale16X16To8X8(&b, crPtr); - prevDCCr = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCr, - &b, - &temp1, - &temp2, - &onStackChrominanceQuantTable, - unzig.Data); - } + int xOff = (i & 1) * 8; + int yOff = (i & 2) * 4; + + pixelConverter.Convert(pixels.Frames.RootFrame, x + xOff, y + yOff); + + cbPtr[i] = pixelConverter.Cb; + crPtr[i] = pixelConverter.Cr; + + prevDCY = this.WriteBlock( + QuantIndex.Luminance, + prevDCY, + &pixelConverter.Y, + &temp1, + &temp2, + &onStackLuminanceQuantTable, + unzig.Data); } + + Block8x8F.Scale16X16To8X8(&b, cbPtr); + prevDCCb = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCb, + &b, + &temp1, + &temp2, + &onStackChrominanceQuantTable, + unzig.Data); + + Block8x8F.Scale16X16To8X8(&b, crPtr); + prevDCCr = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCr, + &b, + &temp1, + &temp2, + &onStackChrominanceQuantTable, + unzig.Data); } } } diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs index cdc2a9197..33d625725 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs @@ -211,11 +211,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// public void Dispose() { - for (int i = 0; i < this.HuffmanTrees.Length; i++) - { - this.HuffmanTrees[i].Dispose(); - } - if (this.Components != null) { foreach (OrigComponent component in this.Components) @@ -680,7 +675,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort foreach (OrigComponent component in this.Components) { - component.InitializeDerivedData(this); + component.InitializeDerivedData(this.configuration.MemoryManager, this); } this.ColorSpace = this.DeduceJpegColorSpace(); @@ -788,7 +783,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort private Image PostProcessIntoImage() where TPixel : struct, IPixel { - using (var postProcessor = new JpegImagePostProcessor(this)) + using (var postProcessor = new JpegImagePostProcessor(this.configuration.MemoryManager, this)) { var image = new Image(this.configuration, this.ImageWidth, this.ImageHeight, this.MetaData); postProcessor.PostProcess(image.Frames.RootFrame); diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/OrigJpegUtils.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/OrigJpegUtils.cs deleted file mode 100644 index 01ed5063b..000000000 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/OrigJpegUtils.cs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils -{ - /// - /// Jpeg specific utilities and extension methods - /// - internal static class OrigJpegUtils - { - /// - /// Copy a region of an image into dest. De "outlier" area will be stretched out with pixels on the right and bottom of the image. - /// - /// The pixel type - /// The input pixel acessor - /// The destination - /// Starting Y coord - /// Starting X coord - public static void CopyRGBBytesStretchedTo( - this PixelAccessor pixels, - PixelArea dest, - int sourceY, - int sourceX) - where TPixel : struct, IPixel - { - pixels.SafeCopyTo(dest, sourceY, sourceX); - int stretchFromX = pixels.Width - sourceX; - int stretchFromY = pixels.Height - sourceY; - StretchPixels(dest, stretchFromX, stretchFromY); - } - - // Nothing to stretch if (fromX, fromY) is outside the area, or is at (0,0) - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsInvalidStretchStartingPosition(PixelArea area, int fromX, int fromY) - where TPixel : struct, IPixel - { - return fromX <= 0 || fromY <= 0 || fromX >= area.Width || fromY >= area.Height; - } - - private static void StretchPixels(PixelArea area, int fromX, int fromY) - where TPixel : struct, IPixel - { - if (IsInvalidStretchStartingPosition(area, fromX, fromY)) - { - return; - } - - for (int y = 0; y < fromY; y++) - { - ref RGB24 ptrBase = ref GetRowStart(area, y); - - for (int x = fromX; x < area.Width; x++) - { - // Copy the left neighbour pixel to the current one - Unsafe.Add(ref ptrBase, x) = Unsafe.Add(ref ptrBase, x - 1); - } - } - - for (int y = fromY; y < area.Height; y++) - { - ref RGB24 currBase = ref GetRowStart(area, y); - ref RGB24 prevBase = ref GetRowStart(area, y - 1); - - for (int x = 0; x < area.Width; x++) - { - // Copy the top neighbour pixel to the current one - Unsafe.Add(ref currBase, x) = Unsafe.Add(ref prevBase, x); - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ref RGB24 GetRowStart(PixelArea area, int y) - where TPixel : struct, IPixel - { - return ref Unsafe.As(ref area.GetRowSpan(y).DangerousGetPinnableReference()); - } - - [StructLayout(LayoutKind.Sequential, Size = 3)] - private struct RGB24 - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsComponent.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsComponent.cs index 3c35e311f..0742293c7 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsComponent.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// /// Gets or sets the output /// - public Buffer Output; + public IBuffer Output; /// /// Gets or sets the scaling factors diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs index f60097dc9..2442c3998 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs @@ -15,10 +15,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// internal class PdfJsFrameComponent : IDisposable, IJpegComponent { + private readonly MemoryManager memoryManager; #pragma warning disable SA1401 // Fields should be private - public PdfJsFrameComponent(PdfJsFrame frame, byte id, int horizontalFactor, int verticalFactor, byte quantizationTableIndex, int index) + public PdfJsFrameComponent(MemoryManager memoryManager, PdfJsFrame frame, byte id, int horizontalFactor, int verticalFactor, byte quantizationTableIndex, int index) { + this.memoryManager = memoryManager; this.Frame = frame; this.Id = id; this.HorizontalSamplingFactor = horizontalFactor; @@ -58,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// /// Gets the block data /// - public Buffer BlockData { get; private set; } + public IBuffer BlockData { get; private set; } /// public int Index { get; } @@ -114,7 +116,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components int blocksBufferSize = 64 * this.BlocksPerColumnForMcu * (this.BlocksPerLineForMcu + 1); // Pooled. Disposed via frame disposal - this.BlockData = Buffer.CreateClean(blocksBufferSize); + this.BlockData = this.memoryManager.Allocate(blocksBufferSize, true); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs index 9dc831567..3c43ba244 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs @@ -12,32 +12,34 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// internal struct PdfJsHuffmanTable : IDisposable { - private Buffer lookahead; - private Buffer valOffset; - private Buffer maxcode; - private Buffer huffval; + private BasicArrayBuffer lookahead; + private BasicArrayBuffer valOffset; + private BasicArrayBuffer maxcode; + private IManagedByteBuffer huffval; /// /// Initializes a new instance of the struct. /// + /// The to use for buffer allocations. /// The code lengths /// The huffman values - public PdfJsHuffmanTable(byte[] lengths, byte[] values) + public PdfJsHuffmanTable(MemoryManager memoryManager, byte[] lengths, byte[] values) { - this.lookahead = Buffer.CreateClean(256); - this.valOffset = Buffer.CreateClean(18); - this.maxcode = Buffer.CreateClean(18); + // TODO: Replace FakeBuffer usages with standard or array orfixed-sized arrays + this.lookahead = memoryManager.AllocateFake(256); + this.valOffset = memoryManager.AllocateFake(18); + this.maxcode = memoryManager.AllocateFake(18); - using (var huffsize = Buffer.CreateClean(257)) - using (var huffcode = Buffer.CreateClean(257)) + using (IBuffer huffsize = memoryManager.Allocate(257)) + using (IBuffer huffcode = memoryManager.Allocate(257)) { - GenerateSizeTable(lengths, huffsize); - GenerateCodeTable(huffsize, huffcode); - GenerateDecoderTables(lengths, huffcode, this.valOffset, this.maxcode); - GenerateLookaheadTables(lengths, values, this.lookahead); + GenerateSizeTable(lengths, huffsize.Span); + GenerateCodeTable(huffsize.Span, huffcode.Span); + GenerateDecoderTables(lengths, huffcode.Span, this.valOffset.Span, this.maxcode.Span); + GenerateLookaheadTables(lengths, values, this.lookahead.Span); } - this.huffval = Buffer.CreateClean(values.Length); + this.huffval = memoryManager.AllocateManagedByteBuffer(values.Length, true); Buffer.BlockCopy(values, 0, this.huffval.Array, 0, values.Length); this.MaxCode = this.maxcode.Array; diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsIDCT.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsIDCT.cs index 49bdc2423..00fa1985d 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsIDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsIDCT.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsJpegPixelArea.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsJpegPixelArea.cs index 034986c2c..f16fb9a2c 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsJpegPixelArea.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsJpegPixelArea.cs @@ -14,22 +14,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// internal struct PdfJsJpegPixelArea : IDisposable { + private readonly MemoryManager memoryManager; + private readonly int imageWidth; private readonly int imageHeight; - private Buffer componentData; + private IBuffer componentData; private int rowStride; /// /// Initializes a new instance of the struct. /// + /// The to use for buffer allocations. /// The image width /// The image height /// The number of components - public PdfJsJpegPixelArea(int imageWidth, int imageHeight, int numberOfComponents) + public PdfJsJpegPixelArea(MemoryManager memoryManager, int imageWidth, int imageHeight, int numberOfComponents) { + this.memoryManager = memoryManager; this.imageWidth = imageWidth; this.imageHeight = imageHeight; this.Width = 0; @@ -69,19 +73,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components this.rowStride = width * numberOfComponents; var scale = new Vector2(this.imageWidth / (float)width, this.imageHeight / (float)height); - this.componentData = new Buffer(width * height * numberOfComponents); - Span componentDataSpan = this.componentData; + this.componentData = this.memoryManager.Allocate(width * height * numberOfComponents); + Span componentDataSpan = this.componentData.Span; const uint Mask3Lsb = 0xFFFFFFF8; // Used to clear the 3 LSBs - using (var xScaleBlockOffset = new Buffer(width)) + using (IBuffer xScaleBlockOffset = this.memoryManager.Allocate(width)) { - Span xScaleBlockOffsetSpan = xScaleBlockOffset; + Span xScaleBlockOffsetSpan = xScaleBlockOffset.Span; for (int i = 0; i < numberOfComponents; i++) { ref PdfJsComponent component = ref components.Components[i]; Vector2 componentScale = component.Scale * scale; int offset = i; - Span output = component.Output; + Span output = component.Output.Span; int blocksPerScanline = (component.BlocksPerLine + 1) << 3; // Precalculate the xScaleBlockOffset diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsQuantizationTables.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsQuantizationTables.cs index 1000ce82c..afe0b3007 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsQuantizationTables.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsQuantizationTables.cs @@ -12,6 +12,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// internal sealed class PdfJsQuantizationTables : IDisposable { + public PdfJsQuantizationTables(MemoryManager memoryManager) + { + this.Tables = memoryManager.Allocate2D(64, 4); + } + /// /// Gets the ZigZag scan table /// @@ -49,8 +54,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components get; set; } - = new Buffer2D(64, 4); - /// public void Dispose() { diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs index e2e5d985e..9e245ea2c 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs @@ -707,6 +707,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeBaseline(PdfJsFrameComponent component, int offset, ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, Stream stream) { + Span blockDataSpan = component.BlockData.Span; + int t = this.DecodeHuffman(ref dcHuffmanTable, stream); if (this.endOfStreamReached || this.unexpectedMarkerReached) { @@ -714,7 +716,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components } int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream); - component.BlockData[offset] = (short)(component.Pred += diff); + blockDataSpan[offset] = (short)(component.Pred += diff); int k = 1; while (k < 64) @@ -748,7 +750,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components byte z = PdfJsQuantizationTables.DctZigZag[k]; short re = (short)this.ReceiveAndExtend(s, stream); - component.BlockData[offset + z] = re; + blockDataSpan[offset + z] = re; k++; } } @@ -756,6 +758,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeDCFirst(PdfJsFrameComponent component, int offset, ref PdfJsHuffmanTable dcHuffmanTable, Stream stream) { + Span blockDataSpan = component.BlockData.Span; + int t = this.DecodeHuffman(ref dcHuffmanTable, stream); if (this.endOfStreamReached || this.unexpectedMarkerReached) { @@ -763,19 +767,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components } int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream) << this.successiveState; - component.BlockData[offset] = (short)(component.Pred += diff); + blockDataSpan[offset] = (short)(component.Pred += diff); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeDCSuccessive(PdfJsFrameComponent component, int offset, Stream stream) { + Span blockDataSpan = component.BlockData.Span; + int bit = this.ReadBit(stream); if (this.endOfStreamReached || this.unexpectedMarkerReached) { return; } - component.BlockData[offset] |= (short)(bit << this.successiveState); + blockDataSpan[offset] |= (short)(bit << this.successiveState); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs index 211c24d20..4fa0bc281 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs @@ -216,7 +216,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort ushort marker = this.ReadUint16(); fileMarker = new PdfJsFileMarker(marker, (int)this.InputStream.Position - 2); - this.quantizationTables = new PdfJsQuantizationTables(); + this.quantizationTables = new PdfJsQuantizationTables(this.configuration.MemoryManager); this.dcHuffmanTables = new PdfJsHuffmanTables(); this.acHuffmanTables = new PdfJsHuffmanTables(); @@ -335,7 +335,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort throw new ImageFormatException($"Unsupported color mode. Max components 4; found {this.NumberOfComponents}"); } - this.pixelArea = new PdfJsJpegPixelArea(image.Width, image.Height, this.NumberOfComponents); + this.pixelArea = new PdfJsJpegPixelArea(this.configuration.MemoryManager, image.Width, image.Height, this.NumberOfComponents); this.pixelArea.LinearizeBlockData(this.components, image.Width, image.Height); if (this.NumberOfComponents == 1) @@ -648,7 +648,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort maxV = v; } - var component = new PdfJsFrameComponent(this.Frame, this.temp[index], h, v, this.temp[index + 2], i); + var component = new PdfJsFrameComponent(this.configuration.MemoryManager, this.Frame, this.temp[index], h, v, this.temp[index + 2], i); this.Frame.Components[i] = component; this.Frame.ComponentIds[i] = component.Id; @@ -673,23 +673,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort throw new ImageFormatException($"DHT has wrong length: {remaining}"); } - using (var huffmanData = Buffer.CreateClean(256)) + using (IManagedByteBuffer huffmanData = this.configuration.MemoryManager.AllocateCleanManagedByteBuffer(256)) { + Span huffmanSpan = huffmanData.Span; for (int i = 2; i < remaining;) { byte huffmanTableSpec = (byte)this.InputStream.ReadByte(); this.InputStream.Read(huffmanData.Array, 0, 16); - using (var codeLengths = Buffer.CreateClean(17)) + using (IManagedByteBuffer codeLengths = this.configuration.MemoryManager.AllocateCleanManagedByteBuffer(17)) { + Span codeLengthsSpan = codeLengths.Span; int codeLengthSum = 0; for (int j = 1; j < 17; j++) { - codeLengthSum += codeLengths[j] = huffmanData[j - 1]; + codeLengthSum += codeLengthsSpan[j] = huffmanSpan[j - 1]; } - using (var huffmanValues = Buffer.CreateClean(256)) + using (IManagedByteBuffer huffmanValues = this.configuration.MemoryManager.AllocateCleanManagedByteBuffer(256)) { this.InputStream.Read(huffmanValues.Array, 0, codeLengthSum); @@ -784,18 +786,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort { int blocksPerLine = component.BlocksPerLine; int blocksPerColumn = component.BlocksPerColumn; - using (var computationBuffer = Buffer.CreateClean(64)) - using (var multiplicationBuffer = Buffer.CreateClean(64)) + using (IBuffer computationBuffer = this.configuration.MemoryManager.Allocate(64, true)) + using (IBuffer multiplicationBuffer = this.configuration.MemoryManager.Allocate(64, true)) { Span quantizationTable = this.quantizationTables.Tables.GetRowSpan(frameComponent.QuantizationTableIndex); - Span computationBufferSpan = computationBuffer; + Span computationBufferSpan = computationBuffer.Span; // For AA&N IDCT method, multiplier are equal to quantization // coefficients scaled by scalefactor[row]*scalefactor[col], where // scalefactor[0] = 1 // scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 // For integer operation, the multiplier table is to be scaled by 12. - Span multiplierSpan = multiplicationBuffer; + Span multiplierSpan = multiplicationBuffer.Span; // for (int i = 0; i < 64; i++) // { @@ -823,7 +825,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort /// The values private void BuildHuffmanTable(PdfJsHuffmanTables tables, int index, byte[] codeLengths, byte[] values) { - tables[index] = new PdfJsHuffmanTable(codeLengths, values); + tables[index] = new PdfJsHuffmanTable(this.configuration.MemoryManager, codeLengths, values); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/Formats/Png/PngChunk.cs b/src/ImageSharp/Formats/Png/PngChunk.cs index f90def5b3..2483a3ad9 100644 --- a/src/ImageSharp/Formats/Png/PngChunk.cs +++ b/src/ImageSharp/Formats/Png/PngChunk.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Memory; + namespace SixLabors.ImageSharp.Formats.Png { /// @@ -25,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// Gets or sets the data bytes appropriate to the chunk type, if any. /// This field can be of zero length. /// - public byte[] Data { get; set; } + public IManagedByteBuffer Data { get; set; } /// /// Gets or sets a CRC (Cyclic Redundancy Check) calculated on the preceding bytes in the chunk, diff --git a/src/ImageSharp/Formats/Png/PngConfigurationModule.cs b/src/ImageSharp/Formats/Png/PngConfigurationModule.cs index 9346f7567..ab6f31d49 100644 --- a/src/ImageSharp/Formats/Png/PngConfigurationModule.cs +++ b/src/ImageSharp/Formats/Png/PngConfigurationModule.cs @@ -9,11 +9,11 @@ namespace SixLabors.ImageSharp.Formats.Png public sealed class PngConfigurationModule : IConfigurationModule { /// - public void Configure(Configuration host) + public void Configure(Configuration config) { - host.SetEncoder(ImageFormats.Png, new PngEncoder()); - host.SetDecoder(ImageFormats.Png, new PngDecoder()); - host.AddImageFormatDetector(new PngImageFormatDetector()); + config.SetEncoder(ImageFormats.Png, new PngEncoder()); + config.SetDecoder(ImageFormats.Png, new PngDecoder()); + config.AddImageFormatDetector(new PngImageFormatDetector()); } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index e39187e08..fbff0ae1d 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -137,12 +137,12 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Previous scanline processed /// - private Buffer previousScanline; + private IManagedByteBuffer previousScanline; /// /// The current scanline that is being processed /// - private Buffer scanline; + private IManagedByteBuffer scanline; /// /// The index of the current scanline being processed @@ -191,6 +191,8 @@ namespace SixLabors.ImageSharp.Formats.Png this.ignoreMetadata = options.IgnoreMetadata; } + private MemoryManager MemoryManager => this.configuration.MemoryManager; + /// /// Decodes the stream to the image. /// @@ -222,11 +224,11 @@ namespace SixLabors.ImageSharp.Formats.Png switch (currentChunk.Type) { case PngChunkTypes.Header: - this.ReadHeaderChunk(currentChunk.Data); + this.ReadHeaderChunk(currentChunk.Data.Array); this.ValidateHeader(); break; case PngChunkTypes.Physical: - this.ReadPhysicalChunk(metadata, currentChunk.Data); + this.ReadPhysicalChunk(metadata, currentChunk.Data.Array); break; case PngChunkTypes.Data: if (image == null) @@ -240,17 +242,17 @@ namespace SixLabors.ImageSharp.Formats.Png break; case PngChunkTypes.Palette: byte[] pal = new byte[currentChunk.Length]; - Buffer.BlockCopy(currentChunk.Data, 0, pal, 0, currentChunk.Length); + Buffer.BlockCopy(currentChunk.Data.Array, 0, pal, 0, currentChunk.Length); this.palette = pal; break; case PngChunkTypes.PaletteAlpha: byte[] alpha = new byte[currentChunk.Length]; - Buffer.BlockCopy(currentChunk.Data, 0, alpha, 0, currentChunk.Length); + Buffer.BlockCopy(currentChunk.Data.Array, 0, alpha, 0, currentChunk.Length); this.paletteAlpha = alpha; this.AssignTransparentMarkers(alpha); break; case PngChunkTypes.Text: - this.ReadTextChunk(metadata, currentChunk.Data, currentChunk.Length); + this.ReadTextChunk(metadata, currentChunk.Data.Array, currentChunk.Length); break; case PngChunkTypes.End: this.isEndChunkReached = true; @@ -262,7 +264,8 @@ namespace SixLabors.ImageSharp.Formats.Png // Data is rented in ReadChunkData() if (currentChunk.Data != null) { - ArrayPool.Shared.Return(currentChunk.Data); + currentChunk.Data.Dispose(); + currentChunk.Data = null; } } } @@ -296,17 +299,17 @@ namespace SixLabors.ImageSharp.Formats.Png switch (currentChunk.Type) { case PngChunkTypes.Header: - this.ReadHeaderChunk(currentChunk.Data); + this.ReadHeaderChunk(currentChunk.Data.Array); this.ValidateHeader(); break; case PngChunkTypes.Physical: - this.ReadPhysicalChunk(metadata, currentChunk.Data); + this.ReadPhysicalChunk(metadata, currentChunk.Data.Array); break; case PngChunkTypes.Data: this.SkipChunkDataAndCrc(currentChunk); break; case PngChunkTypes.Text: - this.ReadTextChunk(metadata, currentChunk.Data, currentChunk.Length); + this.ReadTextChunk(metadata, currentChunk.Data.Array, currentChunk.Length); break; case PngChunkTypes.End: this.isEndChunkReached = true; @@ -318,7 +321,7 @@ namespace SixLabors.ImageSharp.Formats.Png // Data is rented in ReadChunkData() if (currentChunk.Data != null) { - ArrayPool.Shared.Return(currentChunk.Data); + ArrayPool.Shared.Return(currentChunk.Data.Array); } } } @@ -434,8 +437,8 @@ namespace SixLabors.ImageSharp.Formats.Png this.bytesPerSample = this.header.BitDepth / 8; } - this.previousScanline = Buffer.CreateClean(this.bytesPerScanline); - this.scanline = Buffer.CreateClean(this.bytesPerScanline); + this.previousScanline = this.MemoryManager.AllocateCleanManagedByteBuffer(this.bytesPerScanline); + this.scanline = this.configuration.MemoryManager.AllocateCleanManagedByteBuffer(this.bytesPerScanline); } /// @@ -555,7 +558,8 @@ namespace SixLabors.ImageSharp.Formats.Png } this.currentRowBytesRead = 0; - var filterType = (FilterType)this.scanline[0]; + Span scanlineSpan = this.scanline.Span; + var filterType = (FilterType)scanlineSpan[0]; switch (filterType) { @@ -564,22 +568,22 @@ namespace SixLabors.ImageSharp.Formats.Png case FilterType.Sub: - SubFilter.Decode(this.scanline, this.bytesPerPixel); + SubFilter.Decode(scanlineSpan, this.bytesPerPixel); break; case FilterType.Up: - UpFilter.Decode(this.scanline, this.previousScanline); + UpFilter.Decode(scanlineSpan, this.previousScanline.Span); break; case FilterType.Average: - AverageFilter.Decode(this.scanline, this.previousScanline, this.bytesPerPixel); + AverageFilter.Decode(scanlineSpan, this.previousScanline.Span, this.bytesPerPixel); break; case FilterType.Paeth: - PaethFilter.Decode(this.scanline, this.previousScanline, this.bytesPerPixel); + PaethFilter.Decode(scanlineSpan, this.previousScanline.Span, this.bytesPerPixel); break; default: @@ -750,11 +754,11 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.header.BitDepth == 16) { int length = this.header.Width * 3; - using (var compressed = new Buffer(length)) + using (IBuffer compressed = this.configuration.MemoryManager.Allocate(length)) { // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(scanlineBuffer, compressed, length); - PixelOperations.Instance.PackFromRgb24Bytes(compressed, rowSpan, this.header.Width); + this.From16BitTo8Bit(scanlineBuffer, compressed.Span, length); + PixelOperations.Instance.PackFromRgb24Bytes(compressed.Span, rowSpan, this.header.Width); } } else @@ -767,10 +771,10 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.header.BitDepth == 16) { int length = this.header.Width * 3; - using (var compressed = new Buffer(length)) + using (IBuffer compressed = this.configuration.MemoryManager.Allocate(length)) { // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(scanlineBuffer, compressed, length); + this.From16BitTo8Bit(scanlineBuffer, compressed.Span, length); Span rgb24Span = compressed.Span.NonPortableCast(); for (int x = 0; x < this.header.Width; x++) @@ -808,11 +812,11 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.header.BitDepth == 16) { int length = this.header.Width * 4; - using (var compressed = new Buffer(length)) + using (IBuffer compressed = this.configuration.MemoryManager.Allocate(length)) { // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(scanlineBuffer, compressed, length); - PixelOperations.Instance.PackFromRgba32Bytes(compressed, rowSpan, this.header.Width); + this.From16BitTo8Bit(scanlineBuffer, compressed.Span, length); + PixelOperations.Instance.PackFromRgba32Bytes(compressed.Span, rowSpan, this.header.Width); } } else @@ -1011,18 +1015,20 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.header.BitDepth == 16) { int length = this.header.Width * 3; - using (var compressed = new Buffer(length)) + using (IBuffer compressed = this.configuration.MemoryManager.Allocate(length)) { + Span compressedSpan = compressed.Span; + // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(scanlineBuffer, compressed, length); + this.From16BitTo8Bit(scanlineBuffer, compressedSpan, length); if (this.hasTrans) { for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 3) { - rgba.R = compressed[o]; - rgba.G = compressed[o + 1]; - rgba.B = compressed[o + 2]; + rgba.R = compressedSpan[o]; + rgba.G = compressedSpan[o + 1]; + rgba.B = compressedSpan[o + 2]; rgba.A = (byte)(this.rgb24Trans.Equals(rgba.Rgb) ? 0 : 255); color.PackFromRgba32(rgba); @@ -1033,9 +1039,9 @@ namespace SixLabors.ImageSharp.Formats.Png { for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 3) { - rgba.R = compressed[o]; - rgba.G = compressed[o + 1]; - rgba.B = compressed[o + 2]; + rgba.R = compressedSpan[o]; + rgba.G = compressedSpan[o + 1]; + rgba.B = compressedSpan[o + 2]; color.PackFromRgba32(rgba); rowSpan[x] = color; @@ -1079,16 +1085,18 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.header.BitDepth == 16) { int length = this.header.Width * 4; - using (var compressed = new Buffer(length)) + using (IBuffer compressed = this.configuration.MemoryManager.Allocate(length)) { + Span compressedSpan = compressed.Span; + // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(scanlineBuffer, compressed, length); + this.From16BitTo8Bit(scanlineBuffer, compressedSpan, length); for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 4) { - rgba.R = compressed[o]; - rgba.G = compressed[o + 1]; - rgba.B = compressed[o + 2]; - rgba.A = compressed[o + 3]; + rgba.R = compressedSpan[o]; + rgba.G = compressedSpan[o + 1]; + rgba.B = compressedSpan[o + 2]; + rgba.A = compressedSpan[o + 3]; color.PackFromRgba32(rgba); rowSpan[x] = color; @@ -1254,7 +1262,7 @@ namespace SixLabors.ImageSharp.Formats.Png this.crc.Reset(); this.crc.Update(this.chunkTypeBuffer); - this.crc.Update(chunk.Data, 0, chunk.Length); + this.crc.Update(chunk.Data.Array, 0, chunk.Length); if (this.crc.Value != chunk.Crc && IsCriticalChunk(chunk)) { @@ -1278,8 +1286,8 @@ namespace SixLabors.ImageSharp.Formats.Png private void ReadChunkData(PngChunk chunk) { // We rent the buffer here to return it afterwards in Decode() - chunk.Data = ArrayPool.Shared.Rent(chunk.Length); - this.currentStream.Read(chunk.Data, 0, chunk.Length); + chunk.Data = this.configuration.MemoryManager.AllocateCleanManagedByteBuffer(chunk.Length); + this.currentStream.Read(chunk.Data.Array, 0, chunk.Length); } /// @@ -1350,7 +1358,7 @@ namespace SixLabors.ImageSharp.Formats.Png private void SwapBuffers() { - Buffer temp = this.previousScanline; + IManagedByteBuffer temp = this.previousScanline; this.previousScanline = this.scanline; this.scanline = temp; } diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index 4abafc9e8..0c40ccf2a 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Quantizers; @@ -66,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Png public void Encode(Image image, Stream stream) where TPixel : struct, IPixel { - using (var encoder = new PngEncoderCore(this)) + using (var encoder = new PngEncoderCore(image.GetMemoryManager(), this)) { encoder.Encode(image, stream); } diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 385d40b6b..1ab7a83ce 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -19,6 +19,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// internal sealed class PngEncoderCore : IDisposable { + private readonly MemoryManager memoryManager; + /// /// The maximum block size, defaults at 64k for uncompressed blocks. /// @@ -72,37 +74,37 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The previous scanline. /// - private Buffer previousScanline; + private IManagedByteBuffer previousScanline; /// /// The raw scanline. /// - private Buffer rawScanline; + private IManagedByteBuffer rawScanline; /// /// The filtered scanline result. /// - private Buffer result; + private IManagedByteBuffer result; /// /// The buffer for the sub filter /// - private Buffer sub; + private IManagedByteBuffer sub; /// /// The buffer for the up filter /// - private Buffer up; + private IManagedByteBuffer up; /// /// The buffer for the average filter /// - private Buffer average; + private IManagedByteBuffer average; /// /// The buffer for the paeth filter /// - private Buffer paeth; + private IManagedByteBuffer paeth; /// /// The png color type. @@ -147,9 +149,11 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The options for influencing the encoder - public PngEncoderCore(IPngEncoderOptions options) + public PngEncoderCore(MemoryManager memoryManager, IPngEncoderOptions options) { + this.memoryManager = memoryManager; this.ignoreMetadata = options.IgnoreMetadata; this.paletteSize = options.PaletteSize > 0 ? options.PaletteSize.Clamp(1, int.MaxValue) : int.MaxValue; this.pngColorType = options.PngColorType; @@ -353,11 +357,11 @@ namespace SixLabors.ImageSharp.Formats.Png { if (this.bytesPerPixel == 4) { - PixelOperations.Instance.ToRgba32Bytes(rowSpan, this.rawScanline, this.width); + PixelOperations.Instance.ToRgba32Bytes(rowSpan, this.rawScanline.Span, this.width); } else { - PixelOperations.Instance.ToRgb24Bytes(rowSpan, this.rawScanline, this.width); + PixelOperations.Instance.ToRgb24Bytes(rowSpan, this.rawScanline.Span, this.width); } } @@ -369,13 +373,14 @@ namespace SixLabors.ImageSharp.Formats.Png /// The row span. /// The row. /// The - private Buffer EncodePixelRow(Span rowSpan, int row) + private IManagedByteBuffer EncodePixelRow(Span rowSpan, int row) where TPixel : struct, IPixel { switch (this.pngColorType) { case PngColorType.Palette: - Buffer.BlockCopy(this.palettePixelData, row * this.rawScanline.Length, this.rawScanline.Array, 0, this.rawScanline.Length); + // TODO: Use Span copy! + Buffer.BlockCopy(this.palettePixelData, row * this.rawScanline.Length(), this.rawScanline.Array, 0, this.rawScanline.Length()); break; case PngColorType.Grayscale: case PngColorType.GrayscaleWithAlpha: @@ -394,7 +399,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// to be most compressible, using lowest total variation as proxy for compressibility. /// /// The - private Buffer GetOptimalFilteredScanline() + private IManagedByteBuffer GetOptimalFilteredScanline() { Span scanSpan = this.rawScanline.Span; Span prevSpan = this.previousScanline.Span; @@ -402,18 +407,18 @@ namespace SixLabors.ImageSharp.Formats.Png // Palette images don't compress well with adaptive filtering. if (this.pngColorType == PngColorType.Palette || this.bitDepth < 8) { - NoneFilter.Encode(this.rawScanline, this.result); + NoneFilter.Encode(this.rawScanline.Span, this.result.Span); return this.result; } // This order, while different to the enumerated order is more likely to produce a smaller sum // early on which shaves a couple of milliseconds off the processing time. - UpFilter.Encode(scanSpan, prevSpan, this.up, out int currentSum); + UpFilter.Encode(scanSpan, prevSpan, this.up.Span, out int currentSum); int lowestSum = currentSum; - Buffer actualResult = this.up; + IManagedByteBuffer actualResult = this.up; - PaethFilter.Encode(scanSpan, prevSpan, this.paeth, this.bytesPerPixel, out currentSum); + PaethFilter.Encode(scanSpan, prevSpan, this.paeth.Span, this.bytesPerPixel, out currentSum); if (currentSum < lowestSum) { @@ -421,7 +426,7 @@ namespace SixLabors.ImageSharp.Formats.Png actualResult = this.paeth; } - SubFilter.Encode(scanSpan, this.sub, this.bytesPerPixel, out currentSum); + SubFilter.Encode(scanSpan, this.sub.Span, this.bytesPerPixel, out currentSum); if (currentSum < lowestSum) { @@ -429,7 +434,7 @@ namespace SixLabors.ImageSharp.Formats.Png actualResult = this.sub; } - AverageFilter.Encode(scanSpan, prevSpan, this.average, this.bytesPerPixel, out currentSum); + AverageFilter.Encode(scanSpan, prevSpan, this.average.Span, this.bytesPerPixel, out currentSum); if (currentSum < lowestSum) { @@ -516,12 +521,15 @@ namespace SixLabors.ImageSharp.Formats.Png // Get max colors for bit depth. int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3; - byte[] colorTable = ArrayPool.Shared.Rent(colorTableLength); - byte[] alphaTable = ArrayPool.Shared.Rent(pixelCount); var rgba = default(Rgba32); bool anyAlpha = false; - try + + using (IManagedByteBuffer colorTable = this.memoryManager.AllocateManagedByteBuffer(colorTableLength)) + using (IManagedByteBuffer alphaTable = this.memoryManager.AllocateManagedByteBuffer(pixelCount)) { + Span colorTableSpan = colorTable.Span; + Span alphaTableSpan = alphaTable.Span; + for (byte i = 0; i < pixelCount; i++) { if (quantized.Pixels.Contains(i)) @@ -531,9 +539,9 @@ namespace SixLabors.ImageSharp.Formats.Png byte alpha = rgba.A; - colorTable[offset] = rgba.R; - colorTable[offset + 1] = rgba.G; - colorTable[offset + 2] = rgba.B; + colorTableSpan[offset] = rgba.R; + colorTableSpan[offset + 1] = rgba.G; + colorTableSpan[offset + 2] = rgba.B; if (alpha > this.threshold) { @@ -541,23 +549,18 @@ namespace SixLabors.ImageSharp.Formats.Png } anyAlpha = anyAlpha || alpha < 255; - alphaTable[i] = alpha; + alphaTableSpan[i] = alpha; } } - this.WriteChunk(stream, PngChunkTypes.Palette, colorTable, 0, colorTableLength); + this.WriteChunk(stream, PngChunkTypes.Palette, colorTable.Array, 0, colorTableLength); // Write the transparency data if (anyAlpha) { - this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, alphaTable, 0, pixelCount); + this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, alphaTable.Array, 0, pixelCount); } } - finally - { - ArrayPool.Shared.Return(colorTable); - ArrayPool.Shared.Return(alphaTable); - } return quantized; } @@ -619,16 +622,16 @@ namespace SixLabors.ImageSharp.Formats.Png this.bytesPerScanline = this.width * this.bytesPerPixel; int resultLength = this.bytesPerScanline + 1; - this.previousScanline = Buffer.CreateClean(this.bytesPerScanline); - this.rawScanline = Buffer.CreateClean(this.bytesPerScanline); - this.result = Buffer.CreateClean(resultLength); + this.previousScanline = this.memoryManager.AllocateCleanManagedByteBuffer(this.bytesPerScanline); + this.rawScanline = this.memoryManager.AllocateCleanManagedByteBuffer(this.bytesPerScanline); + this.result = this.memoryManager.AllocateCleanManagedByteBuffer(resultLength); if (this.pngColorType != PngColorType.Palette) { - this.sub = Buffer.CreateClean(resultLength); - this.up = Buffer.CreateClean(resultLength); - this.average = Buffer.CreateClean(resultLength); - this.paeth = Buffer.CreateClean(resultLength); + this.sub = this.memoryManager.AllocateCleanManagedByteBuffer(resultLength); + this.up = this.memoryManager.AllocateCleanManagedByteBuffer(resultLength); + this.average = this.memoryManager.AllocateCleanManagedByteBuffer(resultLength); + this.paeth = this.memoryManager.AllocateCleanManagedByteBuffer(resultLength); } byte[] buffer; @@ -641,10 +644,10 @@ namespace SixLabors.ImageSharp.Formats.Png { for (int y = 0; y < this.height; y++) { - Buffer r = this.EncodePixelRow(pixels.GetPixelRowSpan(y), y); + IManagedByteBuffer r = this.EncodePixelRow(pixels.GetPixelRowSpan(y), y); deflateStream.Write(r.Array, 0, resultLength); - Buffer temp = this.rawScanline; + IManagedByteBuffer temp = this.rawScanline; this.rawScanline = this.previousScanline; this.previousScanline = temp; } diff --git a/src/ImageSharp/IImageProcessingContext{TPixel}.cs b/src/ImageSharp/IImageProcessingContext{TPixel}.cs index 552e8d579..b73337905 100644 --- a/src/ImageSharp/IImageProcessingContext{TPixel}.cs +++ b/src/ImageSharp/IImageProcessingContext{TPixel}.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.Primitives; @@ -14,6 +15,12 @@ namespace SixLabors.ImageSharp public interface IImageProcessingContext where TPixel : struct, IPixel { + /// + /// Gets a reference to the used to allocate buffers + /// for this context. + /// + MemoryManager MemoryManager { get; } + /// /// Adds the processor to the current set of image operations to be applied. /// diff --git a/src/ImageSharp/Image/Image.Decode.cs b/src/ImageSharp/Image/Image.Decode.cs index ef00ff15f..72492a494 100644 --- a/src/ImageSharp/Image/Image.Decode.cs +++ b/src/ImageSharp/Image/Image.Decode.cs @@ -29,12 +29,12 @@ namespace SixLabors.ImageSharp return null; } - using (var buffer = new Buffer(maxHeaderSize)) + using (IManagedByteBuffer buffer = config.MemoryManager.AllocateManagedByteBuffer(maxHeaderSize)) { long startPosition = stream.Position; stream.Read(buffer.Array, 0, maxHeaderSize); stream.Position = startPosition; - return config.FormatDetectors.Select(x => x.DetectFormat(buffer)).LastOrDefault(x => x != null); + return config.FormatDetectors.Select(x => x.DetectFormat(buffer.Span)).LastOrDefault(x => x != null); } } diff --git a/src/ImageSharp/Image/ImageFrame.LoadPixelData.cs b/src/ImageSharp/Image/ImageFrame.LoadPixelData.cs index e2230c436..b9341a1b2 100644 --- a/src/ImageSharp/Image/ImageFrame.LoadPixelData.cs +++ b/src/ImageSharp/Image/ImageFrame.LoadPixelData.cs @@ -20,30 +20,32 @@ namespace SixLabors.ImageSharp /// /// Create a new instance of the class from the given byte array in format. /// + /// The memory manager to use for allocations /// The byte array containing image data. /// The width of the final image. /// The height of the final image. /// The pixel format. /// A new . - public static ImageFrame LoadPixelData(Span data, int width, int height) + public static ImageFrame LoadPixelData(MemoryManager memoryManager, Span data, int width, int height) where TPixel : struct, IPixel - => LoadPixelData(data.NonPortableCast(), width, height); + => LoadPixelData(memoryManager, data.NonPortableCast(), width, height); /// /// Create a new instance of the class from the raw data. /// + /// The memory manager to use for allocations /// The Span containing the image Pixel data. /// The width of the final image. /// The height of the final image. /// The pixel format. /// A new . - public static ImageFrame LoadPixelData(Span data, int width, int height) + public static ImageFrame LoadPixelData(MemoryManager memoryManager, Span data, int width, int height) where TPixel : struct, IPixel { int count = width * height; Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); - var image = new ImageFrame(width, height); + var image = new ImageFrame(memoryManager, width, height); SpanHelper.Copy(data, image.GetPixelSpan(), count); return image; diff --git a/src/ImageSharp/Image/ImageFrameCollection.cs b/src/ImageSharp/Image/ImageFrameCollection.cs index 3e9bb0343..aefeacce1 100644 --- a/src/ImageSharp/Image/ImageFrameCollection.cs +++ b/src/ImageSharp/Image/ImageFrameCollection.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp this.parent = parent; // Frames are already cloned within the caller - this.frames.Add(new ImageFrame(width, height)); + this.frames.Add(new ImageFrame(parent.GetConfiguration().MemoryManager, width, height)); } internal ImageFrameCollection(Image parent, IEnumerable> frames) @@ -80,7 +80,11 @@ namespace SixLabors.ImageSharp /// public ImageFrame AddFrame(TPixel[] data) { - var frame = ImageFrame.LoadPixelData(new Span(data), this.RootFrame.Width, this.RootFrame.Height); + var frame = ImageFrame.LoadPixelData( + this.parent.GetMemoryManager(), + new Span(data), + this.RootFrame.Width, + this.RootFrame.Height); this.frames.Add(frame); return frame; } @@ -143,7 +147,7 @@ namespace SixLabors.ImageSharp /// public ImageFrame CreateFrame() { - var frame = new ImageFrame(this.RootFrame.Width, this.RootFrame.Height); + var frame = new ImageFrame(this.parent.GetConfiguration().MemoryManager, this.RootFrame.Width, this.RootFrame.Height); this.frames.Add(frame); return frame; } diff --git a/src/ImageSharp/Image/ImageFrame{TPixel}.cs b/src/ImageSharp/Image/ImageFrame{TPixel}.cs index ba475f9cf..833a22f7c 100644 --- a/src/ImageSharp/Image/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/Image/ImageFrame{TPixel}.cs @@ -7,6 +7,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Helpers; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.PixelFormats; @@ -21,72 +22,84 @@ namespace SixLabors.ImageSharp public sealed class ImageFrame : IPixelSource, IDisposable where TPixel : struct, IPixel { - /// - /// The image pixels. Not private as Buffer2D requires an array in its constructor. - /// - private Buffer2D pixelBuffer; - private bool isDisposed; /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The width of the image in pixels. /// The height of the image in pixels. - internal ImageFrame(int width, int height) - : this(width, height, new ImageFrameMetaData()) + internal ImageFrame(MemoryManager memoryManager, int width, int height) + : this(memoryManager, width, height, new ImageFrameMetaData()) { } /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The width of the image in pixels. /// The height of the image in pixels. /// The meta data. - internal ImageFrame(int width, int height, ImageFrameMetaData metaData) + internal ImageFrame(MemoryManager memoryManager, int width, int height, ImageFrameMetaData metaData) { + Guard.NotNull(memoryManager, nameof(memoryManager)); Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); Guard.NotNull(metaData, nameof(metaData)); - this.pixelBuffer = Buffer2D.CreateClean(width, height); + this.MemoryManager = memoryManager; + this.PixelBuffer = memoryManager.AllocateClean2D(width, height); this.MetaData = metaData; } /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The of the frame. /// The meta data. - internal ImageFrame(Size size, ImageFrameMetaData metaData) - : this(size.Width, size.Height, metaData) + internal ImageFrame(MemoryManager memoryManager, Size size, ImageFrameMetaData metaData) + : this(memoryManager, size.Width, size.Height, metaData) { } /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The source. - internal ImageFrame(ImageFrame source) + internal ImageFrame(MemoryManager memoryManager, ImageFrame source) { - this.pixelBuffer = new Buffer2D(source.pixelBuffer.Width, source.pixelBuffer.Height); - source.pixelBuffer.Span.CopyTo(this.pixelBuffer.Span); + this.MemoryManager = memoryManager; + this.PixelBuffer = memoryManager.Allocate2D(source.PixelBuffer.Width, source.PixelBuffer.Height); + source.PixelBuffer.Span.CopyTo(this.PixelBuffer.Span); this.MetaData = source.MetaData.Clone(); } + /// + /// Gets the to use for buffer allocations. + /// + public MemoryManager MemoryManager { get; } + + /// + /// Gets the image pixels. Not private as Buffer2D requires an array in its constructor. + /// + internal Buffer2D PixelBuffer { get; private set; } + /// - Buffer2D IPixelSource.PixelBuffer => this.pixelBuffer; + Buffer2D IPixelSource.PixelBuffer => this.PixelBuffer; /// /// Gets the width. /// - public int Width => this.pixelBuffer.Width; + public int Width => this.PixelBuffer.Width; /// /// Gets the height. /// - public int Height => this.pixelBuffer.Height; + public int Height => this.PixelBuffer.Height; /// /// Gets the meta data of the frame. @@ -104,13 +117,13 @@ namespace SixLabors.ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - return this.pixelBuffer[x, y]; + return this.PixelBuffer[x, y]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] set { - this.pixelBuffer[x, y] = value; + this.PixelBuffer[x, y] = value; } } @@ -123,7 +136,7 @@ namespace SixLabors.ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ref TPixel GetPixelReference(int x, int y) { - return ref this.pixelBuffer[x, y]; + return ref this.PixelBuffer[x, y]; } /// @@ -139,12 +152,26 @@ namespace SixLabors.ImageSharp } /// - /// Copies the pixels to another of the same size. + /// Copies the pixels to a of the same size. /// /// The target pixel buffer accessor. internal void CopyTo(PixelAccessor target) { - SpanHelper.Copy(this.GetPixelSpan(), target.PixelBuffer.Span); + this.CopyTo(target.PixelBuffer); + } + + /// + /// Copies the pixels to a of the same size. + /// + /// The target pixel buffer accessor. + internal void CopyTo(Buffer2D target) + { + if (this.Size() != target.Size()) + { + throw new ArgumentException("ImageFrame.CopyTo(): target must be of the same size!", nameof(target)); + } + + SpanHelper.Copy(this.GetPixelSpan(), target.Span); } /// @@ -156,8 +183,8 @@ namespace SixLabors.ImageSharp Guard.NotNull(pixelSource, nameof(pixelSource)); // Push my memory into the accessor (which in turn unpins the old buffer ready for the images use) - Buffer2D newPixels = pixelSource.SwapBufferOwnership(this.pixelBuffer); - this.pixelBuffer = newPixels; + Buffer2D newPixels = pixelSource.SwapBufferOwnership(this.PixelBuffer); + this.PixelBuffer = newPixels; } /// @@ -168,9 +195,9 @@ namespace SixLabors.ImageSharp { Guard.NotNull(pixelSource, nameof(pixelSource)); - Buffer2D temp = this.pixelBuffer; - this.pixelBuffer = pixelSource.pixelBuffer; - pixelSource.pixelBuffer = temp; + Buffer2D temp = this.PixelBuffer; + this.PixelBuffer = pixelSource.PixelBuffer; + pixelSource.PixelBuffer = temp; } /// @@ -183,8 +210,8 @@ namespace SixLabors.ImageSharp return; } - this.pixelBuffer?.Dispose(); - this.pixelBuffer = null; + this.PixelBuffer?.Dispose(); + this.PixelBuffer = null; // Note disposing is done. this.isDisposed = true; @@ -211,7 +238,7 @@ namespace SixLabors.ImageSharp Func scaleFunc = PackedPixelConverterHelper.ComputeScaleFunction(); - var target = new ImageFrame(this.Width, this.Height, this.MetaData.Clone()); + var target = new ImageFrame(this.MemoryManager, this.Width, this.Height, this.MetaData.Clone()); using (PixelAccessor pixels = this.Lock()) using (PixelAccessor targetPixels = target.Lock()) @@ -240,7 +267,7 @@ namespace SixLabors.ImageSharp /// The internal ImageFrame Clone() { - return new ImageFrame(this); + return new ImageFrame(this.MemoryManager, this); } /// diff --git a/src/ImageSharp/Image/PixelAccessor{TPixel}.cs b/src/ImageSharp/Image/PixelAccessor{TPixel}.cs index 8dcb1f760..63e4c015c 100644 --- a/src/ImageSharp/Image/PixelAccessor{TPixel}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TPixel}.cs @@ -17,25 +17,6 @@ namespace SixLabors.ImageSharp internal sealed class PixelAccessor : IDisposable, IBuffer2D where TPixel : struct, IPixel { -#pragma warning disable SA1401 // Fields must be private - /// - /// The containing the pixel data. - /// - internal Buffer2D PixelBuffer; - private bool ownedBuffer; -#pragma warning restore SA1401 // Fields must be private - - /// - /// A value indicating whether this instance of the given entity has been disposed. - /// - /// if this instance has been disposed; otherwise, . - /// - /// If the entity is disposed, it must not be disposed a second time. The isDisposed field is set the first time the entity - /// is disposed. If the isDisposed field is true, then the Dispose() method will not dispose again. This help not to prolong the entity's - /// life in the Garbage Collector. - /// - private bool isDisposed; - /// /// Initializes a new instance of the class. /// @@ -46,47 +27,13 @@ namespace SixLabors.ImageSharp Guard.MustBeGreaterThan(image.PixelBuffer.Width, 0, "image width"); Guard.MustBeGreaterThan(image.PixelBuffer.Height, 0, "image height"); - this.SetPixelBufferUnsafe(image.PixelBuffer, false); - } - - /// - /// Initializes a new instance of the class. - /// - /// The width of the image represented by the pixel buffer. - /// The height of the image represented by the pixel buffer. - public PixelAccessor(int width, int height) - : this(width, height, Buffer2D.CreateClean(width, height), true) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The width of the image represented by the pixel buffer. - /// The height of the image represented by the pixel buffer. - /// The pixel buffer. - /// if set to true [owned buffer]. - private PixelAccessor(int width, int height, Buffer2D pixels, bool ownedBuffer) - { - Guard.NotNull(pixels, nameof(pixels)); - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - - this.SetPixelBufferUnsafe(pixels, ownedBuffer); + this.SetPixelBufferUnsafe(image.PixelBuffer); } /// - /// Finalizes an instance of the class. + /// Gets the containing the pixel data. /// - ~PixelAccessor() - { - this.Dispose(); - } - - /// - /// Gets the pixel buffer array. - /// - public TPixel[] PixelArray => this.PixelBuffer.Array; + internal Buffer2D PixelBuffer { get; private set; } /// /// Gets the size of a single pixel in the number of bytes. @@ -105,7 +52,7 @@ namespace SixLabors.ImageSharp public int Height { get; private set; } /// - Span IBuffer2D.Span => this.PixelBuffer; + public Span Span => this.PixelBuffer.Span; private static PixelOperations Operations => PixelOperations.Instance; @@ -121,36 +68,21 @@ namespace SixLabors.ImageSharp get { this.CheckCoordinates(x, y); - return this.PixelArray[(y * this.Width) + x]; + return this.Span[(y * this.Width) + x]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] set { this.CheckCoordinates(x, y); - this.PixelArray[(y * this.Width) + x] = value; + Span span = this.Span; + span[(y * this.Width) + x] = value; } } /// public void Dispose() { - if (this.isDisposed || !this.ownedBuffer) - { - return; - } - - // Note disposing is done. - this.isDisposed = true; - - this.PixelBuffer.Dispose(); - - // This object will be cleaned up by the Dispose method. - // Therefore, you should call GC.SuppressFinalize to - // take this object off the finalization queue - // and prevent finalization code for this object - // from executing a second time. - GC.SuppressFinalize(this); } /// @@ -158,66 +90,7 @@ namespace SixLabors.ImageSharp /// public void Reset() { - this.PixelBuffer.Clear(); - } - - /// - /// Copy an area of pixels to the image. - /// - /// The area. - /// The target row index. - /// The target column index. - /// - /// Thrown when an unsupported component order value is passed. - /// - internal void CopyFrom(PixelArea area, int targetY, int targetX = 0) - { - this.CheckCoordinates(area, targetX, targetY); - - this.CopyFrom(area, targetX, targetY, area.Width, area.Height); - } - - /// - /// Copy pixels from the image to an area of pixels. - /// - /// The area. - /// The source row index. - /// The source column index. - /// - /// Thrown when an unsupported component order value is passed. - /// - internal void CopyTo(PixelArea area, int sourceY, int sourceX = 0) - { - this.CheckCoordinates(area, sourceX, sourceY); - - this.CopyTo(area, sourceX, sourceY, area.Width, area.Height); - } - - /// - /// Copy pixels from the image to an area of pixels. This method will make sure that the pixels - /// that are copied are within the bounds of the image. - /// - /// The area. - /// The source row index. - /// The source column index. - /// - /// Thrown when an unsupported component order value is passed. - /// - internal void SafeCopyTo(PixelArea area, int sourceY, int sourceX = 0) - { - int width = Math.Min(area.Width, this.Width - sourceX); - if (width < 1) - { - return; - } - - int height = Math.Min(area.Height, this.Height - sourceY); - if (height < 1) - { - return; - } - - this.CopyTo(area, sourceX, sourceY, width, height); + this.PixelBuffer.Buffer.Clear(); } /// @@ -229,7 +102,7 @@ namespace SixLabors.ImageSharp internal Buffer2D SwapBufferOwnership(Buffer2D pixels) { Buffer2D oldPixels = this.PixelBuffer; - this.SetPixelBufferUnsafe(pixels, this.ownedBuffer); + this.SetPixelBufferUnsafe(pixels); return oldPixels; } @@ -242,170 +115,13 @@ namespace SixLabors.ImageSharp SpanHelper.Copy(this.PixelBuffer.Span, target.PixelBuffer.Span); } - /// - /// Copies from an area in format. - /// - /// The area. - /// The target column index. - /// The target row index. - /// The width. - /// The height. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CopyFromZyx(PixelArea area, int targetX, int targetY, int width, int height) - { - for (int y = 0; y < height; y++) - { - Span source = area.GetRowSpan(y); - Span destination = this.GetRowSpan(targetX, targetY + y); - - Operations.PackFromBgr24Bytes(source, destination, width); - } - } - - /// - /// Copies from an area in format. - /// - /// The area. - /// The target column index. - /// The target row index. - /// The width. - /// The height. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CopyFromZyxw(PixelArea area, int targetX, int targetY, int width, int height) - { - for (int y = 0; y < height; y++) - { - Span source = area.GetRowSpan(y); - Span destination = this.GetRowSpan(targetX, targetY + y); - - Operations.PackFromBgra32Bytes(source, destination, width); - } - } - - /// - /// Copies from an area in format. - /// - /// The area. - /// The target column index. - /// The target row index. - /// The width. - /// The height. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CopyFromXyz(PixelArea area, int targetX, int targetY, int width, int height) - { - for (int y = 0; y < height; y++) - { - Span source = area.GetRowSpan(y); - Span destination = this.GetRowSpan(targetX, targetY + y); - - Operations.PackFromRgb24Bytes(source, destination, width); - } - } - - /// - /// Copies from an area in format. - /// - /// The area. - /// The target column index. - /// The target row index. - /// The width. - /// The height. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CopyFromXyzw(PixelArea area, int targetX, int targetY, int width, int height) - { - for (int y = 0; y < height; y++) - { - Span source = area.GetRowSpan(y); - Span destination = this.GetRowSpan(targetX, targetY + y); - Operations.PackFromRgba32Bytes(source, destination, width); - } - } - - /// - /// Copies to an area in format. - /// - /// The row. - /// The source column index. - /// The source row index. - /// The width. - /// The height. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CopyToZyx(PixelArea area, int sourceX, int sourceY, int width, int height) - { - for (int y = 0; y < height; y++) - { - Span source = this.GetRowSpan(sourceX, sourceY + y); - Span destination = area.GetRowSpan(y); - Operations.ToBgr24Bytes(source, destination, width); - } - } - - /// - /// Copies to an area in format. - /// - /// The row. - /// The source column index. - /// The source row index. - /// The width. - /// The height. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CopyToZyxw(PixelArea area, int sourceX, int sourceY, int width, int height) - { - for (int y = 0; y < height; y++) - { - Span source = this.GetRowSpan(sourceX, sourceY + y); - Span destination = area.GetRowSpan(y); - Operations.ToBgra32Bytes(source, destination, width); - } - } - - /// - /// Copies to an area in format. - /// - /// The row. - /// The source column index. - /// The source row index. - /// The width. - /// The height. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CopyToXyz(PixelArea area, int sourceX, int sourceY, int width, int height) - { - for (int y = 0; y < height; y++) - { - Span source = this.GetRowSpan(sourceX, sourceY + y); - Span destination = area.GetRowSpan(y); - Operations.ToRgb24Bytes(source, destination, width); - } - } - - /// - /// Copies to an area in format. - /// - /// The row. - /// The source column index. - /// The source row index. - /// The width. - /// The height. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CopyToXyzw(PixelArea area, int sourceX, int sourceY, int width, int height) - { - for (int y = 0; y < height; y++) - { - Span source = this.GetRowSpan(sourceX, sourceY + y); - Span destination = area.GetRowSpan(y); - Operations.ToRgba32Bytes(source, destination, width); - } - } - /// /// Sets the pixel buffer in an unsafe manor this should not be used unless you know what its doing!!! /// /// The pixel buffer - /// if set to true then this instance ownes the buffer and thus should dispose of it afterwards. - private void SetPixelBufferUnsafe(Buffer2D pixels, bool ownedBuffer) + private void SetPixelBufferUnsafe(Buffer2D pixels) { this.PixelBuffer = pixels; - this.ownedBuffer = ownedBuffer; this.Width = pixels.Width; this.Height = pixels.Height; @@ -413,95 +129,6 @@ namespace SixLabors.ImageSharp this.RowStride = this.Width * this.PixelSize; } - /// - /// Copy an area of pixels to the image. - /// - /// The area. - /// The target column index. - /// The target row index. - /// The width of the area to copy. - /// The height of the area to copy. - /// - /// Thrown when an unsupported component order value is passed. - /// - private void CopyFrom(PixelArea area, int targetX, int targetY, int width, int height) - { - switch (area.ComponentOrder) - { - case ComponentOrder.Zyx: - this.CopyFromZyx(area, targetX, targetY, width, height); - break; - case ComponentOrder.Zyxw: - this.CopyFromZyxw(area, targetX, targetY, width, height); - break; - case ComponentOrder.Xyz: - this.CopyFromXyz(area, targetX, targetY, width, height); - break; - case ComponentOrder.Xyzw: - this.CopyFromXyzw(area, targetX, targetY, width, height); - break; - default: - throw new NotSupportedException(); - } - } - - /// - /// Copy pixels from the image to an area of pixels. - /// - /// The area. - /// The source column index. - /// The source row index. - /// The width of the area to copy. - /// The height of the area to copy. - /// - /// Thrown when an unsupported component order value is passed. - /// - private void CopyTo(PixelArea area, int sourceX, int sourceY, int width, int height) - { - switch (area.ComponentOrder) - { - case ComponentOrder.Zyx: - this.CopyToZyx(area, sourceX, sourceY, width, height); - break; - case ComponentOrder.Zyxw: - this.CopyToZyxw(area, sourceX, sourceY, width, height); - break; - case ComponentOrder.Xyz: - this.CopyToXyz(area, sourceX, sourceY, width, height); - break; - case ComponentOrder.Xyzw: - this.CopyToXyzw(area, sourceX, sourceY, width, height); - break; - default: - throw new NotSupportedException(); - } - } - - /// - /// Checks that the given area and offset are within the bounds of the image. - /// - /// The area. - /// The x-coordinate of the pixel. Must be greater than zero and less than the width of the image. - /// The y-coordinate of the pixel. Must be greater than zero and less than the height of the image. - /// - /// Thrown if the dimensions are not within the bounds of the image. - /// - [Conditional("DEBUG")] - private void CheckCoordinates(PixelArea area, int x, int y) - { - int width = Math.Min(area.Width, this.Width - x); - if (width < 1) - { - throw new ArgumentOutOfRangeException(nameof(width), width, "Invalid area size specified."); - } - - int height = Math.Min(area.Height, this.Height - y); - if (height < 1) - { - throw new ArgumentOutOfRangeException(nameof(height), height, "Invalid area size specified."); - } - } - /// /// Checks the coordinates to ensure they are within bounds. /// diff --git a/src/ImageSharp/Image/PixelArea{TPixel}.cs b/src/ImageSharp/Image/PixelArea{TPixel}.cs deleted file mode 100644 index 1c7256455..000000000 --- a/src/ImageSharp/Image/PixelArea{TPixel}.cs +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Diagnostics; -using System.IO; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp -{ - /// - /// Represents an area of generic pixels. - /// - /// The pixel format. - internal sealed class PixelArea : IDisposable - where TPixel : struct, IPixel - { - /// - /// A value indicating whether this instance of the given entity has been disposed. - /// - /// if this instance has been disposed; otherwise, . - /// - /// If the entity is disposed, it must not be disposed a second time. The isDisposed field is set the first time the entity - /// is disposed. If the isDisposed field is true, then the Dispose() method will not dispose again. This help not to prolong the entity's - /// life in the Garbage Collector. - /// - private bool isDisposed; - - /// - /// The underlying buffer containing the raw pixel data. - /// - private readonly Buffer byteBuffer; - - /// - /// Initializes a new instance of the class. - /// - /// The width. - /// The bytes. - /// The component order. - /// - /// Thrown if is the incorrect length. - /// - public PixelArea(int width, byte[] bytes, ComponentOrder componentOrder) - : this(width, 1, bytes, componentOrder) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The width. - /// The height. - /// The bytes. - /// The component order. - /// - /// Thrown if is the incorrect length. - /// - public PixelArea(int width, int height, byte[] bytes, ComponentOrder componentOrder) - { - this.CheckBytesLength(width, height, bytes, componentOrder); - - this.Width = width; - this.Height = height; - this.ComponentOrder = componentOrder; - this.RowStride = width * GetComponentCount(componentOrder); - this.Length = bytes.Length; // TODO: Is this the right value for Length? - - this.byteBuffer = new Buffer(bytes); - } - - /// - /// Initializes a new instance of the class. - /// - /// The width. - /// The component order. - public PixelArea(int width, ComponentOrder componentOrder) - : this(width, 1, componentOrder, 0) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The width. - /// The component order. - /// The number of bytes to pad each row. - public PixelArea(int width, ComponentOrder componentOrder, int padding) - : this(width, 1, componentOrder, padding) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The width. - /// The height. - /// The component order. - public PixelArea(int width, int height, ComponentOrder componentOrder) - : this(width, height, componentOrder, 0) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The width. - /// The height. - /// The component order. - /// The number of bytes to pad each row. - public PixelArea(int width, int height, ComponentOrder componentOrder, int padding) - { - this.Width = width; - this.Height = height; - this.ComponentOrder = componentOrder; - this.RowStride = (width * GetComponentCount(componentOrder)) + padding; - this.Length = this.RowStride * height; - - this.byteBuffer = Buffer.CreateClean(this.Length); - } - - /// - /// Gets the data in bytes. - /// - public byte[] Bytes => this.byteBuffer.Array; - - /// - /// Gets the length of the buffer. - /// - public int Length { get; } - - /// - /// Gets the component order. - /// - public ComponentOrder ComponentOrder { get; } - - /// - /// Gets the height. - /// - public int Height { get; } - - /// - /// Gets the width of one row in the number of bytes. - /// - public int RowStride { get; } - - /// - /// Gets the width. - /// - public int Width { get; } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - if (this.isDisposed) - { - return; - } - - this.byteBuffer.Dispose(); - this.isDisposed = true; - } - - /// - /// Reads the stream to the area. - /// - /// The stream. - public void Read(Stream stream) - { - stream.Read(this.Bytes, 0, this.Length); - } - - /// - /// Writes the area to the stream. - /// - /// The stream. - public void Write(Stream stream) - { - stream.Write(this.Bytes, 0, this.Length); - } - - /// - /// Resets the bytes of the array to it's initial value. - /// - public void Reset() - { - this.byteBuffer.Clear(); - } - - /// - /// Gets a to the row y. - /// - /// The y coordinate - /// The - internal Span GetRowSpan(int y) - { - return this.byteBuffer.Slice(y * this.RowStride); - } - - /// - /// Gets component count for the given order. - /// - /// The component order. - /// - /// The . - /// - /// - /// Thrown if an invalid order is given. - /// - private static int GetComponentCount(ComponentOrder componentOrder) - { - switch (componentOrder) - { - case ComponentOrder.Zyx: - case ComponentOrder.Xyz: - return 3; - case ComponentOrder.Zyxw: - case ComponentOrder.Xyzw: - return 4; - } - - throw new NotSupportedException(); - } - - /// - /// Checks that the length of the byte array to ensure that it matches the given width and height. - /// - /// The width. - /// The height. - /// The byte array. - /// The component order. - /// - /// Thrown if the byte array is th incorrect length. - /// - [Conditional("DEBUG")] - private void CheckBytesLength(int width, int height, byte[] bytes, ComponentOrder componentOrder) - { - int requiredLength = (width * GetComponentCount(componentOrder)) * height; - if (bytes.Length != requiredLength) - { - throw new ArgumentOutOfRangeException( - nameof(bytes), - $"Invalid byte array length. Length {bytes.Length}; Should be {requiredLength}."); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 9d30030da..cb0539f78 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -61,6 +61,10 @@ TextTemplatingFileGenerator Block8x8F.Generated.cs + + TextTemplatingFileGenerator + GenericBlock8x8.Generated.cs + TextTemplatingFileGenerator Block8x8F.Generated.cs @@ -91,6 +95,11 @@ True Block8x8F.Generated.tt + + True + True + GenericBlock8x8.Generated.tt + True True @@ -117,4 +126,7 @@ PorterDuffFunctions.Generated.tt + + + \ No newline at end of file diff --git a/src/ImageSharp/Memory/ArrayPoolMemoryManager.Buffer{T}.cs b/src/ImageSharp/Memory/ArrayPoolMemoryManager.Buffer{T}.cs new file mode 100644 index 000000000..d4f58fb6f --- /dev/null +++ b/src/ImageSharp/Memory/ArrayPoolMemoryManager.Buffer{T}.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Contains and + /// + public partial class ArrayPoolMemoryManager + { + /// + /// The buffer implementation of + /// + private class Buffer : IBuffer + 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 Span Span => this.Data.AsSpan().NonPortableCast().Slice(0, this.length); + + /// + public void Dispose() + { + if (this.Data == null || this.sourcePoolReference == null) + { + return; + } + + if (this.sourcePoolReference.TryGetTarget(out ArrayPool pool)) + { + pool.Return(this.Data); + } + + this.sourcePoolReference = null; + this.Data = null; + } + } + + /// + /// The implementation of . + /// + private class ManagedByteBuffer : Buffer, IManagedByteBuffer + { + public ManagedByteBuffer(byte[] data, int length, ArrayPool sourcePool) + : base(data, length, sourcePool) + { + } + + public byte[] Array => this.Data; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/ArrayPoolMemoryManager.CommonFactoryMethods.cs b/src/ImageSharp/Memory/ArrayPoolMemoryManager.CommonFactoryMethods.cs new file mode 100644 index 000000000..d1424870d --- /dev/null +++ b/src/ImageSharp/Memory/ArrayPoolMemoryManager.CommonFactoryMethods.cs @@ -0,0 +1,69 @@ +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Contains common factory methods and configuration constants. + /// + public partial class ArrayPoolMemoryManager + { + /// + /// The default value for: maximum size of pooled arrays in bytes. + /// Currently set to 24MB, which is equivalent to 8 megapixels of raw 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; + + /// + /// This is the default. Should be good for most use cases. + /// + /// The memory manager + public static ArrayPoolMemoryManager CreateDefault() + { + return new ArrayPoolMemoryManager( + DefaultMaxPooledBufferSizeInBytes, + DefaultBufferSelectorThresholdInBytes, + DefaultLargePoolBucketCount, + DefaultNormalPoolBucketCount); + } + + /// + /// For environments with limited memory capabilities. Only small images are pooled, which can result in reduced througput. + /// + /// The memory manager + public static ArrayPoolMemoryManager CreateWithModeratePooling() + { + return new ArrayPoolMemoryManager(1024 * 1024, 32 * 1024, 16, 24); + } + + /// + /// Only pool small buffers like image rows. + /// + /// The memory manager + public static ArrayPoolMemoryManager CreateWithMinimalPooling() + { + return new ArrayPoolMemoryManager(64 * 1024, 32 * 1024, 8, 24); + } + + /// + /// RAM is not an issue for me, gimme maximum througput! + /// + /// The memory manager + public static ArrayPoolMemoryManager CreateWithAggressivePooling() + { + return new ArrayPoolMemoryManager(128 * 1024 * 1024, 32 * 1024 * 1024, 16, 32); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/ArrayPoolMemoryManager.cs b/src/ImageSharp/Memory/ArrayPoolMemoryManager.cs new file mode 100644 index 000000000..7b8c7ab32 --- /dev/null +++ b/src/ImageSharp/Memory/ArrayPoolMemoryManager.cs @@ -0,0 +1,140 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Buffers; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Implements by allocating memory from . + /// + public partial class ArrayPoolMemoryManager : MemoryManager + { + /// + /// 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; + + private readonly int maxArraysPerBucketNormalPool; + + private readonly int maxArraysPerBucketLargePool; + + /// + /// Initializes a new instance of the class. + /// + public ArrayPoolMemoryManager() + : 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 ArrayPoolMemoryManager(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 ArrayPoolMemoryManager(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 ArrayPoolMemoryManager(int maxPoolSizeInBytes, int poolSelectorThresholdInBytes, int maxArraysPerBucketLargePool, int maxArraysPerBucketNormalPool) + { + Guard.MustBeGreaterThan(maxPoolSizeInBytes, 0, nameof(maxPoolSizeInBytes)); + Guard.MustBeLessThanOrEqualTo(poolSelectorThresholdInBytes, maxPoolSizeInBytes, nameof(poolSelectorThresholdInBytes)); + + this.MaxPoolSizeInBytes = maxPoolSizeInBytes; + this.PoolSelectorThresholdInBytes = poolSelectorThresholdInBytes; + 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; } + + /// + public override void ReleaseRetainedResources() + { + this.InitArrayPools(); + } + + /// + internal override IBuffer Allocate(int length, bool clear) + { + 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 (clear) + { + buffer.Clear(); + } + + return buffer; + } + + /// + internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, bool clear) + { + ArrayPool pool = this.GetArrayPool(length); + byte[] byteArray = pool.Rent(length); + + var buffer = new ManagedByteBuffer(byteArray, length, pool); + if (clear) + { + buffer.Clear(); + } + + return buffer; + } + + private static int GetLargeBufferThresholdInBytes(int maxPoolSizeInBytes) + { + return maxPoolSizeInBytes / 4; + } + + 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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/BasicArrayBuffer.cs b/src/ImageSharp/Memory/BasicArrayBuffer.cs new file mode 100644 index 000000000..30ca210ac --- /dev/null +++ b/src/ImageSharp/Memory/BasicArrayBuffer.cs @@ -0,0 +1,51 @@ +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Exposes an array through the interface. + /// + internal class BasicArrayBuffer : IBuffer + where T : struct + { + public BasicArrayBuffer(T[] array, int length) + { + DebugGuard.MustBeLessThanOrEqualTo(length, array.Length, nameof(length)); + this.Array = array; + this.Length = length; + } + + public BasicArrayBuffer(T[] array) + : this(array, array.Length) + { + } + + public T[] Array { get; } + + public int Length { get; } + + public Span Span => this.Array.AsSpan().Slice(0, this.Length); + + /// + /// Returns a reference to specified element of the buffer. + /// + /// The index + /// The reference to the specified element + public ref T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + DebugGuard.MustBeLessThan(index, this.Length, nameof(index)); + + Span span = this.Span; + return ref span[index]; + } + } + + public void Dispose() + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/BasicByteBuffer.cs b/src/ImageSharp/Memory/BasicByteBuffer.cs new file mode 100644 index 000000000..96b69ad3b --- /dev/null +++ b/src/ImageSharp/Memory/BasicByteBuffer.cs @@ -0,0 +1,10 @@ +namespace SixLabors.ImageSharp.Memory +{ + internal class BasicByteBuffer : BasicArrayBuffer, IManagedByteBuffer + { + internal BasicByteBuffer(byte[] array) + : base(array) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index 99b10cae7..dc992368c 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; using SixLabors.Primitives; @@ -11,44 +12,37 @@ namespace SixLabors.ImageSharp.Memory /// interpreted as a 2D region of x elements. /// /// The value type. - internal class Buffer2D : Buffer, IBuffer2D + internal class Buffer2D : IBuffer2D, IDisposable where T : struct { - public Buffer2D(Size size) - : this(size.Width, size.Height) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The number of elements in a row - /// The number of rows - public Buffer2D(int width, int height) - : base(width * height) - { - this.Width = width; - this.Height = height; - } - /// /// Initializes a new instance of the class. /// - /// The array to pin + /// The buffer to wrap /// The number of elements in a row /// The number of rows - public Buffer2D(T[] array, int width, int height) - : base(array, width * height) + public Buffer2D(IBuffer wrappedBuffer, int width, int height) { + this.Buffer = wrappedBuffer; this.Width = width; this.Height = height; } /// - public int Width { get; } + public int Width { get; private set; } /// - public int Height { get; } + public int Height { get; private set; } + + /// + /// Gets the span to the whole area. + /// + public Span Span => this.Buffer.Span; + + /// + /// Gets the backing + /// + public IBuffer Buffer { get; private set; } /// /// Gets a reference to the element at the specified position. @@ -63,29 +57,39 @@ namespace SixLabors.ImageSharp.Memory { DebugGuard.MustBeLessThan(x, this.Width, nameof(x)); DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); - - return ref this.Array[(this.Width * y) + x]; + Span span = this.Buffer.Span; + return ref span[(this.Width * y) + x]; } } /// - /// Creates a clean instance of initializing it's elements with 'default(T)'. + /// Disposes the instance /// - /// The number of elements in a row - /// The number of rows - /// The instance - public static Buffer2D CreateClean(int width, int height) + public void Dispose() { - var buffer = new Buffer2D(width, height); - buffer.Clear(); - return buffer; + this.Buffer?.Dispose(); } /// - /// Creates a clean instance of initializing it's elements with 'default(T)'. + /// Swap the contents (, , ) of the two buffers. + /// Useful to transfer the contents of a temporary to a persistent /// - /// The size of the buffer - /// The instance - public static Buffer2D CreateClean(Size size) => CreateClean(size.Width, size.Height); + /// The first buffer + /// The second buffer + public static void SwapContents(Buffer2D a, Buffer2D b) + { + Size aSize = a.Size(); + Size bSize = b.Size(); + + IBuffer temp = a.Buffer; + a.Buffer = b.Buffer; + b.Buffer = temp; + + b.Width = aSize.Width; + b.Height = aSize.Height; + + a.Width = bSize.Width; + a.Height = bSize.Height; + } } } \ No newline at end of file diff --git a/src/ImageSharp/Memory/BufferArea{T}.cs b/src/ImageSharp/Memory/BufferArea{T}.cs index b5ed3566f..588eae483 100644 --- a/src/ImageSharp/Memory/BufferArea{T}.cs +++ b/src/ImageSharp/Memory/BufferArea{T}.cs @@ -45,11 +45,26 @@ namespace SixLabors.ImageSharp.Memory /// public Size Size => this.Rectangle.Size; + /// + /// Gets the width + /// + public int Width => this.Rectangle.Width; + + /// + /// Gets the height + /// + public int Height => this.Rectangle.Height; + /// /// Gets the pixel stride which is equal to the width of . /// public int Stride => this.DestinationBuffer.Width; + /// + /// Gets a value indicating whether the area refers to the entire + /// + public bool IsFullBufferArea => this.Size == this.DestinationBuffer.Size(); + /// /// Gets or sets a value at the given index. /// @@ -63,7 +78,7 @@ namespace SixLabors.ImageSharp.Memory /// /// The reference to the [0,0] element [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref T GetReferenceToOrigo() => + public ref T GetReferenceToOrigin() => ref this.DestinationBuffer.Span[(this.Rectangle.Y * this.DestinationBuffer.Width) + this.Rectangle.X]; /// @@ -122,9 +137,25 @@ namespace SixLabors.ImageSharp.Memory } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int GetRowIndex(int y) + internal int GetRowIndex(int y) { return (y + this.Rectangle.Y) * this.DestinationBuffer.Width; } + + public void Clear() + { + // Optimization for when the size of the area is the same as the buffer size. + if (this.IsFullBufferArea) + { + this.DestinationBuffer.Span.Clear(); + return; + } + + for (int y = 0; y < this.Rectangle.Height; y++) + { + Span row = this.GetRowSpan(y); + row.Clear(); + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/Memory/BufferExtensions.cs b/src/ImageSharp/Memory/BufferExtensions.cs new file mode 100644 index 000000000..919a6ef34 --- /dev/null +++ b/src/ImageSharp/Memory/BufferExtensions.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Memory +{ + internal static class BufferExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Length(this IBuffer buffer) + where T : struct => buffer.Span.Length; + + /// + /// Gets a to an offseted position inside the buffer. + /// + /// The buffer + /// The start + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span Slice(this IBuffer buffer, int start) + where T : struct + { + return buffer.Span.Slice(start); + } + + /// + /// Gets a to an offsetted position inside the buffer. + /// + /// The buffer + /// The start + /// The length of the slice + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span Slice(this IBuffer buffer, int start, int length) + where T : struct + { + return buffer.Span.Slice(start, length); + } + + /// + /// Clears the contents of this buffer. + /// + /// The buffer + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Clear(this IBuffer buffer) + where T : struct + { + buffer.Span.Clear(); + } + + public static ref T DangerousGetPinnableReference(this IBuffer buffer) + where T : struct => + ref buffer.Span.DangerousGetPinnableReference(); + + public static void Read(this Stream stream, IManagedByteBuffer buffer) + { + stream.Read(buffer.Array, 0, buffer.Length()); + } + + public static void Write(this Stream stream, IManagedByteBuffer buffer) + { + stream.Write(buffer.Array, 0, buffer.Length()); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/Buffer{T}.cs b/src/ImageSharp/Memory/Buffer{T}.cs deleted file mode 100644 index 67af23426..000000000 --- a/src/ImageSharp/Memory/Buffer{T}.cs +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Memory -{ - /// - /// - /// Manages a buffer of value type objects as a Disposable resource. - /// The backing array is either pooled or comes from the outside. - /// - /// The value type. - internal class Buffer : IBuffer - where T : struct - { - /// - /// A pointer to the first element of when pinned. - /// - private IntPtr pointer; - - /// - /// A handle that allows to access the managed as an unmanaged memory by pinning. - /// - private GCHandle handle; - - /// - /// A value indicating wheter should be returned to - /// when disposing this instance. - /// - private bool isPoolingOwner; - - /// - /// Initializes a new instance of the class. - /// - /// The desired count of elements. (Minimum size for ) - public Buffer(int length) - { - this.Length = length; - this.Array = PixelDataPool.Rent(length); - this.isPoolingOwner = true; - } - - /// - /// Initializes a new instance of the class. - /// - /// The array to pin. - public Buffer(T[] array) - { - this.Length = array.Length; - this.Array = array; - this.isPoolingOwner = false; - } - - /// - /// Initializes a new instance of the class. - /// - /// The array to pin. - /// The count of "relevant" elements in 'array'. - public Buffer(T[] array, int length) - { - if (array.Length < length) - { - throw new ArgumentException("Can't initialize a PinnedBuffer with array.Length < count", nameof(array)); - } - - this.Length = length; - this.Array = array; - this.isPoolingOwner = false; - } - - /// - /// Finalizes an instance of the class. - /// - ~Buffer() - { - this.UnPin(); - } - - /// - /// Gets a value indicating whether this instance is disposed, or has lost ownership of . - /// - public bool IsDisposedOrLostArrayOwnership { get; private set; } - - /// - /// Gets the count of "relevant" elements. It's usually smaller than 'Array.Length' when is pooled. - /// - public int Length { get; private set; } - - /// - /// Gets the backing pinned array. - /// - public T[] Array { get; private set; } - - /// - /// Gets a to the backing buffer. - /// - public Span Span => this; - - /// - /// Returns a reference to specified element of the buffer. - /// - /// The index - /// The reference to the specified element - public ref T this[int index] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - DebugGuard.MustBeLessThan(index, this.Length, nameof(index)); - return ref this.Array[index]; - } - } - - /// - /// Converts to an . - /// - /// The to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(Buffer buffer) - { - return new ReadOnlySpan(buffer.Array, 0, buffer.Length); - } - - /// - /// Converts to an . - /// - /// The to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator Span(Buffer buffer) - { - return new Span(buffer.Array, 0, buffer.Length); - } - - /// - /// Creates a clean instance of initializing it's elements with 'default(T)'. - /// - /// The desired count of elements. (Minimum size for ) - /// The instance - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Buffer CreateClean(int count) - { - var buffer = new Buffer(count); - buffer.Clear(); - return buffer; - } - - /// - /// Gets a to an offseted position inside the buffer. - /// - /// The start - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span Slice(int start) - { - return new Span(this.Array, start, this.Length - start); - } - - /// - /// Gets a to an offsetted position inside the buffer. - /// - /// The start - /// The length of the slice - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span Slice(int start, int length) - { - return new Span(this.Array, start, length); - } - - /// - /// Disposes the instance by unpinning the array, and returning the pooled buffer when necessary. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dispose() - { - if (this.IsDisposedOrLostArrayOwnership) - { - return; - } - - this.IsDisposedOrLostArrayOwnership = true; - this.UnPin(); - - if (this.isPoolingOwner) - { - PixelDataPool.Return(this.Array); - } - - this.isPoolingOwner = false; - this.Array = null; - this.Length = 0; - - GC.SuppressFinalize(this); - } - - /// - /// Unpins and makes the object "quasi-disposed" so the array is no longer owned by this object. - /// If is rented, it's the callers responsibility to return it to it's pool. (Most likely ) - /// - /// The unpinned - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public T[] TakeArrayOwnership() - { - if (this.IsDisposedOrLostArrayOwnership) - { - throw new InvalidOperationException( - "TakeArrayOwnership() is invalid: either Buffer is disposed or TakeArrayOwnership() has been called multiple times!"); - } - - this.IsDisposedOrLostArrayOwnership = true; - this.UnPin(); - T[] array = this.Array; - this.Array = null; - this.isPoolingOwner = false; - return array; - } - - /// - /// Clears the contents of this buffer. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear() - { - this.Span.Clear(); - } - - /// - /// Pins . - /// - /// The pinned pointer - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public IntPtr Pin() - { - if (this.IsDisposedOrLostArrayOwnership) - { - throw new InvalidOperationException( - "Pin() is invalid on a buffer with IsDisposedOrLostArrayOwnership == true!"); - } - - if (this.pointer == IntPtr.Zero) - { - this.handle = GCHandle.Alloc(this.Array, GCHandleType.Pinned); - this.pointer = this.handle.AddrOfPinnedObject(); - } - - return this.pointer; - } - - /// - /// Unpins . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void UnPin() - { - if (this.pointer == IntPtr.Zero || !this.handle.IsAllocated) - { - return; - } - - this.handle.Free(); - this.pointer = IntPtr.Zero; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Memory/IBuffer{T}.cs b/src/ImageSharp/Memory/IBuffer{T}.cs index a0f80063f..db6bf5b38 100644 --- a/src/ImageSharp/Memory/IBuffer{T}.cs +++ b/src/ImageSharp/Memory/IBuffer{T}.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; namespace SixLabors.ImageSharp.Memory diff --git a/src/ImageSharp/Memory/IManagedByteBuffer.cs b/src/ImageSharp/Memory/IManagedByteBuffer.cs new file mode 100644 index 000000000..4d159ce86 --- /dev/null +++ b/src/ImageSharp/Memory/IManagedByteBuffer.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Represents a byte buffer backed by a managed array. Useful for interop with classic .NET API-s. + /// + internal interface IManagedByteBuffer : IBuffer + { + /// + /// Gets the managed array backing this buffer instance. + /// + byte[] Array { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/MemoryManager.cs b/src/ImageSharp/Memory/MemoryManager.cs new file mode 100644 index 000000000..52bdc897f --- /dev/null +++ b/src/ImageSharp/Memory/MemoryManager.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Memory managers are used to allocate memory for image processing operations. + /// + public abstract class MemoryManager + { + /// + /// Allocates an of size , optionally + /// clearing the buffer before it gets returned. + /// + /// Type of the data stored in the buffer + /// Size of the buffer to allocate + /// True to clear the backing memory of the buffer + /// A buffer of values of type . + internal abstract IBuffer Allocate(int length, bool clear) + where T : struct; + + /// + /// Allocates an + /// + /// The requested buffer length + /// A value indicating whether to clean the buffer + /// The + internal abstract IManagedByteBuffer AllocateManagedByteBuffer(int length, bool clear); + + /// + /// Temporal workaround. A method providing a "Buffer" based on a generic array without the 'Unsafe.As()' hackery. + /// Should be replaced with 'Allocate()' as soon as SixLabors.Shapes has Span-based API-s! + /// + internal BasicArrayBuffer AllocateFake(int length, bool dummy = false) + where T : struct + { + return new BasicArrayBuffer(new T[length]); + } + + /// + /// Releases all retained resources not being in use. + /// Eg: by resetting array pools and letting GC to free the arrays. + /// + public virtual void ReleaseRetainedResources() + { + } + } +} diff --git a/src/ImageSharp/Memory/MemoryManagerExtensions.cs b/src/ImageSharp/Memory/MemoryManagerExtensions.cs new file mode 100644 index 000000000..206000245 --- /dev/null +++ b/src/ImageSharp/Memory/MemoryManagerExtensions.cs @@ -0,0 +1,79 @@ +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Extension methods for . + /// + internal static class MemoryManagerExtensions + { + /// + /// Allocates a of size . + /// Note: Depending on the implementation, the buffer may not cleared before + /// returning, so it may contain data from an earlier use. + /// + /// Type of the data stored in the buffer + /// The + /// Size of the buffer to allocate + /// A buffer of values of type . + public static IBuffer Allocate(this MemoryManager memoryManager, int length) + where T : struct + { + return memoryManager.Allocate(length, false); + } + + public static IBuffer AllocateClean(this MemoryManager memoryManager, int length) + where T : struct + { + return memoryManager.Allocate(length, true); + } + + public static IManagedByteBuffer AllocateManagedByteBuffer(this MemoryManager memoryManager, int length) + { + return memoryManager.AllocateManagedByteBuffer(length, false); + } + + public static IManagedByteBuffer AllocateCleanManagedByteBuffer(this MemoryManager memoryManager, int length) + { + return memoryManager.AllocateManagedByteBuffer(length, true); + } + + public static Buffer2D Allocate2D(this MemoryManager memoryManager, int width, int height, bool clear) + where T : struct + { + IBuffer buffer = memoryManager.Allocate(width * height, clear); + + return new Buffer2D(buffer, width, height); + } + + public static Buffer2D Allocate2D(this MemoryManager memoryManager, Size size) + where T : struct => + Allocate2D(memoryManager, size.Width, size.Height, false); + + public static Buffer2D Allocate2D(this MemoryManager memoryManager, int width, int height) + where T : struct => + Allocate2D(memoryManager, width, height, false); + + public static Buffer2D AllocateClean2D(this MemoryManager memoryManager, int width, int height) + where T : struct => + Allocate2D(memoryManager, width, height, true); + + /// + /// Allocates padded buffers for BMP encoder/decoder. (Replacing old PixelRow/PixelArea) + /// + /// The + /// Pixel count in the row + /// The pixel size in bytes, eg. 3 for RGB + /// The padding + /// A + public static IManagedByteBuffer AllocatePaddedPixelRowBuffer( + this MemoryManager memoryManager, + int width, + int pixelSizeInBytes, + int paddingInBytes) + { + int length = (width * pixelSizeInBytes) + paddingInBytes; + return memoryManager.AllocateManagedByteBuffer(length); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/PixelDataPool{T}.cs b/src/ImageSharp/Memory/PixelDataPool{T}.cs deleted file mode 100644 index 80c9c410e..000000000 --- a/src/ImageSharp/Memory/PixelDataPool{T}.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Buffers; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Memory -{ - /// - /// Provides a resource pool that enables reusing instances of value type arrays for image data . - /// - /// The value type. - internal class PixelDataPool - where T : struct - { - /// - /// The maximum size of pooled arrays in bytes. - /// Currently set to 32MB, which is equivalent to 8 megapixels of raw data. - /// - internal const int MaxPooledBufferSizeInBytes = 32 * 1024 * 1024; - - /// - /// The threshold to pool arrays in which has less buckets for memory safety. - /// - private const int LargeBufferThresholdInBytes = 8 * 1024 * 1024; - - /// - /// The maximum array length of the . - /// - private static readonly int MaxLargeArrayLength = MaxPooledBufferSizeInBytes / Unsafe.SizeOf(); - - /// - /// The maximum array length of the . - /// - private static readonly int MaxNormalArrayLength = LargeBufferThresholdInBytes / Unsafe.SizeOf(); - - /// - /// The for huge buffers, which is not kept clean. - /// - private static readonly ArrayPool LargeArrayPool = ArrayPool.Create(MaxLargeArrayLength, 8); - - /// - /// The for small-to-medium buffers which is not kept clean. - /// - private static readonly ArrayPool NormalArrayPool = ArrayPool.Create(MaxNormalArrayLength, 24); - - /// - /// Rents the pixel array from the pool. - /// - /// The minimum length of the array to return. - /// The - public static T[] Rent(int minimumLength) - { - if (minimumLength <= MaxNormalArrayLength) - { - return NormalArrayPool.Rent(minimumLength); - } - else - { - return LargeArrayPool.Rent(minimumLength); - } - } - - /// - /// Returns the rented pixel array back to the pool. - /// - /// The array to return to the buffer pool. - public static void Return(T[] array) - { - if (array.Length <= MaxNormalArrayLength) - { - NormalArrayPool.Return(array); - } - else - { - LargeArrayPool.Return(array); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Memory/SimpleGcMemoryManager.cs b/src/ImageSharp/Memory/SimpleGcMemoryManager.cs new file mode 100644 index 000000000..f4518bbb9 --- /dev/null +++ b/src/ImageSharp/Memory/SimpleGcMemoryManager.cs @@ -0,0 +1,19 @@ +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Implements by newing up arrays by the GC on every allocation requests. + /// + public class SimpleGcMemoryManager : MemoryManager + { + /// + internal override IBuffer Allocate(int length, bool clear) + { + return new BasicArrayBuffer(new T[length]); + } + + internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, bool clear) + { + return new BasicByteBuffer(new byte[length]); + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs index 99a20516d..d3c6cf16c 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs @@ -26,7 +26,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders internal class Normal : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -39,13 +38,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -63,9 +62,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Multiply : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -78,13 +77,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -102,9 +101,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Add : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -117,13 +116,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -141,9 +140,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Substract : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -156,13 +155,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -180,9 +179,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Screen : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -195,13 +194,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -219,9 +218,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Darken : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -234,13 +233,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -258,9 +257,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Lighten : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -273,13 +272,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -297,9 +296,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Overlay : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -312,13 +311,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -336,9 +335,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class HardLight : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -351,13 +350,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -375,9 +374,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Src : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -390,13 +389,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -414,9 +413,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Atop : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -429,13 +428,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -453,9 +452,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Over : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -468,13 +467,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -492,9 +491,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class In : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -507,13 +506,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -531,9 +530,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Out : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -546,13 +545,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -570,9 +569,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Dest : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -585,13 +584,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -609,9 +608,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class DestAtop : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -624,13 +623,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -648,9 +647,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class DestOver : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -663,13 +662,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -687,9 +686,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class DestIn : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -702,13 +701,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -726,9 +725,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class DestOut : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -741,13 +740,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -765,9 +764,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Clear : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -780,13 +779,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -804,9 +803,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Xor : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -819,13 +818,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -843,5 +842,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + } } \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt index 9d7d73db9..eebee676f 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt @@ -68,7 +68,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders #> internal class <#=blender#> : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -81,13 +80,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -105,6 +104,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + <# } diff --git a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs index 54cb09c28..666fb3891 100644 --- a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.PixelFormats { @@ -27,6 +28,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// Blend 2 pixels together. /// + /// The /// The destination span. /// The background span. /// The source span. @@ -34,6 +36,6 @@ namespace SixLabors.ImageSharp.PixelFormats /// A value between 0 and 1 indicating the weight of the second source vector. /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. /// - public abstract void Blend(Span destination, Span background, Span source, Span amount); + public abstract void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount); } } diff --git a/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs b/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs index 947e53157..ca79841c7 100644 --- a/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs +++ b/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp public static IImageProcessingContext Lomograph(this IImageProcessingContext source, GraphicsOptions options) where TPixel : struct, IPixel { - source.ApplyProcessor(new LomographProcessor(options)); + source.ApplyProcessor(new LomographProcessor(source.MemoryManager, options)); return source; } @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp public static IImageProcessingContext Lomograph(this IImageProcessingContext source, Rectangle rectangle, GraphicsOptions options) where TPixel : struct, IPixel { - source.ApplyProcessor(new LomographProcessor(options), rectangle); + source.ApplyProcessor(new LomographProcessor(source.MemoryManager, options), rectangle); return source; } } diff --git a/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs b/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs index c96087d57..d12cc7612 100644 --- a/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs +++ b/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp public static IImageProcessingContext Polaroid(this IImageProcessingContext source, GraphicsOptions options) where TPixel : struct, IPixel { - source.ApplyProcessor(new PolaroidProcessor(options)); + source.ApplyProcessor(new PolaroidProcessor(source.MemoryManager, options)); return source; } @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp public static IImageProcessingContext Polaroid(this IImageProcessingContext source, Rectangle rectangle, GraphicsOptions options) where TPixel : struct, IPixel { - source.ApplyProcessor(new PolaroidProcessor(options), rectangle); + source.ApplyProcessor(new PolaroidProcessor(source.MemoryManager, options), rectangle); return source; } } diff --git a/src/ImageSharp/Processing/Effects/BackgroundColor.cs b/src/ImageSharp/Processing/Effects/BackgroundColor.cs index da00b4ddd..22aad9ca6 100644 --- a/src/ImageSharp/Processing/Effects/BackgroundColor.cs +++ b/src/ImageSharp/Processing/Effects/BackgroundColor.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp /// The . public static IImageProcessingContext BackgroundColor(this IImageProcessingContext source, TPixel color, GraphicsOptions options) where TPixel : struct, IPixel - => source.ApplyProcessor(new BackgroundColorProcessor(color, options)); + => source.ApplyProcessor(new BackgroundColorProcessor(source.MemoryManager, color, options)); /// /// Replaces the background color of image with the given one. @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp /// The . public static IImageProcessingContext BackgroundColor(this IImageProcessingContext source, TPixel color, Rectangle rectangle, GraphicsOptions options) where TPixel : struct, IPixel - => source.ApplyProcessor(new BackgroundColorProcessor(color, options), rectangle); + => source.ApplyProcessor(new BackgroundColorProcessor(source.MemoryManager, color, options), rectangle); /// /// Replaces the background color of image with the given one. diff --git a/src/ImageSharp/Processing/Overlays/Glow.cs b/src/ImageSharp/Processing/Overlays/Glow.cs index ee3596348..0c3552b4d 100644 --- a/src/ImageSharp/Processing/Overlays/Glow.cs +++ b/src/ImageSharp/Processing/Overlays/Glow.cs @@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp /// The . private static IImageProcessingContext Glow(this IImageProcessingContext source, TPixel color, ValueSize radius, Rectangle rectangle, GraphicsOptions options) where TPixel : struct, IPixel - => source.ApplyProcessor(new GlowProcessor(color, radius, options), rectangle); + => source.ApplyProcessor(new GlowProcessor(source.MemoryManager, color, radius, options), rectangle); /// /// Applies a radial glow effect to an image. @@ -170,6 +170,6 @@ namespace SixLabors.ImageSharp /// The . private static IImageProcessingContext Glow(this IImageProcessingContext source, TPixel color, ValueSize radius, GraphicsOptions options) where TPixel : struct, IPixel - => source.ApplyProcessor(new GlowProcessor(color, radius, options)); + => source.ApplyProcessor(new GlowProcessor(source.MemoryManager, color, radius, options)); } } diff --git a/src/ImageSharp/Processing/Overlays/Vignette.cs b/src/ImageSharp/Processing/Overlays/Vignette.cs index cc93ccedc..4b9f2f866 100644 --- a/src/ImageSharp/Processing/Overlays/Vignette.cs +++ b/src/ImageSharp/Processing/Overlays/Vignette.cs @@ -151,10 +151,10 @@ namespace SixLabors.ImageSharp private static IImageProcessingContext VignetteInternal(this IImageProcessingContext source, TPixel color, ValueSize radiusX, ValueSize radiusY, Rectangle rectangle, GraphicsOptions options) where TPixel : struct, IPixel - => source.ApplyProcessor(new VignetteProcessor(color, radiusX, radiusY, options), rectangle); + => source.ApplyProcessor(new VignetteProcessor(source.MemoryManager, color, radiusX, radiusY, options), rectangle); private static IImageProcessingContext VignetteInternal(this IImageProcessingContext source, TPixel color, ValueSize radiusX, ValueSize radiusY, GraphicsOptions options) where TPixel : struct, IPixel - => source.ApplyProcessor(new VignetteProcessor(color, radiusX, radiusY, options)); + => source.ApplyProcessor(new VignetteProcessor(source.MemoryManager, color, radiusX, radiusY, options)); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs index cf9b7be9c..2c13ead16 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Processing.Processors int maxY = endY - 1; int maxX = endX - 1; - using (var targetPixels = new PixelAccessor(source.Width, source.Height)) + using (Buffer2D targetPixels = configuration.MemoryManager.Allocate2D(source.Width, source.Height)) { source.CopyTo(targetPixels); @@ -122,7 +122,7 @@ namespace SixLabors.ImageSharp.Processing.Processors } }); - source.SwapPixelsBuffers(targetPixels); + Buffer2D.SwapContents(source.PixelBuffer, targetPixels); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs index 57f434ee0..0d87aa1dc 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -43,15 +43,12 @@ namespace SixLabors.ImageSharp.Processing.Processors /// protected override void OnApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { - int width = source.Width; - int height = source.Height; ParallelOptions parallelOptions = configuration.ParallelOptions; - using (var firstPassPixels = new PixelAccessor(width, height)) - using (PixelAccessor sourcePixels = source.Lock()) + using (Buffer2D firstPassPixels = configuration.MemoryManager.Allocate2D(source.Size())) { - this.ApplyConvolution(firstPassPixels, sourcePixels, source.Bounds(), this.KernelX, parallelOptions); - this.ApplyConvolution(sourcePixels, firstPassPixels, sourceRectangle, this.KernelY, parallelOptions); + this.ApplyConvolution(firstPassPixels, source.PixelBuffer, source.Bounds(), this.KernelX, parallelOptions); + this.ApplyConvolution(source.PixelBuffer, firstPassPixels, sourceRectangle, this.KernelY, parallelOptions); } } @@ -67,8 +64,8 @@ namespace SixLabors.ImageSharp.Processing.Processors /// The kernel operator. /// The parellel options private void ApplyConvolution( - PixelAccessor targetPixels, - PixelAccessor sourcePixels, + Buffer2D targetPixels, + Buffer2D sourcePixels, Rectangle sourceRectangle, Fast2DArray kernel, ParallelOptions parallelOptions) diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs index 96db9a4ce..2f369113d 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs @@ -5,6 +5,7 @@ using System; using System.Numerics; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Helpers; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; @@ -45,7 +46,7 @@ namespace SixLabors.ImageSharp.Processing.Processors int maxY = endY - 1; int maxX = endX - 1; - using (var targetPixels = new PixelAccessor(source.Width, source.Height)) + using (Buffer2D targetPixels = configuration.MemoryManager.Allocate2D(source.Size())) { source.CopyTo(targetPixels); @@ -94,7 +95,7 @@ namespace SixLabors.ImageSharp.Processing.Processors } }); - source.SwapPixelsBuffers(targetPixels); + Buffer2D.SwapContents(source.PixelBuffer, targetPixels); } } } diff --git a/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs index 72e9b8f55..720b87691 100644 --- a/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs @@ -17,16 +17,20 @@ namespace SixLabors.ImageSharp.Processing.Processors internal class BackgroundColorProcessor : ImageProcessor where TPixel : struct, IPixel { + private readonly MemoryManager memoryManager; + private readonly GraphicsOptions options; /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The to set the background color to. /// The options defining blending algorithum and amount. - public BackgroundColorProcessor(TPixel color, GraphicsOptions options) + public BackgroundColorProcessor(MemoryManager memoryManager, TPixel color, GraphicsOptions options) { this.Value = color; + this.memoryManager = memoryManager; this.options = options; } @@ -67,13 +71,17 @@ namespace SixLabors.ImageSharp.Processing.Processors int width = maxX - minX; - using (var colors = new Buffer(width)) - using (var amount = new Buffer(width)) + using (IBuffer colors = this.memoryManager.Allocate(width)) + using (IBuffer amount = this.memoryManager.Allocate(width)) { + // Be careful! Do not capture colorSpan & amountSpan in the lambda below! + Span colorSpan = colors.Span; + Span amountSpan = amount.Span; + for (int i = 0; i < width; i++) { - colors[i] = this.Value; - amount[i] = this.options.BlendPercentage; + colorSpan[i] = this.Value; + amountSpan[i] = this.options.BlendPercentage; } PixelBlender blender = PixelOperations.Instance.GetPixelBlender(this.options.BlenderMode); @@ -86,7 +94,7 @@ namespace SixLabors.ImageSharp.Processing.Processors Span destination = source.GetPixelRowSpan(y - startY).Slice(minX - startX, width); // This switched color & destination in the 2nd and 3rd places because we are applying the target colour under the current one - blender.Blend(destination, colors, destination, amount); + blender.Blend(this.memoryManager, destination, colors.Span, destination, amount.Span); }); } } diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs index b22a49798..c199a32c8 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs @@ -5,6 +5,7 @@ using System; using System.Numerics; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Helpers; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; @@ -65,7 +66,7 @@ namespace SixLabors.ImageSharp.Processing.Processors int radius = this.BrushSize >> 1; int levels = this.Levels; - using (var targetPixels = new PixelAccessor(source.Width, source.Height)) + using (Buffer2D targetPixels = configuration.MemoryManager.Allocate2D(source.Size())) { source.CopyTo(targetPixels); @@ -133,7 +134,7 @@ namespace SixLabors.ImageSharp.Processing.Processors } }); - source.SwapPixelsBuffers(targetPixels); + Buffer2D.SwapContents(source.PixelBuffer, targetPixels); } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs index 5ea57fd27..33c4338d5 100644 --- a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System.Numerics; + +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; @@ -15,22 +17,27 @@ namespace SixLabors.ImageSharp.Processing.Processors where TPixel : struct, IPixel { private static readonly TPixel VeryDarkGreen = ColorBuilder.FromRGBA(0, 10, 0, 255); + + private readonly MemoryManager memoryManager; + private readonly GraphicsOptions options; /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The options effecting blending and composition. - public LomographProcessor(GraphicsOptions options) + public LomographProcessor(MemoryManager memoryManager, GraphicsOptions options) : base(MatrixFilters.LomographFilter) { + this.memoryManager = memoryManager; this.options = options; } /// protected override void AfterApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { - new VignetteProcessor(VeryDarkGreen, this.options).Apply(source, sourceRectangle, configuration); + new VignetteProcessor(this.memoryManager, VeryDarkGreen, this.options).Apply(source, sourceRectangle, configuration); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs index 5491db7ef..152d586af 100644 --- a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; @@ -15,23 +16,28 @@ namespace SixLabors.ImageSharp.Processing.Processors { private static readonly TPixel VeryDarkOrange = ColorBuilder.FromRGB(102, 34, 0); private static readonly TPixel LightOrange = ColorBuilder.FromRGBA(255, 153, 102, 128); + + private readonly MemoryManager memoryManager; + private readonly GraphicsOptions options; /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The options effecting blending and composition. - public PolaroidProcessor(GraphicsOptions options) + public PolaroidProcessor(MemoryManager memoryManager, GraphicsOptions options) : base(MatrixFilters.PolaroidFilter) { + this.memoryManager = memoryManager; this.options = options; } /// protected override void AfterApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { - new VignetteProcessor(VeryDarkOrange, this.options).Apply(source, sourceRectangle, configuration); - new GlowProcessor(LightOrange, source.Width / 4F, this.options).Apply(source, sourceRectangle, configuration); + new VignetteProcessor(this.memoryManager, VeryDarkOrange, this.options).Apply(source, sourceRectangle, configuration); + new GlowProcessor(this.memoryManager, LightOrange, source.Width / 4F, this.options).Apply(source, sourceRectangle, configuration); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs index b02585d8f..9ab301718 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs @@ -19,17 +19,21 @@ namespace SixLabors.ImageSharp.Processing.Processors internal class GlowProcessor : ImageProcessor where TPixel : struct, IPixel { + private readonly MemoryManager memoryManager; + private readonly GraphicsOptions options; private readonly PixelBlender blender; /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The color or the glow. /// The radius of the glow. /// The options effecting blending and composition. - public GlowProcessor(TPixel color, ValueSize radius, GraphicsOptions options) + public GlowProcessor(MemoryManager memoryManager, TPixel color, ValueSize radius, GraphicsOptions options) { + this.memoryManager = memoryManager; this.options = options; this.GlowColor = color; this.Radius = radius; @@ -61,7 +65,7 @@ namespace SixLabors.ImageSharp.Processing.Processors TPixel glowColor = this.GlowColor; Vector2 centre = Rectangle.Center(sourceRectangle); - var finalRadius = this.Radius.Calculate(source.Size()); + float finalRadius = this.Radius.Calculate(source.Size()); float maxDistance = finalRadius > 0 ? MathF.Min(finalRadius, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; @@ -83,11 +87,14 @@ namespace SixLabors.ImageSharp.Processing.Processors } int width = maxX - minX; - using (var rowColors = new Buffer(width)) + using (IBuffer rowColors = this.memoryManager.Allocate(width)) { + // Be careful! Do not capture rowColorsSpan in the lambda below! + Span rowColorsSpan = rowColors.Span; + for (int i = 0; i < width; i++) { - rowColors[i] = glowColor; + rowColorsSpan[i] = glowColor; } Parallel.For( @@ -96,19 +103,20 @@ namespace SixLabors.ImageSharp.Processing.Processors configuration.ParallelOptions, y => { - using (var amounts = new Buffer(width)) + using (IBuffer amounts = this.memoryManager.Allocate(width)) { + Span amountsSpan = amounts.Span; int offsetY = y - startY; int offsetX = minX - startX; for (int i = 0; i < width; i++) { float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); - amounts[i] = (this.options.BlendPercentage * (1 - (.95F * (distance / maxDistance)))).Clamp(0, 1); + amountsSpan[i] = (this.options.BlendPercentage * (1 - (.95F * (distance / maxDistance)))).Clamp(0, 1); } Span destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width); - this.blender.Blend(destination, destination, rowColors, amounts); + this.blender.Blend(this.memoryManager, destination, destination, rowColors.Span, amountsSpan); } }); } diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs index 7b592a6a4..d47211f0c 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs @@ -19,21 +19,25 @@ namespace SixLabors.ImageSharp.Processing.Processors internal class VignetteProcessor : ImageProcessor where TPixel : struct, IPixel { + private readonly MemoryManager memoryManager; + private readonly GraphicsOptions options; private readonly PixelBlender blender; /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The color of the vignette. /// The x-radius. /// The y-radius. /// The options effecting blending and composition. - public VignetteProcessor(TPixel color, ValueSize radiusX, ValueSize radiusY, GraphicsOptions options) + public VignetteProcessor(MemoryManager memoryManager, TPixel color, ValueSize radiusX, ValueSize radiusY, GraphicsOptions options) { this.VignetteColor = color; this.RadiusX = radiusX; this.RadiusY = radiusY; + this.memoryManager = memoryManager; this.options = options; this.blender = PixelOperations.Instance.GetPixelBlender(this.options.BlenderMode); } @@ -41,11 +45,13 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The color of the vignette. /// The options effecting blending and composition. - public VignetteProcessor(TPixel color, GraphicsOptions options) + public VignetteProcessor(MemoryManager memoryManager, TPixel color, GraphicsOptions options) { this.VignetteColor = color; + this.memoryManager = memoryManager; this.options = options; this.blender = PixelOperations.Instance.GetPixelBlender(this.options.BlenderMode); } @@ -80,8 +86,8 @@ namespace SixLabors.ImageSharp.Processing.Processors TPixel vignetteColor = this.VignetteColor; Vector2 centre = Rectangle.Center(sourceRectangle); - var finalradiusX = this.RadiusX.Calculate(source.Size()); - var finalradiusY = this.RadiusY.Calculate(source.Size()); + float finalradiusX = this.RadiusX.Calculate(source.Size()); + float finalradiusY = this.RadiusY.Calculate(source.Size()); float rX = finalradiusX > 0 ? MathF.Min(finalradiusX, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; float rY = finalradiusY > 0 ? MathF.Min(finalradiusY, sourceRectangle.Height * .5F) : sourceRectangle.Height * .5F; float maxDistance = MathF.Sqrt((rX * rX) + (rY * rY)); @@ -104,11 +110,14 @@ namespace SixLabors.ImageSharp.Processing.Processors } int width = maxX - minX; - using (var rowColors = new Buffer(width)) + using (IBuffer rowColors = this.memoryManager.Allocate(width)) { + // Be careful! Do not capture rowColorsSpan in the lambda below! + Span rowColorsSpan = rowColors.Span; + for (int i = 0; i < width; i++) { - rowColors[i] = vignetteColor; + rowColorsSpan[i] = vignetteColor; } Parallel.For( @@ -117,19 +126,20 @@ namespace SixLabors.ImageSharp.Processing.Processors configuration.ParallelOptions, y => { - using (var amounts = new Buffer(width)) + using (IBuffer amounts = this.memoryManager.Allocate(width)) { + Span amountsSpan = amounts.Span; int offsetY = y - startY; int offsetX = minX - startX; for (int i = 0; i < width; i++) { float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); - amounts[i] = (this.options.BlendPercentage * (.9F * (distance / maxDistance))).Clamp(0, 1); + amountsSpan[i] = (this.options.BlendPercentage * (.9F * (distance / maxDistance))).Clamp(0, 1); } Span destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width); - this.blender.Blend(destination, destination, rowColors, amounts); + this.blender.Blend(this.memoryManager, destination, destination, rowColors.Span, amountsSpan); } }); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs index 5ea23726e..287ce9d41 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors // We will always be creating the clone even for mutate because we may need to resize the canvas IEnumerable> frames = - source.Frames.Select(x => new ImageFrame(this.targetDimensions, x.MetaData.Clone())); + source.Frames.Select(x => new ImageFrame(source.GetMemoryManager(), this.targetDimensions, x.MetaData.Clone())); // Use the overload to prevent an extra frame being added return new Image(source.GetConfiguration(), source.MetaData.Clone(), frames); @@ -131,8 +131,10 @@ namespace SixLabors.ImageSharp.Processing.Processors int xLength = (int)MathF.Ceiling((radius.X * 2) + 2); int yLength = (int)MathF.Ceiling((radius.Y * 2) + 2); - using (var yBuffer = new Buffer2D(yLength, height)) - using (var xBuffer = new Buffer2D(xLength, height)) + MemoryManager memoryManager = configuration.MemoryManager; + + using (Buffer2D yBuffer = memoryManager.Allocate2D(yLength, height)) + using (Buffer2D xBuffer = memoryManager.Allocate2D(xLength, height)) { Parallel.For( 0, diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs index 00547d014..37c76bdcf 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Processing.Processors int minX = Math.Max(this.CropRectangle.X, sourceRectangle.X); int maxX = Math.Min(this.CropRectangle.Right, sourceRectangle.Right); - using (var targetPixels = new PixelAccessor(this.CropRectangle.Width, this.CropRectangle.Height)) + using (Buffer2D targetPixels = configuration.MemoryManager.Allocate2D(this.CropRectangle.Size)) { Parallel.For( minY, @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Processing.Processors SpanHelper.Copy(sourceRow, targetRow, maxX - minX); }); - source.SwapPixelsBuffers(targetPixels); + Buffer2D.SwapContents(source.PixelBuffer, targetPixels); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs index de60177f2..9b8b785b5 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs @@ -4,6 +4,7 @@ using System; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Helpers; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; @@ -54,11 +55,10 @@ namespace SixLabors.ImageSharp.Processing.Processors /// The configuration. private void FlipX(ImageFrame source, Configuration configuration) { - int width = source.Width; int height = source.Height; int halfHeight = (int)Math.Ceiling(source.Height * .5F); - using (var targetPixels = new PixelAccessor(width, height)) + using (Buffer2D targetPixels = configuration.MemoryManager.Allocate2D(source.Size())) { Parallel.For( 0, @@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Processing.Processors altSourceRow.CopyTo(targetRow); }); - source.SwapPixelsBuffers(targetPixels); + Buffer2D.SwapContents(source.PixelBuffer, targetPixels); } } @@ -92,7 +92,7 @@ namespace SixLabors.ImageSharp.Processing.Processors int height = source.Height; int halfWidth = (int)Math.Ceiling(width * .5F); - using (var targetPixels = new PixelAccessor(width, height)) + using (Buffer2D targetPixels = configuration.MemoryManager.Allocate2D(source.Size())) { Parallel.For( 0, @@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Processing.Processors } }); - source.SwapPixelsBuffers(targetPixels); + Buffer2D.SwapContents(source.PixelBuffer, targetPixels); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs index 90e33bddf..458871cdc 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs @@ -71,8 +71,11 @@ namespace SixLabors.ImageSharp.Processing.Processors } // We will always be creating the clone even for mutate because we may need to resize the canvas - IEnumerable> frames = - source.Frames.Select(x => new ImageFrame(this.targetDimensions.Width, this.targetDimensions.Height, x.MetaData.Clone())); + IEnumerable> frames = source.Frames.Select( + x => new ImageFrame( + source.GetMemoryManager(), + this.targetDimensions, + x.MetaData.Clone())); // Use the overload to prevent an extra frame being added return new Image(source.GetConfiguration(), source.MetaData.Clone(), frames); @@ -128,8 +131,10 @@ namespace SixLabors.ImageSharp.Processing.Processors int xLength = (int)MathF.Ceiling((radius.X * 2) + 2); int yLength = (int)MathF.Ceiling((radius.Y * 2) + 2); - using (var yBuffer = new Buffer2D(yLength, height)) - using (var xBuffer = new Buffer2D(xLength, height)) + MemoryManager memoryManager = configuration.MemoryManager; + + using (Buffer2D yBuffer = memoryManager.Allocate2D(yLength, height)) + using (Buffer2D xBuffer = memoryManager.Allocate2D(xLength, height)) { Parallel.For( 0, diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs index b9cb58707..8c851d7d8 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs @@ -3,6 +3,8 @@ using System; using System.Runtime.CompilerServices; + +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; @@ -19,18 +21,21 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The sampler to perform the resize operation. /// The target width. /// The target height. /// /// The structure that specifies the portion of the target image object to draw to. /// - protected ResamplingWeightedProcessor(IResampler sampler, int width, int height, Rectangle resizeRectangle) + protected ResamplingWeightedProcessor(MemoryManager memoryManager, IResampler sampler, int width, int height, Rectangle resizeRectangle) { + Guard.NotNull(memoryManager, nameof(memoryManager)); Guard.NotNull(sampler, nameof(sampler)); Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); + this.MemoryManager = memoryManager; this.Sampler = sampler; this.Width = width; this.Height = height; @@ -57,6 +62,8 @@ namespace SixLabors.ImageSharp.Processing.Processors /// public Rectangle ResizeRectangle { get; protected set; } + protected MemoryManager MemoryManager { get; } + /// /// Gets or sets the horizontal weights. /// @@ -86,7 +93,7 @@ namespace SixLabors.ImageSharp.Processing.Processors IResampler sampler = this.Sampler; float radius = MathF.Ceiling(scale * sampler.Radius); - var result = new WeightsBuffer(sourceSize, destinationSize); + var result = new WeightsBuffer(this.MemoryManager, sourceSize, destinationSize); for (int i = 0; i < destinationSize; i++) { diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs index b05d77868..2c18dc29b 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -24,25 +24,27 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The sampler to perform the resize operation. /// The target width. /// The target height. - public ResizeProcessor(IResampler sampler, int width, int height) - : base(sampler, width, height, new Rectangle(0, 0, width, height)) + public ResizeProcessor(MemoryManager memoryManager, IResampler sampler, int width, int height) + : base(memoryManager, sampler, width, height, new Rectangle(0, 0, width, height)) { } /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The sampler to perform the resize operation. /// The target width. /// The target height. /// /// The structure that specifies the portion of the target image object to draw to. /// - public ResizeProcessor(IResampler sampler, int width, int height, Rectangle resizeRectangle) - : base(sampler, width, height, resizeRectangle) + public ResizeProcessor(MemoryManager memoryManager, IResampler sampler, int width, int height, Rectangle resizeRectangle) + : base(memoryManager, sampler, width, height, resizeRectangle) { } @@ -54,12 +56,16 @@ namespace SixLabors.ImageSharp.Processing.Processors /// protected override Image CreateDestination(Image source, Rectangle sourceRectangle) { - // We will always be creating the clone even for mutate because we may need to resize the canvas - IEnumerable> frames = - source.Frames.Select(x => new ImageFrame(this.Width, this.Height, x.MetaData.Clone())); + Configuration config = source.GetConfiguration(); - // Use the overload to prevent an extra frame being added - return new Image(source.GetConfiguration(), source.MetaData.Clone(), frames); + // We will always be creating the clone even for mutate because thats the way this base processor works + // ------------ + // For resize we know we are going to populate every pixel with fresh data and we want a different target size so + // let's manually clone an empty set of images at the correct target and then have the base class process them in turn. + IEnumerable> frames = source.Frames.Select(x => new ImageFrame(source.GetConfiguration().MemoryManager, this.Width, this.Height, x.MetaData.Clone())); // this will create places holders + var image = new Image(config, source.MetaData.Clone(), frames); // base the place holder images in to prevent a extra frame being added + + return image; } /// @@ -118,38 +124,37 @@ namespace SixLabors.ImageSharp.Processing.Processors // First process the columns. Since we are not using multiple threads startY and endY // are the upper and lower bounds of the source rectangle. // TODO: Using a transposed variant of 'firstPassPixels' could eliminate the need for the WeightsWindow.ComputeWeightedColumnSum() method, and improve speed! - using (var firstPassPixels = new Buffer2D(width, source.Height)) + using (Buffer2D firstPassPixels = this.MemoryManager.Allocate2D(width, source.Height)) { - firstPassPixels.Clear(); + firstPassPixels.Buffer.Clear(); - Parallel.For( + ParallelFor.WithTemporaryBuffer( 0, sourceRectangle.Bottom, - configuration.ParallelOptions, - y => + configuration, + source.Width, + (int y, IBuffer tempRowBuffer) => { - // TODO: Without Parallel.For() this buffer object could be reused: - using (var tempRowBuffer = new Buffer(source.Width)) - { - Span firstPassRow = firstPassPixels.GetRowSpan(y); - Span sourceRow = source.GetPixelRowSpan(y); - PixelOperations.Instance.ToVector4(sourceRow, tempRowBuffer, sourceRow.Length); + Span firstPassRow = firstPassPixels.GetRowSpan(y); + Span sourceRow = source.GetPixelRowSpan(y); + Span tempRowSpan = tempRowBuffer.Span; - if (this.Compand) + PixelOperations.Instance.ToVector4(sourceRow, tempRowSpan, sourceRow.Length); + + if (this.Compand) + { + for (int x = minX; x < maxX; x++) { - for (int x = minX; x < maxX; x++) - { - WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; - firstPassRow[x] = window.ComputeExpandedWeightedRowSum(tempRowBuffer, sourceX); - } + WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; + firstPassRow[x] = window.ComputeExpandedWeightedRowSum(tempRowSpan, sourceX); } - else + } + else + { + for (int x = minX; x < maxX; x++) { - for (int x = minX; x < maxX; x++) - { - WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; - firstPassRow[x] = window.ComputeWeightedRowSum(tempRowBuffer, sourceX); - } + WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; + firstPassRow[x] = window.ComputeWeightedRowSum(tempRowSpan, sourceX); } } }); diff --git a/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs b/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs index 0e91087f9..d633a3869 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs @@ -17,11 +17,12 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// Initializes a new instance of the class. /// + /// The MemoryManager to use for allocations. /// The size of the source window /// The size of the destination window - public WeightsBuffer(int sourceSize, int destinationSize) + public WeightsBuffer(MemoryManager memoryManager, int sourceSize, int destinationSize) { - this.dataBuffer = Buffer2D.CreateClean(sourceSize, destinationSize); + this.dataBuffer = memoryManager.Allocate2D(sourceSize, destinationSize, true); this.Weights = new WeightsWindow[destinationSize]; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs b/src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs index b0a530514..399b3db84 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// The buffer containing the weights values. /// - private readonly Buffer buffer; + private readonly IBuffer buffer; /// /// Initializes a new instance of the struct. @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Processing.Processors { this.flatStartIndex = (index * buffer.Width) + left; this.Left = left; - this.buffer = buffer; + this.buffer = buffer.Buffer; this.Length = length; } @@ -57,7 +57,8 @@ namespace SixLabors.ImageSharp.Processing.Processors [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref float GetStartReference() { - return ref this.buffer[this.flatStartIndex]; + Span span = this.buffer.Span; + return ref span[this.flatStartIndex]; } /// diff --git a/src/ImageSharp/Processing/Transforms/Resize.cs b/src/ImageSharp/Processing/Transforms/Resize.cs index 832b02dea..18def03e6 100644 --- a/src/ImageSharp/Processing/Transforms/Resize.cs +++ b/src/ImageSharp/Processing/Transforms/Resize.cs @@ -193,7 +193,7 @@ namespace SixLabors.ImageSharp Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - img.Mutate(x => x.ApplyProcessor(new ResizeProcessor(sampler, width, height, targetRectangle) { Compand = compand }, sourceRectangle)); + img.Mutate(x => x.ApplyProcessor(new ResizeProcessor(source.MemoryManager, sampler, width, height, targetRectangle) { Compand = compand }, sourceRectangle)); }); } @@ -233,7 +233,7 @@ namespace SixLabors.ImageSharp Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - img.Mutate(x => x.ApplyProcessor(new ResizeProcessor(sampler, width, height, targetRectangle) { Compand = compand })); + img.Mutate(x => x.ApplyProcessor(new ResizeProcessor(source.MemoryManager, sampler, width, height, targetRectangle) { Compand = compand })); }); } } diff --git a/src/ImageSharp/Quantizers/Quantize.cs b/src/ImageSharp/Quantizers/Quantize.cs index d99d4af34..f2a09abb7 100644 --- a/src/ImageSharp/Quantizers/Quantize.cs +++ b/src/ImageSharp/Quantizers/Quantize.cs @@ -4,6 +4,7 @@ using System; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Quantizers; @@ -61,23 +62,25 @@ namespace SixLabors.ImageSharp QuantizedImage quantized = quantizer.Quantize(img.Frames.RootFrame, maxColors); int palleteCount = quantized.Palette.Length - 1; - using (var pixels = new PixelAccessor(quantized.Width, quantized.Height)) + using (Buffer2D pixels = source.MemoryManager.Allocate2D(quantized.Width, quantized.Height)) { Parallel.For( 0, pixels.Height, img.GetConfiguration().ParallelOptions, y => - { - for (int x = 0; x < pixels.Width; x++) { - int i = x + (y * pixels.Width); - TPixel color = quantized.Palette[Math.Min(palleteCount, quantized.Pixels[i])]; - pixels[x, y] = color; - } - }); + Span row = pixels.GetRowSpan(y); + int yy = y * pixels.Width; + for (int x = 0; x < pixels.Width; x++) + { + int i = x + yy; + TPixel color = quantized.Palette[Math.Min(palleteCount, quantized.Pixels[i])]; + row[x] = color; + } + }); - img.Frames[0].SwapPixelsBuffers(pixels); + Buffer2D.SwapContents(img.Frames[0].PixelBuffer, pixels); } }); } diff --git a/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs b/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs index 31e424060..0b41edf98 100644 --- a/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs +++ b/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs @@ -115,6 +115,7 @@ namespace SixLabors.ImageSharp.Quantizers.Base /// /// Override this to process the pixel in the first pass of the algorithm + /// TODO: We really should do this on a per-row basis! Shouldn't we internalize this method? /// /// The pixel to quantize /// diff --git a/src/ImageSharp/Quantizers/WuArrayPool.cs b/src/ImageSharp/Quantizers/WuArrayPool.cs deleted file mode 100644 index 04a70637b..000000000 --- a/src/ImageSharp/Quantizers/WuArrayPool.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Buffers; - -namespace SixLabors.ImageSharp.Quantizers -{ - /// - /// Provides array pooling for the . - /// This is a separate class so that the pools can be shared accross multiple generic quantizer instaces. - /// - internal static class WuArrayPool - { - /// - /// The long array pool. - /// - public static readonly ArrayPool LongPool = ArrayPool.Create(TableLength, 25); - - /// - /// The float array pool. - /// - public static readonly ArrayPool FloatPool = ArrayPool.Create(TableLength, 5); - - /// - /// The byte array pool. - /// - public static readonly ArrayPool BytePool = ArrayPool.Create(TableLength, 5); - - /// - /// The table length. Matches the calculated value in - /// - private const int TableLength = 2471625; - } -} \ No newline at end of file diff --git a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs index ce2a71da4..0aadf4973 100644 --- a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Quantizers.Base; @@ -35,6 +36,14 @@ namespace SixLabors.ImageSharp.Quantizers public class WuQuantizer : QuantizerBase where TPixel : struct, IPixel { + // TODO: The WuQuantizer code is rising several questions: + // - Do we really need to ALWAYS allocate the whole table of size TableLength? (~ 2471625 * sizeof(long) * 5 bytes ) + // - Isn't an AOS ("array of structures") layout more efficient & more readable than SOA ("structure of arrays") for this particular use case? + // (T, R, G, B, A, M2) could be grouped together! + // - There are per-pixel virtual calls in InitialQuantizePixel, why not do it on a per-row basis? + // - It's a frequently used class, we need tests! (So we can optimize safely.) There are tests in the original!!! We should just adopt them! + // https://github.com/JeremyAnsel/JeremyAnsel.ColorQuant/blob/master/JeremyAnsel.ColorQuant/JeremyAnsel.ColorQuant.Tests/WuColorQuantizerTests.cs + /// /// The index bits. /// @@ -68,37 +77,37 @@ namespace SixLabors.ImageSharp.Quantizers /// /// Moment of P(c). /// - private long[] vwt; + private IBuffer vwt; /// /// Moment of r*P(c). /// - private long[] vmr; + private IBuffer vmr; /// /// Moment of g*P(c). /// - private long[] vmg; + private IBuffer vmg; /// /// Moment of b*P(c). /// - private long[] vmb; + private IBuffer vmb; /// /// Moment of a*P(c). /// - private long[] vma; + private IBuffer vma; /// /// Moment of c^2*P(c). /// - private float[] m2; + private IBuffer m2; /// /// Color space tag. /// - private byte[] tag; + private IBuffer tag; /// /// Maximum allowed color depth @@ -136,27 +145,29 @@ namespace SixLabors.ImageSharp.Quantizers this.palette = null; this.colorMap.Clear(); + MemoryManager memoryManager = image.MemoryManager; + try { - this.vwt = WuArrayPool.LongPool.Rent(TableLength); - this.vmr = WuArrayPool.LongPool.Rent(TableLength); - this.vmg = WuArrayPool.LongPool.Rent(TableLength); - this.vmb = WuArrayPool.LongPool.Rent(TableLength); - this.vma = WuArrayPool.LongPool.Rent(TableLength); - this.m2 = WuArrayPool.FloatPool.Rent(TableLength); - this.tag = WuArrayPool.BytePool.Rent(TableLength); + this.vwt = memoryManager.AllocateClean(TableLength); + this.vmr = memoryManager.AllocateClean(TableLength); + this.vmg = memoryManager.AllocateClean(TableLength); + this.vmb = memoryManager.AllocateClean(TableLength); + this.vma = memoryManager.AllocateClean(TableLength); + this.m2 = memoryManager.AllocateClean(TableLength); + this.tag = memoryManager.AllocateClean(TableLength); return base.Quantize(image, this.colors); } finally { - WuArrayPool.LongPool.Return(this.vwt, true); - WuArrayPool.LongPool.Return(this.vmr, true); - WuArrayPool.LongPool.Return(this.vmg, true); - WuArrayPool.LongPool.Return(this.vmb, true); - WuArrayPool.LongPool.Return(this.vma, true); - WuArrayPool.FloatPool.Return(this.m2, true); - WuArrayPool.BytePool.Return(this.tag, true); + this.vwt.Dispose(); + this.vmr.Dispose(); + this.vmg.Dispose(); + this.vmb.Dispose(); + this.vma.Dispose(); + this.m2.Dispose(); + this.tag.Dispose(); } } @@ -170,14 +181,14 @@ namespace SixLabors.ImageSharp.Quantizers { this.Mark(ref this.colorCube[k], (byte)k); - float weight = Volume(ref this.colorCube[k], this.vwt); + float weight = Volume(ref this.colorCube[k], this.vwt.Span); if (MathF.Abs(weight) > Constants.Epsilon) { - float r = Volume(ref this.colorCube[k], this.vmr); - float g = Volume(ref this.colorCube[k], this.vmg); - float b = Volume(ref this.colorCube[k], this.vmb); - float a = Volume(ref this.colorCube[k], this.vma); + float r = Volume(ref this.colorCube[k], this.vmr.Span); + float g = Volume(ref this.colorCube[k], this.vmg.Span); + float b = Volume(ref this.colorCube[k], this.vmb.Span); + float a = Volume(ref this.colorCube[k], this.vma.Span); ref TPixel color = ref this.palette[k]; color.PackFromVector4(new Vector4(r, g, b, a) / weight / 255F); @@ -203,14 +214,21 @@ namespace SixLabors.ImageSharp.Quantizers int index = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1); - this.vwt[index]++; - this.vmr[index] += rgba.R; - this.vmg[index] += rgba.G; - this.vmb[index] += rgba.B; - this.vma[index] += rgba.A; + Span vwtSpan = this.vwt.Span; + Span vmrSpan = this.vmr.Span; + Span vmgSpan = this.vmg.Span; + Span vmbSpan = this.vmb.Span; + Span vmaSpan = this.vma.Span; + Span m2Span = this.m2.Span; + + vwtSpan[index]++; + vmrSpan[index] += rgba.R; + vmgSpan[index] += rgba.G; + vmbSpan[index] += rgba.B; + vmaSpan[index] += rgba.A; var vector = new Vector4(rgba.R, rgba.G, rgba.B, rgba.A); - this.m2[index] += Vector4.Dot(vector, vector); + m2Span[index] += Vector4.Dot(vector, vector); } /// @@ -228,7 +246,7 @@ namespace SixLabors.ImageSharp.Quantizers } } - this.Get3DMoments(); + this.Get3DMoments(source.MemoryManager); this.BuildCube(); } @@ -302,7 +320,7 @@ namespace SixLabors.ImageSharp.Quantizers /// The cube. /// The moment. /// The result. - private static float Volume(ref Box cube, long[] moment) + private static float Volume(ref Box cube, Span moment) { return moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)] - moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] @@ -329,7 +347,7 @@ namespace SixLabors.ImageSharp.Quantizers /// The direction. /// The moment. /// The result. - private static long Bottom(ref Box cube, int direction, long[] moment) + private static long Bottom(ref Box cube, int direction, Span moment) { switch (direction) { @@ -390,7 +408,7 @@ namespace SixLabors.ImageSharp.Quantizers /// The position. /// The moment. /// The result. - private static long Top(ref Box cube, int direction, int position, long[] moment) + private static long Top(ref Box cube, int direction, int position, Span moment) { switch (direction) { @@ -446,41 +464,60 @@ namespace SixLabors.ImageSharp.Quantizers /// /// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box. /// - private void Get3DMoments() + private void Get3DMoments(MemoryManager memoryManager) { - long[] volume = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount); - long[] volumeR = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount); - long[] volumeG = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount); - long[] volumeB = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount); - long[] volumeA = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount); - float[] volume2 = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount); - - long[] area = ArrayPool.Shared.Rent(IndexAlphaCount); - long[] areaR = ArrayPool.Shared.Rent(IndexAlphaCount); - long[] areaG = ArrayPool.Shared.Rent(IndexAlphaCount); - long[] areaB = ArrayPool.Shared.Rent(IndexAlphaCount); - long[] areaA = ArrayPool.Shared.Rent(IndexAlphaCount); - float[] area2 = ArrayPool.Shared.Rent(IndexAlphaCount); - - try + Span vwtSpan = this.vwt.Span; + Span vmrSpan = this.vmr.Span; + Span vmgSpan = this.vmg.Span; + Span vmbSpan = this.vmb.Span; + Span vmaSpan = this.vma.Span; + Span m2Span = this.m2.Span; + + using (IBuffer volume = memoryManager.Allocate(IndexCount * IndexAlphaCount)) + using (IBuffer volumeR = memoryManager.Allocate(IndexCount * IndexAlphaCount)) + using (IBuffer volumeG = memoryManager.Allocate(IndexCount * IndexAlphaCount)) + using (IBuffer volumeB = memoryManager.Allocate(IndexCount * IndexAlphaCount)) + using (IBuffer volumeA = memoryManager.Allocate(IndexCount * IndexAlphaCount)) + using (IBuffer volume2 = memoryManager.Allocate(IndexCount * IndexAlphaCount)) + + using (IBuffer area = memoryManager.Allocate(IndexAlphaCount)) + using (IBuffer areaR = memoryManager.Allocate(IndexAlphaCount)) + using (IBuffer areaG = memoryManager.Allocate(IndexAlphaCount)) + using (IBuffer areaB = memoryManager.Allocate(IndexAlphaCount)) + using (IBuffer areaA = memoryManager.Allocate(IndexAlphaCount)) + using (IBuffer area2 = memoryManager.Allocate(IndexAlphaCount)) { + Span volumeSpan = volume.Span; + Span volumeRSpan = volumeR.Span; + Span volumeGSpan = volumeG.Span; + Span volumeBSpan = volumeB.Span; + Span volumeASpan = volumeA.Span; + Span volume2Span = volume2.Span; + + Span areaSpan = area.Span; + Span areaRSpan = areaR.Span; + Span areaGSpan = areaG.Span; + Span areaBSpan = areaB.Span; + Span areaASpan = areaA.Span; + Span area2Span = area2.Span; + for (int r = 1; r < IndexCount; r++) { - Array.Clear(volume, 0, IndexCount * IndexAlphaCount); - Array.Clear(volumeR, 0, IndexCount * IndexAlphaCount); - Array.Clear(volumeG, 0, IndexCount * IndexAlphaCount); - Array.Clear(volumeB, 0, IndexCount * IndexAlphaCount); - Array.Clear(volumeA, 0, IndexCount * IndexAlphaCount); - Array.Clear(volume2, 0, IndexCount * IndexAlphaCount); + volume.Clear(); + volumeR.Clear(); + volumeG.Clear(); + volumeB.Clear(); + volumeA.Clear(); + volume2.Clear(); for (int g = 1; g < IndexCount; g++) { - Array.Clear(area, 0, IndexAlphaCount); - Array.Clear(areaR, 0, IndexAlphaCount); - Array.Clear(areaG, 0, IndexAlphaCount); - Array.Clear(areaB, 0, IndexAlphaCount); - Array.Clear(areaA, 0, IndexAlphaCount); - Array.Clear(area2, 0, IndexAlphaCount); + area.Clear(); + areaR.Clear(); + areaG.Clear(); + areaB.Clear(); + areaA.Clear(); + area2.Clear(); for (int b = 1; b < IndexCount; b++) { @@ -495,58 +532,42 @@ namespace SixLabors.ImageSharp.Quantizers { int ind1 = GetPaletteIndex(r, g, b, a); - line += this.vwt[ind1]; - lineR += this.vmr[ind1]; - lineG += this.vmg[ind1]; - lineB += this.vmb[ind1]; - lineA += this.vma[ind1]; - line2 += this.m2[ind1]; + line += vwtSpan[ind1]; + lineR += vmrSpan[ind1]; + lineG += vmgSpan[ind1]; + lineB += vmbSpan[ind1]; + lineA += vmaSpan[ind1]; + line2 += m2Span[ind1]; - area[a] += line; - areaR[a] += lineR; - areaG[a] += lineG; - areaB[a] += lineB; - areaA[a] += lineA; - area2[a] += line2; + areaSpan[a] += line; + areaRSpan[a] += lineR; + areaGSpan[a] += lineG; + areaBSpan[a] += lineB; + areaASpan[a] += lineA; + area2Span[a] += line2; int inv = (b * IndexAlphaCount) + a; - volume[inv] += area[a]; - volumeR[inv] += areaR[a]; - volumeG[inv] += areaG[a]; - volumeB[inv] += areaB[a]; - volumeA[inv] += areaA[a]; - volume2[inv] += area2[a]; + volumeSpan[inv] += areaSpan[a]; + volumeRSpan[inv] += areaRSpan[a]; + volumeGSpan[inv] += areaGSpan[a]; + volumeBSpan[inv] += areaBSpan[a]; + volumeASpan[inv] += areaASpan[a]; + volume2Span[inv] += area2Span[a]; int ind2 = ind1 - GetPaletteIndex(1, 0, 0, 0); - this.vwt[ind1] = this.vwt[ind2] + volume[inv]; - this.vmr[ind1] = this.vmr[ind2] + volumeR[inv]; - this.vmg[ind1] = this.vmg[ind2] + volumeG[inv]; - this.vmb[ind1] = this.vmb[ind2] + volumeB[inv]; - this.vma[ind1] = this.vma[ind2] + volumeA[inv]; - this.m2[ind1] = this.m2[ind2] + volume2[inv]; + vwtSpan[ind1] = vwtSpan[ind2] + volumeSpan[inv]; + vmrSpan[ind1] = vmrSpan[ind2] + volumeRSpan[inv]; + vmgSpan[ind1] = vmgSpan[ind2] + volumeGSpan[inv]; + vmbSpan[ind1] = vmbSpan[ind2] + volumeBSpan[inv]; + vmaSpan[ind1] = vmaSpan[ind2] + volumeASpan[inv]; + m2Span[ind1] = m2Span[ind2] + volume2Span[inv]; } } } } } - finally - { - ArrayPool.Shared.Return(volume); - ArrayPool.Shared.Return(volumeR); - ArrayPool.Shared.Return(volumeG); - ArrayPool.Shared.Return(volumeB); - ArrayPool.Shared.Return(volumeA); - ArrayPool.Shared.Return(volume2); - - ArrayPool.Shared.Return(area); - ArrayPool.Shared.Return(areaR); - ArrayPool.Shared.Return(areaG); - ArrayPool.Shared.Return(areaB); - ArrayPool.Shared.Return(areaA); - ArrayPool.Shared.Return(area2); - } } /// @@ -556,31 +577,33 @@ namespace SixLabors.ImageSharp.Quantizers /// The . private float Variance(ref Box cube) { - float dr = Volume(ref cube, this.vmr); - float dg = Volume(ref cube, this.vmg); - float db = Volume(ref cube, this.vmb); - float da = Volume(ref cube, this.vma); + float dr = Volume(ref cube, this.vmr.Span); + float dg = Volume(ref cube, this.vmg.Span); + float db = Volume(ref cube, this.vmb.Span); + float da = Volume(ref cube, this.vma.Span); + + Span m2Span = this.m2.Span; float xx = - this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)] - - this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] - - this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] - + this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] - - this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)] - + this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] - + this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] - - this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] - - this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A1)] - + this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] - + this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] - - this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] - + this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] - - this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] - - this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] - + this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; + m2Span[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)] + - m2Span[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] + - m2Span[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] + + m2Span[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] + - m2Span[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)] + + m2Span[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] + + m2Span[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] + - m2Span[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] + - m2Span[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A1)] + + m2Span[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] + + m2Span[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] + - m2Span[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] + + m2Span[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] + - m2Span[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] + - m2Span[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] + + m2Span[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; var vector = new Vector4(dr, dg, db, da); - return xx - (Vector4.Dot(vector, vector) / Volume(ref cube, this.vwt)); + return xx - (Vector4.Dot(vector, vector) / Volume(ref cube, this.vwt.Span)); } /// @@ -603,22 +626,22 @@ namespace SixLabors.ImageSharp.Quantizers /// The . private float Maximize(ref Box cube, int direction, int first, int last, out int cut, float wholeR, float wholeG, float wholeB, float wholeA, float wholeW) { - long baseR = Bottom(ref cube, direction, this.vmr); - long baseG = Bottom(ref cube, direction, this.vmg); - long baseB = Bottom(ref cube, direction, this.vmb); - long baseA = Bottom(ref cube, direction, this.vma); - long baseW = Bottom(ref cube, direction, this.vwt); + long baseR = Bottom(ref cube, direction, this.vmr.Span); + long baseG = Bottom(ref cube, direction, this.vmg.Span); + long baseB = Bottom(ref cube, direction, this.vmb.Span); + long baseA = Bottom(ref cube, direction, this.vma.Span); + long baseW = Bottom(ref cube, direction, this.vwt.Span); float max = 0F; cut = -1; for (int i = first; i < last; i++) { - float halfR = baseR + Top(ref cube, direction, i, this.vmr); - float halfG = baseG + Top(ref cube, direction, i, this.vmg); - float halfB = baseB + Top(ref cube, direction, i, this.vmb); - float halfA = baseA + Top(ref cube, direction, i, this.vma); - float halfW = baseW + Top(ref cube, direction, i, this.vwt); + float halfR = baseR + Top(ref cube, direction, i, this.vmr.Span); + float halfG = baseG + Top(ref cube, direction, i, this.vmg.Span); + float halfB = baseB + Top(ref cube, direction, i, this.vmb.Span); + float halfA = baseA + Top(ref cube, direction, i, this.vma.Span); + float halfW = baseW + Top(ref cube, direction, i, this.vwt.Span); if (MathF.Abs(halfW) < Constants.Epsilon) { @@ -662,11 +685,11 @@ namespace SixLabors.ImageSharp.Quantizers /// Returns a value indicating whether the box has been split. private bool Cut(ref Box set1, ref Box set2) { - float wholeR = Volume(ref set1, this.vmr); - float wholeG = Volume(ref set1, this.vmg); - float wholeB = Volume(ref set1, this.vmb); - float wholeA = Volume(ref set1, this.vma); - float wholeW = Volume(ref set1, this.vwt); + float wholeR = Volume(ref set1, this.vmr.Span); + float wholeG = Volume(ref set1, this.vmg.Span); + float wholeB = Volume(ref set1, this.vmb.Span); + float wholeA = Volume(ref set1, this.vma.Span); + float wholeW = Volume(ref set1, this.vwt.Span); float maxr = this.Maximize(ref set1, 3, set1.R0 + 1, set1.R1, out int cutr, wholeR, wholeG, wholeB, wholeA, wholeW); float maxg = this.Maximize(ref set1, 2, set1.G0 + 1, set1.G1, out int cutg, wholeR, wholeG, wholeB, wholeA, wholeW); @@ -750,6 +773,8 @@ namespace SixLabors.ImageSharp.Quantizers /// A label. private void Mark(ref Box cube, byte label) { + Span tagSpan = this.tag.Span; + for (int r = cube.R0 + 1; r <= cube.R1; r++) { for (int g = cube.G0 + 1; g <= cube.G1; g++) @@ -758,7 +783,7 @@ namespace SixLabors.ImageSharp.Quantizers { for (int a = cube.A0 + 1; a <= cube.A1; a++) { - this.tag[GetPaletteIndex(r, g, b, a)] = label; + tagSpan[GetPaletteIndex(r, g, b, a)] = label; } } } @@ -841,7 +866,9 @@ namespace SixLabors.ImageSharp.Quantizers int b = rgba.B >> (8 - IndexBits); int a = rgba.A >> (8 - IndexAlphaBits); - return this.tag[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; + Span tagSpan = this.tag.Span; + + return tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; } } } \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4.cs index 83c2a2ee8..7d8519875 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4.cs @@ -2,6 +2,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk { using System.Numerics; + using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; @@ -12,9 +13,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk public abstract class PackFromVector4 where TPixel : struct, IPixel { - private Buffer source; + private IBuffer source; - private Buffer destination; + private IBuffer destination; [Params(16, 128, 512)] public int Count { get; set; } @@ -22,8 +23,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [GlobalSetup] public void Setup() { - this.destination = new Buffer(this.Count); - this.source = new Buffer(this.Count); + this.destination = Configuration.Default.MemoryManager.Allocate(this.Count); + this.source = Configuration.Default.MemoryManager.Allocate(this.Count); } [GlobalCleanup] @@ -36,25 +37,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [Benchmark(Baseline = true)] public void PerElement() { - Vector4[] s = this.source.Array; - TPixel[] d = this.destination.Array; + ref Vector4 s = ref this.source.Span.DangerousGetPinnableReference(); + ref TPixel d = ref this.destination.Span.DangerousGetPinnableReference(); for (int i = 0; i < this.Count; i++) { - d[i].PackFromVector4(s[i]); + Unsafe.Add(ref d, i).PackFromVector4(Unsafe.Add(ref s, i)); } } [Benchmark] public void CommonBulk() { - new PixelOperations().PackFromVector4(this.source, this.destination, this.Count); + new PixelOperations().PackFromVector4(this.source.Span, this.destination.Span, this.Count); } [Benchmark] public void OptimizedBulk() { - PixelOperations.Instance.PackFromVector4(this.source, this.destination, this.Count); + PixelOperations.Instance.PackFromVector4(this.source.Span, this.destination.Span, this.Count); } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs deleted file mode 100644 index b4f6ea9c0..000000000 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs +++ /dev/null @@ -1,77 +0,0 @@ -namespace SixLabors.ImageSharp.Benchmarks -{ - using System.Numerics; - using System.Runtime.CompilerServices; - - using BenchmarkDotNet.Attributes; - - using SixLabors.ImageSharp; - using SixLabors.ImageSharp.Memory; - - /// - /// Compares two implementation candidates for general BulkPixelOperations.ToVector4(): - /// - One iterating with pointers - /// - One iterating with ref locals - /// - public unsafe class PackFromVector4ReferenceVsPointer - { - private Buffer destination; - - private Buffer source; - - [Params(16, 128, 1024)] - public int Count { get; set; } - - [GlobalSetup] - public void Setup() - { - this.destination = new Buffer(this.Count); - this.source = new Buffer(this.Count * 4); - this.source.Pin(); - this.destination.Pin(); - } - - [GlobalCleanup] - public void Cleanup() - { - this.source.Dispose(); - this.destination.Dispose(); - } - - [Benchmark(Baseline = true)] - public void PackUsingPointers() - { - Vector4* sp = (Vector4*)this.source.Pin(); - byte* dp = (byte*)this.destination.Pin(); - int count = this.Count; - int size = sizeof(Rgba32); - - for (int i = 0; i < count; i++) - { - Vector4 v = Unsafe.Read(sp); - Rgba32 c = default(Rgba32); - c.PackFromVector4(v); - Unsafe.Write(dp, c); - - sp++; - dp += size; - } - } - - [Benchmark] - public void PackUsingReferences() - { - ref Vector4 sp = ref this.source.Array[0]; - ref Rgba32 dp = ref this.destination.Array[0]; - int count = this.Count; - - for (int i = 0; i < count; i++) - { - dp.PackFromVector4(sp); - - sp = Unsafe.Add(ref sp, 1); - dp = Unsafe.Add(ref dp, 1); - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs index 5c3648c2d..882d77dd1 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs @@ -1,6 +1,8 @@ // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk { + using System; + using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Memory; @@ -9,9 +11,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk public abstract class PackFromXyzw where TPixel : struct, IPixel { - private Buffer destination; + private IBuffer destination; - private Buffer source; + private IBuffer source; [Params(16, 128, 1024)] public int Count { get; set; } @@ -19,8 +21,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [GlobalSetup] public void Setup() { - this.destination = new Buffer(this.Count); - this.source = new Buffer(this.Count * 4); + this.destination = Configuration.Default.MemoryManager.Allocate(this.Count); + this.source = Configuration.Default.MemoryManager.Allocate(this.Count * 4); } [GlobalCleanup] @@ -33,13 +35,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [Benchmark(Baseline = true)] public void PerElement() { - byte[] s = this.source.Array; - TPixel[] d = this.destination.Array; + Span s = this.source.Span; + Span d = this.destination.Span; for (int i = 0; i < this.Count; i++) { int i4 = i * 4; - TPixel c = default(TPixel); + var c = default(TPixel); c.PackFromRgba32(new Rgba32(s[i4], s[i4 + 1], s[i4 + 2], s[i4 + 3])); d[i] = c; } @@ -48,13 +50,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [Benchmark] public void CommonBulk() { - new PixelOperations().PackFromRgba32Bytes(this.source, this.destination, this.Count); + new PixelOperations().PackFromRgba32Bytes(this.source.Span, this.destination.Span, this.Count); } [Benchmark] public void OptimizedBulk() { - PixelOperations.Instance.PackFromRgba32Bytes(this.source, this.destination, this.Count); + PixelOperations.Instance.PackFromRgba32Bytes(this.source.Span, this.destination.Span, this.Count); } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs index 2bf4e0da6..653714150 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs @@ -1,6 +1,7 @@ // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk { + using System; using System.Numerics; using BenchmarkDotNet.Attributes; @@ -11,9 +12,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk public abstract class ToVector4 where TPixel : struct, IPixel { - private Buffer source; + private IBuffer source; - private Buffer destination; + private IBuffer destination; [Params(64, 300, 1024)] public int Count { get; set; } @@ -21,8 +22,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [GlobalSetup] public void Setup() { - this.source = new Buffer(this.Count); - this.destination = new Buffer(this.Count); + this.source = Configuration.Default.MemoryManager.Allocate(this.Count); + this.destination = Configuration.Default.MemoryManager.Allocate(this.Count); } [GlobalCleanup] @@ -35,8 +36,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [Benchmark(Baseline = true)] public void PerElement() { - TPixel[] s = this.source.Array; - Vector4[] d = this.destination.Array; + Span s = this.source.Span; + Span d = this.destination.Span; for (int i = 0; i < this.Count; i++) { @@ -48,13 +49,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [Benchmark] public void CommonBulk() { - new PixelOperations().ToVector4(this.source, this.destination, this.Count); + new PixelOperations().ToVector4(this.source.Span, this.destination.Span, this.Count); } [Benchmark] public void OptimizedBulk() { - PixelOperations.Instance.ToVector4(this.source, this.destination, this.Count); + PixelOperations.Instance.ToVector4(this.source.Span, this.destination.Span, this.Count); } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs index 2d624c19f..b2def64ac 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs @@ -1,6 +1,9 @@ // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk { + using System; + using System.Numerics; + using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Memory; @@ -9,9 +12,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk public abstract class ToXyz where TPixel : struct, IPixel { - private Buffer source; + private IBuffer source; - private Buffer destination; + private IBuffer destination; [Params(16, 128, 1024)] public int Count { get; set; } @@ -19,8 +22,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [GlobalSetup] public void Setup() { - this.source = new Buffer(this.Count); - this.destination = new Buffer(this.Count * 3); + this.source = Configuration.Default.MemoryManager.Allocate(this.Count); + this.destination = Configuration.Default.MemoryManager.Allocate(this.Count * 3); } [GlobalCleanup] @@ -33,8 +36,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [Benchmark(Baseline = true)] public void PerElement() { - TPixel[] s = this.source.Array; - byte[] d = this.destination.Array; + Span s = this.source.Span; + Span d = this.destination.Span; + var rgb = default(Rgb24); for (int i = 0; i < this.Count; i++) @@ -51,13 +55,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [Benchmark] public void CommonBulk() { - new PixelOperations().ToRgb24Bytes(this.source, this.destination, this.Count); + new PixelOperations().ToRgb24Bytes(this.source.Span, this.destination.Span, this.Count); } [Benchmark] public void OptimizedBulk() { - PixelOperations.Instance.ToRgb24Bytes(this.source, this.destination, this.Count); + PixelOperations.Instance.ToRgb24Bytes(this.source.Span, this.destination.Span, this.Count); } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs index 150b55aed..dd9a628c2 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs @@ -14,9 +14,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk public abstract class ToXyzw where TPixel : struct, IPixel { - private Buffer source; + private IBuffer source; - private Buffer destination; + private IBuffer destination; [Params(16, 128, 1024)] public int Count { get; set; } @@ -24,8 +24,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [GlobalSetup] public void Setup() { - this.source = new Buffer(this.Count); - this.destination = new Buffer(this.Count * 4); + this.source = Configuration.Default.MemoryManager.Allocate(this.Count); + this.destination = Configuration.Default.MemoryManager.Allocate(this.Count * 4); } [GlobalCleanup] @@ -38,8 +38,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [Benchmark(Baseline = true)] public void PerElement() { - TPixel[] s = this.source.Array; - byte[] d = this.destination.Array; + Span s = this.source.Span; + Span d = this.destination.Span; + var rgba = default(Rgba32); for (int i = 0; i < this.Count; i++) @@ -57,13 +58,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [Benchmark] public void CommonBulk() { - new PixelOperations().ToRgba32Bytes(this.source, this.destination, this.Count); + new PixelOperations().ToRgba32Bytes(this.source.Span, this.destination.Span, this.Count); } [Benchmark] public void OptimizedBulk() { - PixelOperations.Instance.ToRgba32Bytes(this.source, this.destination, this.Count); + PixelOperations.Instance.ToRgba32Bytes(this.source.Span, this.destination.Span, this.Count); } } diff --git a/tests/ImageSharp.Benchmarks/General/ClearBuffer.cs b/tests/ImageSharp.Benchmarks/General/ClearBuffer.cs deleted file mode 100644 index 0ac1413be..000000000 --- a/tests/ImageSharp.Benchmarks/General/ClearBuffer.cs +++ /dev/null @@ -1,42 +0,0 @@ -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.General -{ - using System; - using System.Runtime.CompilerServices; - - using BenchmarkDotNet.Attributes; - - using SixLabors.ImageSharp.Memory; - - public unsafe class ClearBuffer - { - private Buffer buffer; - - [Params(32, 128, 512)] - public int Count { get; set; } - - [GlobalSetup] - public void Setup() - { - this.buffer = new Buffer(this.Count); - } - - [GlobalCleanup] - public void Cleanup() - { - this.buffer.Dispose(); - } - - [Benchmark(Baseline = true)] - public void Array_Clear() - { - Array.Clear(this.buffer.Array, 0, this.Count); - } - - [Benchmark] - public void Unsafe_InitBlock() - { - Unsafe.InitBlock((void*)this.buffer.Pin(), default(byte), (uint)this.Count * sizeof(uint)); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/IterateArray.cs b/tests/ImageSharp.Benchmarks/General/IterateArray.cs deleted file mode 100644 index 48ee266fe..000000000 --- a/tests/ImageSharp.Benchmarks/General/IterateArray.cs +++ /dev/null @@ -1,73 +0,0 @@ -namespace SixLabors.ImageSharp.Benchmarks.General -{ - using System.Numerics; - using System.Runtime.CompilerServices; - - using BenchmarkDotNet.Attributes; - - using SixLabors.ImageSharp.Memory; - - public class IterateArray - { - // Usual pinned stuff - private Buffer buffer; - - // An array that's not pinned by intent! - private Vector4[] array; - - [Params(64, 1024)] - public int Length { get; set; } - - [GlobalSetup] - public void Setup() - { - this.buffer = new Buffer(this.Length); - this.buffer.Pin(); - this.array = new Vector4[this.Length]; - } - - [Benchmark(Baseline = true)] - public Vector4 IterateIndexed() - { - Vector4 sum = new Vector4(); - Vector4[] a = this.array; - - for (int i = 0; i < a.Length; i++) - { - sum += a[i]; - } - return sum; - } - - [Benchmark] - public unsafe Vector4 IterateUsingPointers() - { - Vector4 sum = new Vector4(); - - Vector4* ptr = (Vector4*) this.buffer.Pin(); - Vector4* end = ptr + this.Length; - - for (; ptr < end; ptr++) - { - sum += *ptr; - } - - return sum; - } - - [Benchmark] - public Vector4 IterateUsingReferences() - { - Vector4 sum = new Vector4(); - - ref Vector4 start = ref this.array[0]; - - for (int i = 0; i < this.Length; i++) - { - sum += Unsafe.Add(ref start, i); - } - - return sum; - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/PixelIndexing.cs b/tests/ImageSharp.Benchmarks/General/PixelIndexing.cs deleted file mode 100644 index 0e21caffb..000000000 --- a/tests/ImageSharp.Benchmarks/General/PixelIndexing.cs +++ /dev/null @@ -1,362 +0,0 @@ -namespace SixLabors.ImageSharp.Benchmarks.General -{ - using System.Numerics; - using System.Runtime.CompilerServices; - - using BenchmarkDotNet.Attributes; - - using SixLabors.ImageSharp.Memory; - - // Pixel indexing benchmarks compare different methods for getting/setting all pixel values in a subsegment of a single pixel row. - public abstract unsafe class PixelIndexing - { - /// - /// https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/Pinnable.cs - /// - protected class Pinnable - { - public T Data; - } - - /// - /// The indexer methods are encapsulated into a struct to make sure everything is inlined. - /// - internal struct Data - { - private Vector4* pointer; - - private Pinnable pinnable; - - private Vector4[] array; - - private int width; - - public Data(Buffer2D buffer) - { - this.pointer = (Vector4*)buffer.Pin(); - this.pinnable = Unsafe.As>(buffer.Array); - this.array = buffer.Array; - this.width = buffer.Width; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 GetPointersBasicImpl(int x, int y) - { - return this.pointer[y * this.width + x]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 GetPointersSrcsUnsafeImpl(int x, int y) - { - // This is the original solution in PixelAccessor: - return Unsafe.Read((byte*)this.pointer + (((y * this.width) + x) * Unsafe.SizeOf())); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 GetReferencesImpl(int x, int y) - { - int elementOffset = (y * this.width) + x; - return Unsafe.Add(ref this.pinnable.Data, elementOffset); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref Vector4 GetReferencesRefReturnsImpl(int x, int y) - { - int elementOffset = (y * this.width) + x; - return ref Unsafe.Add(ref this.pinnable.Data, elementOffset); - } - - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void IndexWithPointersBasicImpl(int x, int y, Vector4 v) - { - this.pointer[y * this.width + x] = v; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void IndexWithPointersSrcsUnsafeImpl(int x, int y, Vector4 v) - { - Unsafe.Write((byte*)this.pointer + (((y * this.width) + x) * Unsafe.SizeOf()), v); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void IndexWithReferencesOnPinnableIncorrectImpl(int x, int y, Vector4 v) - { - int elementOffset = (y * this.width) + x; - // Incorrect, because also should add a runtime-specific byte offset here. See https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/Span.cs#L68 - Unsafe.Add(ref this.pinnable.Data, elementOffset) = v; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref Vector4 IndexWithReferencesOnPinnableIncorrectRefReturnImpl(int x, int y) - { - int elementOffset = (y * this.width) + x; - // Incorrect, because also should add a runtime-specific byte offset here. See https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/Span.cs#L68 - return ref Unsafe.Add(ref this.pinnable.Data, elementOffset); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void IndexWithUnsafeReferenceArithmeticsOnArray0Impl(int x, int y, Vector4 v) - { - int elementOffset = (y * this.width) + x; - Unsafe.Add(ref this.array[0], elementOffset) = v; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref Vector4 IndexWithUnsafeReferenceArithmeticsOnArray0RefReturnImpl(int x, int y) - { - int elementOffset = (y * this.width) + x; - return ref Unsafe.Add(ref this.array[0], elementOffset); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void IndexSetArrayStraightforward(int x, int y, Vector4 v) - { - // No magic. - // We just index right into the array as normal people do. - // And it looks like this is the fastest way! - this.array[(y * this.width) + x] = v; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref Vector4 IndexWithReferencesOnArrayStraightforwardRefReturnImpl(int x, int y) - { - // No magic. - // We just index right into the array as normal people do. - // And it looks like this is the fastest way! - return ref this.array[(y * this.width) + x]; - } - } - - internal Buffer2D buffer; - - protected int width; - - protected int startIndex; - - protected int endIndex; - - protected Vector4* pointer; - - protected Vector4[] array; - - protected Pinnable pinnable; - - // [Params(1024)] - public int Count { get; set; } = 1024; - - [GlobalSetup] - public void Setup() - { - this.width = 2048; - this.buffer = new Buffer2D(2048, 2048); - this.pointer = (Vector4*)this.buffer.Pin(); - this.array = this.buffer.Array; - this.pinnable = Unsafe.As>(this.array); - - this.startIndex = 2048 / 2 - (this.Count / 2); - this.endIndex = 2048 / 2 + (this.Count / 2); - } - - [GlobalCleanup] - public void Cleanup() - { - this.buffer.Dispose(); - } - - } - - public class PixelIndexingGetter : PixelIndexing - { - [Benchmark(Description = "Index.Get: Pointers+arithmetics", Baseline = true)] - public Vector4 IndexWithPointersBasic() - { - Vector4 sum = Vector4.Zero; - Data data = new Data(this.buffer); - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - sum += data.GetPointersBasicImpl(x, y); - } - - return sum; - } - - [Benchmark(Description = "Index.Get: Pointers+SRCS.Unsafe")] - public Vector4 IndexWithPointersSrcsUnsafe() - { - Vector4 sum = Vector4.Zero; - Data data = new Data(this.buffer); - - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - sum += data.GetPointersSrcsUnsafeImpl(x, y); - } - - return sum; - } - - [Benchmark(Description = "Index.Get: References")] - public Vector4 IndexWithReferences() - { - Vector4 sum = Vector4.Zero; - Data data = new Data(this.buffer); - - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - sum += data.GetReferencesImpl(x, y); - } - - return sum; - } - - [Benchmark(Description = "Index.Get: References|refreturns")] - public Vector4 IndexWithReferencesRefReturns() - { - Vector4 sum = Vector4.Zero; - Data data = new Data(this.buffer); - - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - sum += data.GetReferencesRefReturnsImpl(x, y); - } - - return sum; - } - } - - public class PixelIndexingSetter : PixelIndexing - { - [Benchmark(Description = "!!! Index.Set: Pointers|arithmetics", Baseline = true)] - public void IndexWithPointersBasic() - { - Vector4 v = new Vector4(1, 2, 3, 4); - Data data = new Data(this.buffer); - - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - data.IndexWithPointersBasicImpl(x, y, v); - } - } - - [Benchmark(Description = "Index.Set: Pointers|SRCS.Unsafe")] - public void IndexWithPointersSrcsUnsafe() - { - Vector4 v = new Vector4(1, 2, 3, 4); - Data data = new Data(this.buffer); - - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - data.IndexWithPointersSrcsUnsafeImpl(x, y, v); - } - } - - [Benchmark(Description = "Index.Set: References|IncorrectPinnable")] - public void IndexWithReferencesPinnableBasic() - { - Vector4 v = new Vector4(1, 2, 3, 4); - Data data = new Data(this.buffer); - - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - data.IndexWithReferencesOnPinnableIncorrectImpl(x, y, v); - } - } - - [Benchmark(Description = "Index.Set: References|IncorrectPinnable|refreturn")] - public void IndexWithReferencesPinnableRefReturn() - { - Vector4 v = new Vector4(1, 2, 3, 4); - Data data = new Data(this.buffer); - - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - data.IndexWithReferencesOnPinnableIncorrectRefReturnImpl(x, y) = v; - } - } - - [Benchmark(Description = "Index.Set: References|Array[0]Unsafe")] - public void IndexWithReferencesArrayBasic() - { - Vector4 v = new Vector4(1, 2, 3, 4); - Data data = new Data(this.buffer); - - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - data.IndexWithUnsafeReferenceArithmeticsOnArray0Impl(x, y, v); - } - } - - [Benchmark(Description = "Index.Set: References|Array[0]Unsafe|refreturn")] - public void IndexWithReferencesArrayRefReturn() - { - Vector4 v = new Vector4(1, 2, 3, 4); - Data data = new Data(this.buffer); - - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - data.IndexWithUnsafeReferenceArithmeticsOnArray0RefReturnImpl(x, y) = v; - } - } - - [Benchmark(Description = "!!! Index.Set: References|Array+Straight")] - public void IndexWithReferencesArrayStraightforward() - { - Vector4 v = new Vector4(1, 2, 3, 4); - Data data = new Data(this.buffer); - - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - // No magic. - // We just index right into the array as normal people do. - // And it looks like this is the fastest way! - data.IndexSetArrayStraightforward(x, y, v); - } - } - - - [Benchmark(Description = "!!! Index.Set: References|Array+Straight|refreturn")] - public void IndexWithReferencesArrayStraightforwardRefReturn() - { - Vector4 v = new Vector4(1, 2, 3, 4); - Data data = new Data(this.buffer); - - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - // No magic. - // We just index right into the array as normal people do. - // And it looks like this is the fastest way! - data.IndexWithReferencesOnArrayStraightforwardRefReturnImpl(x, y) = v; - } - } - - [Benchmark(Description = "!!! Index.Set: SmartUnsafe")] - public void SmartUnsafe() - { - Vector4 v = new Vector4(1, 2, 3, 4); - Data data = new Data(this.buffer); - - // This method is basically an unsafe variant of .GetRowSpan(y) + indexing individual pixels in the row. - // If a user seriously needs by-pixel manipulation to be performant, we should provide this option. - - ref Vector4 rowStart = ref data.IndexWithReferencesOnArrayStraightforwardRefReturnImpl(this.startIndex, this.startIndex); - - for (int i = 0; i < this.Count; i++) - { - // We don't have to add 'Width * y' here! - Unsafe.Add(ref rowStart, i) = v; - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs b/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs index 70ea164d6..e5eb29544 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream()) { - PngEncoder options = new PngEncoder { Quantizer = new OctreeQuantizer { Dither = false }, PaletteSize = 256 }; + PngEncoder options = new PngEncoder() { Quantizer = new OctreeQuantizer { Dither = false }, PaletteSize = 256 }; this.bmpCore.SaveAsPng(memoryStream, options); } @@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream()) { - PngEncoder options = new PngEncoder { Quantizer = new PaletteQuantizer(), PaletteSize = 256 }; + PngEncoder options = new PngEncoder() { Quantizer = new PaletteQuantizer(), PaletteSize = 256 }; this.bmpCore.SaveAsPng(memoryStream, options); } @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream()) { - PngEncoder options = new PngEncoder { Quantizer = new PaletteQuantizer { Dither = false }, PaletteSize = 256 }; + PngEncoder options = new PngEncoder() { Quantizer = new PaletteQuantizer { Dither = false }, PaletteSize = 256 }; this.bmpCore.SaveAsPng(memoryStream, options); } diff --git a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs index b3c1e4ee9..53522a51f 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs @@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Image new OctreeQuantizer() : new PaletteQuantizer(); - var options = new PngEncoder { Quantizer = quantizer }; + var options = new PngEncoder() { Quantizer = quantizer }; this.bmpCore.SaveAsPng(memoryStream, options); } } diff --git a/tests/ImageSharp.Benchmarks/Image/Jpeg/YCbCrColorConversion.cs b/tests/ImageSharp.Benchmarks/Image/Jpeg/YCbCrColorConversion.cs index 93420aacf..c47aff9cf 100644 --- a/tests/ImageSharp.Benchmarks/Image/Jpeg/YCbCrColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Image/Jpeg/YCbCrColorConversion.cs @@ -76,7 +76,7 @@ } // no need to dispose when buffer is not array owner - buffers[i] = new Buffer2D(values, values.Length, 1); + buffers[i] = Configuration.Default.MemoryManager.Allocate2D(values.Length, 1); } return buffers; diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs index 5a3131f79..c088e8eed 100644 --- a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs +++ b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Benchmarks Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = Configuration.Default.MemoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -59,21 +59,20 @@ namespace SixLabors.ImageSharp.Benchmarks { using (Image image = new Image(800, 800)) { - Buffer amounts = new Buffer(image.Width); - - for (int x = 0; x < image.Width; x++) - { - amounts[x] = 1; - } - using (PixelAccessor pixels = image.Lock()) + using (IBuffer amounts = Configuration.Default.MemoryManager.Allocate(image.Width)) { - for (int y = 0; y < image.Height; y++) + amounts.Span.Fill(1); + + using (PixelAccessor pixels = image.Lock()) { - Span span = pixels.GetRowSpan(y); - BulkVectorConvert(span, span, span, amounts); + for (int y = 0; y < image.Height; y++) + { + Span span = pixels.GetRowSpan(y); + this.BulkVectorConvert(span, span, span, amounts.Span); + } } + return new CoreSize(image.Width, image.Height); } - return new CoreSize(image.Width, image.Height); } } @@ -82,21 +81,20 @@ namespace SixLabors.ImageSharp.Benchmarks { using (Image image = new Image(800, 800)) { - Buffer amounts = new Buffer(image.Width); - - for (int x = 0; x < image.Width; x++) + using (IBuffer amounts = Configuration.Default.MemoryManager.Allocate(image.Width)) { - amounts[x] = 1; - } - using (PixelAccessor pixels = image.Lock()) - { - for (int y = 0; y < image.Height; y++) + amounts.Span.Fill(1); + using (PixelAccessor pixels = image.Lock()) { - Span span = pixels.GetRowSpan(y); - BulkPixelConvert(span, span, span, amounts); + for (int y = 0; y < image.Height; y++) + { + Span span = pixels.GetRowSpan(y); + this.BulkPixelConvert(span, span, span, amounts.Span); + } } + + return new CoreSize(image.Width, image.Height); } - return new CoreSize(image.Width, image.Height); } } } diff --git a/tests/ImageSharp.Benchmarks/Samplers/Glow.cs b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs index 1f88c4fbf..5f4e2d75b 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Glow.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Benchmarks [GlobalSetup] public void Setup() { - this.bulk = new GlowProcessor(NamedColors.Beige, 800 * .5f, GraphicsOptions.Default); + this.bulk = new GlowProcessor(Configuration.Default.MemoryManager, NamedColors.Beige, 800 * .5f, GraphicsOptions.Default); this.parallel = new GlowProcessorParallel(NamedColors.Beige) { Radius = 800 * .5f, }; } @@ -103,13 +103,10 @@ namespace SixLabors.ImageSharp.Benchmarks } int width = maxX - minX; - using (Buffer rowColors = new Buffer(width)) + using (IBuffer rowColors = Configuration.Default.MemoryManager.Allocate(width)) using (PixelAccessor sourcePixels = source.Lock()) { - for (int i = 0; i < width; i++) - { - rowColors[i] = glowColor; - } + rowColors.Span.Fill(glowColor); Parallel.For( minY, diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs index 941807f54..5ca09ad1d 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs @@ -1,131 +1,144 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.IO; -using System.Numerics; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Drawing; -using SixLabors.ImageSharp.Drawing.Brushes; -using SixLabors.ImageSharp.Drawing.Pens; -using SixLabors.ImageSharp.Drawing.Processors; -using SixLabors.ImageSharp.Processing; -using Moq; -using SixLabors.Primitives; -using SixLabors.Shapes; -using Xunit; - namespace SixLabors.ImageSharp.Tests.Drawing.Paths { - public class ShapeRegionTests + using System; + + using Moq; + + using SixLabors.ImageSharp.Drawing; + using SixLabors.Primitives; + using SixLabors.Shapes; + + using Xunit; + + public class ShapeRegionTests { private readonly Mock pathMock; - private readonly SixLabors.Primitives.RectangleF bounds; + + private readonly RectangleF bounds; public ShapeRegionTests() { this.pathMock = new Mock(); this.bounds = new RectangleF(10.5f, 10, 10, 10); - pathMock.Setup(x => x.Bounds).Returns(this.bounds); + this.pathMock.Setup(x => x.Bounds).Returns(this.bounds); // wire up the 2 mocks to reference eachother - pathMock.Setup(x => x.AsClosedPath()).Returns(() => pathMock.Object); + this.pathMock.Setup(x => x.AsClosedPath()).Returns(() => this.pathMock.Object); } [Fact] public void ShapeRegionWithPathCallsAsShape() { - new ShapeRegion(pathMock.Object); + new ShapeRegion(this.pathMock.Object); - pathMock.Verify(x => x.AsClosedPath()); + this.pathMock.Verify(x => x.AsClosedPath()); } [Fact] public void ShapeRegionWithPathRetainsShape() { - ShapeRegion region = new ShapeRegion(pathMock.Object); + ShapeRegion region = new ShapeRegion(this.pathMock.Object); - Assert.Equal(pathMock.Object, region.Shape); + Assert.Equal(this.pathMock.Object, region.Shape); } [Fact] public void ShapeRegionFromPathConvertsBoundsProxyToShape() { - ShapeRegion region = new ShapeRegion(pathMock.Object); + ShapeRegion region = new ShapeRegion(this.pathMock.Object); - Assert.Equal(Math.Floor(bounds.Left), region.Bounds.Left); - Assert.Equal(Math.Ceiling(bounds.Right), region.Bounds.Right); + Assert.Equal(Math.Floor(this.bounds.Left), region.Bounds.Left); + Assert.Equal(Math.Ceiling(this.bounds.Right), region.Bounds.Right); - pathMock.Verify(x => x.Bounds); + this.pathMock.Verify(x => x.Bounds); } [Fact] public void ShapeRegionFromPathMaxIntersectionsProxyToShape() { - ShapeRegion region = new ShapeRegion(pathMock.Object); + ShapeRegion region = new ShapeRegion(this.pathMock.Object); int i = region.MaxIntersections; - pathMock.Verify(x => x.MaxIntersections); + this.pathMock.Verify(x => x.MaxIntersections); } [Fact] public void ShapeRegionFromPathScanYProxyToShape() { int yToScan = 10; - ShapeRegion region = new ShapeRegion(pathMock.Object); - - pathMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((s, e, b, o) => { - Assert.Equal(yToScan, s.Y); - Assert.Equal(yToScan, e.Y); - Assert.True(s.X < bounds.Left); - Assert.True(e.X > bounds.Right); - }).Returns(0); + ShapeRegion region = new ShapeRegion(this.pathMock.Object); + + this.pathMock + .Setup( + x => x.FindIntersections( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())).Callback( + (s, e, b, o) => + { + Assert.Equal(yToScan, s.Y); + Assert.Equal(yToScan, e.Y); + Assert.True(s.X < this.bounds.Left); + Assert.True(e.X > this.bounds.Right); + }).Returns(0); int i = region.Scan(yToScan, new float[0], 0); - pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + this.pathMock.Verify( + x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + Times.Once); } [Fact] public void ShapeRegionFromShapeScanYProxyToShape() { int yToScan = 10; - ShapeRegion region = new ShapeRegion(pathMock.Object); - - pathMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((s, e, b, o) => { - Assert.Equal(yToScan, s.Y); - Assert.Equal(yToScan, e.Y); - Assert.True(s.X < bounds.Left); - Assert.True(e.X > bounds.Right); - }).Returns(0); + ShapeRegion region = new ShapeRegion(this.pathMock.Object); + + this.pathMock + .Setup( + x => x.FindIntersections( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())).Callback( + (s, e, b, o) => + { + Assert.Equal(yToScan, s.Y); + Assert.Equal(yToScan, e.Y); + Assert.True(s.X < this.bounds.Left); + Assert.True(e.X > this.bounds.Right); + }).Returns(0); int i = region.Scan(yToScan, new float[0], 0); - pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + this.pathMock.Verify( + x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + Times.Once); } [Fact] public void ShapeRegionFromShapeConvertsBoundsProxyToShape() { - ShapeRegion region = new ShapeRegion(pathMock.Object); + ShapeRegion region = new ShapeRegion(this.pathMock.Object); - Assert.Equal(Math.Floor(bounds.Left), region.Bounds.Left); - Assert.Equal(Math.Ceiling(bounds.Right), region.Bounds.Right); + Assert.Equal(Math.Floor(this.bounds.Left), region.Bounds.Left); + Assert.Equal(Math.Ceiling(this.bounds.Right), region.Bounds.Right); - pathMock.Verify(x => x.Bounds); + this.pathMock.Verify(x => x.Bounds); } [Fact] public void ShapeRegionFromShapeMaxIntersectionsProxyToShape() { - ShapeRegion region = new ShapeRegion(pathMock.Object); + ShapeRegion region = new ShapeRegion(this.pathMock.Object); int i = region.MaxIntersections; - pathMock.Verify(x => x.MaxIntersections); + this.pathMock.Verify(x => x.MaxIntersections); } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs index 07e75acf4..70badd34c 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs @@ -9,71 +9,56 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Drawing { - public class SolidBezierTests : FileTestBase + [GroupOutput("Drawing")] + public class SolidBezierTests { - [Fact] - public void ImageShouldBeOverlayedByFilledPolygon() + [Theory] + [WithBlankImages(500, 500, PixelTypes.Rgba32)] + public void FilledBezier(TestImageProvider provider) + where TPixel : struct, IPixel { - string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledBezier"); - SixLabors.Primitives.PointF[] simplePath = new SixLabors.Primitives.PointF[] { + Primitives.PointF[] simplePath = { new Vector2(10, 400), new Vector2(30, 10), new Vector2(240, 30), new Vector2(300, 400) }; - using (Image image = new Image(500, 500)) - { - image.Mutate(x => x - .BackgroundColor(Rgba32.Blue) - .Fill(Rgba32.HotPink, new Polygon(new CubicBezierLineSegment(simplePath)))); - image.Save($"{path}/Simple.png"); - using (PixelAccessor sourcePixels = image.Lock()) - { - Assert.Equal(Rgba32.HotPink, sourcePixels[150, 300]); + TPixel blue = NamedColors.Blue; + TPixel hotPink = NamedColors.HotPink; - //curve points should not be never be set - Assert.Equal(Rgba32.Blue, sourcePixels[240, 30]); - - // inside shape should not be empty - Assert.Equal(Rgba32.HotPink, sourcePixels[200, 250]); - } + using (Image image = provider.GetImage()) + { + + image.Mutate(x => x + .BackgroundColor(blue) + .Fill(hotPink, new Polygon(new CubicBezierLineSegment(simplePath)))); + image.DebugSave(provider); + image.CompareToReferenceOutput(provider); } } - [Fact] - public void ImageShouldBeOverlayedByFilledPolygonOpacity() + [Theory] + [WithBlankImages(500, 500, PixelTypes.Rgba32)] + public void OverlayByFilledPolygonOpacity(TestImageProvider provider) + where TPixel : struct, IPixel { - string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledBezier"); - SixLabors.Primitives.PointF[] simplePath = new SixLabors.Primitives.PointF[] { + Primitives.PointF[] simplePath = { new Vector2(10, 400), new Vector2(30, 10), new Vector2(240, 30), new Vector2(300, 400) }; + Rgba32 color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); - using (Image image = new Image(500, 500)) + using (var image = provider.GetImage() as Image) { image.Mutate(x => x .BackgroundColor(Rgba32.Blue) .Fill(color, new Polygon(new CubicBezierLineSegment(simplePath)))); - image.Save($"{path}/Opacity.png"); - - //shift background color towards forground color by the opacity amount - Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); - - using (PixelAccessor sourcePixels = image.Lock()) - { - //top of curve - Assert.Equal(mergedColor, sourcePixels[138, 116]); - - //curve points should not be never be set - Assert.Equal(Rgba32.Blue, sourcePixels[240, 30]); - - // inside shape should not be empty - Assert.Equal(mergedColor, sourcePixels[200, 250]); - } + image.DebugSave(provider); + image.CompareToReferenceOutput(provider); } } } diff --git a/tests/ImageSharp.Tests/FakeImageOperationsProvider.cs b/tests/ImageSharp.Tests/FakeImageOperationsProvider.cs index f750bfcfa..c2ed7238d 100644 --- a/tests/ImageSharp.Tests/FakeImageOperationsProvider.cs +++ b/tests/ImageSharp.Tests/FakeImageOperationsProvider.cs @@ -5,6 +5,9 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.Primitives; @@ -84,6 +87,9 @@ namespace SixLabors.ImageSharp.Tests }); return this; } + + public MemoryManager MemoryManager => this.source.GetConfiguration().MemoryManager; + public struct AppliedOpperation { public Rectangle? Rectangle { get; set; } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index 362df2be6..3a5fbe838 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -15,8 +15,6 @@ namespace SixLabors.ImageSharp.Tests public class BmpEncoderTests : FileTestBase { - private const PixelTypes PixelTypesToTest = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24; - public static readonly TheoryData BitsPerPixel = new TheoryData { @@ -50,9 +48,7 @@ namespace SixLabors.ImageSharp.Tests { TestBmpEncoderCore(provider, bitsPerPixel); } - - private static void TestBmpEncoderCore(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) where TPixel : struct, IPixel { diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 97128e2c9..22a811fee 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -14,8 +14,6 @@ namespace SixLabors.ImageSharp.Tests { using System; - - public class GeneralFormatTests : FileTestBase { [Theory] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs index 191cfec73..e50d84852 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { Block8x8F block = CreateRandomFloatBlock(0, 100); - using (var buffer = new Buffer2D(20, 20)) + using (var buffer = Configuration.Default.MemoryManager.Allocate2D(20, 20)) { BufferArea area = buffer.GetArea(5, 10, 8, 8); block.CopyTo(area); @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var start = new Point(50, 50); - using (var buffer = new Buffer2D(100, 100)) + using (var buffer = Configuration.Default.MemoryManager.Allocate2D(100, 100)) { BufferArea area = buffer.GetArea(start.X, start.Y, 8 * horizontalFactor, 8 * verticalFactor); block.CopyTo(area, horizontalFactor, verticalFactor); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs similarity index 51% rename from tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs rename to tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs index 887e9d7e9..193e26fcb 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs @@ -1,19 +1,17 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - - - // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg { + using System; using System.Numerics; - using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; + using SixLabors.ImageSharp.Formats.Jpeg.Common; + using SixLabors.ImageSharp.Helpers; using SixLabors.ImageSharp.PixelFormats; + using SixLabors.Primitives; using Xunit; - public class JpegUtilsTests + public class GenericBlock8x8Tests { public static Image CreateTestImage() where TPixel : struct, IPixel @@ -25,10 +23,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { for (int j = 0; j < 10; j++) { - var v = new Vector4(i / 10f, j / 10f, 0, 1); - + var rgba = new Rgba32((byte)(i+1), (byte)(j+1), (byte)200, (byte)255); var color = default(TPixel); - color.PackFromVector4(v); + color.PackFromRgba32(rgba); pixels[i, j] = color; } @@ -39,40 +36,38 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [Theory] - [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgba32| PixelTypes.Rgba32 | PixelTypes.Argb32)] - public void CopyStretchedRGBTo_FromOrigo(TestImageProvider provider) + [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32 /* | PixelTypes.Rgba32 | PixelTypes.Argb32*/)] + public void LoadAndStretchCorners_FromOrigo(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image src = provider.GetImage()) - using (Image dest = new Image(8,8)) - using (PixelArea area = new PixelArea(8, 8, ComponentOrder.Xyz)) - using (PixelAccessor s = src.Lock()) - using (PixelAccessor d = dest.Lock()) + using (Image s = provider.GetImage()) { - s.CopyRGBBytesStretchedTo(area, 0, 0); - d.CopyFrom(area, 0, 0); + var d = default(GenericBlock8x8); + d.LoadAndStretchEdges(s.Frames.RootFrame, 0, 0); + + 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.Rgba32| PixelTypes.Rgba32 | PixelTypes.Argb32)] - public void CopyStretchedRGBTo_WithOffset(TestImageProvider provider) + [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32)] + public unsafe void LoadAndStretchCorners_WithOffset(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image src = provider.GetImage()) - using (PixelArea area = new PixelArea(8, 8, ComponentOrder.Xyz)) - using (Image dest = new Image(8, 8)) - using (PixelAccessor s = src.Lock()) - using (PixelAccessor d = dest.Lock()) + using (Image s = provider.GetImage()) { - s.CopyRGBBytesStretchedTo(area, 7, 6); - d.CopyFrom(area, 0, 0); + var d = default(GenericBlock8x8); + d.LoadAndStretchEdges(s.Frames.RootFrame, 6, 7); Assert.Equal(s[6, 7], d[0, 0]); Assert.Equal(s[6, 8], d[0, 1]); @@ -102,5 +97,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg 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)); + } + + Rgb24 expected00 = new Rgb24(0, 0, 0); + Rgb24 expected07 = new Rgb24(7, 14, 21); + Rgb24 expected11 = new Rgb24(9, 18, 27); + Rgb24 expected77 = new Rgb24(63, 126, 189); + Rgb24 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]); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index f141905ef..bf6b1f4ab 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -289,7 +289,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } // no need to dispose when buffer is not array owner - buffers[i] = new Buffer2D(values, values.Length, 1); + buffers[i] = new Buffer2D(new BasicArrayBuffer(values), values.Length, 1); } return new JpegColorConverter.ComponentValues(buffers, 0); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index cb1987aef..9d04cf354 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -118,8 +118,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } public const string DecodeBaselineJpegOutputName = "DecodeBaselineJpeg"; - - + [Theory] [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes, false)] [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes, true)] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs index df6d1ef1b..423673ef9 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs @@ -55,8 +55,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { string imageFile = provider.SourceFileOrDescription; using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile)) - using (var pp = new JpegImagePostProcessor(decoder)) - using (var imageFrame = new ImageFrame(decoder.ImageWidth, decoder.ImageHeight)) + using (var pp = new JpegImagePostProcessor(Configuration.Default.MemoryManager, decoder)) + using (var imageFrame = new ImageFrame(Configuration.Default.MemoryManager, decoder.ImageWidth, decoder.ImageHeight)) { pp.DoPostProcessorStep(imageFrame); @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { string imageFile = provider.SourceFileOrDescription; using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile)) - using (var pp = new JpegImagePostProcessor(decoder)) + using (var pp = new JpegImagePostProcessor(Configuration.Default.MemoryManager, decoder)) using (var image = new Image(decoder.ImageWidth, decoder.ImageHeight)) { pp.PostProcess(image.Frames.RootFrame); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs index 26ec454f9..0276e1708 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; - using Xunit.Abstractions; // ReSharper disable InconsistentNaming diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 6e68c43f2..4508c6863 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.Output.WriteLine($"Component{i}: {diff}"); averageDifference += diff.average; totalDifference += diff.total; - tolerance += libJpegComponent.SpectralBlocks.Length; + tolerance += libJpegComponent.SpectralBlocks.Buffer.Span.Length; } averageDifference /= componentCount; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs index 40b41b9cb..9e287eb2d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils this.HeightInBlocks = heightInBlocks; this.WidthInBlocks = widthInBlocks; this.Index = index; - this.SpectralBlocks = new Buffer2D(this.WidthInBlocks, this.HeightInBlocks); + this.SpectralBlocks = Configuration.Default.MemoryManager.Allocate2D(this.WidthInBlocks, this.HeightInBlocks); } public Size Size => new Size(this.WidthInBlocks, this.HeightInBlocks); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs index ef9a73d12..e18323f84 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs @@ -6,7 +6,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Jpeg.Common; - using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; using Xunit.Abstractions; @@ -132,7 +131,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// /// Original: https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L239 - /// Applyies IDCT transformation on "s" copying transformed values to "d", using temporal block "temp" + /// Applyies IDCT transformation on "s" copying transformed values to "d", using temporary block "temp" /// /// /// diff --git a/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs b/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs index afae9cae8..1f2137070 100644 --- a/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Tests { ArgumentException ex = Assert.Throws(() => { - this.collection.AddFrame(new ImageFrame(1, 1)); + this.collection.AddFrame(new ImageFrame(Configuration.Default.MemoryManager, 1, 1)); }); Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Tests ArgumentException ex = Assert.Throws(() => { - this.collection.InsertFrame(1, new ImageFrame(1, 1)); + this.collection.InsertFrame(1, new ImageFrame(Configuration.Default.MemoryManager, 1, 1)); }); Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); @@ -103,8 +103,8 @@ namespace SixLabors.ImageSharp.Tests ArgumentException ex = Assert.Throws(() => { var collection = new ImageFrameCollection(this.image, new[] { - new ImageFrame(10,10), - new ImageFrame(1,1), + new ImageFrame(Configuration.Default.MemoryManager,10,10), + new ImageFrame(Configuration.Default.MemoryManager,1,1), }); }); @@ -115,7 +115,7 @@ namespace SixLabors.ImageSharp.Tests public void RemoveAtFrame_ThrowIfRemovingLastFrame() { var collection = new ImageFrameCollection(this.image, new[] { - new ImageFrame(10,10) + new ImageFrame(Configuration.Default.MemoryManager,10,10) }); InvalidOperationException ex = Assert.Throws(() => @@ -130,8 +130,8 @@ namespace SixLabors.ImageSharp.Tests { var collection = new ImageFrameCollection(this.image, new[] { - new ImageFrame(10,10), - new ImageFrame(10,10), + new ImageFrame(Configuration.Default.MemoryManager,10,10), + new ImageFrame(Configuration.Default.MemoryManager,10,10), }); collection.RemoveFrame(0); @@ -142,8 +142,8 @@ namespace SixLabors.ImageSharp.Tests public void RootFrameIsFrameAtIndexZero() { var collection = new ImageFrameCollection(this.image, new[] { - new ImageFrame(10,10), - new ImageFrame(10,10), + new ImageFrame(Configuration.Default.MemoryManager,10,10), + new ImageFrame(Configuration.Default.MemoryManager,10,10), }); Assert.Equal(collection.RootFrame, collection[0]); @@ -153,8 +153,8 @@ namespace SixLabors.ImageSharp.Tests public void ConstructorPopulatesFrames() { var collection = new ImageFrameCollection(this.image, new[] { - new ImageFrame(10,10), - new ImageFrame(10,10), + new ImageFrame(Configuration.Default.MemoryManager,10,10), + new ImageFrame(Configuration.Default.MemoryManager,10,10), }); Assert.Equal(2, collection.Count); @@ -164,8 +164,8 @@ namespace SixLabors.ImageSharp.Tests public void DisposeClearsCollection() { var collection = new ImageFrameCollection(this.image, new[] { - new ImageFrame(10,10), - new ImageFrame(10,10), + new ImageFrame(Configuration.Default.MemoryManager,10,10), + new ImageFrame(Configuration.Default.MemoryManager,10,10), }); collection.Dispose(); @@ -177,8 +177,8 @@ namespace SixLabors.ImageSharp.Tests public void Dispose_DisposesAllInnerFrames() { var collection = new ImageFrameCollection(this.image, new[] { - new ImageFrame(10,10), - new ImageFrame(10,10), + new ImageFrame(Configuration.Default.MemoryManager,10,10), + new ImageFrame(Configuration.Default.MemoryManager,10,10), }); IPixelSource[] framesSnapShot = collection.OfType>().ToArray(); @@ -198,7 +198,7 @@ namespace SixLabors.ImageSharp.Tests { using (Image img = provider.GetImage()) { - img.Frames.AddFrame(new ImageFrame(10, 10));// add a frame anyway + img.Frames.AddFrame(new ImageFrame(Configuration.Default.MemoryManager,10, 10));// add a frame anyway using (Image cloned = img.Frames.CloneFrame(0)) { Assert.Equal(2, img.Frames.Count); @@ -216,7 +216,7 @@ namespace SixLabors.ImageSharp.Tests { var sourcePixelData = img.GetPixelSpan().ToArray(); - img.Frames.AddFrame(new ImageFrame(10, 10)); + img.Frames.AddFrame(new ImageFrame(Configuration.Default.MemoryManager,10, 10)); using (Image cloned = img.Frames.ExportFrame(0)) { Assert.Equal(1, img.Frames.Count); @@ -244,7 +244,7 @@ namespace SixLabors.ImageSharp.Tests public void AddFrame_clones_sourceFrame() { var pixelData = this.image.Frames.RootFrame.GetPixelSpan().ToArray(); - var otherFRame = new ImageFrame(10, 10); + var otherFRame = new ImageFrame(Configuration.Default.MemoryManager,10, 10); var addedFrame = this.image.Frames.AddFrame(otherFRame); addedFrame.ComparePixelBufferTo(otherFRame.GetPixelSpan()); Assert.NotEqual(otherFRame, addedFrame); @@ -254,7 +254,7 @@ namespace SixLabors.ImageSharp.Tests public void InsertFrame_clones_sourceFrame() { var pixelData = this.image.Frames.RootFrame.GetPixelSpan().ToArray(); - var otherFRame = new ImageFrame(10, 10); + var otherFRame = new ImageFrame(Configuration.Default.MemoryManager,10, 10); var addedFrame = this.image.Frames.InsertFrame(0, otherFRame); addedFrame.ComparePixelBufferTo(otherFRame.GetPixelSpan()); Assert.NotEqual(otherFRame, addedFrame); @@ -308,7 +308,7 @@ namespace SixLabors.ImageSharp.Tests this.image.Frames.CreateFrame(); } - var frame = new ImageFrame(10, 10); + var frame = new ImageFrame(Configuration.Default.MemoryManager,10, 10); Assert.False(this.image.Frames.Contains(frame)); } diff --git a/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs b/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs deleted file mode 100644 index 36f262832..000000000 --- a/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; -using Xunit; - -namespace SixLabors.ImageSharp.Tests -{ - /// - /// Tests the class. - /// - public class PixelAccessorTests - { - public static Image CreateTestImage() - where TPixel : struct, IPixel - { - var image = new Image(10, 10); - using (PixelAccessor pixels = image.Lock()) - { - for (int i = 0; i < 10; i++) - { - for (int j = 0; j < 10; j++) - { - var v = new Vector4(i, j, 0, 1); - v /= 10; - - var color = default(TPixel); - color.PackFromVector4(v); - - pixels[i, j] = color; - } - } - } - return image; - } - - [Theory] - [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All, ComponentOrder.Xyz)] - [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All, ComponentOrder.Zyx)] - [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All, ComponentOrder.Xyzw)] - [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All, ComponentOrder.Zyxw)] - internal void CopyTo_Then_CopyFrom_OnFullImageRect(TestImageProvider provider, ComponentOrder order) - where TPixel : struct, IPixel - { - using (Image src = provider.GetImage()) - { - using (Image dest = new Image(src.Width, src.Height)) - { - using (PixelArea area = new PixelArea(src.Width, src.Height, order)) - { - using (PixelAccessor srcPixels = src.Lock()) - { - srcPixels.CopyTo(area, 0, 0); - } - - using (PixelAccessor destPixels = dest.Lock()) - { - destPixels.CopyFrom(area, 0, 0); - } - } - - Assert.True(src.IsEquivalentTo(dest, false)); - } - } - } - - [Theory] - [WithBlankImages(16, 16, PixelTypes.All, ComponentOrder.Xyz)] - [WithBlankImages(16, 16, PixelTypes.All, ComponentOrder.Zyx)] - [WithBlankImages(16, 16, PixelTypes.All, ComponentOrder.Xyzw)] - // [WithBlankImages(16, 16, PixelTypes.All, ComponentOrder.Zyxw)] TODO: This fails sometimes on Travis. Investigate - internal void CopyToThenCopyFromWithOffset(TestImageProvider provider, ComponentOrder order) - where TPixel : struct, IPixel - { - using (Image destImage = new Image(8, 8)) - { - using (Image srcImage = provider.GetImage()) - { - srcImage.Mutate(x => x.Fill(NamedColors.Red, new Rectangle(4, 4, 8, 8))); - using (PixelAccessor srcPixels = srcImage.Lock()) - { - using (PixelArea area = new PixelArea(8, 8, order)) - { - srcPixels.CopyTo(area, 4, 4); - - using (PixelAccessor destPixels = destImage.Lock()) - { - destPixels.CopyFrom(area, 0, 0); - } - } - } - } - - provider.Utility.SourceFileOrDescription = order.ToString(); - provider.Utility.SaveTestOutputFile(destImage, "bmp"); - - using (Image expectedImage = new Image(8, 8)) - { - expectedImage.Mutate(x => x.Fill(NamedColors.Red)); - Assert.True(destImage.IsEquivalentTo(expectedImage)); - } - } - } - - - [Fact] - public void CopyFromZYX() - { - using (Image image = new Image(1, 1)) - { - CopyFromZYXImpl(image); - } - } - - [Fact] - public void CopyFromZYXW() - { - using (Image image = new Image(1, 1)) - { - CopyFromZYXWImpl(image); - } - } - - [Fact] - public void CopyToZYX() - { - using (Image image = new Image(1, 1)) - { - CopyToZYXImpl(image); - } - } - - [Fact] - public void CopyToZYXW() - { - using (Image image = new Image(1, 1)) - { - CopyToZYXWImpl(image); - } - } - - private static void CopyFromZYXImpl(Image image) - where TPixel : struct, IPixel - { - using (PixelAccessor pixels = image.Lock()) - { - byte red = 1; - byte green = 2; - byte blue = 3; - byte alpha = 255; - - using (PixelArea row = new PixelArea(1, ComponentOrder.Zyx)) - { - row.Bytes[0] = blue; - row.Bytes[1] = green; - row.Bytes[2] = red; - - pixels.CopyFrom(row, 0); - - Rgba32 color = (Rgba32)(object)pixels[0, 0]; - Assert.Equal(red, color.R); - Assert.Equal(green, color.G); - Assert.Equal(blue, color.B); - Assert.Equal(alpha, color.A); - } - } - } - - private static void CopyFromZYXWImpl(Image image) - where TPixel : struct, IPixel - { - using (PixelAccessor pixels = image.Lock()) - { - byte red = 1; - byte green = 2; - byte blue = 3; - byte alpha = 4; - - using (PixelArea row = new PixelArea(1, ComponentOrder.Zyxw)) - { - row.Bytes[0] = blue; - row.Bytes[1] = green; - row.Bytes[2] = red; - row.Bytes[3] = alpha; - - pixels.CopyFrom(row, 0); - - Rgba32 color = (Rgba32)(object)pixels[0, 0]; - Assert.Equal(red, color.R); - Assert.Equal(green, color.G); - Assert.Equal(blue, color.B); - Assert.Equal(alpha, color.A); - } - } - } - - private static void CopyToZYXImpl(Image image) - where TPixel : struct, IPixel - { - using (PixelAccessor pixels = image.Lock()) - { - byte red = 1; - byte green = 2; - byte blue = 3; - - using (PixelArea row = new PixelArea(1, ComponentOrder.Zyx)) - { - pixels[0, 0] = (TPixel)(object)new Rgba32(red, green, blue); - - pixels.CopyTo(row, 0); - - Assert.Equal(blue, row.Bytes[0]); - Assert.Equal(green, row.Bytes[1]); - Assert.Equal(red, row.Bytes[2]); - } - } - } - - private static void CopyToZYXWImpl(Image image) - where TPixel : struct, IPixel - { - using (PixelAccessor pixels = image.Lock()) - { - byte red = 1; - byte green = 2; - byte blue = 3; - byte alpha = 4; - - using (PixelArea row = new PixelArea(1, ComponentOrder.Zyxw)) - { - pixels[0, 0] = (TPixel)(object)new Rgba32(red, green, blue, alpha); - - pixels.CopyTo(row, 0); - - Assert.Equal(blue, row.Bytes[0]); - Assert.Equal(green, row.Bytes[1]); - Assert.Equal(red, row.Bytes[2]); - Assert.Equal(alpha, row.Bytes[3]); - } - } - } - } -} diff --git a/tests/ImageSharp.Tests/Memory/ArrayPoolMemoryManagerTests.cs b/tests/ImageSharp.Tests/Memory/ArrayPoolMemoryManagerTests.cs new file mode 100644 index 000000000..a199bb319 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/ArrayPoolMemoryManagerTests.cs @@ -0,0 +1,198 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Memory +{ + using System; + using System.Linq; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + + using SixLabors.ImageSharp.Memory; + + using Xunit; + + public class ArrayPoolMemoryManagerTests + { + private const int MaxPooledBufferSizeInBytes = 2048; + + private const int PoolSelectorThresholdInBytes = MaxPooledBufferSizeInBytes / 2; + + private MemoryManager MemoryManager { get; set; } = new ArrayPoolMemoryManager(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes); + + /// + /// Rent a buffer -> return it -> re-rent -> verify if it's span points to the previous location + /// + private bool CheckIsRentingPooledBuffer(int length) + where T : struct + { + IBuffer buffer = this.MemoryManager.Allocate(length); + ref T ptrToPrevPosition0 = ref buffer.DangerousGetPinnableReference(); + buffer.Dispose(); + + buffer = this.MemoryManager.Allocate(length); + bool sameBuffers = Unsafe.AreSame(ref ptrToPrevPosition0, ref buffer.DangerousGetPinnableReference()); + buffer.Dispose(); + + return sameBuffers; + } + + public class BufferTests : BufferTestSuite + { + public BufferTests() + : base(new ArrayPoolMemoryManager(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes)) + { + } + } + + public class Constructor + { + [Fact] + public void WhenBothParametersPassedByUser() + { + var mgr = new ArrayPoolMemoryManager(1111, 666); + Assert.Equal(1111, mgr.MaxPoolSizeInBytes); + Assert.Equal(666, mgr.PoolSelectorThresholdInBytes); + } + + [Fact] + public void WhenPassedOnly_MaxPooledBufferSizeInBytes_SmallerThresholdValueIsAutoCalculated() + { + var mgr = new ArrayPoolMemoryManager(5000); + Assert.Equal(5000, mgr.MaxPoolSizeInBytes); + Assert.True(mgr.PoolSelectorThresholdInBytes < mgr.MaxPoolSizeInBytes); + } + + [Fact] + public void When_PoolSelectorThresholdInBytes_IsGreaterThan_MaxPooledBufferSizeInBytes_ExceptionIsThrown() + { + Assert.ThrowsAny(() => { new ArrayPoolMemoryManager(100, 200); }); + } + } + + [StructLayout(LayoutKind.Explicit, Size = MaxPooledBufferSizeInBytes / 5)] + struct LargeStruct + { + } + + [Theory] + [InlineData(32)] + [InlineData(512)] + [InlineData(MaxPooledBufferSizeInBytes - 1)] + public void SmallBuffersArePooled_OfByte(int size) + { + Assert.True(this.CheckIsRentingPooledBuffer(size)); + } + + + [Theory] + [InlineData(128 * 1024 * 1024)] + [InlineData(MaxPooledBufferSizeInBytes + 1)] + public void LargeBuffersAreNotPooled_OfByte(int size) + { + Assert.False(this.CheckIsRentingPooledBuffer(size)); + } + + [Fact] + public unsafe void SmallBuffersArePooled_OfBigValueType() + { + int count = MaxPooledBufferSizeInBytes / sizeof(LargeStruct) - 1; + + Assert.True(this.CheckIsRentingPooledBuffer(count)); + } + + [Fact] + public unsafe void LaregeBuffersAreNotPooled_OfBigValueType() + { + int count = MaxPooledBufferSizeInBytes / sizeof(LargeStruct) + 1; + + Assert.False(this.CheckIsRentingPooledBuffer(count)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CleaningRequests_AreControlledByAllocationParameter_Clean(bool clean) + { + using (IBuffer firstAlloc = this.MemoryManager.Allocate(42)) + { + firstAlloc.Span.Fill(666); + } + + using (IBuffer secondAlloc = this.MemoryManager.Allocate(42, clean)) + { + int expected = clean ? 0 : 666; + Assert.Equal(expected, secondAlloc.Span[0]); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ReleaseRetainedResources_ReplacesInnerArrayPool(bool keepBufferAlive) + { + IBuffer buffer = this.MemoryManager.Allocate(32); + ref int ptrToPrev0 = ref buffer.Span.DangerousGetPinnableReference(); + + if (!keepBufferAlive) + { + buffer.Dispose(); + } + + this.MemoryManager.ReleaseRetainedResources(); + + buffer = this.MemoryManager.Allocate(32); + + Assert.False(Unsafe.AreSame(ref ptrToPrev0, ref buffer.DangerousGetPinnableReference())); + } + + [Fact] + public void ReleaseRetainedResources_DisposingPreviouslyAllocatedBuffer_IsAllowed() + { + IBuffer buffer = this.MemoryManager.Allocate(32); + this.MemoryManager.ReleaseRetainedResources(); + buffer.Dispose(); + } + + [Fact] + public void AllocationOverLargeArrayThreshold_UsesDifferentPool() + { + int arrayLengthThreshold = PoolSelectorThresholdInBytes / sizeof(int); + + IBuffer small = this.MemoryManager.Allocate(arrayLengthThreshold - 1); + ref int ptr2Small = ref small.DangerousGetPinnableReference(); + small.Dispose(); + + IBuffer large = this.MemoryManager.Allocate(arrayLengthThreshold + 1); + + Assert.False(Unsafe.AreSame(ref ptr2Small, ref large.DangerousGetPinnableReference())); + } + + [Fact] + public void CreateWithAggressivePooling() + { + this.MemoryManager = ArrayPoolMemoryManager.CreateWithAggressivePooling(); + + Assert.True(this.CheckIsRentingPooledBuffer(4096 * 4096)); + } + + [Fact] + public void CreateDefault() + { + this.MemoryManager = ArrayPoolMemoryManager.CreateDefault(); + + Assert.False(this.CheckIsRentingPooledBuffer(2 * 4096 * 4096)); + Assert.True(this.CheckIsRentingPooledBuffer(2048 * 2048)); + } + + [Fact] + public void CreateWithModeratePooling() + { + this.MemoryManager = ArrayPoolMemoryManager.CreateWithModeratePooling(); + + Assert.False(this.CheckIsRentingPooledBuffer(2048 * 2048)); + Assert.True(this.CheckIsRentingPooledBuffer(1024 * 16)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index d662a1b3e..565e06572 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -8,6 +8,7 @@ namespace SixLabors.ImageSharp.Tests.Memory using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Tests.Common; + using SixLabors.Primitives; using Xunit; @@ -16,56 +17,64 @@ namespace SixLabors.ImageSharp.Tests.Memory // ReSharper disable once ClassNeverInstantiated.Local private class Assert : Xunit.Assert { - public static void SpanPointsTo(Span span, Buffer buffer, int bufferOffset = 0) + public static void SpanPointsTo(Span span, IBuffer buffer, int bufferOffset = 0) where T : struct { ref T actual = ref span.DangerousGetPinnableReference(); - ref T expected = ref Unsafe.Add(ref buffer[0], bufferOffset); + ref T expected = ref Unsafe.Add(ref buffer.DangerousGetPinnableReference(), bufferOffset); Assert.True(Unsafe.AreSame(ref expected, ref actual), "span does not point to the expected position"); } } - [Theory] - [InlineData(7, 42)] - [InlineData(1025, 17)] - public void Construct(int width, int height) + private MemoryManager MemoryManager { get; } = new MockMemoryManager(); + + private class MockMemoryManager : MemoryManager { - using (Buffer2D buffer = new Buffer2D(width, height)) + internal override IBuffer Allocate(int length, bool clear) { - Assert.Equal(width, buffer.Width); - Assert.Equal(height, buffer.Height); - Assert.Equal(width * height, buffer.Length); + T[] array = new T[length + 42]; + + if (!clear) + { + Span data = array.AsSpan().NonPortableCast(); + for (int i = 0; i < data.Length; i++) + { + data[i] = 42; + } + } + + return new BasicArrayBuffer(array, length); + } + + internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, bool clear) + { + throw new NotImplementedException(); } } [Theory] [InlineData(7, 42)] [InlineData(1025, 17)] - public void Construct_FromExternalArray(int width, int height) + public void Construct(int width, int height) { - TestStructs.Foo[] array = new TestStructs.Foo[width * height + 10]; - using (Buffer2D buffer = new Buffer2D(array, width, height)) + using (Buffer2D buffer = this.MemoryManager.Allocate2D(width, height)) { Assert.Equal(width, buffer.Width); Assert.Equal(height, buffer.Height); - Assert.Equal(width * height, buffer.Length); + Assert.Equal(width * height, buffer.Buffer.Length()); } } - [Fact] public void CreateClean() { - for (int i = 0; i < 100; i++) + using (Buffer2D buffer = this.MemoryManager.Allocate2D(42, 42, true)) { - using (Buffer2D buffer = Buffer2D.CreateClean(42, 42)) + Span span = buffer.Span; + for (int j = 0; j < span.Length; j++) { - for (int j = 0; j < buffer.Length; j++) - { - Assert.Equal(0, buffer.Array[j]); - buffer.Array[j] = 666; - } + Assert.Equal(0, span[j]); } } } @@ -76,13 +85,13 @@ namespace SixLabors.ImageSharp.Tests.Memory [InlineData(17, 42, 41)] public void GetRowSpanY(int width, int height, int y) { - using (Buffer2D buffer = new Buffer2D(width, height)) + using (Buffer2D buffer = this.MemoryManager.Allocate2D(width, height)) { Span span = buffer.GetRowSpan(y); // Assert.Equal(width * y, span.Start); Assert.Equal(width, span.Length); - Assert.SpanPointsTo(span, buffer, width * y); + Assert.SpanPointsTo(span, buffer.Buffer, width * y); } } @@ -92,13 +101,13 @@ namespace SixLabors.ImageSharp.Tests.Memory [InlineData(17, 42, 0, 41)] public void GetRowSpanXY(int width, int height, int x, int y) { - using (Buffer2D buffer = new Buffer2D(width, height)) + using (Buffer2D buffer = this.MemoryManager.Allocate2D(width, height)) { Span span = buffer.GetRowSpan(x, y); // Assert.Equal(width * y + x, span.Start); Assert.Equal(width - x, span.Length); - Assert.SpanPointsTo(span, buffer, width * y + x); + Assert.SpanPointsTo(span, buffer.Buffer, width * y + x); } } @@ -108,16 +117,33 @@ namespace SixLabors.ImageSharp.Tests.Memory [InlineData(99, 88, 98, 87)] public void Indexer(int width, int height, int x, int y) { - using (Buffer2D buffer = new Buffer2D(width, height)) + using (Buffer2D buffer = this.MemoryManager.Allocate2D(width, height)) { - TestStructs.Foo[] array = buffer.Array; + Span span = buffer.Buffer.Span; ref TestStructs.Foo actual = ref buffer[x, y]; - ref TestStructs.Foo expected = ref array[y * width + x]; + ref TestStructs.Foo expected = ref span[y * width + x]; Assert.True(Unsafe.AreSame(ref expected, ref actual)); } } + + [Fact] + public void SwapContents() + { + using (Buffer2D a = this.MemoryManager.Allocate2D(10, 5)) + using (Buffer2D b = this.MemoryManager.Allocate2D(3, 7)) + { + IBuffer aa = a.Buffer; + IBuffer bb = b.Buffer; + + Buffer2D.SwapContents(a, b); + + Assert.Equal(bb, a.Buffer); + Assert.Equal(new Size(3, 7), a.Size()); + Assert.Equal(new Size(10, 5), b.Size()); + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs index 026b69498..db7367d97 100644 --- a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs +++ b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Memory [Fact] public void Construct() { - using (var buffer = new Buffer2D(10, 20)) + using (var buffer = Configuration.Default.MemoryManager.Allocate2D(10, 20)) { var rectangle = new Rectangle(3,2, 5, 6); var area = new BufferArea(buffer, rectangle); @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Memory private static Buffer2D CreateTestBuffer(int w, int h) { - var buffer = new Buffer2D(w, h); + var buffer = Configuration.Default.MemoryManager.Allocate2D(w, h); for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) @@ -104,11 +104,44 @@ namespace SixLabors.ImageSharp.Tests.Memory { BufferArea area0 = buffer.GetArea(6, 8, 10, 10); - ref int r = ref area0.GetReferenceToOrigo(); + ref int r = ref area0.GetReferenceToOrigin(); int expected = buffer[6, 8]; Assert.Equal(expected, r); } } + + [Fact] + public void Clear_FullArea() + { + using (Buffer2D buffer = CreateTestBuffer(22, 13)) + { + buffer.GetArea().Clear(); + Span fullSpan = buffer.Span; + Assert.True(fullSpan.SequenceEqual(new int[fullSpan.Length])); + } + } + + [Fact] + public void Clear_SubArea() + { + using (Buffer2D buffer = CreateTestBuffer(20, 30)) + { + BufferArea area = buffer.GetArea(5, 5, 10, 10); + area.Clear(); + + Assert.NotEqual(0, buffer[4, 4]); + Assert.NotEqual(0, buffer[15, 15]); + + Assert.Equal(0, buffer[5, 5]); + Assert.Equal(0, buffer[14, 14]); + + for (int y = area.Rectangle.Y; y < area.Rectangle.Bottom; y++) + { + Span span = buffer.GetRowSpan(y).Slice(area.Rectangle.X, area.Width); + Assert.True(span.SequenceEqual(new int[area.Width])); + } + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/BufferTestSuite.cs b/tests/ImageSharp.Tests/Memory/BufferTestSuite.cs new file mode 100644 index 000000000..50477cb5c --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/BufferTestSuite.cs @@ -0,0 +1,285 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; +using Xunit; +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Tests.Memory +{ + + + /// + /// Inherit this class to test an implementation (provided by ). + /// + public abstract class BufferTestSuite + { + protected BufferTestSuite(MemoryManager memoryManager) + { + this.MemoryManager = memoryManager; + } + + protected MemoryManager MemoryManager { get; } + + public struct CustomStruct : IEquatable + { + public long A; + + public byte B; + + public float C; + + public CustomStruct(long a, byte b, float c) + { + this.A = a; + this.B = b; + this.C = c; + } + + public bool Equals(CustomStruct other) + { + return this.A == other.A && this.B == other.B && this.C.Equals(other.C); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is CustomStruct && this.Equals((CustomStruct)obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = this.A.GetHashCode(); + hashCode = (hashCode * 397) ^ this.B.GetHashCode(); + hashCode = (hashCode * 397) ^ this.C.GetHashCode(); + return hashCode; + } + } + } + + public static readonly TheoryData LenthValues = new TheoryData { 0, 1, 7, 1023, 1024 }; + + [Theory] + [MemberData(nameof(LenthValues))] + public void HasCorrectLength_byte(int desiredLength) + { + this.TestHasCorrectLength(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void HasCorrectLength_float(int desiredLength) + { + this.TestHasCorrectLength(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void HasCorrectLength_CustomStruct(int desiredLength) + { + this.TestHasCorrectLength(desiredLength); + } + + private void TestHasCorrectLength(int desiredLength) + where T : struct + { + using (IBuffer buffer = this.MemoryManager.Allocate(desiredLength)) + { + Assert.Equal(desiredLength, buffer.Span.Length); + } + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void CanAllocateCleanBuffer_byte(int desiredLength) + { + this.TestCanAllocateCleanBuffer(desiredLength, false); + this.TestCanAllocateCleanBuffer(desiredLength, true); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void CanAllocateCleanBuffer_double(int desiredLength) + { + this.TestCanAllocateCleanBuffer(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void CanAllocateCleanBuffer_CustomStruct(int desiredLength) + { + this.TestCanAllocateCleanBuffer(desiredLength); + } + + private IBuffer Allocate(int desiredLength, bool clean, bool managedByteBuffer) + where T : struct + { + if (managedByteBuffer) + { + if (!(this.MemoryManager.AllocateManagedByteBuffer(desiredLength, clean) is IBuffer buffer)) + { + throw new InvalidOperationException("typeof(T) != typeof(byte)"); + } + + return buffer; + } + + return this.MemoryManager.Allocate(desiredLength, clean); + } + + private void TestCanAllocateCleanBuffer(int desiredLength, bool testManagedByteBuffer = false) + where T : struct, IEquatable + { + ReadOnlySpan expected = new T[desiredLength]; + + for (int i = 0; i < 10; i++) + { + using (IBuffer buffer = this.Allocate(desiredLength, true, testManagedByteBuffer)) + { + Assert.True(buffer.Span.SequenceEqual(expected)); + } + } + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void SpanPropertyIsAlwaysTheSame_int(int desiredLength) + { + this.TestSpanPropertyIsAlwaysTheSame(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void SpanPropertyIsAlwaysTheSame_byte(int desiredLength) + { + this.TestSpanPropertyIsAlwaysTheSame(desiredLength, false); + this.TestSpanPropertyIsAlwaysTheSame(desiredLength, true); + } + + private void TestSpanPropertyIsAlwaysTheSame(int desiredLength, bool testManagedByteBuffer = false) + where T : struct + { + using (IBuffer buffer = this.Allocate(desiredLength, false, testManagedByteBuffer)) + { + ref T a = ref buffer.Span.DangerousGetPinnableReference(); + ref T b = ref buffer.Span.DangerousGetPinnableReference(); + ref T c = ref buffer.Span.DangerousGetPinnableReference(); + + Assert.True(Unsafe.AreSame(ref a, ref b)); + Assert.True(Unsafe.AreSame(ref b, ref c)); + } + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void WriteAndReadElements_float(int desiredLength) + { + this.TestWriteAndReadElements(desiredLength, x => x * 1.2f); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void WriteAndReadElements_byte(int desiredLength) + { + this.TestWriteAndReadElements(desiredLength, x => (byte)(x+1), false); + this.TestWriteAndReadElements(desiredLength, x => (byte)(x + 1), true); + } + + private void TestWriteAndReadElements(int desiredLength, Func getExpectedValue, bool testManagedByteBuffer = false) + where T : struct + { + using (IBuffer buffer = this.Allocate(desiredLength, false, testManagedByteBuffer)) + { + T[] expectedVals = new T[buffer.Length()]; + + for (int i = 0; i < buffer.Length(); i++) + { + Span span = buffer.Span; + expectedVals[i] = getExpectedValue(i); + span[i] = expectedVals[i]; + } + + for (int i = 0; i < buffer.Length(); i++) + { + Span span = buffer.Span; + Assert.Equal(expectedVals[i], span[i]); + } + } + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void IndexingSpan_WhenOutOfRange_Throws_byte(int desiredLength) + { + this.TestIndexOutOfRangeShouldThrow(desiredLength, false); + this.TestIndexOutOfRangeShouldThrow(desiredLength, true); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void IndexingSpan_WhenOutOfRange_Throws_long(int desiredLength) + { + this.TestIndexOutOfRangeShouldThrow(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void IndexingSpan_WhenOutOfRange_Throws_CustomStruct(int desiredLength) + { + this.TestIndexOutOfRangeShouldThrow(desiredLength); + } + + private T TestIndexOutOfRangeShouldThrow(int desiredLength, bool testManagedByteBuffer = false) + where T : struct, IEquatable + { + var dummy = default(T); + + using (IBuffer buffer = this.Allocate(desiredLength, false, testManagedByteBuffer)) + { + Assert.ThrowsAny( + () => + { + Span span = buffer.Span; + dummy = span[desiredLength]; + }); + + Assert.ThrowsAny( + () => + { + Span span = buffer.Span; + dummy = span[desiredLength + 1]; + }); + + Assert.ThrowsAny( + () => + { + Span span = buffer.Span; + dummy = span[desiredLength + 42]; + }); + } + + return dummy; + } + + [Theory] + [InlineData(1)] + [InlineData(7)] + [InlineData(1024)] + [InlineData(6666)] + public void ManagedByteBuffer_ArrayIsCorrect(int desiredLength) + { + using (IManagedByteBuffer buffer = this.MemoryManager.AllocateManagedByteBuffer(desiredLength)) + { + ref byte array0 = ref buffer.Array[0]; + ref byte span0 = ref buffer.DangerousGetPinnableReference(); + + Assert.True(Unsafe.AreSame(ref span0, ref array0)); + Assert.True(buffer.Array.Length >= buffer.Span.Length); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/BufferTests.cs b/tests/ImageSharp.Tests/Memory/BufferTests.cs deleted file mode 100644 index e1efeb24e..000000000 --- a/tests/ImageSharp.Tests/Memory/BufferTests.cs +++ /dev/null @@ -1,251 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Tests.Memory -{ - using System; - using System.Runtime.CompilerServices; - - using SixLabors.ImageSharp.Memory; - - using Xunit; - - public unsafe class BufferTests - { - // ReSharper disable once ClassNeverInstantiated.Local - private class Assert : Xunit.Assert - { - public static void SpanPointsTo(Span span, Buffer buffer, int bufferOffset = 0) - where T : struct - { - ref T actual = ref span.DangerousGetPinnableReference(); - ref T expected = ref Unsafe.Add(ref buffer[0], bufferOffset); - - Assert.True(Unsafe.AreSame(ref expected, ref actual), "span does not point to the expected position"); - } - - public static void Equal(void* expected, void* actual) - { - Assert.Equal((IntPtr)expected, (IntPtr)actual); - } - } - - [Theory] - [InlineData(42)] - [InlineData(1111)] - public void ConstructWithOwnArray(int count) - { - using (Buffer buffer = new Buffer(count)) - { - Assert.False(buffer.IsDisposedOrLostArrayOwnership); - Assert.NotNull(buffer.Array); - Assert.Equal(count, buffer.Length); - Assert.True(buffer.Array.Length >= count); - } - } - - [Theory] - [InlineData(42)] - [InlineData(1111)] - public void ConstructWithExistingArray(int count) - { - TestStructs.Foo[] array = new TestStructs.Foo[count]; - using (Buffer buffer = new Buffer(array)) - { - Assert.False(buffer.IsDisposedOrLostArrayOwnership); - Assert.Equal(array, buffer.Array); - Assert.Equal(count, buffer.Length); - } - } - - [Fact] - public void Clear() - { - TestStructs.Foo[] a = { new TestStructs.Foo() { A = 1, B = 2 }, new TestStructs.Foo() { A = 3, B = 4 } }; - using (Buffer buffer = new Buffer(a)) - { - buffer.Clear(); - - Assert.Equal(default(TestStructs.Foo), a[0]); - Assert.Equal(default(TestStructs.Foo), a[1]); - } - } - - [Fact] - public void CreateClean() - { - for (int i = 0; i < 100; i++) - { - using (Buffer buffer = Buffer.CreateClean(42)) - { - for (int j = 0; j < buffer.Length; j++) - { - Assert.Equal(0, buffer.Array[j]); - buffer.Array[j] = 666; - } - } - } - } - - public class Indexer - { - public static readonly TheoryData IndexerData = - new TheoryData() - { - { 10, 0 }, - { 16, 3 }, - { 10, 9 } - }; - - [Theory] - [MemberData(nameof(IndexerData))] - public void Read(int length, int index) - { - TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length); - - using (Buffer buffer = new Buffer(a)) - { - TestStructs.Foo element = buffer[index]; - - Assert.Equal(a[index], element); - } - } - - [Theory] - [MemberData(nameof(IndexerData))] - public void Write(int length, int index) - { - TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length); - - using (Buffer buffer = new Buffer(a)) - { - buffer[index] = new TestStructs.Foo(666, 666); - - Assert.Equal(new TestStructs.Foo(666, 666), a[index]); - } - } - } - - [Fact] - public void Dispose() - { - Buffer buffer = new Buffer(42); - buffer.Dispose(); - - Assert.True(buffer.IsDisposedOrLostArrayOwnership); - } - - [Theory] - [InlineData(7)] - [InlineData(123)] - public void CastToSpan(int bufferLength) - { - using (Buffer buffer = new Buffer(bufferLength)) - { - Span span = buffer; - - //Assert.Equal(buffer.Array, span.ToArray()); - //Assert.Equal(0, span.Start); - Assert.SpanPointsTo(span, buffer); - Assert.Equal(span.Length, bufferLength); - } - } - - [Fact] - public void Span() - { - using (Buffer buffer = new Buffer(42)) - { - Span span = buffer.Span; - - // Assert.Equal(buffer.Array, span.ToArray()); - // Assert.Equal(0, span.Start); - Assert.SpanPointsTo(span, buffer); - Assert.Equal(42, span.Length); - } - } - - public class Slice - { - - [Theory] - [InlineData(7, 2)] - [InlineData(123, 17)] - public void WithStartOnly(int bufferLength, int start) - { - using (Buffer buffer = new Buffer(bufferLength)) - { - Span span = buffer.Slice(start); - - Assert.SpanPointsTo(span, buffer, start); - Assert.Equal(span.Length, bufferLength - start); - } - } - - [Theory] - [InlineData(7, 2, 5)] - [InlineData(123, 17, 42)] - public void WithStartAndLength(int bufferLength, int start, int spanLength) - { - using (Buffer buffer = new Buffer(bufferLength)) - { - Span span = buffer.Slice(start, spanLength); - - Assert.SpanPointsTo(span, buffer, start); - Assert.Equal(span.Length, spanLength); - } - } - } - - [Fact] - public void UnPinAndTakeArrayOwnership() - { - TestStructs.Foo[] data = null; - using (Buffer buffer = new Buffer(42)) - { - data = buffer.TakeArrayOwnership(); - Assert.True(buffer.IsDisposedOrLostArrayOwnership); - } - - Assert.NotNull(data); - Assert.True(data.Length >= 42); - } - - public class Pin - { - [Fact] - public void ReturnsPinnedPointerToTheBeginningOfArray() - { - using (Buffer buffer = new Buffer(42)) - { - TestStructs.Foo* actual = (TestStructs.Foo*)buffer.Pin(); - fixed (TestStructs.Foo* expected = buffer.Array) - { - Assert.Equal(expected, actual); - } - } - } - - [Fact] - public void SecondCallReturnsTheSamePointer() - { - using (Buffer buffer = new Buffer(42)) - { - IntPtr ptr1 = buffer.Pin(); - IntPtr ptr2 = buffer.Pin(); - - Assert.Equal(ptr1, ptr2); - } - } - - [Fact] - public void WhenCalledOnDisposedBuffer_ThrowsInvalidOperationException() - { - Buffer buffer = new Buffer(42); - buffer.Dispose(); - - Assert.Throws(() => buffer.Pin()); - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/PixelDataPoolTests.cs b/tests/ImageSharp.Tests/Memory/PixelDataPoolTests.cs deleted file mode 100644 index caba9a464..000000000 --- a/tests/ImageSharp.Tests/Memory/PixelDataPoolTests.cs +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Linq; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; -using Xunit; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Memory -{ - using System; - - /// - /// Tests the class. - /// - public class PixelDataPoolTests - { - private const int MaxPooledBufferSizeInBytes = PixelDataPool.MaxPooledBufferSizeInBytes; - - readonly object monitor = new object(); - - [Theory] - [InlineData(1)] - [InlineData(1024)] - public void PixelDataPoolRentsMinimumSize(int size) - { - Rgba32[] pixels = PixelDataPool.Rent(size); - - Assert.True(pixels.Length >= size); - } - - [Fact] - public void PixelDataPoolDoesNotThrowWhenReturningNonPooled() - { - Rgba32[] pixels = new Rgba32[1024]; - - PixelDataPool.Return(pixels); - - Assert.True(pixels.Length >= 1024); - } - - /// - /// Rent 'n' buffers -> return all -> re-rent, verify if there is at least one in common. - /// - private bool CheckIsPooled(int n, int count) - where T : struct - { - lock (this.monitor) - { - T[][] original = new T[n][]; - - for (int i = 0; i < n; i++) - { - original[i] = PixelDataPool.Rent(count); - } - - for (int i = 0; i < n; i++) - { - PixelDataPool.Return(original[i]); - } - - T[][] verification = new T[n][]; - - for (int i = 0; i < n; i++) - { - verification[i] = PixelDataPool.Rent(count); - } - - return original.Intersect(verification).Any(); - } - } - - [Theory] - [InlineData(32)] - [InlineData(512)] - [InlineData(MaxPooledBufferSizeInBytes-1)] - public void SmallBuffersArePooled(int size) - { - Assert.True(this.CheckIsPooled(5, size)); - } - - [Theory] - [InlineData(128 * 1024 * 1024)] - [InlineData(MaxPooledBufferSizeInBytes+1)] - public void LargeBuffersAreNotPooled_OfByte(int size) - { - Assert.False(this.CheckIsPooled(2, size)); - } - - [StructLayout(LayoutKind.Explicit, Size = 512)] - struct TestStruct - { - } - - [Fact] - public unsafe void LaregeBuffersAreNotPooled_OfBigValueType() - { - const int mb128 = 128 * 1024 * 1024; - int count = mb128 / sizeof(TestStruct); - - Assert.False(this.CheckIsPooled(2, count)); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/SimpleGcMemoryManagerTests.cs b/tests/ImageSharp.Tests/Memory/SimpleGcMemoryManagerTests.cs new file mode 100644 index 000000000..0d1c2beb8 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/SimpleGcMemoryManagerTests.cs @@ -0,0 +1,15 @@ +namespace SixLabors.ImageSharp.Tests.Memory +{ + using SixLabors.ImageSharp.Memory; + + public class SimpleGcMemoryManagerTests + { + public class BufferTests : BufferTestSuite + { + public BufferTests() + : base(new SimpleGcMemoryManager()) + { + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/SpanUtilityTests.cs b/tests/ImageSharp.Tests/Memory/SpanUtilityTests.cs index 395c32546..049c4c6ba 100644 --- a/tests/ImageSharp.Tests/Memory/SpanUtilityTests.cs +++ b/tests/ImageSharp.Tests/Memory/SpanUtilityTests.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +// ReSharper disable InconsistentNaming +// ReSharper disable AccessToStaticMemberViaDerivedType namespace SixLabors.ImageSharp.Tests.Memory { using System; @@ -30,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Memory { float[] stuff = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; - Span span = new Span(stuff); + var span = new Span(stuff); ref Vector v = ref span.FetchVector(); @@ -39,199 +41,8 @@ namespace SixLabors.ImageSharp.Tests.Memory Assert.Equal(2, v[2]); Assert.Equal(3, v[3]); } - - [Fact] - public void AsBytes() - { - TestStructs.Foo[] fooz = { new TestStructs.Foo(1, 2), new TestStructs.Foo(3, 4), new TestStructs.Foo(5, 6) }; - - using (Buffer colorBuf = new Buffer(fooz)) - { - Span orig = colorBuf.Slice(1); - Span asBytes = orig.AsBytes(); - - // Assert.Equal(asBytes.Start, sizeof(Foo)); - Assert.Equal(orig.Length * Unsafe.SizeOf(), asBytes.Length); - Assert.SameRefs(ref orig.DangerousGetPinnableReference(), ref asBytes.DangerousGetPinnableReference()); - } - } - - public class Construct - { - [Fact] - public void Basic() - { - TestStructs.Foo[] array = TestStructs.Foo.CreateArray(3); - - // Act: - Span span = new Span(array); - - // Assert: - Assert.Equal(array, span.ToArray()); - Assert.Equal(3, span.Length); - Assert.SameRefs(ref array[0], ref span.DangerousGetPinnableReference()); - } - - [Fact] - public void WithStart() - { - TestStructs.Foo[] array = TestStructs.Foo.CreateArray(4); - int start = 2; - - // Act: - Span span = new Span(array, start); - - // Assert: - Assert.SameRefs(ref array[start], ref span.DangerousGetPinnableReference()); - Assert.Equal(array.Length - start, span.Length); - } - - [Fact] - public void WithStartAndLength() - { - TestStructs.Foo[] array = TestStructs.Foo.CreateArray(10); - int start = 2; - int length = 3; - // Act: - Span span = new Span(array, start, length); - - // Assert: - Assert.SameRefs(ref array[start], ref span.DangerousGetPinnableReference()); - Assert.Equal(length, span.Length); - } - } - - public class Slice - { - [Fact] - public void StartOnly() - { - TestStructs.Foo[] array = TestStructs.Foo.CreateArray(5); - int start0 = 2; - int start1 = 2; - int totalOffset = start0 + start1; - - Span span = new Span(array, start0); - - // Act: - span = span.Slice(start1); - - // Assert: - Assert.SameRefs(ref array[totalOffset], ref span.DangerousGetPinnableReference()); - Assert.Equal(array.Length - totalOffset, span.Length); - } - - [Fact] - public void StartAndLength() - { - TestStructs.Foo[] array = TestStructs.Foo.CreateArray(10); - int start0 = 2; - int start1 = 2; - int totalOffset = start0 + start1; - int sliceLength = 3; - - Span span = new Span(array, start0); - - // Act: - span = span.Slice(start1, sliceLength); - - // Assert: - Assert.SameRefs(ref array[totalOffset], ref span.DangerousGetPinnableReference()); - Assert.Equal(sliceLength, span.Length); - } - } - - //[Theory] - //[InlineData(4)] - //[InlineData(1500)] - //public void Clear(int count) - //{ - // Foo[] array = Foo.CreateArray(count + 42); - - // int offset = 2; - // Span ap = new Span(array, offset); - - // // Act: - // ap.Clear(count); - - // Assert.NotEqual(default(Foo), array[offset - 1]); - // Assert.Equal(default(Foo), array[offset]); - // Assert.Equal(default(Foo), array[offset + count - 1]); - // Assert.NotEqual(default(Foo), array[offset + count]); - //} - - public class Indexer - { - public static readonly TheoryData IndexerData = - new TheoryData() - { - { 10, 0, 0 }, - { 10, 2, 0 }, - { 16, 0, 3 }, - { 16, 2, 3 }, - { 10, 0, 9 }, - { 10, 1, 8 } - }; - - [Theory] - [MemberData(nameof(IndexerData))] - public void Read(int length, int start, int index) - { - TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length); - Span span = new Span(a, start); - - TestStructs.Foo element = span[index]; - - Assert.Equal(a[start + index], element); - } - - [Theory] - [MemberData(nameof(IndexerData))] - public void Write(int length, int start, int index) - { - TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length); - Span span = new Span(a, start); - - span[index] = new TestStructs.Foo(666, 666); - - Assert.Equal(new TestStructs.Foo(666, 666), a[start + index]); - } - - [Theory] - [InlineData(10, 0, 0, 5)] - [InlineData(10, 1, 1, 5)] - [InlineData(10, 1, 1, 6)] - [InlineData(10, 1, 1, 7)] - public void AsBytes_Read(int length, int start, int index, int byteOffset) - { - TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length); - Span span = new Span(a, start); - - Span bytes = span.AsBytes(); - - byte actual = bytes[index * Unsafe.SizeOf() + byteOffset]; - - ref byte baseRef = ref Unsafe.As(ref a[0]); - byte expected = Unsafe.Add(ref baseRef, (start + index) * Unsafe.SizeOf() + byteOffset); - - Assert.Equal(expected, actual); - } - } - - [Theory] - [InlineData(0, 4)] - [InlineData(2, 4)] - [InlineData(3, 4)] - public void DangerousGetPinnableReference(int start, int length) - { - TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length); - Span span = new Span(a, start); - ref TestStructs.Foo r = ref span.DangerousGetPinnableReference(); - - Assert.True(Unsafe.AreSame(ref a[start], ref r)); - } - - public class Copy + + public class SpanHelper_Copy { private static void AssertNotDefault(T[] data, int idx) where T : struct @@ -267,8 +78,8 @@ namespace SixLabors.ImageSharp.Tests.Memory TestStructs.Foo[] source = TestStructs.Foo.CreateArray(count + 2); TestStructs.Foo[] dest = new TestStructs.Foo[count + 5]; - Span apSource = new Span(source, 1); - Span apDest = new Span(dest, 1); + var apSource = new Span(source, 1); + var apDest = new Span(dest, 1); SpanHelper.Copy(apSource, apDest, count - 1); @@ -290,8 +101,8 @@ namespace SixLabors.ImageSharp.Tests.Memory TestStructs.AlignedFoo[] source = TestStructs.AlignedFoo.CreateArray(count + 2); TestStructs.AlignedFoo[] dest = new TestStructs.AlignedFoo[count + 5]; - Span apSource = new Span(source, 1); - Span apDest = new Span(dest, 1); + var apSource = new Span(source, 1); + var apDest = new Span(dest, 1); SpanHelper.Copy(apSource, apDest, count - 1); @@ -313,8 +124,8 @@ namespace SixLabors.ImageSharp.Tests.Memory int[] source = CreateTestInts(count + 2); int[] dest = new int[count + 5]; - Span apSource = new Span(source, 1); - Span apDest = new Span(dest, 1); + var apSource = new Span(source, 1); + var apDest = new Span(dest, 1); SpanHelper.Copy(apSource, apDest, count - 1); @@ -337,8 +148,8 @@ namespace SixLabors.ImageSharp.Tests.Memory TestStructs.Foo[] source = TestStructs.Foo.CreateArray(count + 2); byte[] dest = new byte[destCount + sizeof(TestStructs.Foo) * 2]; - Span apSource = new Span(source, 1); - Span apDest = new Span(dest, sizeof(TestStructs.Foo)); + var apSource = new Span(source, 1); + var apDest = new Span(dest, sizeof(TestStructs.Foo)); SpanHelper.Copy(apSource.AsBytes(), apDest, (count - 1) * sizeof(TestStructs.Foo)); @@ -360,8 +171,8 @@ namespace SixLabors.ImageSharp.Tests.Memory TestStructs.AlignedFoo[] source = TestStructs.AlignedFoo.CreateArray(count + 2); byte[] dest = new byte[destCount + sizeof(TestStructs.AlignedFoo) * 2]; - Span apSource = new Span(source, 1); - Span apDest = new Span(dest, sizeof(TestStructs.AlignedFoo)); + var apSource = new Span(source, 1); + var apDest = new Span(dest, sizeof(TestStructs.AlignedFoo)); SpanHelper.Copy(apSource.AsBytes(), apDest, (count - 1) * sizeof(TestStructs.AlignedFoo)); @@ -383,8 +194,8 @@ namespace SixLabors.ImageSharp.Tests.Memory int[] source = CreateTestInts(count + 2); byte[] dest = new byte[destCount + sizeof(int) + 1]; - Span apSource = new Span(source); - Span apDest = new Span(dest); + var apSource = new Span(source); + var apDest = new Span(dest); SpanHelper.Copy(apSource.AsBytes(), apDest, count * sizeof(int)); @@ -404,8 +215,8 @@ namespace SixLabors.ImageSharp.Tests.Memory byte[] source = CreateTestBytes(srcCount); TestStructs.Foo[] dest = new TestStructs.Foo[count + 2]; - Span apSource = new Span(source); - Span apDest = new Span(dest); + var apSource = new Span(source); + var apDest = new Span(dest); SpanHelper.Copy(apSource, apDest.AsBytes(), count * sizeof(TestStructs.Foo)); @@ -417,26 +228,7 @@ namespace SixLabors.ImageSharp.Tests.Memory Assert.True((bool)ElementsAreEqual(dest, source, count - 1)); Assert.False((bool)ElementsAreEqual(dest, source, count)); } - - [Fact] - public void Color32ToBytes() - { - Rgba32[] colors = { new Rgba32(0, 1, 2, 3), new Rgba32(4, 5, 6, 7), new Rgba32(8, 9, 10, 11), }; - - using (Buffer colorBuf = new Buffer(colors)) - using (Buffer byteBuf = new Buffer(colors.Length * 4)) - { - SpanHelper.Copy(colorBuf.Span.AsBytes(), byteBuf, colorBuf.Length * sizeof(Rgba32)); - - byte[] a = byteBuf.Array; - - for (int i = 0; i < byteBuf.Length; i++) - { - Assert.Equal((byte)i, a[i]); - } - } - } - + internal static bool ElementsAreEqual(TestStructs.Foo[] array, byte[] rawArray, int index) { fixed (TestStructs.Foo* pArray = array) diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs index b95f8fdf6..50babde69 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs @@ -12,6 +12,8 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders { + using SixLabors.ImageSharp.Memory; + public class PorterDuffFunctionsTests_TPixel { private static Span AsSpan(T value) @@ -25,6 +27,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(0.6f, 0.6f, 0.6f, 1) }, }; + private MemoryManager MemoryManager { get; } = Configuration.Default.MemoryManager; + [Theory] [MemberData(nameof(NormalBlendFunctionData))] public void NormalBlendFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) @@ -49,7 +53,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.Normal().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Normal().Blend(this.MemoryManager, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -88,7 +92,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.Multiply().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Multiply().Blend(this.MemoryManager, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -127,7 +131,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.Add().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Add().Blend(this.MemoryManager, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -166,7 +170,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.Substract().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Substract().Blend(this.MemoryManager, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -205,7 +209,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.Screen().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Screen().Blend(this.MemoryManager, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -244,7 +248,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.Darken().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Darken().Blend(this.MemoryManager, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -283,7 +287,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.Lighten().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Lighten().Blend(this.MemoryManager, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -322,7 +326,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.Overlay().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Overlay().Blend(this.MemoryManager, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -361,7 +365,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.HardLight().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.HardLight().Blend(this.MemoryManager, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.cs index 945a4f502..c0039bb37 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.cs @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats TestOperation( source, expected, - (s, d) => ImageSharp.Rgba32.PixelOperations.ToVector4SimdAligned(s, d, 64) + (s, d) => ImageSharp.Rgba32.PixelOperations.ToVector4SimdAligned(s, d.Span, 64) ); } @@ -50,14 +50,14 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats int times = 200000; int count = 1024; - using (Buffer source = new Buffer(count)) - using (Buffer dest = new Buffer(count)) + using (IBuffer source = Configuration.Default.MemoryManager.Allocate(count)) + using (IBuffer dest = Configuration.Default.MemoryManager.Allocate(count)) { this.Measure( times, () => { - PixelOperations.Instance.ToVector4(source, dest, count); + PixelOperations.Instance.ToVector4(source.Span, dest.Span, count); }); } } @@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats TestOperation( source, expected, - (s, d) => Operations.PackFromVector4(s, d, count) + (s, d) => Operations.PackFromVector4(s, d.Span, count) ); } @@ -141,7 +141,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats TestOperation( source, expected, - (s, d) => Operations.ToVector4(s, d, count) + (s, d) => Operations.ToVector4(s, d.Span, count) ); } @@ -163,7 +163,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats TestOperation( source, expected, - (s, d) => Operations.PackFromRgb24Bytes(s, d, count) + (s, d) => Operations.PackFromRgb24Bytes(s, d.Span, count) ); } @@ -187,7 +187,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats TestOperation( source, expected, - (s, d) => Operations.ToRgb24Bytes(s, d, count) + (s, d) => Operations.ToRgb24Bytes(s, d.Span, count) ); } @@ -208,7 +208,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats TestOperation( source, expected, - (s, d) => Operations.PackFromRgba32Bytes(s, d, count) + (s, d) => Operations.PackFromRgba32Bytes(s, d.Span, count) ); } @@ -233,7 +233,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats TestOperation( source, expected, - (s, d) => Operations.ToRgba32Bytes(s, d, count) + (s, d) => Operations.ToRgba32Bytes(s, d.Span, count) ); } @@ -254,7 +254,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats TestOperation( source, expected, - (s, d) => Operations.PackFromBgr24Bytes(s, d, count) + (s, d) => Operations.PackFromBgr24Bytes(s, d.Span, count) ); } @@ -278,7 +278,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats TestOperation( source, expected, - (s, d) => Operations.ToBgr24Bytes(s, d, count) + (s, d) => Operations.ToBgr24Bytes(s, d.Span, count) ); } @@ -299,7 +299,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats TestOperation( source, expected, - (s, d) => Operations.PackFromBgra32Bytes(s, d, count) + (s, d) => Operations.PackFromBgra32Bytes(s, d.Span, count) ); } @@ -324,7 +324,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats TestOperation( source, expected, - (s, d) => Operations.ToBgra32Bytes(s, d, count) + (s, d) => Operations.ToBgra32Bytes(s, d.Span, count) ); } @@ -333,25 +333,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats where TSource : struct where TDest : struct { - public Buffer SourceBuffer { get; } - public Buffer ActualDestBuffer { get; } - public Buffer ExpectedDestBuffer { get; } - - public Span Source => this.SourceBuffer; - public Span ActualDest => this.ActualDestBuffer; - + public TSource[] SourceBuffer { get; } + public IBuffer ActualDestBuffer { get; } + public TDest[] ExpectedDestBuffer { get; } + public TestBuffers(TSource[] source, TDest[] expectedDest) { - this.SourceBuffer = new Buffer(source); - this.ExpectedDestBuffer = new Buffer(expectedDest); - this.ActualDestBuffer = new Buffer(expectedDest.Length); + this.SourceBuffer = source; + this.ExpectedDestBuffer = expectedDest; + this.ActualDestBuffer = Configuration.Default.MemoryManager.Allocate(expectedDest.Length); } public void Dispose() { - this.SourceBuffer.Dispose(); this.ActualDestBuffer.Dispose(); - this.ExpectedDestBuffer.Dispose(); } private const float Tolerance = 0.0001f; @@ -362,8 +357,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats if (typeof(TDest) == typeof(Vector4)) { - Vector4[] expected = this.ExpectedDestBuffer.Array as Vector4[]; - Vector4[] actual = this.ActualDestBuffer.Array as Vector4[]; + + Span expected = this.ExpectedDestBuffer.AsSpan().NonPortableCast(); + Span actual = this.ActualDestBuffer.Span.NonPortableCast(); for (int i = 0; i < count; i++) { @@ -374,8 +370,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats } else { - TDest[] expected = this.ExpectedDestBuffer.Array; - TDest[] actual = this.ActualDestBuffer.Array; + Span expected = this.ExpectedDestBuffer.AsSpan(); + Span actual = this.ActualDestBuffer.Span; for (int i = 0; i < count; i++) { Assert.Equal(expected[i], actual[i]); @@ -387,7 +383,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats internal static void TestOperation( TSource[] source, TDest[] expected, - Action, Buffer> action) + Action> action) where TSource : struct where TDest : struct { @@ -401,7 +397,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats internal static Vector4[] CreateVector4TestData(int length) { Vector4[] result = new Vector4[length]; - Random rnd = new Random(42); // Deterministic random values + var rnd = new Random(42); // Deterministic random values for (int i = 0; i < result.Length; i++) { @@ -414,7 +410,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { TPixel[] result = new TPixel[length]; - Random rnd = new Random(42); // Deterministic random values + var rnd = new Random(42); // Deterministic random values for (int i = 0; i < result.Length; i++) { @@ -428,7 +424,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats internal static byte[] CreateByteTestData(int length) { byte[] result = new byte[length]; - Random rnd = new Random(42); // Deterministic random values + var rnd = new Random(42); // Deterministic random values for (int i = 0; i < result.Length; i++) { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs index 3508d544b..89e9a13b5 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs @@ -11,12 +11,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { public class GaussianBlurTest : FileTestBase { - public static readonly TheoryData GaussianBlurValues - = new TheoryData - { - 3, - 5 - }; + public static readonly TheoryData GaussianBlurValues = new TheoryData { 3, 5 }; [Theory] [WithFileCollection(nameof(DefaultFiles), nameof(GaussianBlurValues), DefaultPixelType)] @@ -36,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution where TPixel : struct, IPixel { using (Image source = provider.GetImage()) - using (var image = source.Clone()) + using (Image image = source.Clone()) { var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs index 608fcf10c..a9127f61d 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs @@ -11,12 +11,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects { public class OilPaintTest : FileTestBase { - public static readonly TheoryData OilPaintValues - = new TheoryData - { - { 15, 10 }, - { 6, 5 } - }; + public static readonly TheoryData OilPaintValues = new TheoryData + { + { 15, 10 }, { 6, 5 } + }; [Theory] [WithFileCollection(nameof(DefaultFiles), nameof(OilPaintValues), DefaultPixelType)] diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs index d4de4c3d2..9ca399498 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms using (Image image = provider.GetImage()) { image.Mutate(x => x.Flip(flipType)); - image.DebugSave(provider, flipType, Extensions.Bmp); + image.DebugSave(provider, flipType); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs index 98dbbadab..54a3b7418 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs @@ -11,6 +11,8 @@ using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { + using SixLabors.ImageSharp.Memory; + public class ResizeProfilingBenchmarks : MeasureFixture { public ResizeProfilingBenchmarks(ITestOutputHelper output) @@ -38,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms // [Fact] public void PrintWeightsData() { - var proc = new ResizeProcessor(KnownResamplers.Bicubic, 200, 200); + var proc = new ResizeProcessor(Configuration.Default.MemoryManager, new BicubicResampler(), 200, 200); WeightsBuffer weights = proc.PrecomputeWeights(200, 500); diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs index b5a8d1265..18fd29237 100644 --- a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs +++ b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs @@ -73,7 +73,7 @@ { Assert.True(image[0, 0].Equals(default(TPixel))); - IQuantizer quantizer = new WuQuantizer { Dither = dither }; + IQuantizer quantizer = new WuQuantizer() { Dither = dither }; foreach (ImageFrame frame in image.Frames) { diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs index a201b39bd..d1270dcfd 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs @@ -19,13 +19,14 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs int length = source.Length; Guard.MustBeSizedAtLeast(dest, length, nameof(dest)); - using (var rgbaBuffer = new Buffer(length)) + using (IBuffer rgbaBuffer = Configuration.Default.MemoryManager.Allocate(length)) { - PixelOperations.Instance.ToRgba32(source, rgbaBuffer, length); + Span rgbaSpan = rgbaBuffer.Span; + PixelOperations.Instance.ToRgba32(source, rgbaSpan, length); for (int i = 0; i < length; i++) { - ref Rgba32 s = ref rgbaBuffer[i]; + ref Rgba32 s = ref rgbaSpan[i]; ref Argb32 d = ref dest[i]; d.PackFromRgba32(s); @@ -39,13 +40,14 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs int length = source.Length; Guard.MustBeSizedAtLeast(dest, length, nameof(dest)); - using (var rgbaBuffer = new Buffer(length)) + using (IBuffer rgbaBuffer = Configuration.Default.MemoryManager.Allocate(length)) { - PixelOperations.Instance.ToRgba32(source, rgbaBuffer, length); + Span rgbaSpan = rgbaBuffer.Span; + PixelOperations.Instance.ToRgba32(source, rgbaSpan, length); for (int i = 0; i < length; i++) { - ref Rgba32 s = ref rgbaBuffer[i]; + ref Rgba32 s = ref rgbaSpan[i]; ref TPixel d = ref dest[i]; d.PackFromRgba32(s); @@ -59,13 +61,14 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs int length = source.Length; Guard.MustBeSizedAtLeast(dest, length, nameof(dest)); - using (var rgbaBuffer = new Buffer(length)) + using (IBuffer rgbBuffer = Configuration.Default.MemoryManager.Allocate(length)) { - PixelOperations.Instance.ToRgb24(source, rgbaBuffer, length); + Span rgbSpan = rgbBuffer.Span; + PixelOperations.Instance.ToRgb24(source, rgbSpan, length); for (int i = 0; i < length; i++) { - ref Rgb24 s = ref rgbaBuffer[i]; + ref Rgb24 s = ref rgbSpan[i]; ref TPixel d = ref dest[i]; var rgba = default(Rgba32); s.ToRgba32(ref rgba); @@ -96,18 +99,20 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs var image = new Image(w, h); - using (var workBuffer = new Buffer(w)) + using (IBuffer workBuffer = Configuration.Default.MemoryManager.Allocate(w)) { - var destPtr = (Argb32*)workBuffer.Pin(); - for (int y = 0; y < h; y++) + fixed (Argb32* destPtr = &workBuffer.DangerousGetPinnableReference()) { - Span row = image.Frames.RootFrame.GetPixelRowSpan(y); + for (int y = 0; y < h; y++) + { + Span row = image.Frames.RootFrame.GetPixelRowSpan(y); - byte* sourcePtr = sourcePtrBase + data.Stride * y; + byte* sourcePtr = sourcePtrBase + data.Stride * y; - Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); + Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); - FromArgb32(workBuffer, row); + FromArgb32(workBuffer.Span, row); + } } } @@ -138,18 +143,20 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs var image = new Image(w, h); - using (var workBuffer = new Buffer(w)) + using (IBuffer workBuffer = Configuration.Default.MemoryManager.Allocate(w)) { - var destPtr = (Rgb24*)workBuffer.Pin(); - for (int y = 0; y < h; y++) + fixed (Rgb24* destPtr = &workBuffer.DangerousGetPinnableReference()) { - Span row = image.Frames.RootFrame.GetPixelRowSpan(y); + for (int y = 0; y < h; y++) + { + Span row = image.Frames.RootFrame.GetPixelRowSpan(y); - byte* sourcePtr = sourcePtrBase + data.Stride * y; + byte* sourcePtr = sourcePtrBase + data.Stride * y; - Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); + Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); - FromRgb24(workBuffer, row); + FromRgb24(workBuffer.Span, row); + } } } @@ -170,17 +177,19 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs long destRowByteCount = data.Stride; long sourceRowByteCount = w * sizeof(Argb32); - using (var workBuffer = new Buffer(w)) + using (IBuffer workBuffer = image.GetConfiguration().MemoryManager.Allocate(w)) { - var sourcePtr = (Argb32*)workBuffer.Pin(); - - for (int y = 0; y < h; y++) + fixed (Argb32* sourcePtr = &workBuffer.DangerousGetPinnableReference()) { - Span row = image.Frames.RootFrame.GetPixelRowSpan(y); - ToArgb32(row, workBuffer); - byte* destPtr = destPtrBase + data.Stride * y; - Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); + for (int y = 0; y < h; y++) + { + Span row = image.Frames.RootFrame.GetPixelRowSpan(y); + ToArgb32(row, workBuffer.Span); + byte* destPtr = destPtrBase + data.Stride * y; + + Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); + } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 6f15cf439..6014e2533 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -30,10 +30,11 @@ namespace SixLabors.ImageSharp.Tests public static void MakeOpaque(this IImageProcessingContext ctx) where TPixel : struct, IPixel { + MemoryManager memoryManager = ctx.MemoryManager; ctx.Apply( img => { - using (var temp = new Buffer2D(img.Width, img.Height)) + using (Buffer2D temp = memoryManager.Allocate2D(img.Width, img.Height)) { Span tempSpan = temp.Span; foreach (ImageFrame frame in img.Frames) @@ -183,7 +184,7 @@ namespace SixLabors.ImageSharp.Tests bool appendPixelTypeToFileName = true) where TPixel : struct, IPixel { - using (Image firstFrameOnlyImage = new Image(image.Width, image.Height)) + using (var firstFrameOnlyImage = new Image(image.Width, image.Height)) using (Image referenceImage = GetReferenceOutputImage( provider, testOutputDetails, @@ -253,7 +254,7 @@ namespace SixLabors.ImageSharp.Tests testOutputDetails, appendPixelTypeToFileName); - var temporalFrameImages = new List>(); + var temporaryFrameImages = new List>(); IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(frameFiles[0]); @@ -265,14 +266,14 @@ namespace SixLabors.ImageSharp.Tests } var tempImage = Image.Load(path, decoder); - temporalFrameImages.Add(tempImage); + temporaryFrameImages.Add(tempImage); } - Image firstTemp = temporalFrameImages[0]; + Image firstTemp = temporaryFrameImages[0]; var result = new Image(firstTemp.Width, firstTemp.Height); - foreach (Image fi in temporalFrameImages) + foreach (Image fi in temporaryFrameImages) { result.Frames.AddFrame(fi.Frames.RootFrame); fi.Dispose(); @@ -405,9 +406,11 @@ namespace SixLabors.ImageSharp.Tests Span pixels = image.Frames.RootFrame.GetPixelSpan(); - for (int i = 0; i < buffer.Length; i++) + Span bufferSpan = buffer.Span; + + for (int i = 0; i < bufferSpan.Length; i++) { - float value = buffer[i] * scale; + float value = bufferSpan[i] * scale; var v = new Vector4(value, value, value, 1f); pixels[i].PackFromVector4(v); }