mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
155 changed files with 3205 additions and 3978 deletions
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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
|
|||
} |
|||
} |
|||
@ -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 |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -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}."); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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() |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
namespace SixLabors.ImageSharp.Memory |
|||
{ |
|||
internal class BasicByteBuffer : BasicArrayBuffer<byte>, IManagedByteBuffer |
|||
{ |
|||
internal BasicByteBuffer(byte[] array) |
|||
: base(array) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -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()); |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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() |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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]); |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue