diff --git a/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs b/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs
index 4e9a432b16..01b7fbce08 100644
--- a/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs
+++ b/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs
@@ -48,13 +48,13 @@ private CurFrameMetadata(CurFrameMetadata other)
/// Gets or sets the encoding width.
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater.
///
- public byte EncodingWidth { get; set; }
+ public byte? EncodingWidth { get; set; }
///
/// Gets or sets the encoding height.
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater.
///
- public byte EncodingHeight { get; set; }
+ public byte? EncodingHeight { get; set; }
///
/// Gets or sets the number of bits per pixel.
@@ -80,20 +80,6 @@ public static CurFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectin
};
}
- 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;
BmpBitsPerPixel bbpp = bpp switch
{
@@ -116,8 +102,8 @@ public static CurFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectin
{
BmpBitsPerPixel = bbpp,
Compression = compression,
- EncodingWidth = encodingWidth,
- EncodingHeight = encodingHeight,
+ EncodingWidth = ClampEncodingDimension(metadata.EncodingWidth),
+ EncodingHeight = ClampEncodingDimension(metadata.EncodingHeight),
ColorTable = compression == IconFrameCompression.Bmp ? metadata.ColorTable : null
};
}
@@ -138,8 +124,8 @@ public void AfterFrameApply(ImageFrame source, ImageFrame
@@ -156,7 +142,7 @@ internal void FromIconDirEntry(IconDirEntry entry)
this.HotspotY = entry.BitCount;
}
- internal IconDirEntry ToIconDirEntry()
+ internal IconDirEntry ToIconDirEntry(Size size)
{
byte colorCount = this.Compression == IconFrameCompression.Png || this.BmpBitsPerPixel > BmpBitsPerPixel.Bit8
? (byte)0
@@ -164,8 +150,8 @@ internal IconDirEntry ToIconDirEntry()
return new()
{
- Width = this.EncodingWidth,
- Height = this.EncodingHeight,
+ Width = ClampEncodingDimension(this.EncodingWidth ?? size.Width),
+ Height = ClampEncodingDimension(this.EncodingHeight ?? size.Height),
Planes = this.HotspotX,
BitCount = this.HotspotY,
ColorCount = colorCount
@@ -233,13 +219,22 @@ private PixelTypeInfo GetPixelTypeInfo()
};
}
- private static byte Scale(byte? value, int destination, float ratio)
+ private static byte ScaleEncodingDimension(byte? value, int destination, float ratio)
{
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
+ };
}
diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
index 3c6e269e43..797e825dc4 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
@@ -207,22 +207,29 @@ public void Encode(Image image, Stream stream, CancellationToken
this.WriteApplicationExtensions(stream, image.Frames.Count, this.repeatCount ?? gifMetadata.RepeatCount, xmpProfile);
}
- this.EncodeFirstFrame(stream, frameMetadata, quantized);
-
- // Capture the global palette for reuse on subsequent frames and cleanup the quantized frame.
- TPixel[] globalPalette = image.Frames.Count == 1 ? [] : quantized.Palette.ToArray();
-
- this.EncodeAdditionalFrames(
- stream,
- image,
- globalPalette,
- derivedTransparencyIndex,
- frameMetadata.DisposalMode,
- cancellationToken);
-
- stream.WriteByte(GifConstants.EndIntroducer);
+ // If the token is cancelled during encoding of frames we must ensure the
+ // quantized frame is disposed.
+ try
+ {
+ this.EncodeFirstFrame(stream, frameMetadata, quantized, cancellationToken);
+
+ // Capture the global palette for reuse on subsequent frames and cleanup the quantized frame.
+ TPixel[] globalPalette = image.Frames.Count == 1 ? [] : quantized.Palette.ToArray();
+
+ this.EncodeAdditionalFrames(
+ stream,
+ image,
+ globalPalette,
+ derivedTransparencyIndex,
+ frameMetadata.DisposalMode,
+ cancellationToken);
+ }
+ finally
+ {
+ stream.WriteByte(GifConstants.EndIntroducer);
- quantized?.Dispose();
+ quantized?.Dispose();
+ }
}
private static GifFrameMetadata GetGifFrameMetadata(ImageFrame frame, int transparencyIndex)
@@ -310,9 +317,12 @@ private void EncodeAdditionalFrames(
private void EncodeFirstFrame(
Stream stream,
GifFrameMetadata metadata,
- IndexedImageFrame quantized)
+ IndexedImageFrame quantized,
+ CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
+ cancellationToken.ThrowIfCancellationRequested();
+
this.WriteGraphicalControlExtension(metadata, stream);
Buffer2D indices = ((IPixelSource)quantized).PixelBuffer;
diff --git a/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs b/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs
index a2d1c01391..62aa705cbe 100644
--- a/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs
+++ b/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs
@@ -41,13 +41,13 @@ private IcoFrameMetadata(IcoFrameMetadata other)
/// Gets or sets the encoding width.
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater.
///
- public byte EncodingWidth { get; set; }
+ public byte? EncodingWidth { get; set; }
///
/// Gets or sets the encoding height.
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater.
///
- public byte EncodingHeight { get; set; }
+ public byte? EncodingHeight { get; set; }
///
/// Gets or sets the number of bits per pixel.
@@ -73,20 +73,6 @@ public static IcoFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectin
};
}
- 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;
BmpBitsPerPixel bbpp = bpp switch
{
@@ -109,8 +95,8 @@ public static IcoFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectin
{
BmpBitsPerPixel = bbpp,
Compression = compression,
- EncodingWidth = encodingWidth,
- EncodingHeight = encodingHeight,
+ EncodingWidth = ClampEncodingDimension(metadata.EncodingWidth),
+ EncodingHeight = ClampEncodingDimension(metadata.EncodingHeight),
ColorTable = compression == IconFrameCompression.Bmp ? metadata.ColorTable : null
};
}
@@ -131,8 +117,8 @@ public void AfterFrameApply(ImageFrame source, ImageFrame
@@ -147,7 +133,7 @@ internal void FromIconDirEntry(IconDirEntry entry)
this.EncodingHeight = entry.Height;
}
- internal IconDirEntry ToIconDirEntry()
+ internal IconDirEntry ToIconDirEntry(Size size)
{
byte colorCount = this.Compression == IconFrameCompression.Png || this.BmpBitsPerPixel > BmpBitsPerPixel.Bit8
? (byte)0
@@ -155,8 +141,8 @@ internal IconDirEntry ToIconDirEntry()
return new()
{
- Width = this.EncodingWidth,
- Height = this.EncodingHeight,
+ Width = ClampEncodingDimension(this.EncodingWidth ?? size.Width),
+ Height = ClampEncodingDimension(this.EncodingHeight ?? size.Height),
Planes = 1,
ColorCount = colorCount,
BitCount = this.Compression switch
@@ -228,13 +214,22 @@ private PixelTypeInfo GetPixelTypeInfo()
};
}
- private static byte Scale(byte? value, int destination, float ratio)
+ private static byte ScaleEncodingDimension(byte? value, int destination, float ratio)
{
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
+ };
}
diff --git a/src/ImageSharp/Formats/Icon/IconEncoderCore.cs b/src/ImageSharp/Formats/Icon/IconEncoderCore.cs
index 80c3ec4c31..03e01f912f 100644
--- a/src/ImageSharp/Formats/Icon/IconEncoderCore.cs
+++ b/src/ImageSharp/Formats/Icon/IconEncoderCore.cs
@@ -123,13 +123,13 @@ private void InitHeader(Image image)
image.Frames.Select(i =>
{
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(),
IconFileType.CUR =>
image.Frames.Select(i =>
{
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(),
_ => throw new NotSupportedException(),
};
diff --git a/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs b/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs
index dddc629b3e..8b379e4d76 100644
--- a/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs
+++ b/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs
@@ -17,25 +17,32 @@ internal class BinaryEncoder
///
/// The type of input pixel.
/// The configuration.
- /// The bytestream to write to.
+ /// The byte stream to write to.
/// The input image.
/// The ColorType to use.
- /// Data type of the pixles components.
- ///
+ /// Data type of the pixels components.
+ /// The token to monitor for cancellation requests.
+ ///
/// Thrown if an invalid combination of setting is requested.
///
- public static void WritePixels(Configuration configuration, Stream stream, ImageFrame image, PbmColorType colorType, PbmComponentType componentType)
+ public static void WritePixels(
+ Configuration configuration,
+ Stream stream,
+ ImageFrame image,
+ PbmColorType colorType,
+ PbmComponentType componentType,
+ CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
if (colorType == PbmColorType.Grayscale)
{
if (componentType == PbmComponentType.Byte)
{
- WriteGrayscale(configuration, stream, image);
+ WriteGrayscale(configuration, stream, image, cancellationToken);
}
else if (componentType == PbmComponentType.Short)
{
- WriteWideGrayscale(configuration, stream, image);
+ WriteWideGrayscale(configuration, stream, image, cancellationToken);
}
else
{
@@ -46,31 +53,28 @@ public static void WritePixels(Configuration configuration, Stream strea
{
if (componentType == PbmComponentType.Byte)
{
- WriteRgb(configuration, stream, image);
+ WriteRgb(configuration, stream, image, cancellationToken);
}
else if (componentType == PbmComponentType.Short)
{
- WriteWideRgb(configuration, stream, image);
+ WriteWideRgb(configuration, stream, image, cancellationToken);
}
else
{
throw new ImageFormatException("Component type not supported for Color PBM.");
}
}
- else
+ else if (componentType == PbmComponentType.Bit)
{
- if (componentType == PbmComponentType.Bit)
- {
- WriteBlackAndWhite(configuration, stream, image);
- }
- else
- {
- throw new ImageFormatException("Component type not supported for Black & White PBM.");
- }
+ WriteBlackAndWhite(configuration, stream, image, cancellationToken);
}
}
- private static void WriteGrayscale(Configuration configuration, Stream stream, ImageFrame image)
+ private static void WriteGrayscale(
+ Configuration configuration,
+ Stream stream,
+ ImageFrame image,
+ CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
int width = image.Width;
@@ -82,6 +86,8 @@ private static void WriteGrayscale(Configuration configuration, Stream s
for (int y = 0; y < height; y++)
{
+ cancellationToken.ThrowIfCancellationRequested();
+
Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations.Instance.ToL8Bytes(
@@ -94,7 +100,11 @@ private static void WriteGrayscale(Configuration configuration, Stream s
}
}
- private static void WriteWideGrayscale(Configuration configuration, Stream stream, ImageFrame image)
+ private static void WriteWideGrayscale(
+ Configuration configuration,
+ Stream stream,
+ ImageFrame image,
+ CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
const int bytesPerPixel = 2;
@@ -107,6 +117,8 @@ private static void WriteWideGrayscale(Configuration configuration, Stre
for (int y = 0; y < height; y++)
{
+ cancellationToken.ThrowIfCancellationRequested();
+
Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations.Instance.ToL16Bytes(
@@ -119,7 +131,11 @@ private static void WriteWideGrayscale(Configuration configuration, Stre
}
}
- private static void WriteRgb(Configuration configuration, Stream stream, ImageFrame image)
+ private static void WriteRgb(
+ Configuration configuration,
+ Stream stream,
+ ImageFrame image,
+ CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
const int bytesPerPixel = 3;
@@ -132,6 +148,8 @@ private static void WriteRgb(Configuration configuration, Stream stream,
for (int y = 0; y < height; y++)
{
+ cancellationToken.ThrowIfCancellationRequested();
+
Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations.Instance.ToRgb24Bytes(
@@ -144,7 +162,11 @@ private static void WriteRgb(Configuration configuration, Stream stream,
}
}
- private static void WriteWideRgb(Configuration configuration, Stream stream, ImageFrame image)
+ private static void WriteWideRgb(
+ Configuration configuration,
+ Stream stream,
+ ImageFrame image,
+ CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
const int bytesPerPixel = 6;
@@ -157,6 +179,8 @@ private static void WriteWideRgb(Configuration configuration, Stream str
for (int y = 0; y < height; y++)
{
+ cancellationToken.ThrowIfCancellationRequested();
+
Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations.Instance.ToRgb48Bytes(
@@ -169,7 +193,12 @@ private static void WriteWideRgb(Configuration configuration, Stream str
}
}
- private static void WriteBlackAndWhite(Configuration configuration, Stream stream, ImageFrame image)
+ private static void WriteBlackAndWhite(
+ Configuration
+ configuration,
+ Stream stream,
+ ImageFrame image,
+ CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
int width = image.Width;
@@ -181,6 +210,8 @@ private static void WriteBlackAndWhite(Configuration configuration, Stre
for (int y = 0; y < height; y++)
{
+ cancellationToken.ThrowIfCancellationRequested();
+
Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations.Instance.ToL8(
diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs
index 843f1880e6..e0330ca6b4 100644
--- a/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs
+++ b/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs
@@ -68,8 +68,7 @@ public void Encode(Image image, Stream stream, CancellationToken
byte signature = this.DeduceSignature();
this.WriteHeader(stream, signature, image.Size);
-
- this.WritePixels(stream, image.Frames.RootFrame);
+ this.WritePixels(stream, image.Frames.RootFrame, cancellationToken);
stream.Flush();
}
@@ -167,16 +166,29 @@ private void WriteHeader(Stream stream, byte signature, Size pixelSize)
///
/// The containing pixel data.
///
- private void WritePixels(Stream stream, ImageFrame image)
+ /// The token to monitor for cancellation requests.
+ private void WritePixels(Stream stream, ImageFrame image, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
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
{
- BinaryEncoder.WritePixels(this.configuration, stream, image, this.colorType, this.componentType);
+ BinaryEncoder.WritePixels(
+ this.configuration,
+ stream,
+ image,
+ this.colorType,
+ this.componentType,
+ cancellationToken);
}
}
}
diff --git a/src/ImageSharp/Formats/Pbm/PlainEncoder.cs b/src/ImageSharp/Formats/Pbm/PlainEncoder.cs
index 29260f54aa..bab508720d 100644
--- a/src/ImageSharp/Formats/Pbm/PlainEncoder.cs
+++ b/src/ImageSharp/Formats/Pbm/PlainEncoder.cs
@@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm;
///
/// Pixel encoding methods for the PBM plain encoding.
///
-internal class PlainEncoder
+internal static class PlainEncoder
{
private const byte NewLine = 0x0a;
private const byte Space = 0x20;
@@ -31,45 +31,56 @@ internal class PlainEncoder
///
/// The type of input pixel.
/// The configuration.
- /// The bytestream to write to.
+ /// The byte stream to write to.
/// The input image.
/// The ColorType to use.
- /// Data type of the pixles components.
- public static void WritePixels(Configuration configuration, Stream stream, ImageFrame image, PbmColorType colorType, PbmComponentType componentType)
+ /// Data type of the pixels components.
+ /// The token to monitor for cancellation requests.
+ public static void WritePixels(
+ Configuration configuration,
+ Stream stream,
+ ImageFrame image,
+ PbmColorType colorType,
+ PbmComponentType componentType,
+ CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
if (colorType == PbmColorType.Grayscale)
{
if (componentType == PbmComponentType.Byte)
{
- WriteGrayscale(configuration, stream, image);
+ WriteGrayscale(configuration, stream, image, cancellationToken);
}
else
{
- WriteWideGrayscale(configuration, stream, image);
+ WriteWideGrayscale(configuration, stream, image, cancellationToken);
}
}
else if (colorType == PbmColorType.Rgb)
{
if (componentType == PbmComponentType.Byte)
{
- WriteRgb(configuration, stream, image);
+ WriteRgb(configuration, stream, image, cancellationToken);
}
else
{
- WriteWideRgb(configuration, stream, image);
+ WriteWideRgb(configuration, stream, image, cancellationToken);
}
}
else
{
- WriteBlackAndWhite(configuration, stream, image);
+ WriteBlackAndWhite(configuration, stream, image, cancellationToken);
}
// Write EOF indicator, as some encoders expect it.
stream.WriteByte(Space);
}
- private static void WriteGrayscale(Configuration configuration, Stream stream, ImageFrame image)
+ private static void WriteGrayscale(
+ Configuration configuration,
+ Stream stream,
+ ImageFrame image,
+ CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
int width = image.Width;
@@ -83,6 +94,8 @@ private static void WriteGrayscale(Configuration configuration, Stream s
for (int y = 0; y < height; y++)
{
+ cancellationToken.ThrowIfCancellationRequested();
+
Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations.Instance.ToL8(
configuration,
@@ -102,7 +115,11 @@ private static void WriteGrayscale(Configuration configuration, Stream s
}
}
- private static void WriteWideGrayscale(Configuration configuration, Stream stream, ImageFrame image)
+ private static void WriteWideGrayscale(
+ Configuration configuration,
+ Stream stream,
+ ImageFrame image,
+ CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
int width = image.Width;
@@ -116,6 +133,8 @@ private static void WriteWideGrayscale(Configuration configuration, Stre
for (int y = 0; y < height; y++)
{
+ cancellationToken.ThrowIfCancellationRequested();
+
Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations.Instance.ToL16(
configuration,
@@ -135,7 +154,11 @@ private static void WriteWideGrayscale(Configuration configuration, Stre
}
}
- private static void WriteRgb(Configuration configuration, Stream stream, ImageFrame image)
+ private static void WriteRgb(
+ Configuration configuration,
+ Stream stream,
+ ImageFrame image,
+ CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
int width = image.Width;
@@ -149,6 +172,8 @@ private static void WriteRgb(Configuration configuration, Stream stream,
for (int y = 0; y < height; y++)
{
+ cancellationToken.ThrowIfCancellationRequested();
+
Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations.Instance.ToRgb24(
configuration,
@@ -174,7 +199,11 @@ private static void WriteRgb(Configuration configuration, Stream stream,
}
}
- private static void WriteWideRgb(Configuration configuration, Stream stream, ImageFrame image)
+ private static void WriteWideRgb(
+ Configuration configuration,
+ Stream stream,
+ ImageFrame image,
+ CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
int width = image.Width;
@@ -188,6 +217,8 @@ private static void WriteWideRgb(Configuration configuration, Stream str
for (int y = 0; y < height; y++)
{
+ cancellationToken.ThrowIfCancellationRequested();
+
Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations.Instance.ToRgb48(
configuration,
@@ -213,7 +244,11 @@ private static void WriteWideRgb(Configuration configuration, Stream str
}
}
- private static void WriteBlackAndWhite(Configuration configuration, Stream stream, ImageFrame image)
+ private static void WriteBlackAndWhite(
+ Configuration configuration,
+ Stream stream,
+ ImageFrame image,
+ CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
int width = image.Width;
@@ -227,6 +262,8 @@ private static void WriteBlackAndWhite(Configuration configuration, Stre
for (int y = 0; y < height; y++)
{
+ cancellationToken.ThrowIfCancellationRequested();
+
Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations.Instance.ToL8(
configuration,
@@ -236,8 +273,7 @@ private static void WriteBlackAndWhite(Configuration configuration, Stre
int written = 0;
for (int x = 0; x < width; x++)
{
- byte value = (rowSpan[x].PackedValue < 128) ? One : Zero;
- plainSpan[written++] = value;
+ plainSpan[written++] = (rowSpan[x].PackedValue < 128) ? One : Zero;
plainSpan[written++] = Space;
}
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index 05220e8019..ea36d9fe1e 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -226,6 +226,7 @@ public void Encode(Image image, Stream stream, CancellationToken
bool userAnimateRootFrame = this.animateRootFrame == true;
if ((!userAnimateRootFrame && !pngMetadata.AnimateRootFrame) || image.Frames.Count == 1)
{
+ cancellationToken.ThrowIfCancellationRequested();
FrameControl frameControl = new((uint)this.width, (uint)this.height);
this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, false);
currentFrameIndex++;
diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
index 4f6985f9db..da55ef9f9b 100644
--- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
@@ -137,7 +137,7 @@ public void Encode(Image image, Stream stream, CancellationToken
long ifdMarker = WriteHeader(writer, buffer);
- Image? metadataImage = image;
+ Image? imageMetadata = image;
foreach (ImageFrame frame in image.Frames)
{
@@ -154,8 +154,8 @@ public void Encode(Image image, Stream stream, CancellationToken
ImageFrame encodingFrame = clonedFrame ?? frame;
- ifdMarker = this.WriteFrame(writer, encodingFrame, image.Metadata, metadataImage, this.BitsPerPixel.Value, this.CompressionType.Value, ifdMarker);
- metadataImage = null;
+ ifdMarker = this.WriteFrame(writer, encodingFrame, image.Metadata, imageMetadata, this.BitsPerPixel.Value, this.CompressionType.Value, ifdMarker);
+ imageMetadata = null;
}
finally
{
diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
index d22d357fe3..e4ebe14731 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
@@ -388,7 +388,13 @@ public void EncodeStatic(Stream stream, Image image)
/// Flag indicating, if an animation parameter is present.
/// The image to encode from.
/// A indicating whether the frame contains an alpha channel.
- private bool Encode(Stream stream, ImageFrame frame, Rectangle bounds, WebpFrameMetadata frameMetadata, bool hasAnimation, Image image)
+ private bool Encode(
+ Stream stream,
+ ImageFrame frame,
+ Rectangle bounds,
+ WebpFrameMetadata frameMetadata,
+ bool hasAnimation,
+ Image image)
where TPixel : unmanaged, IPixel
{
int width = bounds.Width;
diff --git a/src/ImageSharp/Formats/Webp/WebpEncoder.cs b/src/ImageSharp/Formats/Webp/WebpEncoder.cs
index 622fe0181e..2e459ff58e 100644
--- a/src/ImageSharp/Formats/Webp/WebpEncoder.cs
+++ b/src/ImageSharp/Formats/Webp/WebpEncoder.cs
@@ -8,6 +8,14 @@ namespace SixLabors.ImageSharp.Formats.Webp;
///
public sealed class WebpEncoder : AnimatedImageEncoder
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public WebpEncoder()
+
+ // Match the default behavior of the native reference encoder.
+ => this.TransparentColorMode = TransparentColorMode.Clear;
+
///
/// Gets the webp file format used. Either lossless or lossy.
/// Defaults to lossy.
diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
index b8f27a8326..b3270786d7 100644
--- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
+++ b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
@@ -166,6 +166,9 @@ public void Encode(Image image, Stream stream, CancellationToken
// Encode the first frame.
ImageFrame previousFrame = image.Frames.RootFrame;
WebpFrameMetadata frameMetadata = previousFrame.Metadata.GetWebpMetadata();
+
+ cancellationToken.ThrowIfCancellationRequested();
+
hasAlpha |= encoder.Encode(previousFrame, previousFrame.Bounds, frameMetadata, stream, hasAnimation);
if (hasAnimation)
@@ -304,6 +307,7 @@ public void Encode(Image image, Stream stream, CancellationToken
}
else
{
+ cancellationToken.ThrowIfCancellationRequested();
encoder.EncodeStatic(stream, image);
encoder.EncodeFooter(image, in vp8x, hasAlpha, stream, initialPosition);
}
diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
index d68ec47557..09ef49a61e 100644
--- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
@@ -397,6 +397,66 @@ public void Encode_Issue2467(TestImageProvider provider, BmpBits
reencodedImage.CompareToOriginal(provider);
}
+ [Fact]
+ public void Encode_WithTransparentColorBehaviorClear_Works()
+ {
+ // arrange
+ using Image image = new(50, 50);
+ BmpEncoder encoder = new()
+ {
+ BitsPerPixel = BmpBitsPerPixel.Bit32,
+ SupportTransparency = true,
+ TransparentColorMode = TransparentColorMode.Clear,
+ };
+ Rgba32 rgba32 = Color.Blue.ToPixel();
+ image.ProcessPixelRows(accessor =>
+ {
+ for (int y = 0; y < image.Height; y++)
+ {
+ Span 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 actual = Image.Load(memStream);
+ Rgba32 expectedColor = Color.Blue.ToPixel();
+
+ actual.ProcessPixelRows(accessor =>
+ {
+ Rgba32 transparent = Color.Transparent.ToPixel();
+ for (int y = 0; y < accessor.Height; y++)
+ {
+ Span 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(
TestImageProvider provider,
BmpBitsPerPixel bitsPerPixel,
diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
index c08db84eb6..f12f66186e 100644
--- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
@@ -361,4 +361,62 @@ public void Encode_Animated_VisualTest(TestImageProvider provide
provider.Utility.SaveTestOutputFile(image, "png", new PngEncoder(), "animated");
provider.Utility.SaveTestOutputFile(image, "gif", new GifEncoder(), "animated");
}
+
+ [Fact]
+ public void Encode_WithTransparentColorBehaviorClear_Works()
+ {
+ // arrange
+ using Image image = new(50, 50);
+ GifEncoder encoder = new()
+ {
+ TransparentColorMode = TransparentColorMode.Clear,
+ };
+ Rgba32 rgba32 = Color.Blue.ToPixel();
+ image.ProcessPixelRows(accessor =>
+ {
+ for (int y = 0; y < image.Height; y++)
+ {
+ Span 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 actual = Image.Load(memStream);
+ Rgba32 expectedColor = Color.Blue.ToPixel();
+
+ actual.ProcessPixelRows(accessor =>
+ {
+ Rgba32 transparent = Color.Transparent.ToPixel();
+ for (int y = 0; y < accessor.Height; y++)
+ {
+ Span rowSpan = accessor.GetRowSpan(y);
+
+ if (y > 25)
+ {
+ expectedColor = transparent;
+ }
+
+ for (int x = 0; x < accessor.Width; x++)
+ {
+ Assert.Equal(expectedColor, rowSpan[x]);
+ }
+ }
+ });
+ }
}
diff --git a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs
index f7ee7614af..bac52fc728 100644
--- a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs
@@ -20,8 +20,8 @@ public void CurDecoder_Decode(TestImageProvider provider)
using Image image = provider.GetImage(CurDecoder.Instance);
CurFrameMetadata meta = image.Frames[0].Metadata.GetCurMetadata();
- Assert.Equal(image.Width, meta.EncodingWidth);
- Assert.Equal(image.Height, meta.EncodingHeight);
+ Assert.Equal(image.Width, meta.EncodingWidth.Value);
+ Assert.Equal(image.Height, meta.EncodingHeight.Value);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel);
}
@@ -33,8 +33,8 @@ public void CurDecoder_Decode2(TestImageProvider provider)
{
using Image image = provider.GetImage(CurDecoder.Instance);
CurFrameMetadata meta = image.Frames[0].Metadata.GetCurMetadata();
- Assert.Equal(image.Width, meta.EncodingWidth);
- Assert.Equal(image.Height, meta.EncodingHeight);
+ Assert.Equal(image.Width, meta.EncodingWidth.Value);
+ Assert.Equal(image.Height, meta.EncodingHeight.Value);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel);
}
diff --git a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs
index 59c40c9245..bf94e1d489 100644
--- a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Cur;
using SixLabors.ImageSharp.Formats.Ico;
using SixLabors.ImageSharp.PixelFormats;
@@ -63,4 +64,70 @@ public void CanConvertFromIco(TestImageProvider provider)
Assert.Equal(icoFrame.EncodingHeight, curFrame.EncodingHeight);
}
}
+
+ [Fact]
+ public void Encode_WithTransparentColorBehaviorClear_Works()
+ {
+ // arrange
+ using Image image = new(50, 50);
+ CurEncoder encoder = new()
+ {
+ TransparentColorMode = TransparentColorMode.Clear,
+
+ };
+ Rgba32 rgba32 = Color.Blue.ToPixel();
+ image.ProcessPixelRows(accessor =>
+ {
+ for (int y = 0; y < image.Height; y++)
+ {
+ Span 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 actual = Image.Load(memStream);
+ Rgba32 expectedColor = Color.Blue.ToPixel();
+
+ actual.ProcessPixelRows(accessor =>
+ {
+ Rgba32 transparent = Color.Transparent.ToPixel();
+ for (int y = 0; y < accessor.Height; y++)
+ {
+ Span rowSpan = accessor.GetRowSpan(y);
+ Span 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]);
+ }
+ }
+ });
+ }
}
diff --git a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs
index bc46df0955..e076ccab60 100644
--- a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs
@@ -53,8 +53,8 @@ public void Bpp1Test(TestImageProvider provider)
int expectedWidth = image.Width >= 256 ? 0 : image.Width;
int expectedHeight = image.Height >= 256 ? 0 : image.Height;
- Assert.Equal(expectedWidth, meta.EncodingWidth);
- Assert.Equal(expectedHeight, meta.EncodingHeight);
+ Assert.Equal(expectedWidth, meta.EncodingWidth.Value);
+ Assert.Equal(expectedHeight, meta.EncodingHeight.Value);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Bit1, meta.BmpBitsPerPixel);
}
@@ -89,8 +89,8 @@ public void Bpp24Test(TestImageProvider provider)
int expectedWidth = image.Width >= 256 ? 0 : image.Width;
int expectedHeight = image.Height >= 256 ? 0 : image.Height;
- Assert.Equal(expectedWidth, meta.EncodingWidth);
- Assert.Equal(expectedHeight, meta.EncodingHeight);
+ Assert.Equal(expectedWidth, meta.EncodingWidth.Value);
+ Assert.Equal(expectedHeight, meta.EncodingHeight.Value);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Bit24, meta.BmpBitsPerPixel);
}
@@ -125,8 +125,8 @@ public void Bpp32Test(TestImageProvider provider)
int expectedWidth = image.Width >= 256 ? 0 : image.Width;
int expectedHeight = image.Height >= 256 ? 0 : image.Height;
- Assert.Equal(expectedWidth, meta.EncodingWidth);
- Assert.Equal(expectedHeight, meta.EncodingHeight);
+ Assert.Equal(expectedWidth, meta.EncodingWidth.Value);
+ Assert.Equal(expectedHeight, meta.EncodingHeight.Value);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel);
}
@@ -160,8 +160,8 @@ public void Bpp4Test(TestImageProvider provider)
int expectedWidth = image.Width >= 256 ? 0 : image.Width;
int expectedHeight = image.Height >= 256 ? 0 : image.Height;
- Assert.Equal(expectedWidth, meta.EncodingWidth);
- Assert.Equal(expectedHeight, meta.EncodingHeight);
+ Assert.Equal(expectedWidth, meta.EncodingWidth.Value);
+ Assert.Equal(expectedHeight, meta.EncodingHeight.Value);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Bit4, meta.BmpBitsPerPixel);
}
@@ -196,8 +196,8 @@ public void Bpp8Test(TestImageProvider provider)
int expectedWidth = image.Width >= 256 ? 0 : image.Width;
int expectedHeight = image.Height >= 256 ? 0 : image.Height;
- Assert.Equal(expectedWidth, meta.EncodingWidth);
- Assert.Equal(expectedHeight, meta.EncodingHeight);
+ Assert.Equal(expectedWidth, meta.EncodingWidth.Value);
+ Assert.Equal(expectedHeight, meta.EncodingHeight.Value);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Bit8, meta.BmpBitsPerPixel);
}
@@ -226,8 +226,8 @@ public void InvalidPngTest(TestImageProvider provider)
int expectedWidth = image.Width >= 256 ? 0 : image.Width;
int expectedHeight = image.Height >= 256 ? 0 : image.Height;
- Assert.Equal(expectedWidth, meta.EncodingWidth);
- Assert.Equal(expectedHeight, meta.EncodingHeight);
+ Assert.Equal(expectedWidth, meta.EncodingWidth.Value);
+ Assert.Equal(expectedHeight, meta.EncodingHeight.Value);
Assert.Equal(IconFrameCompression.Png, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel);
}
@@ -324,8 +324,8 @@ public void IcoFakeTest(TestImageProvider provider)
int expectedWidth = image.Width >= 256 ? 0 : image.Width;
int expectedHeight = image.Height >= 256 ? 0 : image.Height;
- Assert.Equal(expectedWidth, meta.EncodingWidth);
- Assert.Equal(expectedHeight, meta.EncodingHeight);
+ Assert.Equal(expectedWidth, meta.EncodingWidth.Value);
+ Assert.Equal(expectedHeight, meta.EncodingHeight.Value);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel);
}
diff --git a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs
index 751db384d7..4c7438d568 100644
--- a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Cur;
using SixLabors.ImageSharp.Formats.Ico;
using SixLabors.ImageSharp.PixelFormats;
@@ -62,4 +63,62 @@ public void CanConvertFromCur(TestImageProvider provider)
Assert.Equal(curFrame.ColorTable, icoFrame.ColorTable);
}
}
+
+ [Fact]
+ public void Encode_WithTransparentColorBehaviorClear_Works()
+ {
+ // arrange
+ using Image image = new(50, 50);
+ IcoEncoder encoder = new()
+ {
+ TransparentColorMode = TransparentColorMode.Clear,
+ };
+ Rgba32 rgba32 = Color.Blue.ToPixel();
+ image.ProcessPixelRows(accessor =>
+ {
+ for (int y = 0; y < image.Height; y++)
+ {
+ Span 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 actual = Image.Load(memStream);
+ Rgba32 expectedColor = Color.Blue.ToPixel();
+
+ actual.ProcessPixelRows(accessor =>
+ {
+ Rgba32 transparent = Color.Transparent.ToPixel();
+ for (int y = 0; y < accessor.Height; y++)
+ {
+ Span rowSpan = accessor.GetRowSpan(y);
+
+ if (y > 25)
+ {
+ expectedColor = transparent;
+ }
+
+ for (int x = 0; x < accessor.Width; x++)
+ {
+ Assert.Equal(expectedColor, rowSpan[x]);
+ }
+ }
+ });
+ }
}
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
index b6bb243afa..b4995d77b6 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
@@ -340,10 +340,10 @@ public void Encode_PreserveBits(string imagePath, PngBitDepth pngBitDepth)
[InlineData(PngColorType.Palette)]
[InlineData(PngColorType.RgbWithAlpha)]
[InlineData(PngColorType.GrayscaleWithAlpha)]
- public void Encode_WithPngTransparentColorBehaviorClear_Works(PngColorType colorType)
+ public void Encode_WithTransparentColorBehaviorClear_Works(PngColorType colorType)
{
// arrange
- Image image = new(50, 50);
+ using Image image = new(50, 50);
PngEncoder encoder = new()
{
TransparentColorMode = TransparentColorMode.Clear,
diff --git a/tests/ImageSharp.Tests/Formats/Qoi/QoiEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Qoi/QoiEncoderTests.cs
index 32ade4a1e9..9da9ad3275 100644
--- a/tests/ImageSharp.Tests/Formats/Qoi/QoiEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Qoi/QoiEncoderTests.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Qoi;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@@ -41,4 +42,62 @@ public static void Encode(TestImageProvider provider, QoiChannel
Assert.Equal(qoiMetadata.Channels, channels);
Assert.Equal(qoiMetadata.ColorSpace, colorSpace);
}
+
+ [Fact]
+ public void Encode_WithTransparentColorBehaviorClear_Works()
+ {
+ // arrange
+ using Image image = new(50, 50);
+ QoiEncoder encoder = new()
+ {
+ TransparentColorMode = TransparentColorMode.Clear,
+ };
+ Rgba32 rgba32 = Color.Blue.ToPixel();
+ image.ProcessPixelRows(accessor =>
+ {
+ for (int y = 0; y < image.Height; y++)
+ {
+ Span 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 actual = Image.Load(memStream);
+ Rgba32 expectedColor = Color.Blue.ToPixel();
+
+ actual.ProcessPixelRows(accessor =>
+ {
+ Rgba32 transparent = Color.Transparent.ToPixel();
+ for (int y = 0; y < accessor.Height; y++)
+ {
+ Span rowSpan = accessor.GetRowSpan(y);
+
+ if (y > 25)
+ {
+ expectedColor = transparent;
+ }
+
+ for (int x = 0; x < accessor.Width; x++)
+ {
+ Assert.Equal(expectedColor, rowSpan[x]);
+ }
+ }
+ });
+ }
}
diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs
index 615e0fc921..adf0b4353d 100644
--- a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@@ -10,6 +11,7 @@
namespace SixLabors.ImageSharp.Tests.Formats.Tga;
[Trait("Format", "Tga")]
+[ValidateDisposedMemoryAllocations]
public class TgaEncoderTests
{
public static readonly TheoryData BitsPerPixel =
@@ -32,43 +34,35 @@ public class TgaEncoderTests
[MemberData(nameof(TgaBitsPerPixelFiles))]
public void TgaEncoder_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel)
{
- var options = new TgaEncoder();
+ TgaEncoder options = new();
- var testFile = TestFile.Create(imagePath);
- using (Image input = testFile.CreateRgba32Image())
- {
- using (var memStream = new MemoryStream())
- {
- input.Save(memStream, options);
- memStream.Position = 0;
- using (var output = Image.Load(memStream))
- {
- TgaMetadata meta = output.Metadata.GetTgaMetadata();
- Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel);
- }
- }
- }
+ TestFile testFile = TestFile.Create(imagePath);
+ using Image input = testFile.CreateRgba32Image();
+ using MemoryStream memStream = new();
+
+ input.Save(memStream, options);
+ memStream.Position = 0;
+
+ using Image output = Image.Load(memStream);
+ TgaMetadata meta = output.Metadata.GetTgaMetadata();
+ Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel);
}
[Theory]
[MemberData(nameof(TgaBitsPerPixelFiles))]
public void TgaEncoder_WithCompression_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel)
{
- var options = new TgaEncoder() { Compression = TgaCompression.RunLength };
- var testFile = TestFile.Create(imagePath);
- using (Image input = testFile.CreateRgba32Image())
- {
- using (var memStream = new MemoryStream())
- {
- input.Save(memStream, options);
- memStream.Position = 0;
- using (var output = Image.Load(memStream))
- {
- TgaMetadata meta = output.Metadata.GetTgaMetadata();
- Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel);
- }
- }
- }
+ TgaEncoder options = new() { Compression = TgaCompression.RunLength };
+ TestFile testFile = TestFile.Create(imagePath);
+ using Image input = testFile.CreateRgba32Image();
+ using MemoryStream memStream = new();
+
+ input.Save(memStream, options);
+ memStream.Position = 0;
+
+ using Image output = Image.Load(memStream);
+ TgaMetadata meta = output.Metadata.GetTgaMetadata();
+ Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel);
}
[Theory]
@@ -136,17 +130,13 @@ public void TgaEncoder_DoesNotAlwaysUseRunLengthPackets(TestImageProvide
[Fact]
public void TgaEncoder_RunLengthDoesNotCrossRowBoundaries()
{
- var options = new TgaEncoder() { Compression = TgaCompression.RunLength };
+ TgaEncoder options = new() { Compression = TgaCompression.RunLength };
- using (var input = new Image(30, 30))
- {
- using (var memStream = new MemoryStream())
- {
- input.Save(memStream, options);
- byte[] imageBytes = memStream.ToArray();
- Assert.Equal(138, imageBytes.Length);
- }
- }
+ using Image input = new(30, 30);
+ using MemoryStream memStream = new();
+ input.Save(memStream, options);
+ byte[] imageBytes = memStream.ToArray();
+ Assert.Equal(138, imageBytes.Length);
}
[Theory]
@@ -159,6 +149,65 @@ public void TgaEncoder_WorksWithDiscontiguousBuffers(TestImageProvider image = new(50, 50);
+ TgaEncoder encoder = new()
+ {
+ BitsPerPixel = TgaBitsPerPixel.Bit32,
+ TransparentColorMode = TransparentColorMode.Clear,
+ };
+ Rgba32 rgba32 = Color.Blue.ToPixel();
+ image.ProcessPixelRows(accessor =>
+ {
+ for (int y = 0; y < image.Height; y++)
+ {
+ Span 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 actual = Image.Load(memStream);
+ Rgba32 expectedColor = Color.Blue.ToPixel();
+
+ actual.ProcessPixelRows(accessor =>
+ {
+ Rgba32 transparent = Color.Transparent.ToPixel();
+ for (int y = 0; y < accessor.Height; y++)
+ {
+ Span 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(
TestImageProvider provider,
TgaBitsPerPixel bitsPerPixel,
@@ -167,20 +216,15 @@ private static void TestTgaEncoderCore(
float compareTolerance = 0.01f)
where TPixel : unmanaged, IPixel
{
- using (Image image = provider.GetImage())
- {
- var encoder = new TgaEncoder { BitsPerPixel = bitsPerPixel, Compression = compression };
+ using Image image = provider.GetImage();
+ TgaEncoder encoder = new() { BitsPerPixel = bitsPerPixel, Compression = compression };
- using (var memStream = new MemoryStream())
- {
- image.DebugSave(provider, encoder);
- image.Save(memStream, encoder);
- memStream.Position = 0;
- using (var encodedImage = (Image)Image.Load(memStream))
- {
- ImageComparingUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance);
- }
- }
- }
+ using MemoryStream memStream = new();
+ image.DebugSave(provider, encoder);
+ image.Save(memStream, encoder);
+ memStream.Position = 0;
+
+ using Image encodedImage = (Image)Image.Load(memStream);
+ ImageComparingUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance);
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
index 0d59625ca7..674ca5c5bb 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
@@ -256,7 +256,7 @@ public void TiffEncoder_WritesIfdOffsetAtWordBoundary()
TiffEncoder tiffEncoder = new();
using MemoryStream memStream = new();
using Image image = new(1, 1);
- byte[] expectedIfdOffsetBytes = { 12, 0 };
+ byte[] expectedIfdOffsetBytes = [12, 0];
// act
image.Save(memStream, tiffEncoder);
@@ -613,8 +613,7 @@ public void TiffEncode_WorksWithDiscontiguousBuffers(TestImageProvider image = provider.GetImage();
- TiffEncoder encoder = new()
- { PhotometricInterpretation = photometricInterpretation };
+ TiffEncoder encoder = new() { PhotometricInterpretation = photometricInterpretation };
image.DebugSave(provider, encoder);
}
}
diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs b/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs
index d8d9f4fe2a..0656f9e0bc 100644
--- a/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs
+++ b/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs
@@ -12,8 +12,8 @@ public class Decode_Cancellation : ImageLoadTestBase
{
public Decode_Cancellation() => this.TopLevelConfiguration.StreamProcessingBufferSize = 128;
- public static readonly string[] TestFileForEachCodec = new[]
- {
+ public static readonly string[] TestFileForEachCodec =
+ [
TestImages.Jpeg.Baseline.Snake,
// TODO: Figure out Unix cancellation failures, and validate cancellation for each decoder.
@@ -24,7 +24,7 @@ public class Decode_Cancellation : ImageLoadTestBase
//TestImages.Tga.Bit32BottomRight,
//TestImages.Webp.Lossless.WithExif,
//TestImages.Pbm.GrayscaleBinaryWide
- };
+ ];
public static object[][] IdentifyData { get; } = TestFileForEachCodec.Select(f => new object[] { f }).ToArray();
@@ -32,16 +32,16 @@ public class Decode_Cancellation : ImageLoadTestBase
[MemberData(nameof(IdentifyData))]
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);
await Assert.ThrowsAnyAsync(async () => await Image.IdentifyAsync(fs, preCancelled));
}
private static TheoryData CreateLoadData()
{
- double[] percentages = new[] { 0, 0.3, 0.7 };
+ double[] percentages = [0, 0.3, 0.7];
- TheoryData data = new();
+ TheoryData data = [];
foreach (string file in TestFileForEachCodec)
{
diff --git a/tests/ImageSharp.Tests/Image/ImageTests.EncodeCancellation.cs b/tests/ImageSharp.Tests/Image/ImageTests.EncodeCancellation.cs
new file mode 100644
index 0000000000..f3b1a01c94
--- /dev/null
+++ b/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 image = new(10, 10);
+ await Assert.ThrowsAsync(
+ async () => await image.SaveAsBmpAsync(Stream.Null, new CancellationToken(canceled: true)));
+ }
+
+ [Fact]
+ public async Task Encode_PreCancellation_Cur()
+ {
+ using Image image = new(10, 10);
+ await Assert.ThrowsAsync(
+ async () => await image.SaveAsCurAsync(Stream.Null, new CancellationToken(canceled: true)));
+ }
+
+ [Fact]
+ public async Task Encode_PreCancellation_Gif()
+ {
+ using Image image = new(10, 10);
+ await Assert.ThrowsAsync(
+ async () => await image.SaveAsGifAsync(Stream.Null, new CancellationToken(canceled: true)));
+ }
+
+ [Fact]
+ public async Task Encode_PreCancellation_Animated_Gif()
+ {
+ using Image image = new(10, 10);
+ image.Frames.CreateFrame();
+
+ await Assert.ThrowsAsync(
+ async () => await image.SaveAsGifAsync(Stream.Null, new CancellationToken(canceled: true)));
+ }
+
+ [Fact]
+ public async Task Encode_PreCancellation_Ico()
+ {
+ using Image image = new(10, 10);
+ await Assert.ThrowsAsync(
+ async () => await image.SaveAsIcoAsync(Stream.Null, new CancellationToken(canceled: true)));
+ }
+
+ [Fact]
+ public async Task Encode_PreCancellation_Jpeg()
+ {
+ using Image image = new(10, 10);
+ await Assert.ThrowsAsync(
+ async () => await image.SaveAsJpegAsync(Stream.Null, new CancellationToken(canceled: true)));
+ }
+
+ [Fact]
+ public async Task Encode_PreCancellation_Pbm()
+ {
+ using Image image = new(10, 10);
+ await Assert.ThrowsAsync(
+ async () => await image.SaveAsPbmAsync(Stream.Null, new CancellationToken(canceled: true)));
+ }
+
+ [Fact]
+ public async Task Encode_PreCancellation_Png()
+ {
+ using Image image = new(10, 10);
+ await Assert.ThrowsAsync(
+ async () => await image.SaveAsPngAsync(Stream.Null, new CancellationToken(canceled: true)));
+ }
+
+ [Fact]
+ public async Task Encode_PreCancellation_Animated_Png()
+ {
+ using Image