Browse Source

implement JpegEncoder cancellation

pull/1296/head
Anton Firszov 6 years ago
parent
commit
f69abc36e9
  1. 18
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  2. 28
      tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
  3. 20
      tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs

18
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -201,6 +201,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
cancellationToken.ThrowIfCancellationRequested();
const ushort max = JpegConstants.MaxLength;
if (image.Width >= max || image.Height >= max)
@ -249,7 +250,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.WriteDefineHuffmanTables(componentCount);
// Write the image data.
this.WriteStartOfScan(image);
this.WriteStartOfScan(image, cancellationToken);
// Write the End Of Image marker.
this.buffer[0] = JpegConstants.Markers.XFF;
@ -398,7 +399,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
private void Encode444<TPixel>(Image<TPixel> pixels)
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
private void Encode444<TPixel>(Image<TPixel> pixels, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
@ -420,6 +422,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
for (int y = 0; y < pixels.Height; y += 8)
{
cancellationToken.ThrowIfCancellationRequested();
var currentRows = new RowOctet<TPixel>(pixelBuffer, y);
for (int x = 0; x < pixels.Width; x += 8)
@ -945,7 +948,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The pixel accessor providing access to the image pixels.</param>
private void WriteStartOfScan<TPixel>(Image<TPixel> image)
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
private void WriteStartOfScan<TPixel>(Image<TPixel> image, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
@ -955,10 +959,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
switch (this.subsample)
{
case JpegSubsample.Ratio444:
this.Encode444(image);
this.Encode444(image, cancellationToken);
break;
case JpegSubsample.Ratio420:
this.Encode420(image);
this.Encode420(image, cancellationToken);
break;
}
@ -972,7 +976,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
private void Encode420<TPixel>(Image<TPixel> pixels)
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
private void Encode420<TPixel>(Image<TPixel> pixels, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
@ -1000,6 +1005,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
for (int y = 0; y < pixels.Height; y += 16)
{
cancellationToken.ThrowIfCancellationRequested();
for (int x = 0; x < pixels.Width; x += 16)
{
for (int i = 0; i < 4; i++)

28
tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs

@ -1,8 +1,11 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
@ -284,5 +287,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
IccProfile values = input.Metadata.IccProfile;
Assert.Equal(values.Entries, actual.Entries);
}
[Theory]
[InlineData(JpegSubsample.Ratio420, 0)]
[InlineData(JpegSubsample.Ratio420, 3)]
[InlineData(JpegSubsample.Ratio420, 10)]
[InlineData(JpegSubsample.Ratio444, 0)]
[InlineData(JpegSubsample.Ratio444, 3)]
[InlineData(JpegSubsample.Ratio444, 10)]
public async Task Encode_IsCancellable(JpegSubsample subsample, int cancellationDelayMs)
{
using var image = new Image<Rgba32>(5000, 5000);
using MemoryStream stream = new MemoryStream();
var cts = new CancellationTokenSource();
if (cancellationDelayMs == 0)
{
cts.Cancel();
}
else
{
cts.CancelAfter(cancellationDelayMs);
}
var encoder = new JpegEncoder() { Subsample = subsample };
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => image.SaveAsync(stream, encoder, cts.Token));
}
}
}

20
tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs

@ -3,9 +3,9 @@
using System;
using System.IO;
using System.Threading;
using Moq;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Tests
[InlineData("test.bmp")]
[InlineData("test.jpg")]
[InlineData("test.gif")]
public async Task SaveNeverCallsSyncMethods(string filename)
public async Task SaveAsync_NeverCallsSyncMethods(string filename)
{
using (var image = new Image<Rgba32>(5, 5))
{
@ -102,6 +102,20 @@ namespace SixLabors.ImageSharp.Tests
}
}
}
[Fact]
public async Task SaveAsync_WithNonSeekableStream_IsCancellable()
{
using var image = new Image<Rgba32>(500, 500);
IImageEncoder encoder = new BmpEncoder();
using var stream = new MemoryStream();
var asyncStream = new AsyncStreamWrapper(stream, () => false);
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromTicks(1));
await Assert.ThrowsAnyAsync<TaskCanceledException>(() =>
image.SaveAsync(asyncStream, encoder, cts.Token));
}
}
}
}

Loading…
Cancel
Save