Skip to content

Commit 1017121

Browse files
Merge pull request #1059 from SixLabors/js/format-with-identify
Allow returning the image format with the info.
2 parents 0322ee2 + 8762bde commit 1017121

File tree

5 files changed

+86
-44
lines changed

5 files changed

+86
-44
lines changed

src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@ public IImageFormat DetectFormat(ReadOnlySpan<byte> header)
2222

2323
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
2424
{
25-
short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(header);
26-
return header.Length >= this.HeaderSize &&
27-
(fileTypeMarker == BmpConstants.TypeMarkers.Bitmap || fileTypeMarker == BmpConstants.TypeMarkers.BitmapArray);
25+
if (header.Length >= this.HeaderSize)
26+
{
27+
short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(header);
28+
return fileTypeMarker == BmpConstants.TypeMarkers.Bitmap || fileTypeMarker == BmpConstants.TypeMarkers.BitmapArray;
29+
}
30+
31+
return false;
2832
}
2933
}
3034
}

src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
2323
{
2424
if (header.Length >= this.HeaderSize)
2525
{
26-
// There is no magick bytes in a tga file, so at least the image type
26+
// There are no magic bytes in a tga file, so at least the image type
2727
// and the colormap type in the header will be checked for a valid value.
2828
if (header[1] != 0 && header[1] != 1)
2929
{
@@ -34,7 +34,7 @@ private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
3434
return imageType.IsValid();
3535
}
3636

37-
return true;
37+
return false;
3838
}
3939
}
4040
}

src/ImageSharp/Image.Decode.cs

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
// Copyright (c) Six Labors and contributors.
1+
// Copyright (c) Six Labors and contributors.
22
// Licensed under the Apache License, Version 2.0.
33

4+
using System;
45
using System.IO;
56
using System.Linq;
67
using SixLabors.ImageSharp.Formats;
@@ -47,19 +48,26 @@ internal static Image<TPixel> CreateUninitialized<TPixel>(
4748
/// <returns>The mime type or null if none found.</returns>
4849
private static IImageFormat InternalDetectFormat(Stream stream, Configuration config)
4950
{
50-
// This is probably a candidate for making into a public API in the future!
51-
int maxHeaderSize = config.MaxHeaderSize;
52-
if (maxHeaderSize <= 0)
51+
// We take a minimum of the stream length vs the max header size and always check below
52+
// to ensure that only formats that headers fit within the given buffer length are tested.
53+
int headerSize = (int)Math.Min(config.MaxHeaderSize, stream.Length);
54+
if (headerSize <= 0)
5355
{
5456
return null;
5557
}
5658

57-
using (IManagedByteBuffer buffer = config.MemoryAllocator.AllocateManagedByteBuffer(maxHeaderSize, AllocationOptions.Clean))
59+
using (IManagedByteBuffer buffer = config.MemoryAllocator.AllocateManagedByteBuffer(headerSize, AllocationOptions.Clean))
5860
{
5961
long startPosition = stream.Position;
60-
stream.Read(buffer.Array, 0, maxHeaderSize);
62+
stream.Read(buffer.Array, 0, headerSize);
6163
stream.Position = startPosition;
62-
return config.ImageFormatsManager.FormatDetectors.Select(x => x.DetectFormat(buffer.GetSpan())).LastOrDefault(x => x != null);
64+
65+
// Does the given stream contain enough data to fit in the header for the format
66+
// and does that data match the format specification?
67+
// Individual formats should still check since they are public.
68+
return config.ImageFormatsManager.FormatDetectors
69+
.Where(x => x.HeaderSize <= headerSize)
70+
.Select(x => x.DetectFormat(buffer.GetSpan())).LastOrDefault(x => x != null);
6371
}
6472
}
6573

@@ -123,10 +131,14 @@ private static (Image img, IImageFormat format) Decode(Stream stream, Configurat
123131
/// <returns>
124132
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
125133
/// </returns>
126-
private static IImageInfo InternalIdentity(Stream stream, Configuration config)
134+
private static (IImageInfo info, IImageFormat format) InternalIdentity(Stream stream, Configuration config)
127135
{
128-
var detector = DiscoverDecoder(stream, config, out IImageFormat _) as IImageInfoDetector;
129-
return detector?.Identify(config, stream);
136+
if (!(DiscoverDecoder(stream, config, out IImageFormat format) is IImageInfoDetector detector))
137+
{
138+
return (null, null);
139+
}
140+
141+
return (detector?.Identify(config, stream), format);
130142
}
131143
}
132-
}
144+
}

src/ImageSharp/Image.FromStream.cs

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,20 @@ namespace SixLabors.ImageSharp
1616
public abstract partial class Image
1717
{
1818
/// <summary>
19-
/// By reading the header on the provided stream this calculates the images mime type.
19+
/// By reading the header on the provided stream this calculates the images format type.
2020
/// </summary>
2121
/// <param name="stream">The image stream to read the header from.</param>
2222
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
23-
/// <returns>The mime type or null if none found.</returns>
23+
/// <returns>The format type or null if none found.</returns>
2424
public static IImageFormat DetectFormat(Stream stream) => DetectFormat(Configuration.Default, stream);
2525

2626
/// <summary>
27-
/// By reading the header on the provided stream this calculates the images mime type.
27+
/// By reading the header on the provided stream this calculates the images format type.
2828
/// </summary>
2929
/// <param name="config">The configuration.</param>
3030
/// <param name="stream">The image stream to read the header from.</param>
3131
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
32-
/// <returns>The mime type or null if none found.</returns>
32+
/// <returns>The format type or null if none found.</returns>
3333
public static IImageFormat DetectFormat(Configuration config, Stream stream)
3434
=> WithSeekableStream(config, stream, s => InternalDetectFormat(s, config));
3535

@@ -41,26 +41,43 @@ public static IImageFormat DetectFormat(Configuration config, Stream stream)
4141
/// <returns>
4242
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
4343
/// </returns>
44-
public static IImageInfo Identify(Stream stream) => Identify(Configuration.Default, stream);
44+
public static IImageInfo Identify(Stream stream) => Identify(stream, out IImageFormat _);
45+
46+
/// <summary>
47+
/// By reading the header on the provided stream this reads the raw image information.
48+
/// </summary>
49+
/// <param name="stream">The image stream to read the header from.</param>
50+
/// <param name="format">The format type of the decoded image.</param>
51+
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
52+
/// <returns>
53+
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
54+
/// </returns>
55+
public static IImageInfo Identify(Stream stream, out IImageFormat format) => Identify(Configuration.Default, stream, out format);
4556

4657
/// <summary>
4758
/// Reads the raw image information from the specified stream without fully decoding it.
4859
/// </summary>
4960
/// <param name="config">The configuration.</param>
5061
/// <param name="stream">The image stream to read the information from.</param>
62+
/// <param name="format">The format type of the decoded image.</param>
5163
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
5264
/// <returns>
5365
/// The <see cref="IImageInfo"/> or null if suitable info detector is not found.
5466
/// </returns>
55-
public static IImageInfo Identify(Configuration config, Stream stream)
56-
=> WithSeekableStream(config, stream, s => InternalIdentity(s, config ?? Configuration.Default));
67+
public static IImageInfo Identify(Configuration config, Stream stream, out IImageFormat format)
68+
{
69+
(IImageInfo info, IImageFormat format) data = WithSeekableStream(config, stream, s => InternalIdentity(s, config ?? Configuration.Default));
70+
71+
format = data.format;
72+
return data.info;
73+
}
5774

5875
/// <summary>
5976
/// Decode a new instance of the <see cref="Image"/> class from the given stream.
6077
/// The pixel format is selected by the decoder.
6178
/// </summary>
6279
/// <param name="stream">The stream containing image information.</param>
63-
/// <param name="format">the mime type of the decoded image.</param>
80+
/// <param name="format">The format type of the decoded image.</param>
6481
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
6582
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception>
6683
/// <returns>A new <see cref="Image"/>.</returns>>
@@ -126,7 +143,7 @@ public static Image<TPixel> Load<TPixel>(Stream stream)
126143
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
127144
/// </summary>
128145
/// <param name="stream">The stream containing image information.</param>
129-
/// <param name="format">the mime type of the decoded image.</param>
146+
/// <param name="format">The format type of the decoded image.</param>
130147
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
131148
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception>
132149
/// <typeparam name="TPixel">The pixel format.</typeparam>
@@ -180,7 +197,7 @@ public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream)
180197
/// </summary>
181198
/// <param name="config">The configuration options.</param>
182199
/// <param name="stream">The stream containing image information.</param>
183-
/// <param name="format">the mime type of the decoded image.</param>
200+
/// <param name="format">The format type of the decoded image.</param>
184201
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
185202
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception>
186203
/// <typeparam name="TPixel">The pixel format.</typeparam>
@@ -215,7 +232,7 @@ public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream, ou
215232
/// </summary>
216233
/// <param name="config">The configuration options.</param>
217234
/// <param name="stream">The stream containing image information.</param>
218-
/// <param name="format">the mime type of the decoded image.</param>
235+
/// <param name="format">The format type of the decoded image.</param>
219236
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
220237
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception>
221238
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>

tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,14 @@
33

44
using System.IO;
55
using SixLabors.ImageSharp.Formats;
6-
using SixLabors.ImageSharp.Formats.Bmp;
7-
using SixLabors.ImageSharp.Formats.Gif;
8-
using SixLabors.ImageSharp.Formats.Jpeg;
96
using SixLabors.ImageSharp.Formats.Png;
107
using SixLabors.ImageSharp.PixelFormats;
118
using Xunit;
129

1310
namespace SixLabors.ImageSharp.Tests
1411
{
1512
using System;
13+
using System.Linq;
1614
using System.Reflection;
1715
using SixLabors.ImageSharp.Processing;
1816
using SixLabors.ImageSharp.Processing.Processors.Quantization;
@@ -167,38 +165,49 @@ public void ImageShouldPreservePixelByteOrderWhenSerialized()
167165
[InlineData(100, 100, "jpg")]
168166
[InlineData(100, 10, "jpg")]
169167
[InlineData(10, 100, "jpg")]
170-
public void CanIdentifyImageLoadedFromBytes(int width, int height, string format)
168+
[InlineData(100, 100, "tga")]
169+
[InlineData(100, 10, "tga")]
170+
[InlineData(10, 100, "tga")]
171+
public void CanIdentifyImageLoadedFromBytes(int width, int height, string extension)
171172
{
172173
using (var image = Image.LoadPixelData(new Rgba32[width * height], width, height))
173174
{
174175
using (var memoryStream = new MemoryStream())
175176
{
176-
image.Save(memoryStream, GetEncoder(format));
177+
IImageFormat format = GetFormat(extension);
178+
image.Save(memoryStream, format);
177179
memoryStream.Position = 0;
178180

179181
IImageInfo imageInfo = Image.Identify(memoryStream);
180182

181183
Assert.Equal(imageInfo.Width, width);
182184
Assert.Equal(imageInfo.Height, height);
185+
memoryStream.Position = 0;
186+
187+
imageInfo = Image.Identify(memoryStream, out IImageFormat detectedFormat);
188+
189+
Assert.Equal(format, detectedFormat);
183190
}
184191
}
185192
}
186193

187-
private static IImageEncoder GetEncoder(string format)
194+
[Fact]
195+
public void IdentifyReturnsNullWithInvalidStream()
188196
{
189-
switch (format)
197+
byte[] invalid = new byte[10];
198+
199+
using (var memoryStream = new MemoryStream(invalid))
190200
{
191-
case "png":
192-
return new PngEncoder();
193-
case "gif":
194-
return new GifEncoder();
195-
case "bmp":
196-
return new BmpEncoder();
197-
case "jpg":
198-
return new JpegEncoder();
199-
default:
200-
throw new ArgumentOutOfRangeException(nameof(format), format, null);
201+
IImageInfo imageInfo = Image.Identify(memoryStream, out IImageFormat format);
202+
203+
Assert.Null(imageInfo);
204+
Assert.Null(format);
201205
}
202206
}
207+
208+
private static IImageFormat GetFormat(string format)
209+
{
210+
return Configuration.Default.ImageFormats.FirstOrDefault(x => x.FileExtensions.Contains(format));
211+
}
203212
}
204213
}

0 commit comments

Comments
 (0)