diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs index 2d29e23fe5..320c94c96b 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 844df0e0e9..cc22b26391 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 ca6f7630d9..d8ea435586 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 ba2fca4e4b..39afd965c8 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 658164339d..9630c707ef 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/DrawImage.cs b/src/ImageSharp.Drawing/DrawImage.cs index d55e224162..f1db72db60 100644 --- a/src/ImageSharp.Drawing/DrawImage.cs +++ b/src/ImageSharp.Drawing/DrawImage.cs @@ -18,24 +18,13 @@ namespace SixLabors.ImageSharp /// The image this method extends. /// The image to blend with the currently processing image. /// The pixel format. - /// The size to draw the blended image. /// The location to draw the blended image. /// The options. /// The . - public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, Size size, Point location, GraphicsOptions options) + public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, Point location, GraphicsOptions options) where TPixel : struct, IPixel { - if (size == default(Size)) - { - size = new Size(image.Width, image.Height); - } - - if (location == default(Point)) - { - location = Point.Empty; - } - - source.ApplyProcessor(new DrawImageProcessor(image, size, location, options)); + source.ApplyProcessor(new DrawImageProcessor(image, location, options)); return source; } @@ -45,14 +34,14 @@ namespace SixLabors.ImageSharp /// The pixel format. /// The image this method extends. /// The image to blend with the currently processing image. - /// The opacity of the image image to blend. Must be between 0 and 1. + /// The opacity of the image to blend. Must be between 0 and 1. /// The . - public static IImageProcessingContext Blend(this IImageProcessingContext source, Image image, float percent) + public static IImageProcessingContext Blend(this IImageProcessingContext source, Image image, float opacity) where TPixel : struct, IPixel { GraphicsOptions options = GraphicsOptions.Default; - options.BlendPercentage = percent; - return DrawImage(source, image, default(Size), default(Point), options); + options.BlendPercentage = opacity; + return DrawImage(source, image, Point.Empty, options); } /// @@ -62,15 +51,15 @@ namespace SixLabors.ImageSharp /// The image this method extends. /// The image to blend with the currently processing image. /// The blending mode. - /// The opacity of the image image to blend. Must be between 0 and 1. + /// The opacity of the image to blend. Must be between 0 and 1. /// The . - public static IImageProcessingContext Blend(this IImageProcessingContext source, Image image, PixelBlenderMode blender, float percent) + public static IImageProcessingContext Blend(this IImageProcessingContext source, Image image, PixelBlenderMode blender, float opacity) where TPixel : struct, IPixel { GraphicsOptions options = GraphicsOptions.Default; - options.BlendPercentage = percent; + options.BlendPercentage = opacity; options.BlenderMode = blender; - return DrawImage(source, image, default(Size), default(Point), options); + return DrawImage(source, image, Point.Empty, options); } /// @@ -79,12 +68,12 @@ namespace SixLabors.ImageSharp /// The pixel format. /// The image this method extends. /// The image to blend with the currently processing image. - /// The options, including the blending type and belnding amount. + /// The options, including the blending type and blending amount. /// The . public static IImageProcessingContext Blend(this IImageProcessingContext source, Image image, GraphicsOptions options) where TPixel : struct, IPixel { - return DrawImage(source, image, default(Size), default(Point), options); + return DrawImage(source, image, Point.Empty, options); } /// @@ -93,16 +82,15 @@ namespace SixLabors.ImageSharp /// The image this method extends. /// The image to blend with the currently processing image. /// The pixel format. - /// The opacity of the image image to blend. Must be between 0 and 1. - /// The size to draw the blended image. + /// The opacity of the image to blend. Must be between 0 and 1. /// The location to draw the blended image. /// The . - public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, float percent, Size size, Point location) + public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, float opacity, Point location) where TPixel : struct, IPixel { GraphicsOptions options = GraphicsOptions.Default; - options.BlendPercentage = percent; - return source.DrawImage(image, size, location, options); + options.BlendPercentage = opacity; + return source.DrawImage(image, location, options); } /// @@ -112,17 +100,16 @@ namespace SixLabors.ImageSharp /// The image to blend with the currently processing image. /// The pixel format. /// The type of bending to apply. - /// The opacity of the image image to blend. Must be between 0 and 1. - /// The size to draw the blended image. + /// The opacity of the image to blend. Must be between 0 and 1. /// The location to draw the blended image. /// The . - public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, PixelBlenderMode blender, float percent, Size size, Point location) + public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, PixelBlenderMode blender, float opacity, Point location) where TPixel : struct, IPixel { GraphicsOptions options = GraphicsOptions.Default; options.BlenderMode = blender; - options.BlendPercentage = percent; - return source.DrawImage(image, size, location, options); + options.BlendPercentage = opacity; + return source.DrawImage(image, location, options); } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Paths/ShapePath.cs b/src/ImageSharp.Drawing/Paths/ShapePath.cs index 61f1291c45..4c22787195 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 a96b03dd04..cc27f7fbb8 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 47763c0aaf..632b4d449d 100644 --- a/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs @@ -19,93 +19,77 @@ namespace SixLabors.ImageSharp.Drawing.Processors internal class DrawImageProcessor : ImageProcessor where TPixel : struct, IPixel { - private readonly PixelBlender blender; - /// /// Initializes a new instance of the class. /// /// The image to blend with the currently processing image. - /// The size to draw the blended image. /// The location to draw the blended image. - /// The opacity of the image to blend. Between 0 and 100. - public DrawImageProcessor(Image image, Size size, Point location, GraphicsOptions options) + /// The opacity of the image to blend. Between 0 and 1. + public DrawImageProcessor(Image image, Point location, GraphicsOptions options) { Guard.MustBeBetweenOrEqualTo(options.BlendPercentage, 0, 1, nameof(options.BlendPercentage)); + this.Image = image; - this.Size = size; - this.Alpha = options.BlendPercentage; - this.blender = PixelOperations.Instance.GetPixelBlender(options.BlenderMode); + this.Opacity = options.BlendPercentage; + this.Blender = PixelOperations.Instance.GetPixelBlender(options.BlenderMode); this.Location = location; } /// - /// Gets the image to blend. + /// Gets the image to blend /// public Image Image { get; } /// - /// Gets the alpha percentage value. + /// Gets the opacity of the image to blend /// - public float Alpha { get; } + public float Opacity { get; } /// - /// Gets the size to draw the blended image. + /// Gets the pixel blender /// - public Size Size { get; } + public PixelBlender Blender { get; } /// - /// Gets the location to draw the blended image. + /// Gets the location to draw the blended image /// public Point Location { get; } /// protected override void OnApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { - Image disposableImage = null; Image targetImage = this.Image; + PixelBlender blender = this.Blender; + int locationY = this.Location.Y; - try - { - if (targetImage.Size() != this.Size) - { - targetImage = disposableImage = this.Image.Clone(x => x.Resize(this.Size.Width, this.Size.Height)); - } + // Align start/end positions. + Rectangle bounds = targetImage.Bounds(); - // Align start/end positions. - Rectangle bounds = targetImage.Bounds(); - int minX = Math.Max(this.Location.X, sourceRectangle.X); - int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Width); - maxX = Math.Min(this.Location.X + this.Size.Width, maxX); - int targetX = minX - this.Location.X; + int minX = Math.Max(this.Location.X, sourceRectangle.X); + int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Width); + int targetX = minX - this.Location.X; - int minY = Math.Max(this.Location.Y, sourceRectangle.Y); - int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); + int minY = Math.Max(this.Location.Y, sourceRectangle.Y); + int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); - maxY = Math.Min(this.Location.Y + this.Size.Height, maxY); + int width = maxX - minX; - int width = maxX - minX; - using (var amount = new Buffer(width)) - { - for (int i = 0; i < width; i++) - { - amount[i] = this.Alpha; - } + MemoryManager memoryManager = this.Image.GetConfiguration().MemoryManager; - Parallel.For( - minY, - maxY, - configuration.ParallelOptions, - y => - { - Span background = source.GetPixelRowSpan(y).Slice(minX, width); - Span foreground = targetImage.GetPixelRowSpan(y - this.Location.Y).Slice(targetX, width); - this.blender.Blend(background, background, foreground, amount); - }); - } - } - finally + using (IBuffer amount = memoryManager.Allocate(width)) { - disposableImage?.Dispose(); + amount.Span.Fill(this.Opacity); + + Parallel.For( + minY, + maxY, + configuration.ParallelOptions, + y => + { + Span background = source.GetPixelRowSpan(y).Slice(minX, width); + Span foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width); + 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 679ca6a228..3bf18a37ba 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 b6ef4be218..076785526c 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 0acb846c50..612ced5d8d 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 58a952c406..c4954ef0d1 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 d9767d45ea..487f464d8e 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 0188bc03cf..7f46b7a847 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 b717abab1c..7a9a34ac1a 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 0000000000..da91259051 --- /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 7401035331..9a627eeb77 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 6e6feed84e..7ccc65e27e 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() { @@ -46,6 +51,9 @@ namespace SixLabors.ImageSharp return this.destination; } + /// + public Size GetCurrentSize() => this.GetCurrentBounds().Size; + /// public IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle) { @@ -70,7 +78,12 @@ namespace SixLabors.ImageSharp /// public IImageProcessingContext ApplyProcessor(IImageProcessor processor) { - return this.ApplyProcessor(processor, this.source.Bounds()); + return this.ApplyProcessor(processor, this.GetCurrentBounds()); + } + + private Rectangle GetCurrentBounds() + { + return this.destination?.Bounds() ?? this.source.Bounds(); } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index e552ac1042..9f4dba5b4f 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 366afceb5f..d80e43c63b 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 d34d170847..66c8b6c086 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 3c22518057..48c8ceb8c9 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 ccf46a17d6..b548098be3 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 41c8e944d8..13ca5f2c61 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 3284dad657..0c73efea46 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 b7bfd0fd25..35c4148964 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 39a6bee2e4..d8963a8b60 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 574967b6bf..5e8e8fa2cc 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 87c1431e02..1be637b6df 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 125ec52723..2adf3e02d0 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 0000000000..1bb37a7d32 --- /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 0000000000..d9b15b34fa --- /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 0000000000..e20e850d74 --- /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 38507d58c8..2a3817400c 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/DecoderThrowHelper.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/DecoderThrowHelper.cs index d5a9340d72..904ce00dde 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/DecoderThrowHelper.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/DecoderThrowHelper.cs @@ -18,6 +18,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder [MethodImpl(MethodImplOptions.NoInlining)] public static void ThrowExceptionForErrorCode(this OrigDecoderErrorCode errorCode) { + // REMARK: If this method throws for an image that is expected to be decodable, + // consider using the ***Unsafe variant of the parsing method that asks for ThrowExceptionForErrorCode() + // then verify the error code + implement fallback logic manually! switch (errorCode) { case OrigDecoderErrorCode.NoError: diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/InputProcessor.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/InputProcessor.cs index a7a5fcd986..88599808fc 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; } @@ -105,6 +107,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder return this.Bytes.ReadByte(this.InputStream); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public OrigDecoderErrorCode ReadByteUnsafe(out byte result) + { + this.LastErrorCode = this.Bytes.ReadByteUnsafe(this.InputStream, out result); + return this.LastErrorCode; + } + /// /// Decodes a single bit /// TODO: This method (and also the usages) could be optimized by batching! diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs index c87752b371..e83dd75a54 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 4c97d57415..dbc7bb0f7f 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 02bd451b94..923fe244eb 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 0000000000..3c95a85080 --- /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 2912a87199..ba40ef72b8 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 cdc2a91972..58513fd297 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) @@ -240,6 +235,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort // Check for the Start Of Image marker. this.InputProcessor.ReadFull(this.Temp, 0, 2); + if (this.Temp[0] != OrigJpegConstants.Markers.XFF || this.Temp[1] != OrigJpegConstants.Markers.SOI) { throw new ImageFormatException("Missing SOI marker."); @@ -252,6 +248,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort while (processBytes) { this.InputProcessor.ReadFull(this.Temp, 0, 2); + + if (this.InputProcessor.ReachedEOF) + { + // We've reached the end of the stream. + processBytes = false; + } + while (this.Temp[0] != 0xff) { // Strictly speaking, this is a format error. However, libjpeg is @@ -286,7 +289,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort { // Section B.1.1.2 says, "Any marker may optionally be preceded by any // number of fill bytes, which are bytes assigned code X'FF'". - marker = this.InputProcessor.ReadByte(); + this.InputProcessor.ReadByteUnsafe(out marker); + + if (this.InputProcessor.ReachedEOF) + { + // We've reached the end of the stream. + processBytes = false; + break; + } } // End Of Image. @@ -308,7 +318,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort // Read the 16-bit length of the segment. The value includes the 2 bytes for the // length itself, so we subtract 2 to get the number of remaining bytes. - this.InputProcessor.ReadFull(this.Temp, 0, 2); + this.InputProcessor.ReadFullUnsafe(this.Temp, 0, 2); int remaining = (this.Temp[0] << 8) + this.Temp[1] - 2; if (remaining < 0) { @@ -356,12 +366,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort return; } - // when this is a progressive image this gets called a number of times - // need to know how many times this should be called in total. this.ProcessStartOfScanMarker(remaining); - if (this.InputProcessor.ReachedEOF || !this.IsProgressive) + if (this.InputProcessor.ReachedEOF) { - // if unexpeced EOF reached or this is not a progressive image we can stop processing bytes as we now have the image data. + // If unexpected EOF reached. We can stop processing bytes as we now have the image data. processBytes = false; } @@ -395,15 +403,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort { this.InputProcessor.Skip(remaining); } - else if (marker < OrigJpegConstants.Markers.SOF0) - { - // See Table B.1 "Marker code assignments". - throw new ImageFormatException("Unknown marker"); - } - else - { - throw new ImageFormatException("Unknown marker"); - } break; } @@ -680,7 +679,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 +787,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 01ed5063ba..0000000000 --- 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 3c35e311f1..0742293c78 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 f60097dc9c..2442c39981 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 9dc8315677..3c43ba2444 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 49bdc2423e..00fa1985dd 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 034986c2cb..f16fb9a2c2 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 1000ce82c5..afe0b30072 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 e2e5d985e6..9e245ea2c6 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 211c24d208..4fa0bc281d 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 f90def5b38..2483a3ad9d 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 9346f7567e..ab6f31d49a 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 e39187e086..fbff0ae1d9 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 4abafc9e80..0c40ccf2a0 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 385d40b6ba..1ab7a83ce0 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/GraphicsOptions.cs b/src/ImageSharp/GraphicsOptions.cs index 114eaab2a6..a094abacbe 100644 --- a/src/ImageSharp/GraphicsOptions.cs +++ b/src/ImageSharp/GraphicsOptions.cs @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp // some API thought post V1. /// - /// Gets or sets a value indicating the blending percentage to apply to the drawing operation + /// Gets or sets a value indicating the blending mode to apply to the drawing operation /// public PixelBlenderMode BlenderMode { diff --git a/src/ImageSharp/IImageProcessingContext{TPixel}.cs b/src/ImageSharp/IImageProcessingContext{TPixel}.cs index 552e8d579d..0e8efde3b3 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,18 @@ 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; } + + /// + /// Gets the image dimensions at the current point in the processing pipeline. + /// + /// The + Size GetCurrentSize(); + /// /// Adds the processor to the current set of image operations to be applied. /// @@ -40,7 +53,7 @@ namespace SixLabors.ImageSharp /// /// Adds the processors to the current image /// - /// The current image or a new image depending on withere this is alloed to mutate the source image. + /// The current image or a new image depending on whether this is allowed to mutate the source image. Image Apply(); } } \ No newline at end of file diff --git a/src/ImageSharp/Image/IImage.cs b/src/ImageSharp/Image/IImage.cs index 7355dc1fec..b9e2cee616 100644 --- a/src/ImageSharp/Image/IImage.cs +++ b/src/ImageSharp/Image/IImage.cs @@ -1,12 +1,14 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; + namespace SixLabors.ImageSharp { /// /// Encapsulates the properties and methods that describe an image. /// - public interface IImage : IImageInfo + public interface IImage : IImageInfo, IDisposable { } } \ No newline at end of file diff --git a/src/ImageSharp/Image/Image.Decode.cs b/src/ImageSharp/Image/Image.Decode.cs index ef00ff15fd..72492a494b 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 e2230c4367..b9341a1b24 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 3e9bb03435..aefeacce15 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 ba475f9cf3..833a22f7c7 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/Image{TPixel}.cs b/src/ImageSharp/Image/Image{TPixel}.cs index be38b41f24..f264d8a59d 100644 --- a/src/ImageSharp/Image/Image{TPixel}.cs +++ b/src/ImageSharp/Image/Image{TPixel}.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. /// /// The pixel format. - public sealed partial class Image : IImage, IDisposable, IConfigurable + public sealed partial class Image : IImage, IConfigurable where TPixel : struct, IPixel { private Configuration configuration; diff --git a/src/ImageSharp/Image/PixelAccessor{TPixel}.cs b/src/ImageSharp/Image/PixelAccessor{TPixel}.cs index 8dcb1f7602..63e4c015c1 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 1c7256455e..0000000000 --- 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 9d30030da6..cb0539f786 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 0000000000..d4f58fb6fb --- /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 0000000000..d1424870da --- /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 0000000000..7b8c7ab326 --- /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 0000000000..30ca210ac4 --- /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 0000000000..96b69ad3bf --- /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 99b10cae7e..dc992368c9 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 b5ed3566fa..588eae483d 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 0000000000..919a6ef345 --- /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 67af23426a..0000000000 --- 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 a0f80063f8..db6bf5b389 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 0000000000..4d159ce863 --- /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 0000000000..52bdc897fc --- /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 0000000000..2060002450 --- /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 80c9c410e8..0000000000 --- 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 0000000000..f4518bbb9d --- /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 99a20516d2..d3c6cf16ce 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 9d7d73db99..eebee676fc 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 54cb09c28a..666fb38913 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 947e531578..ca79841c76 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/Opacity.cs b/src/ImageSharp/Processing/ColorMatrix/Opacity.cs index b310b4b915..b59c068905 100644 --- a/src/ImageSharp/Processing/ColorMatrix/Opacity.cs +++ b/src/ImageSharp/Processing/ColorMatrix/Opacity.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp public static partial class ImageExtensions { /// - /// Alters the alpha component of the image. + /// Multiplies the alpha component of the image. /// /// The pixel format. /// The image this method extends. @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp => source.ApplyProcessor(new OpacityProcessor(amount)); /// - /// Alters the alpha component of the image. + /// Multiplies the alpha component of the image. /// /// The pixel format. /// The image this method extends. diff --git a/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs b/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs index c96087d57e..d12cc76122 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 da00b4ddd0..22aad9ca6a 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 ee35963487..0c3552b4d2 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 cc93ccedc6..4b9f2f866c 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/CloningImageProcessor.cs b/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs index 4672b2ad45..7257bd6643 100644 --- a/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs +++ b/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs @@ -96,6 +96,7 @@ namespace SixLabors.ImageSharp.Processing /// /// This method is called before the process is applied to prepare the processor. + /// TODO: We should probably name this 'BeforeFrameApply' /// /// The source image. Cannot be null. /// The cloned/destination image. Cannot be null. @@ -108,6 +109,7 @@ namespace SixLabors.ImageSharp.Processing /// /// Applies the process to the specified portion of the specified at the specified location /// and with the specified size. + /// TODO: We should probably name this 'ApplyToFrame' /// /// The source image. Cannot be null. /// The cloned/destination image. Cannot be null. @@ -117,6 +119,7 @@ namespace SixLabors.ImageSharp.Processing /// /// This method is called after the process is applied to prepare the processor. + /// TODO: We should probably name this 'AfterFrameApply' /// /// The source image. Cannot be null. /// The cloned/destination image. Cannot be null. diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs index cf9b7be9c8..2c13ead162 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 57f434ee08..0d87aa1dc4 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 96db9a4ce0..2f369113d9 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 72e9b8f555..720b876913 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 b22a497987..c199a32c8a 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 5ea57fd27b..33c4338d58 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 5491db7efe..152d586afe 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 b02585d8fd..9ab301718c 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 7b592a6a4d..d47211f0cb 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 8595e86922..ac11e0dfb3 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs @@ -21,27 +21,6 @@ namespace SixLabors.ImageSharp.Processing.Processors internal class AffineTransformProcessor : InterpolatedTransformProcessorBase where TPixel : struct, IPixel { - private Size targetDimensions; - - /// - /// Initializes a new instance of the class. - /// - /// The transform matrix - public AffineTransformProcessor(Matrix3x2 matrix) - : this(matrix, KnownResamplers.Bicubic) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The transform matrix - /// The sampler to perform the transform operation. - public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler) - : this(matrix, sampler, Size.Empty) - { - } - /// /// Initializes a new instance of the class. /// @@ -51,10 +30,8 @@ namespace SixLabors.ImageSharp.Processing.Processors public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Size targetDimensions) : base(sampler) { - // Transforms are inverted else the output is the opposite of the expected. - Matrix3x2.Invert(matrix, out matrix); this.TransformMatrix = matrix; - this.targetDimensions = targetDimensions; + this.TargetDimensions = targetDimensions; } /// @@ -62,18 +39,17 @@ namespace SixLabors.ImageSharp.Processing.Processors /// public Matrix3x2 TransformMatrix { get; } + /// + /// Gets the target dimensions to constrain the transformed image to + /// + public Size TargetDimensions { get; } + /// protected override Image CreateDestination(Image source, Rectangle sourceRectangle) { - if (this.targetDimensions == Size.Empty) - { - // TODO: CreateDestination() should not modify the processors state! (kinda CQRS) - this.targetDimensions = this.GetTransformedDimensions(sourceRectangle.Size, this.TransformMatrix); - } - // 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); @@ -86,8 +62,8 @@ namespace SixLabors.ImageSharp.Processing.Processors Rectangle sourceRectangle, Configuration configuration) { - int height = this.targetDimensions.Height; - int width = this.targetDimensions.Width; + int height = this.TargetDimensions.Height; + int width = this.TargetDimensions.Width; Rectangle sourceBounds = source.Bounds(); var targetBounds = new Rectangle(0, 0, width, height); @@ -95,6 +71,9 @@ namespace SixLabors.ImageSharp.Processing.Processors // Since could potentially be resizing the canvas we might need to re-calculate the matrix Matrix3x2 matrix = this.GetProcessingMatrix(sourceBounds, targetBounds); + // Convert from screen to world space. + Matrix3x2.Invert(matrix, out matrix); + if (this.Sampler is NearestNeighborResampler) { Parallel.For( @@ -130,8 +109,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, @@ -204,8 +185,8 @@ namespace SixLabors.ImageSharp.Processing.Processors var vector = source[i, j].ToVector4(); // Values are first premultiplied to prevent darkening of edge pixels - Vector4 mupltiplied = vector.Premultiply(); - sum += mupltiplied * xWeight * yWeight; + Vector4 multiplied = vector.Premultiply(); + sum += multiplied * xWeight * yWeight; } } @@ -230,16 +211,5 @@ namespace SixLabors.ImageSharp.Processing.Processors { return this.TransformMatrix; } - - /// - /// Gets the bounding relative to the source for the given transformation matrix. - /// - /// The source rectangle. - /// The transformation matrix. - /// The - protected virtual Size GetTransformedDimensions(Size sourceDimensions, Matrix3x2 matrix) - { - return sourceDimensions; - } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs index c39311bc33..7f811eebc1 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Processing.Processors protected override void BeforeImageApply(Image source, Rectangle sourceRectangle) { Orientation orientation = GetExifOrientation(source); - + Size size = sourceRectangle.Size; switch (orientation) { case Orientation.TopRight: @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Processing.Processors break; case Orientation.BottomRight: - new RotateProcessor((int)RotateType.Rotate180).Apply(source, sourceRectangle); + new RotateProcessor((int)RotateType.Rotate180, size).Apply(source, sourceRectangle); break; case Orientation.BottomLeft: @@ -35,21 +35,21 @@ namespace SixLabors.ImageSharp.Processing.Processors break; case Orientation.LeftTop: - new RotateProcessor((int)RotateType.Rotate90).Apply(source, sourceRectangle); + new RotateProcessor((int)RotateType.Rotate90, size).Apply(source, sourceRectangle); new FlipProcessor(FlipType.Horizontal).Apply(source, sourceRectangle); break; case Orientation.RightTop: - new RotateProcessor((int)RotateType.Rotate90).Apply(source, sourceRectangle); + new RotateProcessor((int)RotateType.Rotate90, size).Apply(source, sourceRectangle); break; case Orientation.RightBottom: new FlipProcessor(FlipType.Vertical).Apply(source, sourceRectangle); - new RotateProcessor((int)RotateType.Rotate270).Apply(source, sourceRectangle); + new RotateProcessor((int)RotateType.Rotate270, size).Apply(source, sourceRectangle); break; case Orientation.LeftBottom: - new RotateProcessor((int)RotateType.Rotate270).Apply(source, sourceRectangle); + new RotateProcessor((int)RotateType.Rotate270, size).Apply(source, sourceRectangle); break; case Orientation.Unknown: diff --git a/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs index 34a0866615..6b8314d172 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs @@ -19,31 +19,22 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// The transform matrix /// The sampler to perform the transform operation. - protected CenteredAffineTransformProcessor(Matrix3x2 matrix, IResampler sampler) - : base(matrix, sampler) + /// The source image size + protected CenteredAffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Size sourceSize) + : base(matrix, sampler, GetTransformedDimensions(sourceSize, matrix)) { } /// protected override Matrix3x2 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle) { - var translationToTargetCenter = Matrix3x2.CreateTranslation(-destinationRectangle.Width * .5F, -destinationRectangle.Height * .5F); - var translateToSourceCenter = Matrix3x2.CreateTranslation(sourceRectangle.Width * .5F, sourceRectangle.Height * .5F); - return translationToTargetCenter * this.TransformMatrix * translateToSourceCenter; + return TransformHelpers.GetCenteredTransformMatrix(sourceRectangle, destinationRectangle, this.TransformMatrix); } - /// - protected override Size GetTransformedDimensions(Size sourceDimensions, Matrix3x2 matrix) + private static Size GetTransformedDimensions(Size sourceDimensions, Matrix3x2 matrix) { var sourceRectangle = new Rectangle(0, 0, sourceDimensions.Width, sourceDimensions.Height); - - if (!Matrix3x2.Invert(this.TransformMatrix, out Matrix3x2 sizeMatrix)) - { - // TODO: Shouldn't we throw an exception instead? - return sourceDimensions; - } - - return TransformHelpers.GetTransformedBoundingRectangle(sourceRectangle, sizeMatrix).Size; + return TransformHelpers.GetTransformedBoundingRectangle(sourceRectangle, matrix).Size; } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/CenteredProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CenteredProjectiveTransformProcessor.cs index dc2dd28ab1..081ea84610 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CenteredProjectiveTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CenteredProjectiveTransformProcessor.cs @@ -19,25 +19,22 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// The transform matrix /// The sampler to perform the transform operation. - protected CenteredProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler) - : base(matrix, sampler) + /// The source image size + protected CenteredProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler, Size sourceSize) + : base(matrix, sampler, GetTransformedDimensions(sourceSize, matrix)) { } /// protected override Matrix4x4 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle) { - var translationToTargetCenter = Matrix4x4.CreateTranslation(-destinationRectangle.Width * .5F, -destinationRectangle.Height * .5F, 0); - var translateToSourceCenter = Matrix4x4.CreateTranslation(sourceRectangle.Width * .5F, sourceRectangle.Height * .5F, 0); - return translationToTargetCenter * this.TransformMatrix * translateToSourceCenter; + return TransformHelpers.GetCenteredTransformMatrix(sourceRectangle, destinationRectangle, this.TransformMatrix); } - /// - protected override Rectangle GetTransformedBoundingRectangle(Rectangle sourceRectangle, Matrix4x4 matrix) + private static Size GetTransformedDimensions(Size sourceDimensions, Matrix4x4 matrix) { - return Matrix4x4.Invert(this.TransformMatrix, out Matrix4x4 sizeMatrix) - ? TransformHelpers.GetTransformedBoundingRectangle(sourceRectangle, sizeMatrix) - : sourceRectangle; + var sourceRectangle = new Rectangle(0, 0, sourceDimensions.Width, sourceDimensions.Height); + return TransformHelpers.GetTransformedBoundingRectangle(sourceRectangle, matrix).Size; } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs index 00547d0147..c04014ae35 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs @@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; +// TODO: Convert this into a cloning processor inheriting TransformProcessor once Anton's memory PR is merged namespace SixLabors.ImageSharp.Processing.Processors { /// @@ -44,7 +45,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 +58,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 de60177f2f..9b8b785b58 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 7e547727e6..e16cd9ee84 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs @@ -22,59 +22,35 @@ namespace SixLabors.ImageSharp.Processing.Processors internal class ProjectiveTransformProcessor : InterpolatedTransformProcessorBase where TPixel : struct, IPixel { - // TODO: We should use a Size instead! (See AffineTransformProcessor) - private Rectangle targetRectangle; - - /// - /// Initializes a new instance of the class. - /// - /// The transform matrix - public ProjectiveTransformProcessor(Matrix4x4 matrix) - : this(matrix, KnownResamplers.Bicubic) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The transform matrix - /// The sampler to perform the transform operation. - public ProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler) - : this(matrix, sampler, Rectangle.Empty) - { - } - /// /// Initializes a new instance of the class. /// /// The transform matrix /// The sampler to perform the transform operation. - /// The rectangle to constrain the transformed image to. - public ProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler, Rectangle rectangle) + /// The target dimensions to constrain the transformed image to. + public ProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler, Size targetDimensions) : base(sampler) { - // Transforms are inverted else the output is the opposite of the expected. - Matrix4x4.Invert(matrix, out matrix); this.TransformMatrix = matrix; - this.targetRectangle = rectangle; + this.TargetDimensions = targetDimensions; } /// - /// Gets the matrix used to supply the non-affine transform + /// Gets the matrix used to supply the projective transform /// public Matrix4x4 TransformMatrix { get; } + /// + /// Gets the target dimensions to constrain the transformed image to + /// + public Size TargetDimensions { get; } + /// protected override Image CreateDestination(Image source, Rectangle sourceRectangle) { - if (this.targetRectangle == Rectangle.Empty) - { - this.targetRectangle = this.GetTransformedBoundingRectangle(sourceRectangle, this.TransformMatrix); - } - // 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.targetRectangle.Width, this.targetRectangle.Height, x.MetaData.Clone())); + source.Frames.Select(x => new ImageFrame(source.GetMemoryManager(), this.TargetDimensions.Width, this.TargetDimensions.Height, x.MetaData.Clone())); // Use the overload to prevent an extra frame being added return new Image(source.GetConfiguration(), source.MetaData.Clone(), frames); @@ -83,12 +59,17 @@ namespace SixLabors.ImageSharp.Processing.Processors /// protected override void OnApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) { - int height = this.targetRectangle.Height; - int width = this.targetRectangle.Width; + int height = this.TargetDimensions.Height; + int width = this.TargetDimensions.Width; + Rectangle sourceBounds = source.Bounds(); + var targetBounds = new Rectangle(0, 0, width, height); // Since could potentially be resizing the canvas we might need to re-calculate the matrix - Matrix4x4 matrix = this.GetProcessingMatrix(sourceBounds, this.targetRectangle); + Matrix4x4 matrix = this.GetProcessingMatrix(sourceBounds, targetBounds); + + // Convert from screen to world space. + Matrix4x4.Invert(matrix, out matrix); if (this.Sampler is NearestNeighborResampler) { @@ -125,8 +106,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, @@ -199,8 +182,8 @@ namespace SixLabors.ImageSharp.Processing.Processors var vector = source[i, j].ToVector4(); // Values are first premultiplied to prevent darkening of edge pixels - Vector4 mupltiplied = vector.Premultiply(); - sum += mupltiplied * xWeight * yWeight; + Vector4 multiplied = vector.Premultiply(); + sum += multiplied * xWeight * yWeight; } } @@ -225,16 +208,5 @@ namespace SixLabors.ImageSharp.Processing.Processors { return this.TransformMatrix; } - - /// - /// Gets the bounding relative to the source for the given transformation matrix. - /// - /// The source rectangle. - /// The transformation matrix. - /// The - protected virtual Rectangle GetTransformedBoundingRectangle(Rectangle sourceRectangle, Matrix4x4 matrix) - { - return sourceRectangle; - } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs deleted file mode 100644 index b9cb58707c..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors -{ - /// - /// Provides methods that allow the resizing of images using various algorithms. - /// Adapted from - /// - /// The pixel format. - internal abstract class ResamplingWeightedProcessor : TransformProcessorBase - where TPixel : struct, IPixel - { - /// - /// Initializes a new instance of the class. - /// - /// 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) - { - Guard.NotNull(sampler, nameof(sampler)); - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - - this.Sampler = sampler; - this.Width = width; - this.Height = height; - this.ResizeRectangle = resizeRectangle; - } - - /// - /// Gets the sampler to perform the resize operation. - /// - public IResampler Sampler { get; } - - /// - /// Gets or sets the width. - /// - public int Width { get; protected set; } - - /// - /// Gets or sets the height. - /// - public int Height { get; protected set; } - - /// - /// Gets or sets the resize rectangle. - /// - public Rectangle ResizeRectangle { get; protected set; } - - /// - /// Gets or sets the horizontal weights. - /// - protected WeightsBuffer HorizontalWeights { get; set; } - - /// - /// Gets or sets the vertical weights. - /// - protected WeightsBuffer VerticalWeights { get; set; } - - /// - /// Computes the weights to apply at each pixel when resizing. - /// - /// The destination size - /// The source size - /// The - // TODO: Made internal to simplify experimenting with weights data. Make it protected again when finished figuring out how to optimize all the stuff! - internal unsafe WeightsBuffer PrecomputeWeights(int destinationSize, int sourceSize) - { - float ratio = (float)sourceSize / destinationSize; - float scale = ratio; - - if (scale < 1F) - { - scale = 1F; - } - - IResampler sampler = this.Sampler; - float radius = MathF.Ceiling(scale * sampler.Radius); - var result = new WeightsBuffer(sourceSize, destinationSize); - - for (int i = 0; i < destinationSize; i++) - { - float center = ((i + .5F) * ratio) - .5F; - - // Keep inside bounds. - int left = (int)Math.Ceiling(center - radius); - if (left < 0) - { - left = 0; - } - - int right = (int)Math.Floor(center + radius); - if (right > sourceSize - 1) - { - right = sourceSize - 1; - } - - float sum = 0; - - WeightsWindow ws = result.GetWeightsWindow(i, left, right); - result.Weights[i] = ws; - - ref float weightsBaseRef = ref ws.GetStartReference(); - - for (int j = left; j <= right; j++) - { - float weight = sampler.GetValue((j - center) / scale); - sum += weight; - - // weights[j - left] = weight: - Unsafe.Add(ref weightsBaseRef, j - left) = weight; - } - - // Normalise, best to do it here rather than in the pixel loop later on. - if (sum > 0) - { - for (int w = 0; w < ws.Length; w++) - { - // weights[w] = weights[w] / sum: - ref float wRef = ref Unsafe.Add(ref weightsBaseRef, w); - wRef = wRef / sum; - } - } - } - - return result; - } - - /// - protected override void BeforeApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) - { - if (!(this.Sampler is NearestNeighborResampler)) - { - this.HorizontalWeights = this.PrecomputeWeights( - this.ResizeRectangle.Width, - sourceRectangle.Width); - - this.VerticalWeights = this.PrecomputeWeights( - this.ResizeRectangle.Height, - sourceRectangle.Height); - } - } - - /// - protected override void AfterApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) - { - base.AfterApply(source, destination, sourceRectangle, configuration); - this.HorizontalWeights?.Dispose(); - this.HorizontalWeights = null; - this.VerticalWeights?.Dispose(); - this.VerticalWeights = null; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs index b05d77868f..2e73920baa 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -5,10 +5,10 @@ using System; using System.Collections.Generic; using System.Linq; using System.Numerics; +using System.Runtime.CompilerServices; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; @@ -16,19 +16,62 @@ namespace SixLabors.ImageSharp.Processing.Processors { /// /// Provides methods that allow the resizing of images using various algorithms. + /// Adapted from /// /// The pixel format. - internal class ResizeProcessor : ResamplingWeightedProcessor + internal class ResizeProcessor : TransformProcessorBase where TPixel : struct, IPixel { + // The following fields are not immutable but are optionally created on demand. + private WeightsBuffer horizontalWeights; + private WeightsBuffer verticalWeights; + + /// + /// Initializes a new instance of the class. + /// + /// The resize options + /// The source image size + public ResizeProcessor(ResizeOptions options, Size sourceSize) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(options.Sampler, nameof(options.Sampler)); + + int tempWidth = options.Size.Width; + int tempHeight = options.Size.Height; + + // Ensure size is populated across both dimensions. + // These dimensions are used to calculate the final dimensions determined by the mode algorithm. + if (tempWidth == 0 && tempHeight > 0) + { + tempWidth = (int)MathF.Round(sourceSize.Width * tempHeight / (float)sourceSize.Height); + } + + if (tempHeight == 0 && tempWidth > 0) + { + tempHeight = (int)MathF.Round(sourceSize.Height * tempWidth / (float)sourceSize.Width); + } + + Guard.MustBeGreaterThan(tempWidth, 0, nameof(tempWidth)); + Guard.MustBeGreaterThan(tempHeight, 0, nameof(tempHeight)); + + (Size size, Rectangle rectangle) locationBounds = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options, tempWidth, tempHeight); + + this.Sampler = options.Sampler; + this.Width = locationBounds.size.Width; + this.Height = locationBounds.size.Height; + this.ResizeRectangle = locationBounds.rectangle; + this.Compand = options.Compand; + } + /// /// Initializes a new instance of the class. /// /// 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)) + /// The source image size + public ResizeProcessor(IResampler sampler, int width, int height, Size sourceSize) + : this(sampler, width, height, sourceSize, new Rectangle(0, 0, width, height), false) { } @@ -38,30 +81,161 @@ namespace SixLabors.ImageSharp.Processing.Processors /// The sampler to perform the resize operation. /// The target width. /// The target height. + /// The source image size /// /// 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) + /// Whether to compress or expand individual pixel color values on processing. + public ResizeProcessor(IResampler sampler, int width, int height, Size sourceSize, Rectangle resizeRectangle, bool compand) { + Guard.NotNull(sampler, nameof(sampler)); + + // Ensure size is populated across both dimensions. + if (width == 0 && height > 0) + { + width = (int)MathF.Round(sourceSize.Width * height / (float)sourceSize.Height); + resizeRectangle.Width = width; + } + + if (height == 0 && width > 0) + { + height = (int)MathF.Round(sourceSize.Height * width / (float)sourceSize.Width); + resizeRectangle.Height = height; + } + + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + + this.Sampler = sampler; + this.Width = width; + this.Height = height; + this.ResizeRectangle = resizeRectangle; + this.Compand = compand; } /// - /// Gets or sets a value indicating whether to compress or expand individual pixel color values on processing. + /// Gets the sampler to perform the resize operation. + /// + public IResampler Sampler { get; } + + /// + /// Gets the target width. + /// + public int Width { get; } + + /// + /// Gets the target height. + /// + public int Height { get; } + + /// + /// Gets the resize rectangle. + /// + public Rectangle ResizeRectangle { get; } + + /// + /// Gets a value indicating whether to compress or expand individual pixel color values on processing. + /// + public bool Compand { get; } + + /// + /// Computes the weights to apply at each pixel when resizing. /// - public bool Compand { get; set; } + /// The to use for buffer allocations + /// The destination size + /// The source size + /// The + // TODO: Made internal to simplify experimenting with weights data. Make it private when finished figuring out how to optimize all the stuff! + internal WeightsBuffer PrecomputeWeights(MemoryManager memoryManager, int destinationSize, int sourceSize) + { + float ratio = (float)sourceSize / destinationSize; + float scale = ratio; + + if (scale < 1F) + { + scale = 1F; + } + + IResampler sampler = this.Sampler; + float radius = MathF.Ceiling(scale * sampler.Radius); + var result = new WeightsBuffer(memoryManager, sourceSize, destinationSize); + + for (int i = 0; i < destinationSize; i++) + { + float center = ((i + .5F) * ratio) - .5F; + + // Keep inside bounds. + int left = (int)Math.Ceiling(center - radius); + if (left < 0) + { + left = 0; + } + + int right = (int)Math.Floor(center + radius); + if (right > sourceSize - 1) + { + right = sourceSize - 1; + } + + float sum = 0; + + WeightsWindow ws = result.GetWeightsWindow(i, left, right); + result.Weights[i] = ws; + + ref float weightsBaseRef = ref ws.GetStartReference(); + + for (int j = left; j <= right; j++) + { + float weight = sampler.GetValue((j - center) / scale); + sum += weight; + + // weights[j - left] = weight: + Unsafe.Add(ref weightsBaseRef, j - left) = weight; + } + + // Normalize, best to do it here rather than in the pixel loop later on. + if (sum > 0) + { + for (int w = 0; w < ws.Length; w++) + { + // weights[w] = weights[w] / sum: + ref float wRef = ref Unsafe.Add(ref weightsBaseRef, w); + wRef = wRef / sum; + } + } + } + + return result; + } /// 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())); + IEnumerable> frames = source.Frames.Select(x => new ImageFrame(source.GetMemoryManager(), this.Width, this.Height, x.MetaData.Clone())); // this will create places holders // Use the overload to prevent an extra frame being added return new Image(source.GetConfiguration(), source.MetaData.Clone(), frames); } + /// + protected override void BeforeApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) + { + if (!(this.Sampler is NearestNeighborResampler)) + { + // TODO: Optimization opportunity: if we could assume that all frames are of the same size, we can move this into 'BeforeImageApply()` + this.horizontalWeights = this.PrecomputeWeights( + source.MemoryManager, + this.ResizeRectangle.Width, + sourceRectangle.Width); + + this.verticalWeights = this.PrecomputeWeights( + source.MemoryManager, + this.ResizeRectangle.Height, + sourceRectangle.Height); + } + } + /// protected override void OnApply(ImageFrame source, ImageFrame cloned, Rectangle sourceRectangle, Configuration configuration) { @@ -118,38 +292,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 = source.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; + + PixelOperations.Instance.ToVector4(sourceRow, tempRowSpan, sourceRow.Length); - if (this.Compand) + 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); } } }); @@ -161,8 +334,8 @@ namespace SixLabors.ImageSharp.Processing.Processors configuration.ParallelOptions, y => { - // Ensure offsets are normalised for cropping and padding. - WeightsWindow window = this.VerticalWeights.Weights[y - startY]; + // Ensure offsets are normalized for cropping and padding. + WeightsWindow window = this.verticalWeights.Weights[y - startY]; Span targetRow = cloned.GetPixelRowSpan(y); if (this.Compand) @@ -191,5 +364,17 @@ namespace SixLabors.ImageSharp.Processing.Processors }); } } + + /// + protected override void AfterApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) + { + base.AfterApply(source, destination, sourceRectangle, configuration); + + // TODO: An exception in the processing chain can leave these buffers undisposed. We should consider making image processors IDisposable! + this.horizontalWeights?.Dispose(); + this.horizontalWeights = null; + this.verticalWeights?.Dispose(); + this.verticalWeights = null; + } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs index b93c869152..824ae4310f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs @@ -22,8 +22,9 @@ namespace SixLabors.ImageSharp.Processing.Processors /// Initializes a new instance of the class. /// /// The angle of rotation in degrees. - public RotateProcessor(float degrees) - : this(degrees, KnownResamplers.Bicubic) + /// The source image size + public RotateProcessor(float degrees, Size sourceSize) + : this(degrees, KnownResamplers.Bicubic, sourceSize) { } @@ -32,8 +33,9 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// The angle of rotation in degrees. /// The sampler to perform the rotating operation. - public RotateProcessor(float degrees, IResampler sampler) - : base(Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty), sampler) + /// The source image size + public RotateProcessor(float degrees, IResampler sampler, Size sourceSize) + : base(Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty), sampler, sourceSize) { this.Degrees = degrees; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs index 8c47b35274..8e3ab7c34f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs @@ -18,8 +18,9 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// The angle in degrees to perform the skew along the x-axis. /// The angle in degrees to perform the skew along the y-axis. - public SkewProcessor(float degreesX, float degreesY) - : this(degreesX, degreesY, KnownResamplers.Bicubic) + /// The source image size + public SkewProcessor(float degreesX, float degreesY, Size sourceSize) + : this(degreesX, degreesY, KnownResamplers.Bicubic, sourceSize) { } @@ -29,8 +30,9 @@ namespace SixLabors.ImageSharp.Processing.Processors /// The angle in degrees to perform the skew along the x-axis. /// The angle in degrees to perform the skew along the y-axis. /// The sampler to perform the skew operation. - public SkewProcessor(float degreesX, float degreesY, IResampler sampler) - : base(Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty), sampler) + /// The source image size + public SkewProcessor(float degreesX, float degreesY, IResampler sampler, Size sourceSize) + : base(Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty), sampler, sourceSize) { this.DegreesX = degreesX; this.DegreesY = degreesY; diff --git a/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs b/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs index 0e91087f90..d633a3869f 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 b0a530514e..399b3db842 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/Options/ResizeHelper.cs b/src/ImageSharp/Processing/Transforms/Options/ResizeHelper.cs index 17a0cc428f..ba6f4509d8 100644 --- a/src/ImageSharp/Processing/Transforms/Options/ResizeHelper.cs +++ b/src/ImageSharp/Processing/Transforms/Options/ResizeHelper.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing @@ -17,52 +16,39 @@ namespace SixLabors.ImageSharp.Processing /// /// Calculates the target location and bounds to perform the resize operation against. /// - /// The pixel format. - /// The source image. + /// The source image size. /// The resize options. + /// The target width + /// The target height /// - /// The . + /// The . /// - public static Rectangle CalculateTargetLocationAndBounds(ImageFrame source, ResizeOptions options) - where TPixel : struct, IPixel + public static (Size, Rectangle) CalculateTargetLocationAndBounds(Size sourceSize, ResizeOptions options, int width, int height) { switch (options.Mode) { case ResizeMode.Crop: - return CalculateCropRectangle(source, options); + return CalculateCropRectangle(sourceSize, options, width, height); case ResizeMode.Pad: - return CalculatePadRectangle(source, options); + return CalculatePadRectangle(sourceSize, options, width, height); case ResizeMode.BoxPad: - return CalculateBoxPadRectangle(source, options); + return CalculateBoxPadRectangle(sourceSize, options, width, height); case ResizeMode.Max: - return CalculateMaxRectangle(source, options); + return CalculateMaxRectangle(sourceSize, options, width, height); case ResizeMode.Min: - return CalculateMinRectangle(source, options); + return CalculateMinRectangle(sourceSize, options, width, height); // Last case ResizeMode.Stretch: default: - return new Rectangle(0, 0, options.Size.Width, options.Size.Height); + return (new Size(width, height), new Rectangle(0, 0, width, height)); } } - /// - /// Calculates the target rectangle for crop mode. - /// - /// The pixel format. - /// The source image. - /// The resize options. - /// - /// The . - /// - private static Rectangle CalculateCropRectangle(ImageFrame source, ResizeOptions options) - where TPixel : struct, IPixel + private static (Size, Rectangle) CalculateCropRectangle(Size source, ResizeOptions options, int width, int height) { - int width = options.Size.Width; - int height = options.Size.Height; - if (width <= 0 || height <= 0) { - return new Rectangle(0, 0, source.Width, source.Height); + return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height)); } float ratio; @@ -161,27 +147,14 @@ namespace SixLabors.ImageSharp.Processing destinationWidth = (int)MathF.Ceiling(sourceWidth * percentHeight); } - return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); + return (new Size(width, height), new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); } - /// - /// Calculates the target rectangle for pad mode. - /// - /// The pixel format. - /// The source image. - /// The resize options. - /// - /// The . - /// - private static Rectangle CalculatePadRectangle(ImageFrame source, ResizeOptions options) - where TPixel : struct, IPixel + private static (Size, Rectangle) CalculatePadRectangle(Size source, ResizeOptions options, int width, int height) { - int width = options.Size.Width; - int height = options.Size.Height; - if (width <= 0 || height <= 0) { - return new Rectangle(0, 0, source.Width, source.Height); + return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height)); } float ratio; @@ -242,27 +215,14 @@ namespace SixLabors.ImageSharp.Processing } } - return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); + return (new Size(width, height), new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); } - /// - /// Calculates the target rectangle for box pad mode. - /// - /// The pixel format. - /// The source image. - /// The resize options. - /// - /// The . - /// - private static Rectangle CalculateBoxPadRectangle(ImageFrame source, ResizeOptions options) - where TPixel : struct, IPixel + private static (Size, Rectangle) CalculateBoxPadRectangle(Size source, ResizeOptions options, int width, int height) { - int width = options.Size.Width; - int height = options.Size.Height; - if (width <= 0 || height <= 0) { - return new Rectangle(0, 0, source.Width, source.Height); + return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height)); } int sourceWidth = source.Width; @@ -325,27 +285,15 @@ namespace SixLabors.ImageSharp.Processing break; } - return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); + return (new Size(width, height), new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); } // Switch to pad mode to downscale and calculate from there. - return CalculatePadRectangle(source, options); + return CalculatePadRectangle(source, options, width, height); } - /// - /// Calculates the target rectangle for max mode. - /// - /// The pixel format. - /// The source image. - /// The resize options. - /// - /// The . - /// - private static Rectangle CalculateMaxRectangle(ImageFrame source, ResizeOptions options) - where TPixel : struct, IPixel + private static (Size, Rectangle) CalculateMaxRectangle(Size source, ResizeOptions options, int width, int height) { - int width = options.Size.Width; - int height = options.Size.Height; int destinationWidth = width; int destinationHeight = height; @@ -369,24 +317,11 @@ namespace SixLabors.ImageSharp.Processing } // Replace the size to match the rectangle. - options.Size = new Size(width, height); - return new Rectangle(0, 0, destinationWidth, destinationHeight); + return (new Size(width, height), new Rectangle(0, 0, destinationWidth, destinationHeight)); } - /// - /// Calculates the target rectangle for min mode. - /// - /// The pixel format. - /// The source image. - /// The resize options. - /// - /// The . - /// - private static Rectangle CalculateMinRectangle(ImageFrame source, ResizeOptions options) - where TPixel : struct, IPixel + private static (Size, Rectangle) CalculateMinRectangle(Size source, ResizeOptions options, int width, int height) { - int width = options.Size.Width; - int height = options.Size.Height; int sourceWidth = source.Width; int sourceHeight = source.Height; int destinationWidth; @@ -395,8 +330,7 @@ namespace SixLabors.ImageSharp.Processing // Don't upscale if (width > sourceWidth || height > sourceHeight) { - options.Size = new Size(sourceWidth, sourceHeight); - return new Rectangle(0, 0, sourceWidth, sourceHeight); + return (new Size(sourceWidth, sourceWidth), new Rectangle(0, 0, sourceWidth, sourceHeight)); } // Fractional variants for preserving aspect ratio. @@ -438,8 +372,7 @@ namespace SixLabors.ImageSharp.Processing } // Replace the size to match the rectangle. - options.Size = new Size(width, height); - return new Rectangle(0, 0, destinationWidth, destinationHeight); + return (new Size(width, height), new Rectangle(0, 0, destinationWidth, destinationHeight)); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Transforms/Options/ResizeOptions.cs b/src/ImageSharp/Processing/Transforms/Options/ResizeOptions.cs index d20eaefb11..f13fa77c99 100644 --- a/src/ImageSharp/Processing/Transforms/Options/ResizeOptions.cs +++ b/src/ImageSharp/Processing/Transforms/Options/ResizeOptions.cs @@ -41,6 +41,6 @@ namespace SixLabors.ImageSharp.Processing /// Gets or sets a value indicating whether to compress /// or expand individual pixel colors the value on processing. /// - public bool Compand { get; set; } + public bool Compand { get; set; } = false; } } diff --git a/src/ImageSharp/Processing/Transforms/Pad.cs b/src/ImageSharp/Processing/Transforms/Pad.cs index eb0f2e9c27..2f637aa162 100644 --- a/src/ImageSharp/Processing/Transforms/Pad.cs +++ b/src/ImageSharp/Processing/Transforms/Pad.cs @@ -1,10 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors; using SixLabors.Primitives; namespace SixLabors.ImageSharp @@ -25,11 +23,11 @@ namespace SixLabors.ImageSharp public static IImageProcessingContext Pad(this IImageProcessingContext source, int width, int height) where TPixel : struct, IPixel { - ResizeOptions options = new ResizeOptions + var options = new ResizeOptions { Size = new Size(width, height), Mode = ResizeMode.BoxPad, - Sampler = new NearestNeighborResampler() + Sampler = KnownResamplers.NearestNeighbor }; return Resize(source, options); diff --git a/src/ImageSharp/Processing/Transforms/Resize.cs b/src/ImageSharp/Processing/Transforms/Resize.cs index 832b02dea7..285f3206dc 100644 --- a/src/ImageSharp/Processing/Transforms/Resize.cs +++ b/src/ImageSharp/Processing/Transforms/Resize.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors; @@ -24,29 +23,10 @@ namespace SixLabors.ImageSharp /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image public static IImageProcessingContext Resize(this IImageProcessingContext source, ResizeOptions options) where TPixel : struct, IPixel - { - return source.Apply(img => - { - // Cheat and bound through a run, inside here we should just be mutating, this really needs moving over to a processor - // Ensure size is populated across both dimensions. - if (options.Size.Width == 0 && options.Size.Height > 0) - { - options.Size = new Size((int)MathF.Round(img.Width * options.Size.Height / (float)img.Height), options.Size.Height); - } - - if (options.Size.Height == 0 && options.Size.Width > 0) - { - options.Size = new Size(options.Size.Width, (int)MathF.Round(img.Height * options.Size.Width / (float)img.Width)); - } - - Rectangle targetRectangle = ResizeHelper.CalculateTargetLocationAndBounds(img.Frames.RootFrame, options); - - img.Mutate(x => Resize(x, options.Size.Width, options.Size.Height, options.Sampler, targetRectangle, options.Compand)); - }); - } + => source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize())); /// - /// Resizes an image to the given . + /// Resizes an image to the given . /// /// The pixel format. /// The image to resize. @@ -55,12 +35,10 @@ namespace SixLabors.ImageSharp /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size) where TPixel : struct, IPixel - { - return Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, false); - } + => Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, false); /// - /// Resizes an image to the given . + /// Resizes an image to the given . /// /// The pixel format. /// The image to resize. @@ -70,9 +48,7 @@ namespace SixLabors.ImageSharp /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size, bool compand) where TPixel : struct, IPixel - { - return Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, compand); - } + => Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, compand); /// /// Resizes an image to the given width and height. @@ -85,9 +61,7 @@ namespace SixLabors.ImageSharp /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height) where TPixel : struct, IPixel - { - return Resize(source, width, height, KnownResamplers.Bicubic, false); - } + => Resize(source, width, height, KnownResamplers.Bicubic, false); /// /// Resizes an image to the given width and height. @@ -101,9 +75,7 @@ namespace SixLabors.ImageSharp /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, bool compand) where TPixel : struct, IPixel - { - return Resize(source, width, height, KnownResamplers.Bicubic, compand); - } + => Resize(source, width, height, KnownResamplers.Bicubic, compand); /// /// Resizes an image to the given width and height with the given sampler. @@ -117,9 +89,7 @@ namespace SixLabors.ImageSharp /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, IResampler sampler) where TPixel : struct, IPixel - { - return Resize(source, width, height, sampler, false); - } + => Resize(source, width, height, sampler, false); /// /// Resizes an image to the given width and height with the given sampler. @@ -133,9 +103,7 @@ namespace SixLabors.ImageSharp /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size, IResampler sampler, bool compand) where TPixel : struct, IPixel - { - return Resize(source, size.Width, size.Height, sampler, new Rectangle(0, 0, size.Width, size.Height), compand); - } + => Resize(source, size.Width, size.Height, sampler, new Rectangle(0, 0, size.Width, size.Height), compand); /// /// Resizes an image to the given width and height with the given sampler. @@ -150,9 +118,7 @@ namespace SixLabors.ImageSharp /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, IResampler sampler, bool compand) where TPixel : struct, IPixel - { - return Resize(source, width, height, sampler, new Rectangle(0, 0, width, height), compand); - } + => Resize(source, width, height, sampler, new Rectangle(0, 0, width, height), compand); /// /// Resizes an image to the given width and height with the given sampler and @@ -172,34 +138,19 @@ namespace SixLabors.ImageSharp /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand) + public static IImageProcessingContext Resize( + this IImageProcessingContext source, + int width, + int height, + IResampler sampler, + Rectangle sourceRectangle, + Rectangle targetRectangle, + bool compand) where TPixel : struct, IPixel - { - return source.Apply(img => - { - // TODO : Stop cheating here and move this stuff into the processors itself - if (width == 0 && height > 0) - { - width = (int)MathF.Round(img.Width * height / (float)img.Height); - targetRectangle.Width = width; - } - - if (height == 0 && width > 0) - { - height = (int)MathF.Round(img.Height * width / (float)img.Width); - targetRectangle.Height = height; - } - - 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)); - }); - } + => source.ApplyProcessor(new ResizeProcessor(sampler, width, height, source.GetCurrentSize(), targetRectangle, compand), sourceRectangle); /// - /// Resizes an image to the given width and height with the given sampler and - /// source rectangle. + /// Resizes an image to the given width and height with the given sampler and source rectangle. /// /// The pixel format. /// The image to resize. @@ -212,29 +163,14 @@ namespace SixLabors.ImageSharp /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, IResampler sampler, Rectangle targetRectangle, bool compand) + public static IImageProcessingContext Resize( + this IImageProcessingContext source, + int width, + int height, + IResampler sampler, + Rectangle targetRectangle, + bool compand) where TPixel : struct, IPixel - { - return source.Apply(img => - { - // TODO : stop cheating here and move this stuff into the processors itself - if (width == 0 && height > 0) - { - width = (int)MathF.Round(img.Width * height / (float)img.Height); - targetRectangle.Width = width; - } - - if (height == 0 && width > 0) - { - height = (int)MathF.Round(img.Height * width / (float)img.Width); - targetRectangle.Height = height; - } - - 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 })); - }); - } + => source.ApplyProcessor(new ResizeProcessor(sampler, width, height, source.GetCurrentSize(), targetRectangle, compand)); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Transforms/Rotate.cs b/src/ImageSharp/Processing/Transforms/Rotate.cs index 69fb7ebf0c..faecdf91f9 100644 --- a/src/ImageSharp/Processing/Transforms/Rotate.cs +++ b/src/ImageSharp/Processing/Transforms/Rotate.cs @@ -44,6 +44,6 @@ namespace SixLabors.ImageSharp /// The public static IImageProcessingContext Rotate(this IImageProcessingContext source, float degrees, IResampler sampler) where TPixel : struct, IPixel - => source.ApplyProcessor(new RotateProcessor(degrees, sampler)); + => source.ApplyProcessor(new RotateProcessor(degrees, sampler, source.GetCurrentSize())); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Transforms/Skew.cs b/src/ImageSharp/Processing/Transforms/Skew.cs index 0613a690b8..fb054211a0 100644 --- a/src/ImageSharp/Processing/Transforms/Skew.cs +++ b/src/ImageSharp/Processing/Transforms/Skew.cs @@ -35,6 +35,6 @@ namespace SixLabors.ImageSharp /// The public static IImageProcessingContext Skew(this IImageProcessingContext source, float degreesX, float degreesY, IResampler sampler) where TPixel : struct, IPixel - => source.ApplyProcessor(new SkewProcessor(degreesX, degreesY, sampler)); + => source.ApplyProcessor(new SkewProcessor(degreesX, degreesY, sampler, source.GetCurrentSize())); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Transforms/Transform.cs b/src/ImageSharp/Processing/Transforms/Transform.cs index e39da8dc0f..66d5d5de7e 100644 --- a/src/ImageSharp/Processing/Transforms/Transform.cs +++ b/src/ImageSharp/Processing/Transforms/Transform.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp /// The public static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix3x2 matrix) where TPixel : struct, IPixel - => Transform(source, matrix, KnownResamplers.NearestNeighbor); + => Transform(source, matrix, KnownResamplers.Bicubic); /// /// Transforms an image by the given matrix using the specified sampling algorithm. @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp /// The public static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix3x2 matrix, IResampler sampler) where TPixel : struct, IPixel - => Transform(source, matrix, sampler, Size.Empty); + => source.ApplyProcessor(new AffineTransformProcessor(matrix, sampler, source.GetCurrentSize())); /// /// Transforms an image by the given matrix using the specified sampling algorithm @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp /// The internal static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix4x4 matrix) where TPixel : struct, IPixel - => Transform(source, matrix, KnownResamplers.NearestNeighbor); + => Transform(source, matrix, KnownResamplers.Bicubic); /// /// Applies a projective transform to the image by the given matrix using the specified sampling algorithm. @@ -103,7 +103,7 @@ namespace SixLabors.ImageSharp /// The internal static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix4x4 matrix, IResampler sampler) where TPixel : struct, IPixel - => Transform(source, matrix, sampler, Rectangle.Empty); + => source.ApplyProcessor(new ProjectiveTransformProcessor(matrix, sampler, source.GetCurrentSize())); /// /// Applies a projective transform to the image by the given matrix using the specified sampling algorithm. @@ -117,6 +117,10 @@ namespace SixLabors.ImageSharp /// The internal static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix4x4 matrix, IResampler sampler, Rectangle rectangle) where TPixel : struct, IPixel - => source.ApplyProcessor(new ProjectiveTransformProcessor(matrix, sampler, rectangle)); + { + var t = Matrix4x4.CreateTranslation(new Vector3(-rectangle.Location, 0)); + Matrix4x4 combinedMatrix = t * matrix; + return source.ApplyProcessor(new ProjectiveTransformProcessor(combinedMatrix, sampler, rectangle.Size)); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Transforms/TransformHelpers.cs b/src/ImageSharp/Processing/Transforms/TransformHelpers.cs index bfb06c4707..1567c11619 100644 --- a/src/ImageSharp/Processing/Transforms/TransformHelpers.cs +++ b/src/ImageSharp/Processing/Transforms/TransformHelpers.cs @@ -59,7 +59,51 @@ namespace SixLabors.ImageSharp } /// - /// Returns the bounding relative to the source for the given transformation matrix. + /// Gets the centered transform matrix based upon the source and destination rectangles + /// + /// The source image bounds. + /// The destination image bounds. + /// The transformation matrix. + /// The + public static Matrix3x2 GetCenteredTransformMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle, Matrix3x2 matrix) + { + // We invert the matrix to handle the transformation from screen to world space. + // This ensures scaling matrices are correct. + Matrix3x2.Invert(matrix, out Matrix3x2 inverted); + + var translationToTargetCenter = Matrix3x2.CreateTranslation(-destinationRectangle.Width * .5F, -destinationRectangle.Height * .5F); + var translateToSourceCenter = Matrix3x2.CreateTranslation(sourceRectangle.Width * .5F, sourceRectangle.Height * .5F); + + Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered); + + // Translate back to world to pass to the Transform method. + return centered; + } + + /// + /// Gets the centered transform matrix based upon the source and destination rectangles + /// + /// The source image bounds. + /// The destination image bounds. + /// The transformation matrix. + /// The + public static Matrix4x4 GetCenteredTransformMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle, Matrix4x4 matrix) + { + // We invert the matrix to handle the transformation from screen to world space. + // This ensures scaling matrices are correct. + Matrix4x4.Invert(matrix, out Matrix4x4 inverted); + + var translationToTargetCenter = Matrix4x4.CreateTranslation(-destinationRectangle.Width * .5F, -destinationRectangle.Height * .5F, 0); + var translateToSourceCenter = Matrix4x4.CreateTranslation(sourceRectangle.Width * .5F, sourceRectangle.Height * .5F, 0); + + Matrix4x4.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix4x4 centered); + + // Translate back to world to pass to the Transform method. + return centered; + } + + /// + /// Returns the bounding rectangle relative to the source for the given transformation matrix. /// /// The source rectangle. /// The transformation matrix. @@ -79,7 +123,7 @@ namespace SixLabors.ImageSharp } /// - /// Returns the bounding relative to the source for the given transformation matrix. + /// Returns the bounding rectangle relative to the source for the given transformation matrix. /// /// The source rectangle. /// The transformation matrix. diff --git a/src/ImageSharp/Quantizers/Quantize.cs b/src/ImageSharp/Quantizers/Quantize.cs index d99d4af34c..f2a09abb77 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 31e424060b..0b41edf98d 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 04a70637b6..0000000000 --- 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 ce2a71da48..0aadf49732 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 83c2a2ee89..7d8519875b 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 b4f6ea9c06..0000000000 --- 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 5c3648c2d8..882d77dd12 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 2bf4e0da67..6537141501 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 2d624c19f1..b2def64ace 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 150b55aed0..dd9a628c25 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 0ac1413be0..0000000000 --- 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 48ee266fe0..0000000000 --- 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 0e21caffbc..0000000000 --- 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 70ea164d69..e5eb295449 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 b3c1e4ee95..53522a51f3 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 93420aacf8..c47aff9cf4 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 5a3131f796..c088e8eed4 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 1f88c4fbfa..5f4e2d75b4 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/BaseImageOperationsExtensionTest.cs b/tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs index 37696987cc..ec46e66107 100644 --- a/tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs +++ b/tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs @@ -16,29 +16,31 @@ namespace SixLabors.ImageSharp.Tests private readonly FakeImageOperationsProvider.FakeImageOperations internalOperations; protected readonly Rectangle rect; protected readonly GraphicsOptions options; + private Image source; public BaseImageOperationsExtensionTest() { - this.options = new GraphicsOptions(false) { }; + this.options = new GraphicsOptions(false); + this.source = new Image(91 + 324, 123 + 56); this.rect = new Rectangle(91, 123, 324, 56); // make this random? - this.internalOperations = new FakeImageOperationsProvider.FakeImageOperations(null, false); + this.internalOperations = new FakeImageOperationsProvider.FakeImageOperations(this.source, false); this.operations = this.internalOperations; } public T Verify(int index = 0) { - Assert.InRange(index, 0, this.internalOperations.applied.Count - 1); + Assert.InRange(index, 0, this.internalOperations.Applied.Count - 1); - var operation = this.internalOperations.applied[index]; + var operation = this.internalOperations.Applied[index]; return Assert.IsType(operation.Processor); } public T Verify(Rectangle rect, int index = 0) { - Assert.InRange(index, 0, this.internalOperations.applied.Count - 1); + Assert.InRange(index, 0, this.internalOperations.Applied.Count - 1); - var operation = this.internalOperations.applied[index]; + var operation = this.internalOperations.Applied[index]; Assert.Equal(rect, operation.Rectangle); return Assert.IsType(operation.Processor); diff --git a/tests/ImageSharp.Tests/ComplexIntegrationTests.cs b/tests/ImageSharp.Tests/ComplexIntegrationTests.cs new file mode 100644 index 0000000000..ad4676872f --- /dev/null +++ b/tests/ImageSharp.Tests/ComplexIntegrationTests.cs @@ -0,0 +1,35 @@ +namespace SixLabors.ImageSharp.Tests +{ + using SixLabors.ImageSharp.Formats.Jpeg; + using SixLabors.ImageSharp.PixelFormats; + using SixLabors.ImageSharp.Processing; + using SixLabors.Primitives; + + using Xunit; + + /// + /// Might be useful to catch complex bugs + /// + public class ComplexIntegrationTests + { + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Snake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio420)] + [WithFile(TestImages.Jpeg.Baseline.Lake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio420)] + [WithFile(TestImages.Jpeg.Baseline.Snake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio444)] + [WithFile(TestImages.Jpeg.Baseline.Lake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio444)] + public void LoadResizeSave(TestImageProvider provider, int quality, JpegSubsample subsample) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(x => x.Resize(new ResizeOptions { Size = new Size(150, 100), Mode = ResizeMode.Max }))) + { + + image.MetaData.ExifProfile = null; // Reduce the size of the file + JpegEncoder options = new JpegEncoder { Subsample = subsample, Quality = quality }; + + provider.Utility.TestName += $"{subsample}_Q{quality}"; + provider.Utility.SaveTestOutputFile(image, "png"); + provider.Utility.SaveTestOutputFile(image, "jpg", options); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs index 6a55d8a561..5fcb860be5 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs @@ -1,17 +1,16 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.IO; -using System.Linq; +using System; +using System.Numerics; +using SixLabors.ImageSharp.Helpers; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; using Xunit; namespace SixLabors.ImageSharp.Tests { - using System; - using System.Numerics; - public class DrawImageTest : FileTestBase { private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32; @@ -23,8 +22,6 @@ namespace SixLabors.ImageSharp.Tests TestImages.Gif.Rings }; - object[][] Modes = System.Enum.GetValues(typeof(PixelBlenderMode)).Cast().Select(x => new object[] { x }).ToArray(); - [Theory] [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Normal)] [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Multiply)] @@ -39,9 +36,10 @@ namespace SixLabors.ImageSharp.Tests where TPixel : struct, IPixel { using (Image image = provider.GetImage()) - using (Image blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes)) + using (var blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes)) { - image.Mutate(x => x.DrawImage(blend, mode, .75f, new Size(image.Width / 2, image.Height / 2), new Point(image.Width / 4, image.Height / 4))); + blend.Mutate(x => x.Resize(image.Width / 2, image.Height / 2)); + image.Mutate(x => x.DrawImage(blend, mode, .75f, new Point(image.Width / 4, image.Height / 4))); image.DebugSave(provider, new { mode }); } } @@ -52,15 +50,25 @@ namespace SixLabors.ImageSharp.Tests where TPixel : struct, IPixel { using (Image image = provider.GetImage()) - using (Image blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes)) + using (var blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes)) { Matrix3x2 rotate = Matrix3x2Extensions.CreateRotationDegrees(45F); Matrix3x2 scale = Matrix3x2Extensions.CreateScale(new SizeF(.25F, .25F)); + Matrix3x2 matrix = rotate * scale; + + // Lets center the matrix so we can tell whether any cut-off issues we may have belong to the drawing processor + Rectangle srcBounds = blend.Bounds(); + Rectangle destBounds = TransformHelpers.GetTransformedBoundingRectangle(srcBounds, matrix); + Matrix3x2 centeredMatrix = TransformHelpers.GetCenteredTransformMatrix(srcBounds, destBounds, matrix); - blend.Mutate(x => x.Transform(rotate * scale)); + // We pass a new rectangle here based on the dest bounds since we've offset the matrix + blend.Mutate(x => x.Transform( + centeredMatrix, + KnownResamplers.Bicubic, + new Rectangle(0, 0, destBounds.Width, destBounds.Height))); var position = new Point((image.Width - blend.Width) / 2, (image.Height - blend.Height) / 2); - image.Mutate(x => x.DrawImage(blend, mode, .75F, new Size(blend.Width, blend.Height), position)); + image.Mutate(x => x.DrawImage(blend, mode, .75F, position)); image.DebugSave(provider, new[] { "Transformed" }); } } @@ -78,7 +86,7 @@ namespace SixLabors.ImageSharp.Tests Rgba32 backgroundPixel = background[0, 0]; Rgba32 overlayPixel = overlay[Math.Abs(xy) + 1, Math.Abs(xy) + 1]; - background.Mutate(x => x.DrawImage(overlay, PixelBlenderMode.Normal, 1F, new Size(overlay.Width, overlay.Height), new Point(xy, xy))); + background.Mutate(x => x.DrawImage(overlay, PixelBlenderMode.Normal, 1F, new Point(xy, xy))); Assert.Equal(Rgba32.White, backgroundPixel); Assert.Equal(overlayPixel, background[0, 0]); @@ -100,7 +108,7 @@ namespace SixLabors.ImageSharp.Tests Rgba32 backgroundPixel = background[xy - 1, xy - 1]; Rgba32 overlayPixel = overlay[0, 0]; - background.Mutate(x => x.DrawImage(overlay, PixelBlenderMode.Normal, 1F, new Size(overlay.Width, overlay.Height), new Point(xy, xy))); + background.Mutate(x => x.DrawImage(overlay, PixelBlenderMode.Normal, 1F, new Point(xy, xy))); Assert.Equal(Rgba32.White, backgroundPixel); Assert.Equal(overlayPixel, background[xy, xy]); @@ -109,4 +117,4 @@ namespace SixLabors.ImageSharp.Tests } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs index 941807f542..5ca09ad1d2 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 07e75acf43..70badd34c5 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 f750bfcfad..db1e7903de 100644 --- a/tests/ImageSharp.Tests/FakeImageOperationsProvider.cs +++ b/tests/ImageSharp.Tests/FakeImageOperationsProvider.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.Collections.Generic; using System.Linq; -using System.Text; +using SixLabors.ImageSharp.Helpers; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Tests @@ -23,13 +23,13 @@ namespace SixLabors.ImageSharp.Tests public IEnumerable> Created(Image source) where TPixel : struct, IPixel { return this.ImageOperators.OfType>() - .Where(x => x.source == source); + .Where(x => x.Source == source); } - public IEnumerable.AppliedOpperation> AppliedOperations(Image source) where TPixel : struct, IPixel + public IEnumerable.AppliedOperation> AppliedOperations(Image source) where TPixel : struct, IPixel { return Created(source) - .SelectMany(x => x.applied); + .SelectMany(x => x.Applied); } public IInternalImageProcessingContext CreateImageProcessingContext(Image source, bool mutate) where TPixel : struct, IPixel @@ -39,36 +39,36 @@ namespace SixLabors.ImageSharp.Tests return op; } - public class FakeImageOperations : IInternalImageProcessingContext where TPixel : struct, IPixel { - public Image source; - - public List applied = new List(); - public bool mutate; + private bool mutate; public FakeImageOperations(Image source, bool mutate) { this.mutate = mutate; - if (mutate) - { - this.source = source; - } - else - { - this.source = source?.Clone(); - } + this.Source = mutate ? source : source?.Clone(); } + public Image Source { get; } + + public List Applied { get; } = new List(); + + public MemoryManager MemoryManager => this.Source.GetConfiguration().MemoryManager; + public Image Apply() { - return source; + return this.Source; + } + + public Size GetCurrentSize() + { + return this.Source.Size(); } public IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle) { - applied.Add(new AppliedOpperation + this.Applied.Add(new AppliedOperation { Processor = processor, Rectangle = rectangle @@ -78,13 +78,14 @@ namespace SixLabors.ImageSharp.Tests public IImageProcessingContext ApplyProcessor(IImageProcessor processor) { - applied.Add(new AppliedOpperation + this.Applied.Add(new AppliedOperation { Processor = processor }); return this; } - public struct AppliedOpperation + + public struct AppliedOperation { public Rectangle? Rectangle { get; set; } public IImageProcessor Processor { get; set; } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 2c0121803b..d958278f6e 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -1,19 +1,27 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats; +using System.IO; +using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.PixelFormats; using Xunit; + // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { - using System.IO; - - using SixLabors.ImageSharp.Formats.Bmp; + using static TestImages.Bmp; - public class BmpDecoderTests : FileTestBase + public class BmpDecoderTests { + public const PixelTypes CommonNonDefaultPixelTypes = + PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; + + public static readonly string[] AllBmpFiles = + { + Car, F, NegHeight, CoreHeader, V5Header, RLE, RLEInverted, Bit8, Bit8Inverted, Bit16, Bit16Inverted + }; + [Theory] [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32)] public void DecodeBmp(TestImageProvider provider) @@ -27,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests } [Theory] - [WithFile(TestImages.Bmp.F, CommonNonDefaultPixelTypes)] + [WithFile(F, CommonNonDefaultPixelTypes)] public void BmpDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) where TPixel : struct, IPixel { @@ -39,18 +47,18 @@ namespace SixLabors.ImageSharp.Tests } [Theory] - [InlineData(TestImages.Bmp.Car, 24)] - [InlineData(TestImages.Bmp.F, 24)] - [InlineData(TestImages.Bmp.NegHeight, 24)] - [InlineData(TestImages.Bmp.Bit8, 8)] - [InlineData(TestImages.Bmp.Bit8Inverted, 8)] + [InlineData(Car, 24)] + [InlineData(F, 24)] + [InlineData(NegHeight, 24)] + [InlineData(Bit8, 8)] + [InlineData(Bit8Inverted, 8)] public void DetectPixelSize(string imagePath, int expectedPixelSize) { - TestFile testFile = TestFile.Create(imagePath); + var testFile = TestFile.Create(imagePath); using (var stream = new MemoryStream(testFile.Bytes, false)) { Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel); } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index d96d3def5e..3a5fbe8387 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -5,31 +5,62 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.PixelFormats; using Xunit; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + + using Xunit.Abstractions; + public class BmpEncoderTests : FileTestBase { - public static readonly TheoryData BitsPerPixel - = new TheoryData + public static readonly TheoryData BitsPerPixel = + new TheoryData + { + BmpBitsPerPixel.Pixel24, + BmpBitsPerPixel.Pixel32 + }; + + public BmpEncoderTests(ITestOutputHelper output) { - BmpBitsPerPixel.Pixel24, - BmpBitsPerPixel.Pixel32 - }; + this.Output = output; + } + + private ITestOutputHelper Output { get; } [Theory] - [MemberData(nameof(BitsPerPixel))] - public void BitmapCanEncodeDifferentBitRates(BmpBitsPerPixel bitsPerPixel) + [WithTestPatternImages(nameof(BitsPerPixel), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)] + public void Encode_IsNotBoundToSinglePixelType(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel { - string path = TestEnvironment.CreateOutputDirectory("Bmp"); + TestBmpEncoderCore(provider, bitsPerPixel); + } - foreach (TestFile file in Files) + [Theory] + [WithTestPatternImages(nameof(BitsPerPixel), 48, 24, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel), 47, 8, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel), 49, 7, PixelTypes.Rgba32)] + [WithSolidFilledImages(nameof(BitsPerPixel), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel), 7, 5, PixelTypes.Rgba32)] + public void Encode_WorksWithDifferentSizes(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel + { + TestBmpEncoderCore(provider, bitsPerPixel); + } + + private static void TestBmpEncoderCore(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) { - string filename = file.GetFileNameWithoutExtension(bitsPerPixel); - using (Image image = file.CreateImage()) - { - image.Save($"{path}/{filename}.bmp", new BmpEncoder { BitsPerPixel = bitsPerPixel }); - } + // there is no alpha in bmp! + image.Mutate(c => c.MakeOpaque()); + + var encoder = new BmpEncoder { BitsPerPixel = bitsPerPixel }; + + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder); } } } diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 97128e2c93..22a811feee 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/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index 9a095548a7..9cdb9f8b1a 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -6,47 +6,89 @@ using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; using Xunit; +using System.IO; +using SixLabors.ImageSharp.Advanced; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { - using System.IO; - using SixLabors.ImageSharp.Advanced; + using System.Collections.Generic; + + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; public class GifDecoderTests { - private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; + private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; + + public static readonly string[] MultiFrameTestFiles = + { + TestImages.Gif.Giphy, TestImages.Gif.Kumin + }; + + public static readonly string[] BasicVerificationFiles = + { + TestImages.Gif.Cheers, + TestImages.Gif.Rings, + + // previously DecodeBadApplicationExtensionLength: + TestImages.Gif.Issues.BadAppExtLength, + TestImages.Gif.Issues.BadAppExtLength_2, - public static readonly string[] TestFiles = { TestImages.Gif.Giphy, TestImages.Gif.Rings, TestImages.Gif.Trans }; + // previously DecodeBadDescriptorDimensionsLength: + TestImages.Gif.Issues.BadDescriptorWidth + }; + + private static readonly Dictionary BasicVerificationFrameCount = + new Dictionary + { + [TestImages.Gif.Cheers] = 93, + [TestImages.Gif.Issues.BadDescriptorWidth] = 36, + }; public static readonly string[] BadAppExtFiles = { TestImages.Gif.Issues.BadAppExtLength, TestImages.Gif.Issues.BadAppExtLength_2 }; [Theory] - [WithFileCollection(nameof(TestFiles), PixelTypes)] - public void DecodeAndReSave(TestImageProvider imageProvider) + [WithFileCollection(nameof(MultiFrameTestFiles), PixelTypes.Rgba32)] + public void Decode_VerifyAllFrames(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = imageProvider.GetImage()) + using (Image image = provider.GetImage()) { - imageProvider.Utility.SaveTestOutputFile(image, "bmp"); - imageProvider.Utility.SaveTestOutputFile(image, "gif"); + image.DebugSaveMultiFrame(provider); + image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); } } [Theory] - [WithFileCollection(nameof(TestFiles), PixelTypes)] - public void DecodeResizeAndSave(TestImageProvider imageProvider) + [WithFile(TestImages.Gif.Trans, TestPixelTypes)] + public void GifDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = imageProvider.GetImage()) + using (Image image = provider.GetImage()) { - image.Mutate(x => x.Resize(new Size(image.Width / 2, image.Height / 2))); - - imageProvider.Utility.SaveTestOutputFile(image, "bmp"); - imageProvider.Utility.SaveTestOutputFile(image, "gif"); + image.DebugSave(provider); + image.CompareFirstFrameToReferenceOutput(provider, ImageComparer.Exact); } } + [Theory] + [WithFileCollection(nameof(BasicVerificationFiles), PixelTypes.Rgba32)] + public void Decode_VerifyRootFrameAndFrameCount(TestImageProvider provider) + where TPixel : struct, IPixel + { + if (!BasicVerificationFrameCount.TryGetValue(provider.SourceFileOrDescription, out int expectedFrameCount)) + { + expectedFrameCount = 1; + } + + using (Image image = provider.GetImage()) + { + Assert.Equal(expectedFrameCount, image.Frames.Count); + image.DebugSave(provider); + image.CompareFirstFrameToReferenceOutput(provider, ImageComparer.Exact); + } + } + [Fact] public void Decode_IgnoreMetadataIsFalse_CommentsAreRead() { @@ -149,27 +191,5 @@ namespace SixLabors.ImageSharp.Tests } } } - - [Theory] - [WithFileCollection(nameof(BadAppExtFiles), PixelTypes.Rgba32)] - public void DecodeBadApplicationExtensionLength(TestImageProvider imageProvider) - where TPixel : struct, IPixel - { - using (Image image = imageProvider.GetImage()) - { - imageProvider.Utility.SaveTestOutputFile(image, "bmp"); - } - } - - [Theory] - [WithFile(TestImages.Gif.Issues.BadDescriptorWidth, PixelTypes.Rgba32)] - public void DecodeBadDescriptorDimensionsLength(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - provider.Utility.SaveTestOutputFile(image, "bmp"); - } - } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index a06e36e2a6..a2f4806f37 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -7,15 +7,16 @@ using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.PixelFormats; using Xunit; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { public class GifEncoderTests { - private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; + private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; [Theory] - [WithTestPatternImages(100, 100, PixelTypes)] + [WithTestPatternImages(100, 100, TestPixelTypes)] public void EncodeGeneratedPatterns(TestImageProvider provider) where TPixel : struct, IPixel { @@ -78,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests } [Fact] - public void Encode_CommentIsToLong_CommentIsTrimmed() + public void Encode_WhenCommentIsTooLong_CommentIsTrimmed() { using (Image input = new Image(1, 1)) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs index 191cfec731..e50d84852a 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 887e9d7e95..193e26fcbd 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 f141905efd..bf6b1f4ab2 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 cb1987aef4..139fa351bb 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -41,8 +41,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Bad.BadEOF, - TestImages.Jpeg.Baseline.Bad.ExifUndefType, TestImages.Jpeg.Issues.MultiHuffmanBaseline394, + TestImages.Jpeg.Baseline.MultiScanBaselineCMYK }; public static string[] ProgressiveTestJpegs = @@ -51,14 +51,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Progressive.Festzug, TestImages.Jpeg.Progressive.Bad.BadEOF, TestImages.Jpeg.Issues.BadCoeffsProgressive178, TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159, - TestImages.Jpeg.Issues.BadZigZagProgressive385 + TestImages.Jpeg.Issues.BadZigZagProgressive385, + TestImages.Jpeg.Progressive.Bad.ExifUndefType }; private static readonly Dictionary CustomToleranceValues = new Dictionary { // Baseline: [TestImages.Jpeg.Baseline.Calliphora] = 0.00002f / 100, - [TestImages.Jpeg.Baseline.Bad.ExifUndefType] = 0.011f / 100, [TestImages.Jpeg.Baseline.Bad.BadEOF] = 0.38f / 100, [TestImages.Jpeg.Baseline.Testorig420] = 0.38f / 100, @@ -70,6 +70,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [TestImages.Jpeg.Progressive.Fb] = 0.16f / 100, [TestImages.Jpeg.Progressive.Progress] = 0.31f / 100, [TestImages.Jpeg.Issues.BadZigZagProgressive385] = 0.23f / 100, + [TestImages.Jpeg.Progressive.Bad.ExifUndefType] = 0.011f / 100, }; public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.RgbaVector; @@ -118,8 +119,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/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index c8d416beaf..cc030bbf7c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -10,117 +10,134 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using System.Collections.Generic; using System.IO; + using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; - using SixLabors.ImageSharp.Processing; + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using SixLabors.Primitives; using Xunit; using Xunit.Abstractions; - public class JpegEncoderTests : MeasureFixture + public class JpegEncoderTests { - public static IEnumerable AllBmpFiles => TestImages.Bmp.All; + public static readonly TheoryData BitsPerPixel_Quality = + new TheoryData + { + { JpegSubsample.Ratio420, 40 }, + { JpegSubsample.Ratio420, 60 }, + { JpegSubsample.Ratio420, 100 }, + + { JpegSubsample.Ratio444, 40 }, + { JpegSubsample.Ratio444, 60 }, + { JpegSubsample.Ratio444, 100 }, + }; - public JpegEncoderTests(ITestOutputHelper output) - : base(output) + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 73, 71, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 24, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 46, 8, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, PixelTypes.Rgba32)] + [WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 7, 5, PixelTypes.Rgba32)] + public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider provider, JpegSubsample subsample, int quality) + where TPixel : struct, IPixel { + TestJpegEncoderCore(provider, subsample, quality); } [Theory] - [WithFile(TestImages.Jpeg.Baseline.Snake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio420)] - [WithFile(TestImages.Jpeg.Baseline.Lake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio420)] - [WithFile(TestImages.Jpeg.Baseline.Snake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio444)] - [WithFile(TestImages.Jpeg.Baseline.Lake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio444)] - public void LoadResizeSave(TestImageProvider provider, int quality, JpegSubsample subsample) + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)] + public void EncodeBaseline_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegSubsample subsample, int quality) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(x => x.Resize(new ResizeOptions { Size = new Size(150, 100), Mode = ResizeMode.Max }))) - { - - image.MetaData.ExifProfile = null; // Reduce the size of the file - JpegEncoder options = new JpegEncoder { Subsample = subsample, Quality = quality }; - - provider.Utility.TestName += $"{subsample}_Q{quality}"; - provider.Utility.SaveTestOutputFile(image, "png"); - provider.Utility.SaveTestOutputFile(image, "jpg", options); - } + TestJpegEncoderCore(provider, subsample, quality); } - [Theory] - [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32 | PixelTypes.Rgba32 | PixelTypes.Argb32, JpegSubsample.Ratio420, 75)] - [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32 | PixelTypes.Rgba32 | PixelTypes.Argb32, JpegSubsample.Ratio444, 75)] - public void OpenBmp_SaveJpeg(TestImageProvider provider, JpegSubsample subSample, int quality) - where TPixel : struct, IPixel + /// + /// Anton's SUPER-SCIENTIFIC tolerance threshold calculation + /// + private static ImageComparer GetComparer(int quality, JpegSubsample subsample) { - using (Image image = provider.GetImage()) - { - ImagingTestCaseUtility utility = provider.Utility; - utility.TestName += "_" + subSample + "_Q" + quality; + float tolerance = 0.015f; // ~1.5% - using (FileStream outputStream = File.OpenWrite(utility.GetTestOutputFileName("jpg"))) + if (quality < 50) + { + tolerance *= 10f; + } + else if (quality < 75 || subsample == JpegSubsample.Ratio420) + { + tolerance *= 5f; + if (subsample == JpegSubsample.Ratio420) { - image.Save(outputStream, new JpegEncoder() - { - Subsample = subSample, - Quality = quality - }); + tolerance *= 2f; } } + + return ImageComparer.Tolerant(tolerance); } - [Fact] - public void Encode_IgnoreMetadataIsFalse_ExifProfileIsWritten() + private static void TestJpegEncoderCore( + TestImageProvider provider, + JpegSubsample subsample, + int quality = 100) + where TPixel : struct, IPixel { - JpegEncoder options = new JpegEncoder() - { - IgnoreMetadata = false - }; - - TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan); - - using (Image input = testFile.CreateImage()) + using (Image image = provider.GetImage()) { - using (MemoryStream memStream = new MemoryStream()) - { - input.Save(memStream, options); - - memStream.Position = 0; - using (Image output = Image.Load(memStream)) - { - Assert.NotNull(output.MetaData.ExifProfile); - } - } + // There is no alpha in Jpeg! + image.Mutate(c => c.MakeOpaque()); + + var encoder = new JpegEncoder() + { + Subsample = subsample, + Quality = quality + }; + string info = $"{subsample}-Q{quality}"; + ImageComparer comparer = GetComparer(quality, subsample); + + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png"); } } + - [Fact] - public void Encode_IgnoreMetadataIsTrue_ExifProfileIgnored() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void IgnoreMetadata_ControlsIfExifProfileIsWritten(bool ignoreMetaData) { - JpegEncoder options = new JpegEncoder() + var encoder = new JpegEncoder() { - IgnoreMetadata = true + IgnoreMetadata = ignoreMetaData }; - - TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan); - - using (Image input = testFile.CreateImage()) + + using (Image input = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage()) { - using (MemoryStream memStream = new MemoryStream()) + using (var memStream = new MemoryStream()) { - input.SaveAsJpeg(memStream, options); + input.Save(memStream, encoder); memStream.Position = 0; - using (Image output = Image.Load(memStream)) + using (var output = Image.Load(memStream)) { - Assert.Null(output.MetaData.ExifProfile); + if (ignoreMetaData) + { + Assert.Null(output.MetaData.ExifProfile); + } + else + { + Assert.NotNull(output.MetaData.ExifProfile); + } } } } } - + [Fact] - public void Encode_Quality_0_And_1_Are_Identical() + public void Quality_0_And_1_Are_Identical() { var options = new JpegEncoder { @@ -143,7 +160,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [Fact] - public void Encode_Quality_0_And_100_Are_Not_Identical() + public void Quality_0_And_100_Are_Not_Identical() { var options = new JpegEncoder { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs index df6d1ef1bb..ec4a42104a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs @@ -21,13 +21,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Bad.BadEOF, - TestImages.Jpeg.Baseline.Bad.ExifUndefType, }; public static string[] ProgressiveTestJpegs = { TestImages.Jpeg.Progressive.Fb, TestImages.Jpeg.Progressive.Progress, - TestImages.Jpeg.Progressive.Festzug, TestImages.Jpeg.Progressive.Bad.BadEOF + TestImages.Jpeg.Progressive.Festzug, TestImages.Jpeg.Progressive.Bad.BadEOF, + TestImages.Jpeg.Progressive.Bad.ExifUndefType, }; public JpegImagePostProcessorTests(ITestOutputHelper output) @@ -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 26ec454f91..0276e17085 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 6e68c43f21..6816b84656 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -28,13 +28,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk, TestImages.Jpeg.Baseline.Jpeg400, TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Testorig420, TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Baseline.Bad.BadEOF, - TestImages.Jpeg.Baseline.Bad.ExifUndefType, + TestImages.Jpeg.Baseline.MultiScanBaselineCMYK }; public static readonly string[] ProgressiveTestJpegs = { TestImages.Jpeg.Progressive.Fb, TestImages.Jpeg.Progressive.Progress, - TestImages.Jpeg.Progressive.Festzug, TestImages.Jpeg.Progressive.Bad.BadEOF + TestImages.Jpeg.Progressive.Festzug, TestImages.Jpeg.Progressive.Bad.BadEOF, + TestImages.Jpeg.Progressive.Bad.ExifUndefType, }; public static readonly string[] AllTestJpegs = BaselineTestJpegs.Concat(ProgressiveTestJpegs).ToArray(); @@ -106,7 +107,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 40b41b9cba..9e287eb2d7 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 ef9a73d12d..e18323f848 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/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 1566ddf442..0010bb41d3 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -10,36 +10,137 @@ using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.PixelFormats; using Xunit; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { - public class PngEncoderTests : FileTestBase + using SixLabors.ImageSharp.Quantizers; + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + + public class PngEncoderTests { - private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; + private const float ToleranceThresholdForPaletteEncoder = 0.01f / 100; + + /// + /// All types except Palette + /// + public static readonly TheoryData PngColorTypes = new TheoryData() + { + PngColorType.RgbWithAlpha, + PngColorType.Rgb, + PngColorType.Grayscale, + PngColorType.GrayscaleWithAlpha, + }; + + /// + /// All types except Palette + /// + public static readonly TheoryData CompressionLevels = new TheoryData() + { + 1, 2, 3, 4, 5, 6, 7, 8, 9 + }; + + public static readonly TheoryData PaletteSizes = new TheoryData() + { + 30, 55, 100, 201, 255 + }; + + public static readonly TheoryData PaletteLargeOnly = new TheoryData() + { + 80, 100, 120, 230 + }; [Theory] - [WithTestPatternImages(100, 100, PixelTypes, PngColorType.RgbWithAlpha)] - [WithTestPatternImages(100, 100, PixelTypes, PngColorType.Rgb)] - [WithTestPatternImages(100, 100, PixelTypes, PngColorType.Palette)] - [WithTestPatternImages(100, 100, PixelTypes, PngColorType.Grayscale)] - [WithTestPatternImages(100, 100, PixelTypes, PngColorType.GrayscaleWithAlpha)] - public void EncodeGeneratedPatterns(TestImageProvider provider, PngColorType pngColorType) + [WithFile(TestImages.Png.Palette8Bpp, nameof(PngColorTypes), PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(PngColorTypes), 48, 24, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(PngColorTypes), 47, 8, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(PngColorTypes), 49, 7, PixelTypes.Rgba32)] + [WithSolidFilledImages(nameof(PngColorTypes), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(PngColorTypes), 7, 5, PixelTypes.Rgba32)] + public void WorksWithDifferentSizes(TestImageProvider provider, PngColorType pngColorType) + where TPixel : struct, IPixel + { + TestPngEncoderCore(provider, pngColorType, appendPngColorType: true); + } + + [Theory] + [WithTestPatternImages(nameof(PngColorTypes), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)] + public void IsNotBoundToSinglePixelType(TestImageProvider provider, PngColorType pngColorType) + where TPixel : struct, IPixel + { + TestPngEncoderCore(provider, pngColorType, appendPixelType: true, appendPngColorType: true); + } + + [Theory] + [WithTestPatternImages(nameof(CompressionLevels), 24, 24, PixelTypes.Rgba32)] + public void WorksWithAllCompressionLevels(TestImageProvider provider, int compressionLevel) + where TPixel : struct, IPixel + { + TestPngEncoderCore(provider, PngColorType.RgbWithAlpha, compressionLevel, appendCompressionLevel: true); + } + + [Theory] + [WithFile(TestImages.Png.Palette8Bpp, nameof(PaletteLargeOnly), PixelTypes.Rgba32)] + public void PaletteColorType_WuQuantizer(TestImageProvider provider, int paletteSize) + where TPixel : struct, IPixel + { + TestPngEncoderCore(provider, PngColorType.Palette, paletteSize: paletteSize, appendPaletteSize: true); + } + + private static bool HasAlpha(PngColorType pngColorType) => + pngColorType == PngColorType.GrayscaleWithAlpha || pngColorType == PngColorType.RgbWithAlpha; + + private static void TestPngEncoderCore( + TestImageProvider provider, + PngColorType pngColorType, + int compressionLevel = 6, + int paletteSize = 0, + bool appendPngColorType = false, + bool appendPixelType = false, + bool appendCompressionLevel = false, + bool appendPaletteSize = false) where TPixel : struct, IPixel { using (Image image = provider.GetImage()) { - var options = new PngEncoder() + if (!HasAlpha(pngColorType)) { - PngColorType = pngColorType - }; - provider.Utility.TestName += "_" + pngColorType; + image.Mutate(c => c.MakeOpaque()); + } + + var encoder = new PngEncoder + { + PngColorType = pngColorType, + CompressionLevel = compressionLevel, + PaletteSize = paletteSize + }; + + string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : ""; + string compressionLevelInfo = appendCompressionLevel ? $"_C{compressionLevel}" : ""; + string paletteSizeInfo = appendPaletteSize ? $"_PaletteSize-{paletteSize}" : ""; + string debugInfo = $"{pngColorTypeInfo}{compressionLevelInfo}{paletteSizeInfo}"; + //string referenceInfo = $"{pngColorTypeInfo}"; - provider.Utility.SaveTestOutputFile(image, "png", options); + // Does DebugSave & load reference CompareToReferenceInput(): + string actualOutputFile = ((ITestImageProvider)provider).Utility.SaveTestOutputFile(image, "png", encoder, debugInfo, appendPixelType); + + IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); + string referenceOutputFile = ((ITestImageProvider)provider).Utility.GetReferenceOutputFileName("png", debugInfo, appendPixelType); + + using (var actualImage = Image.Load(actualOutputFile, referenceDecoder)) + using (var referenceImage = Image.Load(referenceOutputFile, referenceDecoder)) + { + ImageComparer comparer = pngColorType == PngColorType.Palette + ? ImageComparer.Tolerant(ToleranceThresholdForPaletteEncoder) + : ImageComparer.Exact; + + comparer.VerifySimilarity(referenceImage, actualImage); + } } } - + [Theory] - [WithBlankImages(1, 1, PixelTypes.All)] + [WithBlankImages(1, 1, PixelTypes.Rgba32)] public void WritesFileMarker(TestImageProvider provider) where TPixel : struct, IPixel { diff --git a/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs b/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs index afae9cae8c..1f2137070f 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/ImageProcessingContextTests.cs b/tests/ImageSharp.Tests/Image/ImageProcessingContextTests.cs new file mode 100644 index 0000000000..f8f7b6758a --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageProcessingContextTests.cs @@ -0,0 +1,58 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.Primitives; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class ImageProcessingContextTests + { + [Fact] + public void MutatedSizeIsAccuratePerOperation() + { + var x500 = new Size(500, 500); + var x400 = new Size(400, 400); + var x300 = new Size(300, 300); + var x200 = new Size(200, 200); + var x100 = new Size(100, 100); + using (var image = new Image(500, 500)) + { + image.Mutate(x => + x.AssertSize(x500) + .Resize(x400).AssertSize(x400) + .Resize(x300).AssertSize(x300) + .Resize(x200).AssertSize(x200) + .Resize(x100).AssertSize(x100)); + } + } + + [Fact] + public void ClonedSizeIsAccuratePerOperation() + { + var x500 = new Size(500, 500); + var x400 = new Size(400, 400); + var x300 = new Size(300, 300); + var x200 = new Size(200, 200); + var x100 = new Size(100, 100); + using (var image = new Image(500, 500)) + { + image.Clone(x => + x.AssertSize(x500) + .Resize(x400).AssertSize(x400) + .Resize(x300).AssertSize(x300) + .Resize(x200).AssertSize(x200) + .Resize(x100).AssertSize(x100)); + } + } + } + + public static class SizeAssertationExtensions + { + public static IImageProcessingContext AssertSize(this IImageProcessingContext context, Size size) + { + Assert.Equal(size, context.GetCurrentSize()); + return context; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs b/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs deleted file mode 100644 index 1ab3f2ce9f..0000000000 --- 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)] - 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/ImageOperationTests.cs b/tests/ImageSharp.Tests/ImageOperationTests.cs index 59722a84d2..a5d6d2eb96 100644 --- a/tests/ImageSharp.Tests/ImageOperationTests.cs +++ b/tests/ImageSharp.Tests/ImageOperationTests.cs @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Tests { var operations = new FakeImageOperationsProvider.FakeImageOperations(null, false); operations.ApplyProcessors(this.processor); - Assert.Contains(this.processor, operations.applied.Select(x => x.Processor)); + Assert.Contains(this.processor, operations.Applied.Select(x => x.Processor)); } public void Dispose() diff --git a/tests/ImageSharp.Tests/Memory/ArrayPoolMemoryManagerTests.cs b/tests/ImageSharp.Tests/Memory/ArrayPoolMemoryManagerTests.cs new file mode 100644 index 0000000000..a199bb319d --- /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 d662a1b3ef..565e06572b 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 026b694981..db7367d972 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 0000000000..50477cb5cf --- /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 e1efeb24e8..0000000000 --- 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 caba9a4647..0000000000 --- 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 0000000000..0d1c2beb8f --- /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 395c325461..049c4c6ba9 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/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs index edeeebd28e..5e7e9e3a79 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs @@ -272,7 +272,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void ExifTypeUndefined() { - Image image = TestFile.Create(TestImages.Jpeg.Baseline.Bad.ExifUndefType).CreateImage(); + Image image = TestFile.Create(TestImages.Jpeg.Progressive.Bad.ExifUndefType).CreateImage(); Assert.NotNull(image); ExifProfile profile = image.MetaData.ExifProfile; diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs index b95f8fdf61..50babde69a 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 945a4f502f..c0039bb37c 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 3508d544be..89e9a13b5c 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 608fcf10cf..a9127f61dc 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 d4de4c3d2e..9ca3994986 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 98dbbadaba..2e1b43c388 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs @@ -6,7 +6,8 @@ using System.IO; using System.Text; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors; -using Xunit; +using SixLabors.Primitives; + using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms @@ -38,9 +39,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms // [Fact] public void PrintWeightsData() { - var proc = new ResizeProcessor(KnownResamplers.Bicubic, 200, 200); + var size = new Size(500, 500); + var proc = new ResizeProcessor(KnownResamplers.Bicubic, 200, 200, size); - WeightsBuffer weights = proc.PrecomputeWeights(200, 500); + WeightsBuffer weights = proc.PrecomputeWeights(Configuration.Default.MemoryManager, proc.Width, size.Width); var bld = new StringBuilder(); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs index 58fc7fa802..a9c2922d48 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs @@ -1,20 +1,30 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors; + using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Transforms { + + public class PadTest : BaseImageOperationsExtensionTest { -#pragma warning disable xUnit1004 // Test methods should not be skipped - [Fact(Skip = "Skip this is a helper around resize, skip until resize can be refactord")] -#pragma warning restore xUnit1004 // Test methods should not be skipped - public void Pad_width_height_ResizeProcessorWithCorrectOPtionsSet() + [Fact] + public void PadWidthHeightResizeProcessorWithCorrectOptionsSet() { - throw new NotImplementedException("Write test here"); + int width = 500; + int height = 565; + IResampler sampler = KnownResamplers.NearestNeighbor; + + this.operations.Pad(width, height); + ResizeProcessor resizeProcessor = this.Verify>(); + + Assert.Equal(width, resizeProcessor.Width); + Assert.Equal(height, resizeProcessor.Height); + Assert.Equal(sampler, resizeProcessor.Sampler); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs index d2f5cb2c3d..b51d342cf1 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs @@ -1,23 +1,91 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.Primitives; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Transforms { + using SixLabors.ImageSharp.Processing.Processors; + public class ResizeTests : BaseImageOperationsExtensionTest { -#pragma warning disable xUnit1004 // Test methods should not be skipped - [Fact(Skip = "Skip resize tests as they need refactoring to be simpler and just pass data into the resize processor.")] -#pragma warning restore xUnit1004 // Test methods should not be skipped - public void TestMissing() + [Fact] + public void ResizeWidthAndHeight() + { + int width = 50; + int height = 100; + this.operations.Resize(width, height); + ResizeProcessor resizeProcessor = this.Verify>(); + + Assert.Equal(width, resizeProcessor.Width); + Assert.Equal(height, resizeProcessor.Height); + } + + [Fact] + public void ResizeWidthAndHeightAndSampler() + { + int width = 50; + int height = 100; + IResampler sampler = KnownResamplers.Lanczos3; + this.operations.Resize(width, height, sampler); + ResizeProcessor resizeProcessor = this.Verify>(); + + Assert.Equal(width, resizeProcessor.Width); + Assert.Equal(height, resizeProcessor.Height); + Assert.Equal(sampler, resizeProcessor.Sampler); + } + + [Fact] + public void ResizeWidthAndHeightAndSamplerAndCompand() { - // - throw new NotImplementedException("Write test here"); + int width = 50; + int height = 100; + IResampler sampler = KnownResamplers.Lanczos3; + bool compand = true; + + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + this.operations.Resize(width, height, sampler, compand); + ResizeProcessor resizeProcessor = this.Verify>(); + + Assert.Equal(width, resizeProcessor.Width); + Assert.Equal(height, resizeProcessor.Height); + Assert.Equal(sampler, resizeProcessor.Sampler); + Assert.Equal(compand, resizeProcessor.Compand); + } + + [Fact] + public void ResizeWithOptions() + { + int width = 50; + int height = 100; + IResampler sampler = KnownResamplers.Lanczos3; + bool compand = true; + ResizeMode mode = ResizeMode.Stretch; + + var resizeOptions = new ResizeOptions + { + Size = new Size(width, height), + Sampler = sampler, + Compand = compand, + Mode = mode + }; + + this.operations.Resize(resizeOptions); + ResizeProcessor resizeProcessor = this.Verify>(); + + Assert.Equal(width, resizeProcessor.Width); + Assert.Equal(height, resizeProcessor.Height); + Assert.Equal(sampler, resizeProcessor.Sampler); + Assert.Equal(compand, resizeProcessor.Compand); + + // Ensure options are not altered. + Assert.Equal(width, resizeOptions.Size.Width); + Assert.Equal(height, resizeOptions.Size.Height); + Assert.Equal(sampler, resizeOptions.Sampler); + Assert.Equal(compand, resizeOptions.Compand); + Assert.Equal(mode, resizeOptions.Mode); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs index b5a8d1265c..18fd29237c 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/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 864c963327..f1f989581f 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -85,6 +85,7 @@ namespace SixLabors.ImageSharp.Tests public static class Bad { public const string BadEOF = "Jpg/progressive/BadEofProgressive.jpg"; + public const string ExifUndefType = "Jpg/progressive/ExifUndefType.jpg"; } public static readonly string[] All = { Fb, Progress, Festzug }; @@ -95,7 +96,6 @@ namespace SixLabors.ImageSharp.Tests public static class Bad { public const string BadEOF = "Jpg/baseline/badeof.jpg"; - public const string ExifUndefType = "Jpg/baseline/ExifUndefType.jpg"; } public const string Cmyk = "Jpg/baseline/cmyk.jpg"; @@ -113,6 +113,7 @@ namespace SixLabors.ImageSharp.Tests public const string Jpeg444 = "Jpg/baseline/jpeg444.jpg"; public const string Jpeg420Small = "Jpg/baseline/jpeg420small.jpg"; public const string Testorig420 = "Jpg/baseline/testorig.jpg"; + public const string MultiScanBaselineCMYK = "Jpg/baseline/MultiScanBaselineCMYK.jpg"; public static readonly string[] All = { diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs index 0b48170879..7fc9e58d4e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs @@ -86,9 +86,10 @@ namespace SixLabors.ImageSharp.Tests NamedColors.HotPink, NamedColors.Blue }; - int p = 0; + for (int y = top; y < bottom; y++) { + int p = 0; for (int x = left; x < right; x++) { if (x % stride == 0) diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index e7dfe54881..cde8ec9e47 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -157,16 +157,77 @@ namespace SixLabors.ImageSharp.Tests return path; } + public IEnumerable GetTestOutputFileNamesMultiFrame( + int frameCount, + string extension = null, + object testOutputDetails = null, + bool appendPixelTypeToFileName = true) + { + string baseDir = this.GetTestOutputFileName("", testOutputDetails, appendPixelTypeToFileName); + + if (!Directory.Exists(baseDir)) + { + Directory.CreateDirectory(baseDir); + } + + for (int i = 0; i < frameCount; i++) + { + string filePath = $"{baseDir}/{i:D2}.{extension}"; + yield return filePath; + } + } + + public string[] SaveTestOutputFileMultiFrame( + Image image, + string extension = "png", + IImageEncoder encoder = null, + object testOutputDetails = null, + bool appendPixelTypeToFileName = true) + where TPixel : struct, IPixel + { + encoder = encoder ?? TestEnvironment.GetReferenceEncoder($"foo.{extension}"); + + string[] files = this.GetTestOutputFileNamesMultiFrame( + image.Frames.Count, + extension, + testOutputDetails, + appendPixelTypeToFileName).ToArray(); + + for (int i = 0; i < image.Frames.Count; i++) + { + using (Image frameImage = image.Frames.CloneFrame(i)) + { + string filePath = files[i]; + using (FileStream stream = File.OpenWrite(filePath)) + { + frameImage.Save(stream, encoder); + } + } + } + + return files; + } + internal string GetReferenceOutputFileName( string extension, - object settings, + object testOutputDetails, bool appendPixelTypeToFileName) { return TestEnvironment.GetReferenceOutputFileName( - this.GetTestOutputFileName(extension, settings, appendPixelTypeToFileName) + this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName) ); } + public string[] GetReferenceOutputFileNamesMultiFrame( + int frameCount, + string extension, + object testOutputDetails, + bool appendPixelTypeToFileName = true) + { + return this.GetTestOutputFileNamesMultiFrame(frameCount, extension, testOutputDetails) + .Select(TestEnvironment.GetReferenceOutputFileName).ToArray(); + } + internal void Init(string typeName, string methodName, string outputSubfolderName) { this.TestGroupName = typeName; diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs index a201b39bd2..d1270dcfd7 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 2b3cb1bcc3..6014e25334 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -16,11 +16,45 @@ namespace SixLabors.ImageSharp.Tests using System.Numerics; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; + using SixLabors.ImageSharp.MetaData; using Xunit; public static class TestImageExtensions { + /// + /// TODO: This should be a common processing method! The image.Opacity(val) multiplies the alpha channel! + /// + /// + /// + public static void MakeOpaque(this IImageProcessingContext ctx) + where TPixel : struct, IPixel + { + MemoryManager memoryManager = ctx.MemoryManager; + ctx.Apply( + img => + { + using (Buffer2D temp = memoryManager.Allocate2D(img.Width, img.Height)) + { + Span tempSpan = temp.Span; + foreach (ImageFrame frame in img.Frames) + { + Span pixelSpan = frame.GetPixelSpan(); + + PixelOperations.Instance.ToVector4(pixelSpan, tempSpan, pixelSpan.Length); + + for (int i = 0; i < tempSpan.Length; i++) + { + ref Vector4 v = ref tempSpan[i]; + v.W = 1.0f; + } + + PixelOperations.Instance.PackFromVector4(tempSpan, pixelSpan, pixelSpan.Length); + } + } + }); + } + /// /// Saves the image only when not running in the CI server. /// @@ -52,6 +86,28 @@ namespace SixLabors.ImageSharp.Tests return image; } + public static Image DebugSaveMultiFrame( + this Image image, + ITestImageProvider provider, + object testOutputDetails = null, + string extension = "png", + bool appendPixelTypeToFileName = true) + where TPixel : struct, IPixel + { + if (TestEnvironment.RunsOnCI) + { + return image; + } + + // We are running locally then we want to save it out + provider.Utility.SaveTestOutputFileMultiFrame( + image, + extension, + testOutputDetails: testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName); + return image; + } + /// /// Compares the image against the expected Reference output, throws an exception if the images are not similar enough. /// The output file should be named identically to the output produced by . @@ -118,6 +174,55 @@ namespace SixLabors.ImageSharp.Tests return image; } + public static Image CompareFirstFrameToReferenceOutput( + this Image image, + ITestImageProvider provider, + ImageComparer comparer, + object testOutputDetails = null, + string extension = "png", + bool grayscale = false, + bool appendPixelTypeToFileName = true) + where TPixel : struct, IPixel + { + using (var firstFrameOnlyImage = new Image(image.Width, image.Height)) + using (Image referenceImage = GetReferenceOutputImage( + provider, + testOutputDetails, + extension, + appendPixelTypeToFileName)) + { + firstFrameOnlyImage.Frames.AddFrame(image.Frames.RootFrame); + firstFrameOnlyImage.Frames.RemoveFrame(0); + + comparer.VerifySimilarity(referenceImage, firstFrameOnlyImage); + } + + return image; + } + + public static Image CompareToReferenceOutputMultiFrame( + this Image image, + ITestImageProvider provider, + ImageComparer comparer, + object testOutputDetails = null, + string extension = "png", + bool grayscale = false, + bool appendPixelTypeToFileName = true) + where TPixel : struct, IPixel + { + using (Image referenceImage = GetReferenceOutputImageMultiFrame( + provider, + image.Frames.Count, + testOutputDetails, + extension, + appendPixelTypeToFileName)) + { + comparer.VerifySimilarity(referenceImage, image); + } + + return image; + } + public static Image GetReferenceOutputImage(this ITestImageProvider provider, object testOutputDetails = null, string extension = "png", @@ -136,6 +241,49 @@ namespace SixLabors.ImageSharp.Tests return Image.Load(referenceOutputFile, decoder); } + public static Image GetReferenceOutputImageMultiFrame(this ITestImageProvider provider, + int frameCount, + object testOutputDetails = null, + string extension = "png", + bool appendPixelTypeToFileName = true) + where TPixel : struct, IPixel + { + string[] frameFiles = provider.Utility.GetReferenceOutputFileNamesMultiFrame( + frameCount, + extension, + testOutputDetails, + appendPixelTypeToFileName); + + var temporaryFrameImages = new List>(); + + IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(frameFiles[0]); + + foreach (string path in frameFiles) + { + if (!File.Exists(path)) + { + throw new Exception("Reference output file missing: " + path); + } + + var tempImage = Image.Load(path, decoder); + temporaryFrameImages.Add(tempImage); + } + + Image firstTemp = temporaryFrameImages[0]; + + var result = new Image(firstTemp.Width, firstTemp.Height); + + foreach (Image fi in temporaryFrameImages) + { + result.Frames.AddFrame(fi.Frames.RootFrame); + fi.Dispose(); + } + + // remove the initial empty frame: + result.Frames.RemoveFrame(0); + return result; + } + public static IEnumerable GetReferenceOutputSimilarityReports( this Image image, ITestImageProvider provider, @@ -227,20 +375,48 @@ namespace SixLabors.ImageSharp.Tests return image; } + /// + /// Loads the expected image with a reference decoder + compares it to . + /// Also performs a debug save using . + /// + internal static void VerifyEncoder(this Image image, + ITestImageProvider provider, + string extension, + object testOutputDetails, + IImageEncoder encoder, + ImageComparer customComparer = null, + bool appendPixelTypeToFileName = true, + string referenceImageExtension = null + ) + where TPixel : struct, IPixel + { + string actualOutputFile = provider.Utility.SaveTestOutputFile(image, extension, encoder, testOutputDetails, appendPixelTypeToFileName); + IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); + + using (var actualImage = Image.Load(actualOutputFile, referenceDecoder)) + { + ImageComparer comparer = customComparer ?? ImageComparer.Exact; + comparer.VerifySimilarity(actualImage, image); + } + } + internal static Image ToGrayscaleImage(this Buffer2D buffer, float scale) { var image = new Image(buffer.Width, buffer.Height); 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); } return image; } + } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceCodecTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceCodecTests.cs index dde34fcc43..c67e4e06bf 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceCodecTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceCodecTests.cs @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Tests { if (pngColorType != PngColorType.RgbWithAlpha) { - sourceImage.Mutate(c => c.Opacity(1)); + sourceImage.Mutate(c => c.MakeOpaque()); } var encoder = new PngEncoder() { PngColorType = pngColorType }; @@ -93,12 +93,12 @@ namespace SixLabors.ImageSharp.Tests using (Image original = provider.GetImage()) { - original.Mutate(c => c.Opacity(1)); + original.Mutate(c => c.MakeOpaque()); using (var sdBitmap = new System.Drawing.Bitmap(path)) { using (Image resaved = SystemDrawingBridge.FromFromRgb24SystemDrawingBitmap(sdBitmap)) { - resaved.Mutate(c => c.Opacity(1)); + resaved.Mutate(c => c.MakeOpaque()); ImageComparer comparer = ImageComparer.Exact; comparer.VerifySimilarity(original, resaved); } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index f0adeb7534..2f306e9494 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -239,8 +239,28 @@ namespace SixLabors.ImageSharp.Tests where TPixel : struct, IPixel { Assert.NotNull(provider.Utility.SourceFileOrDescription); - Image image = provider.GetImage(); - provider.Utility.SaveTestOutputFile(image, "png"); + using (Image image = provider.GetImage()) + { + provider.Utility.SaveTestOutputFile(image, "png"); + } + } + + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + public void SaveTestOutputFileMultiFrame(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + string[] files = provider.Utility.SaveTestOutputFileMultiFrame(image); + + Assert.True(files.Length > 2); + foreach (string path in files) + { + this.Output.WriteLine(path); + Assert.True(File.Exists(path)); + } + } } [Theory] diff --git a/tests/Images/External b/tests/Images/External index 376605e05b..8714b94dc4 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 376605e05bb704d425b2d17bf5b310f5376da22e +Subproject commit 8714b94dc4bab6788fcbb6254174db2b9c8f69c9 diff --git a/tests/Images/Input/Jpg/baseline/MultiScanBaselineCMYK.jpg b/tests/Images/Input/Jpg/baseline/MultiScanBaselineCMYK.jpg new file mode 100644 index 0000000000..a158e0ba2c --- /dev/null +++ b/tests/Images/Input/Jpg/baseline/MultiScanBaselineCMYK.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96f29bee2175f34b9637d684f8336bc3e5d2bb2711cef352bc9def6ed4424d04 +size 47443 diff --git a/tests/Images/Input/Jpg/baseline/ExifUndefType.jpg b/tests/Images/Input/Jpg/progressive/ExifUndefType.jpg similarity index 100% rename from tests/Images/Input/Jpg/baseline/ExifUndefType.jpg rename to tests/Images/Input/Jpg/progressive/ExifUndefType.jpg