Browse Source

Merge pull request #475 from SixLabors/feature/memory-manager

Configurable & optimized memory management
af/merge-core
Anton Firsov 8 years ago
committed by GitHub
parent
commit
b787c8b4fe
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs
  2. 15
      src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs
  3. 15
      src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs
  4. 15
      src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs
  5. 34
      src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs
  6. 2
      src/ImageSharp.Drawing/Paths/ShapePath.cs
  7. 17
      src/ImageSharp.Drawing/Paths/ShapeRegion.cs
  8. 12
      src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs
  9. 24
      src/ImageSharp.Drawing/Processors/FillProcessor.cs
  10. 129
      src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs
  11. 8
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  12. 1
      src/ImageSharp/ApplyProcessors.cs
  13. 4
      src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs
  14. 4
      src/ImageSharp/Common/Extensions/SimdUtils.cs
  15. 21
      src/ImageSharp/Common/Extensions/StreamExtensions.cs
  16. 60
      src/ImageSharp/Common/Helpers/ParallelFor.cs
  17. 6
      src/ImageSharp/Configuration.cs
  18. 5
      src/ImageSharp/DefaultInternalImageProcessorContext.cs
  19. 37
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  20. 3
      src/ImageSharp/Formats/Bmp/BmpEncoder.cs
  21. 28
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  22. 64
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  23. 4
      src/ImageSharp/Formats/Gif/GifEncoder.cs
  24. 26
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  25. 53
      src/ImageSharp/Formats/Gif/LzwDecoder.cs
  26. 58
      src/ImageSharp/Formats/Gif/LzwEncoder.cs
  27. 11
      src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs
  28. 2
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs
  29. 8
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs
  30. 15
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs
  31. 26
      src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.cs
  32. 43
      src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.tt
  33. 129
      src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.cs
  34. 18
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs
  35. 4
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/InputProcessor.cs
  36. 5
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs
  37. 192
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigHuffmanTree.cs
  38. 34
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/RgbToYCbCrTables.cs
  39. 82
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
  40. 227
      src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs
  41. 9
      src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs
  42. 88
      src/ImageSharp/Formats/Jpeg/GolangPort/Utils/OrigJpegUtils.cs
  43. 2
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsComponent.cs
  44. 8
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs
  45. 32
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs
  46. 1
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsIDCT.cs
  47. 18
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsJpegPixelArea.cs
  48. 7
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsQuantizationTables.cs
  49. 14
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs
  50. 26
      src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs
  51. 4
      src/ImageSharp/Formats/Png/PngChunk.cs
  52. 8
      src/ImageSharp/Formats/Png/PngConfigurationModule.cs
  53. 98
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  54. 4
      src/ImageSharp/Formats/Png/PngEncoder.cs
  55. 87
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  56. 7
      src/ImageSharp/IImageProcessingContext{TPixel}.cs
  57. 4
      src/ImageSharp/Image/Image.Decode.cs
  58. 10
      src/ImageSharp/Image/ImageFrame.LoadPixelData.cs
  59. 10
      src/ImageSharp/Image/ImageFrameCollection.cs
  60. 89
      src/ImageSharp/Image/ImageFrame{TPixel}.cs
  61. 393
      src/ImageSharp/Image/PixelAccessor{TPixel}.cs
  62. 249
      src/ImageSharp/Image/PixelArea{TPixel}.cs
  63. 12
      src/ImageSharp/ImageSharp.csproj
  64. 80
      src/ImageSharp/Memory/ArrayPoolMemoryManager.Buffer{T}.cs
  65. 69
      src/ImageSharp/Memory/ArrayPoolMemoryManager.CommonFactoryMethods.cs
  66. 140
      src/ImageSharp/Memory/ArrayPoolMemoryManager.cs
  67. 51
      src/ImageSharp/Memory/BasicArrayBuffer.cs
  68. 10
      src/ImageSharp/Memory/BasicByteBuffer.cs
  69. 78
      src/ImageSharp/Memory/Buffer2D{T}.cs
  70. 35
      src/ImageSharp/Memory/BufferArea{T}.cs
  71. 68
      src/ImageSharp/Memory/BufferExtensions.cs
  72. 267
      src/ImageSharp/Memory/Buffer{T}.cs
  73. 3
      src/ImageSharp/Memory/IBuffer{T}.cs
  74. 16
      src/ImageSharp/Memory/IManagedByteBuffer.cs
  75. 48
      src/ImageSharp/Memory/MemoryManager.cs
  76. 79
      src/ImageSharp/Memory/MemoryManagerExtensions.cs
  77. 80
      src/ImageSharp/Memory/PixelDataPool{T}.cs
  78. 19
      src/ImageSharp/Memory/SimpleGcMemoryManager.cs
  79. 126
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
  80. 6
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt
  81. 4
      src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs
  82. 4
      src/ImageSharp/Processing/ColorMatrix/Lomograph.cs
  83. 4
      src/ImageSharp/Processing/ColorMatrix/Polaroid.cs
  84. 4
      src/ImageSharp/Processing/Effects/BackgroundColor.cs
  85. 4
      src/ImageSharp/Processing/Overlays/Glow.cs
  86. 4
      src/ImageSharp/Processing/Overlays/Vignette.cs
  87. 4
      src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs
  88. 13
      src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs
  89. 5
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs
  90. 20
      src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs
  91. 5
      src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs
  92. 11
      src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs
  93. 12
      src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs
  94. 22
      src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs
  95. 28
      src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs
  96. 8
      src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
  97. 4
      src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs
  98. 10
      src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs
  99. 13
      src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
  100. 11
      src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs

13
src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs

@ -122,24 +122,27 @@ namespace SixLabors.ImageSharp.Drawing.Brushes
internal override void Apply(Span<float> scanline, int x, int y)
{
// Create a span for colors
using (var amountBuffer = new Buffer<float>(scanline.Length))
using (var overlay = new Buffer<TPixel>(scanline.Length))
using (IBuffer<float> amountBuffer = this.Target.MemoryManager.Allocate<float>(scanline.Length))
using (IBuffer<TPixel> overlay = this.Target.MemoryManager.Allocate<TPixel>(scanline.Length))
{
Span<float> amountSpan = amountBuffer.Span;
Span<TPixel> overlaySpan = overlay.Span;
int sourceY = (y - this.offsetY) % this.yLength;
int offsetX = x - this.offsetX;
Span<TPixel> 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<TPixel> 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);
}
}
}

15
src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs

@ -152,19 +152,24 @@ namespace SixLabors.ImageSharp.Drawing.Brushes
internal override void Apply(Span<float> scanline, int x, int y)
{
int patternY = y % this.pattern.Height;
using (var amountBuffer = new Buffer<float>(scanline.Length))
using (var overlay = new Buffer<TPixel>(scanline.Length))
MemoryManager memoryManager = this.Target.MemoryManager;
using (IBuffer<float> amountBuffer = memoryManager.Allocate<float>(scanline.Length))
using (IBuffer<TPixel> overlay = memoryManager.Allocate<TPixel>(scanline.Length))
{
Span<float> amountSpan = amountBuffer.Span;
Span<TPixel> 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<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer);
this.Blender.Blend(memoryManager, destinationRow, destinationRow, overlaySpan, amountSpan);
}
}
}

15
src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs

@ -65,21 +65,26 @@ namespace SixLabors.ImageSharp.Drawing.Brushes.Processors
/// <remarks>scanlineBuffer will be > scanlineWidth but provide and offset in case we want to share a larger buffer across runs.</remarks>
internal virtual void Apply(Span<float> scanline, int x, int y)
{
using (var amountBuffer = new Buffer<float>(scanline.Length))
using (var overlay = new Buffer<TPixel>(scanline.Length))
MemoryManager memoryManager = this.Target.MemoryManager;
using (IBuffer<float> amountBuffer = memoryManager.Allocate<float>(scanline.Length))
using (IBuffer<TPixel> overlay = memoryManager.Allocate<TPixel>(scanline.Length))
{
Span<float> amountSpan = amountBuffer.Span;
Span<TPixel> 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<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer);
this.Blender.Blend(memoryManager, destinationRow, destinationRow, overlaySpan, amountSpan);
}
}
}

15
src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs

@ -144,22 +144,27 @@ namespace SixLabors.ImageSharp.Drawing.Brushes
/// <inheritdoc />
internal override void Apply(Span<float> scanline, int x, int y)
{
using (var amountBuffer = new Buffer<float>(scanline.Length))
using (var overlay = new Buffer<TPixel>(scanline.Length))
MemoryManager memoryManager = this.Target.MemoryManager;
using (IBuffer<float> amountBuffer = memoryManager.Allocate<float>(scanline.Length))
using (IBuffer<TPixel> overlay = memoryManager.Allocate<TPixel>(scanline.Length))
{
Span<float> amountSpan = amountBuffer.Span;
Span<TPixel> 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<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer);
this.Blender.Blend(memoryManager, destinationRow, destinationRow, overlaySpan, amountSpan);
}
}
}

34
src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs

@ -61,17 +61,14 @@ namespace SixLabors.ImageSharp.Drawing.Brushes
public SolidBrushApplicator(ImageFrame<TPixel> source, TPixel color, GraphicsOptions options)
: base(source, options)
{
this.Colors = new Buffer<TPixel>(source.Width);
for (int i = 0; i < this.Colors.Length; i++)
{
this.Colors[i] = color;
}
this.Colors = source.MemoryManager.Allocate<TPixel>(source.Width);
this.Colors.Span.Fill(color);
}
/// <summary>
/// Gets the colors.
/// </summary>
protected Buffer<TPixel> Colors { get; }
protected IBuffer<TPixel> Colors { get; }
/// <summary>
/// Gets the color for a single pixel.
@ -81,7 +78,7 @@ namespace SixLabors.ImageSharp.Drawing.Brushes
/// <returns>
/// The color
/// </returns>
internal override TPixel this[int x, int y] => this.Colors[x];
internal override TPixel this[int x, int y] => this.Colors.Span[x];
/// <inheritdoc />
public override void Dispose()
@ -92,23 +89,20 @@ namespace SixLabors.ImageSharp.Drawing.Brushes
/// <inheritdoc />
internal override void Apply(Span<float> scanline, int x, int y)
{
try
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
MemoryManager memoryManager = this.Target.MemoryManager;
using (IBuffer<float> amountBuffer = memoryManager.Allocate<float>(scanline.Length))
{
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
Span<float> amountSpan = amountBuffer.Span;
using (var amountBuffer = new Buffer<float>(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);
}
}
}

2
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

17
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<PointF>(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<T> 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;
}
}
}

12
src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs

@ -73,12 +73,12 @@ namespace SixLabors.ImageSharp.Drawing.Processors
int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom);
int width = maxX - minX;
using (var amount = new Buffer<float>(width))
MemoryManager memoryManager = this.Image.GetConfiguration().MemoryManager;
using (IBuffer<float> amount = memoryManager.Allocate<float>(width))
{
for (int i = 0; i < width; i++)
{
amount[i] = this.Opacity;
}
amount.Span.Fill(this.Opacity);
Parallel.For(
minY,
@ -88,7 +88,7 @@ namespace SixLabors.ImageSharp.Drawing.Processors
{
Span<TPixel> background = source.GetPixelRowSpan(y).Slice(minX, width);
Span<TPixel> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width);
blender.Blend(background, background, foreground, amount);
blender.Blend(memoryManager, background, background, foreground, amount.Span);
});
}
}

24
src/ImageSharp.Drawing/Processors/FillProcessor.cs

@ -66,25 +66,25 @@ namespace SixLabors.ImageSharp.Drawing.Processors
int width = maxX - minX;
using (var amount = new Buffer<float>(width))
using (BrushApplicator<TPixel> applicator = this.brush.CreateApplicator(source, sourceRectangle, this.options))
using (IBuffer<float> amount = source.MemoryManager.Allocate<float>(width))
using (BrushApplicator<TPixel> 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);
});
}
}
}

129
src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs

@ -78,8 +78,6 @@ namespace SixLabors.ImageSharp.Drawing.Processors
return; // no effect inside image;
}
ArrayPool<float> arrayPool = ArrayPool<float>.Shared;
int maxIntersections = region.MaxIntersections;
float subpixelCount = 4;
@ -100,101 +98,94 @@ namespace SixLabors.ImageSharp.Drawing.Processors
using (BrushApplicator<TPixel> applicator = this.Brush.CreateApplicator(source, rect, this.Options))
{
float[] buffer = arrayPool.Rent(maxIntersections);
int scanlineWidth = maxX - minX;
using (var scanline = new Buffer<float>(scanlineWidth))
using (BasicArrayBuffer<float> buffer = source.MemoryManager.AllocateFake<float>(maxIntersections))
using (BasicArrayBuffer<float> scanline = source.MemoryManager.AllocateFake<float>(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<float>(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<float>(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);
}
}
}
}

8
src/ImageSharp/Advanced/AdvancedImageExtensions.cs

@ -88,6 +88,14 @@ namespace SixLabors.ImageSharp.Advanced
where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.GetPixelRowSpan(row);
/// <summary>
/// Gets the <see cref="MemoryManager"/> assigned to 'source'.
/// </summary>
/// <param name="source">The source image</param>
/// <returns>Returns the configuration.</returns>
internal static MemoryManager GetMemoryManager(this IConfigurable source)
=> GetConfiguration(source).MemoryManager;
/// <summary>
/// Gets the span to the backing buffer.
/// </summary>

1
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
{

4
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
{
/// <summary>
@ -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;
}
/// <inheritdoc/>

4
src/ImageSharp/Common/Extensions/SimdUtils.cs

@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp
Vector<float> magick = new Vector<float>(32768.0f);
Vector<float> scale = new Vector<float>(255f) / new Vector<float>(256f);
// need to copy to a temporal struct, because
// need to copy to a temporary struct, because
// SimdUtils.Octet.OfUInt32 temp = Unsafe.As<Vector<float>, 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<float> magick = new Vector<float>(32768.0f);
Vector<float> scale = new Vector<float>(255f) / new Vector<float>(256f);
// need to copy to a temporal struct, because
// need to copy to a temporary struct, because
// SimdUtils.Octet.OfUInt32 temp = Unsafe.As<Vector<float>, SimdUtils.Octet.OfUInt32>(ref x)
// does not work. TODO: This might be a CoreClr bug, need to ask/report
var temp = default(Octet.OfUInt32);

21
src/ImageSharp/Common/Extensions/StreamExtensions.cs

@ -29,23 +29,16 @@ namespace SixLabors.ImageSharp
}
else
{
byte[] foo = ArrayPool<byte>.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<byte>.Shared.Return(foo);
count -= bytesRead;
}
}
}

60
src/ImageSharp/Common/Helpers/ParallelFor.cs

@ -0,0 +1,60 @@
using System;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Utility methods for Parallel.For() execution. Use this instead of raw <see cref="Parallel"/> calls!
/// </summary>
internal static class ParallelFor
{
/// <summary>
/// Helper method to execute Parallel.For using the settings in <see cref="Configuration.ParallelOptions"/>
/// </summary>
public static void WithConfiguration(int fromInclusive, int toExclusive, Configuration configuration, Action<int> body)
{
Parallel.For(fromInclusive, toExclusive, configuration.ParallelOptions, body);
}
/// <summary>
/// Helper method to execute Parallel.For with temporary worker buffer shared between executing tasks.
/// The buffer is not guaranteed to be clean!
/// </summary>
/// <typeparam name="T">The value type of the buffer</typeparam>
/// <param name="fromInclusive">The start index, inclusive.</param>
/// <param name="toExclusive">The end index, exclusive.</param>
/// <param name="configuration">The <see cref="Configuration"/> used for getting the <see cref="MemoryManager"/> and <see cref="ParallelOptions"/></param>
/// <param name="bufferLength">The length of the requested parallel buffer</param>
/// <param name="body">The delegate that is invoked once per iteration.</param>
public static void WithTemporaryBuffer<T>(
int fromInclusive,
int toExclusive,
Configuration configuration,
int bufferLength,
Action<int, IBuffer<T>> body)
where T : struct
{
MemoryManager memoryManager = configuration.MemoryManager;
ParallelOptions parallelOptions = configuration.ParallelOptions;
IBuffer<T> InitBuffer()
{
return memoryManager.Allocate<T>(bufferLength);
}
void CleanUpBuffer(IBuffer<T> buffer)
{
buffer.Dispose();
}
IBuffer<T> BodyFunc(int i, ParallelLoopState state, IBuffer<T> buffer)
{
body(i, buffer);
return buffer;
}
Parallel.For(fromInclusive, toExclusive, parallelOptions, InitBuffer, BodyFunc, CleanUpBuffer);
}
}
}

6
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
/// </summary>
public IEnumerable<IImageFormat> ImageFormats => this.imageFormats;
/// <summary>
/// Gets or sets the <see cref="MemoryManager"/> that is currently in use.
/// </summary>
public MemoryManager MemoryManager { get; set; } = ArrayPoolMemoryManager.CreateDefault();
/// <summary>
/// Gets the maximum header size of all the formats.
/// </summary>

5
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
}
}
/// <inheritdoc/>
public MemoryManager MemoryManager => this.source.GetConfiguration().MemoryManager;
/// <inheritdoc/>
public Image<TPixel> Apply()
{

37
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -69,7 +69,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
private BmpInfoHeader infoHeader;
private Configuration configuration;
private readonly Configuration configuration;
private readonly MemoryManager memoryManager;
/// <summary>
/// Initializes a new instance of the <see cref="BmpDecoderCore"/> class.
@ -79,6 +81,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public BmpDecoderCore(Configuration configuration, IBmpDecoderOptions options)
{
this.configuration = configuration;
this.memoryManager = configuration.MemoryManager;
}
/// <summary>
@ -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<byte>.CreateClean(width, height))
using (var buffer = this.memoryManager.AllocateClean2D<byte>(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<byte>.CreateClean(arrayWidth + padding))
using (IManagedByteBuffer row = this.memoryManager.AllocateCleanManagedByteBuffer(arrayWidth + padding))
{
var color = default(TPixel);
var rgba = new Rgba32(0, 0, 0, 255);
Span<byte> 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<TPixel> 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<byte>(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<TPixel>
{
int padding = CalculatePadding(width, 3);
using (var row = new PixelArea<TPixel>(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<TPixel> pixelSpan = pixels.GetRowSpan(newY);
PixelOperations<TPixel>.Instance.PackFromBgr24Bytes(row.Span, pixelSpan, width);
}
}
}
@ -454,14 +460,15 @@ namespace SixLabors.ImageSharp.Formats.Bmp
where TPixel : struct, IPixel<TPixel>
{
int padding = CalculatePadding(width, 4);
using (var row = new PixelArea<TPixel>(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<TPixel> pixelSpan = pixels.GetRowSpan(newY);
PixelOperations<TPixel>.Instance.PackFromBgra32Bytes(row.Span, pixelSpan, width);
}
}
}

3
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<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
var encoder = new BmpEncoderCore(this);
var encoder = new BmpEncoderCore(this, image.GetMemoryManager());
encoder.Encode(image, stream);
}
}

28
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
/// <summary>
/// Gets or sets the number of bits per pixel.
/// </summary>
private BmpBitsPerPixel bitsPerPixel;
private readonly BmpBitsPerPixel bitsPerPixel;
private readonly MemoryManager memoryManager;
/// <summary>
/// Initializes a new instance of the <see cref="BmpEncoderCore"/> class.
/// </summary>
/// <param name="options">The encoder options</param>
public BmpEncoderCore(IBmpEncoderOptions options)
/// <param name="memoryManager">The memory manager</param>
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);
}
/// <summary>
/// Writes the 32bit color palette to the stream.
/// </summary>
@ -154,12 +164,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp
private void Write32Bit<TPixel>(EndianBinaryWriter writer, PixelAccessor<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
{
using (PixelArea<TPixel> row = new PixelArea<TPixel>(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<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.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<TPixel>(EndianBinaryWriter writer, PixelAccessor<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
{
using (PixelArea<TPixel> row = new PixelArea<TPixel>(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<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgr24Bytes(pixelSpan, row.Span, pixelSpan.Length);
writer.Write(row.Array, 0, row.Length());
}
}
}

64
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// The global color table.
/// </summary>
private Buffer<byte> globalColorTable;
private IManagedByteBuffer globalColorTable;
/// <summary>
/// The global color table length
@ -92,6 +92,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
public FrameDecodingMode DecodingMode { get; }
private MemoryManager MemoryManager => this.configuration.MemoryManager;
/// <summary>
/// Decodes the stream to the image.
/// </summary>
@ -333,18 +335,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
continue;
}
byte[] commentsBuffer = ArrayPool<byte>.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<byte>.Shared.Return(commentsBuffer);
}
}
}
@ -359,22 +355,23 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
GifImageDescriptor imageDescriptor = this.ReadImageDescriptor();
Buffer<byte> localColorTable = null;
Buffer<byte> 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<byte>.CreateClean(length);
localColorTable = this.configuration.MemoryManager.AllocateManagedByteBuffer(length, true);
this.currentStream.Read(localColorTable.Array, 0, length);
}
indices = Buffer<byte>.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<byte> 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
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="frame">The frame.</param>
/// <param name="imageWidth">Width of the image.</param>
/// <param name="imageHeight">Height of the image.</param>
private void RestoreToBackground<TPixel>(ImageFrame<TPixel> frame, int imageWidth, int imageHeight)
private void RestoreToBackground<TPixel>(ImageFrame<TPixel> frame)
where TPixel : struct, IPixel<TPixel>
{
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<TPixel> pixelAccessor = frame.Lock())
{
pixelAccessor.Reset();
}
}
else
{
using (var emptyRow = new PixelArea<TPixel>(this.restoreArea.Value.Width, ComponentOrder.Xyzw))
{
using (PixelAccessor<TPixel> 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<TPixel> 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<byte>.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);

4
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<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
var encoder = new GifEncoderCore(this);
var encoder = new GifEncoderCore(image.GetConfiguration().MemoryManager, this);
encoder.Encode(image, stream);
}
}

26
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
/// </summary>
internal sealed class GifEncoderCore
{
private readonly MemoryManager memoryManager;
/// <summary>
/// The temp buffer used to reduce allocations.
/// </summary>
@ -61,9 +64,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="options">The options for the encoder.</param>
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<byte>.Shared.Rent(colorTableLength);
var rgb = default(Rgb24);
try
using (IManagedByteBuffer colorTable = this.memoryManager.AllocateManagedByteBuffer(colorTableLength))
{
Span<byte> 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<byte>.Shared.Return(colorTable);
writer.Write(colorTable.Array, 0, colorTableLength);
}
}
@ -380,7 +382,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
private void WriteImageData<TPixel>(QuantizedImage<TPixel> image, EndianBinaryWriter writer)
where TPixel : struct, IPixel<TPixel>
{
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);
}

53
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
{
/// <summary>
@ -30,17 +32,17 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// The prefix buffer.
/// </summary>
private readonly int[] prefix;
private readonly IBuffer<int> prefix;
/// <summary>
/// The suffix buffer.
/// </summary>
private readonly int[] suffix;
private readonly IBuffer<int> suffix;
/// <summary>
/// The pixel stack buffer.
/// </summary>
private readonly int[] pixelStack;
private readonly IBuffer<int> pixelStack;
/// <summary>
/// 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 <see cref="LzwDecoder"/> class
/// and sets the stream, where the compressed data should be read from.
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="stream">The stream to read from.</param>
/// <exception cref="System.ArgumentNullException"><paramref name="stream"/> is null.</exception>
public LzwDecoder(Stream stream)
public LzwDecoder(MemoryManager memoryManager, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
this.stream = stream;
this.prefix = ArrayPool<int>.Shared.Rent(MaxStackSize);
this.suffix = ArrayPool<int>.Shared.Rent(MaxStackSize);
this.pixelStack = ArrayPool<int>.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<int>(MaxStackSize, true);
this.suffix = memoryManager.Allocate<int>(MaxStackSize, true);
this.pixelStack = memoryManager.Allocate<int>(MaxStackSize + 1, true);
}
/// <summary>
@ -116,10 +115,14 @@ namespace SixLabors.ImageSharp.Formats.Gif
int data = 0;
int first = 0;
Span<int> prefixSpan = this.prefix.Span;
Span<int> suffixSpan = this.suffix.Span;
Span<int> 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<int>.Shared.Return(this.prefix);
ArrayPool<int>.Shared.Return(this.suffix);
ArrayPool<int>.Shared.Return(this.pixelStack);
this.prefix?.Dispose();
this.suffix?.Dispose();
this.pixelStack?.Dispose();
}
this.isDisposed = true;

58
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
{
/// <summary>
@ -69,12 +71,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// The hash table.
/// </summary>
private readonly int[] hashTable;
private readonly IBuffer<int> hashTable;
/// <summary>
/// The code table.
/// </summary>
private readonly int[] codeTable;
private readonly IBuffer<int> codeTable;
/// <summary>
/// Define the storage for the packet accumulator.
@ -189,17 +191,16 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// Initializes a new instance of the <see cref="LzwEncoder"/> class.
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="indexedPixels">The array of indexed pixels.</param>
/// <param name="colorDepth">The color depth in bits.</param>
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<int>.Shared.Rent(HashSize);
this.codeTable = ArrayPool<int>.Shared.Rent(HashSize);
Array.Clear(this.hashTable, 0, HashSize);
Array.Clear(this.codeTable, 0, HashSize);
this.hashTable = memoryManager.Allocate<int>(HashSize, true);
this.codeTable = memoryManager.Allocate<int>(HashSize, true);
}
/// <summary>
@ -258,7 +259,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <param name="stream">The output stream.</param>
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
/// <summary>
/// Reset the code table.
/// </summary>
/// <param name="size">The hash size.</param>
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;
// }
}
/// <summary>
@ -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<int> hashTableSpan = this.hashTable.Span;
Span<int> 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<int>.Shared.Return(this.hashTable);
ArrayPool<int>.Shared.Return(this.codeTable);
this.hashTable?.Dispose();
this.codeTable?.Dispose();
}
this.isDisposed = true;

11
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<float> area)
{
ref byte selfBase = ref Unsafe.As<Block8x8F, byte>(ref this);
ref byte destBase = ref Unsafe.As<float, byte>(ref area.GetReferenceToOrigo());
ref byte destBase = ref Unsafe.As<float, byte>(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<float> area)
{
ref float destBase = ref area.GetReferenceToOrigo();
ref float destBase = ref area.GetReferenceToOrigin();
int destStride = area.Stride;
this.WidenCopyImpl2x2(ref destBase, 0, destStride);

2
src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs

@ -8,7 +8,7 @@ using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
/// <summary>
/// Encapsulates the implementation of processing "raw" <see cref="Buffer{T}"/>-s into Jpeg image channels.
/// Encapsulates the implementation of processing "raw" <see cref="IBuffer{T}"/>-s into Jpeg image channels.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct JpegBlockPostProcessor

8
src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs

@ -22,11 +22,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
/// <summary>
/// Initializes a new instance of the <see cref="JpegComponentPostProcessor"/> class.
/// </summary>
public JpegComponentPostProcessor(JpegImagePostProcessor imagePostProcessor, IJpegComponent component)
public JpegComponentPostProcessor(MemoryManager memoryManager, JpegImagePostProcessor imagePostProcessor, IJpegComponent component)
{
this.Component = component;
this.ImagePostProcessor = imagePostProcessor;
this.ColorBuffer = new Buffer2D<float>(imagePostProcessor.PostProcessorBufferSize);
this.ColorBuffer = memoryManager.Allocate2D<float>(
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; }
/// <summary>
/// Gets the temporal working buffer of color values.
/// Gets the temporary working buffer of color values.
/// </summary>
public Buffer2D<float> ColorBuffer { get; }

15
src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs

@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
/// <summary>
/// Temporal buffer to store a row of colors.
/// </summary>
private readonly Buffer<Vector4> rgbaBuffer;
private readonly IBuffer<Vector4> rgbaBuffer;
/// <summary>
/// The <see cref="ColorConverters.JpegColorConverter"/> corresponding to the current <see cref="JpegColorSpace"/> determined by <see cref="IRawJpegData.ColorSpace"/>.
@ -44,16 +44,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
/// <summary>
/// Initializes a new instance of the <see cref="JpegImagePostProcessor"/> class.
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="rawJpeg">The <see cref="IRawJpegData"/> representing the uncompressed spectral Jpeg data</param>
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<Vector4>(rawJpeg.ImageSizeInPixels.Width);
this.ComponentProcessors = rawJpeg.Components.Select(c => new JpegComponentPostProcessor(memoryManager, this, c)).ToArray();
this.rgbaBuffer = memoryManager.Allocate<Vector4>(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; }
/// <summary>
/// Gets the size of the temporal buffers we need to allocate into <see cref="JpegComponentPostProcessor.ColorBuffer"/>.
/// Gets the size of the temporary buffers we need to allocate into <see cref="JpegComponentPostProcessor.ColorBuffer"/>.
/// </summary>
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<TPixel> destRow = destination.GetPixelRowSpan(yy);
PixelOperations<TPixel>.Instance.PackFromVector4(this.rgbaBuffer, destRow, destination.Width);
PixelOperations<TPixel>.Instance.PackFromVector4(this.rgbaBuffer.Span, destRow, destination.Width);
}
}
}

26
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;
// <auto-generated />
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{
internal unsafe partial struct GenericBlock8x8<T>
{
#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
}
}

43
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;
// <auto-generated />
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{
internal unsafe partial struct GenericBlock8x8<T>
{
#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
}
}

129
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
{
/// <summary>
/// A generic 8x8 block implementation, useful for manipulating custom 8x8 pixel data.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct GenericBlock8x8<T>
where T : struct
{
public const int Size = 64;
public const int SizeInBytes = Size * 3;
/// <summary>
/// FOR TESTING ONLY!
/// Gets or sets a <see cref="Rgb24"/> value at the given index
/// </summary>
/// <param name="idx">The index</param>
/// <returns>The value</returns>
public T this[int idx]
{
get
{
ref T selfRef = ref Unsafe.As<GenericBlock8x8<T>, T>(ref this);
return Unsafe.Add(ref selfRef, idx);
}
set
{
ref T selfRef = ref Unsafe.As<GenericBlock8x8<T>, T>(ref this);
Unsafe.Add(ref selfRef, idx) = value;
}
}
/// <summary>
/// FOR TESTING ONLY!
/// Gets or sets a value in a row+coulumn of the 8x8 block
/// </summary>
/// <param name="x">The x position index in the row</param>
/// <param name="y">The column index</param>
/// <returns>The value</returns>
public T this[int x, int y]
{
get => this[(y * 8) + x];
set => this[(y * 8) + x] = value;
}
public void LoadAndStretchEdges<TPixel>(IPixelSource<TPixel> source, int sourceX, int sourceY)
where TPixel : struct, IPixel<TPixel>
{
var buffer = source.PixelBuffer as Buffer2D<T>;
if (buffer == null)
{
throw new InvalidOperationException("LoadAndStretchEdges<TPixels>() is only valid for TPixel == T !");
}
this.LoadAndStretchEdges(buffer, sourceX, sourceY);
}
/// <summary>
/// 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.
/// </summary>
public void LoadAndStretchEdges(Buffer2D<T> 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<T>();
int remainderXCount = 8 - width;
ref byte blockStart = ref Unsafe.As<GenericBlock8x8<T>, byte>(ref this);
ref byte imageStart = ref Unsafe.As<T, byte>(
ref Unsafe.Add(ref source.GetRowSpan(sourceY).DangerousGetPinnableReference(), sourceX));
int blockRowSizeInBytes = 8 * Unsafe.SizeOf<T>();
int imageRowSizeInBytes = source.Width * Unsafe.SizeOf<T>();
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<byte, T>(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);
}
}
/// <summary>
/// Only for on-stack instances!
/// </summary>
public Span<T> AsSpanUnsafe() => new Span<T>(Unsafe.AsPointer(ref this), Size);
}
}

18
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!
/// </summary>
internal struct Bytes : IDisposable
{
@ -48,20 +48,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// </summary>
public int UnreadableBytes;
private static readonly ArrayPool<byte> BytePool = ArrayPool<byte>.Create(BufferSize, 50);
private static readonly ArrayPool<int> IntPool = ArrayPool<int>.Create(BufferSize, 50);
/// <summary>
/// Creates a new instance of the <see cref="Bytes"/>, and initializes it's buffer.
/// </summary>
/// <returns>The bytes created</returns>
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
/// </summary>
public void Dispose()
{
if (this.Buffer != null)
{
BytePool.Return(this.Buffer);
IntPool.Return(this.BufferAsInt);
}
this.Buffer = null;
this.BufferAsInt = null;
}

4
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
{
/// <summary>
@ -43,7 +45,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
public Stream InputStream { get; }
/// <summary>
/// Gets the temporal buffer, same instance as <see cref="OrigJpegDecoderCore.Temp"/>
/// Gets the temporary buffer, same instance as <see cref="OrigJpegDecoderCore.Temp"/>
/// </summary>
public byte[] Temp { get; }

5
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs

@ -54,8 +54,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <summary>
/// Initializes <see cref="SpectralBlocks"/>
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="decoder">The <see cref="OrigJpegDecoderCore"/> instance</param>
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<Block8x8>.CreateClean(this.SizeInBlocks);
this.SpectralBlocks = memoryManager.Allocate2D<Block8x8>(this.SizeInBlocks.Width, this.SizeInBlocks.Height, true);
}
/// <summary>

192
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
{
/// <summary>
/// Represents a Huffman tree
/// </summary>
internal struct OrigHuffmanTree : IDisposable
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct OrigHuffmanTree
{
/// <summary>
/// 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.
/// </summary>
public int[] Lut;
public FixedInt32Buffer256 Lut;
/// <summary>
/// Gets the the decoded values, sorted by their encoding.
/// </summary>
public int[] Values;
public FixedInt32Buffer256 Values;
/// <summary>
/// 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.
/// </summary>
public int[] MinCodes;
public FixedInt32Buffer16 MinCodes;
/// <summary>
/// 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.
/// </summary>
public int[] MaxCodes;
public FixedInt32Buffer16 MaxCodes;
/// <summary>
/// Gets the array of indices. Indices[i] is the index into Values of MinCodes[i].
/// </summary>
public int[] Indices;
private static readonly ArrayPool<int> IntPool256 = ArrayPool<int>.Create(MaxNCodes, 50);
private static readonly ArrayPool<byte> BytePool256 = ArrayPool<byte>.Create(MaxNCodes, 50);
private static readonly ArrayPool<int> CodesPool16 = ArrayPool<int>.Create(MaxCodeLength, 50);
public FixedInt32Buffer16 Indices;
/// <summary>
/// Creates and initializes an array of <see cref="OrigHuffmanTree" /> instances of size <see cref="NumberOfTrees" />
@ -103,35 +100,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <returns>An array of <see cref="OrigHuffmanTree" /> instances representing the Huffman tables</returns>
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;
}
/// <summary>
/// Disposes the underlying buffers
/// </summary>
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];
}
/// <summary>
/// Internal part of the DHT processor, whatever does it mean
/// </summary>
/// <param name="inputProcessor">The decoder instance</param>
/// <param name="defineHuffmanTablesData">The temporal buffer that holds the data that has been read from the Jpeg stream</param>
/// <param name="defineHuffmanTablesData">The temporary buffer that holds the data that has been read from the Jpeg stream</param>
/// <param name="remaining">Remaining bits</param>
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
/// <param name="code">The code</param>
/// <param name="codeLength">The code length</param>
/// <returns>The value</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetValue(int code, int codeLength)
{
return this.Values[this.Indices[codeLength] + code - this.MinCodes[codeLength]];
}
/// <summary>
/// Initializes the Huffman tree
/// </summary>
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<FixedInt32Buffer256, int>(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<FixedInt32Buffer16, int>(ref this);
return Unsafe.Add(ref self, idx);
}
}
}
}
}

34
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
{
/// <summary>
/// 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)!
/// </summary>
internal unsafe struct RgbToYCbCrTables
{
@ -91,27 +94,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder
}
/// <summary>
/// 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.
/// </summary>
/// <param name="yBlockRaw">The The luminance block.</param>
/// <param name="cbBlockRaw">The red chroma block.</param>
/// <param name="crBlockRaw">The blue chroma block.</param>
/// <param name="tables">The reference to the tables instance.</param>
/// <param name="index">The current index.</param>
/// <param name="r">The red value.</param>
/// <param name="g">The green value.</param>
/// <param name="b">The blue value.</param>
[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<RgbToYCbCrTables, int>(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)]

82
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
{
/// <summary>
/// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks.
/// </summary>
/// <typeparam name="TPixel">The pixel type to work on</typeparam>
internal struct YCbCrForwardConverter<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// The Y component
/// </summary>
public Block8x8F Y;
/// <summary>
/// The Cb component
/// </summary>
public Block8x8F Cb;
/// <summary>
/// The Cr component
/// </summary>
public Block8x8F Cr;
/// <summary>
/// The color conversion tables
/// </summary>
private RgbToYCbCrTables colorTables;
/// <summary>
/// Temporal 8x8 block to hold TPixel data
/// </summary>
private GenericBlock8x8<TPixel> pixelBlock;
/// <summary>
/// Temporal RGB block
/// </summary>
private GenericBlock8x8<Rgb24> rgbBlock;
public static YCbCrForwardConverter<TPixel> Create()
{
var result = default(YCbCrForwardConverter<TPixel>);
result.colorTables = RgbToYCbCrTables.Create();
return result;
}
/// <summary>
/// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (<see cref="Y"/>, <see cref="Cb"/>, <see cref="Cr"/>)
/// </summary>
public void Convert(IPixelSource<TPixel> pixels, int x, int y)
{
this.pixelBlock.LoadAndStretchEdges(pixels, x, y);
Span<Rgb24> rgbSpan = this.rgbBlock.AsSpanUnsafe();
PixelOperations<TPixel>.Instance.ToRgb24(this.pixelBlock.AsSpanUnsafe(), rgbSpan, 64);
ref float yBlockStart = ref Unsafe.As<Block8x8F, float>(ref this.Y);
ref float cbBlockStart = ref Unsafe.As<Block8x8F, float>(ref this.Cb);
ref float crBlockStart = ref Unsafe.As<Block8x8F, float>(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));
}
}
}
}

227
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<TPixel> 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
}
}
/// <summary>
/// Converts the 8x8 region of the image whose top-left corner is x,y to its YCbCr values.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor.</param>
/// <param name="tables">The reference to the tables instance.</param>
/// <param name="x">The x-position within the image.</param>
/// <param name="y">The y-position within the image.</param>
/// <param name="yBlock">The luminance block.</param>
/// <param name="cbBlock">The red chroma block.</param>
/// <param name="crBlock">The blue chroma block.</param>
/// <param name="rgbBytes">Temporal <see cref="PixelArea{TPixel}"/> provided by the caller</param>
private static void ToYCbCr<TPixel>(
PixelAccessor<TPixel> pixels,
RgbToYCbCrTables* tables,
int x,
int y,
Block8x8F* yBlock,
Block8x8F* cbBlock,
Block8x8F* crBlock,
PixelArea<TPixel> rgbBytes)
where TPixel : struct, IPixel<TPixel>
{
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;
}
}
}
/// <summary>
/// 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
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
private void Encode444<TPixel>(PixelAccessor<TPixel> pixels)
private void Encode444<TPixel>(Image<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
{
// TODO: Need a JpegScanEncoder<TPixel> 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<TPixel>)
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<TPixel> pixelConverter = YCbCrForwardConverter<TPixel>.Create();
for (int y = 0; y < pixels.Height; y += 8)
{
using (PixelArea<TPixel> rgbBytes = new PixelArea<TPixel>(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<byte>.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<byte>.Shared.Return(dqt);
}
/// <summary>
@ -858,8 +795,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// Writes the StartOfScan marker.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
private void WriteStartOfScan<TPixel>(PixelAccessor<TPixel> pixels)
/// <param name="image">The pixel accessor providing access to the image pixels.</param>
private void WriteStartOfScan<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
// TODO: Need a JpegScanEncoder<TPixel> 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
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
private void Encode420<TPixel>(PixelAccessor<TPixel> pixels)
private void Encode420<TPixel>(Image<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
{
// TODO: Need a JpegScanEncoder<TPixel> 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<TPixel>.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<TPixel> rgbBytes = new PixelArea<TPixel>(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);
}
}
}

9
src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs

@ -211,11 +211,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// <inheritdoc />
public void Dispose()
{
for (int i = 0; i < this.HuffmanTrees.Length; i++)
{
this.HuffmanTrees[i].Dispose();
}
if (this.Components != null)
{
foreach (OrigComponent component in this.Components)
@ -680,7 +675,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
foreach (OrigComponent component in this.Components)
{
component.InitializeDerivedData(this);
component.InitializeDerivedData(this.configuration.MemoryManager, this);
}
this.ColorSpace = this.DeduceJpegColorSpace();
@ -788,7 +783,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
private Image<TPixel> PostProcessIntoImage<TPixel>()
where TPixel : struct, IPixel<TPixel>
{
using (var postProcessor = new JpegImagePostProcessor(this))
using (var postProcessor = new JpegImagePostProcessor(this.configuration.MemoryManager, this))
{
var image = new Image<TPixel>(this.configuration, this.ImageWidth, this.ImageHeight, this.MetaData);
postProcessor.PostProcess(image.Frames.RootFrame);

88
src/ImageSharp/Formats/Jpeg/GolangPort/Utils/OrigJpegUtils.cs

@ -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
{
/// <summary>
/// Jpeg specific utilities and extension methods
/// </summary>
internal static class OrigJpegUtils
{
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="pixels">The input pixel acessor</param>
/// <param name="dest">The destination <see cref="PixelArea{TPixel}"/></param>
/// <param name="sourceY">Starting Y coord</param>
/// <param name="sourceX">Starting X coord</param>
public static void CopyRGBBytesStretchedTo<TPixel>(
this PixelAccessor<TPixel> pixels,
PixelArea<TPixel> dest,
int sourceY,
int sourceX)
where TPixel : struct, IPixel<TPixel>
{
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<TPixel>(PixelArea<TPixel> area, int fromX, int fromY)
where TPixel : struct, IPixel<TPixel>
{
return fromX <= 0 || fromY <= 0 || fromX >= area.Width || fromY >= area.Height;
}
private static void StretchPixels<TPixel>(PixelArea<TPixel> area, int fromX, int fromY)
where TPixel : struct, IPixel<TPixel>
{
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<TPixel>(PixelArea<TPixel> area, int y)
where TPixel : struct, IPixel<TPixel>
{
return ref Unsafe.As<byte, RGB24>(ref area.GetRowSpan(y).DangerousGetPinnableReference());
}
[StructLayout(LayoutKind.Sequential, Size = 3)]
private struct RGB24
{
}
}
}

2
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsComponent.cs

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// <summary>
/// Gets or sets the output
/// </summary>
public Buffer<short> Output;
public IBuffer<short> Output;
/// <summary>
/// Gets or sets the scaling factors

8
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs

@ -15,10 +15,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary>
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
/// <summary>
/// Gets the block data
/// </summary>
public Buffer<short> BlockData { get; private set; }
public IBuffer<short> BlockData { get; private set; }
/// <inheritdoc />
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<short>.CreateClean(blocksBufferSize);
this.BlockData = this.memoryManager.Allocate<short>(blocksBufferSize, true);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]

32
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs

@ -12,32 +12,34 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary>
internal struct PdfJsHuffmanTable : IDisposable
{
private Buffer<short> lookahead;
private Buffer<short> valOffset;
private Buffer<long> maxcode;
private Buffer<byte> huffval;
private BasicArrayBuffer<short> lookahead;
private BasicArrayBuffer<short> valOffset;
private BasicArrayBuffer<long> maxcode;
private IManagedByteBuffer huffval;
/// <summary>
/// Initializes a new instance of the <see cref="PdfJsHuffmanTable"/> struct.
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="lengths">The code lengths</param>
/// <param name="values">The huffman values</param>
public PdfJsHuffmanTable(byte[] lengths, byte[] values)
public PdfJsHuffmanTable(MemoryManager memoryManager, byte[] lengths, byte[] values)
{
this.lookahead = Buffer<short>.CreateClean(256);
this.valOffset = Buffer<short>.CreateClean(18);
this.maxcode = Buffer<long>.CreateClean(18);
// TODO: Replace FakeBuffer<T> usages with standard or array orfixed-sized arrays
this.lookahead = memoryManager.AllocateFake<short>(256);
this.valOffset = memoryManager.AllocateFake<short>(18);
this.maxcode = memoryManager.AllocateFake<long>(18);
using (var huffsize = Buffer<short>.CreateClean(257))
using (var huffcode = Buffer<short>.CreateClean(257))
using (IBuffer<short> huffsize = memoryManager.Allocate<short>(257))
using (IBuffer<short> huffcode = memoryManager.Allocate<short>(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<byte>.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;

1
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
{

18
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsJpegPixelArea.cs

@ -14,22 +14,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary>
internal struct PdfJsJpegPixelArea : IDisposable
{
private readonly MemoryManager memoryManager;
private readonly int imageWidth;
private readonly int imageHeight;
private Buffer<byte> componentData;
private IBuffer<byte> componentData;
private int rowStride;
/// <summary>
/// Initializes a new instance of the <see cref="PdfJsJpegPixelArea"/> struct.
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="imageWidth">The image width</param>
/// <param name="imageHeight">The image height</param>
/// <param name="numberOfComponents">The number of components</param>
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<byte>(width * height * numberOfComponents);
Span<byte> componentDataSpan = this.componentData;
this.componentData = this.memoryManager.Allocate<byte>(width * height * numberOfComponents);
Span<byte> componentDataSpan = this.componentData.Span;
const uint Mask3Lsb = 0xFFFFFFF8; // Used to clear the 3 LSBs
using (var xScaleBlockOffset = new Buffer<int>(width))
using (IBuffer<int> xScaleBlockOffset = this.memoryManager.Allocate<int>(width))
{
Span<int> xScaleBlockOffsetSpan = xScaleBlockOffset;
Span<int> 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<short> output = component.Output;
Span<short> output = component.Output.Span;
int blocksPerScanline = (component.BlocksPerLine + 1) << 3;
// Precalculate the xScaleBlockOffset

7
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsQuantizationTables.cs

@ -12,6 +12,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary>
internal sealed class PdfJsQuantizationTables : IDisposable
{
public PdfJsQuantizationTables(MemoryManager memoryManager)
{
this.Tables = memoryManager.Allocate2D<short>(64, 4);
}
/// <summary>
/// Gets the ZigZag scan table
/// </summary>
@ -49,8 +54,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
get; set;
}
= new Buffer2D<short>(64, 4);
/// <inheritdoc/>
public void Dispose()
{

14
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<short> 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<short> 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<short> 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)]

26
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<byte>.CreateClean(256))
using (IManagedByteBuffer huffmanData = this.configuration.MemoryManager.AllocateCleanManagedByteBuffer(256))
{
Span<byte> 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<byte>.CreateClean(17))
using (IManagedByteBuffer codeLengths = this.configuration.MemoryManager.AllocateCleanManagedByteBuffer(17))
{
Span<byte> 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<byte>.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<short>.CreateClean(64))
using (var multiplicationBuffer = Buffer<short>.CreateClean(64))
using (IBuffer<short> computationBuffer = this.configuration.MemoryManager.Allocate<short>(64, true))
using (IBuffer<short> multiplicationBuffer = this.configuration.MemoryManager.Allocate<short>(64, true))
{
Span<short> quantizationTable = this.quantizationTables.Tables.GetRowSpan(frameComponent.QuantizationTableIndex);
Span<short> computationBufferSpan = computationBuffer;
Span<short> 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<short> multiplierSpan = multiplicationBuffer;
Span<short> multiplierSpan = multiplicationBuffer.Span;
// for (int i = 0; i < 64; i++)
// {
@ -823,7 +825,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <param name="values">The values</param>
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)]

4
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
{
/// <summary>
@ -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.
/// </summary>
public byte[] Data { get; set; }
public IManagedByteBuffer Data { get; set; }
/// <summary>
/// Gets or sets a CRC (Cyclic Redundancy Check) calculated on the preceding bytes in the chunk,

8
src/ImageSharp/Formats/Png/PngConfigurationModule.cs

@ -9,11 +9,11 @@ namespace SixLabors.ImageSharp.Formats.Png
public sealed class PngConfigurationModule : IConfigurationModule
{
/// <inheritdoc/>
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());
}
}
}

98
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -137,12 +137,12 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Previous scanline processed
/// </summary>
private Buffer<byte> previousScanline;
private IManagedByteBuffer previousScanline;
/// <summary>
/// The current scanline that is being processed
/// </summary>
private Buffer<byte> scanline;
private IManagedByteBuffer scanline;
/// <summary>
/// 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;
/// <summary>
/// Decodes the stream to the image.
/// </summary>
@ -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<byte>.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<byte>.Shared.Return(currentChunk.Data);
ArrayPool<byte>.Shared.Return(currentChunk.Data.Array);
}
}
}
@ -434,8 +437,8 @@ namespace SixLabors.ImageSharp.Formats.Png
this.bytesPerSample = this.header.BitDepth / 8;
}
this.previousScanline = Buffer<byte>.CreateClean(this.bytesPerScanline);
this.scanline = Buffer<byte>.CreateClean(this.bytesPerScanline);
this.previousScanline = this.MemoryManager.AllocateCleanManagedByteBuffer(this.bytesPerScanline);
this.scanline = this.configuration.MemoryManager.AllocateCleanManagedByteBuffer(this.bytesPerScanline);
}
/// <summary>
@ -555,7 +558,8 @@ namespace SixLabors.ImageSharp.Formats.Png
}
this.currentRowBytesRead = 0;
var filterType = (FilterType)this.scanline[0];
Span<byte> 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<byte>(length))
using (IBuffer<byte> compressed = this.configuration.MemoryManager.Allocate<byte>(length))
{
// TODO: Should we use pack from vector here instead?
this.From16BitTo8Bit(scanlineBuffer, compressed, length);
PixelOperations<TPixel>.Instance.PackFromRgb24Bytes(compressed, rowSpan, this.header.Width);
this.From16BitTo8Bit(scanlineBuffer, compressed.Span, length);
PixelOperations<TPixel>.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<byte>(length))
using (IBuffer<byte> compressed = this.configuration.MemoryManager.Allocate<byte>(length))
{
// TODO: Should we use pack from vector here instead?
this.From16BitTo8Bit(scanlineBuffer, compressed, length);
this.From16BitTo8Bit(scanlineBuffer, compressed.Span, length);
Span<Rgb24> rgb24Span = compressed.Span.NonPortableCast<byte, Rgb24>();
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<byte>(length))
using (IBuffer<byte> compressed = this.configuration.MemoryManager.Allocate<byte>(length))
{
// TODO: Should we use pack from vector here instead?
this.From16BitTo8Bit(scanlineBuffer, compressed, length);
PixelOperations<TPixel>.Instance.PackFromRgba32Bytes(compressed, rowSpan, this.header.Width);
this.From16BitTo8Bit(scanlineBuffer, compressed.Span, length);
PixelOperations<TPixel>.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<byte>(length))
using (IBuffer<byte> compressed = this.configuration.MemoryManager.Allocate<byte>(length))
{
Span<byte> 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<byte>(length))
using (IBuffer<byte> compressed = this.configuration.MemoryManager.Allocate<byte>(length))
{
Span<byte> 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<byte>.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);
}
/// <summary>
@ -1350,7 +1358,7 @@ namespace SixLabors.ImageSharp.Formats.Png
private void SwapBuffers()
{
Buffer<byte> temp = this.previousScanline;
IManagedByteBuffer temp = this.previousScanline;
this.previousScanline = this.scanline;
this.scanline = temp;
}

4
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<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
using (var encoder = new PngEncoderCore(this))
using (var encoder = new PngEncoderCore(image.GetMemoryManager(), this))
{
encoder.Encode(image, stream);
}

87
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -19,6 +19,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
internal sealed class PngEncoderCore : IDisposable
{
private readonly MemoryManager memoryManager;
/// <summary>
/// The maximum block size, defaults at 64k for uncompressed blocks.
/// </summary>
@ -72,37 +74,37 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// The previous scanline.
/// </summary>
private Buffer<byte> previousScanline;
private IManagedByteBuffer previousScanline;
/// <summary>
/// The raw scanline.
/// </summary>
private Buffer<byte> rawScanline;
private IManagedByteBuffer rawScanline;
/// <summary>
/// The filtered scanline result.
/// </summary>
private Buffer<byte> result;
private IManagedByteBuffer result;
/// <summary>
/// The buffer for the sub filter
/// </summary>
private Buffer<byte> sub;
private IManagedByteBuffer sub;
/// <summary>
/// The buffer for the up filter
/// </summary>
private Buffer<byte> up;
private IManagedByteBuffer up;
/// <summary>
/// The buffer for the average filter
/// </summary>
private Buffer<byte> average;
private IManagedByteBuffer average;
/// <summary>
/// The buffer for the paeth filter
/// </summary>
private Buffer<byte> paeth;
private IManagedByteBuffer paeth;
/// <summary>
/// The png color type.
@ -147,9 +149,11 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Initializes a new instance of the <see cref="PngEncoderCore"/> class.
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="options">The options for influencing the encoder</param>
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<TPixel>.Instance.ToRgba32Bytes(rowSpan, this.rawScanline, this.width);
PixelOperations<TPixel>.Instance.ToRgba32Bytes(rowSpan, this.rawScanline.Span, this.width);
}
else
{
PixelOperations<TPixel>.Instance.ToRgb24Bytes(rowSpan, this.rawScanline, this.width);
PixelOperations<TPixel>.Instance.ToRgb24Bytes(rowSpan, this.rawScanline.Span, this.width);
}
}
@ -369,13 +373,14 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="rowSpan">The row span.</param>
/// <param name="row">The row.</param>
/// <returns>The <see cref="T:byte[]"/></returns>
private Buffer<byte> EncodePixelRow<TPixel>(Span<TPixel> rowSpan, int row)
private IManagedByteBuffer EncodePixelRow<TPixel>(Span<TPixel> rowSpan, int row)
where TPixel : struct, IPixel<TPixel>
{
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.
/// </summary>
/// <returns>The <see cref="T:byte[]"/></returns>
private Buffer<byte> GetOptimalFilteredScanline()
private IManagedByteBuffer GetOptimalFilteredScanline()
{
Span<byte> scanSpan = this.rawScanline.Span;
Span<byte> 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<byte> 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<byte>.Shared.Rent(colorTableLength);
byte[] alphaTable = ArrayPool<byte>.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<byte> colorTableSpan = colorTable.Span;
Span<byte> 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<byte>.Shared.Return(colorTable);
ArrayPool<byte>.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<byte>.CreateClean(this.bytesPerScanline);
this.rawScanline = Buffer<byte>.CreateClean(this.bytesPerScanline);
this.result = Buffer<byte>.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<byte>.CreateClean(resultLength);
this.up = Buffer<byte>.CreateClean(resultLength);
this.average = Buffer<byte>.CreateClean(resultLength);
this.paeth = Buffer<byte>.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<byte> r = this.EncodePixelRow(pixels.GetPixelRowSpan(y), y);
IManagedByteBuffer r = this.EncodePixelRow(pixels.GetPixelRowSpan(y), y);
deflateStream.Write(r.Array, 0, resultLength);
Buffer<byte> temp = this.rawScanline;
IManagedByteBuffer temp = this.rawScanline;
this.rawScanline = this.previousScanline;
this.previousScanline = temp;
}

7
src/ImageSharp/IImageProcessingContext{TPixel}.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.Primitives;
@ -14,6 +15,12 @@ namespace SixLabors.ImageSharp
public interface IImageProcessingContext<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Gets a reference to the <see cref="MemoryManager" /> used to allocate buffers
/// for this context.
/// </summary>
MemoryManager MemoryManager { get; }
/// <summary>
/// Adds the processor to the current set of image operations to be applied.
/// </summary>

4
src/ImageSharp/Image/Image.Decode.cs

@ -29,12 +29,12 @@ namespace SixLabors.ImageSharp
return null;
}
using (var buffer = new Buffer<byte>(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);
}
}

10
src/ImageSharp/Image/ImageFrame.LoadPixelData.cs

@ -20,30 +20,32 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array in <typeparamref name="TPixel"/> format.
/// </summary>
/// <param name="memoryManager">The memory manager to use for allocations</param>
/// <param name="data">The byte array containing image data.</param>
/// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static ImageFrame<TPixel> LoadPixelData<TPixel>(Span<byte> data, int width, int height)
public static ImageFrame<TPixel> LoadPixelData<TPixel>(MemoryManager memoryManager, Span<byte> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
=> LoadPixelData(data.NonPortableCast<byte, TPixel>(), width, height);
=> LoadPixelData(memoryManager, data.NonPortableCast<byte, TPixel>(), width, height);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the raw <typeparamref name="TPixel"/> data.
/// </summary>
/// <param name="memoryManager">The memory manager to use for allocations</param>
/// <param name="data">The Span containing the image Pixel data.</param>
/// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static ImageFrame<TPixel> LoadPixelData<TPixel>(Span<TPixel> data, int width, int height)
public static ImageFrame<TPixel> LoadPixelData<TPixel>(MemoryManager memoryManager, Span<TPixel> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
int count = width * height;
Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data));
var image = new ImageFrame<TPixel>(width, height);
var image = new ImageFrame<TPixel>(memoryManager, width, height);
SpanHelper.Copy(data, image.GetPixelSpan(), count);
return image;

10
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<TPixel>(width, height));
this.frames.Add(new ImageFrame<TPixel>(parent.GetConfiguration().MemoryManager, width, height));
}
internal ImageFrameCollection(Image<TPixel> parent, IEnumerable<ImageFrame<TPixel>> frames)
@ -80,7 +80,11 @@ namespace SixLabors.ImageSharp
/// <inheritdoc/>
public ImageFrame<TPixel> AddFrame(TPixel[] data)
{
var frame = ImageFrame.LoadPixelData(new Span<TPixel>(data), this.RootFrame.Width, this.RootFrame.Height);
var frame = ImageFrame.LoadPixelData(
this.parent.GetMemoryManager(),
new Span<TPixel>(data),
this.RootFrame.Width,
this.RootFrame.Height);
this.frames.Add(frame);
return frame;
}
@ -143,7 +147,7 @@ namespace SixLabors.ImageSharp
/// <inheritdoc/>
public ImageFrame<TPixel> CreateFrame()
{
var frame = new ImageFrame<TPixel>(this.RootFrame.Width, this.RootFrame.Height);
var frame = new ImageFrame<TPixel>(this.parent.GetConfiguration().MemoryManager, this.RootFrame.Width, this.RootFrame.Height);
this.frames.Add(frame);
return frame;
}

89
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<TPixel> : IPixelSource<TPixel>, IDisposable
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// The image pixels. Not private as Buffer2D requires an array in its constructor.
/// </summary>
private Buffer2D<TPixel> pixelBuffer;
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}" /> class.
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
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())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}" /> class.
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="metaData">The meta data.</param>
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<TPixel>.CreateClean(width, height);
this.MemoryManager = memoryManager;
this.PixelBuffer = memoryManager.AllocateClean2D<TPixel>(width, height);
this.MetaData = metaData;
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}" /> class.
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="size">The <see cref="Size"/> of the frame.</param>
/// <param name="metaData">The meta data.</param>
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)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}" /> class.
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="source">The source.</param>
internal ImageFrame(ImageFrame<TPixel> source)
internal ImageFrame(MemoryManager memoryManager, ImageFrame<TPixel> source)
{
this.pixelBuffer = new Buffer2D<TPixel>(source.pixelBuffer.Width, source.pixelBuffer.Height);
source.pixelBuffer.Span.CopyTo(this.pixelBuffer.Span);
this.MemoryManager = memoryManager;
this.PixelBuffer = memoryManager.Allocate2D<TPixel>(source.PixelBuffer.Width, source.PixelBuffer.Height);
source.PixelBuffer.Span.CopyTo(this.PixelBuffer.Span);
this.MetaData = source.MetaData.Clone();
}
/// <summary>
/// Gets the <see cref="MemoryManager" /> to use for buffer allocations.
/// </summary>
public MemoryManager MemoryManager { get; }
/// <summary>
/// Gets the image pixels. Not private as Buffer2D requires an array in its constructor.
/// </summary>
internal Buffer2D<TPixel> PixelBuffer { get; private set; }
/// <inheritdoc/>
Buffer2D<TPixel> IPixelSource<TPixel>.PixelBuffer => this.pixelBuffer;
Buffer2D<TPixel> IPixelSource<TPixel>.PixelBuffer => this.PixelBuffer;
/// <summary>
/// Gets the width.
/// </summary>
public int Width => this.pixelBuffer.Width;
public int Width => this.PixelBuffer.Width;
/// <summary>
/// Gets the height.
/// </summary>
public int Height => this.pixelBuffer.Height;
public int Height => this.PixelBuffer.Height;
/// <summary>
/// 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];
}
/// <summary>
@ -139,12 +152,26 @@ namespace SixLabors.ImageSharp
}
/// <summary>
/// Copies the pixels to another <see cref="PixelAccessor{TPixel}"/> of the same size.
/// Copies the pixels to a <see cref="PixelAccessor{TPixel}"/> of the same size.
/// </summary>
/// <param name="target">The target pixel buffer accessor.</param>
internal void CopyTo(PixelAccessor<TPixel> target)
{
SpanHelper.Copy(this.GetPixelSpan(), target.PixelBuffer.Span);
this.CopyTo(target.PixelBuffer);
}
/// <summary>
/// Copies the pixels to a <see cref="Buffer2D{TPixel}"/> of the same size.
/// </summary>
/// <param name="target">The target pixel buffer accessor.</param>
internal void CopyTo(Buffer2D<TPixel> target)
{
if (this.Size() != target.Size())
{
throw new ArgumentException("ImageFrame<T>.CopyTo(): target must be of the same size!", nameof(target));
}
SpanHelper.Copy(this.GetPixelSpan(), target.Span);
}
/// <summary>
@ -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<TPixel> newPixels = pixelSource.SwapBufferOwnership(this.pixelBuffer);
this.pixelBuffer = newPixels;
Buffer2D<TPixel> newPixels = pixelSource.SwapBufferOwnership(this.PixelBuffer);
this.PixelBuffer = newPixels;
}
/// <summary>
@ -168,9 +195,9 @@ namespace SixLabors.ImageSharp
{
Guard.NotNull(pixelSource, nameof(pixelSource));
Buffer2D<TPixel> temp = this.pixelBuffer;
this.pixelBuffer = pixelSource.pixelBuffer;
pixelSource.pixelBuffer = temp;
Buffer2D<TPixel> temp = this.PixelBuffer;
this.PixelBuffer = pixelSource.PixelBuffer;
pixelSource.PixelBuffer = temp;
}
/// <summary>
@ -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<Vector4, Vector4> scaleFunc = PackedPixelConverterHelper.ComputeScaleFunction<TPixel, TPixel2>();
var target = new ImageFrame<TPixel2>(this.Width, this.Height, this.MetaData.Clone());
var target = new ImageFrame<TPixel2>(this.MemoryManager, this.Width, this.Height, this.MetaData.Clone());
using (PixelAccessor<TPixel> pixels = this.Lock())
using (PixelAccessor<TPixel2> targetPixels = target.Lock())
@ -240,7 +267,7 @@ namespace SixLabors.ImageSharp
/// <returns>The <see cref="ImageFrame{TPixel}"/></returns>
internal ImageFrame<TPixel> Clone()
{
return new ImageFrame<TPixel>(this);
return new ImageFrame<TPixel>(this.MemoryManager, this);
}
/// <inheritdoc/>

393
src/ImageSharp/Image/PixelAccessor{TPixel}.cs

@ -17,25 +17,6 @@ namespace SixLabors.ImageSharp
internal sealed class PixelAccessor<TPixel> : IDisposable, IBuffer2D<TPixel>
where TPixel : struct, IPixel<TPixel>
{
#pragma warning disable SA1401 // Fields must be private
/// <summary>
/// The <see cref="Buffer{T}"/> containing the pixel data.
/// </summary>
internal Buffer2D<TPixel> PixelBuffer;
private bool ownedBuffer;
#pragma warning restore SA1401 // Fields must be private
/// <summary>
/// A value indicating whether this instance of the given entity has been disposed.
/// </summary>
/// <value><see langword="true"/> if this instance has been disposed; otherwise, <see langword="false"/>.</value>
/// <remarks>
/// 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.
/// </remarks>
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="PixelAccessor{TPixel}"/> class.
/// </summary>
@ -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);
}
/// <summary>
/// Initializes a new instance of the <see cref="PixelAccessor{TPixel}"/> class.
/// </summary>
/// <param name="width">The width of the image represented by the pixel buffer.</param>
/// <param name="height">The height of the image represented by the pixel buffer.</param>
public PixelAccessor(int width, int height)
: this(width, height, Buffer2D<TPixel>.CreateClean(width, height), true)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PixelAccessor{TPixel}" /> class.
/// </summary>
/// <param name="width">The width of the image represented by the pixel buffer.</param>
/// <param name="height">The height of the image represented by the pixel buffer.</param>
/// <param name="pixels">The pixel buffer.</param>
/// <param name="ownedBuffer">if set to <c>true</c> [owned buffer].</param>
private PixelAccessor(int width, int height, Buffer2D<TPixel> 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);
}
/// <summary>
/// Finalizes an instance of the <see cref="PixelAccessor{TPixel}"/> class.
/// Gets the <see cref="Buffer2D{T}"/> containing the pixel data.
/// </summary>
~PixelAccessor()
{
this.Dispose();
}
/// <summary>
/// Gets the pixel buffer array.
/// </summary>
public TPixel[] PixelArray => this.PixelBuffer.Array;
internal Buffer2D<TPixel> PixelBuffer { get; private set; }
/// <summary>
/// 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; }
/// <inheritdoc />
Span<TPixel> IBuffer2D<TPixel>.Span => this.PixelBuffer;
public Span<TPixel> Span => this.PixelBuffer.Span;
private static PixelOperations<TPixel> Operations => PixelOperations<TPixel>.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<TPixel> span = this.Span;
span[(y * this.Width) + x] = value;
}
}
/// <inheritdoc />
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);
}
/// <summary>
@ -158,66 +90,7 @@ namespace SixLabors.ImageSharp
/// </summary>
public void Reset()
{
this.PixelBuffer.Clear();
}
/// <summary>
/// Copy an area of pixels to the image.
/// </summary>
/// <param name="area">The area.</param>
/// <param name="targetY">The target row index.</param>
/// <param name="targetX">The target column index.</param>
/// <exception cref="NotSupportedException">
/// Thrown when an unsupported component order value is passed.
/// </exception>
internal void CopyFrom(PixelArea<TPixel> area, int targetY, int targetX = 0)
{
this.CheckCoordinates(area, targetX, targetY);
this.CopyFrom(area, targetX, targetY, area.Width, area.Height);
}
/// <summary>
/// Copy pixels from the image to an area of pixels.
/// </summary>
/// <param name="area">The area.</param>
/// <param name="sourceY">The source row index.</param>
/// <param name="sourceX">The source column index.</param>
/// <exception cref="NotSupportedException">
/// Thrown when an unsupported component order value is passed.
/// </exception>
internal void CopyTo(PixelArea<TPixel> area, int sourceY, int sourceX = 0)
{
this.CheckCoordinates(area, sourceX, sourceY);
this.CopyTo(area, sourceX, sourceY, area.Width, area.Height);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="area">The area.</param>
/// <param name="sourceY">The source row index.</param>
/// <param name="sourceX">The source column index.</param>
/// <exception cref="NotSupportedException">
/// Thrown when an unsupported component order value is passed.
/// </exception>
internal void SafeCopyTo(PixelArea<TPixel> 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();
}
/// <summary>
@ -229,7 +102,7 @@ namespace SixLabors.ImageSharp
internal Buffer2D<TPixel> SwapBufferOwnership(Buffer2D<TPixel> pixels)
{
Buffer2D<TPixel> 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);
}
/// <summary>
/// Copies from an area in <see cref="ComponentOrder.Zyx"/> format.
/// </summary>
/// <param name="area">The area.</param>
/// <param name="targetX">The target column index.</param>
/// <param name="targetY">The target row index.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CopyFromZyx(PixelArea<TPixel> area, int targetX, int targetY, int width, int height)
{
for (int y = 0; y < height; y++)
{
Span<byte> source = area.GetRowSpan(y);
Span<TPixel> destination = this.GetRowSpan(targetX, targetY + y);
Operations.PackFromBgr24Bytes(source, destination, width);
}
}
/// <summary>
/// Copies from an area in <see cref="ComponentOrder.Zyxw"/> format.
/// </summary>
/// <param name="area">The area.</param>
/// <param name="targetX">The target column index.</param>
/// <param name="targetY">The target row index.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CopyFromZyxw(PixelArea<TPixel> area, int targetX, int targetY, int width, int height)
{
for (int y = 0; y < height; y++)
{
Span<byte> source = area.GetRowSpan(y);
Span<TPixel> destination = this.GetRowSpan(targetX, targetY + y);
Operations.PackFromBgra32Bytes(source, destination, width);
}
}
/// <summary>
/// Copies from an area in <see cref="ComponentOrder.Xyz"/> format.
/// </summary>
/// <param name="area">The area.</param>
/// <param name="targetX">The target column index.</param>
/// <param name="targetY">The target row index.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CopyFromXyz(PixelArea<TPixel> area, int targetX, int targetY, int width, int height)
{
for (int y = 0; y < height; y++)
{
Span<byte> source = area.GetRowSpan(y);
Span<TPixel> destination = this.GetRowSpan(targetX, targetY + y);
Operations.PackFromRgb24Bytes(source, destination, width);
}
}
/// <summary>
/// Copies from an area in <see cref="ComponentOrder.Xyzw"/> format.
/// </summary>
/// <param name="area">The area.</param>
/// <param name="targetX">The target column index.</param>
/// <param name="targetY">The target row index.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CopyFromXyzw(PixelArea<TPixel> area, int targetX, int targetY, int width, int height)
{
for (int y = 0; y < height; y++)
{
Span<byte> source = area.GetRowSpan(y);
Span<TPixel> destination = this.GetRowSpan(targetX, targetY + y);
Operations.PackFromRgba32Bytes(source, destination, width);
}
}
/// <summary>
/// Copies to an area in <see cref="ComponentOrder.Zyx"/> format.
/// </summary>
/// <param name="area">The row.</param>
/// <param name="sourceX">The source column index.</param>
/// <param name="sourceY">The source row index.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CopyToZyx(PixelArea<TPixel> area, int sourceX, int sourceY, int width, int height)
{
for (int y = 0; y < height; y++)
{
Span<TPixel> source = this.GetRowSpan(sourceX, sourceY + y);
Span<byte> destination = area.GetRowSpan(y);
Operations.ToBgr24Bytes(source, destination, width);
}
}
/// <summary>
/// Copies to an area in <see cref="ComponentOrder.Zyxw"/> format.
/// </summary>
/// <param name="area">The row.</param>
/// <param name="sourceX">The source column index.</param>
/// <param name="sourceY">The source row index.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CopyToZyxw(PixelArea<TPixel> area, int sourceX, int sourceY, int width, int height)
{
for (int y = 0; y < height; y++)
{
Span<TPixel> source = this.GetRowSpan(sourceX, sourceY + y);
Span<byte> destination = area.GetRowSpan(y);
Operations.ToBgra32Bytes(source, destination, width);
}
}
/// <summary>
/// Copies to an area in <see cref="ComponentOrder.Xyz"/> format.
/// </summary>
/// <param name="area">The row.</param>
/// <param name="sourceX">The source column index.</param>
/// <param name="sourceY">The source row index.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CopyToXyz(PixelArea<TPixel> area, int sourceX, int sourceY, int width, int height)
{
for (int y = 0; y < height; y++)
{
Span<TPixel> source = this.GetRowSpan(sourceX, sourceY + y);
Span<byte> destination = area.GetRowSpan(y);
Operations.ToRgb24Bytes(source, destination, width);
}
}
/// <summary>
/// Copies to an area in <see cref="ComponentOrder.Xyzw"/> format.
/// </summary>
/// <param name="area">The row.</param>
/// <param name="sourceX">The source column index.</param>
/// <param name="sourceY">The source row index.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CopyToXyzw(PixelArea<TPixel> area, int sourceX, int sourceY, int width, int height)
{
for (int y = 0; y < height; y++)
{
Span<TPixel> source = this.GetRowSpan(sourceX, sourceY + y);
Span<byte> destination = area.GetRowSpan(y);
Operations.ToRgba32Bytes(source, destination, width);
}
}
/// <summary>
/// Sets the pixel buffer in an unsafe manor this should not be used unless you know what its doing!!!
/// </summary>
/// <param name="pixels">The pixel buffer</param>
/// <param name="ownedBuffer">if set to <c>true</c> then this instance ownes the buffer and thus should dispose of it afterwards.</param>
private void SetPixelBufferUnsafe(Buffer2D<TPixel> pixels, bool ownedBuffer)
private void SetPixelBufferUnsafe(Buffer2D<TPixel> 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;
}
/// <summary>
/// Copy an area of pixels to the image.
/// </summary>
/// <param name="area">The area.</param>
/// <param name="targetX">The target column index.</param>
/// <param name="targetY">The target row index.</param>
/// <param name="width">The width of the area to copy.</param>
/// <param name="height">The height of the area to copy.</param>
/// <exception cref="NotSupportedException">
/// Thrown when an unsupported component order value is passed.
/// </exception>
private void CopyFrom(PixelArea<TPixel> 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();
}
}
/// <summary>
/// Copy pixels from the image to an area of pixels.
/// </summary>
/// <param name="area">The area.</param>
/// <param name="sourceX">The source column index.</param>
/// <param name="sourceY">The source row index.</param>
/// <param name="width">The width of the area to copy.</param>
/// <param name="height">The height of the area to copy.</param>
/// <exception cref="NotSupportedException">
/// Thrown when an unsupported component order value is passed.
/// </exception>
private void CopyTo(PixelArea<TPixel> 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();
}
}
/// <summary>
/// Checks that the given area and offset are within the bounds of the image.
/// </summary>
/// <param name="area">The area.</param>
/// <param name="x">The x-coordinate of the pixel. Must be greater than zero and less than the width of the image.</param>
/// <param name="y">The y-coordinate of the pixel. Must be greater than zero and less than the height of the image.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if the dimensions are not within the bounds of the image.
/// </exception>
[Conditional("DEBUG")]
private void CheckCoordinates(PixelArea<TPixel> 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.");
}
}
/// <summary>
/// Checks the coordinates to ensure they are within bounds.
/// </summary>

249
src/ImageSharp/Image/PixelArea{TPixel}.cs

@ -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
{
/// <summary>
/// Represents an area of generic <see cref="Image{TPixel}"/> pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal sealed class PixelArea<TPixel> : IDisposable
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// A value indicating whether this instance of the given entity has been disposed.
/// </summary>
/// <value><see langword="true"/> if this instance has been disposed; otherwise, <see langword="false"/>.</value>
/// <remarks>
/// 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.
/// </remarks>
private bool isDisposed;
/// <summary>
/// The underlying buffer containing the raw pixel data.
/// </summary>
private readonly Buffer<byte> byteBuffer;
/// <summary>
/// Initializes a new instance of the <see cref="PixelArea{TPixel}"/> class.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="bytes">The bytes.</param>
/// <param name="componentOrder">The component order.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if <paramref name="bytes"></paramref> is the incorrect length.
/// </exception>
public PixelArea(int width, byte[] bytes, ComponentOrder componentOrder)
: this(width, 1, bytes, componentOrder)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PixelArea{TPixel}"/> class.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="bytes">The bytes.</param>
/// <param name="componentOrder">The component order.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if <paramref name="bytes"></paramref> is the incorrect length.
/// </exception>
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<byte>(bytes);
}
/// <summary>
/// Initializes a new instance of the <see cref="PixelArea{TPixel}"/> class.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="componentOrder">The component order.</param>
public PixelArea(int width, ComponentOrder componentOrder)
: this(width, 1, componentOrder, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PixelArea{TPixel}"/> class.
/// </summary>
/// <param name="width">The width. </param>
/// <param name="componentOrder">The component order.</param>
/// <param name="padding">The number of bytes to pad each row.</param>
public PixelArea(int width, ComponentOrder componentOrder, int padding)
: this(width, 1, componentOrder, padding)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PixelArea{TPixel}"/> class.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="componentOrder">The component order.</param>
public PixelArea(int width, int height, ComponentOrder componentOrder)
: this(width, height, componentOrder, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PixelArea{TPixel}"/> class.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="componentOrder">The component order.</param>
/// <param name="padding">The number of bytes to pad each row.</param>
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<byte>.CreateClean(this.Length);
}
/// <summary>
/// Gets the data in bytes.
/// </summary>
public byte[] Bytes => this.byteBuffer.Array;
/// <summary>
/// Gets the length of the buffer.
/// </summary>
public int Length { get; }
/// <summary>
/// Gets the component order.
/// </summary>
public ComponentOrder ComponentOrder { get; }
/// <summary>
/// Gets the height.
/// </summary>
public int Height { get; }
/// <summary>
/// Gets the width of one row in the number of bytes.
/// </summary>
public int RowStride { get; }
/// <summary>
/// Gets the width.
/// </summary>
public int Width { get; }
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
if (this.isDisposed)
{
return;
}
this.byteBuffer.Dispose();
this.isDisposed = true;
}
/// <summary>
/// Reads the stream to the area.
/// </summary>
/// <param name="stream">The stream.</param>
public void Read(Stream stream)
{
stream.Read(this.Bytes, 0, this.Length);
}
/// <summary>
/// Writes the area to the stream.
/// </summary>
/// <param name="stream">The stream.</param>
public void Write(Stream stream)
{
stream.Write(this.Bytes, 0, this.Length);
}
/// <summary>
/// Resets the bytes of the array to it's initial value.
/// </summary>
public void Reset()
{
this.byteBuffer.Clear();
}
/// <summary>
/// Gets a <see cref="Span{T}"/> to the row y.
/// </summary>
/// <param name="y">The y coordinate</param>
/// <returns>The <see cref="Span{T}"/></returns>
internal Span<byte> GetRowSpan(int y)
{
return this.byteBuffer.Slice(y * this.RowStride);
}
/// <summary>
/// Gets component count for the given order.
/// </summary>
/// <param name="componentOrder">The component order.</param>
/// <returns>
/// The <see cref="int"/>.
/// </returns>
/// <exception cref="NotSupportedException">
/// Thrown if an invalid order is given.
/// </exception>
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();
}
/// <summary>
/// Checks that the length of the byte array to ensure that it matches the given width and height.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="bytes">The byte array.</param>
/// <param name="componentOrder">The component order.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if the byte array is th incorrect length.
/// </exception>
[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}.");
}
}
}
}

12
src/ImageSharp/ImageSharp.csproj

@ -61,6 +61,10 @@
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>Block8x8F.Generated.cs</LastGenOutput>
</None>
<None Update="Formats\Jpeg\Common\GenericBlock8x8.Generated.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>GenericBlock8x8.Generated.cs</LastGenOutput>
</None>
<None Update="Formats\Jpeg\Components\Block8x8F.Generated.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>Block8x8F.Generated.cs</LastGenOutput>
@ -91,6 +95,11 @@
<AutoGen>True</AutoGen>
<DependentUpon>Block8x8F.Generated.tt</DependentUpon>
</Compile>
<Compile Update="Formats\Jpeg\Common\GenericBlock8x8.Generated.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>GenericBlock8x8.Generated.tt</DependentUpon>
</Compile>
<Compile Update="Formats\Jpeg\Components\Block8x8F.Generated.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
@ -117,4 +126,7 @@
<DependentUpon>PorterDuffFunctions.Generated.tt</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Folder Include="Formats\Jpeg\GolangPort\Utils\" />
</ItemGroup>
</Project>

80
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
{
/// <summary>
/// Contains <see cref="Buffer{T}"/> and <see cref="ManagedByteBuffer"/>
/// </summary>
public partial class ArrayPoolMemoryManager
{
/// <summary>
/// The buffer implementation of <see cref="ArrayPoolMemoryManager"/>
/// </summary>
private class Buffer<T> : IBuffer<T>
where T : struct
{
/// <summary>
/// The length of the buffer
/// </summary>
private readonly int length;
/// <summary>
/// A weak reference to the source pool.
/// </summary>
/// <remarks>
/// 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 <see cref="ArrayPoolMemoryManager.ReleaseRetainedResources"/>, regardless of having buffer instances still being in use.
/// </remarks>
private WeakReference<ArrayPool<byte>> sourcePoolReference;
public Buffer(byte[] data, int length, ArrayPool<byte> sourcePool)
{
this.Data = data;
this.length = length;
this.sourcePoolReference = new WeakReference<ArrayPool<byte>>(sourcePool);
}
/// <summary>
/// Gets the buffer as a byte array.
/// </summary>
protected byte[] Data { get; private set; }
/// <inheritdoc />
public Span<T> Span => this.Data.AsSpan().NonPortableCast<byte, T>().Slice(0, this.length);
/// <inheritdoc />
public void Dispose()
{
if (this.Data == null || this.sourcePoolReference == null)
{
return;
}
if (this.sourcePoolReference.TryGetTarget(out ArrayPool<byte> pool))
{
pool.Return(this.Data);
}
this.sourcePoolReference = null;
this.Data = null;
}
}
/// <summary>
/// The <see cref="IManagedByteBuffer"/> implementation of <see cref="ArrayPoolMemoryManager"/>.
/// </summary>
private class ManagedByteBuffer : Buffer<byte>, IManagedByteBuffer
{
public ManagedByteBuffer(byte[] data, int length, ArrayPool<byte> sourcePool)
: base(data, length, sourcePool)
{
}
public byte[] Array => this.Data;
}
}
}

69
src/ImageSharp/Memory/ArrayPoolMemoryManager.CommonFactoryMethods.cs

@ -0,0 +1,69 @@
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// Contains common factory methods and configuration constants.
/// </summary>
public partial class ArrayPoolMemoryManager
{
/// <summary>
/// The default value for: maximum size of pooled arrays in bytes.
/// Currently set to 24MB, which is equivalent to 8 megapixels of raw <see cref="Rgba32"/> data.
/// </summary>
internal const int DefaultMaxPooledBufferSizeInBytes = 24 * 1024 * 1024;
/// <summary>
/// The value for: The threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.
/// </summary>
private const int DefaultBufferSelectorThresholdInBytes = 8 * 1024 * 1024;
/// <summary>
/// The default bucket count for <see cref="largeArrayPool"/>.
/// </summary>
private const int DefaultLargePoolBucketCount = 6;
/// <summary>
/// The default bucket count for <see cref="normalArrayPool"/>.
/// </summary>
private const int DefaultNormalPoolBucketCount = 16;
/// <summary>
/// This is the default. Should be good for most use cases.
/// </summary>
/// <returns>The memory manager</returns>
public static ArrayPoolMemoryManager CreateDefault()
{
return new ArrayPoolMemoryManager(
DefaultMaxPooledBufferSizeInBytes,
DefaultBufferSelectorThresholdInBytes,
DefaultLargePoolBucketCount,
DefaultNormalPoolBucketCount);
}
/// <summary>
/// For environments with limited memory capabilities. Only small images are pooled, which can result in reduced througput.
/// </summary>
/// <returns>The memory manager</returns>
public static ArrayPoolMemoryManager CreateWithModeratePooling()
{
return new ArrayPoolMemoryManager(1024 * 1024, 32 * 1024, 16, 24);
}
/// <summary>
/// Only pool small buffers like image rows.
/// </summary>
/// <returns>The memory manager</returns>
public static ArrayPoolMemoryManager CreateWithMinimalPooling()
{
return new ArrayPoolMemoryManager(64 * 1024, 32 * 1024, 8, 24);
}
/// <summary>
/// RAM is not an issue for me, gimme maximum througput!
/// </summary>
/// <returns>The memory manager</returns>
public static ArrayPoolMemoryManager CreateWithAggressivePooling()
{
return new ArrayPoolMemoryManager(128 * 1024 * 1024, 32 * 1024 * 1024, 16, 32);
}
}
}

140
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
{
/// <summary>
/// Implements <see cref="MemoryManager"/> by allocating memory from <see cref="ArrayPool{T}"/>.
/// </summary>
public partial class ArrayPoolMemoryManager : MemoryManager
{
/// <summary>
/// The <see cref="ArrayPool{T}"/> for small-to-medium buffers which is not kept clean.
/// </summary>
private ArrayPool<byte> normalArrayPool;
/// <summary>
/// The <see cref="ArrayPool{T}"/> for huge buffers, which is not kept clean.
/// </summary>
private ArrayPool<byte> largeArrayPool;
private readonly int maxArraysPerBucketNormalPool;
private readonly int maxArraysPerBucketLargePool;
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolMemoryManager"/> class.
/// </summary>
public ArrayPoolMemoryManager()
: this(DefaultMaxPooledBufferSizeInBytes, DefaultBufferSelectorThresholdInBytes)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolMemoryManager"/> class.
/// </summary>
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
public ArrayPoolMemoryManager(int maxPoolSizeInBytes)
: this(maxPoolSizeInBytes, GetLargeBufferThresholdInBytes(maxPoolSizeInBytes))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolMemoryManager"/> class.
/// </summary>
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
/// <param name="poolSelectorThresholdInBytes">Arrays over this threshold will be pooled in <see cref="largeArrayPool"/> which has less buckets for memory safety.</param>
public ArrayPoolMemoryManager(int maxPoolSizeInBytes, int poolSelectorThresholdInBytes)
: this(maxPoolSizeInBytes, poolSelectorThresholdInBytes, DefaultLargePoolBucketCount, DefaultNormalPoolBucketCount)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolMemoryManager"/> class.
/// </summary>
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
/// <param name="poolSelectorThresholdInBytes">The threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.</param>
/// <param name="maxArraysPerBucketLargePool">Max arrays per bucket for the large array pool</param>
/// <param name="maxArraysPerBucketNormalPool">Max arrays per bucket for the normal array pool</param>
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();
}
/// <summary>
/// Gets the maximum size of pooled arrays in bytes.
/// </summary>
public int MaxPoolSizeInBytes { get; }
/// <summary>
/// Gets the threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.
/// </summary>
public int PoolSelectorThresholdInBytes { get; }
/// <inheritdoc />
public override void ReleaseRetainedResources()
{
this.InitArrayPools();
}
/// <inheritdoc />
internal override IBuffer<T> Allocate<T>(int length, bool clear)
{
int itemSizeBytes = Unsafe.SizeOf<T>();
int bufferSizeInBytes = length * itemSizeBytes;
ArrayPool<byte> pool = this.GetArrayPool(bufferSizeInBytes);
byte[] byteArray = pool.Rent(bufferSizeInBytes);
var buffer = new Buffer<T>(byteArray, length, pool);
if (clear)
{
buffer.Clear();
}
return buffer;
}
/// <inheritdoc />
internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, bool clear)
{
ArrayPool<byte> 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<byte> GetArrayPool(int bufferSizeInBytes)
{
return bufferSizeInBytes <= this.PoolSelectorThresholdInBytes ? this.normalArrayPool : this.largeArrayPool;
}
private void InitArrayPools()
{
this.largeArrayPool = ArrayPool<byte>.Create(this.MaxPoolSizeInBytes, this.maxArraysPerBucketLargePool);
this.normalArrayPool = ArrayPool<byte>.Create(this.PoolSelectorThresholdInBytes, this.maxArraysPerBucketNormalPool);
}
}
}

51
src/ImageSharp/Memory/BasicArrayBuffer.cs

@ -0,0 +1,51 @@
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// Exposes an array through the <see cref="IBuffer{T}"/> interface.
/// </summary>
internal class BasicArrayBuffer<T> : IBuffer<T>
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<T> Span => this.Array.AsSpan().Slice(0, this.Length);
/// <summary>
/// Returns a reference to specified element of the buffer.
/// </summary>
/// <param name="index">The index</param>
/// <returns>The reference to the specified element</returns>
public ref T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
DebugGuard.MustBeLessThan(index, this.Length, nameof(index));
Span<T> span = this.Span;
return ref span[index];
}
}
public void Dispose()
{
}
}
}

10
src/ImageSharp/Memory/BasicByteBuffer.cs

@ -0,0 +1,10 @@
namespace SixLabors.ImageSharp.Memory
{
internal class BasicByteBuffer : BasicArrayBuffer<byte>, IManagedByteBuffer
{
internal BasicByteBuffer(byte[] array)
: base(array)
{
}
}
}

78
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 <see cref="Width"/> x <see cref="Height"/> elements.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
internal class Buffer2D<T> : Buffer<T>, IBuffer2D<T>
internal class Buffer2D<T> : IBuffer2D<T>, IDisposable
where T : struct
{
public Buffer2D(Size size)
: this(size.Width, size.Height)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Buffer2D{T}"/> class.
/// </summary>
/// <param name="width">The number of elements in a row</param>
/// <param name="height">The number of rows</param>
public Buffer2D(int width, int height)
: base(width * height)
{
this.Width = width;
this.Height = height;
}
/// <summary>
/// Initializes a new instance of the <see cref="Buffer2D{T}"/> class.
/// </summary>
/// <param name="array">The array to pin</param>
/// <param name="wrappedBuffer">The buffer to wrap</param>
/// <param name="width">The number of elements in a row</param>
/// <param name="height">The number of rows</param>
public Buffer2D(T[] array, int width, int height)
: base(array, width * height)
public Buffer2D(IBuffer<T> wrappedBuffer, int width, int height)
{
this.Buffer = wrappedBuffer;
this.Width = width;
this.Height = height;
}
/// <inheritdoc />
public int Width { get; }
public int Width { get; private set; }
/// <inheritdoc />
public int Height { get; }
public int Height { get; private set; }
/// <summary>
/// Gets the span to the whole area.
/// </summary>
public Span<T> Span => this.Buffer.Span;
/// <summary>
/// Gets the backing <see cref="IBuffer{T}"/>
/// </summary>
public IBuffer<T> Buffer { get; private set; }
/// <summary>
/// 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<T> span = this.Buffer.Span;
return ref span[(this.Width * y) + x];
}
}
/// <summary>
/// Creates a clean instance of <see cref="Buffer2D{T}"/> initializing it's elements with 'default(T)'.
/// Disposes the <see cref="Buffer2D{T}"/> instance
/// </summary>
/// <param name="width">The number of elements in a row</param>
/// <param name="height">The number of rows</param>
/// <returns>The <see cref="Buffer{T}"/> instance</returns>
public static Buffer2D<T> CreateClean(int width, int height)
public void Dispose()
{
var buffer = new Buffer2D<T>(width, height);
buffer.Clear();
return buffer;
this.Buffer?.Dispose();
}
/// <summary>
/// Creates a clean instance of <see cref="Buffer2D{T}"/> initializing it's elements with 'default(T)'.
/// Swap the contents (<see cref="Buffer"/>, <see cref="Width"/>, <see cref="Height"/>) of the two buffers.
/// Useful to transfer the contents of a temporary <see cref="Buffer2D{T}"/> to a persistent <see cref="ImageFrame{TPixel}.PixelBuffer"/>
/// </summary>
/// <param name="size">The size of the buffer</param>
/// <returns>The <see cref="Buffer2D{T}"/> instance</returns>
public static Buffer2D<T> CreateClean(Size size) => CreateClean(size.Width, size.Height);
/// <param name="a">The first buffer</param>
/// <param name="b">The second buffer</param>
public static void SwapContents(Buffer2D<T> a, Buffer2D<T> b)
{
Size aSize = a.Size();
Size bSize = b.Size();
IBuffer<T> 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;
}
}
}

35
src/ImageSharp/Memory/BufferArea{T}.cs

@ -45,11 +45,26 @@ namespace SixLabors.ImageSharp.Memory
/// </summary>
public Size Size => this.Rectangle.Size;
/// <summary>
/// Gets the width
/// </summary>
public int Width => this.Rectangle.Width;
/// <summary>
/// Gets the height
/// </summary>
public int Height => this.Rectangle.Height;
/// <summary>
/// Gets the pixel stride which is equal to the width of <see cref="DestinationBuffer"/>.
/// </summary>
public int Stride => this.DestinationBuffer.Width;
/// <summary>
/// Gets a value indicating whether the area refers to the entire <see cref="DestinationBuffer"/>
/// </summary>
public bool IsFullBufferArea => this.Size == this.DestinationBuffer.Size();
/// <summary>
/// Gets or sets a value at the given index.
/// </summary>
@ -63,7 +78,7 @@ namespace SixLabors.ImageSharp.Memory
/// </summary>
/// <returns>The reference to the [0,0] element</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T GetReferenceToOrigo() =>
public ref T GetReferenceToOrigin() =>
ref this.DestinationBuffer.Span[(this.Rectangle.Y * this.DestinationBuffer.Width) + this.Rectangle.X];
/// <summary>
@ -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<T> row = this.GetRowSpan(y);
row.Clear();
}
}
}
}

68
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<T>(this IBuffer<T> buffer)
where T : struct => buffer.Span.Length;
/// <summary>
/// Gets a <see cref="Span{T}"/> to an offseted position inside the buffer.
/// </summary>
/// <param name="buffer">The buffer</param>
/// <param name="start">The start</param>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> Slice<T>(this IBuffer<T> buffer, int start)
where T : struct
{
return buffer.Span.Slice(start);
}
/// <summary>
/// Gets a <see cref="Span{T}"/> to an offsetted position inside the buffer.
/// </summary>
/// <param name="buffer">The buffer</param>
/// <param name="start">The start</param>
/// <param name="length">The length of the slice</param>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> Slice<T>(this IBuffer<T> buffer, int start, int length)
where T : struct
{
return buffer.Span.Slice(start, length);
}
/// <summary>
/// Clears the contents of this buffer.
/// </summary>
/// <param name="buffer">The buffer</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Clear<T>(this IBuffer<T> buffer)
where T : struct
{
buffer.Span.Clear();
}
public static ref T DangerousGetPinnableReference<T>(this IBuffer<T> 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());
}
}
}

267
src/ImageSharp/Memory/Buffer{T}.cs

@ -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
{
/// <inheritdoc />
/// <summary>
/// Manages a buffer of value type objects as a Disposable resource.
/// The backing array is either pooled or comes from the outside.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
internal class Buffer<T> : IBuffer<T>
where T : struct
{
/// <summary>
/// A pointer to the first element of <see cref="Array"/> when pinned.
/// </summary>
private IntPtr pointer;
/// <summary>
/// A handle that allows to access the managed <see cref="Array"/> as an unmanaged memory by pinning.
/// </summary>
private GCHandle handle;
/// <summary>
/// A value indicating wheter <see cref="Array"/> should be returned to <see cref="PixelDataPool{T}"/>
/// when disposing this <see cref="Buffer{T}"/> instance.
/// </summary>
private bool isPoolingOwner;
/// <summary>
/// Initializes a new instance of the <see cref="Buffer{T}"/> class.
/// </summary>
/// <param name="length">The desired count of elements. (Minimum size for <see cref="Array"/>)</param>
public Buffer(int length)
{
this.Length = length;
this.Array = PixelDataPool<T>.Rent(length);
this.isPoolingOwner = true;
}
/// <summary>
/// Initializes a new instance of the <see cref="Buffer{T}"/> class.
/// </summary>
/// <param name="array">The array to pin.</param>
public Buffer(T[] array)
{
this.Length = array.Length;
this.Array = array;
this.isPoolingOwner = false;
}
/// <summary>
/// Initializes a new instance of the <see cref="Buffer{T}"/> class.
/// </summary>
/// <param name="array">The array to pin.</param>
/// <param name="length">The count of "relevant" elements in 'array'.</param>
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;
}
/// <summary>
/// Finalizes an instance of the <see cref="Buffer{T}"/> class.
/// </summary>
~Buffer()
{
this.UnPin();
}
/// <summary>
/// Gets a value indicating whether this <see cref="Buffer{T}"/> instance is disposed, or has lost ownership of <see cref="Array"/>.
/// </summary>
public bool IsDisposedOrLostArrayOwnership { get; private set; }
/// <summary>
/// Gets the count of "relevant" elements. It's usually smaller than 'Array.Length' when <see cref="Array"/> is pooled.
/// </summary>
public int Length { get; private set; }
/// <summary>
/// Gets the backing pinned array.
/// </summary>
public T[] Array { get; private set; }
/// <summary>
/// Gets a <see cref="Span{T}"/> to the backing buffer.
/// </summary>
public Span<T> Span => this;
/// <summary>
/// Returns a reference to specified element of the buffer.
/// </summary>
/// <param name="index">The index</param>
/// <returns>The reference to the specified element</returns>
public ref T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
DebugGuard.MustBeLessThan(index, this.Length, nameof(index));
return ref this.Array[index];
}
}
/// <summary>
/// Converts <see cref="Buffer{T}"/> to an <see cref="ReadOnlySpan{T}"/>.
/// </summary>
/// <param name="buffer">The <see cref="Buffer{T}"/> to convert.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ReadOnlySpan<T>(Buffer<T> buffer)
{
return new ReadOnlySpan<T>(buffer.Array, 0, buffer.Length);
}
/// <summary>
/// Converts <see cref="Buffer{T}"/> to an <see cref="Span{T}"/>.
/// </summary>
/// <param name="buffer">The <see cref="Buffer{T}"/> to convert.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Span<T>(Buffer<T> buffer)
{
return new Span<T>(buffer.Array, 0, buffer.Length);
}
/// <summary>
/// Creates a clean instance of <see cref="Buffer{T}"/> initializing it's elements with 'default(T)'.
/// </summary>
/// <param name="count">The desired count of elements. (Minimum size for <see cref="Array"/>)</param>
/// <returns>The <see cref="Buffer{T}"/> instance</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Buffer<T> CreateClean(int count)
{
var buffer = new Buffer<T>(count);
buffer.Clear();
return buffer;
}
/// <summary>
/// Gets a <see cref="Span{T}"/> to an offseted position inside the buffer.
/// </summary>
/// <param name="start">The start</param>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<T> Slice(int start)
{
return new Span<T>(this.Array, start, this.Length - start);
}
/// <summary>
/// Gets a <see cref="Span{T}"/> to an offsetted position inside the buffer.
/// </summary>
/// <param name="start">The start</param>
/// <param name="length">The length of the slice</param>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<T> Slice(int start, int length)
{
return new Span<T>(this.Array, start, length);
}
/// <summary>
/// Disposes the <see cref="Buffer{T}"/> instance by unpinning the array, and returning the pooled buffer when necessary.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
if (this.IsDisposedOrLostArrayOwnership)
{
return;
}
this.IsDisposedOrLostArrayOwnership = true;
this.UnPin();
if (this.isPoolingOwner)
{
PixelDataPool<T>.Return(this.Array);
}
this.isPoolingOwner = false;
this.Array = null;
this.Length = 0;
GC.SuppressFinalize(this);
}
/// <summary>
/// Unpins <see cref="Array"/> and makes the object "quasi-disposed" so the array is no longer owned by this object.
/// If <see cref="Array"/> is rented, it's the callers responsibility to return it to it's pool. (Most likely <see cref="PixelDataPool{T}"/>)
/// </summary>
/// <returns>The unpinned <see cref="Array"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T[] TakeArrayOwnership()
{
if (this.IsDisposedOrLostArrayOwnership)
{
throw new InvalidOperationException(
"TakeArrayOwnership() is invalid: either Buffer<T> 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;
}
/// <summary>
/// Clears the contents of this buffer.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear()
{
this.Span.Clear();
}
/// <summary>
/// Pins <see cref="Array"/>.
/// </summary>
/// <returns>The pinned pointer</returns>
[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;
}
/// <summary>
/// Unpins <see cref="Array"/>.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void UnPin()
{
if (this.pointer == IntPtr.Zero || !this.handle.IsAllocated)
{
return;
}
this.handle.Free();
this.pointer = IntPtr.Zero;
}
}
}

3
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

16
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
{
/// <summary>
/// Represents a byte buffer backed by a managed array. Useful for interop with classic .NET API-s.
/// </summary>
internal interface IManagedByteBuffer : IBuffer<byte>
{
/// <summary>
/// Gets the managed array backing this buffer instance.
/// </summary>
byte[] Array { get; }
}
}

48
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
{
/// <summary>
/// Memory managers are used to allocate memory for image processing operations.
/// </summary>
public abstract class MemoryManager
{
/// <summary>
/// Allocates an <see cref="IBuffer{T}"/> of size <paramref name="length"/>, optionally
/// clearing the buffer before it gets returned.
/// </summary>
/// <typeparam name="T">Type of the data stored in the buffer</typeparam>
/// <param name="length">Size of the buffer to allocate</param>
/// <param name="clear">True to clear the backing memory of the buffer</param>
/// <returns>A buffer of values of type <typeparamref name="T"/>.</returns>
internal abstract IBuffer<T> Allocate<T>(int length, bool clear)
where T : struct;
/// <summary>
/// Allocates an <see cref="IManagedByteBuffer"/>
/// </summary>
/// <param name="length">The requested buffer length</param>
/// <param name="clear">A value indicating whether to clean the buffer</param>
/// <returns>The <see cref="IManagedByteBuffer"/></returns>
internal abstract IManagedByteBuffer AllocateManagedByteBuffer(int length, bool clear);
/// <summary>
/// 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!
/// </summary>
internal BasicArrayBuffer<T> AllocateFake<T>(int length, bool dummy = false)
where T : struct
{
return new BasicArrayBuffer<T>(new T[length]);
}
/// <summary>
/// Releases all retained resources not being in use.
/// Eg: by resetting array pools and letting GC to free the arrays.
/// </summary>
public virtual void ReleaseRetainedResources()
{
}
}
}

79
src/ImageSharp/Memory/MemoryManagerExtensions.cs

@ -0,0 +1,79 @@
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// Extension methods for <see cref="MemoryManager"/>.
/// </summary>
internal static class MemoryManagerExtensions
{
/// <summary>
/// Allocates a <see cref="IBuffer{T}"/> of size <paramref name="length"/>.
/// Note: Depending on the implementation, the buffer may not cleared before
/// returning, so it may contain data from an earlier use.
/// </summary>
/// <typeparam name="T">Type of the data stored in the buffer</typeparam>
/// <param name="memoryManager">The <see cref="MemoryManager"/></param>
/// <param name="length">Size of the buffer to allocate</param>
/// <returns>A buffer of values of type <typeparamref name="T"/>.</returns>
public static IBuffer<T> Allocate<T>(this MemoryManager memoryManager, int length)
where T : struct
{
return memoryManager.Allocate<T>(length, false);
}
public static IBuffer<T> AllocateClean<T>(this MemoryManager memoryManager, int length)
where T : struct
{
return memoryManager.Allocate<T>(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<T> Allocate2D<T>(this MemoryManager memoryManager, int width, int height, bool clear)
where T : struct
{
IBuffer<T> buffer = memoryManager.Allocate<T>(width * height, clear);
return new Buffer2D<T>(buffer, width, height);
}
public static Buffer2D<T> Allocate2D<T>(this MemoryManager memoryManager, Size size)
where T : struct =>
Allocate2D<T>(memoryManager, size.Width, size.Height, false);
public static Buffer2D<T> Allocate2D<T>(this MemoryManager memoryManager, int width, int height)
where T : struct =>
Allocate2D<T>(memoryManager, width, height, false);
public static Buffer2D<T> AllocateClean2D<T>(this MemoryManager memoryManager, int width, int height)
where T : struct =>
Allocate2D<T>(memoryManager, width, height, true);
/// <summary>
/// Allocates padded buffers for BMP encoder/decoder. (Replacing old PixelRow/PixelArea)
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/></param>
/// <param name="width">Pixel count in the row</param>
/// <param name="pixelSizeInBytes">The pixel size in bytes, eg. 3 for RGB</param>
/// <param name="paddingInBytes">The padding</param>
/// <returns>A <see cref="IManagedByteBuffer"/></returns>
public static IManagedByteBuffer AllocatePaddedPixelRowBuffer(
this MemoryManager memoryManager,
int width,
int pixelSizeInBytes,
int paddingInBytes)
{
int length = (width * pixelSizeInBytes) + paddingInBytes;
return memoryManager.AllocateManagedByteBuffer(length);
}
}
}

80
src/ImageSharp/Memory/PixelDataPool{T}.cs

@ -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
{
/// <summary>
/// Provides a resource pool that enables reusing instances of value type arrays for image data <see cref="T:T[]"/>.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
internal class PixelDataPool<T>
where T : struct
{
/// <summary>
/// The maximum size of pooled arrays in bytes.
/// Currently set to 32MB, which is equivalent to 8 megapixels of raw <see cref="Rgba32"/> data.
/// </summary>
internal const int MaxPooledBufferSizeInBytes = 32 * 1024 * 1024;
/// <summary>
/// The threshold to pool arrays in <see cref="LargeArrayPool"/> which has less buckets for memory safety.
/// </summary>
private const int LargeBufferThresholdInBytes = 8 * 1024 * 1024;
/// <summary>
/// The maximum array length of the <see cref="LargeArrayPool"/>.
/// </summary>
private static readonly int MaxLargeArrayLength = MaxPooledBufferSizeInBytes / Unsafe.SizeOf<T>();
/// <summary>
/// The maximum array length of the <see cref="NormalArrayPool"/>.
/// </summary>
private static readonly int MaxNormalArrayLength = LargeBufferThresholdInBytes / Unsafe.SizeOf<T>();
/// <summary>
/// The <see cref="ArrayPool{T}"/> for huge buffers, which is not kept clean.
/// </summary>
private static readonly ArrayPool<T> LargeArrayPool = ArrayPool<T>.Create(MaxLargeArrayLength, 8);
/// <summary>
/// The <see cref="ArrayPool{T}"/> for small-to-medium buffers which is not kept clean.
/// </summary>
private static readonly ArrayPool<T> NormalArrayPool = ArrayPool<T>.Create(MaxNormalArrayLength, 24);
/// <summary>
/// Rents the pixel array from the pool.
/// </summary>
/// <param name="minimumLength">The minimum length of the array to return.</param>
/// <returns>The <see cref="T:TPixel[]"/></returns>
public static T[] Rent(int minimumLength)
{
if (minimumLength <= MaxNormalArrayLength)
{
return NormalArrayPool.Rent(minimumLength);
}
else
{
return LargeArrayPool.Rent(minimumLength);
}
}
/// <summary>
/// Returns the rented pixel array back to the pool.
/// </summary>
/// <param name="array">The array to return to the buffer pool.</param>
public static void Return(T[] array)
{
if (array.Length <= MaxNormalArrayLength)
{
NormalArrayPool.Return(array);
}
else
{
LargeArrayPool.Return(array);
}
}
}
}

19
src/ImageSharp/Memory/SimpleGcMemoryManager.cs

@ -0,0 +1,19 @@
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// Implements <see cref="MemoryManager"/> by newing up arrays by the GC on every allocation requests.
/// </summary>
public class SimpleGcMemoryManager : MemoryManager
{
/// <inheritdoc />
internal override IBuffer<T> Allocate<T>(int length, bool clear)
{
return new BasicArrayBuffer<T>(new T[length]);
}
internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, bool clear)
{
return new BasicByteBuffer(new byte[length]);
}
}
}

126
src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs

@ -26,7 +26,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
internal class Normal : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
@ -39,13 +38,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
/// <inheritdoc />
public override void Blend(Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
public override void Blend(MemoryManager memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> 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<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -63,9 +62,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
}
}
internal class Multiply : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
@ -78,13 +77,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
/// <inheritdoc />
public override void Blend(Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
public override void Blend(MemoryManager memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> 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<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -102,9 +101,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
}
}
internal class Add : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
@ -117,13 +116,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
/// <inheritdoc />
public override void Blend(Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
public override void Blend(MemoryManager memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> 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<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -141,9 +140,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
}
}
internal class Substract : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
@ -156,13 +155,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
/// <inheritdoc />
public override void Blend(Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
public override void Blend(MemoryManager memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> 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<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -180,9 +179,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
}
}
internal class Screen : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
@ -195,13 +194,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
/// <inheritdoc />
public override void Blend(Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
public override void Blend(MemoryManager memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> 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<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -219,9 +218,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
}
}
internal class Darken : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
@ -234,13 +233,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
/// <inheritdoc />
public override void Blend(Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
public override void Blend(MemoryManager memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> 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<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -258,9 +257,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
}
}
internal class Lighten : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
@ -273,13 +272,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
/// <inheritdoc />
public override void Blend(Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
public override void Blend(MemoryManager memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> 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<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -297,9 +296,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
}
}
internal class Overlay : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
@ -312,13 +311,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
/// <inheritdoc />
public override void Blend(Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
public override void Blend(MemoryManager memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> 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<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -336,9 +335,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
}
}
internal class HardLight : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
@ -351,13 +350,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
/// <inheritdoc />
public override void Blend(Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
public override void Blend(MemoryManager memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> 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<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -375,9 +374,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
}
}
internal class Src : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
@ -390,13 +389,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
/// <inheritdoc />
public override void Blend(Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
public override void Blend(MemoryManager memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> 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<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -414,9 +413,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
}
}
internal class Atop : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
@ -429,13 +428,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
/// <inheritdoc />
public override void Blend(Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
public override void Blend(MemoryManager memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> 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<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -453,9 +452,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
}
}
internal class Over : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
@ -468,13 +467,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
/// <inheritdoc />
public override void Blend(Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
public override void Blend(MemoryManager memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> 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<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -492,9 +491,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
}
}
internal class In : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
@ -507,13 +506,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
/// <inheritdoc />
public override void Blend(Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
public override void Blend(MemoryManager memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> 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<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -531,9 +530,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
}
}
internal class Out : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
@ -546,13 +545,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
/// <inheritdoc />
public override void Blend(Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
public override void Blend(MemoryManager memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> 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<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -570,9 +569,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
}
}
internal class Dest : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
@ -585,13 +584,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
/// <inheritdoc />
public override void Blend(Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
public override void Blend(MemoryManager memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> 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<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -609,9 +608,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
}
}
internal class DestAtop : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
@ -624,13 +623,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
/// <inheritdoc />
public override void Blend(Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
public override void Blend(MemoryManager memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> 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<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -648,9 +647,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
}
}
internal class DestOver : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
@ -663,13 +662,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
/// <inheritdoc />
public override void Blend(Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
public override void Blend(MemoryManager memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> 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<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -687,9 +686,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
}
}
internal class DestIn : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
@ -702,13 +701,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
/// <inheritdoc />
public override void Blend(Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
public override void Blend(MemoryManager memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> 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<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -726,9 +725,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
}
}
internal class DestOut : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
@ -741,13 +740,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
/// <inheritdoc />
public override void Blend(Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
public override void Blend(MemoryManager memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> 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<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -765,9 +764,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
}
}
internal class Clear : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
@ -780,13 +779,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
/// <inheritdoc />
public override void Blend(Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
public override void Blend(MemoryManager memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> 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<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -804,9 +803,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
}
}
internal class Xor : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
@ -819,13 +818,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
/// <inheritdoc />
public override void Blend(Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
public override void Blend(MemoryManager memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> 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<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -843,5 +842,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
}
}
}
}

6
src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt

@ -68,7 +68,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
#>
internal class <#=blender#> : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
@ -81,13 +80,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
/// <inheritdoc />
public override void Blend(Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
public override void Blend(MemoryManager memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> 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<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -105,6 +104,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
}
}
}
<#
}

4
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
/// <summary>
/// Blend 2 pixels together.
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/></param>
/// <param name="destination">The destination span.</param>
/// <param name="background">The background span.</param>
/// <param name="source">The source span.</param>
@ -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.
/// </param>
public abstract void Blend(Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount);
public abstract void Blend(MemoryManager memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount);
}
}

4
src/ImageSharp/Processing/ColorMatrix/Lomograph.cs

@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp
public static IImageProcessingContext<TPixel> Lomograph<TPixel>(this IImageProcessingContext<TPixel> source, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new LomographProcessor<TPixel>(options));
source.ApplyProcessor(new LomographProcessor<TPixel>(source.MemoryManager, options));
return source;
}
@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp
public static IImageProcessingContext<TPixel> Lomograph<TPixel>(this IImageProcessingContext<TPixel> source, Rectangle rectangle, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new LomographProcessor<TPixel>(options), rectangle);
source.ApplyProcessor(new LomographProcessor<TPixel>(source.MemoryManager, options), rectangle);
return source;
}
}

4
src/ImageSharp/Processing/ColorMatrix/Polaroid.cs

@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp
public static IImageProcessingContext<TPixel> Polaroid<TPixel>(this IImageProcessingContext<TPixel> source, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new PolaroidProcessor<TPixel>(options));
source.ApplyProcessor(new PolaroidProcessor<TPixel>(source.MemoryManager, options));
return source;
}
@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp
public static IImageProcessingContext<TPixel> Polaroid<TPixel>(this IImageProcessingContext<TPixel> source, Rectangle rectangle, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new PolaroidProcessor<TPixel>(options), rectangle);
source.ApplyProcessor(new PolaroidProcessor<TPixel>(source.MemoryManager, options), rectangle);
return source;
}
}

4
src/ImageSharp/Processing/Effects/BackgroundColor.cs

@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> BackgroundColor<TPixel>(this IImageProcessingContext<TPixel> source, TPixel color, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new BackgroundColorProcessor<TPixel>(color, options));
=> source.ApplyProcessor(new BackgroundColorProcessor<TPixel>(source.MemoryManager, color, options));
/// <summary>
/// Replaces the background color of image with the given one.
@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> BackgroundColor<TPixel>(this IImageProcessingContext<TPixel> source, TPixel color, Rectangle rectangle, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new BackgroundColorProcessor<TPixel>(color, options), rectangle);
=> source.ApplyProcessor(new BackgroundColorProcessor<TPixel>(source.MemoryManager, color, options), rectangle);
/// <summary>
/// Replaces the background color of image with the given one.

4
src/ImageSharp/Processing/Overlays/Glow.cs

@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
private static IImageProcessingContext<TPixel> Glow<TPixel>(this IImageProcessingContext<TPixel> source, TPixel color, ValueSize radius, Rectangle rectangle, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new GlowProcessor<TPixel>(color, radius, options), rectangle);
=> source.ApplyProcessor(new GlowProcessor<TPixel>(source.MemoryManager, color, radius, options), rectangle);
/// <summary>
/// Applies a radial glow effect to an image.
@ -170,6 +170,6 @@ namespace SixLabors.ImageSharp
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
private static IImageProcessingContext<TPixel> Glow<TPixel>(this IImageProcessingContext<TPixel> source, TPixel color, ValueSize radius, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new GlowProcessor<TPixel>(color, radius, options));
=> source.ApplyProcessor(new GlowProcessor<TPixel>(source.MemoryManager, color, radius, options));
}
}

4
src/ImageSharp/Processing/Overlays/Vignette.cs

@ -151,10 +151,10 @@ namespace SixLabors.ImageSharp
private static IImageProcessingContext<TPixel> VignetteInternal<TPixel>(this IImageProcessingContext<TPixel> source, TPixel color, ValueSize radiusX, ValueSize radiusY, Rectangle rectangle, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new VignetteProcessor<TPixel>(color, radiusX, radiusY, options), rectangle);
=> source.ApplyProcessor(new VignetteProcessor<TPixel>(source.MemoryManager, color, radiusX, radiusY, options), rectangle);
private static IImageProcessingContext<TPixel> VignetteInternal<TPixel>(this IImageProcessingContext<TPixel> source, TPixel color, ValueSize radiusX, ValueSize radiusY, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new VignetteProcessor<TPixel>(color, radiusX, radiusY, options));
=> source.ApplyProcessor(new VignetteProcessor<TPixel>(source.MemoryManager, color, radiusX, radiusY, options));
}
}

4
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<TPixel>(source.Width, source.Height))
using (Buffer2D<TPixel> targetPixels = configuration.MemoryManager.Allocate2D<TPixel>(source.Width, source.Height))
{
source.CopyTo(targetPixels);
@ -122,7 +122,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
}
});
source.SwapPixelsBuffers(targetPixels);
Buffer2D<TPixel>.SwapContents(source.PixelBuffer, targetPixels);
}
}
}

13
src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs

@ -43,15 +43,12 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
int width = source.Width;
int height = source.Height;
ParallelOptions parallelOptions = configuration.ParallelOptions;
using (var firstPassPixels = new PixelAccessor<TPixel>(width, height))
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
using (Buffer2D<TPixel> firstPassPixels = configuration.MemoryManager.Allocate2D<TPixel>(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
/// <param name="kernel">The kernel operator.</param>
/// <param name="parallelOptions">The parellel options</param>
private void ApplyConvolution(
PixelAccessor<TPixel> targetPixels,
PixelAccessor<TPixel> sourcePixels,
Buffer2D<TPixel> targetPixels,
Buffer2D<TPixel> sourcePixels,
Rectangle sourceRectangle,
Fast2DArray<float> kernel,
ParallelOptions parallelOptions)

5
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<TPixel>(source.Width, source.Height))
using (Buffer2D<TPixel> targetPixels = configuration.MemoryManager.Allocate2D<TPixel>(source.Size()))
{
source.CopyTo(targetPixels);
@ -94,7 +95,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
}
});
source.SwapPixelsBuffers(targetPixels);
Buffer2D<TPixel>.SwapContents(source.PixelBuffer, targetPixels);
}
}
}

20
src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs

@ -17,16 +17,20 @@ namespace SixLabors.ImageSharp.Processing.Processors
internal class BackgroundColorProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly MemoryManager memoryManager;
private readonly GraphicsOptions options;
/// <summary>
/// Initializes a new instance of the <see cref="BackgroundColorProcessor{TPixel}"/> class.
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="color">The <typeparamref name="TPixel"/> to set the background color to.</param>
/// <param name="options">The options defining blending algorithum and amount.</param>
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<TPixel>(width))
using (var amount = new Buffer<float>(width))
using (IBuffer<TPixel> colors = this.memoryManager.Allocate<TPixel>(width))
using (IBuffer<float> amount = this.memoryManager.Allocate<float>(width))
{
// Be careful! Do not capture colorSpan & amountSpan in the lambda below!
Span<TPixel> colorSpan = colors.Span;
Span<float> 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<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(this.options.BlenderMode);
@ -86,7 +94,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
Span<TPixel> 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);
});
}
}

5
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<TPixel>(source.Width, source.Height))
using (Buffer2D<TPixel> targetPixels = configuration.MemoryManager.Allocate2D<TPixel>(source.Size()))
{
source.CopyTo(targetPixels);
@ -133,7 +134,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
}
});
source.SwapPixelsBuffers(targetPixels);
Buffer2D<TPixel>.SwapContents(source.PixelBuffer, targetPixels);
}
}
}

11
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<TPixel>
{
private static readonly TPixel VeryDarkGreen = ColorBuilder<TPixel>.FromRGBA(0, 10, 0, 255);
private readonly MemoryManager memoryManager;
private readonly GraphicsOptions options;
/// <summary>
/// Initializes a new instance of the <see cref="LomographProcessor{TPixel}" /> class.
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="options">The options effecting blending and composition.</param>
public LomographProcessor(GraphicsOptions options)
public LomographProcessor(MemoryManager memoryManager, GraphicsOptions options)
: base(MatrixFilters.LomographFilter)
{
this.memoryManager = memoryManager;
this.options = options;
}
/// <inheritdoc/>
protected override void AfterApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
new VignetteProcessor<TPixel>(VeryDarkGreen, this.options).Apply(source, sourceRectangle, configuration);
new VignetteProcessor<TPixel>(this.memoryManager, VeryDarkGreen, this.options).Apply(source, sourceRectangle, configuration);
}
}
}

12
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<TPixel>.FromRGB(102, 34, 0);
private static readonly TPixel LightOrange = ColorBuilder<TPixel>.FromRGBA(255, 153, 102, 128);
private readonly MemoryManager memoryManager;
private readonly GraphicsOptions options;
/// <summary>
/// Initializes a new instance of the <see cref="PolaroidProcessor{TPixel}" /> class.
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="options">The options effecting blending and composition.</param>
public PolaroidProcessor(GraphicsOptions options)
public PolaroidProcessor(MemoryManager memoryManager, GraphicsOptions options)
: base(MatrixFilters.PolaroidFilter)
{
this.memoryManager = memoryManager;
this.options = options;
}
/// <inheritdoc/>
protected override void AfterApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
new VignetteProcessor<TPixel>(VeryDarkOrange, this.options).Apply(source, sourceRectangle, configuration);
new GlowProcessor<TPixel>(LightOrange, source.Width / 4F, this.options).Apply(source, sourceRectangle, configuration);
new VignetteProcessor<TPixel>(this.memoryManager, VeryDarkOrange, this.options).Apply(source, sourceRectangle, configuration);
new GlowProcessor<TPixel>(this.memoryManager, LightOrange, source.Width / 4F, this.options).Apply(source, sourceRectangle, configuration);
}
}
}

22
src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs

@ -19,17 +19,21 @@ namespace SixLabors.ImageSharp.Processing.Processors
internal class GlowProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly MemoryManager memoryManager;
private readonly GraphicsOptions options;
private readonly PixelBlender<TPixel> blender;
/// <summary>
/// Initializes a new instance of the <see cref="GlowProcessor{TPixel}" /> class.
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="color">The color or the glow.</param>
/// <param name="radius">The radius of the glow.</param>
/// <param name="options">The options effecting blending and composition.</param>
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<TPixel>(width))
using (IBuffer<TPixel> rowColors = this.memoryManager.Allocate<TPixel>(width))
{
// Be careful! Do not capture rowColorsSpan in the lambda below!
Span<TPixel> 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<float>(width))
using (IBuffer<float> amounts = this.memoryManager.Allocate<float>(width))
{
Span<float> 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<TPixel> destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width);
this.blender.Blend(destination, destination, rowColors, amounts);
this.blender.Blend(this.memoryManager, destination, destination, rowColors.Span, amountsSpan);
}
});
}

28
src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs

@ -19,21 +19,25 @@ namespace SixLabors.ImageSharp.Processing.Processors
internal class VignetteProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly MemoryManager memoryManager;
private readonly GraphicsOptions options;
private readonly PixelBlender<TPixel> blender;
/// <summary>
/// Initializes a new instance of the <see cref="VignetteProcessor{TPixel}" /> class.
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="color">The color of the vignette.</param>
/// <param name="radiusX">The x-radius.</param>
/// <param name="radiusY">The y-radius.</param>
/// <param name="options">The options effecting blending and composition.</param>
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<TPixel>.Instance.GetPixelBlender(this.options.BlenderMode);
}
@ -41,11 +45,13 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <summary>
/// Initializes a new instance of the <see cref="VignetteProcessor{TPixel}" /> class.
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="color">The color of the vignette.</param>
/// <param name="options">The options effecting blending and composition.</param>
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<TPixel>.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<TPixel>(width))
using (IBuffer<TPixel> rowColors = this.memoryManager.Allocate<TPixel>(width))
{
// Be careful! Do not capture rowColorsSpan in the lambda below!
Span<TPixel> 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<float>(width))
using (IBuffer<float> amounts = this.memoryManager.Allocate<float>(width))
{
Span<float> 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<TPixel> destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width);
this.blender.Blend(destination, destination, rowColors, amounts);
this.blender.Blend(this.memoryManager, destination, destination, rowColors.Span, amountsSpan);
}
});
}

8
src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs

@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
// We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames =
source.Frames.Select(x => new ImageFrame<TPixel>(this.targetDimensions, x.MetaData.Clone()));
source.Frames.Select(x => new ImageFrame<TPixel>(source.GetMemoryManager(), this.targetDimensions, x.MetaData.Clone()));
// Use the overload to prevent an extra frame being added
return new Image<TPixel>(source.GetConfiguration(), source.MetaData.Clone(), frames);
@ -131,8 +131,10 @@ namespace SixLabors.ImageSharp.Processing.Processors
int xLength = (int)MathF.Ceiling((radius.X * 2) + 2);
int yLength = (int)MathF.Ceiling((radius.Y * 2) + 2);
using (var yBuffer = new Buffer2D<float>(yLength, height))
using (var xBuffer = new Buffer2D<float>(xLength, height))
MemoryManager memoryManager = configuration.MemoryManager;
using (Buffer2D<float> yBuffer = memoryManager.Allocate2D<float>(yLength, height))
using (Buffer2D<float> xBuffer = memoryManager.Allocate2D<float>(xLength, height))
{
Parallel.For(
0,

4
src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs

@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
int minX = Math.Max(this.CropRectangle.X, sourceRectangle.X);
int maxX = Math.Min(this.CropRectangle.Right, sourceRectangle.Right);
using (var targetPixels = new PixelAccessor<TPixel>(this.CropRectangle.Width, this.CropRectangle.Height))
using (Buffer2D<TPixel> targetPixels = configuration.MemoryManager.Allocate2D<TPixel>(this.CropRectangle.Size))
{
Parallel.For(
minY,
@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
SpanHelper.Copy(sourceRow, targetRow, maxX - minX);
});
source.SwapPixelsBuffers(targetPixels);
Buffer2D<TPixel>.SwapContents(source.PixelBuffer, targetPixels);
}
}

10
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
/// <param name="configuration">The configuration.</param>
private void FlipX(ImageFrame<TPixel> source, Configuration configuration)
{
int width = source.Width;
int height = source.Height;
int halfHeight = (int)Math.Ceiling(source.Height * .5F);
using (var targetPixels = new PixelAccessor<TPixel>(width, height))
using (Buffer2D<TPixel> targetPixels = configuration.MemoryManager.Allocate2D<TPixel>(source.Size()))
{
Parallel.For(
0,
@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
altSourceRow.CopyTo(targetRow);
});
source.SwapPixelsBuffers(targetPixels);
Buffer2D<TPixel>.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<TPixel>(width, height))
using (Buffer2D<TPixel> targetPixels = configuration.MemoryManager.Allocate2D<TPixel>(source.Size()))
{
Parallel.For(
0,
@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
}
});
source.SwapPixelsBuffers(targetPixels);
Buffer2D<TPixel>.SwapContents(source.PixelBuffer, targetPixels);
}
}
}

13
src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs

@ -71,8 +71,11 @@ namespace SixLabors.ImageSharp.Processing.Processors
}
// We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames =
source.Frames.Select(x => new ImageFrame<TPixel>(this.targetDimensions.Width, this.targetDimensions.Height, x.MetaData.Clone()));
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select(
x => new ImageFrame<TPixel>(
source.GetMemoryManager(),
this.targetDimensions,
x.MetaData.Clone()));
// Use the overload to prevent an extra frame being added
return new Image<TPixel>(source.GetConfiguration(), source.MetaData.Clone(), frames);
@ -128,8 +131,10 @@ namespace SixLabors.ImageSharp.Processing.Processors
int xLength = (int)MathF.Ceiling((radius.X * 2) + 2);
int yLength = (int)MathF.Ceiling((radius.Y * 2) + 2);
using (var yBuffer = new Buffer2D<float>(yLength, height))
using (var xBuffer = new Buffer2D<float>(xLength, height))
MemoryManager memoryManager = configuration.MemoryManager;
using (Buffer2D<float> yBuffer = memoryManager.Allocate2D<float>(yLength, height))
using (Buffer2D<float> xBuffer = memoryManager.Allocate2D<float>(xLength, height))
{
Parallel.For(
0,

11
src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs

@ -3,6 +3,8 @@
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
@ -19,18 +21,21 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <summary>
/// Initializes a new instance of the <see cref="ResamplingWeightedProcessor{TPixel}"/> class.
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="sampler">The sampler to perform the resize operation.</param>
/// <param name="width">The target width.</param>
/// <param name="height">The target height.</param>
/// <param name="resizeRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the target image object to draw to.
/// </param>
protected ResamplingWeightedProcessor(IResampler sampler, int width, int height, Rectangle resizeRectangle)
protected ResamplingWeightedProcessor(MemoryManager memoryManager, IResampler sampler, int width, int height, Rectangle resizeRectangle)
{
Guard.NotNull(memoryManager, nameof(memoryManager));
Guard.NotNull(sampler, nameof(sampler));
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
this.MemoryManager = memoryManager;
this.Sampler = sampler;
this.Width = width;
this.Height = height;
@ -57,6 +62,8 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// </summary>
public Rectangle ResizeRectangle { get; protected set; }
protected MemoryManager MemoryManager { get; }
/// <summary>
/// Gets or sets the horizontal weights.
/// </summary>
@ -86,7 +93,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
IResampler sampler = this.Sampler;
float radius = MathF.Ceiling(scale * sampler.Radius);
var result = new WeightsBuffer(sourceSize, destinationSize);
var result = new WeightsBuffer(this.MemoryManager, sourceSize, destinationSize);
for (int i = 0; i < destinationSize; i++)
{

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save