diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index 412b1d807..0e093a834 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -64,7 +64,7 @@ jobs:
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit
- name: Update Codecov
- uses: codecov/codecov-action@v1.0.7
+ uses: codecov/codecov-action@v1
if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors')
with:
flags: unittests
diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs
index c5abbda61..54a773be0 100644
--- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs
+++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs
@@ -23,7 +23,9 @@ namespace SixLabors.ImageSharp.Advanced
///
/// The source image.
/// The target file path to save the image to.
- /// The matching encoder.
+ /// The file path is null.
+ /// No encoder available for provided path.
+ /// The matching .
public static IImageEncoder DetectEncoder(this Image source, string filePath)
{
Guard.NotNull(filePath, nameof(filePath));
diff --git a/src/ImageSharp/ImageExtensions.cs b/src/ImageSharp/ImageExtensions.cs
index d40c5c271..75cd32106 100644
--- a/src/ImageSharp/ImageExtensions.cs
+++ b/src/ImageSharp/ImageExtensions.cs
@@ -18,27 +18,29 @@ namespace SixLabors.ImageSharp
public static partial class ImageExtensions
{
///
- /// Writes the image to the given stream using the currently loaded image format.
+ /// Writes the image to the given file path using an encoder detected from the path.
///
/// The source image.
/// The file path to save the image to.
/// The path is null.
+ /// No encoder available for provided path.
public static void Save(this Image source, string path)
=> source.Save(path, source.DetectEncoder(path));
///
- /// Writes the image to the given stream using the currently loaded image format.
+ /// Writes the image to the given file path using an encoder detected from the path.
///
/// The source image.
/// The file path to save the image to.
/// The token to monitor for cancellation requests.
/// The path is null.
+ /// No encoder available for provided path.
/// A representing the asynchronous operation.
public static Task SaveAsync(this Image source, string path, CancellationToken cancellationToken = default)
=> source.SaveAsync(path, source.DetectEncoder(path), cancellationToken);
///
- /// Writes the image to the given stream using the currently loaded image format.
+ /// Writes the image to the given file path using the given image encoder.
///
/// The source image.
/// The file path to save the image to.
@@ -56,7 +58,7 @@ namespace SixLabors.ImageSharp
}
///
- /// Writes the image to the given stream using the currently loaded image format.
+ /// Writes the image to the given file path using the given image encoder.
///
/// The source image.
/// The file path to save the image to.
@@ -73,12 +75,15 @@ namespace SixLabors.ImageSharp
{
Guard.NotNull(path, nameof(path));
Guard.NotNull(encoder, nameof(encoder));
- using Stream fs = source.GetConfiguration().FileSystem.Create(path);
- await source.SaveAsync(fs, encoder, cancellationToken).ConfigureAwait(false);
+
+ using (Stream fs = source.GetConfiguration().FileSystem.Create(path))
+ {
+ await source.SaveAsync(fs, encoder, cancellationToken).ConfigureAwait(false);
+ }
}
///
- /// Writes the image to the given stream using the currently loaded image format.
+ /// Writes the image to the given stream using the given image format.
///
/// The source image.
/// The stream to save the image to.
@@ -115,6 +120,50 @@ namespace SixLabors.ImageSharp
source.Save(stream, encoder);
}
+ ///
+ /// Writes the image to the given stream using the given image format.
+ ///
+ /// The source image.
+ /// The stream to save the image to.
+ /// The format to save the image in.
+ /// The token to monitor for cancellation requests.
+ /// The stream is null.
+ /// The format is null.
+ /// The stream is not writable.
+ /// No encoder available for provided format.
+ /// A representing the asynchronous operation.
+ public static Task SaveAsync(
+ this Image source,
+ Stream stream,
+ IImageFormat format,
+ CancellationToken cancellationToken = default)
+ {
+ Guard.NotNull(stream, nameof(stream));
+ Guard.NotNull(format, nameof(format));
+
+ if (!stream.CanWrite)
+ {
+ throw new NotSupportedException("Cannot write to the stream.");
+ }
+
+ IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format);
+
+ if (encoder is null)
+ {
+ var sb = new StringBuilder();
+ sb.AppendLine("No encoder was found for the provided mime type. Registered encoders include:");
+
+ foreach (KeyValuePair val in source.GetConfiguration().ImageFormatsManager.ImageEncoders)
+ {
+ sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine);
+ }
+
+ throw new NotSupportedException(sb.ToString());
+ }
+
+ return source.SaveAsync(stream, encoder, cancellationToken);
+ }
+
///
/// Returns a Base64 encoded string from the given image.
/// The result is prepended with a Data URI
diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs
index 27eca523c..8a4c703e0 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs
@@ -29,8 +29,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// Initializes a new instance of the class.
///
public BokehBlurProcessor()
- : this(DefaultRadius, DefaultComponents, DefaultGamma)
{
+ this.Radius = DefaultRadius;
+ this.Components = DefaultComponents;
+ this.Gamma = DefaultGamma;
}
///
@@ -47,6 +49,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
public BokehBlurProcessor(int radius, int components, float gamma)
{
+ Guard.MustBeGreaterThan(radius, 0, nameof(radius));
+ Guard.MustBeBetweenOrEqualTo(components, 1, 6, nameof(components));
Guard.MustBeGreaterThanOrEqualTo(gamma, 1, nameof(gamma));
this.Radius = radius;
diff --git a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs
index 40c3b65b5..4e6b002d0 100644
--- a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs
+++ b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs
@@ -72,6 +72,37 @@ namespace SixLabors.ImageSharp.Tests
}
}
+ [Theory]
+ [InlineData("test.png", "image/png")]
+ [InlineData("test.tga", "image/tga")]
+ [InlineData("test.bmp", "image/bmp")]
+ [InlineData("test.jpg", "image/jpeg")]
+ [InlineData("test.gif", "image/gif")]
+ public async Task SaveStreamWithMime(string filename, string mimeType)
+ {
+ using (var image = new Image(5, 5))
+ {
+ string ext = Path.GetExtension(filename);
+ IImageFormat format = image.GetConfiguration().ImageFormatsManager.FindFormatByFileExtension(ext);
+ Assert.Equal(mimeType, format.DefaultMimeType);
+
+ using (var stream = new MemoryStream())
+ {
+ var asyncStream = new AsyncStreamWrapper(stream, () => false);
+ await image.SaveAsync(asyncStream, format);
+
+ stream.Position = 0;
+
+ (Image Image, IImageFormat Format) imf = await Image.LoadWithFormatAsync(stream);
+
+ Assert.Equal(format, imf.Format);
+ Assert.Equal(mimeType, imf.Format.DefaultMimeType);
+
+ imf.Image.Dispose();
+ }
+ }
+ }
+
[Fact]
public async Task ThrowsWhenDisposed()
{
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs
index 490a6ea49..50b8782e4 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs
@@ -35,6 +35,19 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
0.02565295+0.01611732j 0.0153483+0.01605112j 0.00698622+0.01370844j
0.00135338+0.00998296j -0.00152245+0.00604545j -0.00227282+0.002851j ]]";
+ [Theory]
+ [InlineData(-10, 2, 3f)]
+ [InlineData(-1, 2, 3f)]
+ [InlineData(0, 2, 3f)]
+ [InlineData(20, -1, 3f)]
+ [InlineData(20, -0, 3f)]
+ [InlineData(20, 4, -10f)]
+ [InlineData(20, 4, 0f)]
+ public void VerifyBokehBlurProcessorArguments_Fail(int radius, int components, float gamma)
+ {
+ Assert.Throws(() => new BokehBlurProcessor(radius, components, gamma));
+ }
+
[Fact]
public void VerifyComplexComponents()
{