Skip to content

Commit f8573e3

Browse files
Nikita BalabaevNikita Balabaev
authored andcommitted
Add an equivalent for System.Drawing.Image.GetPixelFormatSize() (#258)
1 parent ba6e004 commit f8573e3

22 files changed

Lines changed: 297 additions & 27 deletions

src/ImageSharp/Formats/Bmp/BmpDecoder.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,16 @@ public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
3737

3838
return new BmpDecoderCore(configuration, this).Decode<TPixel>(stream);
3939
}
40+
41+
/// <inheritdoc/>
42+
public int DetectPixelSize(Configuration configuration, Stream stream)
43+
{
44+
Guard.NotNull(stream, "stream");
45+
46+
byte[] buffer = new byte[2];
47+
stream.Skip(28);
48+
stream.Read(buffer, 0, 2);
49+
return BitConverter.ToInt16(buffer, 0);
50+
}
4051
}
4152
}

src/ImageSharp/Formats/Gif/GifDecoder.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,16 @@ public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
3333
var decoder = new GifDecoderCore<TPixel>(configuration, this);
3434
return decoder.Decode(stream);
3535
}
36+
37+
/// <inheritdoc/>
38+
public int DetectPixelSize(Configuration configuration, Stream stream)
39+
{
40+
Guard.NotNull(stream, "stream");
41+
42+
byte[] buffer = new byte[1];
43+
stream.Skip(10); // Skip the identifier and size
44+
stream.Read(buffer, 0, 1); // Skip the identifier and size
45+
return (buffer[0] & 0x07) + 1; // The lowest 3 bits represent the bit depth minus 1
46+
}
3647
}
3748
}

src/ImageSharp/Formats/IImageDecoder.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,13 @@ public interface IImageDecoder
2525
/// <returns>The decoded image</returns>
2626
Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
2727
where TPixel : struct, IPixel<TPixel>;
28+
29+
/// <summary>
30+
/// Detects the image pixel size from the specified stream.
31+
/// </summary>
32+
/// <param name="configuration">The configuration for the image.</param>
33+
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
34+
/// <returns>The color depth, in number of bits per pixel</returns>
35+
int DetectPixelSize(Configuration configuration, Stream stream);
2836
}
2937
}

src/ImageSharp/Formats/Jpeg/JpegDecoder.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,16 @@ public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
3232
return decoder.Decode<TPixel>(stream);
3333
}
3434
}
35+
36+
/// <inheritdoc/>
37+
public int DetectPixelSize(Configuration configuration, Stream stream)
38+
{
39+
Guard.NotNull(stream, "stream");
40+
41+
using (JpegDecoderCore decoder = new JpegDecoderCore(configuration, this))
42+
{
43+
return decoder.DetectPixelSize(stream);
44+
}
45+
}
3546
}
3647
}

src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ internal sealed unsafe class JpegDecoderCore : IDisposable
3030
/// </summary>
3131
public const int MaxTq = 3;
3232

33+
/// <summary>
34+
/// The only supported precision
35+
/// </summary>
36+
public const int SupportedPrecision = 8;
37+
3338
// Complex value type field + mutable + available to other classes = the field MUST NOT be private :P
3439
#pragma warning disable SA1401 // FieldsMustBePrivate
3540

@@ -191,7 +196,7 @@ public JpegDecoderCore(Configuration configuration, IJpegDecoderOptions options)
191196
public bool IgnoreMetadata { get; private set; }
192197

193198
/// <summary>
194-
/// Decodes the image from the specified <see cref="Stream"/> and sets
199+
/// Decodes the image from the specified <see cref="Stream"/> and sets
195200
/// the data to image.
196201
/// </summary>
197202
/// <typeparam name="TPixel">The pixel format.</typeparam>
@@ -208,6 +213,17 @@ public Image<TPixel> Decode<TPixel>(Stream stream)
208213
return image;
209214
}
210215

216+
/// <summary>
217+
/// Detects the image pixel size from the specified stream.
218+
/// </summary>
219+
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
220+
/// <returns>The color depth, in number of bits per pixel</returns>
221+
public int DetectPixelSize(Stream stream)
222+
{
223+
this.ProcessStream(new ImageMetaData(), stream, true);
224+
return this.ComponentCount * SupportedPrecision;
225+
}
226+
211227
/// <summary>
212228
/// Dispose
213229
/// </summary>
@@ -1196,7 +1212,7 @@ private void ProcessStartOfFrameMarker(int remaining)
11961212
this.InputProcessor.ReadFull(this.Temp, 0, remaining);
11971213

11981214
// We only support 8-bit precision.
1199-
if (this.Temp[0] != 8)
1215+
if (this.Temp[0] != SupportedPrecision)
12001216
{
12011217
throw new ImageFormatException("Only 8-Bit precision supported.");
12021218
}
@@ -1238,7 +1254,7 @@ private void ProcessStartOfFrameMarker(int remaining)
12381254

12391255
if (h == 3 || v == 3)
12401256
{
1241-
throw new ImageFormatException("Lnsupported subsampling ratio");
1257+
throw new ImageFormatException("Unsupported subsampling ratio");
12421258
}
12431259

12441260
switch (this.ComponentCount)

src/ImageSharp/Formats/Png/PngDecoder.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,17 @@ public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
5656
var decoder = new PngDecoderCore(configuration, this);
5757
return decoder.Decode<TPixel>(stream);
5858
}
59+
60+
/// <summary>
61+
/// Detects the image pixel size from the specified stream.
62+
/// </summary>
63+
/// <param name="configuration">The configuration for the image.</param>
64+
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
65+
/// <returns>The color depth, in number of bits per pixel</returns>
66+
public int DetectPixelSize(Configuration configuration, Stream stream)
67+
{
68+
var decoder = new PngDecoderCore(configuration, this);
69+
return decoder.DetectPixelSize(stream);
70+
}
5971
}
6072
}

src/ImageSharp/Formats/Png/PngDecoderCore.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,58 @@ public Image<TPixel> Decode<TPixel>(Stream stream)
261261
}
262262
}
263263

264+
/// <summary>
265+
/// Detects the image pixel size from the specified stream.
266+
/// </summary>
267+
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
268+
/// <returns>The color depth, in number of bits per pixel</returns>
269+
public int DetectPixelSize(Stream stream)
270+
{
271+
this.currentStream = stream;
272+
this.currentStream.Skip(8);
273+
try
274+
{
275+
PngChunk currentChunk;
276+
while (!this.isEndChunkReached && (currentChunk = this.ReadChunk()) != null)
277+
{
278+
try
279+
{
280+
switch (currentChunk.Type)
281+
{
282+
case PngChunkTypes.Header:
283+
this.ReadHeaderChunk(currentChunk.Data);
284+
this.ValidateHeader();
285+
this.isEndChunkReached = true;
286+
break;
287+
case PngChunkTypes.End:
288+
this.isEndChunkReached = true;
289+
break;
290+
}
291+
}
292+
finally
293+
{
294+
// Data is rented in ReadChunkData()
295+
if (currentChunk.Data != null)
296+
{
297+
ArrayPool<byte>.Shared.Return(currentChunk.Data);
298+
}
299+
}
300+
}
301+
}
302+
finally
303+
{
304+
this.scanline?.Dispose();
305+
this.previousScanline?.Dispose();
306+
}
307+
308+
if (this.header == null)
309+
{
310+
throw new ImageFormatException("PNG Image hasn't header chunk");
311+
}
312+
313+
return this.CalculateBitsPerPixel();
314+
}
315+
264316
/// <summary>
265317
/// Converts a byte array to a new array where each value in the original array is represented by the specified number of bits.
266318
/// </summary>
@@ -343,6 +395,28 @@ private void InitializeImage<TPixel>(ImageMetaData metadata, out Image<TPixel> i
343395
this.scanline = Buffer<byte>.CreateClean(this.bytesPerScanline);
344396
}
345397

398+
/// <summary>
399+
/// Calculates the correct number of bits per pixel for the given color type.
400+
/// </summary>
401+
/// <returns>The <see cref="int"/></returns>
402+
private int CalculateBitsPerPixel()
403+
{
404+
switch (this.pngColorType)
405+
{
406+
case PngColorType.Grayscale:
407+
case PngColorType.Palette:
408+
return this.header.BitDepth;
409+
case PngColorType.GrayscaleWithAlpha:
410+
return this.header.BitDepth * 2;
411+
case PngColorType.Rgb:
412+
return this.header.BitDepth * 3;
413+
case PngColorType.RgbWithAlpha:
414+
return this.header.BitDepth * 4;
415+
default:
416+
throw new NotSupportedException("Unsupported PNG color type");
417+
}
418+
}
419+
346420
/// <summary>
347421
/// Calculates the correct number of bytes per pixel for the given color type.
348422
/// </summary>

src/ImageSharp/Image/Image.Decode.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,5 +81,19 @@ private static (Image<TPixel> img, IImageFormat format) Decode<TPixel>(Stream st
8181
Image<TPixel> img = decoder.Decode<TPixel>(config, stream);
8282
return (img, format);
8383
}
84+
85+
/// <summary>
86+
/// Detects the image pixel size.
87+
/// </summary>
88+
/// <param name="stream">The stream.</param>
89+
/// <param name="config">the configuration.</param>
90+
/// <returns>
91+
/// The color depth, in number of bits per pixel or null if suitable decoder not found.
92+
/// </returns>
93+
private static int? InternalDetectPixelSize(Stream stream, Configuration config)
94+
{
95+
IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat _);
96+
return decoder?.DetectPixelSize(config, stream);
97+
}
8498
}
8599
}

src/ImageSharp/Image/Image.FromStream.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,33 @@ public static IImageFormat DetectFormat(Configuration config, Stream stream)
3939
return WithSeekableStream(stream, s => InternalDetectFormat(s, config ?? Configuration.Default));
4040
}
4141

42+
/// <summary>
43+
/// By reading the header on the provided stream this calculates the images color depth.
44+
/// </summary>
45+
/// <param name="stream">The image stream to read the header from.</param>
46+
/// <exception cref="NotSupportedException">
47+
/// Thrown if the stream is not readable nor seekable.
48+
/// </exception>
49+
/// <returns>The color depth, in number of bits per pixel or null if suitable decoder not found</returns>
50+
public static int? DetectPixelSize(Stream stream)
51+
{
52+
return DetectPixelSize(null, stream);
53+
}
54+
55+
/// <summary>
56+
/// By reading the header on the provided stream this calculates the images color depth.
57+
/// </summary>
58+
/// <param name="config">The configuration.</param>
59+
/// <param name="stream">The image stream to read the header from.</param>
60+
/// <exception cref="NotSupportedException">
61+
/// Thrown if the stream is not readable nor seekable.
62+
/// </exception>
63+
/// <returns>The color depth, in number of bits per pixel or null if suitable decoder not found</returns>
64+
public static int? DetectPixelSize(Configuration config, Stream stream)
65+
{
66+
return WithSeekableStream(stream, s => InternalDetectPixelSize(s, config ?? Configuration.Default));
67+
}
68+
4269
/// <summary>
4370
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream.
4471
/// </summary>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// <copyright file="GifDecoderTests.cs" company="James Jackson-South">
2+
// Copyright (c) James Jackson-South and contributors.
3+
// Licensed under the Apache License, Version 2.0.
4+
// </copyright>
5+
6+
// ReSharper disable InconsistentNaming
7+
namespace ImageSharp.Tests
8+
{
9+
using System.IO;
10+
11+
using Xunit;
12+
13+
public class BmpDecoderTests
14+
{
15+
[Theory]
16+
[InlineData(TestImages.Bmp.Car, 24)]
17+
[InlineData(TestImages.Bmp.F, 24)]
18+
[InlineData(TestImages.Bmp.NegHeight, 24)]
19+
[InlineData(TestImages.Bmp.Bpp8, 8)]
20+
public void DetectPixelSize(string imagePath, int expectedPixelSize)
21+
{
22+
TestFile testFile = TestFile.Create(imagePath);
23+
using (var stream = new MemoryStream(testFile.Bytes, false))
24+
{
25+
Assert.Equal(expectedPixelSize, Image.DetectPixelSize(stream));
26+
}
27+
}
28+
}
29+
}

0 commit comments

Comments
 (0)