Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@ public IImageFormat DetectFormat(ReadOnlySpan<byte> header)

private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
{
short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(header);
return header.Length >= this.HeaderSize &&
(fileTypeMarker == BmpConstants.TypeMarkers.Bitmap || fileTypeMarker == BmpConstants.TypeMarkers.BitmapArray);
if (header.Length >= this.HeaderSize)
{
short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(header);
return fileTypeMarker == BmpConstants.TypeMarkers.Bitmap || fileTypeMarker == BmpConstants.TypeMarkers.BitmapArray;
}

return false;
}
}
}
4 changes: 2 additions & 2 deletions src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
{
if (header.Length >= this.HeaderSize)
{
// There is no magick bytes in a tga file, so at least the image type
// There are no magic bytes in a tga file, so at least the image type
// and the colormap type in the header will be checked for a valid value.
if (header[1] != 0 && header[1] != 1)
{
Expand All @@ -34,7 +34,7 @@ private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
return imageType.IsValid();
}

return true;
return false;
}
}
}
34 changes: 23 additions & 11 deletions src/ImageSharp/Image.Decode.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

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

using (IManagedByteBuffer buffer = config.MemoryAllocator.AllocateManagedByteBuffer(maxHeaderSize, AllocationOptions.Clean))
using (IManagedByteBuffer buffer = config.MemoryAllocator.AllocateManagedByteBuffer(headerSize, AllocationOptions.Clean))
{
long startPosition = stream.Position;
stream.Read(buffer.Array, 0, maxHeaderSize);
stream.Read(buffer.Array, 0, headerSize);
stream.Position = startPosition;
return config.ImageFormatsManager.FormatDetectors.Select(x => x.DetectFormat(buffer.GetSpan())).LastOrDefault(x => x != null);

// Does the given stream contain enough data to fit in the header for the format
// and does that data match the format specification?
// Individual formats should still check since they are public.
return config.ImageFormatsManager.FormatDetectors
.Where(x => x.HeaderSize <= headerSize)
.Select(x => x.DetectFormat(buffer.GetSpan())).LastOrDefault(x => x != null);
}
}

Expand Down Expand Up @@ -123,10 +131,14 @@ private static (Image img, IImageFormat format) Decode(Stream stream, Configurat
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns>
private static IImageInfo InternalIdentity(Stream stream, Configuration config)
private static (IImageInfo info, IImageFormat format) InternalIdentity(Stream stream, Configuration config)
{
var detector = DiscoverDecoder(stream, config, out IImageFormat _) as IImageInfoDetector;
return detector?.Identify(config, stream);
if (!(DiscoverDecoder(stream, config, out IImageFormat format) is IImageInfoDetector detector))
{
return (null, null);
}

return (detector?.Identify(config, stream), format);
}
}
}
}
39 changes: 28 additions & 11 deletions src/ImageSharp/Image.FromStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@ namespace SixLabors.ImageSharp
public abstract partial class Image
{
/// <summary>
/// By reading the header on the provided stream this calculates the images mime type.
/// By reading the header on the provided stream this calculates the images format type.
/// </summary>
/// <param name="stream">The image stream to read the header from.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <returns>The mime type or null if none found.</returns>
/// <returns>The format type or null if none found.</returns>
public static IImageFormat DetectFormat(Stream stream) => DetectFormat(Configuration.Default, stream);

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

Expand All @@ -41,26 +41,43 @@ public static IImageFormat DetectFormat(Configuration config, Stream stream)
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns>
public static IImageInfo Identify(Stream stream) => Identify(Configuration.Default, stream);
public static IImageInfo Identify(Stream stream) => Identify(stream, out IImageFormat _);

/// <summary>
/// By reading the header on the provided stream this reads the raw image information.
/// </summary>
/// <param name="stream">The image stream to read the header from.</param>
/// <param name="format">The format type of the decoded image.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns>
public static IImageInfo Identify(Stream stream, out IImageFormat format) => Identify(Configuration.Default, stream, out format);

/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="config">The configuration.</param>
/// <param name="stream">The image stream to read the information from.</param>
/// <param name="format">The format type of the decoded image.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector is not found.
/// </returns>
public static IImageInfo Identify(Configuration config, Stream stream)
=> WithSeekableStream(config, stream, s => InternalIdentity(s, config ?? Configuration.Default));
public static IImageInfo Identify(Configuration config, Stream stream, out IImageFormat format)
{
(IImageInfo info, IImageFormat format) data = WithSeekableStream(config, stream, s => InternalIdentity(s, config ?? Configuration.Default));

format = data.format;
return data.info;
}

/// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream.
/// The pixel format is selected by the decoder.
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <param name="format">the mime type of the decoded image.</param>
/// <param name="format">The format type of the decoded image.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception>
/// <returns>A new <see cref="Image"/>.</returns>>
Expand Down Expand Up @@ -126,7 +143,7 @@ public static Image<TPixel> Load<TPixel>(Stream stream)
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <param name="format">the mime type of the decoded image.</param>
/// <param name="format">The format type of the decoded image.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
Expand Down Expand Up @@ -180,7 +197,7 @@ public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream)
/// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="stream">The stream containing image information.</param>
/// <param name="format">the mime type of the decoded image.</param>
/// <param name="format">The format type of the decoded image.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
Expand Down Expand Up @@ -215,7 +232,7 @@ public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream, ou
/// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="stream">The stream containing image information.</param>
/// <param name="format">the mime type of the decoded image.</param>
/// <param name="format">The format type of the decoded image.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
Expand Down
43 changes: 26 additions & 17 deletions tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@

using System.IO;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;

namespace SixLabors.ImageSharp.Tests
{
using System;
using System.Linq;
using System.Reflection;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
Expand Down Expand Up @@ -167,38 +165,49 @@ public void ImageShouldPreservePixelByteOrderWhenSerialized()
[InlineData(100, 100, "jpg")]
[InlineData(100, 10, "jpg")]
[InlineData(10, 100, "jpg")]
public void CanIdentifyImageLoadedFromBytes(int width, int height, string format)
[InlineData(100, 100, "tga")]
[InlineData(100, 10, "tga")]
[InlineData(10, 100, "tga")]
public void CanIdentifyImageLoadedFromBytes(int width, int height, string extension)
{
using (var image = Image.LoadPixelData(new Rgba32[width * height], width, height))
{
using (var memoryStream = new MemoryStream())
{
image.Save(memoryStream, GetEncoder(format));
IImageFormat format = GetFormat(extension);
image.Save(memoryStream, format);
memoryStream.Position = 0;

IImageInfo imageInfo = Image.Identify(memoryStream);

Assert.Equal(imageInfo.Width, width);
Assert.Equal(imageInfo.Height, height);
memoryStream.Position = 0;

imageInfo = Image.Identify(memoryStream, out IImageFormat detectedFormat);

Assert.Equal(format, detectedFormat);
}
}
}

private static IImageEncoder GetEncoder(string format)
[Fact]
public void IdentifyReturnsNullWithInvalidStream()
{
switch (format)
byte[] invalid = new byte[10];

using (var memoryStream = new MemoryStream(invalid))
{
case "png":
return new PngEncoder();
case "gif":
return new GifEncoder();
case "bmp":
return new BmpEncoder();
case "jpg":
return new JpegEncoder();
default:
throw new ArgumentOutOfRangeException(nameof(format), format, null);
IImageInfo imageInfo = Image.Identify(memoryStream, out IImageFormat format);

Assert.Null(imageInfo);
Assert.Null(format);
}
}

private static IImageFormat GetFormat(string format)
{
return Configuration.Default.ImageFormats.FirstOrDefault(x => x.FileExtensions.Contains(format));
}
}
}