📷 A modern, cross-platform, 2D Graphics library for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

182 lines
6.1 KiB

// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Text;
using System.IO;
using System.Threading;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Pbm
{
/// <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 byte NewLine = (byte)'\n';
private const byte Space = (byte)' ';
private const byte P = (byte)'P';
/// <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);
byte 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 byte DeduceSignature()
{
byte signature;
if (this.colorType == PbmColorType.BlackAndWhite)
{
if (this.encoding == PbmEncoding.Plain)
{
signature = (byte)'1';
}
else
{
signature = (byte)'4';
}
}
else if (this.colorType == PbmColorType.Grayscale)
{
if (this.encoding == PbmEncoding.Plain)
{
signature = (byte)'2';
}
else
{
signature = (byte)'5';
}
}
else
{
// RGB ColorType
if (this.encoding == PbmEncoding.Plain)
{
signature = (byte)'3';
}
else
{
signature = (byte)'6';
}
}
return signature;
}
private void WriteHeader(Stream stream, byte signature, Size pixelSize)
{
Span<byte> buffer = stackalloc byte[128];
int written = 3;
buffer[0] = P;
buffer[1] = signature;
buffer[2] = NewLine;
Utf8Formatter.TryFormat(pixelSize.Width, buffer.Slice(written), out int bytesWritten);
written += bytesWritten;
buffer[written++] = Space;
Utf8Formatter.TryFormat(pixelSize.Height, buffer.Slice(written), out bytesWritten);
written += bytesWritten;
buffer[written++] = NewLine;
if (this.colorType != PbmColorType.BlackAndWhite)
{
Utf8Formatter.TryFormat(this.maxPixelValue, buffer.Slice(written), out bytesWritten);
written += bytesWritten;
buffer[written++] = NewLine;
}
stream.Write(buffer, 0, written);
}
/// <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);
}
}
}
}