mirror of https://github.com/SixLabors/ImageSharp
43 changed files with 2332 additions and 2 deletions
@ -0,0 +1,192 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Pixel decoding methods for the PBM binary encoding.
|
|||
/// </summary>
|
|||
internal class BinaryDecoder |
|||
{ |
|||
/// <summary>
|
|||
/// Decode the specified pixels.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of pixel to encode to.</typeparam>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
/// <param name="pixels">The pixel array to encode into.</param>
|
|||
/// <param name="stream">The stream to read the data from.</param>
|
|||
/// <param name="colorType">The ColorType to decode.</param>
|
|||
/// <param name="maxPixelValue">The maximum expected pixel value</param>
|
|||
/// <exception cref="InvalidImageContentException">
|
|||
/// Thrown if an invalid combination of setting is requested.
|
|||
/// </exception>
|
|||
public static void Process<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream, PbmColorType colorType, int maxPixelValue) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
if (colorType == PbmColorType.Grayscale) |
|||
{ |
|||
if (maxPixelValue < 256) |
|||
{ |
|||
ProcessGrayscale(configuration, pixels, stream); |
|||
} |
|||
else |
|||
{ |
|||
ProcessWideGrayscale(configuration, pixels, stream); |
|||
} |
|||
} |
|||
else if (colorType == PbmColorType.Rgb) |
|||
{ |
|||
if (maxPixelValue < 256) |
|||
{ |
|||
ProcessRgb(configuration, pixels, stream); |
|||
} |
|||
else |
|||
{ |
|||
ProcessWideRgb(configuration, pixels, stream); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
ProcessBlackAndWhite(configuration, pixels, stream); |
|||
} |
|||
} |
|||
|
|||
private static void ProcessGrayscale<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = pixels.Width; |
|||
int height = pixels.Height; |
|||
int bytesPerPixel = 1; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
|||
Span<byte> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
stream.Read(rowSpan); |
|||
Span<TPixel> pixelSpan = pixels.GetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.FromL8Bytes( |
|||
configuration, |
|||
rowSpan, |
|||
pixelSpan, |
|||
width); |
|||
} |
|||
} |
|||
|
|||
private static void ProcessWideGrayscale<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = pixels.Width; |
|||
int height = pixels.Height; |
|||
int bytesPerPixel = 2; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
|||
Span<byte> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
stream.Read(rowSpan); |
|||
Span<TPixel> pixelSpan = pixels.GetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.FromL16Bytes( |
|||
configuration, |
|||
rowSpan, |
|||
pixelSpan, |
|||
width); |
|||
} |
|||
} |
|||
|
|||
private static void ProcessRgb<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = pixels.Width; |
|||
int height = pixels.Height; |
|||
int bytesPerPixel = 3; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
|||
Span<byte> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
stream.Read(rowSpan); |
|||
Span<TPixel> pixelSpan = pixels.GetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.FromRgb24Bytes( |
|||
configuration, |
|||
rowSpan, |
|||
pixelSpan, |
|||
width); |
|||
} |
|||
} |
|||
|
|||
private static void ProcessWideRgb<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = pixels.Width; |
|||
int height = pixels.Height; |
|||
int bytesPerPixel = 6; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
|||
Span<byte> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
stream.Read(rowSpan); |
|||
Span<TPixel> pixelSpan = pixels.GetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.FromRgb48Bytes( |
|||
configuration, |
|||
rowSpan, |
|||
pixelSpan, |
|||
width); |
|||
} |
|||
} |
|||
|
|||
private static void ProcessBlackAndWhite<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = pixels.Width; |
|||
int height = pixels.Height; |
|||
int startBit = 0; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width); |
|||
Span<L8> rowSpan = row.GetSpan(); |
|||
var white = new L8(255); |
|||
var black = new L8(0); |
|||
|
|||
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) % 8; |
|||
if (startBit != 0) |
|||
{ |
|||
stream.Seek(-1, System.IO.SeekOrigin.Current); |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
Span<TPixel> pixelSpan = pixels.GetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.FromL8( |
|||
configuration, |
|||
rowSpan, |
|||
pixelSpan); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,203 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Pixel encoding methods for the PBM binary encoding.
|
|||
/// </summary>
|
|||
internal class BinaryEncoder |
|||
{ |
|||
/// <summary>
|
|||
/// Decode pixels into the PBM binary encoding.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of input pixel.</typeparam>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
/// <param name="stream">The bytestream to write to.</param>
|
|||
/// <param name="image">The input image.</param>
|
|||
/// <param name="colorType">The ColorType to use.</param>
|
|||
/// <param name="maxPixelValue">The maximum expected pixel value</param>
|
|||
/// <exception cref="InvalidImageContentException">
|
|||
/// Thrown if an invalid combination of setting is requested.
|
|||
/// </exception>
|
|||
public static void WritePixels<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image, PbmColorType colorType, int maxPixelValue) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
if (colorType == PbmColorType.Grayscale) |
|||
{ |
|||
if (maxPixelValue < 256) |
|||
{ |
|||
WriteGrayscale(configuration, stream, image); |
|||
} |
|||
else |
|||
{ |
|||
WriteWideGrayscale(configuration, stream, image); |
|||
} |
|||
} |
|||
else if (colorType == PbmColorType.Rgb) |
|||
{ |
|||
if (maxPixelValue < 256) |
|||
{ |
|||
WriteRgb(configuration, stream, image); |
|||
} |
|||
else |
|||
{ |
|||
WriteWideRgb(configuration, stream, image); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
WriteBlackAndWhite(configuration, stream, image); |
|||
} |
|||
} |
|||
|
|||
private static void WriteGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = image.Size().Width; |
|||
int height = image.Size().Height; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width); |
|||
Span<byte> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<TPixel> pixelSpan = image.GetPixelRowSpan(y); |
|||
|
|||
PixelOperations<TPixel>.Instance.ToL8Bytes( |
|||
configuration, |
|||
pixelSpan, |
|||
rowSpan, |
|||
width); |
|||
|
|||
stream.Write(rowSpan); |
|||
} |
|||
} |
|||
|
|||
private static void WriteWideGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = image.Size().Width; |
|||
int height = image.Size().Height; |
|||
int bytesPerPixel = 2; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
|||
Span<byte> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<TPixel> pixelSpan = image.GetPixelRowSpan(y); |
|||
|
|||
PixelOperations<TPixel>.Instance.ToL16Bytes( |
|||
configuration, |
|||
pixelSpan, |
|||
rowSpan, |
|||
width); |
|||
|
|||
stream.Write(rowSpan); |
|||
} |
|||
} |
|||
|
|||
private static void WriteRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = image.Size().Width; |
|||
int height = image.Size().Height; |
|||
int bytesPerPixel = 3; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
|||
Span<byte> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<TPixel> pixelSpan = image.GetPixelRowSpan(y); |
|||
|
|||
PixelOperations<TPixel>.Instance.ToRgb24Bytes( |
|||
configuration, |
|||
pixelSpan, |
|||
rowSpan, |
|||
width); |
|||
|
|||
stream.Write(rowSpan); |
|||
} |
|||
} |
|||
|
|||
private static void WriteWideRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = image.Size().Width; |
|||
int height = image.Size().Height; |
|||
int bytesPerPixel = 6; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
|||
Span<byte> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<TPixel> pixelSpan = image.GetPixelRowSpan(y); |
|||
|
|||
PixelOperations<TPixel>.Instance.ToRgb48Bytes( |
|||
configuration, |
|||
pixelSpan, |
|||
rowSpan, |
|||
width); |
|||
|
|||
stream.Write(rowSpan); |
|||
} |
|||
} |
|||
|
|||
private static void WriteBlackAndWhite<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = image.Size().Width; |
|||
int height = image.Size().Height; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width); |
|||
Span<L8> rowSpan = row.GetSpan(); |
|||
|
|||
int previousValue = 0; |
|||
int startBit = 0; |
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<TPixel> pixelSpan = image.GetPixelRowSpan(y); |
|||
|
|||
PixelOperations<TPixel>.Instance.ToL8( |
|||
configuration, |
|||
pixelSpan, |
|||
rowSpan); |
|||
|
|||
for (int x = 0; x < width;) |
|||
{ |
|||
int value = previousValue; |
|||
for (int i = startBit; i < 8; i++) |
|||
{ |
|||
if (rowSpan[x].PackedValue < 128) |
|||
{ |
|||
value |= 0x80 >> i; |
|||
} |
|||
|
|||
x++; |
|||
if (x == width) |
|||
{ |
|||
previousValue = value; |
|||
startBit = (i + 1) % 8; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (startBit == 0) |
|||
{ |
|||
stream.WriteByte((byte)value); |
|||
previousValue = 0; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using SixLabors.ImageSharp.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Extensions methods for <see cref="BufferedReadStream"/>.
|
|||
/// </summary>
|
|||
internal static class BufferedReadStreamExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Skip over any whitespace or any comments.
|
|||
/// </summary>
|
|||
public static void SkipWhitespaceAndComments(this BufferedReadStream stream) |
|||
{ |
|||
bool isWhitespace; |
|||
do |
|||
{ |
|||
int val = stream.ReadByte(); |
|||
|
|||
// Comments start with '#' and end at the next new-line.
|
|||
if (val == 0x23) |
|||
{ |
|||
int innerValue; |
|||
do |
|||
{ |
|||
innerValue = stream.ReadByte(); |
|||
} |
|||
while (innerValue != 0x0a); |
|||
|
|||
// Continue searching for whitespace.
|
|||
val = innerValue; |
|||
} |
|||
|
|||
isWhitespace = val is 0x09 or 0x0a or 0x0d or 0x20; |
|||
} |
|||
while (isWhitespace); |
|||
stream.Seek(-1, SeekOrigin.Current); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Read a decimal text value.
|
|||
/// </summary>
|
|||
/// <returns>The integer value of the decimal.</returns>
|
|||
public static int ReadDecimal(this BufferedReadStream stream) |
|||
{ |
|||
int value = 0; |
|||
while (true) |
|||
{ |
|||
int current = stream.ReadByte() - 0x30; |
|||
if (current < 0 || current > 9) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
value = (value * 10) + current; |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Configuration options for use during PBM encoding.
|
|||
/// </summary>
|
|||
internal interface IPbmEncoderOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the encoding of the pixels.
|
|||
/// </summary>
|
|||
PbmEncoding? Encoding { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the Color type of the resulting image.
|
|||
/// </summary>
|
|||
PbmColorType? ColorType { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the maximum pixel value, per component.
|
|||
/// </summary>
|
|||
int? MaxPixelValue { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Pbm; |
|||
using SixLabors.ImageSharp.Metadata; |
|||
|
|||
namespace SixLabors.ImageSharp |
|||
{ |
|||
/// <summary>
|
|||
/// Extension methods for the <see cref="ImageMetadata"/> type.
|
|||
/// </summary>
|
|||
public static partial class MetadataExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the pbm format specific metadata for the image.
|
|||
/// </summary>
|
|||
/// <param name="metadata">The metadata this method extends.</param>
|
|||
/// <returns>The <see cref="PbmMetadata"/>.</returns>
|
|||
public static PbmMetadata GetPbmMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(PbmFormat.Instance); |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Provides enumeration of available PBM color types.
|
|||
/// </summary>
|
|||
public enum PbmColorType : byte |
|||
{ |
|||
/// <summary>
|
|||
/// PBM
|
|||
/// </summary>
|
|||
BlackAndWhite = 0, |
|||
|
|||
/// <summary>
|
|||
/// PGM - Greyscale. Single component.
|
|||
/// </summary>
|
|||
Grayscale = 1, |
|||
|
|||
/// <summary>
|
|||
/// PPM - RGB Color. 3 components.
|
|||
/// </summary>
|
|||
Rgb = 2, |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Registers the image encoders, decoders and mime type detectors for the Pbm format.
|
|||
/// </summary>
|
|||
public sealed class PbmConfigurationModule : IConfigurationModule |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public void Configure(Configuration configuration) |
|||
{ |
|||
configuration.ImageFormatsManager.SetEncoder(PbmFormat.Instance, new PbmEncoder()); |
|||
configuration.ImageFormatsManager.SetDecoder(PbmFormat.Instance, new PbmDecoder()); |
|||
configuration.ImageFormatsManager.AddImageFormatDetector(new PbmImageFormatDetector()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Contains PBM constant values defined in the specification.
|
|||
/// </summary>
|
|||
internal static class PbmConstants |
|||
{ |
|||
/// <summary>
|
|||
/// The maximum allowable pixel value of a ppm image.
|
|||
/// </summary>
|
|||
public const ushort MaxLength = 65535; |
|||
|
|||
/// <summary>
|
|||
/// The list of mimetypes that equate to a ppm.
|
|||
/// </summary>
|
|||
public static readonly IEnumerable<string> MimeTypes = new[] { "image/x-portable-pixmap", "image/x-portable-anymap", "image/x-portable-arbitrarymap" }; |
|||
|
|||
/// <summary>
|
|||
/// The list of file extensions that equate to a ppm.
|
|||
/// </summary>
|
|||
public static readonly IEnumerable<string> FileExtensions = new[] { "ppm", "pbm", "pgm", "pam" }; |
|||
} |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Image decoder for generating an image out of a ppm stream.
|
|||
/// </summary>
|
|||
public sealed class PbmDecoder : IImageDecoder, IImageInfoDetector |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
var decoder = new PbmDecoderCore(configuration); |
|||
return decoder.Decode<TPixel>(configuration, stream); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public Image Decode(Configuration configuration, Stream stream) |
|||
=> this.Decode<Rgb24>(configuration, stream); |
|||
|
|||
/// <inheritdoc/>
|
|||
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
var decoder = new PbmDecoderCore(configuration); |
|||
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) |
|||
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken) |
|||
.ConfigureAwait(false); |
|||
|
|||
/// <inheritdoc/>
|
|||
public IImageInfo Identify(Configuration configuration, Stream stream) |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
var decoder = new PbmDecoderCore(configuration); |
|||
return decoder.Identify(configuration, stream); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public async Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
// The introduction of a local variable that refers to an object the implements
|
|||
// IDisposable means you must use async/await, where the compiler generates the
|
|||
// state machine and a continuation.
|
|||
var decoder = new PbmDecoderCore(configuration); |
|||
return await decoder.IdentifyAsync(configuration, stream, cancellationToken) |
|||
.ConfigureAwait(false); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,169 @@ |
|||
// 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; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Performs the PBM decoding operation.
|
|||
/// </summary>
|
|||
internal sealed class PbmDecoderCore : IImageDecoderInternals |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PbmDecoderCore" /> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
public PbmDecoderCore(Configuration configuration) => this.Configuration = configuration ?? Configuration.Default; |
|||
|
|||
/// <inheritdoc />
|
|||
public Configuration Configuration { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the colortype to use
|
|||
/// </summary>
|
|||
public PbmColorType ColorType { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the size of the pixel array
|
|||
/// </summary>
|
|||
public Size PixelSize { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the maximum pixel value
|
|||
/// </summary>
|
|||
public int MaxPixelValue { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the Encoding of pixels
|
|||
/// </summary>
|
|||
public PbmEncoding Encoding { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="ImageMetadata"/> decoded by this decoder instance.
|
|||
/// </summary>
|
|||
public ImageMetadata Metadata { get; private set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
Size IImageDecoderInternals.Dimensions => this.PixelSize; |
|||
|
|||
/// <inheritdoc/>
|
|||
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
this.ProcessHeader(stream); |
|||
|
|||
var image = new Image<TPixel>(this.Configuration, this.PixelSize.Width, this.PixelSize.Height, this.Metadata); |
|||
|
|||
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer(); |
|||
|
|||
this.ProcessPixels(stream, pixels); |
|||
|
|||
return image; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) |
|||
{ |
|||
this.ProcessHeader(stream); |
|||
|
|||
int bitsPerPixel = this.MaxPixelValue > 255 ? 16 : 8; |
|||
return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.PixelSize.Width, this.PixelSize.Height, this.Metadata); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Processes the ppm header.
|
|||
/// </summary>
|
|||
/// <param name="stream">The input stream.</param>
|
|||
private void ProcessHeader(BufferedReadStream stream) |
|||
{ |
|||
byte[] buffer = new byte[2]; |
|||
|
|||
int bytesRead = stream.Read(buffer, 0, 2); |
|||
if (bytesRead != 2 || buffer[0] != 'P') |
|||
{ |
|||
// Empty or not an PPM image.
|
|||
throw new InvalidImageContentException("TODO"); |
|||
} |
|||
|
|||
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 picels 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 NotImplementedException("TODO"); |
|||
} |
|||
|
|||
stream.SkipWhitespaceAndComments(); |
|||
int width = stream.ReadDecimal(); |
|||
stream.SkipWhitespaceAndComments(); |
|||
int height = stream.ReadDecimal(); |
|||
stream.SkipWhitespaceAndComments(); |
|||
if (this.ColorType != PbmColorType.BlackAndWhite) |
|||
{ |
|||
this.MaxPixelValue = stream.ReadDecimal(); |
|||
stream.SkipWhitespaceAndComments(); |
|||
} |
|||
else |
|||
{ |
|||
this.MaxPixelValue = 1; |
|||
} |
|||
|
|||
this.PixelSize = new Size(width, height); |
|||
this.Metadata = new ImageMetadata(); |
|||
PbmMetadata meta = this.Metadata.GetPbmMetadata(); |
|||
meta.Encoding = this.Encoding; |
|||
meta.ColorType = this.ColorType; |
|||
meta.MaxPixelValue = this.MaxPixelValue; |
|||
} |
|||
|
|||
private void ProcessPixels<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
if (this.Encoding == PbmEncoding.Binary) |
|||
{ |
|||
BinaryDecoder.Process(this.Configuration, pixels, stream, this.ColorType, this.MaxPixelValue); |
|||
} |
|||
else |
|||
{ |
|||
PlainDecoder.Process(this.Configuration, pixels, stream, this.ColorType, this.MaxPixelValue); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Image encoder for writing an image to a stream as PGM, PBM, PPM or PAM bitmap.
|
|||
/// </summary>
|
|||
public sealed class PbmEncoder : IImageEncoder, IPbmEncoderOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the Encoding of the pixels.
|
|||
/// </summary>
|
|||
public PbmEncoding? Encoding { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the Color type of the resulting image.
|
|||
/// </summary>
|
|||
public PbmColorType? ColorType { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the maximum pixel value, per component.
|
|||
/// </summary>
|
|||
public int? MaxPixelValue { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Encode<TPixel>(Image<TPixel> image, Stream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
var encoder = new PbmEncoderCore(image.GetConfiguration(), this); |
|||
encoder.Encode(image, stream); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
var encoder = new PbmEncoderCore(image.GetConfiguration(), this); |
|||
return encoder.EncodeAsync(image, stream, cancellationToken); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,176 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.IO; |
|||
using System.Text; |
|||
using System.Threading; |
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Image encoder for writing an image to a stream as a PGM, PBM, PPM or PAM bitmap.
|
|||
/// </summary>
|
|||
internal sealed class PbmEncoderCore : IImageEncoderInternals |
|||
{ |
|||
private const char NewLine = '\n'; |
|||
|
|||
/// <summary>
|
|||
/// The global configuration.
|
|||
/// </summary>
|
|||
private Configuration configuration; |
|||
|
|||
/// <summary>
|
|||
/// The encoder options.
|
|||
/// </summary>
|
|||
private readonly IPbmEncoderOptions options; |
|||
|
|||
/// <summary>
|
|||
/// The encoding for the pixels.
|
|||
/// </summary>
|
|||
private PbmEncoding encoding; |
|||
|
|||
/// <summary>
|
|||
/// Gets the Color type of the resulting image.
|
|||
/// </summary>
|
|||
private PbmColorType colorType; |
|||
|
|||
/// <summary>
|
|||
/// Gets the maximum pixel value, per component.
|
|||
/// </summary>
|
|||
private int maxPixelValue; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PbmEncoderCore"/> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
/// <param name="options">The encoder options.</param>
|
|||
public PbmEncoderCore(Configuration configuration, IPbmEncoderOptions options) |
|||
{ |
|||
this.configuration = configuration; |
|||
this.options = options; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
|
|||
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
|
|||
/// <param name="cancellationToken">The token to request cancellation.</param>
|
|||
public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
Guard.NotNull(image, nameof(image)); |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
this.DeduceOptions(image); |
|||
|
|||
string signature = this.DeduceSignature(); |
|||
this.WriteHeader(stream, signature, image.Size()); |
|||
|
|||
this.WritePixels(stream, image.Frames.RootFrame); |
|||
|
|||
stream.Flush(); |
|||
} |
|||
|
|||
private void DeduceOptions<TPixel>(Image<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
this.configuration = image.GetConfiguration(); |
|||
PbmMetadata metadata = image.Metadata.GetPbmMetadata(); |
|||
this.encoding = this.options.Encoding ?? metadata.Encoding; |
|||
this.colorType = this.options.ColorType ?? metadata.ColorType; |
|||
if (this.colorType != PbmColorType.BlackAndWhite) |
|||
{ |
|||
this.maxPixelValue = this.options.MaxPixelValue ?? metadata.MaxPixelValue; |
|||
} |
|||
} |
|||
|
|||
private string DeduceSignature() |
|||
{ |
|||
string signature; |
|||
if (this.colorType == PbmColorType.BlackAndWhite) |
|||
{ |
|||
if (this.encoding == PbmEncoding.Plain) |
|||
{ |
|||
signature = "P1"; |
|||
} |
|||
else |
|||
{ |
|||
signature = "P4"; |
|||
} |
|||
} |
|||
else if (this.colorType == PbmColorType.Grayscale) |
|||
{ |
|||
if (this.encoding == PbmEncoding.Plain) |
|||
{ |
|||
signature = "P2"; |
|||
} |
|||
else |
|||
{ |
|||
signature = "P5"; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// RGB ColorType
|
|||
if (this.encoding == PbmEncoding.Plain) |
|||
{ |
|||
signature = "P3"; |
|||
} |
|||
else |
|||
{ |
|||
signature = "P6"; |
|||
} |
|||
} |
|||
|
|||
return signature; |
|||
} |
|||
|
|||
private void WriteHeader(Stream stream, string signature, Size pixelSize) |
|||
{ |
|||
var builder = new StringBuilder(20); |
|||
builder.Append(signature); |
|||
builder.Append(NewLine); |
|||
builder.Append(pixelSize.Width.ToString()); |
|||
builder.Append(NewLine); |
|||
builder.Append(pixelSize.Height.ToString()); |
|||
builder.Append(NewLine); |
|||
if (this.colorType != PbmColorType.BlackAndWhite) |
|||
{ |
|||
builder.Append(this.maxPixelValue.ToString()); |
|||
builder.Append(NewLine); |
|||
} |
|||
|
|||
string headerStr = builder.ToString(); |
|||
byte[] headerBytes = Encoding.ASCII.GetBytes(headerStr); |
|||
stream.Write(headerBytes, 0, headerBytes.Length); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the pixel data to the binary stream.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
|
|||
/// <param name="image">
|
|||
/// The <see cref="ImageFrame{TPixel}"/> containing pixel data.
|
|||
/// </param>
|
|||
private void WritePixels<TPixel>(Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
if (this.encoding == PbmEncoding.Plain) |
|||
{ |
|||
PlainEncoder.WritePixels(this.configuration, stream, image, this.colorType, this.maxPixelValue); |
|||
} |
|||
else |
|||
{ |
|||
BinaryEncoder.WritePixels(this.configuration, stream, image, this.colorType, this.maxPixelValue); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Provides enumeration of available PBM encodings.
|
|||
/// </summary>
|
|||
public enum PbmEncoding : byte |
|||
{ |
|||
/// <summary>
|
|||
/// Plain text decimal encoding.
|
|||
/// </summary>
|
|||
Plain = 0, |
|||
|
|||
/// <summary>
|
|||
/// Binary integer encoding.
|
|||
/// </summary>
|
|||
Binary = 1, |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Registers the image encoders, decoders and mime type detectors for the PBM format.
|
|||
/// </summary>
|
|||
public sealed class PbmFormat : IImageFormat<PbmMetadata> |
|||
{ |
|||
private PbmFormat() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the current instance.
|
|||
/// </summary>
|
|||
public static PbmFormat Instance { get; } = new PbmFormat(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public string Name => "PBM"; |
|||
|
|||
/// <inheritdoc/>
|
|||
public string DefaultMimeType => "image/x-portable-pixmap"; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IEnumerable<string> MimeTypes => PbmConstants.MimeTypes; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IEnumerable<string> FileExtensions => PbmConstants.FileExtensions; |
|||
|
|||
/// <inheritdoc/>
|
|||
public PbmMetadata CreateDefaultFormatMetadata() => new(); |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Detects Pbm file headers.
|
|||
/// </summary>
|
|||
public sealed class PbmImageFormatDetector : IImageFormatDetector |
|||
{ |
|||
private const byte P = (byte)'P'; |
|||
private const byte Zero = (byte)'0'; |
|||
private const byte Seven = (byte)'7'; |
|||
|
|||
/// <inheritdoc/>
|
|||
public int HeaderSize => 2; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IImageFormat DetectFormat(ReadOnlySpan<byte> header) => this.IsSupportedFileFormat(header) ? PbmFormat.Instance : null; |
|||
|
|||
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header) |
|||
{ |
|||
if (header.Length >= this.HeaderSize) |
|||
{ |
|||
return header[0] == P && header[1] > Zero && header[1] < Seven; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Provides PBM specific metadata information for the image.
|
|||
/// </summary>
|
|||
public class PbmMetadata : IDeepCloneable |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PbmMetadata"/> class.
|
|||
/// </summary>
|
|||
public PbmMetadata() |
|||
{ |
|||
this.MaxPixelValue = this.ColorType == PbmColorType.BlackAndWhite ? 1 : 255; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PbmMetadata"/> class.
|
|||
/// </summary>
|
|||
/// <param name="other">The metadata to create an instance from.</param>
|
|||
private PbmMetadata(PbmMetadata other) |
|||
{ |
|||
this.Encoding = other.Encoding; |
|||
this.ColorType = other.ColorType; |
|||
this.MaxPixelValue = other.MaxPixelValue; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the encoding of the pixels.
|
|||
/// </summary>
|
|||
public PbmEncoding Encoding { get; set; } = PbmEncoding.Plain; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the color type.
|
|||
/// </summary>
|
|||
public PbmColorType ColorType { get; set; } = PbmColorType.Grayscale; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the maximum pixel value.
|
|||
/// </summary>
|
|||
public int MaxPixelValue { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public IDeepCloneable DeepClone() => new PbmMetadata(this); |
|||
} |
|||
} |
|||
@ -0,0 +1,197 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Pixel decoding methods for the PBM plain encoding.
|
|||
/// </summary>
|
|||
internal class PlainDecoder |
|||
{ |
|||
/// <summary>
|
|||
/// Decode the specified pixels.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of pixel to encode to.</typeparam>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
/// <param name="pixels">The pixel array to encode into.</param>
|
|||
/// <param name="stream">The stream to read the data from.</param>
|
|||
/// <param name="colorType">The ColorType to decode.</param>
|
|||
/// <param name="maxPixelValue">The maximum expected pixel value</param>
|
|||
public static void Process<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream, PbmColorType colorType, int maxPixelValue) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
if (colorType == PbmColorType.Grayscale) |
|||
{ |
|||
if (maxPixelValue < 256) |
|||
{ |
|||
ProcessGrayscale(configuration, pixels, stream); |
|||
} |
|||
else |
|||
{ |
|||
ProcessWideGrayscale(configuration, pixels, stream); |
|||
} |
|||
} |
|||
else if (colorType == PbmColorType.Rgb) |
|||
{ |
|||
if (maxPixelValue < 256) |
|||
{ |
|||
ProcessRgb(configuration, pixels, stream); |
|||
} |
|||
else |
|||
{ |
|||
ProcessWideRgb(configuration, pixels, stream); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
ProcessBlackAndWhite(configuration, pixels, stream); |
|||
} |
|||
} |
|||
|
|||
private static void ProcessGrayscale<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = pixels.Width; |
|||
int height = pixels.Height; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width); |
|||
Span<L8> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
byte value = (byte)stream.ReadDecimal(); |
|||
stream.SkipWhitespaceAndComments(); |
|||
rowSpan[x] = new L8(value); |
|||
} |
|||
|
|||
Span<TPixel> pixelSpan = pixels.GetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.FromL8( |
|||
configuration, |
|||
rowSpan, |
|||
pixelSpan); |
|||
} |
|||
} |
|||
|
|||
private static void ProcessWideGrayscale<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = pixels.Width; |
|||
int height = pixels.Height; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<L16> row = allocator.Allocate<L16>(width); |
|||
Span<L16> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
ushort value = (ushort)stream.ReadDecimal(); |
|||
stream.SkipWhitespaceAndComments(); |
|||
rowSpan[x] = new L16(value); |
|||
} |
|||
|
|||
Span<TPixel> pixelSpan = pixels.GetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.FromL16( |
|||
configuration, |
|||
rowSpan, |
|||
pixelSpan); |
|||
} |
|||
} |
|||
|
|||
private static void ProcessRgb<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = pixels.Width; |
|||
int height = pixels.Height; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<Rgb24> row = allocator.Allocate<Rgb24>(width); |
|||
Span<Rgb24> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
byte red = (byte)stream.ReadDecimal(); |
|||
stream.SkipWhitespaceAndComments(); |
|||
byte green = (byte)stream.ReadDecimal(); |
|||
stream.SkipWhitespaceAndComments(); |
|||
byte blue = (byte)stream.ReadDecimal(); |
|||
stream.SkipWhitespaceAndComments(); |
|||
rowSpan[x] = new Rgb24(red, green, blue); |
|||
} |
|||
|
|||
Span<TPixel> pixelSpan = pixels.GetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.FromRgb24( |
|||
configuration, |
|||
rowSpan, |
|||
pixelSpan); |
|||
} |
|||
} |
|||
|
|||
private static void ProcessWideRgb<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = pixels.Width; |
|||
int height = pixels.Height; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<Rgb48> row = allocator.Allocate<Rgb48>(width); |
|||
Span<Rgb48> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
ushort red = (ushort)stream.ReadDecimal(); |
|||
stream.SkipWhitespaceAndComments(); |
|||
ushort green = (ushort)stream.ReadDecimal(); |
|||
stream.SkipWhitespaceAndComments(); |
|||
ushort blue = (ushort)stream.ReadDecimal(); |
|||
stream.SkipWhitespaceAndComments(); |
|||
rowSpan[x] = new Rgb48(red, green, blue); |
|||
} |
|||
|
|||
Span<TPixel> pixelSpan = pixels.GetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.FromRgb48( |
|||
configuration, |
|||
rowSpan, |
|||
pixelSpan); |
|||
} |
|||
} |
|||
|
|||
private static void ProcessBlackAndWhite<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = pixels.Width; |
|||
int height = pixels.Height; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width); |
|||
Span<L8> rowSpan = row.GetSpan(); |
|||
var white = new L8(0); |
|||
var black = new L8(255); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
int value = stream.ReadDecimal(); |
|||
stream.SkipWhitespaceAndComments(); |
|||
rowSpan[x] = value == 0 ? white : black; |
|||
} |
|||
|
|||
Span<TPixel> pixelSpan = pixels.GetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.FromL8( |
|||
configuration, |
|||
rowSpan, |
|||
pixelSpan); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,228 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Pbm |
|||
{ |
|||
/// <summary>
|
|||
/// Pixel encoding methods for the PBM plain encoding.
|
|||
/// </summary>
|
|||
internal class PlainEncoder |
|||
{ |
|||
private const int MaxLineLength = 70; |
|||
private const byte NewLine = 0x0a; |
|||
private const byte Space = 0x20; |
|||
private const byte Zero = 0x30; |
|||
private const byte One = 0x31; |
|||
|
|||
/// <summary>
|
|||
/// Decode pixels into the PBM plain encoding.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of input pixel.</typeparam>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
/// <param name="stream">The bytestream to write to.</param>
|
|||
/// <param name="image">The input image.</param>
|
|||
/// <param name="colorType">The ColorType to use.</param>
|
|||
/// <param name="maxPixelValue">The maximum expected pixel value</param>
|
|||
public static void WritePixels<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image, PbmColorType colorType, int maxPixelValue) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
if (colorType == PbmColorType.Grayscale) |
|||
{ |
|||
if (maxPixelValue < 256) |
|||
{ |
|||
WriteGrayscale(configuration, stream, image); |
|||
} |
|||
else |
|||
{ |
|||
WriteWideGrayscale(configuration, stream, image); |
|||
} |
|||
} |
|||
else if (colorType == PbmColorType.Rgb) |
|||
{ |
|||
if (maxPixelValue < 256) |
|||
{ |
|||
WriteRgb(configuration, stream, image); |
|||
} |
|||
else |
|||
{ |
|||
WriteWideRgb(configuration, stream, image); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
WriteBlackAndWhite(configuration, stream, image); |
|||
} |
|||
} |
|||
|
|||
private static void WriteGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = image.Size().Width; |
|||
int height = image.Size().Height; |
|||
int bytesWritten = -1; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width); |
|||
Span<L8> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<TPixel> pixelSpan = image.GetPixelRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.ToL8( |
|||
configuration, |
|||
pixelSpan, |
|||
rowSpan); |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteWhitespace(stream, ref bytesWritten); |
|||
bytesWritten += stream.WriteDecimal(rowSpan[x].PackedValue); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static void WriteWideGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = image.Size().Width; |
|||
int height = image.Size().Height; |
|||
int bytesWritten = -1; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<L16> row = allocator.Allocate<L16>(width); |
|||
Span<L16> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<TPixel> pixelSpan = image.GetPixelRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.ToL16( |
|||
configuration, |
|||
pixelSpan, |
|||
rowSpan); |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteWhitespace(stream, ref bytesWritten); |
|||
bytesWritten += stream.WriteDecimal(rowSpan[x].PackedValue); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static void WriteRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = image.Size().Width; |
|||
int height = image.Size().Height; |
|||
int bytesWritten = -1; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<Rgb24> row = allocator.Allocate<Rgb24>(width); |
|||
Span<Rgb24> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<TPixel> pixelSpan = image.GetPixelRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.ToRgb24( |
|||
configuration, |
|||
pixelSpan, |
|||
rowSpan); |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteWhitespace(stream, ref bytesWritten); |
|||
bytesWritten += stream.WriteDecimal(rowSpan[x].R); |
|||
WriteWhitespace(stream, ref bytesWritten); |
|||
bytesWritten += stream.WriteDecimal(rowSpan[x].G); |
|||
WriteWhitespace(stream, ref bytesWritten); |
|||
bytesWritten += stream.WriteDecimal(rowSpan[x].B); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static void WriteWideRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = image.Size().Width; |
|||
int height = image.Size().Height; |
|||
int bytesWritten = -1; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<Rgb48> row = allocator.Allocate<Rgb48>(width); |
|||
Span<Rgb48> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<TPixel> pixelSpan = image.GetPixelRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.ToRgb48( |
|||
configuration, |
|||
pixelSpan, |
|||
rowSpan); |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteWhitespace(stream, ref bytesWritten); |
|||
bytesWritten += stream.WriteDecimal(rowSpan[x].R); |
|||
WriteWhitespace(stream, ref bytesWritten); |
|||
bytesWritten += stream.WriteDecimal(rowSpan[x].G); |
|||
WriteWhitespace(stream, ref bytesWritten); |
|||
bytesWritten += stream.WriteDecimal(rowSpan[x].B); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static void WriteBlackAndWhite<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = image.Size().Width; |
|||
int height = image.Size().Height; |
|||
int bytesWritten = -1; |
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width); |
|||
Span<L8> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<TPixel> pixelSpan = image.GetPixelRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.ToL8( |
|||
configuration, |
|||
pixelSpan, |
|||
rowSpan); |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteWhitespace(stream, ref bytesWritten); |
|||
if (rowSpan[x].PackedValue > 127) |
|||
{ |
|||
stream.WriteByte(Zero); |
|||
} |
|||
else |
|||
{ |
|||
stream.WriteByte(One); |
|||
} |
|||
|
|||
bytesWritten++; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static void WriteWhitespace(Stream stream, ref int bytesWritten) |
|||
{ |
|||
if (bytesWritten > MaxLineLength) |
|||
{ |
|||
stream.WriteByte(NewLine); |
|||
bytesWritten = 1; |
|||
} |
|||
else if (bytesWritten == -1) |
|||
{ |
|||
bytesWritten = 0; |
|||
} |
|||
else |
|||
{ |
|||
stream.WriteByte(Space); |
|||
bytesWritten++; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// 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; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,155 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
using SixLabors.ImageSharp.Formats; |
|||
using SixLabors.ImageSharp.Formats.Pbm; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Pbm |
|||
{ |
|||
public class ImageExtensionsTest |
|||
{ |
|||
[Fact] |
|||
public void SaveAsPbm_Path() |
|||
{ |
|||
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); |
|||
string file = Path.Combine(dir, "SaveAsPbm_Path.pbm"); |
|||
|
|||
using (var image = new Image<L8>(10, 10)) |
|||
{ |
|||
image.SaveAsPbm(file); |
|||
} |
|||
|
|||
using (Image.Load(file, out IImageFormat mime)) |
|||
{ |
|||
Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task SaveAsPbmAsync_Path() |
|||
{ |
|||
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); |
|||
string file = Path.Combine(dir, "SaveAsPbmAsync_Path.pbm"); |
|||
|
|||
using (var image = new Image<L8>(10, 10)) |
|||
{ |
|||
await image.SaveAsPbmAsync(file); |
|||
} |
|||
|
|||
using (Image.Load(file, out IImageFormat mime)) |
|||
{ |
|||
Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void SaveAsPbm_Path_Encoder() |
|||
{ |
|||
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); |
|||
string file = Path.Combine(dir, "SaveAsPbm_Path_Encoder.pbm"); |
|||
|
|||
using (var image = new Image<L8>(10, 10)) |
|||
{ |
|||
image.SaveAsPbm(file, new PbmEncoder()); |
|||
} |
|||
|
|||
using (Image.Load(file, out IImageFormat mime)) |
|||
{ |
|||
Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task SaveAsPbmAsync_Path_Encoder() |
|||
{ |
|||
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); |
|||
string file = Path.Combine(dir, "SaveAsPbmAsync_Path_Encoder.pbm"); |
|||
|
|||
using (var image = new Image<L8>(10, 10)) |
|||
{ |
|||
await image.SaveAsPbmAsync(file, new PbmEncoder()); |
|||
} |
|||
|
|||
using (Image.Load(file, out IImageFormat mime)) |
|||
{ |
|||
Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void SaveAsPbm_Stream() |
|||
{ |
|||
using var memoryStream = new MemoryStream(); |
|||
|
|||
using (var image = new Image<L8>(10, 10)) |
|||
{ |
|||
image.SaveAsPbm(memoryStream); |
|||
} |
|||
|
|||
memoryStream.Position = 0; |
|||
|
|||
using (Image.Load(memoryStream, out IImageFormat mime)) |
|||
{ |
|||
Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task SaveAsPbmAsync_StreamAsync() |
|||
{ |
|||
using var memoryStream = new MemoryStream(); |
|||
|
|||
using (var image = new Image<L8>(10, 10)) |
|||
{ |
|||
await image.SaveAsPbmAsync(memoryStream); |
|||
} |
|||
|
|||
memoryStream.Position = 0; |
|||
|
|||
using (Image.Load(memoryStream, out IImageFormat mime)) |
|||
{ |
|||
Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void SaveAsPbm_Stream_Encoder() |
|||
{ |
|||
using var memoryStream = new MemoryStream(); |
|||
|
|||
using (var image = new Image<L8>(10, 10)) |
|||
{ |
|||
image.SaveAsPbm(memoryStream, new PbmEncoder()); |
|||
} |
|||
|
|||
memoryStream.Position = 0; |
|||
|
|||
using (Image.Load(memoryStream, out IImageFormat mime)) |
|||
{ |
|||
Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task SaveAsPbmAsync_Stream_Encoder() |
|||
{ |
|||
using var memoryStream = new MemoryStream(); |
|||
|
|||
using (var image = new Image<L8>(10, 10)) |
|||
{ |
|||
await image.SaveAsPbmAsync(memoryStream, new PbmEncoder()); |
|||
} |
|||
|
|||
memoryStream.Position = 0; |
|||
|
|||
using (Image.Load(memoryStream, out IImageFormat mime)) |
|||
{ |
|||
Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Formats.Pbm; |
|||
|
|||
using Xunit; |
|||
using static SixLabors.ImageSharp.Tests.TestImages.Pbm; |
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Pbm |
|||
{ |
|||
[Trait("Format", "Pbm")] |
|||
public class PbmDecoderTests |
|||
{ |
|||
[Theory] |
|||
[InlineData(BlackAndWhitePlain, PbmColorType.BlackAndWhite)] |
|||
[InlineData(BlackAndWhiteBinary, PbmColorType.BlackAndWhite)] |
|||
[InlineData(GrayscalePlain, PbmColorType.Grayscale)] |
|||
[InlineData(GrayscaleBinary, PbmColorType.Grayscale)] |
|||
[InlineData(GrayscaleBinaryWide, PbmColorType.Grayscale)] |
|||
[InlineData(RgbPlain, PbmColorType.Rgb)] |
|||
[InlineData(RgbBinary, PbmColorType.Rgb)] |
|||
public void PpmDecoder_CanDecode(string imagePath, PbmColorType expectedColorType) |
|||
{ |
|||
// Arrange
|
|||
var testFile = TestFile.Create(imagePath); |
|||
using var stream = new MemoryStream(testFile.Bytes, false); |
|||
|
|||
// Act
|
|||
using var image = Image.Load(stream); |
|||
|
|||
// Assert
|
|||
Assert.NotNull(image); |
|||
PbmMetadata bitmapMetadata = image.Metadata.GetPbmMetadata(); |
|||
Assert.NotNull(bitmapMetadata); |
|||
Assert.Equal(expectedColorType, bitmapMetadata.ColorType); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,144 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Formats.Pbm; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Tests.Formats.Tga; |
|||
|
|||
using Xunit; |
|||
using static SixLabors.ImageSharp.Tests.TestImages.Pbm; |
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Pbm |
|||
{ |
|||
[Collection("RunSerial")] |
|||
[Trait("Format", "Pbm")] |
|||
public class PbmEncoderTests |
|||
{ |
|||
public static readonly TheoryData<PbmColorType> ColorType = |
|||
new TheoryData<PbmColorType> |
|||
{ |
|||
PbmColorType.BlackAndWhite, |
|||
PbmColorType.Grayscale, |
|||
PbmColorType.Rgb |
|||
}; |
|||
|
|||
public static readonly TheoryData<string, PbmColorType> PbmColorTypeFiles = |
|||
new TheoryData<string, PbmColorType> |
|||
{ |
|||
{ BlackAndWhiteBinary, PbmColorType.BlackAndWhite }, |
|||
{ BlackAndWhitePlain, PbmColorType.BlackAndWhite }, |
|||
{ GrayscaleBinary, PbmColorType.Grayscale }, |
|||
{ GrayscaleBinaryWide, PbmColorType.Grayscale }, |
|||
{ GrayscalePlain, PbmColorType.Grayscale }, |
|||
{ RgbBinary, PbmColorType.Rgb }, |
|||
{ RgbPlain, PbmColorType.Rgb }, |
|||
}; |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(PbmColorTypeFiles))] |
|||
public void PbmEncoder_PreserveColorType(string imagePath, PbmColorType pbmColorType) |
|||
{ |
|||
var options = new PbmEncoder(); |
|||
|
|||
var testFile = TestFile.Create(imagePath); |
|||
using (Image<Rgba32> input = testFile.CreateRgba32Image()) |
|||
{ |
|||
using (var memStream = new MemoryStream()) |
|||
{ |
|||
input.Save(memStream, options); |
|||
memStream.Position = 0; |
|||
using (var output = Image.Load<Rgba32>(memStream)) |
|||
{ |
|||
PbmMetadata meta = output.Metadata.GetPbmMetadata(); |
|||
Assert.Equal(pbmColorType, meta.ColorType); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(PbmColorTypeFiles))] |
|||
public void PbmEncoder_WithPlainEncoding_PreserveBitsPerPixel(string imagePath, PbmColorType pbmColorType) |
|||
{ |
|||
var options = new PbmEncoder() |
|||
{ |
|||
Encoding = PbmEncoding.Plain |
|||
}; |
|||
|
|||
TestFile testFile = TestFile.Create(imagePath); |
|||
using (Image<Rgba32> input = testFile.CreateRgba32Image()) |
|||
{ |
|||
using (var memStream = new MemoryStream()) |
|||
{ |
|||
input.Save(memStream, options); |
|||
memStream.Position = 0; |
|||
using (var output = Image.Load<Rgba32>(memStream)) |
|||
{ |
|||
PbmMetadata meta = output.Metadata.GetPbmMetadata(); |
|||
Assert.Equal(pbmColorType, meta.ColorType); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(BlackAndWhitePlain, PixelTypes.Rgb24)] |
|||
public void PbmEncoder_P1_Works<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> => TestPbmEncoderCore(provider, PbmColorType.BlackAndWhite, PbmEncoding.Plain); |
|||
|
|||
[Theory] |
|||
[WithFile(BlackAndWhiteBinary, PixelTypes.Rgb24)] |
|||
public void PbmEncoder_P4_Works<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> => TestPbmEncoderCore(provider, PbmColorType.BlackAndWhite, PbmEncoding.Binary); |
|||
|
|||
/* Disabled as Magick throws an error reading the input image |
|||
[Theory] |
|||
[WithFile(GrayscalePlain, PixelTypes.Rgb24)] |
|||
public void PbmEncoder_P2_Works<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> => TestPbmEncoderCore(provider, PbmColorType.Grayscale, PbmEncoding.Plain); |
|||
*/ |
|||
|
|||
[Theory] |
|||
[WithFile(GrayscaleBinary, PixelTypes.Rgb24)] |
|||
public void PbmEncoder_P5_Works<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> => TestPbmEncoderCore(provider, PbmColorType.Grayscale, PbmEncoding.Binary); |
|||
|
|||
/* Disabled as Magick throws an error reading the input image |
|||
[Theory] |
|||
[WithFile(RgbPlain, PixelTypes.Rgb24)] |
|||
public void PbmEncoder_P3_Works<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> => TestPbmEncoderCore(provider, PbmColorType.Rgb, PbmEncoding.Plain); |
|||
*/ |
|||
|
|||
[Theory] |
|||
[WithFile(RgbBinary, PixelTypes.Rgb24)] |
|||
public void PbmEncoder_P6_Works<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> => TestPbmEncoderCore(provider, PbmColorType.Rgb, PbmEncoding.Binary); |
|||
|
|||
private static void TestPbmEncoderCore<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
PbmColorType colorType, |
|||
PbmEncoding encoding, |
|||
bool useExactComparer = true, |
|||
float compareTolerance = 0.01f) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage()) |
|||
{ |
|||
var encoder = new PbmEncoder { ColorType = colorType, Encoding = encoding }; |
|||
|
|||
using (var memStream = new MemoryStream()) |
|||
{ |
|||
image.Save(memStream, encoder); |
|||
memStream.Position = 0; |
|||
using (var encodedImage = (Image<TPixel>)Image.Load(memStream)) |
|||
{ |
|||
TgaTestUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Formats.Pbm; |
|||
|
|||
using Xunit; |
|||
using static SixLabors.ImageSharp.Tests.TestImages.Pbm; |
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Pbm |
|||
{ |
|||
[Trait("Format", "Pbm")] |
|||
public class PbmMetadataTests |
|||
{ |
|||
[Fact] |
|||
public void CloneIsDeep() |
|||
{ |
|||
var meta = new PbmMetadata { ColorType = PbmColorType.Grayscale }; |
|||
var clone = (PbmMetadata)meta.DeepClone(); |
|||
|
|||
clone.ColorType = PbmColorType.Rgb; |
|||
|
|||
Assert.False(meta.ColorType.Equals(clone.ColorType)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(BlackAndWhitePlain, PbmEncoding.Plain)] |
|||
[InlineData(BlackAndWhiteBinary, PbmEncoding.Binary)] |
|||
[InlineData(GrayscaleBinary, PbmEncoding.Binary)] |
|||
[InlineData(GrayscaleBinaryWide, PbmEncoding.Binary)] |
|||
[InlineData(GrayscalePlain, PbmEncoding.Plain)] |
|||
[InlineData(RgbBinary, PbmEncoding.Binary)] |
|||
[InlineData(RgbPlain, PbmEncoding.Plain)] |
|||
public void Identify_DetectsCorrectEncoding(string imagePath, PbmEncoding expectedEncoding) |
|||
{ |
|||
var testFile = TestFile.Create(imagePath); |
|||
using var stream = new MemoryStream(testFile.Bytes, false); |
|||
IImageInfo imageInfo = Image.Identify(stream); |
|||
Assert.NotNull(imageInfo); |
|||
PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata(); |
|||
Assert.NotNull(bitmapMetadata); |
|||
Assert.Equal(expectedEncoding, bitmapMetadata.Encoding); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(BlackAndWhitePlain, PbmColorType.BlackAndWhite)] |
|||
[InlineData(BlackAndWhiteBinary, PbmColorType.BlackAndWhite)] |
|||
[InlineData(GrayscaleBinary, PbmColorType.Grayscale)] |
|||
[InlineData(GrayscaleBinaryWide, PbmColorType.Grayscale)] |
|||
[InlineData(GrayscalePlain, PbmColorType.Grayscale)] |
|||
[InlineData(RgbBinary, PbmColorType.Rgb)] |
|||
[InlineData(RgbPlain, PbmColorType.Rgb)] |
|||
public void Identify_DetectsCorrectColorType(string imagePath, PbmColorType expectedColorType) |
|||
{ |
|||
var testFile = TestFile.Create(imagePath); |
|||
using var stream = new MemoryStream(testFile.Bytes, false); |
|||
IImageInfo imageInfo = Image.Identify(stream); |
|||
Assert.NotNull(imageInfo); |
|||
PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata(); |
|||
Assert.NotNull(bitmapMetadata); |
|||
Assert.Equal(expectedColorType, bitmapMetadata.ColorType); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(BlackAndWhitePlain, 1)] |
|||
[InlineData(BlackAndWhiteBinary, 1)] |
|||
[InlineData(GrayscaleBinary, 255)] |
|||
[InlineData(GrayscaleBinaryWide, 65535)] |
|||
[InlineData(GrayscalePlain, 15)] |
|||
[InlineData(RgbBinary, 255)] |
|||
[InlineData(RgbPlain, 15)] |
|||
public void Identify_DetectsCorrectMaxPixelValue(string imagePath, int expectedMaxPixelValue) |
|||
{ |
|||
var testFile = TestFile.Create(imagePath); |
|||
using var stream = new MemoryStream(testFile.Bytes, false); |
|||
IImageInfo imageInfo = Image.Identify(stream); |
|||
Assert.NotNull(imageInfo); |
|||
PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata(); |
|||
Assert.NotNull(bitmapMetadata); |
|||
Assert.Equal(expectedMaxPixelValue, bitmapMetadata.MaxPixelValue); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using ImageMagick; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Pbm |
|||
{ |
|||
public static class PbmTestUtils |
|||
{ |
|||
public static void CompareWithReferenceDecoder<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
Image<TPixel> image, |
|||
bool useExactComparer = true, |
|||
float compareTolerance = 0.01f) |
|||
where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel<TPixel> |
|||
{ |
|||
string path = TestImageProvider<TPixel>.GetFilePathOrNull(provider); |
|||
if (path == null) |
|||
{ |
|||
throw new InvalidOperationException("CompareToOriginal() works only with file providers!"); |
|||
} |
|||
|
|||
var testFile = TestFile.Create(path); |
|||
Image<Rgba32> magickImage = DecodeWithMagick<Rgba32>(Configuration.Default, new FileInfo(testFile.FullPath)); |
|||
if (useExactComparer) |
|||
{ |
|||
ImageComparer.Exact.VerifySimilarity(magickImage, image); |
|||
} |
|||
else |
|||
{ |
|||
ImageComparer.Tolerant(compareTolerance).VerifySimilarity(magickImage, image); |
|||
} |
|||
} |
|||
|
|||
public static Image<TPixel> DecodeWithMagick<TPixel>(Configuration configuration, FileInfo fileInfo) |
|||
where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel<TPixel> |
|||
{ |
|||
using (var magickImage = new MagickImage(fileInfo)) |
|||
{ |
|||
magickImage.AutoOrient(); |
|||
var result = new Image<TPixel>(configuration, magickImage.Width, magickImage.Height); |
|||
|
|||
Assert.True(result.TryGetSinglePixelSpan(out Span<TPixel> resultPixels)); |
|||
|
|||
using (IUnsafePixelCollection<ushort> pixels = magickImage.GetPixelsUnsafe()) |
|||
{ |
|||
byte[] data = pixels.ToByteArray(PixelMapping.RGBA); |
|||
|
|||
PixelOperations<TPixel>.Instance.FromRgba32Bytes( |
|||
configuration, |
|||
data, |
|||
resultPixels, |
|||
resultPixels.Length); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
|||
using Xunit; |
|||
using static SixLabors.ImageSharp.Tests.TestImages.Pbm; |
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Pbm |
|||
{ |
|||
[Trait("Format", "Pbm")] |
|||
public class RoundTripTests |
|||
{ |
|||
[Theory] |
|||
[InlineData(RgbPlain)] |
|||
[InlineData(RgbBinary)] |
|||
public void PbmColorImageCanRoundTrip(string imagePath) |
|||
{ |
|||
// Arrange
|
|||
var testFile = TestFile.Create(imagePath); |
|||
using var stream = new MemoryStream(testFile.Bytes, false); |
|||
|
|||
// Act
|
|||
using var originalImage = Image.Load<Rgb24>(stream); |
|||
using Image<Rgb24> encodedImage = this.RoundTrip(originalImage); |
|||
|
|||
// Assert
|
|||
Assert.NotNull(encodedImage); |
|||
ImageComparer.Exact.VerifySimilarity(originalImage, encodedImage); |
|||
} |
|||
|
|||
private Image<TPixel> RoundTrip<TPixel>(Image<TPixel> originalImage) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using var decodedStream = new MemoryStream(); |
|||
originalImage.SaveAsPbm(decodedStream); |
|||
decodedStream.Seek(0, SeekOrigin.Begin); |
|||
var encodedImage = (Image<TPixel>)Image.Load(decodedStream); |
|||
return encodedImage; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
P6 |
|||
29 30 |
|||
255 |
|||
KNPJLNVWTl^U’tr¡nrÁyy±…‚òáãÿúÿòîÿæêÿ•¡ÄsŠ®ƒ¥°ºÉ»ëçèüòÿêÚçÜ«´ÀmpŠCAŽRVbmY[aKOPDKKAEDBCBSTVPPRZYTˆk_¾…{™YQœUZϧºÿúÿýúÿùýÿ»¿ð~‚¶—™Ñ°°ÇÉŠÞãËðîüõìÿôÎðÊ‡Ž•R?’NQ‚[ug\kTQVIMNLNKPPNNNPVUV]Z[¤xzÀkf’D6à™Ÿÿïÿÿüÿ÷ûÿçóý¯³¿«¨ÄùçÿڹŕŒy¼ÊÍäêÿíëÿÿñÿÝ©º¢bk®bh”berZ[^SSHJHIJENNJJKM[SSn\d²u†¸QP±[Gÿ½ÃÿÞÿÿÿÿùôÿëéã¾µ�ÞÎÒøßÿÿðÿéÐèÁ±ÐåÍôÿêÿÿâõÿÚöØ�µ¢FO•L9¦oc}`ZKHBIJCIOJGJG`QH„b`ÁswÃIHÐrkò°µÿïÿùëö÷èîàÐÍϸ«àÂÃíÌÛá½Éد¹Ò£°Î˜©ËŽš±y€¿‹–®^m·JH—6"¨RH�nnULJIKHP]\EJBxfT¬‚u³aX»A@ÆY_ï�“Ó�ƒ·tujk£``œXV•PN’LJŽHEˆD@‚A=ƒ?;†>:y=9m=8x>7ƒ?1—S=�LD‰fkSPRJJJiknemp‚ml¹†€™B6¼A=½@DÀAE¿CG¾EJ¿HO¾HN¹HN¸KNºOO¾UW¹[_·cl¾ky°]`²rkŸrl—so�rj—qf}KHw_cHJJLIFh__Œ�žžŽž®ŠŠ±{lµmg²rt°w{´}„º„ŽÉ‰–ØŽœä�Ÿñ“›ñ�’ñ�”쟵Õ�¿|†˜lalVPcQOZML_KJeJKTGKEEDJHBPLJ®®½º¯¿³�Á¬½„oƒ|x�eqƒ_kxbo‚}t�“t€«tt¯``£LL¬MP¢OT«dk¶y‚œiq€go```YYPWWRXVTRNNIFHIGGHGDJJH¶¸È»´ÄÀ°Ñ–�Âoq¡\f”J\|f_ryXtˆ]‚ˆS^ˆJ;‰A9‹HG�ON‡IH‚C<‹F8•JPŠTo™w|whXXZPRSOLLJKKKJJJGGFMMJ°´´²¯©¥›¦¢ªÆZtšE^�F_‹{`‡¡Rh“FK±ei¥_cÔ“™öÇÎâÅÍíÃËË” ²eu·SZ•A@ \_‡hpW\^LPMLOMMONIKIGGDKJF�€o—�pjeZ�œ£Zw”L^’]cš�W’§L\§Y>¸wvÞ®ÆÿåùÿïÿÿúÿôëýÜÜüÚ³áÙ‹“³aC—BC}Pqe_nTSQOSQSURLLHNMGZWRÅ£~|kMWWJ�’[|•T^Œxl‘›a~´LV»ldÅŽ‰Ë±¯«®¥ØÕÄêàÕý€·Ézy¤¶}’Ñ��´MQŒPZyhl]WW\a`Y[YPOLVSO[VQÚÇŽ�wWJJ:zŠŠ`�•deŽ“u‰�PO®MRÔ�¨©œ”š‰mŒwP·ŸmâÇŸ²¬Žƒ’—†}¦Šh‹Ú�º±YaŠG: qkvabkpp^a_QRPSSQ\[V´¯€†ƒaVUGv†‹m�¨sl™¥t‰†@>ÂgkíÎÙ£Àƺ²³Á”…£vW�p^Žkf©·àÎïzoˆäºËÅ|�Š?9£UT�kmbhhY]\RUTRTT`b`–—rƒ‚^daTw‰‰ž»‡u¦tŠ�@<Ê�…ùÛçØäùÐÑðȾǕ†y˶¹‚n‚ÓÀÖäÖïv…Õ³¹Û £ƒ@A£WY�mq]abX[ZUXXSUVZ\\|„s€�qvpm}~‡|„�›~Žºx~}@<Ì”™ÿèöäßôÉÓð€™°ˆ–¨ÁÄÓvq~µ¨Áéßÿ~w‡Ç©Æ˜–}AA¤Z[’orX[[WZVUXTTWS[^Zlrthjndciffnhjf¨‡wÂ}s�D@½pvÿÒâîÝóš—³lu››£ÔËÒî…Œ–›·ª¯àrx‰Ñµ¦Ï‘‰ƒ?>¨^_ŽjmVVTYZSVYQZ]T^aXˆŒ•ŠŽ™ŠŽ™��š‹ˆˆ¶�…µlgžHH»X[ý½Ã©Œ�l[[ƒ}‰� ¹¹Äˆš—u�–k€–‹ž¯Û½ÊºklŠ>4œRNŒfg[YWZZUZ]W]`Y^aZ¯³Ä±Ã«°À¤«¹ž§ª‘‡€¤†…£cg£AA¼tqÖ¨™ª�t~wj‹‹Œ˜ š¸Âµ¸Ä¼ÒÍÉíÖèÀ�¶•EO�S?¡yor]\_\Z[[XWYUZ[V]]X±µÂ²¾«±»¤±º”©©nvkxq©yºYO–F8ɉtð̰ãÚÔëéùåêïéëæççïÿäùâ¡°¨_g´ck“hplfjYTU^Z[a`bUWWUURXUQ¿´½¬®µ™©®˜ª®—«§l‚vSk]�n^¯q]¡[B“E*ʆh÷½¾öÑñöåöÿçêÿéÿç´ã¿Š¢cK›]cyX|YThTRV^^cWX_SUXOQPSUR–|Y}}db|gI`O�£–j…{Jm^OS?�fO´y_¸\K¤@7¾RTdrÀ™ž¤khÓ‡�裷Ä��hkˆ]hkSeTOYPPSZ^bNQWLORHMNIQOõÏš�U?OduO‰›„l‡Kp`J_Fsw]�d¨‡€•`l—NRŠMI}MF€QG„VN˜oi‰•�s’€jzcVWOKLRRT[_cMOUSTXJNQNUTå³oÄ®€X^:DO4‡—ˆm‰…NveFlNNrUYx]b|l‡�|‘jd·‡¢haŸzt�z�„�Š�‹ƒ~•lkz[Y`NKPOORTXZLNOOOOMPPNTR�„L‚yWgnUW]M�”Œ‚””\|je‚^c}]h~bn€g‚ƒm•†u™…yž„}�€}�}{�‡†™’™|w‡ljy\[iMLUJJOPRRQRNRRNRSOQTP‰ˆo|qkshjkdŠ…ƒ— ¢„œ–„˜‰ƒ”‡„‘†ƒ�€€‰z}†y}„y†ŒŒz€Šiprnnhuoqup{okvjiuRS[FFJJJJOOKMNJNNI[[W…†‹z„py|suvvruoothmrdkq`inbhmcgfekcKTKdla�”š}„œ[cinma�…ƒ‘‰�smugcm]Z`JHKKIIUTP^^Yab[cgc[_c[bgJUYDHKMJLTLM^QU]W^Z[`__bXVXJMNIRQLUQ†�™ˆ¡Xaipmf™Š‡¢¢umphah]V[PKOLHH]ZWge_nqh`hbUTW_ek=JOAGLEDESOMc[^ZW`RSXZZ\cbhQR]LPVNTV‚Š˜�ФT_jfkh��‹��–nnmb^ce^cWRWIGHQPMff_cfZZaY |
|||
Binary file not shown.
@ -0,0 +1,3 @@ |
|||
P4 |
|||
# CREATOR: bitmap2pbm Version 1.0.0 |
|||
8 4 @0@0 |
|||
@ -0,0 +1,10 @@ |
|||
P1 |
|||
# PBM example |
|||
24 7 |
|||
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 1 1 1 1 0 0 1 1 1 1 0 0 1 1 1 1 0 0 1 1 1 1 0 |
|||
0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 1 0 |
|||
0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 1 0 |
|||
0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 |
|||
0 1 0 0 0 0 0 1 1 1 1 0 0 1 1 1 1 0 0 1 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 |
|||
@ -0,0 +1,10 @@ |
|||
P2 |
|||
24 7 |
|||
15 |
|||
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 3 3 3 3 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 15 15 15 0 |
|||
0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 15 0 |
|||
0 3 3 3 0 0 0 7 7 7 0 0 0 11 11 11 0 0 0 15 15 15 15 0 |
|||
0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 0 0 |
|||
0 3 0 0 0 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 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 |
|||
@ -0,0 +1,8 @@ |
|||
P3 |
|||
# example from the man page |
|||
4 4 |
|||
15 |
|||
0 0 0 0 0 0 0 0 0 15 0 15 |
|||
0 0 0 0 15 7 0 0 0 0 0 0 |
|||
0 0 0 0 0 0 0 15 7 0 0 0 |
|||
15 0 15 0 0 0 0 0 0 0 0 0 |
|||
Binary file not shown.
Loading…
Reference in new issue