Browse Source

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

Allow returning the image format with the info.
af/merge-core
James Jackson-South 7 years ago
committed by GitHub
parent
commit
bb014a90df
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) private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
{ {
short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(header); if (header.Length >= this.HeaderSize)
return header.Length >= this.HeaderSize && {
(fileTypeMarker == BmpConstants.TypeMarkers.Bitmap || fileTypeMarker == BmpConstants.TypeMarkers.BitmapArray); 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) 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. // and the colormap type in the header will be checked for a valid value.
if (header[1] != 0 && header[1] != 1) if (header[1] != 0 && header[1] != 1)
{ {
@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
return imageType.IsValid(); 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. // Licensed under the Apache License, Version 2.0.
using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
@ -47,19 +48,26 @@ namespace SixLabors.ImageSharp
/// <returns>The mime type or null if none found.</returns> /// <returns>The mime type or null if none found.</returns>
private static IImageFormat InternalDetectFormat(Stream stream, Configuration config) private static IImageFormat InternalDetectFormat(Stream stream, Configuration config)
{ {
// This is probably a candidate for making into a public API in the future! // We take a minimum of the stream length vs the max header size and always check below
int maxHeaderSize = config.MaxHeaderSize; // to ensure that only formats that headers fit within the given buffer length are tested.
if (maxHeaderSize <= 0) int headerSize = (int)Math.Min(config.MaxHeaderSize, stream.Length);
if (headerSize <= 0)
{ {
return null; return null;
} }
using (IManagedByteBuffer buffer = config.MemoryAllocator.AllocateManagedByteBuffer(maxHeaderSize, AllocationOptions.Clean)) using (IManagedByteBuffer buffer = config.MemoryAllocator.AllocateManagedByteBuffer(headerSize, AllocationOptions.Clean))
{ {
long startPosition = stream.Position; long startPosition = stream.Position;
stream.Read(buffer.Array, 0, maxHeaderSize); stream.Read(buffer.Array, 0, headerSize);
stream.Position = startPosition; 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> /// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found. /// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns> /// </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; if (!(DiscoverDecoder(stream, config, out IImageFormat format) is IImageInfoDetector detector))
return detector?.Identify(config, stream); {
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 public abstract partial class Image
{ {
/// <summary> /// <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> /// </summary>
/// <param name="stream">The image stream to read the header from.</param> /// <param name="stream">The image stream to read the header from.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception> /// <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); public static IImageFormat DetectFormat(Stream stream) => DetectFormat(Configuration.Default, stream);
/// <summary> /// <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> /// </summary>
/// <param name="config">The configuration.</param> /// <param name="config">The configuration.</param>
/// <param name="stream">The image stream to read the header from.</param> /// <param name="stream">The image stream to read the header from.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception> /// <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) public static IImageFormat DetectFormat(Configuration config, Stream stream)
=> WithSeekableStream(config, stream, s => InternalDetectFormat(s, config)); => WithSeekableStream(config, stream, s => InternalDetectFormat(s, config));
@ -41,26 +41,43 @@ namespace SixLabors.ImageSharp
/// <returns> /// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found. /// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns> /// </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> /// <summary>
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified stream without fully decoding it.
/// </summary> /// </summary>
/// <param name="config">The configuration.</param> /// <param name="config">The configuration.</param>
/// <param name="stream">The image stream to read the information from.</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> /// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <returns> /// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector is not found. /// The <see cref="IImageInfo"/> or null if suitable info detector is not found.
/// </returns> /// </returns>
public static IImageInfo Identify(Configuration config, Stream stream) public static IImageInfo Identify(Configuration config, Stream stream, out IImageFormat format)
=> WithSeekableStream(config, stream, s => InternalIdentity(s, config ?? Configuration.Default)); {
(IImageInfo info, IImageFormat format) data = WithSeekableStream(config, stream, s => InternalIdentity(s, config ?? Configuration.Default));
format = data.format;
return data.info;
}
/// <summary> /// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream. /// Decode a new instance of the <see cref="Image"/> class from the given stream.
/// The pixel format is selected by the decoder. /// The pixel format is selected by the decoder.
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</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="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception> /// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception>
/// <returns>A new <see cref="Image"/>.</returns>> /// <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. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</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="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception> /// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
@ -180,7 +197,7 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="config">The configuration options.</param> /// <param name="config">The configuration options.</param>
/// <param name="stream">The stream containing image information.</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="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception> /// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
@ -215,7 +232,7 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="config">The configuration options.</param> /// <param name="config">The configuration options.</param>
/// <param name="stream">The stream containing image information.</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="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception> /// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>

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

@ -3,9 +3,6 @@
using System.IO; using System.IO;
using SixLabors.ImageSharp.Formats; 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.Formats.Png;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using Xunit; using Xunit;
@ -13,6 +10,7 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests namespace SixLabors.ImageSharp.Tests
{ {
using System; using System;
using System.Linq;
using System.Reflection; using System.Reflection;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;
@ -167,38 +165,49 @@ namespace SixLabors.ImageSharp.Tests
[InlineData(100, 100, "jpg")] [InlineData(100, 100, "jpg")]
[InlineData(100, 10, "jpg")] [InlineData(100, 10, "jpg")]
[InlineData(10, 100, "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 image = Image.LoadPixelData(new Rgba32[width * height], width, height))
{ {
using (var memoryStream = new MemoryStream()) using (var memoryStream = new MemoryStream())
{ {
image.Save(memoryStream, GetEncoder(format)); IImageFormat format = GetFormat(extension);
image.Save(memoryStream, format);
memoryStream.Position = 0; memoryStream.Position = 0;
IImageInfo imageInfo = Image.Identify(memoryStream); IImageInfo imageInfo = Image.Identify(memoryStream);
Assert.Equal(imageInfo.Width, width); Assert.Equal(imageInfo.Width, width);
Assert.Equal(imageInfo.Height, height); 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": IImageInfo imageInfo = Image.Identify(memoryStream, out IImageFormat format);
return new PngEncoder();
case "gif": Assert.Null(imageInfo);
return new GifEncoder(); Assert.Null(format);
case "bmp":
return new BmpEncoder();
case "jpg":
return new JpegEncoder();
default:
throw new ArgumentOutOfRangeException(nameof(format), format, null);
} }
} }
private static IImageFormat GetFormat(string format)
{
return Configuration.Default.ImageFormats.FirstOrDefault(x => x.FileExtensions.Contains(format));
}
} }
} }

Loading…
Cancel
Save