Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9bbd505
Add Async APIs to IImageEncoder and IImageDecoder
tocsoft May 3, 2020
4d95e2e
Save async tests
tocsoft May 3, 2020
dff8ab6
implement Load Async apis
tocsoft May 6, 2020
c69683e
IdentifyAsync
tocsoft May 6, 2020
2599275
Update src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
JimBobSquarePants May 17, 2020
ef3b71f
Update src/ImageSharp/Formats/Png/PngEncoder.cs
JimBobSquarePants May 17, 2020
e643553
Update src/ImageSharp/Image.Decode.cs
JimBobSquarePants May 17, 2020
a27eb49
Update src/ImageSharp/Image.Decode.cs
JimBobSquarePants May 17, 2020
f7beaa1
Update src/ImageSharp/Image.cs
JimBobSquarePants May 17, 2020
2fc3058
Update src/ImageSharp/Image.FromStream.cs
JimBobSquarePants May 17, 2020
740d133
Update src/ImageSharp/Image.FromStream.cs
JimBobSquarePants May 17, 2020
6c18639
Update src/ImageSharp/Image.FromStream.cs
JimBobSquarePants May 17, 2020
b505771
Update src/ImageSharp/Image.FromStream.cs
JimBobSquarePants May 17, 2020
1969165
Update src/ImageSharp/Image.FromStream.cs
JimBobSquarePants May 17, 2020
43b0d5e
Update src/ImageSharp/Image.FromStream.cs
JimBobSquarePants May 17, 2020
84c7958
Merge branch 'master' into sw/fake-async-codecs
JimBobSquarePants May 17, 2020
9bbf05d
async await where required
JimBobSquarePants May 17, 2020
1f74e31
implement IEquatable<T>
JimBobSquarePants May 17, 2020
6361a22
Rename extension
JimBobSquarePants May 17, 2020
5a8f78a
Revert unrequired async/await additions
JimBobSquarePants May 17, 2020
b8232e4
Update Image.FromStream.cs
JimBobSquarePants May 17, 2020
c8c561b
Merge branch 'master' into sw/fake-async-codecs
JimBobSquarePants May 19, 2020
72257ba
Merge branch 'master' into sw/fake-async-codecs
JimBobSquarePants May 27, 2020
5d4a18f
Merge branch 'master' into sw/fake-async-codecs
JimBobSquarePants Jun 9, 2020
73cc796
Use named tuple
JimBobSquarePants Jun 9, 2020
73fed79
Use pooled stream for async decode/identify
JimBobSquarePants Jun 9, 2020
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
59 changes: 56 additions & 3 deletions src/ImageSharp/Advanced/AdvancedImageExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
// Licensed under the GNU Affero General Public License, Version 3.

using System;
using System.Linq;
using System.Runtime.InteropServices;

using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;

Expand All @@ -15,6 +17,47 @@ namespace SixLabors.ImageSharp.Advanced
/// </summary>
public static class AdvancedImageExtensions
{
/// <summary>
/// For a given path find the best encoder to use
/// </summary>
/// <param name="source">The source.</param>
/// <param name="path">The Path</param>
/// <returns>The matching encoder.</returns>
public static IImageEncoder FindEncoded(this Image source, string path)
{
Guard.NotNull(path, nameof(path));

string ext = Path.GetExtension(path);
IImageFormat format = source.GetConfiguration().ImageFormatsManager.FindFormatByFileExtension(ext);
if (format is null)
{
var sb = new StringBuilder();
sb.AppendLine($"No encoder was found for extension '{ext}'. Registered encoders include:");
foreach (IImageFormat fmt in source.GetConfiguration().ImageFormats)
{
sb.AppendFormat(" - {0} : {1}{2}", fmt.Name, string.Join(", ", fmt.FileExtensions), Environment.NewLine);
}

throw new NotSupportedException(sb.ToString());
}

IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format);

if (encoder is null)
{
var sb = new StringBuilder();
sb.AppendLine($"No encoder was found for extension '{ext}' using image format '{format.Name}'. Registered encoders include:");
foreach (KeyValuePair<IImageFormat, IImageEncoder> enc in source.GetConfiguration().ImageFormatsManager.ImageEncoders)
{
sb.AppendFormat(" - {0} : {1}{2}", enc.Key, enc.Value.GetType().Name, Environment.NewLine);
}

throw new NotSupportedException(sb.ToString());
}

return encoder;
}

/// <summary>
/// Accepts a <see cref="IImageVisitor"/> to implement a double-dispatch pattern in order to
/// apply pixel-specific operations on non-generic <see cref="Image"/> instances
Expand All @@ -24,6 +67,16 @@ public static class AdvancedImageExtensions
public static void AcceptVisitor(this Image source, IImageVisitor visitor)
=> source.Accept(visitor);

/// <summary>
/// Accepts a <see cref="IImageVisitor"/> to implement a double-dispatch pattern in order to
/// apply pixel-specific operations on non-generic <see cref="Image"/> instances
/// </summary>
/// <param name="source">The source.</param>
/// <param name="visitor">The visitor.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static async Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor)
=> await source.AcceptAsync(visitor).ConfigureAwait(false);

/// <summary>
/// Gets the configuration for the image.
/// </summary>
Expand Down
17 changes: 17 additions & 0 deletions src/ImageSharp/Advanced/IImageVisitor.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.

using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;

namespace SixLabors.ImageSharp.Advanced
Expand All @@ -19,4 +20,20 @@ public interface IImageVisitor
void Visit<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>;
}

/// <summary>
/// A visitor to implement a double-dispatch pattern in order to apply pixel-specific operations
/// on non-generic <see cref="Image"/> instances.
/// </summary>
public interface IImageVisitorAsync
{
/// <summary>
/// Provides a pixel-specific implementation for a given operation.
/// </summary>
/// <param name="image">The image.</param>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task VisitAsync<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>;
}
}
32 changes: 32 additions & 0 deletions src/ImageSharp/Formats/Bmp/BmpDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the GNU Affero General Public License, Version 3.

using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;

Expand All @@ -27,6 +28,26 @@ public sealed class BmpDecoder : IImageDecoder, IBmpDecoderOptions, IImageInfoDe
/// </summary>
public RleSkippedPixelHandling RleSkippedPixelHandling { get; set; } = RleSkippedPixelHandling.Black;

/// <inheritdoc/>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));

var decoder = new BmpDecoderCore(configuration, this);

try
{
return await decoder.DecodeAsync<TPixel>(stream).ConfigureAwait(false);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;

throw new InvalidImageContentException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}. This error can happen for very large RLE bitmaps, which are not supported.", ex);
}
Comment on lines +35 to +48
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can DRY away these code duplications with the help of an IImageDecoderCore interface if we want. (Same for encoders.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I like this. Can come later though since everything is internal (unless someone is desperately keen to do it).

}

/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
Expand All @@ -50,12 +71,23 @@ public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);

/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);

/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));

return new BmpDecoderCore(configuration, this).Identify(stream);
}

/// <inheritdoc/>
public async Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));

return await new BmpDecoderCore(configuration, this).IdentifyAsync(stream).ConfigureAwait(false);
}
}
}
49 changes: 48 additions & 1 deletion src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.IO;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
Expand Down Expand Up @@ -130,8 +131,40 @@ public BmpDecoderCore(Configuration configuration, IBmpDecoderOptions options)
/// <para><paramref name="stream"/> is null.</para>
/// </exception>
/// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Stream stream)
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
// if we can seek then we arn't in a context that errors on async operations
if (stream.CanSeek)
{
return this.Decode<TPixel>(stream);
}
else
{
// cheat for now do async copy of the stream into memory stream and use the sync version
Copy link
Member

@JimBobSquarePants JimBobSquarePants May 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use a pool backed memory stream that allocates small chunks in .Web. Not sure how performant reading would be though.

https://github.com/SixLabors/ImageSharp.Web/blob/master/src/ImageSharp.Web/ChunkedMemoryStream.cs

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is maybe RecyclableMemoryStream an option? Its meant to be a drop in replacement for MemoryStream

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to have a look at a read-only version of this less overhead and should support all out target frameworks.

Copy link
Member

@antonfirsov antonfirsov May 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RecyclableMemoryStream seems more sophisticated at first glance.

Whichever we choose, I think it would make sense to integrate our adapted solution with MemoryAllocator.

// we should use an array pool backed memorystream implementation
using (var ms = new MemoryStream())
{
await stream.CopyToAsync(ms).ConfigureAwait(false);
ms.Position = 0;
return this.Decode<TPixel>(ms);
}
}
}

/// <summary>
/// Decodes the image from the specified this._stream and sets
/// the data to image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The stream, where the image should be
/// decoded from. Cannot be null (Nothing in Visual Basic).</param>
/// <exception cref="System.ArgumentNullException">
/// <para><paramref name="stream"/> is null.</para>
/// </exception>
/// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
try
{
Expand Down Expand Up @@ -218,6 +251,20 @@ public IImageInfo Identify(Stream stream)
return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metadata);
}

/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
public async Task<IImageInfo> IdentifyAsync(Stream stream)
{
using (var ms = new MemoryStream())
{
await stream.CopyToAsync(ms).ConfigureAwait(false);
ms.Position = 0;
return this.Identify(ms);
}
}

/// <summary>
/// Returns the y- value based on the given height.
/// </summary>
Expand Down
13 changes: 11 additions & 2 deletions src/ImageSharp/Formats/Bmp/BmpEncoder.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.

using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
Expand Down Expand Up @@ -39,5 +40,13 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator());
encoder.Encode(image, stream);
}

/// <inheritdoc/>
public async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator());
await encoder.EncodeAsync(image, stream).ConfigureAwait(false);
}
}
}
}
28 changes: 26 additions & 2 deletions src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using System.Buffers;
using System.IO;
using System.Runtime.InteropServices;

using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Memory;
Expand Down Expand Up @@ -97,8 +97,32 @@ public BmpEncoderCore(IBmpEncoderOptions options, MemoryAllocator memoryAllocato
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
public async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
if (stream.CanSeek)
{
this.Encode(image, stream);
}
else
{
using (var ms = new MemoryStream())
{
this.Encode(image, ms);
ms.Position = 0;
await ms.CopyToAsync(stream).ConfigureAwait(false);
}
}
}

/// <summary>
/// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
Expand Down
34 changes: 34 additions & 0 deletions src/ImageSharp/Formats/Gif/GifDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
Expand All @@ -24,6 +25,27 @@ public sealed class GifDecoder : IImageDecoder, IGifDecoderOptions, IImageInfoDe
/// </summary>
public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All;

/// <inheritdoc/>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new GifDecoderCore(configuration, this);

try
{
return await decoder.DecodeAsync<TPixel>(stream).ConfigureAwait(false);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;

GifThrowHelper.ThrowInvalidImageContentException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);

// Not reachable, as the previous statement will throw a exception.
return null;
}
}

/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
Expand Down Expand Up @@ -54,7 +76,19 @@ public IImageInfo Identify(Configuration configuration, Stream stream)
return decoder.Identify(stream);
}

/// <inheritdoc/>
public async Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));

var decoder = new GifDecoderCore(configuration, this);
return await decoder.IdentifyAsync(stream).ConfigureAwait(false);
}

/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);

/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
}
}
Loading