Skip to content

Commit b29bb74

Browse files
Merge pull request #1196 from SixLabors/sw/fake-async-codecs
Async APIs / Fake Async Codecs
2 parents 16c417f + 73fed79 commit b29bb74

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1790
-119
lines changed

src/ImageSharp/Advanced/AdvancedImageExtensions.cs

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
5-
using System.Linq;
6-
using System.Runtime.InteropServices;
7-
5+
using System.Collections.Generic;
6+
using System.IO;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
using SixLabors.ImageSharp.Formats;
810
using SixLabors.ImageSharp.Memory;
911
using SixLabors.ImageSharp.PixelFormats;
1012

@@ -15,15 +17,66 @@ namespace SixLabors.ImageSharp.Advanced
1517
/// </summary>
1618
public static class AdvancedImageExtensions
1719
{
20+
/// <summary>
21+
/// For a given file path find the best encoder to use via its extension.
22+
/// </summary>
23+
/// <param name="source">The source image.</param>
24+
/// <param name="filePath">The target file path to save the image to.</param>
25+
/// <returns>The matching encoder.</returns>
26+
public static IImageEncoder DetectEncoder(this Image source, string filePath)
27+
{
28+
Guard.NotNull(filePath, nameof(filePath));
29+
30+
string ext = Path.GetExtension(filePath);
31+
IImageFormat format = source.GetConfiguration().ImageFormatsManager.FindFormatByFileExtension(ext);
32+
if (format is null)
33+
{
34+
var sb = new StringBuilder();
35+
sb.AppendLine($"No encoder was found for extension '{ext}'. Registered encoders include:");
36+
foreach (IImageFormat fmt in source.GetConfiguration().ImageFormats)
37+
{
38+
sb.AppendFormat(" - {0} : {1}{2}", fmt.Name, string.Join(", ", fmt.FileExtensions), Environment.NewLine);
39+
}
40+
41+
throw new NotSupportedException(sb.ToString());
42+
}
43+
44+
IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format);
45+
46+
if (encoder is null)
47+
{
48+
var sb = new StringBuilder();
49+
sb.AppendLine($"No encoder was found for extension '{ext}' using image format '{format.Name}'. Registered encoders include:");
50+
foreach (KeyValuePair<IImageFormat, IImageEncoder> enc in source.GetConfiguration().ImageFormatsManager.ImageEncoders)
51+
{
52+
sb.AppendFormat(" - {0} : {1}{2}", enc.Key, enc.Value.GetType().Name, Environment.NewLine);
53+
}
54+
55+
throw new NotSupportedException(sb.ToString());
56+
}
57+
58+
return encoder;
59+
}
60+
1861
/// <summary>
1962
/// Accepts a <see cref="IImageVisitor"/> to implement a double-dispatch pattern in order to
2063
/// apply pixel-specific operations on non-generic <see cref="Image"/> instances
2164
/// </summary>
22-
/// <param name="source">The source.</param>
23-
/// <param name="visitor">The visitor.</param>
65+
/// <param name="source">The source image.</param>
66+
/// <param name="visitor">The image visitor.</param>
2467
public static void AcceptVisitor(this Image source, IImageVisitor visitor)
2568
=> source.Accept(visitor);
2669

70+
/// <summary>
71+
/// Accepts a <see cref="IImageVisitor"/> to implement a double-dispatch pattern in order to
72+
/// apply pixel-specific operations on non-generic <see cref="Image"/> instances
73+
/// </summary>
74+
/// <param name="source">The source image.</param>
75+
/// <param name="visitor">The image visitor.</param>
76+
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
77+
public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor)
78+
=> source.AcceptAsync(visitor);
79+
2780
/// <summary>
2881
/// Gets the configuration for the image.
2982
/// </summary>

src/ImageSharp/Advanced/IImageVisitor.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Apache License, Version 2.0.
33

4+
using System.Threading.Tasks;
45
using SixLabors.ImageSharp.PixelFormats;
56

67
namespace SixLabors.ImageSharp.Advanced
@@ -19,4 +20,20 @@ public interface IImageVisitor
1920
void Visit<TPixel>(Image<TPixel> image)
2021
where TPixel : unmanaged, IPixel<TPixel>;
2122
}
23+
24+
/// <summary>
25+
/// A visitor to implement a double-dispatch pattern in order to apply pixel-specific operations
26+
/// on non-generic <see cref="Image"/> instances.
27+
/// </summary>
28+
public interface IImageVisitorAsync
29+
{
30+
/// <summary>
31+
/// Provides a pixel-specific implementation for a given operation.
32+
/// </summary>
33+
/// <param name="image">The image.</param>
34+
/// <typeparam name="TPixel">The pixel type.</typeparam>
35+
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
36+
Task VisitAsync<TPixel>(Image<TPixel> image)
37+
where TPixel : unmanaged, IPixel<TPixel>;
38+
}
2239
}

src/ImageSharp/Formats/Bmp/BmpDecoder.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System.IO;
5+
using System.Threading.Tasks;
56
using SixLabors.ImageSharp.Memory;
67
using SixLabors.ImageSharp.PixelFormats;
78

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

31+
/// <inheritdoc/>
32+
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
33+
where TPixel : unmanaged, IPixel<TPixel>
34+
{
35+
Guard.NotNull(stream, nameof(stream));
36+
37+
var decoder = new BmpDecoderCore(configuration, this);
38+
39+
try
40+
{
41+
return await decoder.DecodeAsync<TPixel>(stream).ConfigureAwait(false);
42+
}
43+
catch (InvalidMemoryOperationException ex)
44+
{
45+
Size dims = decoder.Dimensions;
46+
47+
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);
48+
}
49+
}
50+
3051
/// <inheritdoc/>
3152
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
3253
where TPixel : unmanaged, IPixel<TPixel>
@@ -50,12 +71,23 @@ public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
5071
/// <inheritdoc />
5172
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
5273

74+
/// <inheritdoc />
75+
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
76+
5377
/// <inheritdoc/>
5478
public IImageInfo Identify(Configuration configuration, Stream stream)
5579
{
5680
Guard.NotNull(stream, nameof(stream));
5781

5882
return new BmpDecoderCore(configuration, this).Identify(stream);
5983
}
84+
85+
/// <inheritdoc/>
86+
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
87+
{
88+
Guard.NotNull(stream, nameof(stream));
89+
90+
return new BmpDecoderCore(configuration, this).IdentifyAsync(stream);
91+
}
6092
}
6193
}

src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
using System.IO;
88
using System.Numerics;
99
using System.Runtime.CompilerServices;
10+
using System.Threading.Tasks;
1011
using SixLabors.ImageSharp.Common.Helpers;
12+
using SixLabors.ImageSharp.IO;
1113
using SixLabors.ImageSharp.Memory;
1214
using SixLabors.ImageSharp.Metadata;
1315
using SixLabors.ImageSharp.PixelFormats;
@@ -130,8 +132,40 @@ public BmpDecoderCore(Configuration configuration, IBmpDecoderOptions options)
130132
/// <para><paramref name="stream"/> is null.</para>
131133
/// </exception>
132134
/// <returns>The decoded image.</returns>
133-
public Image<TPixel> Decode<TPixel>(Stream stream)
135+
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Stream stream)
134136
where TPixel : unmanaged, IPixel<TPixel>
137+
{
138+
// if we can seek then we arn't in a context that errors on async operations
139+
if (stream.CanSeek)
140+
{
141+
return this.Decode<TPixel>(stream);
142+
}
143+
else
144+
{
145+
// cheat for now do async copy of the stream into memory stream and use the sync version
146+
// we should use an array pool backed memorystream implementation
147+
using (var ms = new MemoryStream())
148+
{
149+
await stream.CopyToAsync(ms).ConfigureAwait(false);
150+
ms.Position = 0;
151+
return this.Decode<TPixel>(ms);
152+
}
153+
}
154+
}
155+
156+
/// <summary>
157+
/// Decodes the image from the specified this._stream and sets
158+
/// the data to image.
159+
/// </summary>
160+
/// <typeparam name="TPixel">The pixel format.</typeparam>
161+
/// <param name="stream">The stream, where the image should be
162+
/// decoded from. Cannot be null (Nothing in Visual Basic).</param>
163+
/// <exception cref="System.ArgumentNullException">
164+
/// <para><paramref name="stream"/> is null.</para>
165+
/// </exception>
166+
/// <returns>The decoded image.</returns>
167+
public Image<TPixel> Decode<TPixel>(Stream stream)
168+
where TPixel : unmanaged, IPixel<TPixel>
135169
{
136170
try
137171
{
@@ -218,6 +252,20 @@ public IImageInfo Identify(Stream stream)
218252
return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metadata);
219253
}
220254

255+
/// <summary>
256+
/// Reads the raw image information from the specified stream.
257+
/// </summary>
258+
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
259+
public async Task<IImageInfo> IdentifyAsync(Stream stream)
260+
{
261+
using (var ms = new FixedCapacityPooledMemoryStream(stream.Length))
262+
{
263+
await stream.CopyToAsync(ms).ConfigureAwait(false);
264+
ms.Position = 0;
265+
return this.Identify(ms);
266+
}
267+
}
268+
221269
/// <summary>
222270
/// Returns the y- value based on the given height.
223271
/// </summary>

src/ImageSharp/Formats/Bmp/BmpEncoder.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
// Copyright (c) Six Labors.
1+
// Copyright (c) Six Labors.
22
// Licensed under the Apache License, Version 2.0.
33

44
using System.IO;
5+
using System.Threading.Tasks;
56
using SixLabors.ImageSharp.Advanced;
67
using SixLabors.ImageSharp.PixelFormats;
78
using SixLabors.ImageSharp.Processing.Processors.Quantization;
@@ -39,5 +40,13 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
3940
var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator());
4041
encoder.Encode(image, stream);
4142
}
43+
44+
/// <inheritdoc/>
45+
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream)
46+
where TPixel : unmanaged, IPixel<TPixel>
47+
{
48+
var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator());
49+
return encoder.EncodeAsync(image, stream);
50+
}
4251
}
43-
}
52+
}

src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
using System.Buffers;
66
using System.IO;
77
using System.Runtime.InteropServices;
8-
8+
using System.Threading.Tasks;
99
using SixLabors.ImageSharp.Advanced;
1010
using SixLabors.ImageSharp.Common.Helpers;
1111
using SixLabors.ImageSharp.Memory;
@@ -97,8 +97,32 @@ public BmpEncoderCore(IBmpEncoderOptions options, MemoryAllocator memoryAllocato
9797
/// <typeparam name="TPixel">The pixel format.</typeparam>
9898
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
9999
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
100-
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
100+
public async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream)
101101
where TPixel : unmanaged, IPixel<TPixel>
102+
{
103+
if (stream.CanSeek)
104+
{
105+
this.Encode(image, stream);
106+
}
107+
else
108+
{
109+
using (var ms = new MemoryStream())
110+
{
111+
this.Encode(image, ms);
112+
ms.Position = 0;
113+
await ms.CopyToAsync(stream).ConfigureAwait(false);
114+
}
115+
}
116+
}
117+
118+
/// <summary>
119+
/// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>.
120+
/// </summary>
121+
/// <typeparam name="TPixel">The pixel format.</typeparam>
122+
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
123+
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
124+
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
125+
where TPixel : unmanaged, IPixel<TPixel>
102126
{
103127
Guard.NotNull(image, nameof(image));
104128
Guard.NotNull(stream, nameof(stream));

src/ImageSharp/Formats/Gif/GifDecoder.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

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

28+
/// <inheritdoc/>
29+
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
30+
where TPixel : unmanaged, IPixel<TPixel>
31+
{
32+
var decoder = new GifDecoderCore(configuration, this);
33+
34+
try
35+
{
36+
return await decoder.DecodeAsync<TPixel>(stream).ConfigureAwait(false);
37+
}
38+
catch (InvalidMemoryOperationException ex)
39+
{
40+
Size dims = decoder.Dimensions;
41+
42+
GifThrowHelper.ThrowInvalidImageContentException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
43+
44+
// Not reachable, as the previous statement will throw a exception.
45+
return null;
46+
}
47+
}
48+
2749
/// <inheritdoc/>
2850
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
2951
where TPixel : unmanaged, IPixel<TPixel>
@@ -54,7 +76,19 @@ public IImageInfo Identify(Configuration configuration, Stream stream)
5476
return decoder.Identify(stream);
5577
}
5678

79+
/// <inheritdoc/>
80+
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
81+
{
82+
Guard.NotNull(stream, nameof(stream));
83+
84+
var decoder = new GifDecoderCore(configuration, this);
85+
return decoder.IdentifyAsync(stream);
86+
}
87+
5788
/// <inheritdoc />
5889
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
90+
91+
/// <inheritdoc />
92+
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
5993
}
6094
}

0 commit comments

Comments
 (0)