Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cancellable codec API, and overloads for loading/saving #1296

Merged
merged 29 commits into from
Aug 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
fcecfc1
hax
antonfirsov Jun 10, 2020
21c324d
add cancellable overloads
antonfirsov Jul 21, 2020
a225d1e
add asynchronous decoder logic
antonfirsov Jul 22, 2020
1de73ff
fix tests
antonfirsov Jul 22, 2020
672420c
SemaphoreReadMemoryStream
antonfirsov Jul 22, 2020
42d440e
Merge remote-tracking branch 'origin/js/fix-1276' into af/cancellatio…
antonfirsov Jul 25, 2020
f31c8c1
adapt #1286
antonfirsov Jul 25, 2020
4e70ae5
tests for general cancellation
antonfirsov Jul 25, 2020
0994f61
tons of new overloads
antonfirsov Jul 25, 2020
2a22e86
implement cancellation for Jpeg
antonfirsov Jul 25, 2020
706fedc
better cancellation in jpeg
antonfirsov Jul 25, 2020
874104e
cancellation support for encoders
antonfirsov Jul 25, 2020
166741c
cancellation for Image.SaveAs***
antonfirsov Jul 26, 2020
f69abc3
implement JpegEncoder cancellation
antonfirsov Jul 26, 2020
fb54efb
undo local hacks
antonfirsov Jul 26, 2020
3d77cbe
fix Framework build
antonfirsov Jul 26, 2020
202d996
fix bug in ImageExtensions
antonfirsov Jul 26, 2020
2a63664
fix tests
antonfirsov Jul 26, 2020
97c55d6
skip JpegTests.Identify_IsCancellable
antonfirsov Jul 26, 2020
d91f090
OperationCanceledException -> TaskCanceledException
antonfirsov Jul 30, 2020
6e0149d
Merge remote-tracking branch 'origin/master' into af/cancellation
antonfirsov Jul 31, 2020
f2f3a30
oops the MemoryStream
antonfirsov Jul 31, 2020
9c47c19
try making SaveAsync_WithNonSeekableStream_IsCancellable more robust
antonfirsov Jul 31, 2020
50a6ba1
use default args for CancellationTokens
antonfirsov Aug 3, 2020
1447fbc
address review findings
antonfirsov Aug 3, 2020
30364ac
add ConfigureAwait
antonfirsov Aug 4, 2020
21d94f2
one leftover
antonfirsov Aug 4, 2020
5a4aea8
Merge branch 'master' into af/cancellation
JimBobSquarePants Aug 4, 2020
e707a8d
Minor fixes.
JimBobSquarePants Aug 4, 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
6 changes: 4 additions & 2 deletions src/ImageSharp/Advanced/AdvancedImageExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory;
Expand Down Expand Up @@ -73,9 +74,10 @@ public static void AcceptVisitor(this Image source, IImageVisitor visitor)
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="visitor">The image visitor.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor)
=> source.AcceptAsync(visitor);
public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor, CancellationToken cancellationToken = default)
=> source.AcceptAsync(visitor, cancellationToken);

/// <summary>
/// Gets the configuration for the image.
Expand Down
4 changes: 3 additions & 1 deletion src/ImageSharp/Advanced/IImageVisitor.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

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

Expand Down Expand Up @@ -31,9 +32,10 @@ public interface IImageVisitorAsync
/// Provides a pixel-specific implementation for a given operation.
/// </summary>
/// <param name="image">The image.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task VisitAsync<TPixel>(Image<TPixel> image)
Task VisitAsync<TPixel>(Image<TPixel> image, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.

using System;
using SixLabors.ImageSharp.Memory;

namespace SixLabors.ImageSharp
{
Expand Down Expand Up @@ -32,5 +33,10 @@ public InvalidImageContentException(string errorMessage, Exception innerExceptio
: base(errorMessage, innerException)
{
}

internal InvalidImageContentException(Size size, InvalidMemoryOperationException memoryException)
: this($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {size.Width}x{size.Height}.", memoryException)
{
}
}
}
42 changes: 10 additions & 32 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 Apache License, Version 2.0.

using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
Expand Down Expand Up @@ -36,65 +37,42 @@ public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
Guard.NotNull(stream, nameof(stream));

var decoder = new BmpDecoderCore(configuration, this);

try
{
using var bufferedStream = new BufferedReadStream(configuration, stream);
return decoder.Decode<TPixel>(bufferedStream);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;

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);
}
return decoder.Decode<TPixel>(configuration, stream);
}

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

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

var decoder = new BmpDecoderCore(configuration, this);

try
{
using var bufferedStream = new BufferedReadStream(configuration, stream);
return await decoder.DecodeAsync<TPixel>(bufferedStream).ConfigureAwait(false);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;

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);
}
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}

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

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

using var bufferedStream = new BufferedReadStream(configuration, stream);
return new BmpDecoderCore(configuration, this).Identify(bufferedStream);
return new BmpDecoderCore(configuration, this).Identify(configuration, stream);
}

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

using var bufferedStream = new BufferedReadStream(configuration, stream);
return new BmpDecoderCore(configuration, this).IdentifyAsync(bufferedStream);
return new BmpDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken);
}
}
}
5 changes: 3 additions & 2 deletions src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Buffers.Binary;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
Expand Down Expand Up @@ -118,7 +119,7 @@ public BmpDecoderCore(Configuration configuration, IBmpDecoderOptions options)
public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height);

/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
try
Expand Down Expand Up @@ -197,7 +198,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
}

/// <inheritdoc />
public IImageInfo Identify(BufferedReadStream stream)
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.ReadImageHeaders(stream, out _, out _);
return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metadata);
Expand Down
5 changes: 3 additions & 2 deletions src/ImageSharp/Formats/Bmp/BmpEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.

using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
Expand Down Expand Up @@ -42,11 +43,11 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
}

/// <inheritdoc/>
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream)
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator());
return encoder.EncodeAsync(image, stream);
return encoder.EncodeAsync(image, stream, cancellationToken);
}
}
}
30 changes: 4 additions & 26 deletions src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Buffers;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers;
Expand All @@ -19,7 +20,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary>
/// Image encoder for writing an image to a stream as a Windows bitmap.
/// </summary>
internal sealed class BmpEncoderCore
internal sealed class BmpEncoderCore : IImageEncoderInternals
{
/// <summary>
/// The amount to pad each row by.
Expand Down Expand Up @@ -97,32 +98,9 @@ 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 async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream)
/// <param name="cancellationToken">The token to request cancellation.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
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
48 changes: 10 additions & 38 deletions src/ImageSharp/Formats/Gif/GifDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.

using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
Expand Down Expand Up @@ -30,52 +31,25 @@ public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new GifDecoderCore(configuration, this);

try
{
using var bufferedStream = new BufferedReadStream(configuration, stream);
return decoder.Decode<TPixel>(bufferedStream);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;

GifThrowHelper.ThrowInvalidImageContentException($"Cannot 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;
}
return decoder.Decode<TPixel>(configuration, stream);
}

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

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

try
{
using var bufferedStream = new BufferedReadStream(configuration, stream);
return await decoder.DecodeAsync<TPixel>(bufferedStream).ConfigureAwait(false);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;

GifThrowHelper.ThrowInvalidImageContentException($"Cannot 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;
}
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}

/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream)
=> await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken)
tocsoft marked this conversation as resolved.
Show resolved Hide resolved
.ConfigureAwait(false);

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

using var bufferedStream = new BufferedReadStream(configuration, stream);
return decoder.Identify(bufferedStream);
return decoder.Identify(bufferedStream, default);
}

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

var decoder = new GifDecoderCore(configuration, this);

using var bufferedStream = new BufferedReadStream(configuration, stream);
return decoder.IdentifyAsync(bufferedStream);
return decoder.IdentifyAsync(configuration, stream, cancellationToken);
}
}
}
5 changes: 3 additions & 2 deletions src/ImageSharp/Formats/Gif/GifDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
Expand Down Expand Up @@ -97,7 +98,7 @@ public GifDecoderCore(Configuration configuration, IGifDecoderOptions options)
private MemoryAllocator MemoryAllocator => this.Configuration.MemoryAllocator;

/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Image<TPixel> image = null;
Expand Down Expand Up @@ -158,7 +159,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
}

/// <inheritdoc />
public IImageInfo Identify(BufferedReadStream stream)
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
try
{
Expand Down
5 changes: 3 additions & 2 deletions src/ImageSharp/Formats/Gif/GifEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.

using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
Expand Down Expand Up @@ -41,11 +42,11 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
}

/// <inheritdoc/>
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream)
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new GifEncoderCore(image.GetConfiguration(), this);
return encoder.EncodeAsync(image, stream);
return encoder.EncodeAsync(image, stream, cancellationToken);
}
}
}
Loading