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

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

@ -1,8 +1,11 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
@ -284,5 +287,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
IccProfile values = input.Metadata.IccProfile; IccProfile values = input.Metadata.IccProfile;
Assert.Equal(values.Entries, actual.Entries); 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;
using System.IO; using System.IO;
using System.Threading;
using Moq; using Moq;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using Xunit; using Xunit;
@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Tests
[InlineData("test.bmp")] [InlineData("test.bmp")]
[InlineData("test.jpg")] [InlineData("test.jpg")]
[InlineData("test.gif")] [InlineData("test.gif")]
public async Task SaveNeverCallsSyncMethods(string filename) public async Task SaveAsync_NeverCallsSyncMethods(string filename)
{ {
using (var image = new Image<Rgba32>(5, 5)) 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