Skip to content

Commit ca8e984

Browse files
Merge pull request #1851 from ynse01/pgm-support
Add support for Portable Bitmap images
2 parents ee83e99 + 3730a02 commit ca8e984

File tree

60 files changed

+2553
-121
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+2553
-121
lines changed

src/ImageSharp/Advanced/AotCompilerTools.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using SixLabors.ImageSharp.Formats.Gif;
1111
using SixLabors.ImageSharp.Formats.Jpeg;
1212
using SixLabors.ImageSharp.Formats.Jpeg.Components;
13+
using SixLabors.ImageSharp.Formats.Pbm;
1314
using SixLabors.ImageSharp.Formats.Png;
1415
using SixLabors.ImageSharp.Formats.Tga;
1516
using SixLabors.ImageSharp.Formats.Tiff;
@@ -200,6 +201,7 @@ private static void AotCompileImageEncoderInternals<TPixel>()
200201
default(BmpEncoderCore).Encode<TPixel>(default, default, default);
201202
default(GifEncoderCore).Encode<TPixel>(default, default, default);
202203
default(JpegEncoderCore).Encode<TPixel>(default, default, default);
204+
default(PbmEncoderCore).Encode<TPixel>(default, default, default);
203205
default(PngEncoderCore).Encode<TPixel>(default, default, default);
204206
default(TgaEncoderCore).Encode<TPixel>(default, default, default);
205207
default(TiffEncoderCore).Encode<TPixel>(default, default, default);
@@ -217,6 +219,7 @@ private static void AotCompileImageDecoderInternals<TPixel>()
217219
default(BmpDecoderCore).Decode<TPixel>(default, default, default);
218220
default(GifDecoderCore).Decode<TPixel>(default, default, default);
219221
default(JpegDecoderCore).Decode<TPixel>(default, default, default);
222+
default(PbmDecoderCore).Decode<TPixel>(default, default, default);
220223
default(PngDecoderCore).Decode<TPixel>(default, default, default);
221224
default(TgaDecoderCore).Decode<TPixel>(default, default, default);
222225
default(TiffDecoderCore).Decode<TPixel>(default, default, default);
@@ -234,6 +237,7 @@ private static void AotCompileImageEncoders<TPixel>()
234237
AotCompileImageEncoder<TPixel, BmpEncoder>();
235238
AotCompileImageEncoder<TPixel, GifEncoder>();
236239
AotCompileImageEncoder<TPixel, JpegEncoder>();
240+
AotCompileImageEncoder<TPixel, PbmEncoder>();
237241
AotCompileImageEncoder<TPixel, PngEncoder>();
238242
AotCompileImageEncoder<TPixel, TgaEncoder>();
239243
AotCompileImageEncoder<TPixel, TiffEncoder>();
@@ -251,6 +255,7 @@ private static void AotCompileImageDecoders<TPixel>()
251255
AotCompileImageDecoder<TPixel, BmpDecoder>();
252256
AotCompileImageDecoder<TPixel, GifDecoder>();
253257
AotCompileImageDecoder<TPixel, JpegDecoder>();
258+
AotCompileImageDecoder<TPixel, PbmDecoder>();
254259
AotCompileImageDecoder<TPixel, PngDecoder>();
255260
AotCompileImageDecoder<TPixel, TgaDecoder>();
256261
AotCompileImageDecoder<TPixel, TiffDecoder>();

src/ImageSharp/Configuration.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using SixLabors.ImageSharp.Formats.Bmp;
99
using SixLabors.ImageSharp.Formats.Gif;
1010
using SixLabors.ImageSharp.Formats.Jpeg;
11+
using SixLabors.ImageSharp.Formats.Pbm;
1112
using SixLabors.ImageSharp.Formats.Png;
1213
using SixLabors.ImageSharp.Formats.Tga;
1314
using SixLabors.ImageSharp.Formats.Tiff;
@@ -209,6 +210,7 @@ public void Configure(IConfigurationModule configuration)
209210
/// <see cref="JpegConfigurationModule"/>
210211
/// <see cref="GifConfigurationModule"/>
211212
/// <see cref="BmpConfigurationModule"/>.
213+
/// <see cref="PbmConfigurationModule"/>.
212214
/// <see cref="TgaConfigurationModule"/>.
213215
/// <see cref="TiffConfigurationModule"/>.
214216
/// <see cref="WebpConfigurationModule"/>.
@@ -219,6 +221,7 @@ public void Configure(IConfigurationModule configuration)
219221
new JpegConfigurationModule(),
220222
new GifConfigurationModule(),
221223
new BmpConfigurationModule(),
224+
new PbmConfigurationModule(),
222225
new TgaConfigurationModule(),
223226
new TiffConfigurationModule(),
224227
new WebpConfigurationModule());

src/ImageSharp/Formats/ImageExtensions.Save.cs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using SixLabors.ImageSharp.Formats.Bmp;
1111
using SixLabors.ImageSharp.Formats.Gif;
1212
using SixLabors.ImageSharp.Formats.Jpeg;
13+
using SixLabors.ImageSharp.Formats.Pbm;
1314
using SixLabors.ImageSharp.Formats.Png;
1415
using SixLabors.ImageSharp.Formats.Tga;
1516
using SixLabors.ImageSharp.Formats.Webp;
@@ -331,6 +332,109 @@ public static Task SaveAsJpegAsync(this Image source, Stream stream, JpegEncoder
331332
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance),
332333
cancellationToken);
333334

335+
/// <summary>
336+
/// Saves the image to the given stream with the Pbm format.
337+
/// </summary>
338+
/// <param name="source">The image this method extends.</param>
339+
/// <param name="path">The file path to save the image to.</param>
340+
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
341+
public static void SaveAsPbm(this Image source, string path) => SaveAsPbm(source, path, null);
342+
343+
/// <summary>
344+
/// Saves the image to the given stream with the Pbm format.
345+
/// </summary>
346+
/// <param name="source">The image this method extends.</param>
347+
/// <param name="path">The file path to save the image to.</param>
348+
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
349+
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
350+
public static Task SaveAsPbmAsync(this Image source, string path) => SaveAsPbmAsync(source, path, null);
351+
352+
/// <summary>
353+
/// Saves the image to the given stream with the Pbm format.
354+
/// </summary>
355+
/// <param name="source">The image this method extends.</param>
356+
/// <param name="path">The file path to save the image to.</param>
357+
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
358+
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
359+
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
360+
public static Task SaveAsPbmAsync(this Image source, string path, CancellationToken cancellationToken)
361+
=> SaveAsPbmAsync(source, path, null, cancellationToken);
362+
363+
/// <summary>
364+
/// Saves the image to the given stream with the Pbm format.
365+
/// </summary>
366+
/// <param name="source">The image this method extends.</param>
367+
/// <param name="path">The file path to save the image to.</param>
368+
/// <param name="encoder">The encoder to save the image with.</param>
369+
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
370+
public static void SaveAsPbm(this Image source, string path, PbmEncoder encoder) =>
371+
source.Save(
372+
path,
373+
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance));
374+
375+
/// <summary>
376+
/// Saves the image to the given stream with the Pbm format.
377+
/// </summary>
378+
/// <param name="source">The image this method extends.</param>
379+
/// <param name="path">The file path to save the image to.</param>
380+
/// <param name="encoder">The encoder to save the image with.</param>
381+
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
382+
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
383+
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
384+
public static Task SaveAsPbmAsync(this Image source, string path, PbmEncoder encoder, CancellationToken cancellationToken = default) =>
385+
source.SaveAsync(
386+
path,
387+
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance),
388+
cancellationToken);
389+
390+
/// <summary>
391+
/// Saves the image to the given stream with the Pbm format.
392+
/// </summary>
393+
/// <param name="source">The image this method extends.</param>
394+
/// <param name="stream">The stream to save the image to.</param>
395+
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
396+
public static void SaveAsPbm(this Image source, Stream stream)
397+
=> SaveAsPbm(source, stream, null);
398+
399+
/// <summary>
400+
/// Saves the image to the given stream with the Pbm format.
401+
/// </summary>
402+
/// <param name="source">The image this method extends.</param>
403+
/// <param name="stream">The stream to save the image to.</param>
404+
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
405+
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
406+
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
407+
public static Task SaveAsPbmAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
408+
=> SaveAsPbmAsync(source, stream, null, cancellationToken);
409+
410+
/// <summary>
411+
/// Saves the image to the given stream with the Pbm format.
412+
/// </summary>
413+
/// <param name="source">The image this method extends.</param>
414+
/// <param name="stream">The stream to save the image to.</param>
415+
/// <param name="encoder">The encoder to save the image with.</param>
416+
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
417+
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
418+
public static void SaveAsPbm(this Image source, Stream stream, PbmEncoder encoder)
419+
=> source.Save(
420+
stream,
421+
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance));
422+
423+
/// <summary>
424+
/// Saves the image to the given stream with the Pbm format.
425+
/// </summary>
426+
/// <param name="source">The image this method extends.</param>
427+
/// <param name="stream">The stream to save the image to.</param>
428+
/// <param name="encoder">The encoder to save the image with.</param>
429+
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
430+
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
431+
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
432+
public static Task SaveAsPbmAsync(this Image source, Stream stream, PbmEncoder encoder, CancellationToken cancellationToken = default) =>
433+
source.SaveAsync(
434+
stream,
435+
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance),
436+
cancellationToken);
437+
334438
/// <summary>
335439
/// Saves the image to the given stream with the Png format.
336440
/// </summary>

src/ImageSharp/Formats/ImageExtensions.Save.tt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ using SixLabors.ImageSharp.Advanced;
1515
"Bmp",
1616
"Gif",
1717
"Jpeg",
18+
"Pbm",
1819
"Png",
1920
"Tga",
2021
"Webp",
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Buffers;
6+
using SixLabors.ImageSharp.IO;
7+
using SixLabors.ImageSharp.Memory;
8+
using SixLabors.ImageSharp.PixelFormats;
9+
10+
namespace SixLabors.ImageSharp.Formats.Pbm
11+
{
12+
/// <summary>
13+
/// Pixel decoding methods for the PBM binary encoding.
14+
/// </summary>
15+
internal class BinaryDecoder
16+
{
17+
private static L8 white = new(255);
18+
private static L8 black = new(0);
19+
20+
/// <summary>
21+
/// Decode the specified pixels.
22+
/// </summary>
23+
/// <typeparam name="TPixel">The type of pixel to encode to.</typeparam>
24+
/// <param name="configuration">The configuration.</param>
25+
/// <param name="pixels">The pixel array to encode into.</param>
26+
/// <param name="stream">The stream to read the data from.</param>
27+
/// <param name="colorType">The ColorType to decode.</param>
28+
/// <param name="componentType">Data type of the pixles components.</param>
29+
/// <exception cref="InvalidImageContentException">
30+
/// Thrown if an invalid combination of setting is requested.
31+
/// </exception>
32+
public static void Process<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream, PbmColorType colorType, PbmComponentType componentType)
33+
where TPixel : unmanaged, IPixel<TPixel>
34+
{
35+
if (colorType == PbmColorType.Grayscale)
36+
{
37+
if (componentType == PbmComponentType.Byte)
38+
{
39+
ProcessGrayscale(configuration, pixels, stream);
40+
}
41+
else
42+
{
43+
ProcessWideGrayscale(configuration, pixels, stream);
44+
}
45+
}
46+
else if (colorType == PbmColorType.Rgb)
47+
{
48+
if (componentType == PbmComponentType.Byte)
49+
{
50+
ProcessRgb(configuration, pixels, stream);
51+
}
52+
else
53+
{
54+
ProcessWideRgb(configuration, pixels, stream);
55+
}
56+
}
57+
else
58+
{
59+
ProcessBlackAndWhite(configuration, pixels, stream);
60+
}
61+
}
62+
63+
private static void ProcessGrayscale<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream)
64+
where TPixel : unmanaged, IPixel<TPixel>
65+
{
66+
const int bytesPerPixel = 1;
67+
int width = pixels.Width;
68+
int height = pixels.Height;
69+
MemoryAllocator allocator = configuration.MemoryAllocator;
70+
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel);
71+
Span<byte> rowSpan = row.GetSpan();
72+
73+
for (int y = 0; y < height; y++)
74+
{
75+
stream.Read(rowSpan);
76+
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
77+
PixelOperations<TPixel>.Instance.FromL8Bytes(
78+
configuration,
79+
rowSpan,
80+
pixelSpan,
81+
width);
82+
}
83+
}
84+
85+
private static void ProcessWideGrayscale<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream)
86+
where TPixel : unmanaged, IPixel<TPixel>
87+
{
88+
const int bytesPerPixel = 2;
89+
int width = pixels.Width;
90+
int height = pixels.Height;
91+
MemoryAllocator allocator = configuration.MemoryAllocator;
92+
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel);
93+
Span<byte> rowSpan = row.GetSpan();
94+
95+
for (int y = 0; y < height; y++)
96+
{
97+
stream.Read(rowSpan);
98+
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
99+
PixelOperations<TPixel>.Instance.FromL16Bytes(
100+
configuration,
101+
rowSpan,
102+
pixelSpan,
103+
width);
104+
}
105+
}
106+
107+
private static void ProcessRgb<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream)
108+
where TPixel : unmanaged, IPixel<TPixel>
109+
{
110+
const int bytesPerPixel = 3;
111+
int width = pixels.Width;
112+
int height = pixels.Height;
113+
MemoryAllocator allocator = configuration.MemoryAllocator;
114+
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel);
115+
Span<byte> rowSpan = row.GetSpan();
116+
117+
for (int y = 0; y < height; y++)
118+
{
119+
stream.Read(rowSpan);
120+
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
121+
PixelOperations<TPixel>.Instance.FromRgb24Bytes(
122+
configuration,
123+
rowSpan,
124+
pixelSpan,
125+
width);
126+
}
127+
}
128+
129+
private static void ProcessWideRgb<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream)
130+
where TPixel : unmanaged, IPixel<TPixel>
131+
{
132+
const int bytesPerPixel = 6;
133+
int width = pixels.Width;
134+
int height = pixels.Height;
135+
MemoryAllocator allocator = configuration.MemoryAllocator;
136+
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel);
137+
Span<byte> rowSpan = row.GetSpan();
138+
139+
for (int y = 0; y < height; y++)
140+
{
141+
stream.Read(rowSpan);
142+
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
143+
PixelOperations<TPixel>.Instance.FromRgb48Bytes(
144+
configuration,
145+
rowSpan,
146+
pixelSpan,
147+
width);
148+
}
149+
}
150+
151+
private static void ProcessBlackAndWhite<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream)
152+
where TPixel : unmanaged, IPixel<TPixel>
153+
{
154+
int width = pixels.Width;
155+
int height = pixels.Height;
156+
int startBit = 0;
157+
MemoryAllocator allocator = configuration.MemoryAllocator;
158+
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width);
159+
Span<L8> rowSpan = row.GetSpan();
160+
161+
for (int y = 0; y < height; y++)
162+
{
163+
for (int x = 0; x < width;)
164+
{
165+
int raw = stream.ReadByte();
166+
int bit = startBit;
167+
startBit = 0;
168+
for (; bit < 8; bit++)
169+
{
170+
bool bitValue = (raw & (0x80 >> bit)) != 0;
171+
rowSpan[x] = bitValue ? black : white;
172+
x++;
173+
if (x == width)
174+
{
175+
startBit = (bit + 1) & 7; // Round off to below 8.
176+
if (startBit != 0)
177+
{
178+
stream.Seek(-1, System.IO.SeekOrigin.Current);
179+
}
180+
181+
break;
182+
}
183+
}
184+
}
185+
186+
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
187+
PixelOperations<TPixel>.Instance.FromL8(
188+
configuration,
189+
rowSpan,
190+
pixelSpan);
191+
}
192+
}
193+
}
194+
}

0 commit comments

Comments
 (0)