Browse Source

Merge pull request #1059 from SixLabors/js/format-with-identify

Allow returning the image format with the info.
pull/1060/head
James Jackson-South 6 years ago
committed by GitHub
parent
commit
1017121da7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs
  2. 4
      src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs
  3. 34
      src/ImageSharp/Image.Decode.cs
  4. 39
      src/ImageSharp/Image.FromStream.cs
  5. 43
      tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs

10
src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs

@ -22,9 +22,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp
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
src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs

@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
{
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)
{
@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
return imageType.IsValid();
}
return true;
return false;
}
}
}

34
src/ImageSharp/Image.Decode.cs

@ -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;
@ -47,19 +48,26 @@ namespace SixLabors.ImageSharp
/// <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);
}
}
@ -123,10 +131,14 @@ namespace SixLabors.ImageSharp
/// <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
src/ImageSharp/Image.FromStream.cs

@ -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));
@ -41,26 +41,43 @@ namespace SixLabors.ImageSharp
/// <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>>
@ -126,7 +143,7 @@ namespace SixLabors.ImageSharp
/// 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>
@ -180,7 +197,7 @@ namespace SixLabors.ImageSharp
/// </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>
@ -215,7 +232,7 @@ namespace SixLabors.ImageSharp
/// </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>

43
tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs

@ -3,9 +3,6 @@
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;
@ -13,6 +10,7 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests
{
using System;
using System.Linq;
using System.Reflection;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
@ -167,38 +165,49 @@ namespace SixLabors.ImageSharp.Tests
[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));
}
}
}

Loading…
Cancel
Save