mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
245 changed files with 8442 additions and 1914 deletions
@ -1 +1 @@ |
|||||
Subproject commit a042aba176cdb840d800c6ed4cfe41a54fb7b1e3 |
Subproject commit 59ce17f5a4e1f956811133f41add7638e74c2836 |
||||
@ -0,0 +1,194 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Buffers; |
||||
|
using SixLabors.ImageSharp.IO; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Pbm |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Pixel decoding methods for the PBM binary encoding.
|
||||
|
/// </summary>
|
||||
|
internal class BinaryDecoder |
||||
|
{ |
||||
|
private static L8 white = new(255); |
||||
|
private static L8 black = new(0); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Decode the specified pixels.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The type of pixel to encode to.</typeparam>
|
||||
|
/// <param name="configuration">The configuration.</param>
|
||||
|
/// <param name="pixels">The pixel array to encode into.</param>
|
||||
|
/// <param name="stream">The stream to read the data from.</param>
|
||||
|
/// <param name="colorType">The ColorType to decode.</param>
|
||||
|
/// <param name="componentType">Data type of the pixles components.</param>
|
||||
|
/// <exception cref="InvalidImageContentException">
|
||||
|
/// Thrown if an invalid combination of setting is requested.
|
||||
|
/// </exception>
|
||||
|
public static void Process<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream, PbmColorType colorType, PbmComponentType componentType) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
if (colorType == PbmColorType.Grayscale) |
||||
|
{ |
||||
|
if (componentType == PbmComponentType.Byte) |
||||
|
{ |
||||
|
ProcessGrayscale(configuration, pixels, stream); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
ProcessWideGrayscale(configuration, pixels, stream); |
||||
|
} |
||||
|
} |
||||
|
else if (colorType == PbmColorType.Rgb) |
||||
|
{ |
||||
|
if (componentType == PbmComponentType.Byte) |
||||
|
{ |
||||
|
ProcessRgb(configuration, pixels, stream); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
ProcessWideRgb(configuration, pixels, stream); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
ProcessBlackAndWhite(configuration, pixels, stream); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void ProcessGrayscale<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
const int bytesPerPixel = 1; |
||||
|
int width = pixels.Width; |
||||
|
int height = pixels.Height; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
||||
|
Span<byte> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
stream.Read(rowSpan); |
||||
|
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
||||
|
PixelOperations<TPixel>.Instance.FromL8Bytes( |
||||
|
configuration, |
||||
|
rowSpan, |
||||
|
pixelSpan, |
||||
|
width); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void ProcessWideGrayscale<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
const int bytesPerPixel = 2; |
||||
|
int width = pixels.Width; |
||||
|
int height = pixels.Height; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
||||
|
Span<byte> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
stream.Read(rowSpan); |
||||
|
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
||||
|
PixelOperations<TPixel>.Instance.FromL16Bytes( |
||||
|
configuration, |
||||
|
rowSpan, |
||||
|
pixelSpan, |
||||
|
width); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void ProcessRgb<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
const int bytesPerPixel = 3; |
||||
|
int width = pixels.Width; |
||||
|
int height = pixels.Height; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
||||
|
Span<byte> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
stream.Read(rowSpan); |
||||
|
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
||||
|
PixelOperations<TPixel>.Instance.FromRgb24Bytes( |
||||
|
configuration, |
||||
|
rowSpan, |
||||
|
pixelSpan, |
||||
|
width); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void ProcessWideRgb<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
const int bytesPerPixel = 6; |
||||
|
int width = pixels.Width; |
||||
|
int height = pixels.Height; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
||||
|
Span<byte> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
stream.Read(rowSpan); |
||||
|
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
||||
|
PixelOperations<TPixel>.Instance.FromRgb48Bytes( |
||||
|
configuration, |
||||
|
rowSpan, |
||||
|
pixelSpan, |
||||
|
width); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void ProcessBlackAndWhite<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
int width = pixels.Width; |
||||
|
int height = pixels.Height; |
||||
|
int startBit = 0; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width); |
||||
|
Span<L8> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < width;) |
||||
|
{ |
||||
|
int raw = stream.ReadByte(); |
||||
|
int bit = startBit; |
||||
|
startBit = 0; |
||||
|
for (; bit < 8; bit++) |
||||
|
{ |
||||
|
bool bitValue = (raw & (0x80 >> bit)) != 0; |
||||
|
rowSpan[x] = bitValue ? black : white; |
||||
|
x++; |
||||
|
if (x == width) |
||||
|
{ |
||||
|
startBit = (bit + 1) & 7; // Round off to below 8.
|
||||
|
if (startBit != 0) |
||||
|
{ |
||||
|
stream.Seek(-1, System.IO.SeekOrigin.Current); |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
||||
|
PixelOperations<TPixel>.Instance.FromL8( |
||||
|
configuration, |
||||
|
rowSpan, |
||||
|
pixelSpan); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,208 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Buffers; |
||||
|
using System.IO; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Pbm |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Pixel encoding methods for the PBM binary encoding.
|
||||
|
/// </summary>
|
||||
|
internal class BinaryEncoder |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Decode pixels into the PBM binary encoding.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The type of input pixel.</typeparam>
|
||||
|
/// <param name="configuration">The configuration.</param>
|
||||
|
/// <param name="stream">The bytestream to write to.</param>
|
||||
|
/// <param name="image">The input image.</param>
|
||||
|
/// <param name="colorType">The ColorType to use.</param>
|
||||
|
/// <param name="componentType">Data type of the pixles components.</param>
|
||||
|
/// <exception cref="InvalidImageContentException">
|
||||
|
/// Thrown if an invalid combination of setting is requested.
|
||||
|
/// </exception>
|
||||
|
public static void WritePixels<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image, PbmColorType colorType, PbmComponentType componentType) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
if (colorType == PbmColorType.Grayscale) |
||||
|
{ |
||||
|
if (componentType == PbmComponentType.Byte) |
||||
|
{ |
||||
|
WriteGrayscale(configuration, stream, image); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
WriteWideGrayscale(configuration, stream, image); |
||||
|
} |
||||
|
} |
||||
|
else if (colorType == PbmColorType.Rgb) |
||||
|
{ |
||||
|
if (componentType == PbmComponentType.Byte) |
||||
|
{ |
||||
|
WriteRgb(configuration, stream, image); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
WriteWideRgb(configuration, stream, image); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
WriteBlackAndWhite(configuration, stream, image); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void WriteGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
int width = image.Width; |
||||
|
int height = image.Height; |
||||
|
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width); |
||||
|
Span<byte> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.ToL8Bytes( |
||||
|
configuration, |
||||
|
pixelSpan, |
||||
|
rowSpan, |
||||
|
width); |
||||
|
|
||||
|
stream.Write(rowSpan); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void WriteWideGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
const int bytesPerPixel = 2; |
||||
|
int width = image.Width; |
||||
|
int height = image.Height; |
||||
|
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
||||
|
Span<byte> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.ToL16Bytes( |
||||
|
configuration, |
||||
|
pixelSpan, |
||||
|
rowSpan, |
||||
|
width); |
||||
|
|
||||
|
stream.Write(rowSpan); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void WriteRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
const int bytesPerPixel = 3; |
||||
|
int width = image.Width; |
||||
|
int height = image.Height; |
||||
|
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
||||
|
Span<byte> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.ToRgb24Bytes( |
||||
|
configuration, |
||||
|
pixelSpan, |
||||
|
rowSpan, |
||||
|
width); |
||||
|
|
||||
|
stream.Write(rowSpan); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void WriteWideRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
const int bytesPerPixel = 6; |
||||
|
int width = image.Width; |
||||
|
int height = image.Height; |
||||
|
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
||||
|
Span<byte> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.ToRgb48Bytes( |
||||
|
configuration, |
||||
|
pixelSpan, |
||||
|
rowSpan, |
||||
|
width); |
||||
|
|
||||
|
stream.Write(rowSpan); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void WriteBlackAndWhite<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
int width = image.Width; |
||||
|
int height = image.Height; |
||||
|
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width); |
||||
|
Span<L8> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
int previousValue = 0; |
||||
|
int startBit = 0; |
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.ToL8( |
||||
|
configuration, |
||||
|
pixelSpan, |
||||
|
rowSpan); |
||||
|
|
||||
|
for (int x = 0; x < width;) |
||||
|
{ |
||||
|
int value = previousValue; |
||||
|
for (int i = startBit; i < 8; i++) |
||||
|
{ |
||||
|
if (rowSpan[x].PackedValue < 128) |
||||
|
{ |
||||
|
value |= 0x80 >> i; |
||||
|
} |
||||
|
|
||||
|
x++; |
||||
|
if (x == width) |
||||
|
{ |
||||
|
previousValue = value; |
||||
|
startBit = (i + 1) & 7; // Round off to below 8.
|
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (startBit == 0) |
||||
|
{ |
||||
|
stream.WriteByte((byte)value); |
||||
|
previousValue = 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.IO; |
||||
|
using SixLabors.ImageSharp.IO; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Pbm |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Extensions methods for <see cref="BufferedReadStream"/>.
|
||||
|
/// </summary>
|
||||
|
internal static class BufferedReadStreamExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Skip over any whitespace or any comments.
|
||||
|
/// </summary>
|
||||
|
public static void SkipWhitespaceAndComments(this BufferedReadStream stream) |
||||
|
{ |
||||
|
bool isWhitespace; |
||||
|
do |
||||
|
{ |
||||
|
int val = stream.ReadByte(); |
||||
|
|
||||
|
// Comments start with '#' and end at the next new-line.
|
||||
|
if (val == 0x23) |
||||
|
{ |
||||
|
int innerValue; |
||||
|
do |
||||
|
{ |
||||
|
innerValue = stream.ReadByte(); |
||||
|
} |
||||
|
while (innerValue != 0x0a); |
||||
|
|
||||
|
// Continue searching for whitespace.
|
||||
|
val = innerValue; |
||||
|
} |
||||
|
|
||||
|
isWhitespace = val is 0x09 or 0x0a or 0x0d or 0x20; |
||||
|
} |
||||
|
while (isWhitespace); |
||||
|
stream.Seek(-1, SeekOrigin.Current); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Read a decimal text value.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The integer value of the decimal.</returns>
|
||||
|
public static int ReadDecimal(this BufferedReadStream stream) |
||||
|
{ |
||||
|
int value = 0; |
||||
|
while (true) |
||||
|
{ |
||||
|
int current = stream.ReadByte() - 0x30; |
||||
|
if ((uint)current > 9) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
value = (value * 10) + current; |
||||
|
} |
||||
|
|
||||
|
return value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Pbm |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Configuration options for use during PBM encoding.
|
||||
|
/// </summary>
|
||||
|
internal interface IPbmEncoderOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the encoding of the pixels.
|
||||
|
/// </summary>
|
||||
|
PbmEncoding? Encoding { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the Color type of the resulting image.
|
||||
|
/// </summary>
|
||||
|
PbmColorType? ColorType { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the Data Type of the pixel components.
|
||||
|
/// </summary>
|
||||
|
PbmComponentType? ComponentType { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Pbm; |
||||
|
using SixLabors.ImageSharp.Metadata; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Extension methods for the <see cref="ImageMetadata"/> type.
|
||||
|
/// </summary>
|
||||
|
public static partial class MetadataExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the pbm format specific metadata for the image.
|
||||
|
/// </summary>
|
||||
|
/// <param name="metadata">The metadata this method extends.</param>
|
||||
|
/// <returns>The <see cref="PbmMetadata"/>.</returns>
|
||||
|
public static PbmMetadata GetPbmMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(PbmFormat.Instance); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Pbm |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Provides enumeration of available PBM color types.
|
||||
|
/// </summary>
|
||||
|
public enum PbmColorType : byte |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// PBM
|
||||
|
/// </summary>
|
||||
|
BlackAndWhite = 0, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// PGM - Greyscale. Single component.
|
||||
|
/// </summary>
|
||||
|
Grayscale = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// PPM - RGB Color. 3 components.
|
||||
|
/// </summary>
|
||||
|
Rgb = 2, |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Pbm |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The data type of the components of the pixels.
|
||||
|
/// </summary>
|
||||
|
public enum PbmComponentType : byte |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Single bit per pixel, exclusively for <see cref="PbmColorType.BlackAndWhite"/>.
|
||||
|
/// </summary>
|
||||
|
Bit = 0, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 8 bits unsigned integer per component.
|
||||
|
/// </summary>
|
||||
|
Byte = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 16 bits unsigned integer per component.
|
||||
|
/// </summary>
|
||||
|
Short = 2 |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Pbm |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Registers the image encoders, decoders and mime type detectors for the Pbm format.
|
||||
|
/// </summary>
|
||||
|
public sealed class PbmConfigurationModule : IConfigurationModule |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public void Configure(Configuration configuration) |
||||
|
{ |
||||
|
configuration.ImageFormatsManager.SetEncoder(PbmFormat.Instance, new PbmEncoder()); |
||||
|
configuration.ImageFormatsManager.SetDecoder(PbmFormat.Instance, new PbmDecoder()); |
||||
|
configuration.ImageFormatsManager.AddImageFormatDetector(new PbmImageFormatDetector()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Pbm |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Contains PBM constant values defined in the specification.
|
||||
|
/// </summary>
|
||||
|
internal static class PbmConstants |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The maximum allowable pixel value of a ppm image.
|
||||
|
/// </summary>
|
||||
|
public const ushort MaxLength = 65535; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The list of mimetypes that equate to a ppm.
|
||||
|
/// </summary>
|
||||
|
public static readonly IEnumerable<string> MimeTypes = new[] { "image/x-portable-pixmap", "image/x-portable-anymap" }; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The list of file extensions that equate to a ppm.
|
||||
|
/// </summary>
|
||||
|
public static readonly IEnumerable<string> FileExtensions = new[] { "ppm", "pbm", "pgm" }; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,79 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.IO; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Pbm |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Image decoder for reading PGM, PBM or PPM bitmaps from a stream. These images are from
|
||||
|
/// the family of PNM images.
|
||||
|
/// <list type="bullet">
|
||||
|
/// <item>
|
||||
|
/// <term>PBM</term>
|
||||
|
/// <description>Black and white images.</description>
|
||||
|
/// </item>
|
||||
|
/// <item>
|
||||
|
/// <term>PGM</term>
|
||||
|
/// <description>Grayscale images.</description>
|
||||
|
/// </item>
|
||||
|
/// <item>
|
||||
|
/// <term>PPM</term>
|
||||
|
/// <description>Color images, with RGB pixels.</description>
|
||||
|
/// </item>
|
||||
|
/// </list>
|
||||
|
/// The specification of these images is found at <seealso href="http://netpbm.sourceforge.net/doc/pnm.html"/>.
|
||||
|
/// </summary>
|
||||
|
public sealed class PbmDecoder : IImageDecoder, IImageInfoDetector |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
Guard.NotNull(stream, nameof(stream)); |
||||
|
|
||||
|
var decoder = new PbmDecoderCore(configuration); |
||||
|
return decoder.Decode<TPixel>(configuration, stream); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public Image Decode(Configuration configuration, Stream stream) |
||||
|
=> this.Decode<Rgb24>(configuration, stream); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
Guard.NotNull(stream, nameof(stream)); |
||||
|
|
||||
|
var decoder = new PbmDecoderCore(configuration); |
||||
|
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) |
||||
|
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken) |
||||
|
.ConfigureAwait(false); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public IImageInfo Identify(Configuration configuration, Stream stream) |
||||
|
{ |
||||
|
Guard.NotNull(stream, nameof(stream)); |
||||
|
|
||||
|
var decoder = new PbmDecoderCore(configuration); |
||||
|
return decoder.Identify(configuration, stream); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
Guard.NotNull(stream, nameof(stream)); |
||||
|
|
||||
|
var decoder = new PbmDecoderCore(configuration); |
||||
|
return decoder.IdentifyAsync(configuration, stream, cancellationToken); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,195 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading; |
||||
|
using SixLabors.ImageSharp.IO; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.Metadata; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.ImageSharp.Processing; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Pbm |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Performs the PBM decoding operation.
|
||||
|
/// </summary>
|
||||
|
internal sealed class PbmDecoderCore : IImageDecoderInternals |
||||
|
{ |
||||
|
private int maxPixelValue; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="PbmDecoderCore" /> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="configuration">The configuration.</param>
|
||||
|
public PbmDecoderCore(Configuration configuration) => this.Configuration = configuration ?? Configuration.Default; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public Configuration Configuration { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the colortype to use
|
||||
|
/// </summary>
|
||||
|
public PbmColorType ColorType { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the size of the pixel array
|
||||
|
/// </summary>
|
||||
|
public Size PixelSize { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the component data type
|
||||
|
/// </summary>
|
||||
|
public PbmComponentType ComponentType { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the Encoding of pixels
|
||||
|
/// </summary>
|
||||
|
public PbmEncoding Encoding { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the <see cref="ImageMetadata"/> decoded by this decoder instance.
|
||||
|
/// </summary>
|
||||
|
public ImageMetadata Metadata { get; private set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
Size IImageDecoderInternals.Dimensions => this.PixelSize; |
||||
|
|
||||
|
private bool NeedsUpscaling => this.ColorType != PbmColorType.BlackAndWhite && this.maxPixelValue is not 255 and not 65535; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
this.ProcessHeader(stream); |
||||
|
|
||||
|
var image = new Image<TPixel>(this.Configuration, this.PixelSize.Width, this.PixelSize.Height, this.Metadata); |
||||
|
|
||||
|
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer(); |
||||
|
|
||||
|
this.ProcessPixels(stream, pixels); |
||||
|
if (this.NeedsUpscaling) |
||||
|
{ |
||||
|
this.ProcessUpscaling(image); |
||||
|
} |
||||
|
|
||||
|
return image; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
this.ProcessHeader(stream); |
||||
|
|
||||
|
// BlackAndWhite pixels are encoded into a byte.
|
||||
|
int bitsPerPixel = this.ComponentType == PbmComponentType.Short ? 16 : 8; |
||||
|
return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.PixelSize.Width, this.PixelSize.Height, this.Metadata); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Processes the ppm header.
|
||||
|
/// </summary>
|
||||
|
/// <param name="stream">The input stream.</param>
|
||||
|
private void ProcessHeader(BufferedReadStream stream) |
||||
|
{ |
||||
|
Span<byte> buffer = stackalloc byte[2]; |
||||
|
|
||||
|
int bytesRead = stream.Read(buffer); |
||||
|
if (bytesRead != 2 || buffer[0] != 'P') |
||||
|
{ |
||||
|
throw new InvalidImageContentException("Empty or not an PPM image."); |
||||
|
} |
||||
|
|
||||
|
switch ((char)buffer[1]) |
||||
|
{ |
||||
|
case '1': |
||||
|
// Plain PBM format: 1 component per pixel, boolean value ('0' or '1').
|
||||
|
this.ColorType = PbmColorType.BlackAndWhite; |
||||
|
this.Encoding = PbmEncoding.Plain; |
||||
|
break; |
||||
|
case '2': |
||||
|
// Plain PGM format: 1 component per pixel, in decimal text.
|
||||
|
this.ColorType = PbmColorType.Grayscale; |
||||
|
this.Encoding = PbmEncoding.Plain; |
||||
|
break; |
||||
|
case '3': |
||||
|
// Plain PPM format: 3 components per pixel, in decimal text.
|
||||
|
this.ColorType = PbmColorType.Rgb; |
||||
|
this.Encoding = PbmEncoding.Plain; |
||||
|
break; |
||||
|
case '4': |
||||
|
// Binary PBM format: 1 component per pixel, 8 pixels per byte.
|
||||
|
this.ColorType = PbmColorType.BlackAndWhite; |
||||
|
this.Encoding = PbmEncoding.Binary; |
||||
|
break; |
||||
|
case '5': |
||||
|
// Binary PGM format: 1 components per pixel, in binary integers.
|
||||
|
this.ColorType = PbmColorType.Grayscale; |
||||
|
this.Encoding = PbmEncoding.Binary; |
||||
|
break; |
||||
|
case '6': |
||||
|
// Binary PPM format: 3 components per pixel, in binary integers.
|
||||
|
this.ColorType = PbmColorType.Rgb; |
||||
|
this.Encoding = PbmEncoding.Binary; |
||||
|
break; |
||||
|
case '7': |
||||
|
// PAM image: sequence of images.
|
||||
|
// Not implemented yet
|
||||
|
default: |
||||
|
throw new InvalidImageContentException("Unknown of not implemented image type encountered."); |
||||
|
} |
||||
|
|
||||
|
stream.SkipWhitespaceAndComments(); |
||||
|
int width = stream.ReadDecimal(); |
||||
|
stream.SkipWhitespaceAndComments(); |
||||
|
int height = stream.ReadDecimal(); |
||||
|
stream.SkipWhitespaceAndComments(); |
||||
|
if (this.ColorType != PbmColorType.BlackAndWhite) |
||||
|
{ |
||||
|
this.maxPixelValue = stream.ReadDecimal(); |
||||
|
if (this.maxPixelValue > 255) |
||||
|
{ |
||||
|
this.ComponentType = PbmComponentType.Short; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this.ComponentType = PbmComponentType.Byte; |
||||
|
} |
||||
|
|
||||
|
stream.SkipWhitespaceAndComments(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this.ComponentType = PbmComponentType.Bit; |
||||
|
} |
||||
|
|
||||
|
this.PixelSize = new Size(width, height); |
||||
|
this.Metadata = new ImageMetadata(); |
||||
|
PbmMetadata meta = this.Metadata.GetPbmMetadata(); |
||||
|
meta.Encoding = this.Encoding; |
||||
|
meta.ColorType = this.ColorType; |
||||
|
meta.ComponentType = this.ComponentType; |
||||
|
} |
||||
|
|
||||
|
private void ProcessPixels<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
if (this.Encoding == PbmEncoding.Binary) |
||||
|
{ |
||||
|
BinaryDecoder.Process(this.Configuration, pixels, stream, this.ColorType, this.ComponentType); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
PlainDecoder.Process(this.Configuration, pixels, stream, this.ColorType, this.ComponentType); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void ProcessUpscaling<TPixel>(Image<TPixel> image) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
int maxAllocationValue = this.ComponentType == PbmComponentType.Short ? 65535 : 255; |
||||
|
float factor = maxAllocationValue / this.maxPixelValue; |
||||
|
image.Mutate(x => x.Brightness(factor)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,69 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.IO; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using SixLabors.ImageSharp.Advanced; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Pbm |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Image encoder for writing an image to a stream as PGM, PBM or PPM bitmap. These images are from
|
||||
|
/// the family of PNM images.
|
||||
|
/// <para>
|
||||
|
/// The PNM formats are a fairly simple image format. They share a plain text header, consisting of:
|
||||
|
/// signature, width, height and max_pixel_value only. The pixels follow thereafter and can be in
|
||||
|
/// plain text decimals separated by spaces, or binary encoded.
|
||||
|
/// <list type="bullet">
|
||||
|
/// <item>
|
||||
|
/// <term>PBM</term>
|
||||
|
/// <description>Black and white images, with 1 representing black and 0 representing white.</description>
|
||||
|
/// </item>
|
||||
|
/// <item>
|
||||
|
/// <term>PGM</term>
|
||||
|
/// <description>Grayscale images, scaling from 0 to max_pixel_value, 0 representing black and max_pixel_value representing white.</description>
|
||||
|
/// </item>
|
||||
|
/// <item>
|
||||
|
/// <term>PPM</term>
|
||||
|
/// <description>Color images, with RGB pixels (in that order), with 0 representing black and 2 representing full color.</description>
|
||||
|
/// </item>
|
||||
|
/// </list>
|
||||
|
/// </para>
|
||||
|
/// The specification of these images is found at <seealso href="http://netpbm.sourceforge.net/doc/pnm.html"/>.
|
||||
|
/// </summary>
|
||||
|
public sealed class PbmEncoder : IImageEncoder, IPbmEncoderOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets or sets the Encoding of the pixels.
|
||||
|
/// </summary>
|
||||
|
public PbmEncoding? Encoding { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the Color type of the resulting image.
|
||||
|
/// </summary>
|
||||
|
public PbmColorType? ColorType { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the data type of the pixels components.
|
||||
|
/// </summary>
|
||||
|
public PbmComponentType? ComponentType { get; set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void Encode<TPixel>(Image<TPixel> image, Stream stream) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
var encoder = new PbmEncoderCore(image.GetConfiguration(), this); |
||||
|
encoder.Encode(image, stream); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
var encoder = new PbmEncoderCore(image.GetConfiguration(), this); |
||||
|
return encoder.EncodeAsync(image, stream, cancellationToken); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,187 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Buffers.Text; |
||||
|
using System.IO; |
||||
|
using System.Threading; |
||||
|
using SixLabors.ImageSharp.Advanced; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Pbm |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Image encoder for writing an image to a stream as a PGM, PBM, PPM or PAM bitmap.
|
||||
|
/// </summary>
|
||||
|
internal sealed class PbmEncoderCore : IImageEncoderInternals |
||||
|
{ |
||||
|
private const byte NewLine = (byte)'\n'; |
||||
|
private const byte Space = (byte)' '; |
||||
|
private const byte P = (byte)'P'; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The global configuration.
|
||||
|
/// </summary>
|
||||
|
private Configuration configuration; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The encoder options.
|
||||
|
/// </summary>
|
||||
|
private readonly IPbmEncoderOptions options; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The encoding for the pixels.
|
||||
|
/// </summary>
|
||||
|
private PbmEncoding encoding; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the Color type of the resulting image.
|
||||
|
/// </summary>
|
||||
|
private PbmColorType colorType; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the maximum pixel value, per component.
|
||||
|
/// </summary>
|
||||
|
private PbmComponentType componentType; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="PbmEncoderCore"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="configuration">The configuration.</param>
|
||||
|
/// <param name="options">The encoder options.</param>
|
||||
|
public PbmEncoderCore(Configuration configuration, IPbmEncoderOptions options) |
||||
|
{ |
||||
|
this.configuration = configuration; |
||||
|
this.options = options; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
|
||||
|
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
|
||||
|
/// <param name="cancellationToken">The token to request cancellation.</param>
|
||||
|
public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
Guard.NotNull(image, nameof(image)); |
||||
|
Guard.NotNull(stream, nameof(stream)); |
||||
|
|
||||
|
this.DeduceOptions(image); |
||||
|
|
||||
|
byte signature = this.DeduceSignature(); |
||||
|
this.WriteHeader(stream, signature, image.Size()); |
||||
|
|
||||
|
this.WritePixels(stream, image.Frames.RootFrame); |
||||
|
|
||||
|
stream.Flush(); |
||||
|
} |
||||
|
|
||||
|
private void DeduceOptions<TPixel>(Image<TPixel> image) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
this.configuration = image.GetConfiguration(); |
||||
|
PbmMetadata metadata = image.Metadata.GetPbmMetadata(); |
||||
|
this.encoding = this.options.Encoding ?? metadata.Encoding; |
||||
|
this.colorType = this.options.ColorType ?? metadata.ColorType; |
||||
|
if (this.colorType != PbmColorType.BlackAndWhite) |
||||
|
{ |
||||
|
this.componentType = this.options.ComponentType ?? metadata.ComponentType; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this.componentType = PbmComponentType.Bit; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private byte DeduceSignature() |
||||
|
{ |
||||
|
byte signature; |
||||
|
if (this.colorType == PbmColorType.BlackAndWhite) |
||||
|
{ |
||||
|
if (this.encoding == PbmEncoding.Plain) |
||||
|
{ |
||||
|
signature = (byte)'1'; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
signature = (byte)'4'; |
||||
|
} |
||||
|
} |
||||
|
else if (this.colorType == PbmColorType.Grayscale) |
||||
|
{ |
||||
|
if (this.encoding == PbmEncoding.Plain) |
||||
|
{ |
||||
|
signature = (byte)'2'; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
signature = (byte)'5'; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// RGB ColorType
|
||||
|
if (this.encoding == PbmEncoding.Plain) |
||||
|
{ |
||||
|
signature = (byte)'3'; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
signature = (byte)'6'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return signature; |
||||
|
} |
||||
|
|
||||
|
private void WriteHeader(Stream stream, byte signature, Size pixelSize) |
||||
|
{ |
||||
|
Span<byte> buffer = stackalloc byte[128]; |
||||
|
|
||||
|
int written = 3; |
||||
|
buffer[0] = P; |
||||
|
buffer[1] = signature; |
||||
|
buffer[2] = NewLine; |
||||
|
|
||||
|
Utf8Formatter.TryFormat(pixelSize.Width, buffer.Slice(written), out int bytesWritten); |
||||
|
written += bytesWritten; |
||||
|
buffer[written++] = Space; |
||||
|
Utf8Formatter.TryFormat(pixelSize.Height, buffer.Slice(written), out bytesWritten); |
||||
|
written += bytesWritten; |
||||
|
buffer[written++] = NewLine; |
||||
|
|
||||
|
if (this.colorType != PbmColorType.BlackAndWhite) |
||||
|
{ |
||||
|
int maxPixelValue = this.componentType == PbmComponentType.Short ? 65535 : 255; |
||||
|
Utf8Formatter.TryFormat(maxPixelValue, buffer.Slice(written), out bytesWritten); |
||||
|
written += bytesWritten; |
||||
|
buffer[written++] = NewLine; |
||||
|
} |
||||
|
|
||||
|
stream.Write(buffer, 0, written); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Writes the pixel data to the binary stream.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
|
||||
|
/// <param name="image">
|
||||
|
/// The <see cref="ImageFrame{TPixel}"/> containing pixel data.
|
||||
|
/// </param>
|
||||
|
private void WritePixels<TPixel>(Stream stream, ImageFrame<TPixel> image) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
if (this.encoding == PbmEncoding.Plain) |
||||
|
{ |
||||
|
PlainEncoder.WritePixels(this.configuration, stream, image, this.colorType, this.componentType); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
BinaryEncoder.WritePixels(this.configuration, stream, image, this.colorType, this.componentType); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Pbm |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Provides enumeration of available PBM encodings.
|
||||
|
/// </summary>
|
||||
|
public enum PbmEncoding : byte |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Plain text decimal encoding.
|
||||
|
/// </summary>
|
||||
|
Plain = 0, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Binary integer encoding.
|
||||
|
/// </summary>
|
||||
|
Binary = 1, |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Pbm |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Registers the image encoders, decoders and mime type detectors for the PBM format.
|
||||
|
/// </summary>
|
||||
|
public sealed class PbmFormat : IImageFormat<PbmMetadata> |
||||
|
{ |
||||
|
private PbmFormat() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the current instance.
|
||||
|
/// </summary>
|
||||
|
public static PbmFormat Instance { get; } = new(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public string Name => "PBM"; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public string DefaultMimeType => "image/x-portable-pixmap"; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public IEnumerable<string> MimeTypes => PbmConstants.MimeTypes; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public IEnumerable<string> FileExtensions => PbmConstants.FileExtensions; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public PbmMetadata CreateDefaultFormatMetadata() => new(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Pbm |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Detects Pbm file headers.
|
||||
|
/// </summary>
|
||||
|
public sealed class PbmImageFormatDetector : IImageFormatDetector |
||||
|
{ |
||||
|
private const byte P = (byte)'P'; |
||||
|
private const byte Zero = (byte)'0'; |
||||
|
private const byte Seven = (byte)'7'; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public int HeaderSize => 2; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public IImageFormat DetectFormat(ReadOnlySpan<byte> header) => this.IsSupportedFileFormat(header) ? PbmFormat.Instance : null; |
||||
|
|
||||
|
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header) |
||||
|
{ |
||||
|
#pragma warning disable SA1131 // Use readable conditions
|
||||
|
if (1 < (uint)header.Length) |
||||
|
#pragma warning restore SA1131 // Use readable conditions
|
||||
|
{ |
||||
|
// Signature should be between P1 and P6.
|
||||
|
return header[0] == P && (uint)(header[1] - Zero - 1) < (Seven - Zero - 1); |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Pbm |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Provides PBM specific metadata information for the image.
|
||||
|
/// </summary>
|
||||
|
public class PbmMetadata : IDeepCloneable |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="PbmMetadata"/> class.
|
||||
|
/// </summary>
|
||||
|
public PbmMetadata() => |
||||
|
this.ComponentType = this.ColorType == PbmColorType.BlackAndWhite ? PbmComponentType.Bit : PbmComponentType.Byte; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="PbmMetadata"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="other">The metadata to create an instance from.</param>
|
||||
|
private PbmMetadata(PbmMetadata other) |
||||
|
{ |
||||
|
this.Encoding = other.Encoding; |
||||
|
this.ColorType = other.ColorType; |
||||
|
this.ComponentType = other.ComponentType; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the encoding of the pixels.
|
||||
|
/// </summary>
|
||||
|
public PbmEncoding Encoding { get; set; } = PbmEncoding.Plain; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the color type.
|
||||
|
/// </summary>
|
||||
|
public PbmColorType ColorType { get; set; } = PbmColorType.Grayscale; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the data type of the pixel components.
|
||||
|
/// </summary>
|
||||
|
public PbmComponentType ComponentType { get; set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public IDeepCloneable DeepClone() => new PbmMetadata(this); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,198 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Buffers; |
||||
|
using SixLabors.ImageSharp.IO; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Pbm |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Pixel decoding methods for the PBM plain encoding.
|
||||
|
/// </summary>
|
||||
|
internal class PlainDecoder |
||||
|
{ |
||||
|
private static readonly L8 White = new(255); |
||||
|
private static readonly L8 Black = new(0); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Decode the specified pixels.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The type of pixel to encode to.</typeparam>
|
||||
|
/// <param name="configuration">The configuration.</param>
|
||||
|
/// <param name="pixels">The pixel array to encode into.</param>
|
||||
|
/// <param name="stream">The stream to read the data from.</param>
|
||||
|
/// <param name="colorType">The ColorType to decode.</param>
|
||||
|
/// <param name="componentType">Data type of the pixles components.</param>
|
||||
|
public static void Process<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream, PbmColorType colorType, PbmComponentType componentType) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
if (colorType == PbmColorType.Grayscale) |
||||
|
{ |
||||
|
if (componentType == PbmComponentType.Byte) |
||||
|
{ |
||||
|
ProcessGrayscale(configuration, pixels, stream); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
ProcessWideGrayscale(configuration, pixels, stream); |
||||
|
} |
||||
|
} |
||||
|
else if (colorType == PbmColorType.Rgb) |
||||
|
{ |
||||
|
if (componentType == PbmComponentType.Byte) |
||||
|
{ |
||||
|
ProcessRgb(configuration, pixels, stream); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
ProcessWideRgb(configuration, pixels, stream); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
ProcessBlackAndWhite(configuration, pixels, stream); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void ProcessGrayscale<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
int width = pixels.Width; |
||||
|
int height = pixels.Height; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width); |
||||
|
Span<L8> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < width; x++) |
||||
|
{ |
||||
|
byte value = (byte)stream.ReadDecimal(); |
||||
|
stream.SkipWhitespaceAndComments(); |
||||
|
rowSpan[x] = new L8(value); |
||||
|
} |
||||
|
|
||||
|
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
||||
|
PixelOperations<TPixel>.Instance.FromL8( |
||||
|
configuration, |
||||
|
rowSpan, |
||||
|
pixelSpan); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void ProcessWideGrayscale<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
int width = pixels.Width; |
||||
|
int height = pixels.Height; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<L16> row = allocator.Allocate<L16>(width); |
||||
|
Span<L16> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < width; x++) |
||||
|
{ |
||||
|
ushort value = (ushort)stream.ReadDecimal(); |
||||
|
stream.SkipWhitespaceAndComments(); |
||||
|
rowSpan[x] = new L16(value); |
||||
|
} |
||||
|
|
||||
|
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
||||
|
PixelOperations<TPixel>.Instance.FromL16( |
||||
|
configuration, |
||||
|
rowSpan, |
||||
|
pixelSpan); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void ProcessRgb<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
int width = pixels.Width; |
||||
|
int height = pixels.Height; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<Rgb24> row = allocator.Allocate<Rgb24>(width); |
||||
|
Span<Rgb24> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < width; x++) |
||||
|
{ |
||||
|
byte red = (byte)stream.ReadDecimal(); |
||||
|
stream.SkipWhitespaceAndComments(); |
||||
|
byte green = (byte)stream.ReadDecimal(); |
||||
|
stream.SkipWhitespaceAndComments(); |
||||
|
byte blue = (byte)stream.ReadDecimal(); |
||||
|
stream.SkipWhitespaceAndComments(); |
||||
|
rowSpan[x] = new Rgb24(red, green, blue); |
||||
|
} |
||||
|
|
||||
|
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
||||
|
PixelOperations<TPixel>.Instance.FromRgb24( |
||||
|
configuration, |
||||
|
rowSpan, |
||||
|
pixelSpan); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void ProcessWideRgb<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
int width = pixels.Width; |
||||
|
int height = pixels.Height; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<Rgb48> row = allocator.Allocate<Rgb48>(width); |
||||
|
Span<Rgb48> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < width; x++) |
||||
|
{ |
||||
|
ushort red = (ushort)stream.ReadDecimal(); |
||||
|
stream.SkipWhitespaceAndComments(); |
||||
|
ushort green = (ushort)stream.ReadDecimal(); |
||||
|
stream.SkipWhitespaceAndComments(); |
||||
|
ushort blue = (ushort)stream.ReadDecimal(); |
||||
|
stream.SkipWhitespaceAndComments(); |
||||
|
rowSpan[x] = new Rgb48(red, green, blue); |
||||
|
} |
||||
|
|
||||
|
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
||||
|
PixelOperations<TPixel>.Instance.FromRgb48( |
||||
|
configuration, |
||||
|
rowSpan, |
||||
|
pixelSpan); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void ProcessBlackAndWhite<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
int width = pixels.Width; |
||||
|
int height = pixels.Height; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width); |
||||
|
Span<L8> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < width; x++) |
||||
|
{ |
||||
|
int value = stream.ReadDecimal(); |
||||
|
stream.SkipWhitespaceAndComments(); |
||||
|
rowSpan[x] = value == 0 ? White : Black; |
||||
|
} |
||||
|
|
||||
|
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
||||
|
PixelOperations<TPixel>.Instance.FromL8( |
||||
|
configuration, |
||||
|
rowSpan, |
||||
|
pixelSpan); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,251 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Buffers; |
||||
|
using System.Buffers.Text; |
||||
|
using System.IO; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Pbm |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Pixel encoding methods for the PBM plain encoding.
|
||||
|
/// </summary>
|
||||
|
internal class PlainEncoder |
||||
|
{ |
||||
|
private const byte NewLine = 0x0a; |
||||
|
private const byte Space = 0x20; |
||||
|
private const byte Zero = 0x30; |
||||
|
private const byte One = 0x31; |
||||
|
|
||||
|
private const int MaxCharsPerPixelBlackAndWhite = 2; |
||||
|
private const int MaxCharsPerPixelGrayscale = 4; |
||||
|
private const int MaxCharsPerPixelGrayscaleWide = 6; |
||||
|
private const int MaxCharsPerPixelRgb = 4 * 3; |
||||
|
private const int MaxCharsPerPixelRgbWide = 6 * 3; |
||||
|
|
||||
|
private static readonly StandardFormat DecimalFormat = StandardFormat.Parse("D"); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Decode pixels into the PBM plain encoding.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The type of input pixel.</typeparam>
|
||||
|
/// <param name="configuration">The configuration.</param>
|
||||
|
/// <param name="stream">The bytestream to write to.</param>
|
||||
|
/// <param name="image">The input image.</param>
|
||||
|
/// <param name="colorType">The ColorType to use.</param>
|
||||
|
/// <param name="componentType">Data type of the pixles components.</param>
|
||||
|
public static void WritePixels<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image, PbmColorType colorType, PbmComponentType componentType) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
if (colorType == PbmColorType.Grayscale) |
||||
|
{ |
||||
|
if (componentType == PbmComponentType.Byte) |
||||
|
{ |
||||
|
WriteGrayscale(configuration, stream, image); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
WriteWideGrayscale(configuration, stream, image); |
||||
|
} |
||||
|
} |
||||
|
else if (colorType == PbmColorType.Rgb) |
||||
|
{ |
||||
|
if (componentType == PbmComponentType.Byte) |
||||
|
{ |
||||
|
WriteRgb(configuration, stream, image); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
WriteWideRgb(configuration, stream, image); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
WriteBlackAndWhite(configuration, stream, image); |
||||
|
} |
||||
|
|
||||
|
// Write EOF indicator, as some encoders expect it.
|
||||
|
stream.WriteByte(Space); |
||||
|
} |
||||
|
|
||||
|
private static void WriteGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
int width = image.Width; |
||||
|
int height = image.Height; |
||||
|
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width); |
||||
|
Span<L8> rowSpan = row.GetSpan(); |
||||
|
using IMemoryOwner<byte> plainMemory = allocator.Allocate<byte>(width * MaxCharsPerPixelGrayscale); |
||||
|
Span<byte> plainSpan = plainMemory.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
||||
|
PixelOperations<TPixel>.Instance.ToL8( |
||||
|
configuration, |
||||
|
pixelSpan, |
||||
|
rowSpan); |
||||
|
|
||||
|
int written = 0; |
||||
|
for (int x = 0; x < width; x++) |
||||
|
{ |
||||
|
Utf8Formatter.TryFormat(rowSpan[x].PackedValue, plainSpan.Slice(written), out int bytesWritten, DecimalFormat); |
||||
|
written += bytesWritten; |
||||
|
plainSpan[written++] = Space; |
||||
|
} |
||||
|
|
||||
|
plainSpan[written - 1] = NewLine; |
||||
|
stream.Write(plainSpan, 0, written); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void WriteWideGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
int width = image.Width; |
||||
|
int height = image.Height; |
||||
|
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<L16> row = allocator.Allocate<L16>(width); |
||||
|
Span<L16> rowSpan = row.GetSpan(); |
||||
|
using IMemoryOwner<byte> plainMemory = allocator.Allocate<byte>(width * MaxCharsPerPixelGrayscaleWide); |
||||
|
Span<byte> plainSpan = plainMemory.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
||||
|
PixelOperations<TPixel>.Instance.ToL16( |
||||
|
configuration, |
||||
|
pixelSpan, |
||||
|
rowSpan); |
||||
|
|
||||
|
int written = 0; |
||||
|
for (int x = 0; x < width; x++) |
||||
|
{ |
||||
|
Utf8Formatter.TryFormat(rowSpan[x].PackedValue, plainSpan.Slice(written), out int bytesWritten, DecimalFormat); |
||||
|
written += bytesWritten; |
||||
|
plainSpan[written++] = Space; |
||||
|
} |
||||
|
|
||||
|
plainSpan[written - 1] = NewLine; |
||||
|
stream.Write(plainSpan, 0, written); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void WriteRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
int width = image.Width; |
||||
|
int height = image.Height; |
||||
|
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<Rgb24> row = allocator.Allocate<Rgb24>(width); |
||||
|
Span<Rgb24> rowSpan = row.GetSpan(); |
||||
|
using IMemoryOwner<byte> plainMemory = allocator.Allocate<byte>(width * MaxCharsPerPixelRgb); |
||||
|
Span<byte> plainSpan = plainMemory.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
||||
|
PixelOperations<TPixel>.Instance.ToRgb24( |
||||
|
configuration, |
||||
|
pixelSpan, |
||||
|
rowSpan); |
||||
|
|
||||
|
int written = 0; |
||||
|
for (int x = 0; x < width; x++) |
||||
|
{ |
||||
|
Utf8Formatter.TryFormat(rowSpan[x].R, plainSpan.Slice(written), out int bytesWritten, DecimalFormat); |
||||
|
written += bytesWritten; |
||||
|
plainSpan[written++] = Space; |
||||
|
Utf8Formatter.TryFormat(rowSpan[x].G, plainSpan.Slice(written), out bytesWritten, DecimalFormat); |
||||
|
written += bytesWritten; |
||||
|
plainSpan[written++] = Space; |
||||
|
Utf8Formatter.TryFormat(rowSpan[x].B, plainSpan.Slice(written), out bytesWritten, DecimalFormat); |
||||
|
written += bytesWritten; |
||||
|
plainSpan[written++] = Space; |
||||
|
} |
||||
|
|
||||
|
plainSpan[written - 1] = NewLine; |
||||
|
stream.Write(plainSpan, 0, written); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void WriteWideRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
int width = image.Width; |
||||
|
int height = image.Height; |
||||
|
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<Rgb48> row = allocator.Allocate<Rgb48>(width); |
||||
|
Span<Rgb48> rowSpan = row.GetSpan(); |
||||
|
using IMemoryOwner<byte> plainMemory = allocator.Allocate<byte>(width * MaxCharsPerPixelRgbWide); |
||||
|
Span<byte> plainSpan = plainMemory.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
||||
|
PixelOperations<TPixel>.Instance.ToRgb48( |
||||
|
configuration, |
||||
|
pixelSpan, |
||||
|
rowSpan); |
||||
|
|
||||
|
int written = 0; |
||||
|
for (int x = 0; x < width; x++) |
||||
|
{ |
||||
|
Utf8Formatter.TryFormat(rowSpan[x].R, plainSpan.Slice(written), out int bytesWritten, DecimalFormat); |
||||
|
written += bytesWritten; |
||||
|
plainSpan[written++] = Space; |
||||
|
Utf8Formatter.TryFormat(rowSpan[x].G, plainSpan.Slice(written), out bytesWritten, DecimalFormat); |
||||
|
written += bytesWritten; |
||||
|
plainSpan[written++] = Space; |
||||
|
Utf8Formatter.TryFormat(rowSpan[x].B, plainSpan.Slice(written), out bytesWritten, DecimalFormat); |
||||
|
written += bytesWritten; |
||||
|
plainSpan[written++] = Space; |
||||
|
} |
||||
|
|
||||
|
plainSpan[written - 1] = NewLine; |
||||
|
stream.Write(plainSpan, 0, written); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void WriteBlackAndWhite<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
int width = image.Width; |
||||
|
int height = image.Height; |
||||
|
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width); |
||||
|
Span<L8> rowSpan = row.GetSpan(); |
||||
|
using IMemoryOwner<byte> plainMemory = allocator.Allocate<byte>(width * MaxCharsPerPixelBlackAndWhite); |
||||
|
Span<byte> plainSpan = plainMemory.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
||||
|
PixelOperations<TPixel>.Instance.ToL8( |
||||
|
configuration, |
||||
|
pixelSpan, |
||||
|
rowSpan); |
||||
|
|
||||
|
int written = 0; |
||||
|
for (int x = 0; x < width; x++) |
||||
|
{ |
||||
|
byte value = (rowSpan[x].PackedValue < 128) ? One : Zero; |
||||
|
plainSpan[written++] = value; |
||||
|
plainSpan[written++] = Space; |
||||
|
} |
||||
|
|
||||
|
plainSpan[written - 1] = NewLine; |
||||
|
stream.Write(plainSpan, 0, written); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,21 +1,24 @@ |
|||||
// Copyright (c) Six Labors.
|
// Copyright (c) Six Labors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Memory |
namespace SixLabors.ImageSharp.Memory |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Options for allocating buffers.
|
/// Options for allocating buffers.
|
||||
/// </summary>
|
/// </summary>
|
||||
|
[Flags] |
||||
public enum AllocationOptions |
public enum AllocationOptions |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Indicates that the buffer should just be allocated.
|
/// Indicates that the buffer should just be allocated.
|
||||
/// </summary>
|
/// </summary>
|
||||
None, |
None = 0, |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Indicates that the allocated buffer should be cleaned following allocation.
|
/// Indicates that the allocated buffer should be cleaned following allocation.
|
||||
/// </summary>
|
/// </summary>
|
||||
Clean |
Clean = 1 |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,10 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Memory |
||||
|
{ |
||||
|
internal static class AllocationOptionsExtensions |
||||
|
{ |
||||
|
public static bool Has(this AllocationOptions options, AllocationOptions flag) => (options & flag) == flag; |
||||
|
} |
||||
|
} |
||||
@ -1,105 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Buffers; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using SixLabors.ImageSharp.Memory.Internals; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Memory |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Contains <see cref="Buffer{T}"/> and <see cref="ManagedByteBuffer"/>.
|
|
||||
/// </summary>
|
|
||||
public partial class ArrayPoolMemoryAllocator |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The buffer implementation of <see cref="ArrayPoolMemoryAllocator"/>.
|
|
||||
/// </summary>
|
|
||||
private class Buffer<T> : ManagedBufferBase<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="ArrayPoolMemoryAllocator.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 override Span<T> GetSpan() |
|
||||
{ |
|
||||
if (this.Data is null) |
|
||||
{ |
|
||||
ThrowObjectDisposedException(); |
|
||||
} |
|
||||
#if SUPPORTS_CREATESPAN
|
|
||||
ref byte r0 = ref MemoryMarshal.GetReference<byte>(this.Data); |
|
||||
return MemoryMarshal.CreateSpan(ref Unsafe.As<byte, T>(ref r0), this.length); |
|
||||
#else
|
|
||||
return MemoryMarshal.Cast<byte, T>(this.Data.AsSpan()).Slice(0, this.length); |
|
||||
#endif
|
|
||||
|
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc />
|
|
||||
protected override void Dispose(bool disposing) |
|
||||
{ |
|
||||
if (!disposing || this.Data is null || this.sourcePoolReference is null) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
if (this.sourcePoolReference.TryGetTarget(out ArrayPool<byte> pool)) |
|
||||
{ |
|
||||
pool.Return(this.Data); |
|
||||
} |
|
||||
|
|
||||
this.sourcePoolReference = null; |
|
||||
this.Data = null; |
|
||||
} |
|
||||
|
|
||||
protected override object GetPinnableObject() => this.Data; |
|
||||
|
|
||||
[MethodImpl(InliningOptions.ColdPath)] |
|
||||
private static void ThrowObjectDisposedException() |
|
||||
{ |
|
||||
throw new ObjectDisposedException("ArrayPoolMemoryAllocator.Buffer<T>"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The <see cref="IManagedByteBuffer"/> implementation of <see cref="ArrayPoolMemoryAllocator"/>.
|
|
||||
/// </summary>
|
|
||||
private sealed class ManagedByteBuffer : Buffer<byte>, IManagedByteBuffer |
|
||||
{ |
|
||||
public ManagedByteBuffer(byte[] data, int length, ArrayPool<byte> sourcePool) |
|
||||
: base(data, length, sourcePool) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc />
|
|
||||
public byte[] Array => this.Data; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,76 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Memory |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Contains common factory methods and configuration constants.
|
|
||||
/// </summary>
|
|
||||
public partial class ArrayPoolMemoryAllocator |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The default value for: maximum size of pooled arrays in bytes.
|
|
||||
/// Currently set to 24MB, which is equivalent to 8 megapixels of raw RGBA32 data.
|
|
||||
/// </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; |
|
||||
|
|
||||
// TODO: This value should be determined by benchmarking
|
|
||||
private const int DefaultBufferCapacityInBytes = int.MaxValue / 4; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// This is the default. Should be good for most use cases.
|
|
||||
/// </summary>
|
|
||||
/// <returns>The memory manager.</returns>
|
|
||||
public static ArrayPoolMemoryAllocator CreateDefault() |
|
||||
{ |
|
||||
return new ArrayPoolMemoryAllocator( |
|
||||
DefaultMaxPooledBufferSizeInBytes, |
|
||||
DefaultBufferSelectorThresholdInBytes, |
|
||||
DefaultLargePoolBucketCount, |
|
||||
DefaultNormalPoolBucketCount, |
|
||||
DefaultBufferCapacityInBytes); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// For environments with very limited memory capabilities, only small buffers like image rows are pooled.
|
|
||||
/// </summary>
|
|
||||
/// <returns>The memory manager.</returns>
|
|
||||
public static ArrayPoolMemoryAllocator CreateWithMinimalPooling() |
|
||||
{ |
|
||||
return new ArrayPoolMemoryAllocator(64 * 1024, 32 * 1024, 8, 24); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// For environments with limited memory capabilities, only small array requests are pooled, which can result in reduced throughput.
|
|
||||
/// </summary>
|
|
||||
/// <returns>The memory manager.</returns>
|
|
||||
public static ArrayPoolMemoryAllocator CreateWithModeratePooling() |
|
||||
{ |
|
||||
return new ArrayPoolMemoryAllocator(1024 * 1024, 32 * 1024, 16, 24); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// For environments where memory capabilities are not an issue, the maximum amount of array requests are pooled which results in optimal throughput.
|
|
||||
/// </summary>
|
|
||||
/// <returns>The memory manager.</returns>
|
|
||||
public static ArrayPoolMemoryAllocator CreateWithAggressivePooling() |
|
||||
{ |
|
||||
return new ArrayPoolMemoryAllocator(128 * 1024 * 1024, 32 * 1024 * 1024, 16, 32); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,185 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Buffers; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Memory |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Implements <see cref="MemoryAllocator"/> by allocating memory from <see cref="ArrayPool{T}"/>.
|
|
||||
/// </summary>
|
|
||||
public sealed partial class ArrayPoolMemoryAllocator : MemoryAllocator |
|
||||
{ |
|
||||
private readonly int maxArraysPerBucketNormalPool; |
|
||||
|
|
||||
private readonly int maxArraysPerBucketLargePool; |
|
||||
|
|
||||
/// <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; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
|
|
||||
/// </summary>
|
|
||||
public ArrayPoolMemoryAllocator() |
|
||||
: this(DefaultMaxPooledBufferSizeInBytes, DefaultBufferSelectorThresholdInBytes) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
|
|
||||
/// </summary>
|
|
||||
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
|
|
||||
public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes) |
|
||||
: this(maxPoolSizeInBytes, GetLargeBufferThresholdInBytes(maxPoolSizeInBytes)) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> 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 ArrayPoolMemoryAllocator(int maxPoolSizeInBytes, int poolSelectorThresholdInBytes) |
|
||||
: this(maxPoolSizeInBytes, poolSelectorThresholdInBytes, DefaultLargePoolBucketCount, DefaultNormalPoolBucketCount) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> 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 ArrayPoolMemoryAllocator( |
|
||||
int maxPoolSizeInBytes, |
|
||||
int poolSelectorThresholdInBytes, |
|
||||
int maxArraysPerBucketLargePool, |
|
||||
int maxArraysPerBucketNormalPool) |
|
||||
: this( |
|
||||
maxPoolSizeInBytes, |
|
||||
poolSelectorThresholdInBytes, |
|
||||
maxArraysPerBucketLargePool, |
|
||||
maxArraysPerBucketNormalPool, |
|
||||
DefaultBufferCapacityInBytes) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> 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>
|
|
||||
/// <param name="bufferCapacityInBytes">The length of the largest contiguous buffer that can be handled by this allocator instance.</param>
|
|
||||
public ArrayPoolMemoryAllocator( |
|
||||
int maxPoolSizeInBytes, |
|
||||
int poolSelectorThresholdInBytes, |
|
||||
int maxArraysPerBucketLargePool, |
|
||||
int maxArraysPerBucketNormalPool, |
|
||||
int bufferCapacityInBytes) |
|
||||
{ |
|
||||
Guard.MustBeGreaterThan(maxPoolSizeInBytes, 0, nameof(maxPoolSizeInBytes)); |
|
||||
Guard.MustBeLessThanOrEqualTo(poolSelectorThresholdInBytes, maxPoolSizeInBytes, nameof(poolSelectorThresholdInBytes)); |
|
||||
|
|
||||
this.MaxPoolSizeInBytes = maxPoolSizeInBytes; |
|
||||
this.PoolSelectorThresholdInBytes = poolSelectorThresholdInBytes; |
|
||||
this.BufferCapacityInBytes = bufferCapacityInBytes; |
|
||||
this.maxArraysPerBucketLargePool = maxArraysPerBucketLargePool; |
|
||||
this.maxArraysPerBucketNormalPool = maxArraysPerBucketNormalPool; |
|
||||
|
|
||||
this.InitArrayPools(); |
|
||||
} |
|
||||
|
|
||||
/// <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; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the length of the largest contiguous buffer that can be handled by this allocator instance.
|
|
||||
/// </summary>
|
|
||||
public int BufferCapacityInBytes { get; internal set; } // Setter is internal for easy configuration in tests
|
|
||||
|
|
||||
/// <inheritdoc />
|
|
||||
public override void ReleaseRetainedResources() |
|
||||
{ |
|
||||
this.InitArrayPools(); |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc />
|
|
||||
protected internal override int GetBufferCapacityInBytes() => this.BufferCapacityInBytes; |
|
||||
|
|
||||
/// <inheritdoc />
|
|
||||
public override IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None) |
|
||||
{ |
|
||||
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); |
|
||||
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 (options == AllocationOptions.Clean) |
|
||||
{ |
|
||||
buffer.GetSpan().Clear(); |
|
||||
} |
|
||||
|
|
||||
return buffer; |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc />
|
|
||||
public override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None) |
|
||||
{ |
|
||||
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); |
|
||||
|
|
||||
ArrayPool<byte> pool = this.GetArrayPool(length); |
|
||||
byte[] byteArray = pool.Rent(length); |
|
||||
|
|
||||
var buffer = new ManagedByteBuffer(byteArray, length, pool); |
|
||||
if (options == AllocationOptions.Clean) |
|
||||
{ |
|
||||
buffer.GetSpan().Clear(); |
|
||||
} |
|
||||
|
|
||||
return buffer; |
|
||||
} |
|
||||
|
|
||||
private static int GetLargeBufferThresholdInBytes(int maxPoolSizeInBytes) |
|
||||
{ |
|
||||
return maxPoolSizeInBytes / 4; |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(InliningOptions.ColdPath)] |
|
||||
private static void ThrowInvalidAllocationException<T>(int length, int max) => |
|
||||
throw new InvalidMemoryOperationException( |
|
||||
$"Requested allocation: '{length}' elements of '{typeof(T).Name}' is over the capacity in bytes '{max}' of the MemoryAllocator."); |
|
||||
|
|
||||
private ArrayPool<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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,18 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System.Buffers; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Memory |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Represents a byte buffer backed by a managed array. Useful for interop with classic .NET API-s.
|
|
||||
/// </summary>
|
|
||||
public interface IManagedByteBuffer : IMemoryOwner<byte> |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Gets the managed array backing this buffer instance.
|
|
||||
/// </summary>
|
|
||||
byte[] Array { get; } |
|
||||
} |
|
||||
} |
|
||||
@ -1,20 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Memory.Internals |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Provides an <see cref="IManagedByteBuffer"/> based on <see cref="BasicArrayBuffer{T}"/>.
|
|
||||
/// </summary>
|
|
||||
internal sealed class BasicByteBuffer : BasicArrayBuffer<byte>, IManagedByteBuffer |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="BasicByteBuffer"/> class.
|
|
||||
/// </summary>
|
|
||||
/// <param name="array">The byte array.</param>
|
|
||||
internal BasicByteBuffer(byte[] array) |
|
||||
: base(array) |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,115 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
// Port of BCL internal utility:
|
||||
|
// https://github.com/dotnet/runtime/blob/57bfe474518ab5b7cfe6bf7424a79ce3af9d6657/src/libraries/System.Private.CoreLib/src/System/Gen2GcCallback.cs
|
||||
|
#if NETCOREAPP3_1_OR_GREATER
|
||||
|
using System; |
||||
|
using System.Runtime.ConstrainedExecution; |
||||
|
using System.Runtime.InteropServices; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Memory.Internals |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Schedules a callback roughly every gen 2 GC (you may see a Gen 0 an Gen 1 but only once)
|
||||
|
/// (We can fix this by capturing the Gen 2 count at startup and testing, but I mostly don't care)
|
||||
|
/// </summary>
|
||||
|
internal sealed class Gen2GcCallback : CriticalFinalizerObject |
||||
|
{ |
||||
|
private readonly Func<bool> callback0; |
||||
|
private readonly Func<object, bool> callback1; |
||||
|
private GCHandle weakTargetObj; |
||||
|
|
||||
|
private Gen2GcCallback(Func<bool> callback) |
||||
|
{ |
||||
|
this.callback0 = callback; |
||||
|
} |
||||
|
|
||||
|
private Gen2GcCallback(Func<object, bool> callback, object targetObj) |
||||
|
{ |
||||
|
this.callback1 = callback; |
||||
|
this.weakTargetObj = GCHandle.Alloc(targetObj, GCHandleType.Weak); |
||||
|
} |
||||
|
|
||||
|
~Gen2GcCallback() |
||||
|
{ |
||||
|
if (this.weakTargetObj.IsAllocated) |
||||
|
{ |
||||
|
// Check to see if the target object is still alive.
|
||||
|
object targetObj = this.weakTargetObj.Target; |
||||
|
if (targetObj == null) |
||||
|
{ |
||||
|
// The target object is dead, so this callback object is no longer needed.
|
||||
|
this.weakTargetObj.Free(); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Execute the callback method.
|
||||
|
try |
||||
|
{ |
||||
|
if (!this.callback1(targetObj)) |
||||
|
{ |
||||
|
// If the callback returns false, this callback object is no longer needed.
|
||||
|
this.weakTargetObj.Free(); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
// Ensure that we still get a chance to resurrect this object, even if the callback throws an exception.
|
||||
|
#if DEBUG
|
||||
|
// Except in DEBUG, as we really shouldn't be hitting any exceptions here.
|
||||
|
throw; |
||||
|
#endif
|
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Execute the callback method.
|
||||
|
try |
||||
|
{ |
||||
|
if (!this.callback0()) |
||||
|
{ |
||||
|
// If the callback returns false, this callback object is no longer needed.
|
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
// Ensure that we still get a chance to resurrect this object, even if the callback throws an exception.
|
||||
|
#if DEBUG
|
||||
|
// Except in DEBUG, as we really shouldn't be hitting any exceptions here.
|
||||
|
throw; |
||||
|
#endif
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Resurrect ourselves by re-registering for finalization.
|
||||
|
GC.ReRegisterForFinalize(this); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Schedule 'callback' to be called in the next GC. If the callback returns true it is
|
||||
|
/// rescheduled for the next Gen 2 GC. Otherwise the callbacks stop.
|
||||
|
/// </summary>
|
||||
|
public static void Register(Func<bool> callback) |
||||
|
{ |
||||
|
// Create a unreachable object that remembers the callback function and target object.
|
||||
|
_ = new Gen2GcCallback(callback); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Schedule 'callback' to be called in the next GC. If the callback returns true it is
|
||||
|
/// rescheduled for the next Gen 2 GC. Otherwise the callbacks stop.
|
||||
|
///
|
||||
|
/// NOTE: This callback will be kept alive until either the callback function returns false,
|
||||
|
/// or the target object dies.
|
||||
|
/// </summary>
|
||||
|
public static void Register(Func<object, bool> callback, object targetObj) |
||||
|
{ |
||||
|
// Create a unreachable object that remembers the callback function and target object.
|
||||
|
_ = new Gen2GcCallback(callback, targetObj); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
#endif
|
||||
@ -0,0 +1,21 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Memory.Internals |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines an common interface for ref-counted objects.
|
||||
|
/// </summary>
|
||||
|
internal interface IRefCounted |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Increments the reference counter.
|
||||
|
/// </summary>
|
||||
|
void AddRef(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Decrements the reference counter.
|
||||
|
/// </summary>
|
||||
|
void ReleaseRef(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Threading; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Memory.Internals |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements reference counting lifetime guard mechanism similar to the one provided by <see cref="SafeHandle"/>,
|
||||
|
/// but without the restriction of the guarded object being a handle.
|
||||
|
/// </summary>
|
||||
|
internal abstract class RefCountedLifetimeGuard : IDisposable |
||||
|
{ |
||||
|
private int refCount = 1; |
||||
|
private int disposed; |
||||
|
private int released; |
||||
|
|
||||
|
~RefCountedLifetimeGuard() |
||||
|
{ |
||||
|
Interlocked.Exchange(ref this.disposed, 1); |
||||
|
this.ReleaseRef(); |
||||
|
} |
||||
|
|
||||
|
public bool IsDisposed => this.disposed == 1; |
||||
|
|
||||
|
public void AddRef() => Interlocked.Increment(ref this.refCount); |
||||
|
|
||||
|
public void ReleaseRef() |
||||
|
{ |
||||
|
Interlocked.Decrement(ref this.refCount); |
||||
|
if (this.refCount == 0) |
||||
|
{ |
||||
|
int wasReleased = Interlocked.Exchange(ref this.released, 1); |
||||
|
|
||||
|
if (wasReleased == 0) |
||||
|
{ |
||||
|
this.Release(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
int wasDisposed = Interlocked.Exchange(ref this.disposed, 1); |
||||
|
if (wasDisposed == 0) |
||||
|
{ |
||||
|
this.ReleaseRef(); |
||||
|
GC.SuppressFinalize(this); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected abstract void Release(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Buffers; |
||||
|
using System.Diagnostics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Memory.Internals |
||||
|
{ |
||||
|
internal class SharedArrayPoolBuffer<T> : ManagedBufferBase<T>, IRefCounted |
||||
|
where T : struct |
||||
|
{ |
||||
|
private readonly int lengthInBytes; |
||||
|
private byte[] array; |
||||
|
private LifetimeGuard lifetimeGuard; |
||||
|
|
||||
|
public SharedArrayPoolBuffer(int lengthInElements) |
||||
|
{ |
||||
|
this.lengthInBytes = lengthInElements * Unsafe.SizeOf<T>(); |
||||
|
this.array = ArrayPool<byte>.Shared.Rent(this.lengthInBytes); |
||||
|
this.lifetimeGuard = new LifetimeGuard(this.array); |
||||
|
} |
||||
|
|
||||
|
protected override void Dispose(bool disposing) |
||||
|
{ |
||||
|
if (this.array == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
this.lifetimeGuard.Dispose(); |
||||
|
this.array = null; |
||||
|
} |
||||
|
|
||||
|
public override Span<T> GetSpan() |
||||
|
{ |
||||
|
this.CheckDisposed(); |
||||
|
return MemoryMarshal.Cast<byte, T>(this.array.AsSpan(0, this.lengthInBytes)); |
||||
|
} |
||||
|
|
||||
|
protected override object GetPinnableObject() => this.array; |
||||
|
|
||||
|
public void AddRef() |
||||
|
{ |
||||
|
this.CheckDisposed(); |
||||
|
this.lifetimeGuard.AddRef(); |
||||
|
} |
||||
|
|
||||
|
public void ReleaseRef() => this.lifetimeGuard.ReleaseRef(); |
||||
|
|
||||
|
[Conditional("DEBUG")] |
||||
|
private void CheckDisposed() |
||||
|
{ |
||||
|
if (this.array == null) |
||||
|
{ |
||||
|
throw new ObjectDisposedException("SharedArrayPoolBuffer"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private sealed class LifetimeGuard : RefCountedLifetimeGuard |
||||
|
{ |
||||
|
private byte[] array; |
||||
|
|
||||
|
public LifetimeGuard(byte[] array) => this.array = array; |
||||
|
|
||||
|
protected override void Release() |
||||
|
{ |
||||
|
// If this is called by a finalizer, we will end storing the first array of this bucket
|
||||
|
// on the thread local storage of the finalizer thread.
|
||||
|
// This is not ideal, but subsequent leaks will end up returning arrays to per-cpu buckets,
|
||||
|
// meaning likely a different bucket than it was rented from,
|
||||
|
// but this is PROBABLY better than not returning the arrays at all.
|
||||
|
ArrayPool<byte>.Shared.Return(this.array); |
||||
|
this.array = null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Memory.Internals |
||||
|
{ |
||||
|
internal partial class UniformUnmanagedMemoryPool |
||||
|
{ |
||||
|
public UnmanagedBuffer<T> CreateGuardedBuffer<T>( |
||||
|
UnmanagedMemoryHandle handle, |
||||
|
int lengthInElements, |
||||
|
bool clear) |
||||
|
where T : struct |
||||
|
{ |
||||
|
var buffer = new UnmanagedBuffer<T>(lengthInElements, new ReturnToPoolBufferLifetimeGuard(this, handle)); |
||||
|
if (clear) |
||||
|
{ |
||||
|
buffer.Clear(); |
||||
|
} |
||||
|
|
||||
|
return buffer; |
||||
|
} |
||||
|
|
||||
|
public RefCountedLifetimeGuard CreateGroupLifetimeGuard(UnmanagedMemoryHandle[] handles) => new GroupLifetimeGuard(this, handles); |
||||
|
|
||||
|
private sealed class GroupLifetimeGuard : RefCountedLifetimeGuard |
||||
|
{ |
||||
|
private readonly UniformUnmanagedMemoryPool pool; |
||||
|
private readonly UnmanagedMemoryHandle[] handles; |
||||
|
|
||||
|
public GroupLifetimeGuard(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle[] handles) |
||||
|
{ |
||||
|
this.pool = pool; |
||||
|
this.handles = handles; |
||||
|
} |
||||
|
|
||||
|
protected override void Release() |
||||
|
{ |
||||
|
if (!this.pool.Return(this.handles)) |
||||
|
{ |
||||
|
foreach (UnmanagedMemoryHandle handle in this.handles) |
||||
|
{ |
||||
|
handle.Free(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private sealed class ReturnToPoolBufferLifetimeGuard : UnmanagedBufferLifetimeGuard |
||||
|
{ |
||||
|
private readonly UniformUnmanagedMemoryPool pool; |
||||
|
|
||||
|
public ReturnToPoolBufferLifetimeGuard(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle handle) |
||||
|
: base(handle) => |
||||
|
this.pool = pool; |
||||
|
|
||||
|
protected override void Release() |
||||
|
{ |
||||
|
if (!this.pool.Return(this.Handle)) |
||||
|
{ |
||||
|
this.Handle.Free(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,356 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics; |
||||
|
using System.Threading; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Memory.Internals |
||||
|
{ |
||||
|
internal partial class UniformUnmanagedMemoryPool |
||||
|
#if !NETSTANDARD1_3
|
||||
|
// In case UniformUnmanagedMemoryPool is finalized, we prefer to run its finalizer after the guard finalizers,
|
||||
|
// but we should not rely on this.
|
||||
|
: System.Runtime.ConstrainedExecution.CriticalFinalizerObject |
||||
|
#endif
|
||||
|
{ |
||||
|
private static int minTrimPeriodMilliseconds = int.MaxValue; |
||||
|
private static readonly List<WeakReference<UniformUnmanagedMemoryPool>> AllPools = new(); |
||||
|
private static Timer trimTimer; |
||||
|
|
||||
|
private static readonly Stopwatch Stopwatch = Stopwatch.StartNew(); |
||||
|
|
||||
|
private readonly TrimSettings trimSettings; |
||||
|
private readonly UnmanagedMemoryHandle[] buffers; |
||||
|
private int index; |
||||
|
private long lastTrimTimestamp; |
||||
|
private int finalized; |
||||
|
|
||||
|
public UniformUnmanagedMemoryPool(int bufferLength, int capacity) |
||||
|
: this(bufferLength, capacity, TrimSettings.Default) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public UniformUnmanagedMemoryPool(int bufferLength, int capacity, TrimSettings trimSettings) |
||||
|
{ |
||||
|
this.trimSettings = trimSettings; |
||||
|
this.Capacity = capacity; |
||||
|
this.BufferLength = bufferLength; |
||||
|
this.buffers = new UnmanagedMemoryHandle[capacity]; |
||||
|
|
||||
|
if (trimSettings.Enabled) |
||||
|
{ |
||||
|
UpdateTimer(trimSettings, this); |
||||
|
#if NETCOREAPP3_1_OR_GREATER
|
||||
|
Gen2GcCallback.Register(s => ((UniformUnmanagedMemoryPool)s).Trim(), this); |
||||
|
#endif
|
||||
|
this.lastTrimTimestamp = Stopwatch.ElapsedMilliseconds; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// We don't want UniformUnmanagedMemoryPool and MemoryAllocator to be IDisposable,
|
||||
|
// since the types don't really match Disposable semantics.
|
||||
|
// If a user wants to drop a MemoryAllocator after they finished using it, they should call allocator.ReleaseRetainedResources(),
|
||||
|
// which normally should free the already returned (!) buffers.
|
||||
|
// However in case if this doesn't happen, we need the retained memory to be freed by the finalizer.
|
||||
|
~UniformUnmanagedMemoryPool() |
||||
|
{ |
||||
|
Interlocked.Exchange(ref this.finalized, 1); |
||||
|
this.TrimAll(this.buffers); |
||||
|
} |
||||
|
|
||||
|
public int BufferLength { get; } |
||||
|
|
||||
|
public int Capacity { get; } |
||||
|
|
||||
|
private bool Finalized => this.finalized == 1; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Rent a single buffer. If the pool is full, return <see cref="UnmanagedMemoryHandle.NullHandle"/>.
|
||||
|
/// </summary>
|
||||
|
public UnmanagedMemoryHandle Rent() |
||||
|
{ |
||||
|
UnmanagedMemoryHandle[] buffersLocal = this.buffers; |
||||
|
|
||||
|
// Avoid taking the lock if the pool is is over it's limit:
|
||||
|
if (this.index == buffersLocal.Length || this.Finalized) |
||||
|
{ |
||||
|
return UnmanagedMemoryHandle.NullHandle; |
||||
|
} |
||||
|
|
||||
|
UnmanagedMemoryHandle buffer; |
||||
|
lock (buffersLocal) |
||||
|
{ |
||||
|
// Check again after taking the lock:
|
||||
|
if (this.index == buffersLocal.Length || this.Finalized) |
||||
|
{ |
||||
|
return UnmanagedMemoryHandle.NullHandle; |
||||
|
} |
||||
|
|
||||
|
buffer = buffersLocal[this.index]; |
||||
|
buffersLocal[this.index++] = default; |
||||
|
} |
||||
|
|
||||
|
if (buffer.IsInvalid) |
||||
|
{ |
||||
|
buffer = UnmanagedMemoryHandle.Allocate(this.BufferLength); |
||||
|
} |
||||
|
|
||||
|
return buffer; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Rent <paramref name="bufferCount"/> buffers or return 'null' if the pool is full.
|
||||
|
/// </summary>
|
||||
|
public UnmanagedMemoryHandle[] Rent(int bufferCount) |
||||
|
{ |
||||
|
UnmanagedMemoryHandle[] buffersLocal = this.buffers; |
||||
|
|
||||
|
// Avoid taking the lock if the pool is is over it's limit:
|
||||
|
if (this.index + bufferCount >= buffersLocal.Length + 1 || this.Finalized) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
UnmanagedMemoryHandle[] result; |
||||
|
lock (buffersLocal) |
||||
|
{ |
||||
|
// Check again after taking the lock:
|
||||
|
if (this.index + bufferCount >= buffersLocal.Length + 1 || this.Finalized) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
result = new UnmanagedMemoryHandle[bufferCount]; |
||||
|
for (int i = 0; i < bufferCount; i++) |
||||
|
{ |
||||
|
result[i] = buffersLocal[this.index]; |
||||
|
buffersLocal[this.index++] = UnmanagedMemoryHandle.NullHandle; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (int i = 0; i < result.Length; i++) |
||||
|
{ |
||||
|
if (result[i].IsInvalid) |
||||
|
{ |
||||
|
result[i] = UnmanagedMemoryHandle.Allocate(this.BufferLength); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
// The Return methods return false if and only if:
|
||||
|
// (1) More buffers are returned than rented OR
|
||||
|
// (2) The pool has been finalized.
|
||||
|
// This is defensive programming, since neither of the cases should happen normally
|
||||
|
// (case 1 would be a programming mistake in the library, case 2 should be prevented by the CriticalFinalizerObject contract),
|
||||
|
// so we throw in Debug instead of returning false.
|
||||
|
// In Release, the caller should Free() the handles if false is returned to avoid memory leaks.
|
||||
|
public bool Return(UnmanagedMemoryHandle bufferHandle) |
||||
|
{ |
||||
|
Guard.IsTrue(bufferHandle.IsValid, nameof(bufferHandle), "Returning NullHandle to the pool is not allowed."); |
||||
|
lock (this.buffers) |
||||
|
{ |
||||
|
if (this.Finalized || this.index == 0) |
||||
|
{ |
||||
|
this.DebugThrowInvalidReturn(); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
this.buffers[--this.index] = bufferHandle; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public bool Return(Span<UnmanagedMemoryHandle> bufferHandles) |
||||
|
{ |
||||
|
lock (this.buffers) |
||||
|
{ |
||||
|
if (this.Finalized || this.index - bufferHandles.Length + 1 <= 0) |
||||
|
{ |
||||
|
this.DebugThrowInvalidReturn(); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
for (int i = bufferHandles.Length - 1; i >= 0; i--) |
||||
|
{ |
||||
|
ref UnmanagedMemoryHandle h = ref bufferHandles[i]; |
||||
|
Guard.IsTrue(h.IsValid, nameof(bufferHandles), "Returning NullHandle to the pool is not allowed."); |
||||
|
this.buffers[--this.index] = h; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public void Release() |
||||
|
{ |
||||
|
lock (this.buffers) |
||||
|
{ |
||||
|
for (int i = this.index; i < this.buffers.Length; i++) |
||||
|
{ |
||||
|
ref UnmanagedMemoryHandle buffer = ref this.buffers[i]; |
||||
|
if (buffer.IsInvalid) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
buffer.Free(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Conditional("DEBUG")] |
||||
|
private void DebugThrowInvalidReturn() |
||||
|
{ |
||||
|
if (this.Finalized) |
||||
|
{ |
||||
|
throw new ObjectDisposedException( |
||||
|
nameof(UniformUnmanagedMemoryPool), |
||||
|
"Invalid handle return to the pool! The pool has been finalized."); |
||||
|
} |
||||
|
|
||||
|
throw new InvalidOperationException( |
||||
|
"Invalid handle return to the pool! Returning more buffers than rented."); |
||||
|
} |
||||
|
|
||||
|
private static void UpdateTimer(TrimSettings settings, UniformUnmanagedMemoryPool pool) |
||||
|
{ |
||||
|
lock (AllPools) |
||||
|
{ |
||||
|
AllPools.Add(new WeakReference<UniformUnmanagedMemoryPool>(pool)); |
||||
|
|
||||
|
// Invoke the timer callback more frequently, than trimSettings.TrimPeriodMilliseconds.
|
||||
|
// We are checking in the callback if enough time passed since the last trimming. If not, we do nothing.
|
||||
|
int period = settings.TrimPeriodMilliseconds / 4; |
||||
|
if (trimTimer == null) |
||||
|
{ |
||||
|
trimTimer = new Timer(_ => TimerCallback(), null, period, period); |
||||
|
} |
||||
|
else if (settings.TrimPeriodMilliseconds < minTrimPeriodMilliseconds) |
||||
|
{ |
||||
|
trimTimer.Change(period, period); |
||||
|
} |
||||
|
|
||||
|
minTrimPeriodMilliseconds = Math.Min(minTrimPeriodMilliseconds, settings.TrimPeriodMilliseconds); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void TimerCallback() |
||||
|
{ |
||||
|
lock (AllPools) |
||||
|
{ |
||||
|
// Remove lost references from the list:
|
||||
|
for (int i = AllPools.Count - 1; i >= 0; i--) |
||||
|
{ |
||||
|
if (!AllPools[i].TryGetTarget(out _)) |
||||
|
{ |
||||
|
AllPools.RemoveAt(i); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
foreach (WeakReference<UniformUnmanagedMemoryPool> weakPoolRef in AllPools) |
||||
|
{ |
||||
|
if (weakPoolRef.TryGetTarget(out UniformUnmanagedMemoryPool pool)) |
||||
|
{ |
||||
|
pool.Trim(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private bool Trim() |
||||
|
{ |
||||
|
if (this.Finalized) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
UnmanagedMemoryHandle[] buffersLocal = this.buffers; |
||||
|
|
||||
|
bool isHighPressure = this.IsHighMemoryPressure(); |
||||
|
|
||||
|
if (isHighPressure) |
||||
|
{ |
||||
|
this.TrimAll(buffersLocal); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
long millisecondsSinceLastTrim = Stopwatch.ElapsedMilliseconds - this.lastTrimTimestamp; |
||||
|
if (millisecondsSinceLastTrim > this.trimSettings.TrimPeriodMilliseconds) |
||||
|
{ |
||||
|
return this.TrimLowPressure(buffersLocal); |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
private void TrimAll(UnmanagedMemoryHandle[] buffersLocal) |
||||
|
{ |
||||
|
lock (buffersLocal) |
||||
|
{ |
||||
|
// Trim all:
|
||||
|
for (int i = this.index; i < buffersLocal.Length && buffersLocal[i].IsValid; i++) |
||||
|
{ |
||||
|
buffersLocal[i].Free(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private bool TrimLowPressure(UnmanagedMemoryHandle[] buffersLocal) |
||||
|
{ |
||||
|
lock (buffersLocal) |
||||
|
{ |
||||
|
// Count the buffers in the pool:
|
||||
|
int retainedCount = 0; |
||||
|
for (int i = this.index; i < buffersLocal.Length && buffersLocal[i].IsValid; i++) |
||||
|
{ |
||||
|
retainedCount++; |
||||
|
} |
||||
|
|
||||
|
// Trim 'trimRate' of 'retainedCount':
|
||||
|
int trimCount = (int)Math.Ceiling(retainedCount * this.trimSettings.Rate); |
||||
|
int trimStart = this.index + retainedCount - 1; |
||||
|
int trimStop = this.index + retainedCount - trimCount; |
||||
|
for (int i = trimStart; i >= trimStop; i--) |
||||
|
{ |
||||
|
buffersLocal[i].Free(); |
||||
|
} |
||||
|
|
||||
|
this.lastTrimTimestamp = Stopwatch.ElapsedMilliseconds; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
private bool IsHighMemoryPressure() |
||||
|
{ |
||||
|
#if NETCOREAPP3_1_OR_GREATER
|
||||
|
GCMemoryInfo memoryInfo = GC.GetGCMemoryInfo(); |
||||
|
return memoryInfo.MemoryLoadBytes >= memoryInfo.HighMemoryLoadThresholdBytes * this.trimSettings.HighPressureThresholdRate; |
||||
|
#else
|
||||
|
// We don't have high pressure detection triggering full trimming on other platforms,
|
||||
|
// to counterpart this, the maximum pool size is small.
|
||||
|
return false; |
||||
|
#endif
|
||||
|
} |
||||
|
|
||||
|
public class TrimSettings |
||||
|
{ |
||||
|
// Trim half of the retained pool buffers every minute.
|
||||
|
public int TrimPeriodMilliseconds { get; set; } = 60_000; |
||||
|
|
||||
|
public float Rate { get; set; } = 0.5f; |
||||
|
|
||||
|
// Be more strict about high pressure on 32 bit.
|
||||
|
public unsafe float HighPressureThresholdRate { get; set; } = sizeof(IntPtr) == 8 ? 0.9f : 0.6f; |
||||
|
|
||||
|
public bool Enabled => this.Rate > 0; |
||||
|
|
||||
|
public static TrimSettings Default => new TrimSettings(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Memory.Internals |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines a strategy for managing unmanaged memory ownership.
|
||||
|
/// </summary>
|
||||
|
internal abstract class UnmanagedBufferLifetimeGuard : RefCountedLifetimeGuard |
||||
|
{ |
||||
|
private UnmanagedMemoryHandle handle; |
||||
|
|
||||
|
protected UnmanagedBufferLifetimeGuard(UnmanagedMemoryHandle handle) => this.handle = handle; |
||||
|
|
||||
|
public ref UnmanagedMemoryHandle Handle => ref this.handle; |
||||
|
|
||||
|
public sealed class FreeHandle : UnmanagedBufferLifetimeGuard |
||||
|
{ |
||||
|
public FreeHandle(UnmanagedMemoryHandle handle) |
||||
|
: base(handle) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
protected override void Release() => this.Handle.Free(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Buffers; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Threading; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Memory.Internals |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Allocates and provides an <see cref="IMemoryOwner{T}"/> implementation giving
|
||||
|
/// access to unmanaged buffers allocated by <see cref="Marshal.AllocHGlobal(int)"/>.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||
|
internal sealed unsafe class UnmanagedBuffer<T> : MemoryManager<T>, IRefCounted |
||||
|
where T : struct |
||||
|
{ |
||||
|
private readonly int lengthInElements; |
||||
|
|
||||
|
private readonly UnmanagedBufferLifetimeGuard lifetimeGuard; |
||||
|
|
||||
|
private int disposed; |
||||
|
|
||||
|
public UnmanagedBuffer(int lengthInElements, UnmanagedBufferLifetimeGuard lifetimeGuard) |
||||
|
{ |
||||
|
DebugGuard.NotNull(lifetimeGuard, nameof(lifetimeGuard)); |
||||
|
|
||||
|
this.lengthInElements = lengthInElements; |
||||
|
this.lifetimeGuard = lifetimeGuard; |
||||
|
} |
||||
|
|
||||
|
private void* Pointer => this.lifetimeGuard.Handle.Pointer; |
||||
|
|
||||
|
public override Span<T> GetSpan() |
||||
|
{ |
||||
|
DebugGuard.NotDisposed(this.disposed == 1, this.GetType().Name); |
||||
|
DebugGuard.NotDisposed(this.lifetimeGuard.IsDisposed, this.lifetimeGuard.GetType().Name); |
||||
|
return new(this.Pointer, this.lengthInElements); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override MemoryHandle Pin(int elementIndex = 0) |
||||
|
{ |
||||
|
DebugGuard.NotDisposed(this.disposed == 1, this.GetType().Name); |
||||
|
DebugGuard.NotDisposed(this.lifetimeGuard.IsDisposed, this.lifetimeGuard.GetType().Name); |
||||
|
|
||||
|
// Will be released in Unpin
|
||||
|
this.lifetimeGuard.AddRef(); |
||||
|
|
||||
|
void* pbData = Unsafe.Add<T>(this.Pointer, elementIndex); |
||||
|
return new MemoryHandle(pbData, pinnable: this); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected override void Dispose(bool disposing) |
||||
|
{ |
||||
|
DebugGuard.IsTrue(disposing, nameof(disposing), "Unmanaged buffers should not have finalizer!"); |
||||
|
|
||||
|
if (Interlocked.Exchange(ref this.disposed, 1) == 1) |
||||
|
{ |
||||
|
// Already disposed
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
this.lifetimeGuard.Dispose(); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override void Unpin() => this.lifetimeGuard.ReleaseRef(); |
||||
|
|
||||
|
public void AddRef() => this.lifetimeGuard.AddRef(); |
||||
|
|
||||
|
public void ReleaseRef() => this.lifetimeGuard.ReleaseRef(); |
||||
|
|
||||
|
public static UnmanagedBuffer<T> Allocate(int lengthInElements) => |
||||
|
new(lengthInElements, new UnmanagedBufferLifetimeGuard.FreeHandle(UnmanagedMemoryHandle.Allocate(lengthInElements * Unsafe.SizeOf<T>()))); |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue