@ -2,6 +2,8 @@
// Licensed under the Six Labors Split License.
using System.Buffers ;
using System.Buffers.Binary ;
using System.Runtime.InteropServices ;
using SixLabors.ImageSharp.IO ;
using SixLabors.ImageSharp.Memory ;
using SixLabors.ImageSharp.PixelFormats ;
@ -13,21 +15,25 @@ namespace SixLabors.ImageSharp.Formats.Pbm;
/// </summary>
internal class BinaryDecoder
{
/// <summary>
/// The luminance value written for an unset bit in the black and white format.
/// </summary>
private static L8 white = new ( 2 5 5 ) ;
/// <summary>
/// The luminance value written for a set bit in the black and white format.
/// </summary>
private static L8 black = new ( 0 ) ;
/// <summary>
/// Decode the specified pixels.
/// </summary>
/// <typeparam name="TPixel">The type of pixel to en code to.</typeparam>
/// <typeparam name="TPixel">The type of pixel to d ecode to.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="pixels">The pixel array to en code into.</param>
/// <param name="pixels">The pixel buffer to de code 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>
/// <param name="colorType">The color type of the encoded pixels.</param>
/// <param name="componentType">The data type of the pixel components.</param>
public static void Process < TPixel > ( Configuration configuration , Buffer2D < TPixel > pixels , BufferedReadStream stream , PbmColorType colorType , PbmComponentType componentType )
where TPixel : unmanaged , IPixel < TPixel >
{
@ -59,6 +65,15 @@ internal class BinaryDecoder
}
}
/// <summary>
/// Decodes 8-bit binary grayscale (PGM) pixel data.
/// Each pixel is a single byte that holds its luminance value.
/// When the stream ends early, the rows that were not read keep their default value.
/// </summary>
/// <typeparam name="TPixel">The type of pixel to decode to.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="pixels">The pixel buffer to decode into.</param>
/// <param name="stream">The stream to read the data from.</param>
private static void ProcessGrayscale < TPixel > ( Configuration configuration , Buffer2D < TPixel > pixels , BufferedReadStream stream )
where TPixel : unmanaged , IPixel < TPixel >
{
@ -85,6 +100,15 @@ internal class BinaryDecoder
}
}
/// <summary>
/// Decodes 16-bit binary grayscale (PGM) pixel data.
/// Each pixel is one 16-bit sample, stored most significant byte first.
/// When the stream ends early, the rows that were not read keep their default value.
/// </summary>
/// <typeparam name="TPixel">The type of pixel to decode to.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="pixels">The pixel buffer to decode into.</param>
/// <param name="stream">The stream to read the data from.</param>
private static void ProcessWideGrayscale < TPixel > ( Configuration configuration , Buffer2D < TPixel > pixels , BufferedReadStream stream )
where TPixel : unmanaged , IPixel < TPixel >
{
@ -102,6 +126,10 @@ internal class BinaryDecoder
return ;
}
// The binary format stores 16-bit samples most significant byte first,
// but L16 expects native (little-endian) byte order.
SwapSampleBytes ( rowSpan ) ;
Span < TPixel > pixelSpan = pixels . DangerousGetRowSpan ( y ) ;
PixelOperations < TPixel > . Instance . FromL16Bytes (
configuration ,
@ -111,6 +139,15 @@ internal class BinaryDecoder
}
}
/// <summary>
/// Decodes 8-bit binary color (PPM) pixel data.
/// Each pixel is three bytes in red, green, blue order.
/// When the stream ends early, the rows that were not read keep their default value.
/// </summary>
/// <typeparam name="TPixel">The type of pixel to decode to.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="pixels">The pixel buffer to decode into.</param>
/// <param name="stream">The stream to read the data from.</param>
private static void ProcessRgb < TPixel > ( Configuration configuration , Buffer2D < TPixel > pixels , BufferedReadStream stream )
where TPixel : unmanaged , IPixel < TPixel >
{
@ -137,6 +174,15 @@ internal class BinaryDecoder
}
}
/// <summary>
/// Decodes 16-bit binary color (PPM) pixel data.
/// Each pixel is three 16-bit samples in red, green, blue order, stored most significant byte first.
/// When the stream ends early, the rows that were not read keep their default value.
/// </summary>
/// <typeparam name="TPixel">The type of pixel to decode to.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="pixels">The pixel buffer to decode into.</param>
/// <param name="stream">The stream to read the data from.</param>
private static void ProcessWideRgb < TPixel > ( Configuration configuration , Buffer2D < TPixel > pixels , BufferedReadStream stream )
where TPixel : unmanaged , IPixel < TPixel >
{
@ -154,6 +200,10 @@ internal class BinaryDecoder
return ;
}
// The binary format stores 16-bit samples most significant byte first,
// but Rgb48 expects native (little-endian) byte order.
SwapSampleBytes ( rowSpan ) ;
Span < TPixel > pixelSpan = pixels . DangerousGetRowSpan ( y ) ;
PixelOperations < TPixel > . Instance . FromRgb48Bytes (
configuration ,
@ -163,6 +213,30 @@ internal class BinaryDecoder
}
}
/// <summary>
/// Reverses the byte order of each 16-bit sample in the given row when the host is little-endian.
/// The binary PGM and PPM formats store multi-byte samples most significant byte first.
/// </summary>
/// <param name="rowSpan">The row of big-endian sample data to convert in place.</param>
private static void SwapSampleBytes ( Span < byte > rowSpan )
{
if ( BitConverter . IsLittleEndian )
{
Span < ushort > samples = MemoryMarshal . Cast < byte , ushort > ( rowSpan ) ;
BinaryPrimitives . ReverseEndianness ( samples , samples ) ;
}
}
/// <summary>
/// Decodes binary black and white (PBM) pixel data.
/// Each byte holds eight pixels, most significant bit first, and a set bit means black.
/// Each row starts on a byte boundary, so the last byte of a row can hold unused bits.
/// When the stream ends early, the pixels that were not read keep their default value.
/// </summary>
/// <typeparam name="TPixel">The type of pixel to decode to.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="pixels">The pixel buffer to decode into.</param>
/// <param name="stream">The stream to read the data from.</param>
private static void ProcessBlackAndWhite < TPixel > ( Configuration configuration , Buffer2D < TPixel > pixels , BufferedReadStream stream )
where TPixel : unmanaged , IPixel < TPixel >
{