Browse Source

Process first round of review comments

pull/1851/head
Ynse Hoornenborg 5 years ago
parent
commit
0c8c892647
  1. 2
      src/ImageSharp/Formats/Pbm/BinaryDecoder.cs
  2. 22
      src/ImageSharp/Formats/Pbm/BinaryEncoder.cs
  3. 2
      src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs
  4. 4
      src/ImageSharp/Formats/Pbm/PbmConstants.cs
  5. 18
      src/ImageSharp/Formats/Pbm/PbmDecoder.cs
  6. 4
      src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs
  7. 23
      src/ImageSharp/Formats/Pbm/PbmEncoder.cs
  8. 58
      src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs
  9. 7
      src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs
  10. 129
      src/ImageSharp/Formats/Pbm/PlainEncoder.cs
  11. 19
      src/ImageSharp/Formats/Pbm/StreamExtensions.cs
  12. 55
      tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs
  13. 2
      tests/ImageSharp.Tests/TestImages.cs
  14. 3
      tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png
  15. 3
      tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png
  16. 3
      tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png
  17. 3
      tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png
  18. 3
      tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png
  19. 3
      tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png
  20. 3
      tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png
  21. 10
      tests/Images/Input/Pbm/grayscale_plain_normalized.pgm
  22. 8
      tests/Images/Input/Pbm/rgb_plain_normalized.ppm

2
src/ImageSharp/Formats/Pbm/BinaryDecoder.cs

@ -171,7 +171,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm
x++; x++;
if (x == width) if (x == width)
{ {
startBit = (bit + 1) % 8; startBit = (bit + 1) & 7; // Round off to below 8.
if (startBit != 0) if (startBit != 0)
{ {
stream.Seek(-1, System.IO.SeekOrigin.Current); stream.Seek(-1, System.IO.SeekOrigin.Current);

22
src/ImageSharp/Formats/Pbm/BinaryEncoder.cs

@ -60,8 +60,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm
private static void WriteGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) private static void WriteGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = image.Size().Width; int width = image.Width;
int height = image.Size().Height; int height = image.Height;
MemoryAllocator allocator = configuration.MemoryAllocator; MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width); using IMemoryOwner<byte> row = allocator.Allocate<byte>(width);
Span<byte> rowSpan = row.GetSpan(); Span<byte> rowSpan = row.GetSpan();
@ -83,8 +83,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm
private static void WriteWideGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) private static void WriteWideGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = image.Size().Width; int width = image.Width;
int height = image.Size().Height; int height = image.Height;
int bytesPerPixel = 2; int bytesPerPixel = 2;
MemoryAllocator allocator = configuration.MemoryAllocator; MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel);
@ -107,8 +107,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm
private static void WriteRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) private static void WriteRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = image.Size().Width; int width = image.Width;
int height = image.Size().Height; int height = image.Height;
int bytesPerPixel = 3; int bytesPerPixel = 3;
MemoryAllocator allocator = configuration.MemoryAllocator; MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel);
@ -131,8 +131,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm
private static void WriteWideRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) private static void WriteWideRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = image.Size().Width; int width = image.Width;
int height = image.Size().Height; int height = image.Height;
int bytesPerPixel = 6; int bytesPerPixel = 6;
MemoryAllocator allocator = configuration.MemoryAllocator; MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel);
@ -155,8 +155,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm
private static void WriteBlackAndWhite<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) private static void WriteBlackAndWhite<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = image.Size().Width; int width = image.Width;
int height = image.Size().Height; int height = image.Height;
MemoryAllocator allocator = configuration.MemoryAllocator; MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width); using IMemoryOwner<L8> row = allocator.Allocate<L8>(width);
Span<L8> rowSpan = row.GetSpan(); Span<L8> rowSpan = row.GetSpan();
@ -186,7 +186,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm
if (x == width) if (x == width)
{ {
previousValue = value; previousValue = value;
startBit = (i + 1) % 8; startBit = (i + 1) & 7; // Round off to below 8.
break; break;
} }
} }

2
src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs

@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm
while (true) while (true)
{ {
int current = stream.ReadByte() - 0x30; int current = stream.ReadByte() - 0x30;
if (current < 0 || current > 9) if ((uint)current > 9)
{ {
break; break;
} }

4
src/ImageSharp/Formats/Pbm/PbmConstants.cs

@ -18,11 +18,11 @@ namespace SixLabors.ImageSharp.Formats.Pbm
/// <summary> /// <summary>
/// The list of mimetypes that equate to a ppm. /// The list of mimetypes that equate to a ppm.
/// </summary> /// </summary>
public static readonly IEnumerable<string> MimeTypes = new[] { "image/x-portable-pixmap", "image/x-portable-anymap", "image/x-portable-arbitrarymap" }; public static readonly IEnumerable<string> MimeTypes = new[] { "image/x-portable-pixmap", "image/x-portable-anymap" };
/// <summary> /// <summary>
/// The list of file extensions that equate to a ppm. /// The list of file extensions that equate to a ppm.
/// </summary> /// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "ppm", "pbm", "pgm", "pam" }; public static readonly IEnumerable<string> FileExtensions = new[] { "ppm", "pbm", "pgm" };
} }
} }

18
src/ImageSharp/Formats/Pbm/PbmDecoder.cs

@ -9,7 +9,23 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Pbm namespace SixLabors.ImageSharp.Formats.Pbm
{ {
/// <summary> /// <summary>
/// Image decoder for generating an image out of a ppm stream. /// 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> /// </summary>
public sealed class PbmDecoder : IImageDecoder, IImageInfoDetector public sealed class PbmDecoder : IImageDecoder, IImageInfoDetector
{ {

4
src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs

@ -82,9 +82,9 @@ namespace SixLabors.ImageSharp.Formats.Pbm
/// <param name="stream">The input stream.</param> /// <param name="stream">The input stream.</param>
private void ProcessHeader(BufferedReadStream stream) private void ProcessHeader(BufferedReadStream stream)
{ {
byte[] buffer = new byte[2]; Span<byte> buffer = stackalloc byte[2];
int bytesRead = stream.Read(buffer, 0, 2); int bytesRead = stream.Read(buffer);
if (bytesRead != 2 || buffer[0] != 'P') if (bytesRead != 2 || buffer[0] != 'P')
{ {
// Empty or not an PPM image. // Empty or not an PPM image.

23
src/ImageSharp/Formats/Pbm/PbmEncoder.cs

@ -10,7 +10,28 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Pbm namespace SixLabors.ImageSharp.Formats.Pbm
{ {
/// <summary> /// <summary>
/// Image encoder for writing an image to a stream as PGM, PBM, PPM or PAM bitmap. /// 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 faily 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 seperated 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> /// </summary>
public sealed class PbmEncoder : IImageEncoder, IPbmEncoderOptions public sealed class PbmEncoder : IImageEncoder, IPbmEncoderOptions
{ {

58
src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs

@ -2,12 +2,10 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers; using System.Buffers.Text;
using System.IO; using System.IO;
using System.Text;
using System.Threading; using System.Threading;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Pbm namespace SixLabors.ImageSharp.Formats.Pbm
@ -17,7 +15,9 @@ namespace SixLabors.ImageSharp.Formats.Pbm
/// </summary> /// </summary>
internal sealed class PbmEncoderCore : IImageEncoderInternals internal sealed class PbmEncoderCore : IImageEncoderInternals
{ {
private const char NewLine = '\n'; private const byte NewLine = (byte)'\n';
private const byte Space = (byte)' ';
private const byte P = (byte)'P';
/// <summary> /// <summary>
/// The global configuration. /// The global configuration.
@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm
this.DeduceOptions(image); this.DeduceOptions(image);
string signature = this.DeduceSignature(); byte signature = this.DeduceSignature();
this.WriteHeader(stream, signature, image.Size()); this.WriteHeader(stream, signature, image.Size());
this.WritePixels(stream, image.Frames.RootFrame); this.WritePixels(stream, image.Frames.RootFrame);
@ -91,29 +91,29 @@ namespace SixLabors.ImageSharp.Formats.Pbm
} }
} }
private string DeduceSignature() private byte DeduceSignature()
{ {
string signature; byte signature;
if (this.colorType == PbmColorType.BlackAndWhite) if (this.colorType == PbmColorType.BlackAndWhite)
{ {
if (this.encoding == PbmEncoding.Plain) if (this.encoding == PbmEncoding.Plain)
{ {
signature = "P1"; signature = (byte)'1';
} }
else else
{ {
signature = "P4"; signature = (byte)'4';
} }
} }
else if (this.colorType == PbmColorType.Grayscale) else if (this.colorType == PbmColorType.Grayscale)
{ {
if (this.encoding == PbmEncoding.Plain) if (this.encoding == PbmEncoding.Plain)
{ {
signature = "P2"; signature = (byte)'2';
} }
else else
{ {
signature = "P5"; signature = (byte)'5';
} }
} }
else else
@ -121,35 +121,41 @@ namespace SixLabors.ImageSharp.Formats.Pbm
// RGB ColorType // RGB ColorType
if (this.encoding == PbmEncoding.Plain) if (this.encoding == PbmEncoding.Plain)
{ {
signature = "P3"; signature = (byte)'3';
} }
else else
{ {
signature = "P6"; signature = (byte)'6';
} }
} }
return signature; return signature;
} }
private void WriteHeader(Stream stream, string signature, Size pixelSize) private void WriteHeader(Stream stream, byte signature, Size pixelSize)
{ {
var builder = new StringBuilder(20); Span<byte> buffer = stackalloc byte[128];
builder.Append(signature);
builder.Append(NewLine); int written = 3;
builder.Append(pixelSize.Width.ToString()); buffer[0] = P;
builder.Append(NewLine); buffer[1] = signature;
builder.Append(pixelSize.Height.ToString()); buffer[2] = NewLine;
builder.Append(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) if (this.colorType != PbmColorType.BlackAndWhite)
{ {
builder.Append(this.maxPixelValue.ToString()); Utf8Formatter.TryFormat(this.maxPixelValue, buffer.Slice(written), out bytesWritten);
builder.Append(NewLine); written += bytesWritten;
buffer[written++] = NewLine;
} }
string headerStr = builder.ToString(); stream.Write(buffer, 0, written);
byte[] headerBytes = Encoding.ASCII.GetBytes(headerStr);
stream.Write(headerBytes, 0, headerBytes.Length);
} }
/// <summary> /// <summary>

7
src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs

@ -22,9 +22,12 @@ namespace SixLabors.ImageSharp.Formats.Pbm
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header) private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
{ {
if (header.Length >= this.HeaderSize) #pragma warning disable SA1131 // Use readable conditions
if (1 < (uint)header.Length)
#pragma warning restore SA1131 // Use readable conditions
{ {
return header[0] == P && header[1] > Zero && header[1] < Seven; // Signature should be between P1 and P6.
return header[0] == P && (uint)(header[1] - Zero - 1) < (Seven - Zero - 1);
} }
return false; return false;

129
src/ImageSharp/Formats/Pbm/PlainEncoder.cs

@ -3,6 +3,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Buffers.Text;
using System.IO; using System.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -20,6 +21,14 @@ namespace SixLabors.ImageSharp.Formats.Pbm
private const byte Zero = 0x30; private const byte Zero = 0x30;
private const byte One = 0x31; 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> /// <summary>
/// Decode pixels into the PBM plain encoding. /// Decode pixels into the PBM plain encoding.
/// </summary> /// </summary>
@ -63,12 +72,12 @@ namespace SixLabors.ImageSharp.Formats.Pbm
private static void WriteGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) private static void WriteGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = image.Size().Width; int width = image.Width;
int height = image.Size().Height; int height = image.Height;
int bytesWritten = -1;
MemoryAllocator allocator = configuration.MemoryAllocator; MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width); using IMemoryOwner<L8> row = allocator.Allocate<L8>(width);
Span<L8> rowSpan = row.GetSpan(); Span<L8> rowSpan = row.GetSpan();
Span<byte> plainSpan = stackalloc byte[width * MaxCharsPerPixelGrayscale];
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
@ -78,23 +87,28 @@ namespace SixLabors.ImageSharp.Formats.Pbm
pixelSpan, pixelSpan,
rowSpan); rowSpan);
int written = 0;
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
WriteWhitespace(stream, ref bytesWritten); Utf8Formatter.TryFormat(rowSpan[x].PackedValue, plainSpan.Slice(written), out int bytesWritten, DecimalFormat);
bytesWritten += stream.WriteDecimal(rowSpan[x].PackedValue); 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) private static void WriteWideGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = image.Size().Width; int width = image.Width;
int height = image.Size().Height; int height = image.Height;
int bytesWritten = -1;
MemoryAllocator allocator = configuration.MemoryAllocator; MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<L16> row = allocator.Allocate<L16>(width); using IMemoryOwner<L16> row = allocator.Allocate<L16>(width);
Span<L16> rowSpan = row.GetSpan(); Span<L16> rowSpan = row.GetSpan();
Span<byte> plainSpan = stackalloc byte[width * MaxCharsPerPixelGrayscaleWide];
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
@ -104,23 +118,28 @@ namespace SixLabors.ImageSharp.Formats.Pbm
pixelSpan, pixelSpan,
rowSpan); rowSpan);
int written = 0;
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
WriteWhitespace(stream, ref bytesWritten); Utf8Formatter.TryFormat(rowSpan[x].PackedValue, plainSpan.Slice(written), out int bytesWritten, DecimalFormat);
bytesWritten += stream.WriteDecimal(rowSpan[x].PackedValue); 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) private static void WriteRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = image.Size().Width; int width = image.Width;
int height = image.Size().Height; int height = image.Height;
int bytesWritten = -1;
MemoryAllocator allocator = configuration.MemoryAllocator; MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<Rgb24> row = allocator.Allocate<Rgb24>(width); using IMemoryOwner<Rgb24> row = allocator.Allocate<Rgb24>(width);
Span<Rgb24> rowSpan = row.GetSpan(); Span<Rgb24> rowSpan = row.GetSpan();
Span<byte> plainSpan = stackalloc byte[width * MaxCharsPerPixelRgb];
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
@ -130,27 +149,34 @@ namespace SixLabors.ImageSharp.Formats.Pbm
pixelSpan, pixelSpan,
rowSpan); rowSpan);
int written = 0;
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
WriteWhitespace(stream, ref bytesWritten); Utf8Formatter.TryFormat(rowSpan[x].R, plainSpan.Slice(written), out int bytesWritten, DecimalFormat);
bytesWritten += stream.WriteDecimal(rowSpan[x].R); written += bytesWritten;
WriteWhitespace(stream, ref bytesWritten); plainSpan[written++] = Space;
bytesWritten += stream.WriteDecimal(rowSpan[x].G); Utf8Formatter.TryFormat(rowSpan[x].G, plainSpan.Slice(written), out bytesWritten, DecimalFormat);
WriteWhitespace(stream, ref bytesWritten); written += bytesWritten;
bytesWritten += stream.WriteDecimal(rowSpan[x].B); 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) private static void WriteWideRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = image.Size().Width; int width = image.Width;
int height = image.Size().Height; int height = image.Height;
int bytesWritten = -1;
MemoryAllocator allocator = configuration.MemoryAllocator; MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<Rgb48> row = allocator.Allocate<Rgb48>(width); using IMemoryOwner<Rgb48> row = allocator.Allocate<Rgb48>(width);
Span<Rgb48> rowSpan = row.GetSpan(); Span<Rgb48> rowSpan = row.GetSpan();
Span<byte> plainSpan = stackalloc byte[width * MaxCharsPerPixelRgbWide];
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
@ -160,27 +186,34 @@ namespace SixLabors.ImageSharp.Formats.Pbm
pixelSpan, pixelSpan,
rowSpan); rowSpan);
int written = 0;
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
WriteWhitespace(stream, ref bytesWritten); Utf8Formatter.TryFormat(rowSpan[x].R, plainSpan.Slice(written), out int bytesWritten, DecimalFormat);
bytesWritten += stream.WriteDecimal(rowSpan[x].R); written += bytesWritten;
WriteWhitespace(stream, ref bytesWritten); plainSpan[written++] = Space;
bytesWritten += stream.WriteDecimal(rowSpan[x].G); Utf8Formatter.TryFormat(rowSpan[x].G, plainSpan.Slice(written), out bytesWritten, DecimalFormat);
WriteWhitespace(stream, ref bytesWritten); written += bytesWritten;
bytesWritten += stream.WriteDecimal(rowSpan[x].B); 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) private static void WriteBlackAndWhite<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = image.Size().Width; int width = image.Width;
int height = image.Size().Height; int height = image.Height;
int bytesWritten = -1;
MemoryAllocator allocator = configuration.MemoryAllocator; MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width); using IMemoryOwner<L8> row = allocator.Allocate<L8>(width);
Span<L8> rowSpan = row.GetSpan(); Span<L8> rowSpan = row.GetSpan();
Span<byte> plainSpan = stackalloc byte[width * MaxCharsPerPixelBlackAndWhite];
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
@ -190,38 +223,16 @@ namespace SixLabors.ImageSharp.Formats.Pbm
pixelSpan, pixelSpan,
rowSpan); rowSpan);
int written = 0;
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
WriteWhitespace(stream, ref bytesWritten); byte value = (rowSpan[x].PackedValue > 127) ? Zero : One;
if (rowSpan[x].PackedValue > 127) plainSpan[written++] = value;
{ plainSpan[written++] = Space;
stream.WriteByte(Zero);
}
else
{
stream.WriteByte(One);
}
bytesWritten++;
} }
}
}
private static void WriteWhitespace(Stream stream, ref int bytesWritten) plainSpan[written - 1] = NewLine;
{ stream.Write(plainSpan, 0, written);
if (bytesWritten > MaxLineLength)
{
stream.WriteByte(NewLine);
bytesWritten = 1;
}
else if (bytesWritten == -1)
{
bytesWritten = 0;
}
else
{
stream.WriteByte(Space);
bytesWritten++;
} }
} }
} }

19
src/ImageSharp/Formats/Pbm/StreamExtensions.cs

@ -1,19 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Text;
namespace SixLabors.ImageSharp.Formats.Pbm
{
internal static class StreamExtensions
{
public static int WriteDecimal(this Stream stream, int value)
{
string str = value.ToString();
byte[] bytes = Encoding.ASCII.GetBytes(str);
stream.Write(bytes);
return bytes.Length;
}
}
}

55
tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs

@ -3,7 +3,8 @@
using System.IO; using System.IO;
using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Xunit; using Xunit;
using static SixLabors.ImageSharp.Tests.TestImages.Pbm; using static SixLabors.ImageSharp.Tests.TestImages.Pbm;
@ -21,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm
[InlineData(GrayscaleBinaryWide, PbmColorType.Grayscale)] [InlineData(GrayscaleBinaryWide, PbmColorType.Grayscale)]
[InlineData(RgbPlain, PbmColorType.Rgb)] [InlineData(RgbPlain, PbmColorType.Rgb)]
[InlineData(RgbBinary, PbmColorType.Rgb)] [InlineData(RgbBinary, PbmColorType.Rgb)]
public void PpmDecoder_CanDecode(string imagePath, PbmColorType expectedColorType) public void ImageLoadCanDecode(string imagePath, PbmColorType expectedColorType)
{ {
// Arrange // Arrange
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
@ -36,5 +37,55 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm
Assert.NotNull(bitmapMetadata); Assert.NotNull(bitmapMetadata);
Assert.Equal(expectedColorType, bitmapMetadata.ColorType); Assert.Equal(expectedColorType, bitmapMetadata.ColorType);
} }
[Theory]
[InlineData(BlackAndWhitePlain)]
[InlineData(BlackAndWhiteBinary)]
[InlineData(GrayscalePlain)]
[InlineData(GrayscaleBinary)]
[InlineData(GrayscaleBinaryWide)]
public void ImageLoadL8CanDecode(string imagePath)
{
// Arrange
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
// Act
using var image = Image.Load<L8>(stream);
// Assert
Assert.NotNull(image);
}
[Theory]
[InlineData(RgbPlain)]
[InlineData(RgbBinary)]
public void ImageLoadRgb24CanDecode(string imagePath)
{
// Arrange
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
// Act
using var image = Image.Load<Rgb24>(stream);
// Assert
Assert.NotNull(image);
}
[Theory]
[WithFile(BlackAndWhiteBinary, PixelTypes.L8, true)]
[WithFile(GrayscalePlainNormalized, PixelTypes.L8, true)]
[WithFile(GrayscaleBinary, PixelTypes.L8, true)]
[WithFile(GrayscaleBinaryWide, PixelTypes.L16, true)]
[WithFile(RgbPlainNormalized, PixelTypes.Rgb24, false)]
[WithFile(RgbBinary, PixelTypes.Rgb24, false)]
public void DecodeReferenceImage<TPixel>(TestImageProvider<TPixel> provider, bool isGrayscale)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
image.CompareToReferenceOutput(provider, grayscale: isGrayscale);
}
} }
} }

2
tests/ImageSharp.Tests/TestImages.cs

@ -874,8 +874,10 @@ namespace SixLabors.ImageSharp.Tests
public const string GrayscaleBinary = "Pbm/rings.pgm"; public const string GrayscaleBinary = "Pbm/rings.pgm";
public const string GrayscaleBinaryWide = "Pbm/Gene-UP WebSocket RunImageMask.pgm"; public const string GrayscaleBinaryWide = "Pbm/Gene-UP WebSocket RunImageMask.pgm";
public const string GrayscalePlain = "Pbm/grayscale_plain.pgm"; public const string GrayscalePlain = "Pbm/grayscale_plain.pgm";
public const string GrayscalePlainNormalized = "Pbm/grayscale_plain_normalized.pgm";
public const string RgbBinary = "Pbm/00000_00000.ppm"; public const string RgbBinary = "Pbm/00000_00000.ppm";
public const string RgbPlain = "Pbm/rgb_plain.ppm"; public const string RgbPlain = "Pbm/rgb_plain.ppm";
public const string RgbPlainNormalized = "Pbm/rgb_plain_normalized.ppm";
} }
} }
} }

3
tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:78fc668be9f82c01c277cb2560253b04a1ff74a5af4daaf19327591420a71fec
size 4521

3
tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1339a8170408a7bcde261617cc599587c8f25c4dc94f780976ee1638879888e9
size 147

3
tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:82d0397f38971cf90d7c064db332093e686196e244ece1196cca2071d27f0a6f
size 147

3
tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f8e8b8a1a05e76b1eeb577373c3a6f492e356f0dd58489afded248415cec4a07
size 145

3
tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:388c86b3dd472ef17fb911ae424b81baeeeff74c4161cf5825eab50698d54348
size 27884

3
tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2e3fc46b9f0546941ef95be7b750fb29376a679a921f2581403882b0e76e9caf
size 2250

3
tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c44322c4bf461acea27053057f5241afb029d9a1e66e94dcf1be6f86f7f97727
size 152

10
tests/Images/Input/Pbm/grayscale_plain_normalized.pgm

@ -0,0 +1,10 @@
P2
24 7
255
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 51 51 51 51 0 0 119 119 119 119 0 0 187 187 187 187 0 0 255 255 255 255 0
0 51 0 0 0 0 0 119 0 0 0 0 0 187 0 0 0 0 0 255 0 0 255 0
0 51 51 51 0 0 0 119 119 119 0 0 0 187 187 187 0 0 0 255 255 255 255 0
0 51 0 0 0 0 0 119 0 0 0 0 0 187 0 0 0 0 0 255 0 0 0 0
0 51 0 0 0 0 0 119 119 119 119 0 0 187 187 187 187 0 0 255 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

8
tests/Images/Input/Pbm/rgb_plain_normalized.ppm

@ -0,0 +1,8 @@
P3
# example from the man page
4 4
255
0 0 0 0 0 0 0 0 0 255 0 255
0 0 0 0 255 119 0 0 0 0 0 0
0 0 0 0 0 0 0 255 119 0 0 0
255 0 255 0 0 0 0 0 0 0 0 0
Loading…
Cancel
Save