diff --git a/.gitattributes b/.gitattributes
index 70ced69033..355b64dce1 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -87,7 +87,6 @@
*.eot binary
*.exe binary
*.otf binary
-*.pbm binary
*.pdf binary
*.ppt binary
*.pptx binary
@@ -95,7 +94,6 @@
*.snk binary
*.ttc binary
*.ttf binary
-*.wbmp binary
*.woff binary
*.woff2 binary
*.xls binary
@@ -126,3 +124,9 @@
*.dds filter=lfs diff=lfs merge=lfs -text
*.ktx filter=lfs diff=lfs merge=lfs -text
*.ktx2 filter=lfs diff=lfs merge=lfs -text
+*.pam filter=lfs diff=lfs merge=lfs -text
+*.pbm filter=lfs diff=lfs merge=lfs -text
+*.pgm filter=lfs diff=lfs merge=lfs -text
+*.ppm filter=lfs diff=lfs merge=lfs -text
+*.pnm filter=lfs diff=lfs merge=lfs -text
+*.wbmp filter=lfs diff=lfs merge=lfs -text
diff --git a/Directory.Build.props b/Directory.Build.props
index 3899ce939f..26b3cc5afc 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -13,6 +13,9 @@
$(MSBuildThisFileDirectory)
+
+
+ $(DefineConstants);DEBUG
@@ -30,5 +33,4 @@
true
-
diff --git a/shared-infrastructure b/shared-infrastructure
index a042aba176..59ce17f5a4 160000
--- a/shared-infrastructure
+++ b/shared-infrastructure
@@ -1 +1 @@
-Subproject commit a042aba176cdb840d800c6ed4cfe41a54fb7b1e3
+Subproject commit 59ce17f5a4e1f956811133f41add7638e74c2836
diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs
index 54a773be05..829c6155db 100644
--- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs
+++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs
@@ -143,7 +143,7 @@ namespace SixLabors.ImageSharp.Advanced
/// The source.
/// The row.
/// The
- public static Memory GetPixelRowMemory(this ImageFrame source, int rowIndex)
+ public static Memory DangerousGetPixelRowMemory(this ImageFrame source, int rowIndex)
where TPixel : unmanaged, IPixel
{
Guard.NotNull(source, nameof(source));
@@ -161,7 +161,7 @@ namespace SixLabors.ImageSharp.Advanced
/// The source.
/// The row.
/// The
- public static Memory GetPixelRowMemory(this Image source, int rowIndex)
+ public static Memory DangerousGetPixelRowMemory(this Image source, int rowIndex)
where TPixel : unmanaged, IPixel
{
Guard.NotNull(source, nameof(source));
diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs
index 3961cc6c57..dd0cdf8497 100644
--- a/src/ImageSharp/Advanced/AotCompilerTools.cs
+++ b/src/ImageSharp/Advanced/AotCompilerTools.cs
@@ -10,6 +10,7 @@ using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
+using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Tiff;
@@ -200,6 +201,7 @@ namespace SixLabors.ImageSharp.Advanced
default(BmpEncoderCore).Encode(default, default, default);
default(GifEncoderCore).Encode(default, default, default);
default(JpegEncoderCore).Encode(default, default, default);
+ default(PbmEncoderCore).Encode(default, default, default);
default(PngEncoderCore).Encode(default, default, default);
default(TgaEncoderCore).Encode(default, default, default);
default(TiffEncoderCore).Encode(default, default, default);
@@ -217,6 +219,7 @@ namespace SixLabors.ImageSharp.Advanced
default(BmpDecoderCore).Decode(default, default, default);
default(GifDecoderCore).Decode(default, default, default);
default(JpegDecoderCore).Decode(default, default, default);
+ default(PbmDecoderCore).Decode(default, default, default);
default(PngDecoderCore).Decode(default, default, default);
default(TgaDecoderCore).Decode(default, default, default);
default(TiffDecoderCore).Decode(default, default, default);
@@ -234,6 +237,7 @@ namespace SixLabors.ImageSharp.Advanced
AotCompileImageEncoder();
AotCompileImageEncoder();
AotCompileImageEncoder();
+ AotCompileImageEncoder();
AotCompileImageEncoder();
AotCompileImageEncoder();
AotCompileImageEncoder();
@@ -251,6 +255,7 @@ namespace SixLabors.ImageSharp.Advanced
AotCompileImageDecoder();
AotCompileImageDecoder();
AotCompileImageDecoder();
+ AotCompileImageDecoder();
AotCompileImageDecoder();
AotCompileImageDecoder();
AotCompileImageDecoder();
@@ -529,7 +534,7 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileMemoryManagers()
where TPixel : unmanaged, IPixel
{
- AotCompileMemoryManager();
+ AotCompileMemoryManager();
AotCompileMemoryManager();
}
diff --git a/src/ImageSharp/Common/Helpers/DebugGuard.cs b/src/ImageSharp/Common/Helpers/DebugGuard.cs
index f56cb37a81..f438ca9e24 100644
--- a/src/ImageSharp/Common/Helpers/DebugGuard.cs
+++ b/src/ImageSharp/Common/Helpers/DebugGuard.cs
@@ -26,6 +26,20 @@ namespace SixLabors
}
}
+ ///
+ /// Verifies whether a condition (indicating disposed state) is met, throwing an ObjectDisposedException if it's true.
+ ///
+ /// Whether the object is disposed.
+ /// The name of the object.
+ [Conditional("DEBUG")]
+ public static void NotDisposed(bool isDisposed, string objectName)
+ {
+ if (isDisposed)
+ {
+ throw new ObjectDisposedException(objectName);
+ }
+ }
+
///
/// Verifies, that the target span is of same size than the 'other' span.
///
diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs
index 7687a5b95f..929b786921 100644
--- a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs
+++ b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs
@@ -28,6 +28,10 @@ namespace SixLabors.ImageSharp
///
/// The source span of bytes.
/// The destination span of bytes.
+ ///
+ /// Implementation can assume that source.Length is less or equal than dest.Length.
+ /// Loops should iterate using source.Length.
+ ///
void RunFallbackShuffle(ReadOnlySpan source, Span dest);
}
diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs b/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs
index 07744566a3..abf9e9fed0 100644
--- a/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs
+++ b/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs
@@ -77,6 +77,7 @@ namespace SixLabors.ImageSharp
TShuffle shuffle)
where TShuffle : struct, IShuffle3
{
+ // Source length should be smaller than dest length, and divisible by 3.
VerifyShuffle3SpanInput(source, dest);
#if SUPPORTS_RUNTIME_INTRINSICS
@@ -182,9 +183,9 @@ namespace SixLabors.ImageSharp
where T : struct
{
DebugGuard.IsTrue(
- source.Length == dest.Length,
+ source.Length <= dest.Length,
nameof(source),
- "Input spans must be of same length!");
+ "Source should fit into dest!");
DebugGuard.IsTrue(
source.Length % 3 == 0,
diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs
index ea9524827f..ca83b0764e 100644
--- a/src/ImageSharp/Configuration.cs
+++ b/src/ImageSharp/Configuration.cs
@@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
+using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Tiff;
@@ -26,10 +27,11 @@ namespace SixLabors.ImageSharp
///
/// A lazily initialized configuration default instance.
///
- private static readonly Lazy Lazy = new Lazy(CreateDefaultInstance);
+ private static readonly Lazy Lazy = new(CreateDefaultInstance);
private const int DefaultStreamProcessingBufferSize = 8096;
private int streamProcessingBufferSize = DefaultStreamProcessingBufferSize;
private int maxDegreeOfParallelism = Environment.ProcessorCount;
+ private MemoryAllocator memoryAllocator = MemoryAllocator.Default;
///
/// Initializes a new instance of the class.
@@ -95,6 +97,14 @@ namespace SixLabors.ImageSharp
}
}
+ ///
+ /// Gets or sets a value indicating whether to force image buffers to be contiguous whenever possible.
+ ///
+ ///
+ /// Contiguous allocations are not possible, if the image needs a buffer larger than .
+ ///
+ public bool PreferContiguousImageBuffers { get; set; }
+
///
/// Gets a set of properties for the Configuration.
///
@@ -117,9 +127,31 @@ namespace SixLabors.ImageSharp
public ImageFormatManager ImageFormatsManager { get; set; } = new ImageFormatManager();
///
- /// Gets or sets the that is currently in use.
+ /// Gets or sets the that is currently in use.
+ /// Defaults to .
+ ///
+ /// Allocators are expensive, so it is strongly recommended to use only one busy instance per process.
+ /// In case you need to customize it, you can ensure this by changing
///
- public MemoryAllocator MemoryAllocator { get; set; } = ArrayPoolMemoryAllocator.CreateDefault();
+ ///
+ /// It's possible to reduce allocator footprint by assigning a custom instance created with
+ /// , but note that since the default pooling
+ /// allocators are expensive, it is strictly recommended to use a single process-wide allocator.
+ /// You can ensure this by altering the allocator of , or by implementing custom application logic that
+ /// manages allocator lifetime.
+ ///
+ /// If an allocator has to be dropped for some reason,
+ /// shall be invoked after disposing all associated instances.
+ ///
+ public MemoryAllocator MemoryAllocator
+ {
+ get => this.memoryAllocator;
+ set
+ {
+ Guard.NotNull(value, nameof(this.MemoryAllocator));
+ this.memoryAllocator = value;
+ }
+ }
///
/// Gets the maximum header size of all the formats.
@@ -165,7 +197,7 @@ namespace SixLabors.ImageSharp
MaxDegreeOfParallelism = this.MaxDegreeOfParallelism,
StreamProcessingBufferSize = this.StreamProcessingBufferSize,
ImageFormatsManager = this.ImageFormatsManager,
- MemoryAllocator = this.MemoryAllocator,
+ memoryAllocator = this.memoryAllocator,
ImageOperationsProvider = this.ImageOperationsProvider,
ReadOrigin = this.ReadOrigin,
FileSystem = this.FileSystem,
@@ -178,6 +210,7 @@ namespace SixLabors.ImageSharp
///
///
/// .
+ /// .
/// .
/// .
/// .
@@ -188,6 +221,7 @@ namespace SixLabors.ImageSharp
new JpegConfigurationModule(),
new GifConfigurationModule(),
new BmpConfigurationModule(),
+ new PbmConfigurationModule(),
new TgaConfigurationModule(),
new TiffConfigurationModule(),
new WebpConfigurationModule());
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index 8919befcb2..41adc1cfff 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -306,7 +306,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int newY = Invert(y, height, inverted);
int rowStartIdx = y * width;
Span bufferRow = bufferSpan.Slice(rowStartIdx, width);
- Span pixelRow = pixels.GetRowSpan(newY);
+ Span pixelRow = pixels.DangerousGetRowSpan(newY);
bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y];
if (rowHasUndefinedPixels)
@@ -377,7 +377,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
for (int y = 0; y < height; y++)
{
int newY = Invert(y, height, inverted);
- Span pixelRow = pixels.GetRowSpan(newY);
+ Span pixelRow = pixels.DangerousGetRowSpan(newY);
bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y];
if (rowHasUndefinedPixels)
{
@@ -826,7 +826,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int newY = Invert(y, height, inverted);
this.stream.Read(rowSpan);
int offset = 0;
- Span pixelRow = pixels.GetRowSpan(newY);
+ Span pixelRow = pixels.DangerousGetRowSpan(newY);
for (int x = 0; x < arrayWidth; x++)
{
@@ -878,7 +878,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
this.stream.Read(bufferSpan);
int newY = Invert(y, height, inverted);
- Span pixelRow = pixels.GetRowSpan(newY);
+ Span pixelRow = pixels.DangerousGetRowSpan(newY);
int offset = 0;
for (int x = 0; x < width; x++)
@@ -933,7 +933,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
this.stream.Read(rowSpan);
int newY = Invert(y, height, inverted);
- Span pixelSpan = pixels.GetRowSpan(newY);
+ Span pixelSpan = pixels.DangerousGetRowSpan(newY);
PixelOperations.Instance.FromBgr24Bytes(
this.Configuration,
rowSpan,
@@ -961,7 +961,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
this.stream.Read(rowSpan);
int newY = Invert(y, height, inverted);
- Span pixelSpan = pixels.GetRowSpan(newY);
+ Span pixelSpan = pixels.DangerousGetRowSpan(newY);
PixelOperations.Instance.FromBgra32Bytes(
this.Configuration,
rowSpan,
@@ -1031,7 +1031,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.stream.Read(rowSpan);
int newY = Invert(y, height, inverted);
- Span pixelSpan = pixels.GetRowSpan(newY);
+ Span pixelSpan = pixels.DangerousGetRowSpan(newY);
PixelOperations.Instance.FromBgra32Bytes(
this.Configuration,
@@ -1054,7 +1054,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
width);
int newY = Invert(y, height, inverted);
- Span pixelSpan = pixels.GetRowSpan(newY);
+ Span pixelSpan = pixels.DangerousGetRowSpan(newY);
for (int x = 0; x < width; x++)
{
@@ -1109,7 +1109,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
this.stream.Read(bufferSpan);
int newY = Invert(y, height, inverted);
- Span pixelRow = pixels.GetRowSpan(newY);
+ Span pixelRow = pixels.DangerousGetRowSpan(newY);
int offset = 0;
for (int x = 0; x < width; x++)
diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
index c6ca5b09d2..6384074df3 100644
--- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
@@ -274,7 +274,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
for (int y = pixels.Height - 1; y >= 0; y--)
{
- Span pixelSpan = pixels.GetRowSpan(y);
+ Span pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations.Instance.ToBgra32Bytes(
this.configuration,
pixelSpan,
@@ -300,7 +300,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
for (int y = pixels.Height - 1; y >= 0; y--)
{
- Span pixelSpan = pixels.GetRowSpan(y);
+ Span pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations.Instance.ToBgr24Bytes(
this.configuration,
pixelSpan,
@@ -326,7 +326,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
for (int y = pixels.Height - 1; y >= 0; y--)
{
- Span pixelSpan = pixels.GetRowSpan(y);
+ Span pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations.Instance.ToBgra5551Bytes(
this.configuration,
@@ -379,7 +379,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
for (int y = image.Height - 1; y >= 0; y--)
{
- ReadOnlySpan pixelSpan = quantized.GetPixelRowSpan(y);
+ ReadOnlySpan pixelSpan = quantized.DangerousGetRowSpan(y);
stream.Write(pixelSpan);
for (int i = 0; i < this.padding; i++)
@@ -413,10 +413,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
stream.Write(colorPalette);
-
+ Buffer2D imageBuffer = image.PixelBuffer;
for (int y = image.Height - 1; y >= 0; y--)
{
- ReadOnlySpan inputPixelRow = image.GetPixelRowSpan(y);
+ ReadOnlySpan inputPixelRow = imageBuffer.DangerousGetRowSpan(y);
ReadOnlySpan outputPixelRow = MemoryMarshal.AsBytes(inputPixelRow);
stream.Write(outputPixelRow);
@@ -447,11 +447,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
ReadOnlySpan quantizedColorPalette = quantized.Palette.Span;
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
- ReadOnlySpan pixelRowSpan = quantized.GetPixelRowSpan(0);
+ ReadOnlySpan pixelRowSpan = quantized.DangerousGetRowSpan(0);
int rowPadding = pixelRowSpan.Length % 2 != 0 ? this.padding - 1 : this.padding;
for (int y = image.Height - 1; y >= 0; y--)
{
- pixelRowSpan = quantized.GetPixelRowSpan(y);
+ pixelRowSpan = quantized.DangerousGetRowSpan(y);
int endIdx = pixelRowSpan.Length % 2 == 0 ? pixelRowSpan.Length : pixelRowSpan.Length - 1;
for (int i = 0; i < endIdx; i += 2)
@@ -491,11 +491,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
ReadOnlySpan quantizedColorPalette = quantized.Palette.Span;
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
- ReadOnlySpan quantizedPixelRow = quantized.GetPixelRowSpan(0);
+ ReadOnlySpan quantizedPixelRow = quantized.DangerousGetRowSpan(0);
int rowPadding = quantizedPixelRow.Length % 8 != 0 ? this.padding - 1 : this.padding;
for (int y = image.Height - 1; y >= 0; y--)
{
- quantizedPixelRow = quantized.GetPixelRowSpan(y);
+ quantizedPixelRow = quantized.DangerousGetRowSpan(y);
int endIdx = quantizedPixelRow.Length % 8 == 0 ? quantizedPixelRow.Length : quantizedPixelRow.Length - 8;
for (int i = 0; i < endIdx; i += 8)
diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
index 482a761530..3e33a6e379 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
@@ -445,7 +445,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
for (int y = descriptorTop; y < descriptorBottom && y < imageHeight; y++)
{
- ref byte indicesRowRef = ref MemoryMarshal.GetReference(indices.GetRowSpan(y - descriptorTop));
+ ref byte indicesRowRef = ref MemoryMarshal.GetReference(indices.DangerousGetRowSpan(y - descriptorTop));
// Check if this image is interlaced.
int writeY; // the target y offset to write to
@@ -481,7 +481,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
writeY = y;
}
- ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.GetPixelRowSpan(writeY));
+ ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.PixelBuffer.DangerousGetRowSpan(writeY));
if (!transFlag)
{
diff --git a/src/ImageSharp/Formats/Gif/LzwDecoder.cs b/src/ImageSharp/Formats/Gif/LzwDecoder.cs
index 9eaa55566b..68227db53d 100644
--- a/src/ImageSharp/Formats/Gif/LzwDecoder.cs
+++ b/src/ImageSharp/Formats/Gif/LzwDecoder.cs
@@ -115,14 +115,14 @@ namespace SixLabors.ImageSharp.Formats.Gif
int y = 0;
int x = 0;
int rowMax = width;
- ref byte pixelsRowRef = ref MemoryMarshal.GetReference(pixels.GetRowSpan(y));
+ ref byte pixelsRowRef = ref MemoryMarshal.GetReference(pixels.DangerousGetRowSpan(y));
while (xyz < length)
{
// Reset row reference.
if (xyz == rowMax)
{
x = 0;
- pixelsRowRef = ref MemoryMarshal.GetReference(pixels.GetRowSpan(++y));
+ pixelsRowRef = ref MemoryMarshal.GetReference(pixels.DangerousGetRowSpan(++y));
rowMax = (y * width) + width;
}
diff --git a/src/ImageSharp/Formats/Gif/LzwEncoder.cs b/src/ImageSharp/Formats/Gif/LzwEncoder.cs
index e9fb7ab00b..c52e34f963 100644
--- a/src/ImageSharp/Formats/Gif/LzwEncoder.cs
+++ b/src/ImageSharp/Formats/Gif/LzwEncoder.cs
@@ -275,7 +275,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
for (int y = 0; y < indexedPixels.Height; y++)
{
- ref byte rowSpanRef = ref MemoryMarshal.GetReference(indexedPixels.GetRowSpan(y));
+ ref byte rowSpanRef = ref MemoryMarshal.GetReference(indexedPixels.DangerousGetRowSpan(y));
int offsetX = y == 0 ? 1 : 0;
for (int x = offsetX; x < indexedPixels.Width; x++)
diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.cs b/src/ImageSharp/Formats/ImageExtensions.Save.cs
index c5237f2bc7..a6a65aef62 100644
--- a/src/ImageSharp/Formats/ImageExtensions.Save.cs
+++ b/src/ImageSharp/Formats/ImageExtensions.Save.cs
@@ -10,6 +10,7 @@ using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
+using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Webp;
@@ -331,6 +332,109 @@ namespace SixLabors.ImageSharp
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance),
cancellationToken);
+ ///
+ /// Saves the image to the given stream with the Pbm format.
+ ///
+ /// The image this method extends.
+ /// The file path to save the image to.
+ /// Thrown if the path is null.
+ public static void SaveAsPbm(this Image source, string path) => SaveAsPbm(source, path, null);
+
+ ///
+ /// Saves the image to the given stream with the Pbm format.
+ ///
+ /// The image this method extends.
+ /// The file path to save the image to.
+ /// Thrown if the path is null.
+ /// A representing the asynchronous operation.
+ public static Task SaveAsPbmAsync(this Image source, string path) => SaveAsPbmAsync(source, path, null);
+
+ ///
+ /// Saves the image to the given stream with the Pbm format.
+ ///
+ /// The image this method extends.
+ /// The file path to save the image to.
+ /// The token to monitor for cancellation requests.
+ /// Thrown if the path is null.
+ /// A representing the asynchronous operation.
+ public static Task SaveAsPbmAsync(this Image source, string path, CancellationToken cancellationToken)
+ => SaveAsPbmAsync(source, path, null, cancellationToken);
+
+ ///
+ /// Saves the image to the given stream with the Pbm format.
+ ///
+ /// The image this method extends.
+ /// The file path to save the image to.
+ /// The encoder to save the image with.
+ /// Thrown if the path is null.
+ public static void SaveAsPbm(this Image source, string path, PbmEncoder encoder) =>
+ source.Save(
+ path,
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance));
+
+ ///
+ /// Saves the image to the given stream with the Pbm format.
+ ///
+ /// The image this method extends.
+ /// The file path to save the image to.
+ /// The encoder to save the image with.
+ /// The token to monitor for cancellation requests.
+ /// Thrown if the path is null.
+ /// A representing the asynchronous operation.
+ public static Task SaveAsPbmAsync(this Image source, string path, PbmEncoder encoder, CancellationToken cancellationToken = default) =>
+ source.SaveAsync(
+ path,
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance),
+ cancellationToken);
+
+ ///
+ /// Saves the image to the given stream with the Pbm format.
+ ///
+ /// The image this method extends.
+ /// The stream to save the image to.
+ /// Thrown if the stream is null.
+ public static void SaveAsPbm(this Image source, Stream stream)
+ => SaveAsPbm(source, stream, null);
+
+ ///
+ /// Saves the image to the given stream with the Pbm format.
+ ///
+ /// The image this method extends.
+ /// The stream to save the image to.
+ /// The token to monitor for cancellation requests.
+ /// Thrown if the stream is null.
+ /// A representing the asynchronous operation.
+ public static Task SaveAsPbmAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
+ => SaveAsPbmAsync(source, stream, null, cancellationToken);
+
+ ///
+ /// Saves the image to the given stream with the Pbm format.
+ ///
+ /// The image this method extends.
+ /// The stream to save the image to.
+ /// The encoder to save the image with.
+ /// Thrown if the stream is null.
+ /// A representing the asynchronous operation.
+ public static void SaveAsPbm(this Image source, Stream stream, PbmEncoder encoder)
+ => source.Save(
+ stream,
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance));
+
+ ///
+ /// Saves the image to the given stream with the Pbm format.
+ ///
+ /// The image this method extends.
+ /// The stream to save the image to.
+ /// The encoder to save the image with.
+ /// The token to monitor for cancellation requests.
+ /// Thrown if the stream is null.
+ /// A representing the asynchronous operation.
+ public static Task SaveAsPbmAsync(this Image source, Stream stream, PbmEncoder encoder, CancellationToken cancellationToken = default) =>
+ source.SaveAsync(
+ stream,
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance),
+ cancellationToken);
+
///
/// Saves the image to the given stream with the Png format.
///
diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.tt b/src/ImageSharp/Formats/ImageExtensions.Save.tt
index 874f3ab0dc..c4a00b37cb 100644
--- a/src/ImageSharp/Formats/ImageExtensions.Save.tt
+++ b/src/ImageSharp/Formats/ImageExtensions.Save.tt
@@ -15,6 +15,7 @@ using SixLabors.ImageSharp.Advanced;
"Bmp",
"Gif",
"Jpeg",
+ "Pbm",
"Png",
"Tga",
"Webp",
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
index dad46861e2..79eedf2f7d 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
@@ -223,12 +223,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
this.ComponentCount = componentBuffers.Count;
- this.Component0 = componentBuffers[0].GetRowSpan(row);
+ this.Component0 = componentBuffers[0].DangerousGetRowSpan(row);
// In case of grayscale, Component1 and Component2 point to Component0 memory area
- this.Component1 = this.ComponentCount > 1 ? componentBuffers[1].GetRowSpan(row) : this.Component0;
- this.Component2 = this.ComponentCount > 2 ? componentBuffers[2].GetRowSpan(row) : this.Component0;
- this.Component3 = this.ComponentCount > 3 ? componentBuffers[3].GetRowSpan(row) : Span.Empty;
+ this.Component1 = this.ComponentCount > 1 ? componentBuffers[1].DangerousGetRowSpan(row) : this.Component0;
+ this.Component2 = this.ComponentCount > 2 ? componentBuffers[2].DangerousGetRowSpan(row) : this.Component0;
+ this.Component3 = this.ComponentCount > 3 ? componentBuffers[3].DangerousGetRowSpan(row) : Span.Empty;
}
internal ComponentValues(
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
index 6f104351c8..ce5e5110b6 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
@@ -203,7 +203,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// by the basic H and V specified for the component
for (int y = 0; y < v; y++)
{
- Span blockSpan = component.SpectralBlocks.GetRowSpan(y);
+ Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y);
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
for (int x = 0; x < h; x++)
@@ -254,7 +254,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
for (int j = 0; j < h; j++)
{
this.cancellationToken.ThrowIfCancellationRequested();
- Span blockSpan = component.SpectralBlocks.GetRowSpan(j);
+ Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j);
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
for (int i = 0; i < w; i++)
@@ -377,7 +377,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
for (int y = 0; y < v; y++)
{
int blockRow = (mcuRow * v) + y;
- Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow);
+ Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(blockRow);
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
for (int x = 0; x < h; x++)
@@ -422,7 +422,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
this.cancellationToken.ThrowIfCancellationRequested();
- Span blockSpan = component.SpectralBlocks.GetRowSpan(j);
+ Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j);
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
for (int i = 0; i < w; i++)
@@ -450,7 +450,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
this.cancellationToken.ThrowIfCancellationRequested();
- Span blockSpan = component.SpectralBlocks.GetRowSpan(j);
+ Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j);
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
for (int i = 0; i < w; i++)
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
index 3e04e80b7a..c3bf1cbdd5 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
@@ -84,8 +84,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
int yBuffer = y * this.blockAreaSize.Height;
- Span colorBufferRow = this.ColorBuffer.GetRowSpan(yBuffer);
- Span blockRow = spectralBuffer.GetRowSpan(yBlockStart + y);
+ Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer);
+ Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y);
for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++)
{
@@ -119,11 +119,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
Buffer2D spectralBlocks = this.component.SpectralBlocks;
for (int i = 0; i < spectralBlocks.Height; i++)
{
- spectralBlocks.GetRowSpan(i).Clear();
+ spectralBlocks.DangerousGetRowSpan(i).Clear();
}
}
public Span GetColorBufferRowSpan(int row) =>
- this.ColorBuffer.GetRowSpan(row);
+ this.ColorBuffer.DangerousGetRowSpan(row);
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
index 0003437e74..5edcf565c2 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
@@ -187,7 +187,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
Span proxyRow = this.paddedProxyPixelRow.GetSpan();
PixelOperations.Instance.PackFromRgbPlanes(this.configuration, r, g, b, proxyRow);
- proxyRow.Slice(0, width).CopyTo(this.pixelBuffer.GetRowSpan(yy));
+ proxyRow.Slice(0, width).CopyTo(this.pixelBuffer.DangerousGetRowSpan(yy));
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs
index 16d24cf814..d4a4c1cf45 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs
@@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
int i = 0;
while (y < yEnd)
{
- this[i++] = buffer.GetRowSpan(y++);
+ this[i++] = buffer.DangerousGetRowSpan(y++);
}
}
diff --git a/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs b/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs
new file mode 100644
index 0000000000..33af30434c
--- /dev/null
+++ b/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs
@@ -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
+{
+ ///
+ /// Pixel decoding methods for the PBM binary encoding.
+ ///
+ internal class BinaryDecoder
+ {
+ private static L8 white = new(255);
+ private static L8 black = new(0);
+
+ ///
+ /// Decode the specified pixels.
+ ///
+ /// The type of pixel to encode to.
+ /// The configuration.
+ /// The pixel array to encode into.
+ /// The stream to read the data from.
+ /// The ColorType to decode.
+ /// Data type of the pixles components.
+ ///
+ /// Thrown if an invalid combination of setting is requested.
+ ///
+ public static void Process(Configuration configuration, Buffer2D pixels, BufferedReadStream stream, PbmColorType colorType, PbmComponentType componentType)
+ where TPixel : unmanaged, IPixel
+ {
+ 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(Configuration configuration, Buffer2D pixels, BufferedReadStream stream)
+ where TPixel : unmanaged, IPixel
+ {
+ const int bytesPerPixel = 1;
+ int width = pixels.Width;
+ int height = pixels.Height;
+ MemoryAllocator allocator = configuration.MemoryAllocator;
+ using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel);
+ Span rowSpan = row.GetSpan();
+
+ for (int y = 0; y < height; y++)
+ {
+ stream.Read(rowSpan);
+ Span pixelSpan = pixels.DangerousGetRowSpan(y);
+ PixelOperations.Instance.FromL8Bytes(
+ configuration,
+ rowSpan,
+ pixelSpan,
+ width);
+ }
+ }
+
+ private static void ProcessWideGrayscale(Configuration configuration, Buffer2D pixels, BufferedReadStream stream)
+ where TPixel : unmanaged, IPixel
+ {
+ const int bytesPerPixel = 2;
+ int width = pixels.Width;
+ int height = pixels.Height;
+ MemoryAllocator allocator = configuration.MemoryAllocator;
+ using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel);
+ Span rowSpan = row.GetSpan();
+
+ for (int y = 0; y < height; y++)
+ {
+ stream.Read(rowSpan);
+ Span pixelSpan = pixels.DangerousGetRowSpan(y);
+ PixelOperations.Instance.FromL16Bytes(
+ configuration,
+ rowSpan,
+ pixelSpan,
+ width);
+ }
+ }
+
+ private static void ProcessRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream)
+ where TPixel : unmanaged, IPixel
+ {
+ const int bytesPerPixel = 3;
+ int width = pixels.Width;
+ int height = pixels.Height;
+ MemoryAllocator allocator = configuration.MemoryAllocator;
+ using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel);
+ Span rowSpan = row.GetSpan();
+
+ for (int y = 0; y < height; y++)
+ {
+ stream.Read(rowSpan);
+ Span pixelSpan = pixels.DangerousGetRowSpan(y);
+ PixelOperations.Instance.FromRgb24Bytes(
+ configuration,
+ rowSpan,
+ pixelSpan,
+ width);
+ }
+ }
+
+ private static void ProcessWideRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream)
+ where TPixel : unmanaged, IPixel
+ {
+ const int bytesPerPixel = 6;
+ int width = pixels.Width;
+ int height = pixels.Height;
+ MemoryAllocator allocator = configuration.MemoryAllocator;
+ using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel);
+ Span rowSpan = row.GetSpan();
+
+ for (int y = 0; y < height; y++)
+ {
+ stream.Read(rowSpan);
+ Span pixelSpan = pixels.DangerousGetRowSpan(y);
+ PixelOperations.Instance.FromRgb48Bytes(
+ configuration,
+ rowSpan,
+ pixelSpan,
+ width);
+ }
+ }
+
+ private static void ProcessBlackAndWhite(Configuration configuration, Buffer2D pixels, BufferedReadStream stream)
+ where TPixel : unmanaged, IPixel
+ {
+ int width = pixels.Width;
+ int height = pixels.Height;
+ int startBit = 0;
+ MemoryAllocator allocator = configuration.MemoryAllocator;
+ using IMemoryOwner row = allocator.Allocate(width);
+ Span 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 pixelSpan = pixels.DangerousGetRowSpan(y);
+ PixelOperations.Instance.FromL8(
+ configuration,
+ rowSpan,
+ pixelSpan);
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs b/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs
new file mode 100644
index 0000000000..332ab9b50d
--- /dev/null
+++ b/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs
@@ -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
+{
+ ///
+ /// Pixel encoding methods for the PBM binary encoding.
+ ///
+ internal class BinaryEncoder
+ {
+ ///
+ /// Decode pixels into the PBM binary encoding.
+ ///
+ /// The type of input pixel.
+ /// The configuration.
+ /// The bytestream to write to.
+ /// The input image.
+ /// The ColorType to use.
+ /// Data type of the pixles components.
+ ///
+ /// Thrown if an invalid combination of setting is requested.
+ ///
+ public static void WritePixels(Configuration configuration, Stream stream, ImageFrame image, PbmColorType colorType, PbmComponentType componentType)
+ where TPixel : unmanaged, IPixel
+ {
+ 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(Configuration configuration, Stream stream, ImageFrame image)
+ where TPixel : unmanaged, IPixel
+ {
+ int width = image.Width;
+ int height = image.Height;
+ Buffer2D pixelBuffer = image.PixelBuffer;
+ MemoryAllocator allocator = configuration.MemoryAllocator;
+ using IMemoryOwner row = allocator.Allocate(width);
+ Span rowSpan = row.GetSpan();
+
+ for (int y = 0; y < height; y++)
+ {
+ Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
+
+ PixelOperations.Instance.ToL8Bytes(
+ configuration,
+ pixelSpan,
+ rowSpan,
+ width);
+
+ stream.Write(rowSpan);
+ }
+ }
+
+ private static void WriteWideGrayscale(Configuration configuration, Stream stream, ImageFrame image)
+ where TPixel : unmanaged, IPixel
+ {
+ const int bytesPerPixel = 2;
+ int width = image.Width;
+ int height = image.Height;
+ Buffer2D pixelBuffer = image.PixelBuffer;
+ MemoryAllocator allocator = configuration.MemoryAllocator;
+ using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel);
+ Span rowSpan = row.GetSpan();
+
+ for (int y = 0; y < height; y++)
+ {
+ Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
+
+ PixelOperations.Instance.ToL16Bytes(
+ configuration,
+ pixelSpan,
+ rowSpan,
+ width);
+
+ stream.Write(rowSpan);
+ }
+ }
+
+ private static void WriteRgb(Configuration configuration, Stream stream, ImageFrame image)
+ where TPixel : unmanaged, IPixel
+ {
+ const int bytesPerPixel = 3;
+ int width = image.Width;
+ int height = image.Height;
+ Buffer2D pixelBuffer = image.PixelBuffer;
+ MemoryAllocator allocator = configuration.MemoryAllocator;
+ using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel);
+ Span rowSpan = row.GetSpan();
+
+ for (int y = 0; y < height; y++)
+ {
+ Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
+
+ PixelOperations.Instance.ToRgb24Bytes(
+ configuration,
+ pixelSpan,
+ rowSpan,
+ width);
+
+ stream.Write(rowSpan);
+ }
+ }
+
+ private static void WriteWideRgb(Configuration configuration, Stream stream, ImageFrame image)
+ where TPixel : unmanaged, IPixel
+ {
+ const int bytesPerPixel = 6;
+ int width = image.Width;
+ int height = image.Height;
+ Buffer2D pixelBuffer = image.PixelBuffer;
+ MemoryAllocator allocator = configuration.MemoryAllocator;
+ using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel);
+ Span rowSpan = row.GetSpan();
+
+ for (int y = 0; y < height; y++)
+ {
+ Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
+
+ PixelOperations.Instance.ToRgb48Bytes(
+ configuration,
+ pixelSpan,
+ rowSpan,
+ width);
+
+ stream.Write(rowSpan);
+ }
+ }
+
+ private static void WriteBlackAndWhite(Configuration configuration, Stream stream, ImageFrame image)
+ where TPixel : unmanaged, IPixel
+ {
+ int width = image.Width;
+ int height = image.Height;
+ Buffer2D pixelBuffer = image.PixelBuffer;
+ MemoryAllocator allocator = configuration.MemoryAllocator;
+ using IMemoryOwner row = allocator.Allocate(width);
+ Span rowSpan = row.GetSpan();
+
+ int previousValue = 0;
+ int startBit = 0;
+ for (int y = 0; y < height; y++)
+ {
+ Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
+
+ PixelOperations.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;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs
new file mode 100644
index 0000000000..581d3e592b
--- /dev/null
+++ b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs
@@ -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
+{
+ ///
+ /// Extensions methods for .
+ ///
+ internal static class BufferedReadStreamExtensions
+ {
+ ///
+ /// Skip over any whitespace or any comments.
+ ///
+ 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);
+ }
+
+ ///
+ /// Read a decimal text value.
+ ///
+ /// The integer value of the decimal.
+ 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;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs b/src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs
new file mode 100644
index 0000000000..988d9e560e
--- /dev/null
+++ b/src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Pbm
+{
+ ///
+ /// Configuration options for use during PBM encoding.
+ ///
+ internal interface IPbmEncoderOptions
+ {
+ ///
+ /// Gets the encoding of the pixels.
+ ///
+ PbmEncoding? Encoding { get; }
+
+ ///
+ /// Gets the Color type of the resulting image.
+ ///
+ PbmColorType? ColorType { get; }
+
+ ///
+ /// Gets the Data Type of the pixel components.
+ ///
+ PbmComponentType? ComponentType { get; }
+ }
+}
diff --git a/src/ImageSharp/Formats/Pbm/MetadataExtensions.cs b/src/ImageSharp/Formats/Pbm/MetadataExtensions.cs
new file mode 100644
index 0000000000..cce8fb3187
--- /dev/null
+++ b/src/ImageSharp/Formats/Pbm/MetadataExtensions.cs
@@ -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
+{
+ ///
+ /// Extension methods for the type.
+ ///
+ public static partial class MetadataExtensions
+ {
+ ///
+ /// Gets the pbm format specific metadata for the image.
+ ///
+ /// The metadata this method extends.
+ /// The .
+ public static PbmMetadata GetPbmMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(PbmFormat.Instance);
+ }
+}
diff --git a/src/ImageSharp/Formats/Pbm/PbmColorType.cs b/src/ImageSharp/Formats/Pbm/PbmColorType.cs
new file mode 100644
index 0000000000..827f19344b
--- /dev/null
+++ b/src/ImageSharp/Formats/Pbm/PbmColorType.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Pbm
+{
+ ///
+ /// Provides enumeration of available PBM color types.
+ ///
+ public enum PbmColorType : byte
+ {
+ ///
+ /// PBM
+ ///
+ BlackAndWhite = 0,
+
+ ///
+ /// PGM - Greyscale. Single component.
+ ///
+ Grayscale = 1,
+
+ ///
+ /// PPM - RGB Color. 3 components.
+ ///
+ Rgb = 2,
+ }
+}
diff --git a/src/ImageSharp/Formats/Pbm/PbmComponentType.cs b/src/ImageSharp/Formats/Pbm/PbmComponentType.cs
new file mode 100644
index 0000000000..26272021ce
--- /dev/null
+++ b/src/ImageSharp/Formats/Pbm/PbmComponentType.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Pbm
+{
+ ///
+ /// The data type of the components of the pixels.
+ ///
+ public enum PbmComponentType : byte
+ {
+ ///
+ /// Single bit per pixel, exclusively for .
+ ///
+ Bit = 0,
+
+ ///
+ /// 8 bits unsigned integer per component.
+ ///
+ Byte = 1,
+
+ ///
+ /// 16 bits unsigned integer per component.
+ ///
+ Short = 2
+ }
+}
diff --git a/src/ImageSharp/Formats/Pbm/PbmConfigurationModule.cs b/src/ImageSharp/Formats/Pbm/PbmConfigurationModule.cs
new file mode 100644
index 0000000000..172bda667f
--- /dev/null
+++ b/src/ImageSharp/Formats/Pbm/PbmConfigurationModule.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Pbm
+{
+ ///
+ /// Registers the image encoders, decoders and mime type detectors for the Pbm format.
+ ///
+ public sealed class PbmConfigurationModule : IConfigurationModule
+ {
+ ///
+ public void Configure(Configuration configuration)
+ {
+ configuration.ImageFormatsManager.SetEncoder(PbmFormat.Instance, new PbmEncoder());
+ configuration.ImageFormatsManager.SetDecoder(PbmFormat.Instance, new PbmDecoder());
+ configuration.ImageFormatsManager.AddImageFormatDetector(new PbmImageFormatDetector());
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Pbm/PbmConstants.cs b/src/ImageSharp/Formats/Pbm/PbmConstants.cs
new file mode 100644
index 0000000000..912ffaf856
--- /dev/null
+++ b/src/ImageSharp/Formats/Pbm/PbmConstants.cs
@@ -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
+{
+ ///
+ /// Contains PBM constant values defined in the specification.
+ ///
+ internal static class PbmConstants
+ {
+ ///
+ /// The maximum allowable pixel value of a ppm image.
+ ///
+ public const ushort MaxLength = 65535;
+
+ ///
+ /// The list of mimetypes that equate to a ppm.
+ ///
+ public static readonly IEnumerable MimeTypes = new[] { "image/x-portable-pixmap", "image/x-portable-anymap" };
+
+ ///
+ /// The list of file extensions that equate to a ppm.
+ ///
+ public static readonly IEnumerable FileExtensions = new[] { "ppm", "pbm", "pgm" };
+ }
+}
diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs
new file mode 100644
index 0000000000..2eebbb1d93
--- /dev/null
+++ b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs
@@ -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
+{
+ ///
+ /// Image decoder for reading PGM, PBM or PPM bitmaps from a stream. These images are from
+ /// the family of PNM images.
+ ///
+ /// -
+ /// PBM
+ /// Black and white images.
+ ///
+ /// -
+ /// PGM
+ /// Grayscale images.
+ ///
+ /// -
+ /// PPM
+ /// Color images, with RGB pixels.
+ ///
+ ///
+ /// The specification of these images is found at .
+ ///
+ public sealed class PbmDecoder : IImageDecoder, IImageInfoDetector
+ {
+ ///
+ public Image Decode(Configuration configuration, Stream stream)
+ where TPixel : unmanaged, IPixel
+ {
+ Guard.NotNull(stream, nameof(stream));
+
+ var decoder = new PbmDecoderCore(configuration);
+ return decoder.Decode(configuration, stream);
+ }
+
+ ///
+ public Image Decode(Configuration configuration, Stream stream)
+ => this.Decode(configuration, stream);
+
+ ///
+ public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
+ where TPixel : unmanaged, IPixel
+ {
+ Guard.NotNull(stream, nameof(stream));
+
+ var decoder = new PbmDecoderCore(configuration);
+ return decoder.DecodeAsync(configuration, stream, cancellationToken);
+ }
+
+ ///
+ public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
+ => await this.DecodeAsync(configuration, stream, cancellationToken)
+ .ConfigureAwait(false);
+
+ ///
+ public IImageInfo Identify(Configuration configuration, Stream stream)
+ {
+ Guard.NotNull(stream, nameof(stream));
+
+ var decoder = new PbmDecoderCore(configuration);
+ return decoder.Identify(configuration, stream);
+ }
+
+ ///
+ public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
+ {
+ Guard.NotNull(stream, nameof(stream));
+
+ var decoder = new PbmDecoderCore(configuration);
+ return decoder.IdentifyAsync(configuration, stream, cancellationToken);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs
new file mode 100644
index 0000000000..749fc0292b
--- /dev/null
+++ b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs
@@ -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
+{
+ ///
+ /// Performs the PBM decoding operation.
+ ///
+ internal sealed class PbmDecoderCore : IImageDecoderInternals
+ {
+ private int maxPixelValue;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The configuration.
+ public PbmDecoderCore(Configuration configuration) => this.Configuration = configuration ?? Configuration.Default;
+
+ ///
+ public Configuration Configuration { get; }
+
+ ///
+ /// Gets the colortype to use
+ ///
+ public PbmColorType ColorType { get; private set; }
+
+ ///
+ /// Gets the size of the pixel array
+ ///
+ public Size PixelSize { get; private set; }
+
+ ///
+ /// Gets the component data type
+ ///
+ public PbmComponentType ComponentType { get; private set; }
+
+ ///
+ /// Gets the Encoding of pixels
+ ///
+ public PbmEncoding Encoding { get; private set; }
+
+ ///
+ /// Gets the decoded by this decoder instance.
+ ///
+ public ImageMetadata Metadata { get; private set; }
+
+ ///
+ Size IImageDecoderInternals.Dimensions => this.PixelSize;
+
+ private bool NeedsUpscaling => this.ColorType != PbmColorType.BlackAndWhite && this.maxPixelValue is not 255 and not 65535;
+
+ ///
+ public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken)
+ where TPixel : unmanaged, IPixel
+ {
+ this.ProcessHeader(stream);
+
+ var image = new Image(this.Configuration, this.PixelSize.Width, this.PixelSize.Height, this.Metadata);
+
+ Buffer2D pixels = image.GetRootFramePixelBuffer();
+
+ this.ProcessPixels(stream, pixels);
+ if (this.NeedsUpscaling)
+ {
+ this.ProcessUpscaling(image);
+ }
+
+ return image;
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ /// Processes the ppm header.
+ ///
+ /// The input stream.
+ private void ProcessHeader(BufferedReadStream stream)
+ {
+ Span 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(BufferedReadStream stream, Buffer2D pixels)
+ where TPixel : unmanaged, IPixel
+ {
+ 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(Image image)
+ where TPixel : unmanaged, IPixel
+ {
+ int maxAllocationValue = this.ComponentType == PbmComponentType.Short ? 65535 : 255;
+ float factor = maxAllocationValue / this.maxPixelValue;
+ image.Mutate(x => x.Brightness(factor));
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoder.cs b/src/ImageSharp/Formats/Pbm/PbmEncoder.cs
new file mode 100644
index 0000000000..75d6660635
--- /dev/null
+++ b/src/ImageSharp/Formats/Pbm/PbmEncoder.cs
@@ -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
+{
+ ///
+ /// Image encoder for writing an image to a stream as PGM, PBM or PPM bitmap. These images are from
+ /// the family of PNM images.
+ ///
+ /// 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.
+ ///
+ /// -
+ /// PBM
+ /// Black and white images, with 1 representing black and 0 representing white.
+ ///
+ /// -
+ /// PGM
+ /// Grayscale images, scaling from 0 to max_pixel_value, 0 representing black and max_pixel_value representing white.
+ ///
+ /// -
+ /// PPM
+ /// Color images, with RGB pixels (in that order), with 0 representing black and 2 representing full color.
+ ///
+ ///
+ ///
+ /// The specification of these images is found at .
+ ///
+ public sealed class PbmEncoder : IImageEncoder, IPbmEncoderOptions
+ {
+ ///
+ /// Gets or sets the Encoding of the pixels.
+ ///
+ public PbmEncoding? Encoding { get; set; }
+
+ ///
+ /// Gets or sets the Color type of the resulting image.
+ ///
+ public PbmColorType? ColorType { get; set; }
+
+ ///
+ /// Gets or sets the data type of the pixels components.
+ ///
+ public PbmComponentType? ComponentType { get; set; }
+
+ ///
+ public void Encode(Image image, Stream stream)
+ where TPixel : unmanaged, IPixel
+ {
+ var encoder = new PbmEncoderCore(image.GetConfiguration(), this);
+ encoder.Encode(image, stream);
+ }
+
+ ///
+ public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken)
+ where TPixel : unmanaged, IPixel
+ {
+ var encoder = new PbmEncoderCore(image.GetConfiguration(), this);
+ return encoder.EncodeAsync(image, stream, cancellationToken);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs
new file mode 100644
index 0000000000..9d1f39edf3
--- /dev/null
+++ b/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs
@@ -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
+{
+ ///
+ /// Image encoder for writing an image to a stream as a PGM, PBM, PPM or PAM bitmap.
+ ///
+ internal sealed class PbmEncoderCore : IImageEncoderInternals
+ {
+ private const byte NewLine = (byte)'\n';
+ private const byte Space = (byte)' ';
+ private const byte P = (byte)'P';
+
+ ///
+ /// The global configuration.
+ ///
+ private Configuration configuration;
+
+ ///
+ /// The encoder options.
+ ///
+ private readonly IPbmEncoderOptions options;
+
+ ///
+ /// The encoding for the pixels.
+ ///
+ private PbmEncoding encoding;
+
+ ///
+ /// Gets the Color type of the resulting image.
+ ///
+ private PbmColorType colorType;
+
+ ///
+ /// Gets the maximum pixel value, per component.
+ ///
+ private PbmComponentType componentType;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The configuration.
+ /// The encoder options.
+ public PbmEncoderCore(Configuration configuration, IPbmEncoderOptions options)
+ {
+ this.configuration = configuration;
+ this.options = options;
+ }
+
+ ///
+ /// Encodes the image to the specified stream from the .
+ ///
+ /// The pixel format.
+ /// The to encode from.
+ /// The to encode the image data to.
+ /// The token to request cancellation.
+ public void Encode(Image image, Stream stream, CancellationToken cancellationToken)
+ where TPixel : unmanaged, IPixel
+ {
+ 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(Image image)
+ where TPixel : unmanaged, IPixel
+ {
+ 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 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);
+ }
+
+ ///
+ /// Writes the pixel data to the binary stream.
+ ///
+ /// The pixel format.
+ /// The to write to.
+ ///
+ /// The containing pixel data.
+ ///
+ private void WritePixels(Stream stream, ImageFrame image)
+ where TPixel : unmanaged, IPixel
+ {
+ 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);
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoding.cs b/src/ImageSharp/Formats/Pbm/PbmEncoding.cs
new file mode 100644
index 0000000000..be7fb909f3
--- /dev/null
+++ b/src/ImageSharp/Formats/Pbm/PbmEncoding.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Pbm
+{
+ ///
+ /// Provides enumeration of available PBM encodings.
+ ///
+ public enum PbmEncoding : byte
+ {
+ ///
+ /// Plain text decimal encoding.
+ ///
+ Plain = 0,
+
+ ///
+ /// Binary integer encoding.
+ ///
+ Binary = 1,
+ }
+}
diff --git a/src/ImageSharp/Formats/Pbm/PbmFormat.cs b/src/ImageSharp/Formats/Pbm/PbmFormat.cs
new file mode 100644
index 0000000000..5ffb49652f
--- /dev/null
+++ b/src/ImageSharp/Formats/Pbm/PbmFormat.cs
@@ -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
+{
+ ///
+ /// Registers the image encoders, decoders and mime type detectors for the PBM format.
+ ///
+ public sealed class PbmFormat : IImageFormat
+ {
+ private PbmFormat()
+ {
+ }
+
+ ///
+ /// Gets the current instance.
+ ///
+ public static PbmFormat Instance { get; } = new();
+
+ ///
+ public string Name => "PBM";
+
+ ///
+ public string DefaultMimeType => "image/x-portable-pixmap";
+
+ ///
+ public IEnumerable MimeTypes => PbmConstants.MimeTypes;
+
+ ///
+ public IEnumerable FileExtensions => PbmConstants.FileExtensions;
+
+ ///
+ public PbmMetadata CreateDefaultFormatMetadata() => new();
+ }
+}
diff --git a/src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs b/src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs
new file mode 100644
index 0000000000..15bacc4de7
--- /dev/null
+++ b/src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+namespace SixLabors.ImageSharp.Formats.Pbm
+{
+ ///
+ /// Detects Pbm file headers.
+ ///
+ public sealed class PbmImageFormatDetector : IImageFormatDetector
+ {
+ private const byte P = (byte)'P';
+ private const byte Zero = (byte)'0';
+ private const byte Seven = (byte)'7';
+
+ ///
+ public int HeaderSize => 2;
+
+ ///
+ public IImageFormat DetectFormat(ReadOnlySpan header) => this.IsSupportedFileFormat(header) ? PbmFormat.Instance : null;
+
+ private bool IsSupportedFileFormat(ReadOnlySpan