-
-
Notifications
You must be signed in to change notification settings - Fork 894
Expand file tree
/
Copy pathPbmDecoderCore.cs
More file actions
214 lines (183 loc) · 7.04 KB
/
Copy pathPbmDecoderCore.cs
File metadata and controls
214 lines (183 loc) · 7.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Formats.Pbm;
/// <summary>
/// Performs the PBM decoding operation.
/// </summary>
internal sealed class PbmDecoderCore : IImageDecoderInternals
{
private int maxPixelValue;
/// <summary>
/// The general configuration.
/// </summary>
private readonly Configuration configuration;
/// <summary>
/// The colortype to use
/// </summary>
private PbmColorType colorType;
/// <summary>
/// The size of the pixel array
/// </summary>
private Size pixelSize;
/// <summary>
/// The component data type
/// </summary>
private PbmComponentType componentType;
/// <summary>
/// The Encoding of pixels
/// </summary>
private PbmEncoding encoding;
/// <summary>
/// The <see cref="ImageMetadata"/> decoded by this decoder instance.
/// </summary>
private ImageMetadata? metadata;
/// <summary>
/// Initializes a new instance of the <see cref="PbmDecoderCore" /> class.
/// </summary>
/// <param name="options">The decoder options.</param>
public PbmDecoderCore(DecoderOptions options)
{
this.Options = options;
this.configuration = options.Configuration;
}
/// <inheritdoc/>
public DecoderOptions Options { get; }
/// <inheritdoc/>
public Size 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);
if (this.NeedsUpscaling())
{
this.ProcessUpscaling(image);
}
return image;
}
/// <inheritdoc/>
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.ProcessHeader(stream);
// BlackAndWhite pixels are encoded into a byte.
int bitsPerPixel = this.componentType == PbmComponentType.Short ? 16 : 8;
return new ImageInfo(new PixelTypeInfo(bitsPerPixel), new(this.pixelSize.Width, this.pixelSize.Height), this.metadata);
}
/// <summary>
/// Processes the ppm header.
/// </summary>
/// <param name="stream">The input stream.</param>
/// <exception cref="InvalidImageContentException">An EOF marker has been read before the image has been decoded.</exception>
private void ProcessHeader(BufferedReadStream stream)
{
Span<byte> buffer = stackalloc byte[2];
int bytesRead = stream.Read(buffer);
if (bytesRead != 2 || buffer[0] != 'P')
{
throw new InvalidImageContentException("Empty or not an PPM image.");
}
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 pixels 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 InvalidImageContentException("Unknown of not implemented image type encountered.");
}
if (!stream.TrySkipWhitespaceAndComments() ||
!stream.TryReadDecimal(out int width) ||
!stream.TrySkipWhitespaceAndComments() ||
!stream.TryReadDecimal(out int height) ||
!stream.TrySkipWhitespaceAndComments())
{
ThrowPrematureEof();
}
if (this.colorType != PbmColorType.BlackAndWhite)
{
if (!stream.TryReadDecimal(out this.maxPixelValue))
{
ThrowPrematureEof();
}
if (this.maxPixelValue > 255)
{
this.componentType = PbmComponentType.Short;
}
else
{
this.componentType = PbmComponentType.Byte;
}
stream.TrySkipWhitespaceAndComments();
}
else
{
this.componentType = PbmComponentType.Bit;
}
this.pixelSize = new Size(width, height);
this.metadata = new ImageMetadata();
PbmMetadata meta = this.metadata.GetPbmMetadata();
meta.Encoding = this.encoding;
meta.ColorType = this.colorType;
meta.ComponentType = this.componentType;
[DoesNotReturn]
static void ThrowPrematureEof() => throw new InvalidImageContentException("Reached EOF while reading the header.");
}
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.componentType);
}
else
{
PlainDecoder.Process(this.configuration, pixels, stream, this.colorType, this.componentType);
}
}
private void ProcessUpscaling<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
int maxAllocationValue = this.componentType == PbmComponentType.Short ? 65535 : 255;
float factor = maxAllocationValue / this.maxPixelValue;
image.Mutate(x => x.Brightness(factor));
}
private bool NeedsUpscaling() => this.colorType != PbmColorType.BlackAndWhite && this.maxPixelValue is not 255 and not 65535;
}