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
2 changes: 1 addition & 1 deletion .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion src/ImageSharp/Advanced/AdvancedImageExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ public static class AdvancedImageExtensions
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="filePath">The target file path to save the image to.</param>
/// <returns>The matching encoder.</returns>
/// <exception cref="ArgumentNullException">The file path is null.</exception>
/// <exception cref="NotSupportedException">No encoder available for provided path.</exception>
/// <returns>The matching <see cref="IImageEncoder"/>.</returns>
public static IImageEncoder DetectEncoder(this Image source, string filePath)
{
Guard.NotNull(filePath, nameof(filePath));
Expand Down
63 changes: 56 additions & 7 deletions src/ImageSharp/ImageExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,29 @@ namespace SixLabors.ImageSharp
public static partial class ImageExtensions
{
/// <summary>
/// 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.
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="NotSupportedException">No encoder available for provided path.</exception>
public static void Save(this Image source, string path)
=> source.Save(path, source.DetectEncoder(path));

/// <summary>
/// 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.
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="NotSupportedException">No encoder available for provided path.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsync(this Image source, string path, CancellationToken cancellationToken = default)
=> source.SaveAsync(path, source.DetectEncoder(path), cancellationToken);

/// <summary>
/// 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.
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="path">The file path to save the image to.</param>
Expand All @@ -56,7 +58,7 @@ public static void Save(this Image source, string path, IImageEncoder encoder)
}

/// <summary>
/// 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.
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="path">The file path to save the image to.</param>
Expand All @@ -73,12 +75,15 @@ public static async Task SaveAsync(
{
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);
}
}

/// <summary>
/// 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.
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="stream">The stream to save the image to.</param>
Expand Down Expand Up @@ -115,6 +120,50 @@ public static void Save(this Image source, Stream stream, IImageFormat format)
source.Save(stream, encoder);
}

/// <summary>
/// Writes the image to the given stream using the given image format.
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="format">The format to save the image in.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="ArgumentNullException">The format is null.</exception>
/// <exception cref="NotSupportedException">The stream is not writable.</exception>
/// <exception cref="NotSupportedException">No encoder available for provided format.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
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<IImageFormat, IImageEncoder> 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);
}

/// <summary>
/// Returns a Base64 encoded string from the given image.
/// The result is prepended with a Data URI <see href="https://en.wikipedia.org/wiki/Data_URI_scheme"/>
Expand Down
31 changes: 31 additions & 0 deletions tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,37 @@ public async Task SetEncoding()
}
}

[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<Rgba32>(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()
{
Expand Down