Browse Source

Add tests and fix issues.

pull/2844/head
James Jackson-South 1 year ago
parent
commit
502a354262
  1. 47
      src/ImageSharp/Formats/Cur/CurFrameMetadata.cs
  2. 42
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  3. 47
      src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs
  4. 4
      src/ImageSharp/Formats/Icon/IconEncoderCore.cs
  5. 75
      src/ImageSharp/Formats/Pbm/BinaryEncoder.cs
  6. 22
      src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs
  7. 68
      src/ImageSharp/Formats/Pbm/PlainEncoder.cs
  8. 1
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  9. 6
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  10. 8
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
  11. 8
      src/ImageSharp/Formats/Webp/WebpEncoder.cs
  12. 4
      src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
  13. 60
      tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
  14. 58
      tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
  15. 8
      tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs
  16. 67
      tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs
  17. 28
      tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs
  18. 59
      tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs
  19. 4
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  20. 59
      tests/ImageSharp.Tests/Formats/Qoi/QoiEncoderTests.cs
  21. 152
      tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs
  22. 5
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
  23. 12
      tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs
  24. 131
      tests/ImageSharp.Tests/Image/ImageTests.EncodeCancellation.cs
  25. 13
      tests/ImageSharp.Tests/TestUtilities/PausedMemoryStream.cs
  26. 4
      tests/ImageSharp.Tests/TestUtilities/PausedStream.cs

47
src/ImageSharp/Formats/Cur/CurFrameMetadata.cs

@ -48,13 +48,13 @@ public class CurFrameMetadata : IFormatFrameMetadata<CurFrameMetadata>
/// Gets or sets the encoding width. <br /> /// Gets or sets the encoding width. <br />
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater.
/// </summary> /// </summary>
public byte EncodingWidth { get; set; } public byte? EncodingWidth { get; set; }
/// <summary> /// <summary>
/// Gets or sets the encoding height. <br /> /// Gets or sets the encoding height. <br />
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater.
/// </summary> /// </summary>
public byte EncodingHeight { get; set; } public byte? EncodingHeight { get; set; }
/// <summary> /// <summary>
/// Gets or sets the number of bits per pixel.<br/> /// Gets or sets the number of bits per pixel.<br/>
@ -80,20 +80,6 @@ public class CurFrameMetadata : IFormatFrameMetadata<CurFrameMetadata>
}; };
} }
byte encodingWidth = metadata.EncodingWidth switch
{
> 255 => 0,
<= 255 and >= 1 => (byte)metadata.EncodingWidth,
_ => 0
};
byte encodingHeight = metadata.EncodingHeight switch
{
> 255 => 0,
<= 255 and >= 1 => (byte)metadata.EncodingHeight,
_ => 0
};
int bpp = metadata.PixelTypeInfo.Value.BitsPerPixel; int bpp = metadata.PixelTypeInfo.Value.BitsPerPixel;
BmpBitsPerPixel bbpp = bpp switch BmpBitsPerPixel bbpp = bpp switch
{ {
@ -116,8 +102,8 @@ public class CurFrameMetadata : IFormatFrameMetadata<CurFrameMetadata>
{ {
BmpBitsPerPixel = bbpp, BmpBitsPerPixel = bbpp,
Compression = compression, Compression = compression,
EncodingWidth = encodingWidth, EncodingWidth = ClampEncodingDimension(metadata.EncodingWidth),
EncodingHeight = encodingHeight, EncodingHeight = ClampEncodingDimension(metadata.EncodingHeight),
ColorTable = compression == IconFrameCompression.Bmp ? metadata.ColorTable : null ColorTable = compression == IconFrameCompression.Bmp ? metadata.ColorTable : null
}; };
} }
@ -138,8 +124,8 @@ public class CurFrameMetadata : IFormatFrameMetadata<CurFrameMetadata>
{ {
float ratioX = destination.Width / (float)source.Width; float ratioX = destination.Width / (float)source.Width;
float ratioY = destination.Height / (float)source.Height; float ratioY = destination.Height / (float)source.Height;
this.EncodingWidth = Scale(this.EncodingWidth, destination.Width, ratioX); this.EncodingWidth = ScaleEncodingDimension(this.EncodingWidth, destination.Width, ratioX);
this.EncodingHeight = Scale(this.EncodingHeight, destination.Height, ratioY); this.EncodingHeight = ScaleEncodingDimension(this.EncodingHeight, destination.Height, ratioY);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -156,7 +142,7 @@ public class CurFrameMetadata : IFormatFrameMetadata<CurFrameMetadata>
this.HotspotY = entry.BitCount; this.HotspotY = entry.BitCount;
} }
internal IconDirEntry ToIconDirEntry() internal IconDirEntry ToIconDirEntry(Size size)
{ {
byte colorCount = this.Compression == IconFrameCompression.Png || this.BmpBitsPerPixel > BmpBitsPerPixel.Bit8 byte colorCount = this.Compression == IconFrameCompression.Png || this.BmpBitsPerPixel > BmpBitsPerPixel.Bit8
? (byte)0 ? (byte)0
@ -164,8 +150,8 @@ public class CurFrameMetadata : IFormatFrameMetadata<CurFrameMetadata>
return new() return new()
{ {
Width = this.EncodingWidth, Width = ClampEncodingDimension(this.EncodingWidth ?? size.Width),
Height = this.EncodingHeight, Height = ClampEncodingDimension(this.EncodingHeight ?? size.Height),
Planes = this.HotspotX, Planes = this.HotspotX,
BitCount = this.HotspotY, BitCount = this.HotspotY,
ColorCount = colorCount ColorCount = colorCount
@ -233,13 +219,22 @@ public class CurFrameMetadata : IFormatFrameMetadata<CurFrameMetadata>
}; };
} }
private static byte Scale(byte? value, int destination, float ratio) private static byte ScaleEncodingDimension(byte? value, int destination, float ratio)
{ {
if (value is null) if (value is null)
{ {
return (byte)Math.Clamp(destination, 0, 255); return ClampEncodingDimension(destination);
} }
return Math.Min((byte)MathF.Ceiling(value.Value * ratio), (byte)Math.Clamp(destination, 0, 255)); return ClampEncodingDimension(MathF.Ceiling(value.Value * ratio));
} }
private static byte ClampEncodingDimension(float? dimension)
=> dimension switch
{
// Encoding dimensions can be between 0-256 where 0 means 256 or greater.
> 255 => 0,
<= 255 and >= 1 => (byte)dimension,
_ => 0
};
} }

42
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -207,22 +207,29 @@ internal sealed class GifEncoderCore
this.WriteApplicationExtensions(stream, image.Frames.Count, this.repeatCount ?? gifMetadata.RepeatCount, xmpProfile); this.WriteApplicationExtensions(stream, image.Frames.Count, this.repeatCount ?? gifMetadata.RepeatCount, xmpProfile);
} }
this.EncodeFirstFrame(stream, frameMetadata, quantized); // If the token is cancelled during encoding of frames we must ensure the
// quantized frame is disposed.
// Capture the global palette for reuse on subsequent frames and cleanup the quantized frame. try
TPixel[] globalPalette = image.Frames.Count == 1 ? [] : quantized.Palette.ToArray(); {
this.EncodeFirstFrame(stream, frameMetadata, quantized, cancellationToken);
this.EncodeAdditionalFrames(
stream, // Capture the global palette for reuse on subsequent frames and cleanup the quantized frame.
image, TPixel[] globalPalette = image.Frames.Count == 1 ? [] : quantized.Palette.ToArray();
globalPalette,
derivedTransparencyIndex, this.EncodeAdditionalFrames(
frameMetadata.DisposalMode, stream,
cancellationToken); image,
globalPalette,
stream.WriteByte(GifConstants.EndIntroducer); derivedTransparencyIndex,
frameMetadata.DisposalMode,
cancellationToken);
}
finally
{
stream.WriteByte(GifConstants.EndIntroducer);
quantized?.Dispose(); quantized?.Dispose();
}
} }
private static GifFrameMetadata GetGifFrameMetadata<TPixel>(ImageFrame<TPixel> frame, int transparencyIndex) private static GifFrameMetadata GetGifFrameMetadata<TPixel>(ImageFrame<TPixel> frame, int transparencyIndex)
@ -310,9 +317,12 @@ internal sealed class GifEncoderCore
private void EncodeFirstFrame<TPixel>( private void EncodeFirstFrame<TPixel>(
Stream stream, Stream stream,
GifFrameMetadata metadata, GifFrameMetadata metadata,
IndexedImageFrame<TPixel> quantized) IndexedImageFrame<TPixel> quantized,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
cancellationToken.ThrowIfCancellationRequested();
this.WriteGraphicalControlExtension(metadata, stream); this.WriteGraphicalControlExtension(metadata, stream);
Buffer2D<byte> indices = ((IPixelSource)quantized).PixelBuffer; Buffer2D<byte> indices = ((IPixelSource)quantized).PixelBuffer;

47
src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs

@ -41,13 +41,13 @@ public class IcoFrameMetadata : IFormatFrameMetadata<IcoFrameMetadata>
/// Gets or sets the encoding width. <br /> /// Gets or sets the encoding width. <br />
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater.
/// </summary> /// </summary>
public byte EncodingWidth { get; set; } public byte? EncodingWidth { get; set; }
/// <summary> /// <summary>
/// Gets or sets the encoding height. <br /> /// Gets or sets the encoding height. <br />
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater.
/// </summary> /// </summary>
public byte EncodingHeight { get; set; } public byte? EncodingHeight { get; set; }
/// <summary> /// <summary>
/// Gets or sets the number of bits per pixel.<br/> /// Gets or sets the number of bits per pixel.<br/>
@ -73,20 +73,6 @@ public class IcoFrameMetadata : IFormatFrameMetadata<IcoFrameMetadata>
}; };
} }
byte encodingWidth = metadata.EncodingWidth switch
{
> 255 => 0,
<= 255 and >= 1 => (byte)metadata.EncodingWidth,
_ => 0
};
byte encodingHeight = metadata.EncodingHeight switch
{
> 255 => 0,
<= 255 and >= 1 => (byte)metadata.EncodingHeight,
_ => 0
};
int bpp = metadata.PixelTypeInfo.Value.BitsPerPixel; int bpp = metadata.PixelTypeInfo.Value.BitsPerPixel;
BmpBitsPerPixel bbpp = bpp switch BmpBitsPerPixel bbpp = bpp switch
{ {
@ -109,8 +95,8 @@ public class IcoFrameMetadata : IFormatFrameMetadata<IcoFrameMetadata>
{ {
BmpBitsPerPixel = bbpp, BmpBitsPerPixel = bbpp,
Compression = compression, Compression = compression,
EncodingWidth = encodingWidth, EncodingWidth = ClampEncodingDimension(metadata.EncodingWidth),
EncodingHeight = encodingHeight, EncodingHeight = ClampEncodingDimension(metadata.EncodingHeight),
ColorTable = compression == IconFrameCompression.Bmp ? metadata.ColorTable : null ColorTable = compression == IconFrameCompression.Bmp ? metadata.ColorTable : null
}; };
} }
@ -131,8 +117,8 @@ public class IcoFrameMetadata : IFormatFrameMetadata<IcoFrameMetadata>
{ {
float ratioX = destination.Width / (float)source.Width; float ratioX = destination.Width / (float)source.Width;
float ratioY = destination.Height / (float)source.Height; float ratioY = destination.Height / (float)source.Height;
this.EncodingWidth = Scale(this.EncodingWidth, destination.Width, ratioX); this.EncodingWidth = ScaleEncodingDimension(this.EncodingWidth, destination.Width, ratioX);
this.EncodingHeight = Scale(this.EncodingHeight, destination.Height, ratioY); this.EncodingHeight = ScaleEncodingDimension(this.EncodingHeight, destination.Height, ratioY);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -147,7 +133,7 @@ public class IcoFrameMetadata : IFormatFrameMetadata<IcoFrameMetadata>
this.EncodingHeight = entry.Height; this.EncodingHeight = entry.Height;
} }
internal IconDirEntry ToIconDirEntry() internal IconDirEntry ToIconDirEntry(Size size)
{ {
byte colorCount = this.Compression == IconFrameCompression.Png || this.BmpBitsPerPixel > BmpBitsPerPixel.Bit8 byte colorCount = this.Compression == IconFrameCompression.Png || this.BmpBitsPerPixel > BmpBitsPerPixel.Bit8
? (byte)0 ? (byte)0
@ -155,8 +141,8 @@ public class IcoFrameMetadata : IFormatFrameMetadata<IcoFrameMetadata>
return new() return new()
{ {
Width = this.EncodingWidth, Width = ClampEncodingDimension(this.EncodingWidth ?? size.Width),
Height = this.EncodingHeight, Height = ClampEncodingDimension(this.EncodingHeight ?? size.Height),
Planes = 1, Planes = 1,
ColorCount = colorCount, ColorCount = colorCount,
BitCount = this.Compression switch BitCount = this.Compression switch
@ -228,13 +214,22 @@ public class IcoFrameMetadata : IFormatFrameMetadata<IcoFrameMetadata>
}; };
} }
private static byte Scale(byte? value, int destination, float ratio) private static byte ScaleEncodingDimension(byte? value, int destination, float ratio)
{ {
if (value is null) if (value is null)
{ {
return (byte)Math.Clamp(destination, 0, 255); return ClampEncodingDimension(destination);
} }
return Math.Min((byte)MathF.Ceiling(value.Value * ratio), (byte)Math.Clamp(destination, 0, 255)); return ClampEncodingDimension(MathF.Ceiling(value.Value * ratio));
} }
private static byte ClampEncodingDimension(float? dimension)
=> dimension switch
{
// Encoding dimensions can be between 0-256 where 0 means 256 or greater.
> 255 => 0,
<= 255 and >= 1 => (byte)dimension,
_ => 0
};
} }

4
src/ImageSharp/Formats/Icon/IconEncoderCore.cs

@ -123,13 +123,13 @@ internal abstract class IconEncoderCore
image.Frames.Select(i => image.Frames.Select(i =>
{ {
IcoFrameMetadata metadata = i.Metadata.GetIcoMetadata(); IcoFrameMetadata metadata = i.Metadata.GetIcoMetadata();
return new EncodingFrameMetadata(metadata.Compression, metadata.BmpBitsPerPixel, metadata.ColorTable, metadata.ToIconDirEntry()); return new EncodingFrameMetadata(metadata.Compression, metadata.BmpBitsPerPixel, metadata.ColorTable, metadata.ToIconDirEntry(i.Size));
}).ToArray(), }).ToArray(),
IconFileType.CUR => IconFileType.CUR =>
image.Frames.Select(i => image.Frames.Select(i =>
{ {
CurFrameMetadata metadata = i.Metadata.GetCurMetadata(); CurFrameMetadata metadata = i.Metadata.GetCurMetadata();
return new EncodingFrameMetadata(metadata.Compression, metadata.BmpBitsPerPixel, metadata.ColorTable, metadata.ToIconDirEntry()); return new EncodingFrameMetadata(metadata.Compression, metadata.BmpBitsPerPixel, metadata.ColorTable, metadata.ToIconDirEntry(i.Size));
}).ToArray(), }).ToArray(),
_ => throw new NotSupportedException(), _ => throw new NotSupportedException(),
}; };

75
src/ImageSharp/Formats/Pbm/BinaryEncoder.cs

@ -17,25 +17,32 @@ internal class BinaryEncoder
/// </summary> /// </summary>
/// <typeparam name="TPixel">The type of input pixel.</typeparam> /// <typeparam name="TPixel">The type of input pixel.</typeparam>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
/// <param name="stream">The bytestream to write to.</param> /// <param name="stream">The byte stream to write to.</param>
/// <param name="image">The input image.</param> /// <param name="image">The input image.</param>
/// <param name="colorType">The ColorType to use.</param> /// <param name="colorType">The ColorType to use.</param>
/// <param name="componentType">Data type of the pixles components.</param> /// <param name="componentType">Data type of the pixels components.</param>
/// <exception cref="InvalidImageContentException"> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ImageFormatException">
/// Thrown if an invalid combination of setting is requested. /// Thrown if an invalid combination of setting is requested.
/// </exception> /// </exception>
public static void WritePixels<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image, PbmColorType colorType, PbmComponentType componentType) public static void WritePixels<TPixel>(
Configuration configuration,
Stream stream,
ImageFrame<TPixel> image,
PbmColorType colorType,
PbmComponentType componentType,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
if (colorType == PbmColorType.Grayscale) if (colorType == PbmColorType.Grayscale)
{ {
if (componentType == PbmComponentType.Byte) if (componentType == PbmComponentType.Byte)
{ {
WriteGrayscale(configuration, stream, image); WriteGrayscale(configuration, stream, image, cancellationToken);
} }
else if (componentType == PbmComponentType.Short) else if (componentType == PbmComponentType.Short)
{ {
WriteWideGrayscale(configuration, stream, image); WriteWideGrayscale(configuration, stream, image, cancellationToken);
} }
else else
{ {
@ -46,31 +53,28 @@ internal class BinaryEncoder
{ {
if (componentType == PbmComponentType.Byte) if (componentType == PbmComponentType.Byte)
{ {
WriteRgb(configuration, stream, image); WriteRgb(configuration, stream, image, cancellationToken);
} }
else if (componentType == PbmComponentType.Short) else if (componentType == PbmComponentType.Short)
{ {
WriteWideRgb(configuration, stream, image); WriteWideRgb(configuration, stream, image, cancellationToken);
} }
else else
{ {
throw new ImageFormatException("Component type not supported for Color PBM."); throw new ImageFormatException("Component type not supported for Color PBM.");
} }
} }
else else if (componentType == PbmComponentType.Bit)
{ {
if (componentType == PbmComponentType.Bit) WriteBlackAndWhite(configuration, stream, image, cancellationToken);
{
WriteBlackAndWhite(configuration, stream, image);
}
else
{
throw new ImageFormatException("Component type not supported for Black & White PBM.");
}
} }
} }
private static void WriteGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) private static void WriteGrayscale<TPixel>(
Configuration configuration,
Stream stream,
ImageFrame<TPixel> image,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = image.Width; int width = image.Width;
@ -82,6 +86,8 @@ internal class BinaryEncoder
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
cancellationToken.ThrowIfCancellationRequested();
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8Bytes( PixelOperations<TPixel>.Instance.ToL8Bytes(
@ -94,7 +100,11 @@ internal class BinaryEncoder
} }
} }
private static void WriteWideGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) private static void WriteWideGrayscale<TPixel>(
Configuration configuration,
Stream stream,
ImageFrame<TPixel> image,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
const int bytesPerPixel = 2; const int bytesPerPixel = 2;
@ -107,6 +117,8 @@ internal class BinaryEncoder
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
cancellationToken.ThrowIfCancellationRequested();
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToL16Bytes( PixelOperations<TPixel>.Instance.ToL16Bytes(
@ -119,7 +131,11 @@ internal class BinaryEncoder
} }
} }
private static void WriteRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) private static void WriteRgb<TPixel>(
Configuration configuration,
Stream stream,
ImageFrame<TPixel> image,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
const int bytesPerPixel = 3; const int bytesPerPixel = 3;
@ -132,6 +148,8 @@ internal class BinaryEncoder
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
cancellationToken.ThrowIfCancellationRequested();
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToRgb24Bytes( PixelOperations<TPixel>.Instance.ToRgb24Bytes(
@ -144,7 +162,11 @@ internal class BinaryEncoder
} }
} }
private static void WriteWideRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) private static void WriteWideRgb<TPixel>(
Configuration configuration,
Stream stream,
ImageFrame<TPixel> image,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
const int bytesPerPixel = 6; const int bytesPerPixel = 6;
@ -157,6 +179,8 @@ internal class BinaryEncoder
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
cancellationToken.ThrowIfCancellationRequested();
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToRgb48Bytes( PixelOperations<TPixel>.Instance.ToRgb48Bytes(
@ -169,7 +193,12 @@ internal class BinaryEncoder
} }
} }
private static void WriteBlackAndWhite<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) private static void WriteBlackAndWhite<TPixel>(
Configuration
configuration,
Stream stream,
ImageFrame<TPixel> image,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = image.Width; int width = image.Width;
@ -181,6 +210,8 @@ internal class BinaryEncoder
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
cancellationToken.ThrowIfCancellationRequested();
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8( PixelOperations<TPixel>.Instance.ToL8(

22
src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs

@ -68,8 +68,7 @@ internal sealed class PbmEncoderCore
byte signature = this.DeduceSignature(); byte signature = this.DeduceSignature();
this.WriteHeader(stream, signature, image.Size); this.WriteHeader(stream, signature, image.Size);
this.WritePixels(stream, image.Frames.RootFrame, cancellationToken);
this.WritePixels(stream, image.Frames.RootFrame);
stream.Flush(); stream.Flush();
} }
@ -167,16 +166,29 @@ internal sealed class PbmEncoderCore
/// <param name="image"> /// <param name="image">
/// The <see cref="ImageFrame{TPixel}"/> containing pixel data. /// The <see cref="ImageFrame{TPixel}"/> containing pixel data.
/// </param> /// </param>
private void WritePixels<TPixel>(Stream stream, ImageFrame<TPixel> image) /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
private void WritePixels<TPixel>(Stream stream, ImageFrame<TPixel> image, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
if (this.encoding == PbmEncoding.Plain) if (this.encoding == PbmEncoding.Plain)
{ {
PlainEncoder.WritePixels(this.configuration, stream, image, this.colorType, this.componentType); PlainEncoder.WritePixels(
this.configuration,
stream,
image,
this.colorType,
this.componentType,
cancellationToken);
} }
else else
{ {
BinaryEncoder.WritePixels(this.configuration, stream, image, this.colorType, this.componentType); BinaryEncoder.WritePixels(
this.configuration,
stream,
image,
this.colorType,
this.componentType,
cancellationToken);
} }
} }
} }

68
src/ImageSharp/Formats/Pbm/PlainEncoder.cs

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm;
/// <summary> /// <summary>
/// Pixel encoding methods for the PBM plain encoding. /// Pixel encoding methods for the PBM plain encoding.
/// </summary> /// </summary>
internal class PlainEncoder internal static class PlainEncoder
{ {
private const byte NewLine = 0x0a; private const byte NewLine = 0x0a;
private const byte Space = 0x20; private const byte Space = 0x20;
@ -31,45 +31,56 @@ internal class PlainEncoder
/// </summary> /// </summary>
/// <typeparam name="TPixel">The type of input pixel.</typeparam> /// <typeparam name="TPixel">The type of input pixel.</typeparam>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
/// <param name="stream">The bytestream to write to.</param> /// <param name="stream">The byte stream to write to.</param>
/// <param name="image">The input image.</param> /// <param name="image">The input image.</param>
/// <param name="colorType">The ColorType to use.</param> /// <param name="colorType">The ColorType to use.</param>
/// <param name="componentType">Data type of the pixles components.</param> /// <param name="componentType">Data type of the pixels components.</param>
public static void WritePixels<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image, PbmColorType colorType, PbmComponentType componentType) /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
public static void WritePixels<TPixel>(
Configuration configuration,
Stream stream,
ImageFrame<TPixel> image,
PbmColorType colorType,
PbmComponentType componentType,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
if (colorType == PbmColorType.Grayscale) if (colorType == PbmColorType.Grayscale)
{ {
if (componentType == PbmComponentType.Byte) if (componentType == PbmComponentType.Byte)
{ {
WriteGrayscale(configuration, stream, image); WriteGrayscale(configuration, stream, image, cancellationToken);
} }
else else
{ {
WriteWideGrayscale(configuration, stream, image); WriteWideGrayscale(configuration, stream, image, cancellationToken);
} }
} }
else if (colorType == PbmColorType.Rgb) else if (colorType == PbmColorType.Rgb)
{ {
if (componentType == PbmComponentType.Byte) if (componentType == PbmComponentType.Byte)
{ {
WriteRgb(configuration, stream, image); WriteRgb(configuration, stream, image, cancellationToken);
} }
else else
{ {
WriteWideRgb(configuration, stream, image); WriteWideRgb(configuration, stream, image, cancellationToken);
} }
} }
else else
{ {
WriteBlackAndWhite(configuration, stream, image); WriteBlackAndWhite(configuration, stream, image, cancellationToken);
} }
// Write EOF indicator, as some encoders expect it. // Write EOF indicator, as some encoders expect it.
stream.WriteByte(Space); stream.WriteByte(Space);
} }
private static void WriteGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) private static void WriteGrayscale<TPixel>(
Configuration configuration,
Stream stream,
ImageFrame<TPixel> image,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = image.Width; int width = image.Width;
@ -83,6 +94,8 @@ internal class PlainEncoder
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
cancellationToken.ThrowIfCancellationRequested();
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8( PixelOperations<TPixel>.Instance.ToL8(
configuration, configuration,
@ -102,7 +115,11 @@ internal class PlainEncoder
} }
} }
private static void WriteWideGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) private static void WriteWideGrayscale<TPixel>(
Configuration configuration,
Stream stream,
ImageFrame<TPixel> image,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = image.Width; int width = image.Width;
@ -116,6 +133,8 @@ internal class PlainEncoder
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
cancellationToken.ThrowIfCancellationRequested();
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToL16( PixelOperations<TPixel>.Instance.ToL16(
configuration, configuration,
@ -135,7 +154,11 @@ internal class PlainEncoder
} }
} }
private static void WriteRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) private static void WriteRgb<TPixel>(
Configuration configuration,
Stream stream,
ImageFrame<TPixel> image,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = image.Width; int width = image.Width;
@ -149,6 +172,8 @@ internal class PlainEncoder
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
cancellationToken.ThrowIfCancellationRequested();
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToRgb24( PixelOperations<TPixel>.Instance.ToRgb24(
configuration, configuration,
@ -174,7 +199,11 @@ internal class PlainEncoder
} }
} }
private static void WriteWideRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) private static void WriteWideRgb<TPixel>(
Configuration configuration,
Stream stream,
ImageFrame<TPixel> image,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = image.Width; int width = image.Width;
@ -188,6 +217,8 @@ internal class PlainEncoder
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
cancellationToken.ThrowIfCancellationRequested();
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToRgb48( PixelOperations<TPixel>.Instance.ToRgb48(
configuration, configuration,
@ -213,7 +244,11 @@ internal class PlainEncoder
} }
} }
private static void WriteBlackAndWhite<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) private static void WriteBlackAndWhite<TPixel>(
Configuration configuration,
Stream stream,
ImageFrame<TPixel> image,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = image.Width; int width = image.Width;
@ -227,6 +262,8 @@ internal class PlainEncoder
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
cancellationToken.ThrowIfCancellationRequested();
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8( PixelOperations<TPixel>.Instance.ToL8(
configuration, configuration,
@ -236,8 +273,7 @@ internal class PlainEncoder
int written = 0; int written = 0;
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
byte value = (rowSpan[x].PackedValue < 128) ? One : Zero; plainSpan[written++] = (rowSpan[x].PackedValue < 128) ? One : Zero;
plainSpan[written++] = value;
plainSpan[written++] = Space; plainSpan[written++] = Space;
} }

1
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -226,6 +226,7 @@ internal sealed class PngEncoderCore : IDisposable
bool userAnimateRootFrame = this.animateRootFrame == true; bool userAnimateRootFrame = this.animateRootFrame == true;
if ((!userAnimateRootFrame && !pngMetadata.AnimateRootFrame) || image.Frames.Count == 1) if ((!userAnimateRootFrame && !pngMetadata.AnimateRootFrame) || image.Frames.Count == 1)
{ {
cancellationToken.ThrowIfCancellationRequested();
FrameControl frameControl = new((uint)this.width, (uint)this.height); FrameControl frameControl = new((uint)this.width, (uint)this.height);
this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, false); this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, false);
currentFrameIndex++; currentFrameIndex++;

6
src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs

@ -137,7 +137,7 @@ internal sealed class TiffEncoderCore
long ifdMarker = WriteHeader(writer, buffer); long ifdMarker = WriteHeader(writer, buffer);
Image<TPixel>? metadataImage = image; Image<TPixel>? imageMetadata = image;
foreach (ImageFrame<TPixel> frame in image.Frames) foreach (ImageFrame<TPixel> frame in image.Frames)
{ {
@ -154,8 +154,8 @@ internal sealed class TiffEncoderCore
ImageFrame<TPixel> encodingFrame = clonedFrame ?? frame; ImageFrame<TPixel> encodingFrame = clonedFrame ?? frame;
ifdMarker = this.WriteFrame(writer, encodingFrame, image.Metadata, metadataImage, this.BitsPerPixel.Value, this.CompressionType.Value, ifdMarker); ifdMarker = this.WriteFrame(writer, encodingFrame, image.Metadata, imageMetadata, this.BitsPerPixel.Value, this.CompressionType.Value, ifdMarker);
metadataImage = null; imageMetadata = null;
} }
finally finally
{ {

8
src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs

@ -388,7 +388,13 @@ internal class Vp8Encoder : IDisposable
/// <param name="hasAnimation">Flag indicating, if an animation parameter is present.</param> /// <param name="hasAnimation">Flag indicating, if an animation parameter is present.</param>
/// <param name="image">The image to encode from.</param> /// <param name="image">The image to encode from.</param>
/// <returns>A <see cref="bool"/> indicating whether the frame contains an alpha channel.</returns> /// <returns>A <see cref="bool"/> indicating whether the frame contains an alpha channel.</returns>
private bool Encode<TPixel>(Stream stream, ImageFrame<TPixel> frame, Rectangle bounds, WebpFrameMetadata frameMetadata, bool hasAnimation, Image<TPixel> image) private bool Encode<TPixel>(
Stream stream,
ImageFrame<TPixel> frame,
Rectangle bounds,
WebpFrameMetadata frameMetadata,
bool hasAnimation,
Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = bounds.Width; int width = bounds.Width;

8
src/ImageSharp/Formats/Webp/WebpEncoder.cs

@ -8,6 +8,14 @@ namespace SixLabors.ImageSharp.Formats.Webp;
/// </summary> /// </summary>
public sealed class WebpEncoder : AnimatedImageEncoder public sealed class WebpEncoder : AnimatedImageEncoder
{ {
/// <summary>
/// Initializes a new instance of the <see cref="WebpEncoder"/> class.
/// </summary>
public WebpEncoder()
// Match the default behavior of the native reference encoder.
=> this.TransparentColorMode = TransparentColorMode.Clear;
/// <summary> /// <summary>
/// Gets the webp file format used. Either lossless or lossy. /// Gets the webp file format used. Either lossless or lossy.
/// Defaults to lossy. /// Defaults to lossy.

4
src/ImageSharp/Formats/Webp/WebpEncoderCore.cs

@ -166,6 +166,9 @@ internal sealed class WebpEncoderCore
// Encode the first frame. // Encode the first frame.
ImageFrame<TPixel> previousFrame = image.Frames.RootFrame; ImageFrame<TPixel> previousFrame = image.Frames.RootFrame;
WebpFrameMetadata frameMetadata = previousFrame.Metadata.GetWebpMetadata(); WebpFrameMetadata frameMetadata = previousFrame.Metadata.GetWebpMetadata();
cancellationToken.ThrowIfCancellationRequested();
hasAlpha |= encoder.Encode(previousFrame, previousFrame.Bounds, frameMetadata, stream, hasAnimation); hasAlpha |= encoder.Encode(previousFrame, previousFrame.Bounds, frameMetadata, stream, hasAnimation);
if (hasAnimation) if (hasAnimation)
@ -304,6 +307,7 @@ internal sealed class WebpEncoderCore
} }
else else
{ {
cancellationToken.ThrowIfCancellationRequested();
encoder.EncodeStatic(stream, image); encoder.EncodeStatic(stream, image);
encoder.EncodeFooter(image, in vp8x, hasAlpha, stream, initialPosition); encoder.EncodeFooter(image, in vp8x, hasAlpha, stream, initialPosition);
} }

60
tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs

@ -397,6 +397,66 @@ public class BmpEncoderTests
reencodedImage.CompareToOriginal(provider); reencodedImage.CompareToOriginal(provider);
} }
[Fact]
public void Encode_WithTransparentColorBehaviorClear_Works()
{
// arrange
using Image<Rgba32> image = new(50, 50);
BmpEncoder encoder = new()
{
BitsPerPixel = BmpBitsPerPixel.Bit32,
SupportTransparency = true,
TransparentColorMode = TransparentColorMode.Clear,
};
Rgba32 rgba32 = Color.Blue.ToPixel<Rgba32>();
image.ProcessPixelRows(accessor =>
{
for (int y = 0; y < image.Height; y++)
{
Span<Rgba32> rowSpan = accessor.GetRowSpan(y);
// Half of the test image should be transparent.
if (y > 25)
{
rgba32.A = 0;
}
for (int x = 0; x < image.Width; x++)
{
rowSpan[x] = Rgba32.FromRgba32(rgba32);
}
}
});
// act
using MemoryStream memStream = new();
image.Save(memStream, encoder);
// assert
memStream.Position = 0;
using Image<Rgba32> actual = Image.Load<Rgba32>(memStream);
Rgba32 expectedColor = Color.Blue.ToPixel<Rgba32>();
actual.ProcessPixelRows(accessor =>
{
Rgba32 transparent = Color.Transparent.ToPixel<Rgba32>();
for (int y = 0; y < accessor.Height; y++)
{
Span<Rgba32> rowSpan = accessor.GetRowSpan(y);
if (y > 25)
{
expectedColor = transparent;
}
for (int x = 0; x < accessor.Width; x++)
{
Assert.Equal(expectedColor, rowSpan[x]);
}
}
});
}
private static void TestBmpEncoderCore<TPixel>( private static void TestBmpEncoderCore<TPixel>(
TestImageProvider<TPixel> provider, TestImageProvider<TPixel> provider,
BmpBitsPerPixel bitsPerPixel, BmpBitsPerPixel bitsPerPixel,

58
tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs

@ -361,4 +361,62 @@ public class GifEncoderTests
provider.Utility.SaveTestOutputFile(image, "png", new PngEncoder(), "animated"); provider.Utility.SaveTestOutputFile(image, "png", new PngEncoder(), "animated");
provider.Utility.SaveTestOutputFile(image, "gif", new GifEncoder(), "animated"); provider.Utility.SaveTestOutputFile(image, "gif", new GifEncoder(), "animated");
} }
[Fact]
public void Encode_WithTransparentColorBehaviorClear_Works()
{
// arrange
using Image<Rgba32> image = new(50, 50);
GifEncoder encoder = new()
{
TransparentColorMode = TransparentColorMode.Clear,
};
Rgba32 rgba32 = Color.Blue.ToPixel<Rgba32>();
image.ProcessPixelRows(accessor =>
{
for (int y = 0; y < image.Height; y++)
{
Span<Rgba32> rowSpan = accessor.GetRowSpan(y);
// Half of the test image should be transparent.
if (y > 25)
{
rgba32.A = 0;
}
for (int x = 0; x < image.Width; x++)
{
rowSpan[x] = Rgba32.FromRgba32(rgba32);
}
}
});
// act
using MemoryStream memStream = new();
image.Save(memStream, encoder);
// assert
memStream.Position = 0;
using Image<Rgba32> actual = Image.Load<Rgba32>(memStream);
Rgba32 expectedColor = Color.Blue.ToPixel<Rgba32>();
actual.ProcessPixelRows(accessor =>
{
Rgba32 transparent = Color.Transparent.ToPixel<Rgba32>();
for (int y = 0; y < accessor.Height; y++)
{
Span<Rgba32> rowSpan = accessor.GetRowSpan(y);
if (y > 25)
{
expectedColor = transparent;
}
for (int x = 0; x < accessor.Width; x++)
{
Assert.Equal(expectedColor, rowSpan[x]);
}
}
});
}
} }

8
tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs

@ -20,8 +20,8 @@ public class CurDecoderTests
using Image<Rgba32> image = provider.GetImage(CurDecoder.Instance); using Image<Rgba32> image = provider.GetImage(CurDecoder.Instance);
CurFrameMetadata meta = image.Frames[0].Metadata.GetCurMetadata(); CurFrameMetadata meta = image.Frames[0].Metadata.GetCurMetadata();
Assert.Equal(image.Width, meta.EncodingWidth); Assert.Equal(image.Width, meta.EncodingWidth.Value);
Assert.Equal(image.Height, meta.EncodingHeight); Assert.Equal(image.Height, meta.EncodingHeight.Value);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression); Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel); Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel);
} }
@ -33,8 +33,8 @@ public class CurDecoderTests
{ {
using Image<Rgba32> image = provider.GetImage(CurDecoder.Instance); using Image<Rgba32> image = provider.GetImage(CurDecoder.Instance);
CurFrameMetadata meta = image.Frames[0].Metadata.GetCurMetadata(); CurFrameMetadata meta = image.Frames[0].Metadata.GetCurMetadata();
Assert.Equal(image.Width, meta.EncodingWidth); Assert.Equal(image.Width, meta.EncodingWidth.Value);
Assert.Equal(image.Height, meta.EncodingHeight); Assert.Equal(image.Height, meta.EncodingHeight.Value);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression); Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel); Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel);
} }

67
tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Cur; using SixLabors.ImageSharp.Formats.Cur;
using SixLabors.ImageSharp.Formats.Ico; using SixLabors.ImageSharp.Formats.Ico;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -63,4 +64,70 @@ public class CurEncoderTests
Assert.Equal(icoFrame.EncodingHeight, curFrame.EncodingHeight); Assert.Equal(icoFrame.EncodingHeight, curFrame.EncodingHeight);
} }
} }
[Fact]
public void Encode_WithTransparentColorBehaviorClear_Works()
{
// arrange
using Image<Rgba32> image = new(50, 50);
CurEncoder encoder = new()
{
TransparentColorMode = TransparentColorMode.Clear,
};
Rgba32 rgba32 = Color.Blue.ToPixel<Rgba32>();
image.ProcessPixelRows(accessor =>
{
for (int y = 0; y < image.Height; y++)
{
Span<Rgba32> rowSpan = accessor.GetRowSpan(y);
// Half of the test image should be transparent.
if (y > 25)
{
rgba32.A = 0;
}
for (int x = 0; x < image.Width; x++)
{
rowSpan[x] = Rgba32.FromRgba32(rgba32);
}
}
});
// act
using MemoryStream memStream = new();
image.Save(memStream, encoder);
// assert
memStream.Position = 0;
using Image<Rgba32> actual = Image.Load<Rgba32>(memStream);
Rgba32 expectedColor = Color.Blue.ToPixel<Rgba32>();
actual.ProcessPixelRows(accessor =>
{
Rgba32 transparent = Color.Transparent.ToPixel<Rgba32>();
for (int y = 0; y < accessor.Height; y++)
{
Span<Rgba32> rowSpan = accessor.GetRowSpan(y);
Span<Rgba32> rowSpanOpp = accessor.GetRowSpan(accessor.Height - y - 1);
if (y > 25)
{
expectedColor = transparent;
}
for (int x = 0; x < accessor.Width; x++)
{
if (expectedColor != rowSpan[x])
{
var xx = 0;
}
Assert.Equal(expectedColor, rowSpan[x]);
}
}
});
}
} }

28
tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs

@ -53,8 +53,8 @@ public class IcoDecoderTests
int expectedWidth = image.Width >= 256 ? 0 : image.Width; int expectedWidth = image.Width >= 256 ? 0 : image.Width;
int expectedHeight = image.Height >= 256 ? 0 : image.Height; int expectedHeight = image.Height >= 256 ? 0 : image.Height;
Assert.Equal(expectedWidth, meta.EncodingWidth); Assert.Equal(expectedWidth, meta.EncodingWidth.Value);
Assert.Equal(expectedHeight, meta.EncodingHeight); Assert.Equal(expectedHeight, meta.EncodingHeight.Value);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression); Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Bit1, meta.BmpBitsPerPixel); Assert.Equal(BmpBitsPerPixel.Bit1, meta.BmpBitsPerPixel);
} }
@ -89,8 +89,8 @@ public class IcoDecoderTests
int expectedWidth = image.Width >= 256 ? 0 : image.Width; int expectedWidth = image.Width >= 256 ? 0 : image.Width;
int expectedHeight = image.Height >= 256 ? 0 : image.Height; int expectedHeight = image.Height >= 256 ? 0 : image.Height;
Assert.Equal(expectedWidth, meta.EncodingWidth); Assert.Equal(expectedWidth, meta.EncodingWidth.Value);
Assert.Equal(expectedHeight, meta.EncodingHeight); Assert.Equal(expectedHeight, meta.EncodingHeight.Value);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression); Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Bit24, meta.BmpBitsPerPixel); Assert.Equal(BmpBitsPerPixel.Bit24, meta.BmpBitsPerPixel);
} }
@ -125,8 +125,8 @@ public class IcoDecoderTests
int expectedWidth = image.Width >= 256 ? 0 : image.Width; int expectedWidth = image.Width >= 256 ? 0 : image.Width;
int expectedHeight = image.Height >= 256 ? 0 : image.Height; int expectedHeight = image.Height >= 256 ? 0 : image.Height;
Assert.Equal(expectedWidth, meta.EncodingWidth); Assert.Equal(expectedWidth, meta.EncodingWidth.Value);
Assert.Equal(expectedHeight, meta.EncodingHeight); Assert.Equal(expectedHeight, meta.EncodingHeight.Value);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression); Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel); Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel);
} }
@ -160,8 +160,8 @@ public class IcoDecoderTests
int expectedWidth = image.Width >= 256 ? 0 : image.Width; int expectedWidth = image.Width >= 256 ? 0 : image.Width;
int expectedHeight = image.Height >= 256 ? 0 : image.Height; int expectedHeight = image.Height >= 256 ? 0 : image.Height;
Assert.Equal(expectedWidth, meta.EncodingWidth); Assert.Equal(expectedWidth, meta.EncodingWidth.Value);
Assert.Equal(expectedHeight, meta.EncodingHeight); Assert.Equal(expectedHeight, meta.EncodingHeight.Value);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression); Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Bit4, meta.BmpBitsPerPixel); Assert.Equal(BmpBitsPerPixel.Bit4, meta.BmpBitsPerPixel);
} }
@ -196,8 +196,8 @@ public class IcoDecoderTests
int expectedWidth = image.Width >= 256 ? 0 : image.Width; int expectedWidth = image.Width >= 256 ? 0 : image.Width;
int expectedHeight = image.Height >= 256 ? 0 : image.Height; int expectedHeight = image.Height >= 256 ? 0 : image.Height;
Assert.Equal(expectedWidth, meta.EncodingWidth); Assert.Equal(expectedWidth, meta.EncodingWidth.Value);
Assert.Equal(expectedHeight, meta.EncodingHeight); Assert.Equal(expectedHeight, meta.EncodingHeight.Value);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression); Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Bit8, meta.BmpBitsPerPixel); Assert.Equal(BmpBitsPerPixel.Bit8, meta.BmpBitsPerPixel);
} }
@ -226,8 +226,8 @@ public class IcoDecoderTests
int expectedWidth = image.Width >= 256 ? 0 : image.Width; int expectedWidth = image.Width >= 256 ? 0 : image.Width;
int expectedHeight = image.Height >= 256 ? 0 : image.Height; int expectedHeight = image.Height >= 256 ? 0 : image.Height;
Assert.Equal(expectedWidth, meta.EncodingWidth); Assert.Equal(expectedWidth, meta.EncodingWidth.Value);
Assert.Equal(expectedHeight, meta.EncodingHeight); Assert.Equal(expectedHeight, meta.EncodingHeight.Value);
Assert.Equal(IconFrameCompression.Png, meta.Compression); Assert.Equal(IconFrameCompression.Png, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel); Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel);
} }
@ -324,8 +324,8 @@ public class IcoDecoderTests
int expectedWidth = image.Width >= 256 ? 0 : image.Width; int expectedWidth = image.Width >= 256 ? 0 : image.Width;
int expectedHeight = image.Height >= 256 ? 0 : image.Height; int expectedHeight = image.Height >= 256 ? 0 : image.Height;
Assert.Equal(expectedWidth, meta.EncodingWidth); Assert.Equal(expectedWidth, meta.EncodingWidth.Value);
Assert.Equal(expectedHeight, meta.EncodingHeight); Assert.Equal(expectedHeight, meta.EncodingHeight.Value);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression); Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel); Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel);
} }

59
tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Cur; using SixLabors.ImageSharp.Formats.Cur;
using SixLabors.ImageSharp.Formats.Ico; using SixLabors.ImageSharp.Formats.Ico;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -62,4 +63,62 @@ public class IcoEncoderTests
Assert.Equal(curFrame.ColorTable, icoFrame.ColorTable); Assert.Equal(curFrame.ColorTable, icoFrame.ColorTable);
} }
} }
[Fact]
public void Encode_WithTransparentColorBehaviorClear_Works()
{
// arrange
using Image<Rgba32> image = new(50, 50);
IcoEncoder encoder = new()
{
TransparentColorMode = TransparentColorMode.Clear,
};
Rgba32 rgba32 = Color.Blue.ToPixel<Rgba32>();
image.ProcessPixelRows(accessor =>
{
for (int y = 0; y < image.Height; y++)
{
Span<Rgba32> rowSpan = accessor.GetRowSpan(y);
// Half of the test image should be transparent.
if (y > 25)
{
rgba32.A = 0;
}
for (int x = 0; x < image.Width; x++)
{
rowSpan[x] = Rgba32.FromRgba32(rgba32);
}
}
});
// act
using MemoryStream memStream = new();
image.Save(memStream, encoder);
// assert
memStream.Position = 0;
using Image<Rgba32> actual = Image.Load<Rgba32>(memStream);
Rgba32 expectedColor = Color.Blue.ToPixel<Rgba32>();
actual.ProcessPixelRows(accessor =>
{
Rgba32 transparent = Color.Transparent.ToPixel<Rgba32>();
for (int y = 0; y < accessor.Height; y++)
{
Span<Rgba32> rowSpan = accessor.GetRowSpan(y);
if (y > 25)
{
expectedColor = transparent;
}
for (int x = 0; x < accessor.Width; x++)
{
Assert.Equal(expectedColor, rowSpan[x]);
}
}
});
}
} }

4
tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs

@ -340,10 +340,10 @@ public partial class PngEncoderTests
[InlineData(PngColorType.Palette)] [InlineData(PngColorType.Palette)]
[InlineData(PngColorType.RgbWithAlpha)] [InlineData(PngColorType.RgbWithAlpha)]
[InlineData(PngColorType.GrayscaleWithAlpha)] [InlineData(PngColorType.GrayscaleWithAlpha)]
public void Encode_WithPngTransparentColorBehaviorClear_Works(PngColorType colorType) public void Encode_WithTransparentColorBehaviorClear_Works(PngColorType colorType)
{ {
// arrange // arrange
Image<Rgba32> image = new(50, 50); using Image<Rgba32> image = new(50, 50);
PngEncoder encoder = new() PngEncoder encoder = new()
{ {
TransparentColorMode = TransparentColorMode.Clear, TransparentColorMode = TransparentColorMode.Clear,

59
tests/ImageSharp.Tests/Formats/Qoi/QoiEncoderTests.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Qoi; using SixLabors.ImageSharp.Formats.Qoi;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@ -41,4 +42,62 @@ public class QoiEncoderTests
Assert.Equal(qoiMetadata.Channels, channels); Assert.Equal(qoiMetadata.Channels, channels);
Assert.Equal(qoiMetadata.ColorSpace, colorSpace); Assert.Equal(qoiMetadata.ColorSpace, colorSpace);
} }
[Fact]
public void Encode_WithTransparentColorBehaviorClear_Works()
{
// arrange
using Image<Rgba32> image = new(50, 50);
QoiEncoder encoder = new()
{
TransparentColorMode = TransparentColorMode.Clear,
};
Rgba32 rgba32 = Color.Blue.ToPixel<Rgba32>();
image.ProcessPixelRows(accessor =>
{
for (int y = 0; y < image.Height; y++)
{
Span<Rgba32> rowSpan = accessor.GetRowSpan(y);
// Half of the test image should be transparent.
if (y > 25)
{
rgba32.A = 0;
}
for (int x = 0; x < image.Width; x++)
{
rowSpan[x] = Rgba32.FromRgba32(rgba32);
}
}
});
// act
using MemoryStream memStream = new();
image.Save(memStream, encoder);
// assert
memStream.Position = 0;
using Image<Rgba32> actual = Image.Load<Rgba32>(memStream);
Rgba32 expectedColor = Color.Blue.ToPixel<Rgba32>();
actual.ProcessPixelRows(accessor =>
{
Rgba32 transparent = Color.Transparent.ToPixel<Rgba32>();
for (int y = 0; y < accessor.Height; y++)
{
Span<Rgba32> rowSpan = accessor.GetRowSpan(y);
if (y > 25)
{
expectedColor = transparent;
}
for (int x = 0; x < accessor.Width; x++)
{
Assert.Equal(expectedColor, rowSpan[x]);
}
}
});
}
} }

152
tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@ -10,6 +11,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tga;
namespace SixLabors.ImageSharp.Tests.Formats.Tga; namespace SixLabors.ImageSharp.Tests.Formats.Tga;
[Trait("Format", "Tga")] [Trait("Format", "Tga")]
[ValidateDisposedMemoryAllocations]
public class TgaEncoderTests public class TgaEncoderTests
{ {
public static readonly TheoryData<TgaBitsPerPixel> BitsPerPixel = public static readonly TheoryData<TgaBitsPerPixel> BitsPerPixel =
@ -32,43 +34,35 @@ public class TgaEncoderTests
[MemberData(nameof(TgaBitsPerPixelFiles))] [MemberData(nameof(TgaBitsPerPixelFiles))]
public void TgaEncoder_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel) public void TgaEncoder_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel)
{ {
var options = new TgaEncoder(); TgaEncoder options = new();
var testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateRgba32Image()) using Image<Rgba32> input = testFile.CreateRgba32Image();
{ using MemoryStream memStream = new();
using (var memStream = new MemoryStream())
{ input.Save(memStream, options);
input.Save(memStream, options); memStream.Position = 0;
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream)) using Image<Rgba32> output = Image.Load<Rgba32>(memStream);
{ TgaMetadata meta = output.Metadata.GetTgaMetadata();
TgaMetadata meta = output.Metadata.GetTgaMetadata(); Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel);
Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel);
}
}
}
} }
[Theory] [Theory]
[MemberData(nameof(TgaBitsPerPixelFiles))] [MemberData(nameof(TgaBitsPerPixelFiles))]
public void TgaEncoder_WithCompression_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel) public void TgaEncoder_WithCompression_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel)
{ {
var options = new TgaEncoder() { Compression = TgaCompression.RunLength }; TgaEncoder options = new() { Compression = TgaCompression.RunLength };
var testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateRgba32Image()) using Image<Rgba32> input = testFile.CreateRgba32Image();
{ using MemoryStream memStream = new();
using (var memStream = new MemoryStream())
{ input.Save(memStream, options);
input.Save(memStream, options); memStream.Position = 0;
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream)) using Image<Rgba32> output = Image.Load<Rgba32>(memStream);
{ TgaMetadata meta = output.Metadata.GetTgaMetadata();
TgaMetadata meta = output.Metadata.GetTgaMetadata(); Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel);
Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel);
}
}
}
} }
[Theory] [Theory]
@ -136,17 +130,13 @@ public class TgaEncoderTests
[Fact] [Fact]
public void TgaEncoder_RunLengthDoesNotCrossRowBoundaries() public void TgaEncoder_RunLengthDoesNotCrossRowBoundaries()
{ {
var options = new TgaEncoder() { Compression = TgaCompression.RunLength }; TgaEncoder options = new() { Compression = TgaCompression.RunLength };
using (var input = new Image<Rgba32>(30, 30)) using Image<Rgba32> input = new(30, 30);
{ using MemoryStream memStream = new();
using (var memStream = new MemoryStream()) input.Save(memStream, options);
{ byte[] imageBytes = memStream.ToArray();
input.Save(memStream, options); Assert.Equal(138, imageBytes.Length);
byte[] imageBytes = memStream.ToArray();
Assert.Equal(138, imageBytes.Length);
}
}
} }
[Theory] [Theory]
@ -159,6 +149,65 @@ public class TgaEncoderTests
TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength);
} }
[Fact]
public void Encode_WithTransparentColorBehaviorClear_Works()
{
// arrange
using Image<Rgba32> image = new(50, 50);
TgaEncoder encoder = new()
{
BitsPerPixel = TgaBitsPerPixel.Bit32,
TransparentColorMode = TransparentColorMode.Clear,
};
Rgba32 rgba32 = Color.Blue.ToPixel<Rgba32>();
image.ProcessPixelRows(accessor =>
{
for (int y = 0; y < image.Height; y++)
{
Span<Rgba32> rowSpan = accessor.GetRowSpan(y);
// Half of the test image should be transparent.
if (y > 25)
{
rgba32.A = 0;
}
for (int x = 0; x < image.Width; x++)
{
rowSpan[x] = Rgba32.FromRgba32(rgba32);
}
}
});
// act
using MemoryStream memStream = new();
image.Save(memStream, encoder);
// assert
memStream.Position = 0;
using Image<Rgba32> actual = Image.Load<Rgba32>(memStream);
Rgba32 expectedColor = Color.Blue.ToPixel<Rgba32>();
actual.ProcessPixelRows(accessor =>
{
Rgba32 transparent = Color.Transparent.ToPixel<Rgba32>();
for (int y = 0; y < accessor.Height; y++)
{
Span<Rgba32> rowSpan = accessor.GetRowSpan(y);
if (y > 25)
{
expectedColor = transparent;
}
for (int x = 0; x < accessor.Width; x++)
{
Assert.Equal(expectedColor, rowSpan[x]);
}
}
});
}
private static void TestTgaEncoderCore<TPixel>( private static void TestTgaEncoderCore<TPixel>(
TestImageProvider<TPixel> provider, TestImageProvider<TPixel> provider,
TgaBitsPerPixel bitsPerPixel, TgaBitsPerPixel bitsPerPixel,
@ -167,20 +216,15 @@ public class TgaEncoderTests
float compareTolerance = 0.01f) float compareTolerance = 0.01f)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage()) using Image<TPixel> image = provider.GetImage();
{ TgaEncoder encoder = new() { BitsPerPixel = bitsPerPixel, Compression = compression };
var encoder = new TgaEncoder { BitsPerPixel = bitsPerPixel, Compression = compression };
using (var memStream = new MemoryStream()) using MemoryStream memStream = new();
{ image.DebugSave(provider, encoder);
image.DebugSave(provider, encoder); image.Save(memStream, encoder);
image.Save(memStream, encoder); memStream.Position = 0;
memStream.Position = 0;
using (var encodedImage = (Image<TPixel>)Image.Load(memStream)) using Image<TPixel> encodedImage = (Image<TPixel>)Image.Load(memStream);
{ ImageComparingUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance);
ImageComparingUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance);
}
}
}
} }
} }

5
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs

@ -256,7 +256,7 @@ public class TiffEncoderTests : TiffEncoderBaseTester
TiffEncoder tiffEncoder = new(); TiffEncoder tiffEncoder = new();
using MemoryStream memStream = new(); using MemoryStream memStream = new();
using Image<Rgba32> image = new(1, 1); using Image<Rgba32> image = new(1, 1);
byte[] expectedIfdOffsetBytes = { 12, 0 }; byte[] expectedIfdOffsetBytes = [12, 0];
// act // act
image.Save(memStream, tiffEncoder); image.Save(memStream, tiffEncoder);
@ -613,8 +613,7 @@ public class TiffEncoderTests : TiffEncoderBaseTester
provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200);
using Image<TPixel> image = provider.GetImage(); using Image<TPixel> image = provider.GetImage();
TiffEncoder encoder = new() TiffEncoder encoder = new() { PhotometricInterpretation = photometricInterpretation };
{ PhotometricInterpretation = photometricInterpretation };
image.DebugSave(provider, encoder); image.DebugSave(provider, encoder);
} }
} }

12
tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs

@ -12,8 +12,8 @@ public partial class ImageTests
{ {
public Decode_Cancellation() => this.TopLevelConfiguration.StreamProcessingBufferSize = 128; public Decode_Cancellation() => this.TopLevelConfiguration.StreamProcessingBufferSize = 128;
public static readonly string[] TestFileForEachCodec = new[] public static readonly string[] TestFileForEachCodec =
{ [
TestImages.Jpeg.Baseline.Snake, TestImages.Jpeg.Baseline.Snake,
// TODO: Figure out Unix cancellation failures, and validate cancellation for each decoder. // TODO: Figure out Unix cancellation failures, and validate cancellation for each decoder.
@ -24,7 +24,7 @@ public partial class ImageTests
//TestImages.Tga.Bit32BottomRight, //TestImages.Tga.Bit32BottomRight,
//TestImages.Webp.Lossless.WithExif, //TestImages.Webp.Lossless.WithExif,
//TestImages.Pbm.GrayscaleBinaryWide //TestImages.Pbm.GrayscaleBinaryWide
}; ];
public static object[][] IdentifyData { get; } = TestFileForEachCodec.Select(f => new object[] { f }).ToArray(); public static object[][] IdentifyData { get; } = TestFileForEachCodec.Select(f => new object[] { f }).ToArray();
@ -32,16 +32,16 @@ public partial class ImageTests
[MemberData(nameof(IdentifyData))] [MemberData(nameof(IdentifyData))]
public async Task IdentifyAsync_PreCancelled(string file) public async Task IdentifyAsync_PreCancelled(string file)
{ {
using FileStream fs = File.OpenRead(TestFile.GetInputFileFullPath(file)); await using FileStream fs = File.OpenRead(TestFile.GetInputFileFullPath(file));
CancellationToken preCancelled = new(canceled: true); CancellationToken preCancelled = new(canceled: true);
await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await Image.IdentifyAsync(fs, preCancelled)); await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await Image.IdentifyAsync(fs, preCancelled));
} }
private static TheoryData<bool, string, double> CreateLoadData() private static TheoryData<bool, string, double> CreateLoadData()
{ {
double[] percentages = new[] { 0, 0.3, 0.7 }; double[] percentages = [0, 0.3, 0.7];
TheoryData<bool, string, double> data = new(); TheoryData<bool, string, double> data = [];
foreach (string file in TestFileForEachCodec) foreach (string file in TestFileForEachCodec)
{ {

131
tests/ImageSharp.Tests/Image/ImageTests.EncodeCancellation.cs

@ -0,0 +1,131 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests;
public partial class ImageTests
{
[ValidateDisposedMemoryAllocations]
public class Encode_Cancellation
{
[Fact]
public async Task Encode_PreCancellation_Bmp()
{
using Image<Rgba32> image = new(10, 10);
await Assert.ThrowsAsync<TaskCanceledException>(
async () => await image.SaveAsBmpAsync(Stream.Null, new CancellationToken(canceled: true)));
}
[Fact]
public async Task Encode_PreCancellation_Cur()
{
using Image<Rgba32> image = new(10, 10);
await Assert.ThrowsAsync<TaskCanceledException>(
async () => await image.SaveAsCurAsync(Stream.Null, new CancellationToken(canceled: true)));
}
[Fact]
public async Task Encode_PreCancellation_Gif()
{
using Image<Rgba32> image = new(10, 10);
await Assert.ThrowsAsync<TaskCanceledException>(
async () => await image.SaveAsGifAsync(Stream.Null, new CancellationToken(canceled: true)));
}
[Fact]
public async Task Encode_PreCancellation_Animated_Gif()
{
using Image<Rgba32> image = new(10, 10);
image.Frames.CreateFrame();
await Assert.ThrowsAsync<TaskCanceledException>(
async () => await image.SaveAsGifAsync(Stream.Null, new CancellationToken(canceled: true)));
}
[Fact]
public async Task Encode_PreCancellation_Ico()
{
using Image<Rgba32> image = new(10, 10);
await Assert.ThrowsAsync<TaskCanceledException>(
async () => await image.SaveAsIcoAsync(Stream.Null, new CancellationToken(canceled: true)));
}
[Fact]
public async Task Encode_PreCancellation_Jpeg()
{
using Image<Rgba32> image = new(10, 10);
await Assert.ThrowsAsync<TaskCanceledException>(
async () => await image.SaveAsJpegAsync(Stream.Null, new CancellationToken(canceled: true)));
}
[Fact]
public async Task Encode_PreCancellation_Pbm()
{
using Image<Rgba32> image = new(10, 10);
await Assert.ThrowsAsync<TaskCanceledException>(
async () => await image.SaveAsPbmAsync(Stream.Null, new CancellationToken(canceled: true)));
}
[Fact]
public async Task Encode_PreCancellation_Png()
{
using Image<Rgba32> image = new(10, 10);
await Assert.ThrowsAsync<TaskCanceledException>(
async () => await image.SaveAsPngAsync(Stream.Null, new CancellationToken(canceled: true)));
}
[Fact]
public async Task Encode_PreCancellation_Animated_Png()
{
using Image<Rgba32> image = new(10, 10);
image.Frames.CreateFrame();
await Assert.ThrowsAsync<TaskCanceledException>(
async () => await image.SaveAsPngAsync(Stream.Null, new CancellationToken(canceled: true)));
}
[Fact]
public async Task Encode_PreCancellation_Qoi()
{
using Image<Rgba32> image = new(10, 10);
await Assert.ThrowsAsync<TaskCanceledException>(
async () => await image.SaveAsQoiAsync(Stream.Null, new CancellationToken(canceled: true)));
}
[Fact]
public async Task Encode_PreCancellation_Tga()
{
using Image<Rgba32> image = new(10, 10);
await Assert.ThrowsAsync<TaskCanceledException>(
async () => await image.SaveAsTgaAsync(Stream.Null, new CancellationToken(canceled: true)));
}
[Fact]
public async Task Encode_PreCancellation_Tiff()
{
using Image<Rgba32> image = new(10, 10);
await Assert.ThrowsAsync<TaskCanceledException>(
async () => await image.SaveAsTiffAsync(Stream.Null, new CancellationToken(canceled: true)));
}
[Fact]
public async Task Encode_PreCancellation_Webp()
{
using Image<Rgba32> image = new(10, 10);
await Assert.ThrowsAsync<TaskCanceledException>(
async () => await image.SaveAsWebpAsync(Stream.Null, new CancellationToken(canceled: true)));
}
[Fact]
public async Task Encode_PreCancellation_Animated_Webp()
{
using Image<Rgba32> image = new(10, 10);
image.Frames.CreateFrame();
await Assert.ThrowsAsync<TaskCanceledException>(
async () => await image.SaveAsWebpAsync(Stream.Null, new CancellationToken(canceled: true)));
}
}
}

13
tests/ImageSharp.Tests/TestUtilities/PausedMemoryStream.cs

@ -6,9 +6,10 @@ using System.Buffers;
namespace SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.TestUtilities;
/// <summary> /// <summary>
/// <see cref="PausedMemoryStream"/> is a variant of <see cref="PausedStream"/> that derives from <see cref="MemoryStream"/> instead of encapsulating it. /// <see cref="PausedMemoryStream"/> is a variant of <see cref="PausedStream"/> that derives from
/// It is used to test decoder REacellation without relying on of our standard prefetching of arbitrary streams to <see cref="ImageSharp.IO.ChunkedMemoryStream"/> /// <see cref="MemoryStream"/> instead of encapsulating it.
/// on asynchronous path. /// It is used to test decoder cancellation without relying on of our standard prefetching of arbitrary streams
/// to <see cref="ImageSharp.IO.ChunkedMemoryStream"/> on asynchronous path.
/// </summary> /// </summary>
public class PausedMemoryStream : MemoryStream, IPausedStream public class PausedMemoryStream : MemoryStream, IPausedStream
{ {
@ -108,11 +109,11 @@ public class PausedMemoryStream : MemoryStream, IPausedStream
public override bool CanWrite => base.CanWrite; public override bool CanWrite => base.CanWrite;
public override void Flush() => this.Await(() => base.Flush()); public override void Flush() => this.Await(base.Flush);
public override int Read(byte[] buffer, int offset, int count) => this.Await(() => base.Read(buffer, offset, count)); public override int Read(byte[] buffer, int offset, int count) => this.Await(() => base.Read(buffer, offset, count));
public override long Seek(long offset, SeekOrigin origin) => this.Await(() => base.Seek(offset, origin)); public override long Seek(long offset, SeekOrigin loc) => this.Await(() => base.Seek(offset, loc));
public override void SetLength(long value) => this.Await(() => base.SetLength(value)); public override void SetLength(long value) => this.Await(() => base.SetLength(value));
@ -124,7 +125,7 @@ public class PausedMemoryStream : MemoryStream, IPausedStream
public override void WriteByte(byte value) => this.Await(() => base.WriteByte(value)); public override void WriteByte(byte value) => this.Await(() => base.WriteByte(value));
public override int ReadByte() => this.Await(() => base.ReadByte()); public override int ReadByte() => this.Await(base.ReadByte);
public override void CopyTo(Stream destination, int bufferSize) public override void CopyTo(Stream destination, int bufferSize)
{ {

4
tests/ImageSharp.Tests/TestUtilities/PausedStream.cs

@ -115,7 +115,7 @@ public class PausedStream : Stream, IPausedStream
public override long Position { get => this.innerStream.Position; set => this.innerStream.Position = value; } public override long Position { get => this.innerStream.Position; set => this.innerStream.Position = value; }
public override void Flush() => this.Await(() => this.innerStream.Flush()); public override void Flush() => this.Await(this.innerStream.Flush);
public override int Read(byte[] buffer, int offset, int count) => this.Await(() => this.innerStream.Read(buffer, offset, count)); public override int Read(byte[] buffer, int offset, int count) => this.Await(() => this.innerStream.Read(buffer, offset, count));
@ -131,7 +131,7 @@ public class PausedStream : Stream, IPausedStream
public override void WriteByte(byte value) => this.Await(() => this.innerStream.WriteByte(value)); public override void WriteByte(byte value) => this.Await(() => this.innerStream.WriteByte(value));
public override int ReadByte() => this.Await(() => this.innerStream.ReadByte()); public override int ReadByte() => this.Await(this.innerStream.ReadByte);
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {

Loading…
Cancel
Save