Skip to content

Commit 521fae3

Browse files
Merge pull request #1296 from SixLabors/af/cancellation
Cancellable codec API, and overloads for loading/saving
2 parents 586d99e + e707a8d commit 521fae3

File tree

66 files changed

+1936
-936
lines changed

Some content is hidden

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

66 files changed

+1936
-936
lines changed

src/ImageSharp/Advanced/AdvancedImageExtensions.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.IO;
77
using System.Text;
8+
using System.Threading;
89
using System.Threading.Tasks;
910
using SixLabors.ImageSharp.Formats;
1011
using SixLabors.ImageSharp.Memory;
@@ -73,9 +74,10 @@ public static void AcceptVisitor(this Image source, IImageVisitor visitor)
7374
/// </summary>
7475
/// <param name="source">The source image.</param>
7576
/// <param name="visitor">The image visitor.</param>
77+
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
7678
/// <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+
public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor, CancellationToken cancellationToken = default)
80+
=> source.AcceptAsync(visitor, cancellationToken);
7981

8082
/// <summary>
8183
/// Gets the configuration for the image.

src/ImageSharp/Advanced/IImageVisitor.cs

Lines changed: 3 additions & 1 deletion
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;
45
using System.Threading.Tasks;
56
using SixLabors.ImageSharp.PixelFormats;
67

@@ -31,9 +32,10 @@ public interface IImageVisitorAsync
3132
/// Provides a pixel-specific implementation for a given operation.
3233
/// </summary>
3334
/// <param name="image">The image.</param>
35+
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
3436
/// <typeparam name="TPixel">The pixel type.</typeparam>
3537
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
36-
Task VisitAsync<TPixel>(Image<TPixel> image)
38+
Task VisitAsync<TPixel>(Image<TPixel> image, CancellationToken cancellationToken)
3739
where TPixel : unmanaged, IPixel<TPixel>;
3840
}
3941
}

src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs

Lines changed: 6 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;
5+
using SixLabors.ImageSharp.Memory;
56

67
namespace SixLabors.ImageSharp
78
{
@@ -32,5 +33,10 @@ public InvalidImageContentException(string errorMessage, Exception innerExceptio
3233
: base(errorMessage, innerException)
3334
{
3435
}
36+
37+
internal InvalidImageContentException(Size size, InvalidMemoryOperationException memoryException)
38+
: this($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {size.Width}x{size.Height}.", memoryException)
39+
{
40+
}
3541
}
3642
}

src/ImageSharp/Formats/Bmp/BmpDecoder.cs

Lines changed: 10 additions & 32 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;
56
using System.Threading.Tasks;
67
using SixLabors.ImageSharp.IO;
78
using SixLabors.ImageSharp.Memory;
@@ -36,65 +37,42 @@ public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
3637
Guard.NotNull(stream, nameof(stream));
3738

3839
var decoder = new BmpDecoderCore(configuration, this);
39-
40-
try
41-
{
42-
using var bufferedStream = new BufferedReadStream(configuration, stream);
43-
return decoder.Decode<TPixel>(bufferedStream);
44-
}
45-
catch (InvalidMemoryOperationException ex)
46-
{
47-
Size dims = decoder.Dimensions;
48-
49-
throw new InvalidImageContentException($"Cannot 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);
50-
}
40+
return decoder.Decode<TPixel>(configuration, stream);
5141
}
5242

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

5747
/// <inheritdoc/>
58-
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
48+
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
5949
where TPixel : unmanaged, IPixel<TPixel>
6050
{
6151
Guard.NotNull(stream, nameof(stream));
6252

6353
var decoder = new BmpDecoderCore(configuration, this);
64-
65-
try
66-
{
67-
using var bufferedStream = new BufferedReadStream(configuration, stream);
68-
return await decoder.DecodeAsync<TPixel>(bufferedStream).ConfigureAwait(false);
69-
}
70-
catch (InvalidMemoryOperationException ex)
71-
{
72-
Size dims = decoder.Dimensions;
73-
74-
throw new InvalidImageContentException($"Cannot 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);
75-
}
54+
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
7655
}
7756

7857
/// <inheritdoc />
79-
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream)
80-
=> await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
58+
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
59+
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken)
60+
.ConfigureAwait(false);
8161

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

87-
using var bufferedStream = new BufferedReadStream(configuration, stream);
88-
return new BmpDecoderCore(configuration, this).Identify(bufferedStream);
67+
return new BmpDecoderCore(configuration, this).Identify(configuration, stream);
8968
}
9069

9170
/// <inheritdoc/>
92-
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
71+
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
9372
{
9473
Guard.NotNull(stream, nameof(stream));
9574

96-
using var bufferedStream = new BufferedReadStream(configuration, stream);
97-
return new BmpDecoderCore(configuration, this).IdentifyAsync(bufferedStream);
75+
return new BmpDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken);
9876
}
9977
}
10078
}

src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Buffers.Binary;
77
using System.Numerics;
88
using System.Runtime.CompilerServices;
9+
using System.Threading;
910
using SixLabors.ImageSharp.Common.Helpers;
1011
using SixLabors.ImageSharp.IO;
1112
using SixLabors.ImageSharp.Memory;
@@ -118,7 +119,7 @@ public BmpDecoderCore(Configuration configuration, IBmpDecoderOptions options)
118119
public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height);
119120

120121
/// <inheritdoc />
121-
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
122+
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
122123
where TPixel : unmanaged, IPixel<TPixel>
123124
{
124125
try
@@ -197,7 +198,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
197198
}
198199

199200
/// <inheritdoc />
200-
public IImageInfo Identify(BufferedReadStream stream)
201+
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
201202
{
202203
this.ReadImageHeaders(stream, out _, out _);
203204
return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metadata);

src/ImageSharp/Formats/Bmp/BmpEncoder.cs

Lines changed: 3 additions & 2 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;
56
using System.Threading.Tasks;
67
using SixLabors.ImageSharp.Advanced;
78
using SixLabors.ImageSharp.PixelFormats;
@@ -42,11 +43,11 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
4243
}
4344

4445
/// <inheritdoc/>
45-
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream)
46+
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
4647
where TPixel : unmanaged, IPixel<TPixel>
4748
{
4849
var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator());
49-
return encoder.EncodeAsync(image, stream);
50+
return encoder.EncodeAsync(image, stream, cancellationToken);
5051
}
5152
}
5253
}

src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Buffers;
66
using System.IO;
77
using System.Runtime.InteropServices;
8+
using System.Threading;
89
using System.Threading.Tasks;
910
using SixLabors.ImageSharp.Advanced;
1011
using SixLabors.ImageSharp.Common.Helpers;
@@ -19,7 +20,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
1920
/// <summary>
2021
/// Image encoder for writing an image to a stream as a Windows bitmap.
2122
/// </summary>
22-
internal sealed class BmpEncoderCore
23+
internal sealed class BmpEncoderCore : IImageEncoderInternals
2324
{
2425
/// <summary>
2526
/// The amount to pad each row by.
@@ -97,32 +98,9 @@ public BmpEncoderCore(IBmpEncoderOptions options, MemoryAllocator memoryAllocato
9798
/// <typeparam name="TPixel">The pixel format.</typeparam>
9899
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
99100
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
100-
public async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream)
101+
/// <param name="cancellationToken">The token to request cancellation.</param>
102+
public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
101103
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>
126104
{
127105
Guard.NotNull(image, nameof(image));
128106
Guard.NotNull(stream, nameof(stream));

src/ImageSharp/Formats/Gif/GifDecoder.cs

Lines changed: 10 additions & 38 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;
56
using System.Threading.Tasks;
67
using SixLabors.ImageSharp.IO;
78
using SixLabors.ImageSharp.Memory;
@@ -30,52 +31,25 @@ public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
3031
where TPixel : unmanaged, IPixel<TPixel>
3132
{
3233
var decoder = new GifDecoderCore(configuration, this);
33-
34-
try
35-
{
36-
using var bufferedStream = new BufferedReadStream(configuration, stream);
37-
return decoder.Decode<TPixel>(bufferedStream);
38-
}
39-
catch (InvalidMemoryOperationException ex)
40-
{
41-
Size dims = decoder.Dimensions;
42-
43-
GifThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
44-
45-
// Not reachable, as the previous statement will throw a exception.
46-
return null;
47-
}
34+
return decoder.Decode<TPixel>(configuration, stream);
4835
}
4936

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

5441
/// <inheritdoc/>
55-
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
42+
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
5643
where TPixel : unmanaged, IPixel<TPixel>
5744
{
5845
var decoder = new GifDecoderCore(configuration, this);
59-
60-
try
61-
{
62-
using var bufferedStream = new BufferedReadStream(configuration, stream);
63-
return await decoder.DecodeAsync<TPixel>(bufferedStream).ConfigureAwait(false);
64-
}
65-
catch (InvalidMemoryOperationException ex)
66-
{
67-
Size dims = decoder.Dimensions;
68-
69-
GifThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
70-
71-
// Not reachable, as the previous statement will throw a exception.
72-
return null;
73-
}
46+
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
7447
}
7548

7649
/// <inheritdoc />
77-
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream)
78-
=> await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
50+
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
51+
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken)
52+
.ConfigureAwait(false);
7953

8054
/// <inheritdoc/>
8155
public IImageInfo Identify(Configuration configuration, Stream stream)
@@ -85,18 +59,16 @@ public IImageInfo Identify(Configuration configuration, Stream stream)
8559
var decoder = new GifDecoderCore(configuration, this);
8660

8761
using var bufferedStream = new BufferedReadStream(configuration, stream);
88-
return decoder.Identify(bufferedStream);
62+
return decoder.Identify(bufferedStream, default);
8963
}
9064

9165
/// <inheritdoc/>
92-
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
66+
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
9367
{
9468
Guard.NotNull(stream, nameof(stream));
9569

9670
var decoder = new GifDecoderCore(configuration, this);
97-
98-
using var bufferedStream = new BufferedReadStream(configuration, stream);
99-
return decoder.IdentifyAsync(bufferedStream);
71+
return decoder.IdentifyAsync(configuration, stream, cancellationToken);
10072
}
10173
}
10274
}

src/ImageSharp/Formats/Gif/GifDecoderCore.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Runtime.CompilerServices;
77
using System.Runtime.InteropServices;
88
using System.Text;
9+
using System.Threading;
910
using System.Threading.Tasks;
1011
using SixLabors.ImageSharp.IO;
1112
using SixLabors.ImageSharp.Memory;
@@ -97,7 +98,7 @@ public GifDecoderCore(Configuration configuration, IGifDecoderOptions options)
9798
private MemoryAllocator MemoryAllocator => this.Configuration.MemoryAllocator;
9899

99100
/// <inheritdoc />
100-
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
101+
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
101102
where TPixel : unmanaged, IPixel<TPixel>
102103
{
103104
Image<TPixel> image = null;
@@ -158,7 +159,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
158159
}
159160

160161
/// <inheritdoc />
161-
public IImageInfo Identify(BufferedReadStream stream)
162+
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
162163
{
163164
try
164165
{

src/ImageSharp/Formats/Gif/GifEncoder.cs

Lines changed: 3 additions & 2 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;
56
using System.Threading.Tasks;
67
using SixLabors.ImageSharp.Advanced;
78
using SixLabors.ImageSharp.PixelFormats;
@@ -41,11 +42,11 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
4142
}
4243

4344
/// <inheritdoc/>
44-
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream)
45+
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
4546
where TPixel : unmanaged, IPixel<TPixel>
4647
{
4748
var encoder = new GifEncoderCore(image.GetConfiguration(), this);
48-
return encoder.EncodeAsync(image, stream);
49+
return encoder.EncodeAsync(image, stream, cancellationToken);
4950
}
5051
}
5152
}

0 commit comments

Comments
 (0)