diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index 7d94e1bebc..1058dd19c0 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -80,6 +80,125 @@ namespace SixLabors.ImageSharp } } + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The image file to open and to read the header from. + /// The configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static Task IdentifyAsync(string filePath) + => IdentifyAsync(Configuration.Default, filePath, default); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The image file to open and to read the header from. + /// The configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static Task IdentifyAsync(Configuration configuration, string filePath) + => IdentifyAsync(configuration, filePath, default); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The image file to open and to read the header from. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static Task IdentifyAsync(string filePath, CancellationToken cancellationToken) + => IdentifyAsync(Configuration.Default, filePath, cancellationToken); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The image file to open and to read the header from. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static async Task IdentifyAsync(Configuration configuration, string filePath, CancellationToken cancellationToken) + { + (IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(configuration, filePath, cancellationToken); + return res.ImageInfo; + } + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The image file to open and to read the header from. + /// The configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(string filePath) + => IdentifyWithFormatAsync(Configuration.Default, filePath, default); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The image file to open and to read the header from. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( + string filePath, + CancellationToken cancellationToken) + => IdentifyWithFormatAsync(Configuration.Default, filePath, cancellationToken); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The image file to open and to read the header from. + /// The configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( + Configuration configuration, + string filePath) + => IdentifyWithFormatAsync(configuration, filePath, default); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The image file to open and to read the header from. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static async Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( + Configuration configuration, + string filePath, + CancellationToken cancellationToken) + { + Guard.NotNull(configuration, nameof(configuration)); + using Stream stream = configuration.FileSystem.OpenRead(filePath); + return await IdentifyWithFormatAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); + } + /// /// Create a new instance of the class from the given file. /// @@ -182,6 +301,20 @@ namespace SixLabors.ImageSharp } } + /// + /// Create a new instance of the class from the given file. + /// + /// The file path to the image. + /// The decoder. + /// The configuration is null. + /// The path is null. + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static Task LoadAsync(string path, IImageDecoder decoder) + => LoadAsync(Configuration.Default, path, decoder, default); + /// /// Create a new instance of the class from the given file. /// @@ -197,6 +330,22 @@ namespace SixLabors.ImageSharp public static Task LoadAsync(Configuration configuration, string path, IImageDecoder decoder) => LoadAsync(configuration, path, decoder, default); + /// + /// Create a new instance of the class from the given file. + /// + /// The file path to the image. + /// The decoder. + /// The configuration is null. + /// The path is null. + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static Task> LoadAsync(string path, IImageDecoder decoder) + where TPixel : unmanaged, IPixel + => LoadAsync(Configuration.Default, path, decoder, default); + /// /// Create a new instance of the class from the given file. /// @@ -260,6 +409,20 @@ namespace SixLabors.ImageSharp return LoadAsync(configuration, stream, decoder, cancellationToken); } + /// + /// Create a new instance of the class from the given file. + /// + /// The file path to the image. + /// The configuration is null. + /// The path is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static Task> LoadAsync(string path) + where TPixel : unmanaged, IPixel + => LoadAsync(Configuration.Default, path, default(CancellationToken)); + /// /// Create a new instance of the class from the given file. /// diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 58d6640a06..ac6eec282b 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -135,9 +135,26 @@ namespace SixLabors.ImageSharp /// A representing the asynchronous operation or null if /// a suitable detector is not found. /// - public static async Task IdentifyAsync(Configuration configuration, Stream stream) + public static Task IdentifyAsync(Configuration configuration, Stream stream) + => IdentifyAsync(configuration, stream, default); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The image stream to read the information from. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// A representing the asynchronous operation or null if + /// a suitable detector is not found. + /// + public static async Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) { - (IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(configuration, stream).ConfigureAwait(false); + (IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(configuration, stream, cancellationToken).ConfigureAwait(false); return res.ImageInfo; } @@ -197,6 +214,24 @@ namespace SixLabors.ImageSharp (s, ct) => InternalIdentityAsync(s, configuration ?? Configuration.Default, ct), default); + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The image stream to read the information from. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( + Stream stream, + CancellationToken cancellationToken) + => IdentifyWithFormatAsync(Configuration.Default, stream, cancellationToken); + /// /// Reads the raw image information from the specified stream without fully decoding it. /// @@ -208,7 +243,7 @@ namespace SixLabors.ImageSharp /// The stream is not readable. /// Image contains invalid content. /// - /// The representing the asyncronous operation with the parameter type + /// The representing the asynchronous operation with the parameter type /// property set to null if suitable info detector is not found. /// public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs b/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs index 4e278ad35f..317a5129c4 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [InlineData(false)] [InlineData(true)] - public async Task LoadAsync_Specific_Stream_WhenCancelledDuringRead(bool isInputStreamSeekable) + public async Task LoadAsync_Specific_Stream(bool isInputStreamSeekable) { this.isTestStreamSeekable = isInputStreamSeekable; _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [InlineData(false)] [InlineData(true)] - public async Task LoadAsync_Agnostic_Stream_WhenCancelledDuringRead(bool isInputStreamSeekable) + public async Task LoadAsync_Agnostic_Stream(bool isInputStreamSeekable) { this.isTestStreamSeekable = isInputStreamSeekable; _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Tests } [Fact] - public async Task LoadAsync_Agnostic_Path_WhenCancelledDuringRead() + public async Task LoadAsync_Agnostic_Path() { this.isTestStreamSeekable = true; _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Tests } [Fact] - public async Task LoadAsync_Specific_Path_WhenCancelledDuringRead() + public async Task LoadAsync_Specific_Path() { this.isTestStreamSeekable = true; _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); @@ -64,6 +64,54 @@ namespace SixLabors.ImageSharp.Tests await Assert.ThrowsAsync(() => Image.LoadAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token)); } + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task IdentifyAsync_Stream(bool isInputStreamSeekable) + { + this.isTestStreamSeekable = isInputStreamSeekable; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + await Assert.ThrowsAsync(() => Image.IdentifyAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token)); + } + + [Fact] + public async Task IdentifyAsync_CustomConfiguration_Path() + { + this.isTestStreamSeekable = true; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + await Assert.ThrowsAsync(() => Image.IdentifyAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task IdentifyWithFormatAsync_CustomConfiguration_Stream(bool isInputStreamSeekable) + { + this.isTestStreamSeekable = isInputStreamSeekable; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + await Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token)); + } + + [Fact] + public async Task IdentifyWithFormatAsync_CustomConfiguration_Path() + { + this.isTestStreamSeekable = true; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + await Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token)); + } + + [Fact] + public async Task IdentifyWithFormatAsync_DefaultConfiguration_Stream() + { + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + await Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(this.DataStream, this.cts.Token)); + } + private async Task DoCancel() { // wait until we reach the middle of the steam diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs index 6784f96e86..e5b35ffd23 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs @@ -19,6 +19,8 @@ namespace SixLabors.ImageSharp.Tests { private static readonly string ActualImagePath = TestFile.GetInputFileFullPath(TestImages.Bmp.F); + private static readonly Size ExpectedImageSize = new Size(108, 202); + private byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes; private IImageInfo LocalImageInfo => this.localImageInfoMock.Object; @@ -33,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests { IImageInfo info = Image.Identify(this.ActualImageBytes, out IImageFormat type); - Assert.NotNull(info); + Assert.Equal(ExpectedImageSize, info.Size()); Assert.Equal(ExpectedGlobalFormat, type); } @@ -131,13 +133,46 @@ namespace SixLabors.ImageSharp.Tests using (var stream = new MemoryStream(this.ActualImageBytes)) { var asyncStream = new AsyncStreamWrapper(stream, () => false); - (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(asyncStream); + (IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(asyncStream); - Assert.NotNull(info.ImageInfo); - Assert.Equal(ExpectedGlobalFormat, info.Format); + Assert.Equal(ExpectedImageSize, res.ImageInfo.Size()); + Assert.Equal(ExpectedGlobalFormat, res.Format); } } + [Fact] + public async Task FromPathAsync_CustomConfiguration() + { + IImageInfo info = await Image.IdentifyAsync(this.LocalConfiguration, this.MockFilePath); + Assert.Equal(this.LocalImageInfo, info); + } + + [Fact] + public async Task IdentifyWithFormatAsync_FromPath_CustomConfiguration() + { + (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(this.LocalConfiguration, this.MockFilePath); + Assert.NotNull(info.ImageInfo); + Assert.Equal(this.LocalImageFormat, info.Format); + } + + [Fact] + public async Task IdentifyWithFormatAsync_FromPath_GlobalConfiguration() + { + (IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(ActualImagePath); + + Assert.Equal(ExpectedImageSize, res.ImageInfo.Size()); + Assert.Equal(ExpectedGlobalFormat, res.Format); + } + + [Fact] + public async Task FromPathAsync_GlobalConfiguration() + { + IImageInfo info = await Image.IdentifyAsync(ActualImagePath); + + Assert.Equal(ExpectedImageSize, info.Size()); + } + + [Fact] public async Task FromStreamAsync_CustomConfiguration() { diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs index 1689c1dea7..a5034e43b0 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs @@ -25,102 +25,80 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void Path_Specific() { - using (var img = Image.Load(this.Path)) - { - VerifyDecodedImage(img); - } + using var img = Image.Load(this.Path); + VerifyDecodedImage(img); } [Fact] public void Path_Agnostic() { - using (var img = Image.Load(this.Path)) - { - VerifyDecodedImage(img); - } + using var img = Image.Load(this.Path); + VerifyDecodedImage(img); } [Fact] public async Task Path_Agnostic_Async() { - using (var img = await Image.LoadAsync(this.Path)) - { - VerifyDecodedImage(img); - } + using var img = await Image.LoadAsync(this.Path); + VerifyDecodedImage(img); } [Fact] public async Task Path_Specific_Async() { - using (var img = await Image.LoadAsync(Configuration.Default, this.Path)) - { - VerifyDecodedImage(img); - } + using var img = await Image.LoadAsync(this.Path); + VerifyDecodedImage(img); } [Fact] public async Task Path_Agnostic_Configuration_Async() { - using (var img = await Image.LoadAsync(Configuration.Default, this.Path)) - { - VerifyDecodedImage(img); - } + using var img = await Image.LoadAsync(this.Path); + VerifyDecodedImage(img); } [Fact] public void Path_Decoder_Specific() { - using (var img = Image.Load(this.Path, new BmpDecoder())) - { - VerifyDecodedImage(img); - } + using var img = Image.Load(this.Path, new BmpDecoder()); + VerifyDecodedImage(img); } [Fact] public void Path_Decoder_Agnostic() { - using (var img = Image.Load(this.Path, new BmpDecoder())) - { - VerifyDecodedImage(img); - } + using var img = Image.Load(this.Path, new BmpDecoder()); + VerifyDecodedImage(img); } [Fact] public async Task Path_Decoder_Agnostic_Async() { - using (var img = await Image.LoadAsync(Configuration.Default, this.Path, new BmpDecoder())) - { - VerifyDecodedImage(img); - } + using var img = await Image.LoadAsync(this.Path, new BmpDecoder()); + VerifyDecodedImage(img); } [Fact] public async Task Path_Decoder_Specific_Async() { - using (var img = await Image.LoadAsync(Configuration.Default, this.Path, new BmpDecoder())) - { - VerifyDecodedImage(img); - } + using var img = await Image.LoadAsync(this.Path, new BmpDecoder()); + VerifyDecodedImage(img); } [Fact] public void Path_OutFormat_Specific() { - using (var img = Image.Load(this.Path, out IImageFormat format)) - { - VerifyDecodedImage(img); - Assert.IsType(format); - } + using var img = Image.Load(this.Path, out IImageFormat format); + VerifyDecodedImage(img); + Assert.IsType(format); } [Fact] public void Path_OutFormat_Agnostic() { - using (var img = Image.Load(this.Path, out IImageFormat format)) - { - VerifyDecodedImage(img); - Assert.IsType(format); - } + using var img = Image.Load(this.Path, out IImageFormat format); + VerifyDecodedImage(img); + Assert.IsType(format); } [Fact] @@ -142,6 +120,20 @@ namespace SixLabors.ImageSharp.Tests Image.Load((string)null); }); } + + [Fact] + public Task Async_WhenFileNotFound_Throws() + { + return Assert.ThrowsAsync( + () => Image.LoadAsync(Guid.NewGuid().ToString())); + } + + [Fact] + public Task Async_WhenPathIsNull_Throws() + { + return Assert.ThrowsAsync( + () => Image.LoadAsync((string)null)); + } } } } diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 18f0876d02..5a380f7832 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -197,7 +197,7 @@ namespace SixLabors.ImageSharp.Tests } } - public class TestDecoder : IImageDecoder + public class TestDecoder : IImageDecoder, IImageInfoDetector { private TestFormat testFormat; @@ -243,6 +243,12 @@ namespace SixLabors.ImageSharp.Tests public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, cancellationToken); + + public IImageInfo Identify(Configuration configuration, Stream stream) => + this.IdentifyAsync(configuration, stream, default).GetAwaiter().GetResult(); + + public async Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeImpl(configuration, stream, cancellationToken); } public class TestEncoder : ImageSharp.Formats.IImageEncoder