diff --git a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
deleted file mode 100644
index 27d86f9218..0000000000
--- a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using SixLabors.ImageSharp.Processing.Processors.Quantization;
-
-namespace SixLabors.ImageSharp.Formats.Png;
-
-///
-/// The options available for manipulating the encoder pipeline.
-///
-internal interface IPngEncoderOptions
-{
- ///
- /// Gets the number of bits per sample or per palette index (not per pixel).
- /// Not all values are allowed for all values.
- ///
- PngBitDepth? BitDepth { get; }
-
- ///
- /// Gets the color type.
- ///
- PngColorType? ColorType { get; }
-
- ///
- /// Gets the filter method.
- ///
- PngFilterMethod? FilterMethod { get; }
-
- ///
- /// Gets the compression level 1-9.
- /// Defaults to .
- ///
- PngCompressionLevel CompressionLevel { get; }
-
- ///
- /// Gets the threshold of characters in text metadata, when compression should be used.
- ///
- int TextCompressionThreshold { get; }
-
- ///
- /// Gets the gamma value, that will be written the image.
- ///
- /// The gamma value of the image.
- float? Gamma { get; }
-
- ///
- /// Gets the quantizer for reducing the color count.
- ///
- IQuantizer Quantizer { get; }
-
- ///
- /// Gets the transparency threshold.
- ///
- byte Threshold { get; }
-
- ///
- /// Gets a value indicating whether this instance should write an Adam7 interlaced image.
- ///
- PngInterlaceMode? InterlaceMethod { get; }
-
- ///
- /// Gets a value indicating whether the metadata should be ignored when the image is being encoded.
- /// When set to true, all ancillary chunks will be skipped.
- ///
- bool IgnoreMetadata { get; }
-
- ///
- /// Gets the chunk filter method. This allows to filter ancillary chunks.
- ///
- PngChunkFilter? ChunkFilter { get; }
-
- ///
- /// Gets a value indicating whether fully transparent pixels that may contain R, G, B values which are not 0,
- /// should be converted to transparent black, which can yield in better compression in some cases.
- ///
- PngTransparentColorMode TransparentColorMode { get; }
-}
diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs
index d769bcbc8d..2daa136c3d 100644
--- a/src/ImageSharp/Formats/Png/PngEncoder.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoder.cs
@@ -2,84 +2,91 @@
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Png;
///
/// Image encoder for writing image data to a stream in png format.
///
-public sealed class PngEncoder : IImageEncoder, IPngEncoderOptions
+public class PngEncoder : QuantizingImageEncoder
{
- ///
- public PngBitDepth? BitDepth { get; set; }
-
- ///
- public PngColorType? ColorType { get; set; }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PngEncoder() =>
- ///
- public PngFilterMethod? FilterMethod { get; set; }
+ // We set the quantizer to null here to allow the underlying encoder to create a
+ // quantizer with options appropriate to the encoding bit depth.
+ this.Quantizer = null;
- ///
- public PngCompressionLevel CompressionLevel { get; set; } = PngCompressionLevel.DefaultCompression;
+ ///
+ /// Gets the number of bits per sample or per palette index (not per pixel).
+ /// Not all values are allowed for all values.
+ ///
+ public PngBitDepth? BitDepth { get; init; }
- ///
- public int TextCompressionThreshold { get; set; } = 1024;
+ ///
+ /// Gets the color type.
+ ///
+ public PngColorType? ColorType { get; init; }
- ///
- public float? Gamma { get; set; }
+ ///
+ /// Gets the filter method.
+ ///
+ public PngFilterMethod? FilterMethod { get; init; }
- ///
- public IQuantizer Quantizer { get; set; }
+ ///
+ /// Gets the compression level 1-9.
+ /// Defaults to .
+ ///
+ public PngCompressionLevel CompressionLevel { get; init; } = PngCompressionLevel.DefaultCompression;
- ///
- public byte Threshold { get; set; } = byte.MaxValue;
+ ///
+ /// Gets the threshold of characters in text metadata, when compression should be used.
+ ///
+ public int TextCompressionThreshold { get; init; } = 1024;
- ///
- public PngInterlaceMode? InterlaceMethod { get; set; }
+ ///
+ /// Gets the gamma value, that will be written the image.
+ ///
+ /// The gamma value of the image.
+ public float? Gamma { get; init; }
- ///
- public PngChunkFilter? ChunkFilter { get; set; }
+ ///
+ /// Gets the transparency threshold.
+ ///
+ public byte Threshold { get; init; } = byte.MaxValue;
- ///
- public bool IgnoreMetadata { get; set; }
+ ///
+ /// Gets a value indicating whether this instance should write an Adam7 interlaced image.
+ ///
+ public PngInterlaceMode? InterlaceMethod { get; init; }
- ///
- public PngTransparentColorMode TransparentColorMode { get; set; }
+ ///
+ /// Gets the chunk filter method. This allows to filter ancillary chunks.
+ ///
+ public PngChunkFilter? ChunkFilter { get; init; }
///
- /// Encodes the image to the specified stream from the .
+ /// Gets a value indicating whether fully transparent pixels that may contain R, G, B values which are not 0,
+ /// should be converted to transparent black, which can yield in better compression in some cases.
///
- /// The pixel format.
- /// The to encode from.
- /// The to encode the image data to.
- public void Encode(Image image, Stream stream)
- where TPixel : unmanaged, IPixel
+ public PngTransparentColorMode TransparentColorMode { get; init; }
+
+ ///
+ public override void Encode(Image image, Stream stream)
{
- using (var encoder = new PngEncoderCore(image.GetMemoryAllocator(), image.GetConfiguration(), new PngEncoderOptions(this)))
- {
- encoder.Encode(image, stream);
- }
+ using PngEncoderCore encoder = new(image.GetMemoryAllocator(), image.GetConfiguration(), this);
+ encoder.Encode(image, stream);
}
- ///
- /// Encodes the image to the specified stream from the .
- ///
- /// The pixel format.
- /// The to encode from.
- /// The to encode the image data to.
- /// The token to monitor for cancellation requests.
- /// A representing the asynchronous operation.
- public async Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel
+ ///
+ public override async Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken)
{
// The introduction of a local variable that refers to an object the implements
// IDisposable means you must use async/await, where the compiler generates the
// state machine and a continuation.
- using (var encoder = new PngEncoderCore(image.GetMemoryAllocator(), image.GetConfiguration(), new PngEncoderOptions(this)))
- {
- await encoder.EncodeAsync(image, stream, cancellationToken).ConfigureAwait(false);
- }
+ using PngEncoderCore encoder = new(image.GetMemoryAllocator(), image.GetConfiguration(), this);
+ await encoder.EncodeAsync(image, stream, cancellationToken).ConfigureAwait(false);
}
}
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index c45da6a825..4267580dec 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -5,6 +5,7 @@ using System.Buffers;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Png.Chunks;
@@ -12,6 +13,7 @@ using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Png;
@@ -46,17 +48,42 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
private readonly byte[] chunkDataBuffer = new byte[16];
///
- /// The encoder options
+ /// The encoder with options
///
- private readonly PngEncoderOptions options;
+ private readonly PngEncoder encoder;
///
- /// The bit depth.
+ /// The gamma value
+ ///
+ private float? gamma;
+
+ ///
+ /// The color type.
+ ///
+ private PngColorType colorType;
+
+ ///
+ /// The number of bits per sample or per palette index (not per pixel).
///
private byte bitDepth;
///
- /// Gets or sets a value indicating whether to use 16 bit encoding for supported color types.
+ /// The filter method used to prefilter the encoded pixels before compression.
+ ///
+ private PngFilterMethod filterMethod;
+
+ ///
+ /// Gets the interlace mode.
+ ///
+ private PngInterlaceMode interlaceMode;
+
+ ///
+ /// The chunk filter method. This allows to filter ancillary chunks.
+ ///
+ private PngChunkFilter chunkFilter;
+
+ ///
+ /// A value indicating whether to use 16 bit encoding for supported color types.
///
private bool use16Bit;
@@ -95,12 +122,12 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
///
/// The to use for buffer allocations.
/// The configuration.
- /// The options for influencing the encoder
- public PngEncoderCore(MemoryAllocator memoryAllocator, Configuration configuration, PngEncoderOptions options)
+ /// The encoder with options.
+ public PngEncoderCore(MemoryAllocator memoryAllocator, Configuration configuration, PngEncoder encoder)
{
this.memoryAllocator = memoryAllocator;
this.configuration = configuration;
- this.options = options;
+ this.encoder = encoder;
}
///
@@ -122,16 +149,16 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
ImageMetadata metadata = image.Metadata;
PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance);
- PngEncoderOptionsHelpers.AdjustOptions(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel);
+ this.DeduceOptions(this.encoder, pngMetadata, out this.use16Bit, out this.bytesPerPixel);
Image clonedImage = null;
- bool clearTransparency = this.options.TransparentColorMode == PngTransparentColorMode.Clear;
+ bool clearTransparency = this.encoder.TransparentColorMode == PngTransparentColorMode.Clear;
if (clearTransparency)
{
clonedImage = image.Clone();
ClearTransparentPixels(clonedImage);
}
- IndexedImageFrame quantized = this.CreateQuantizedImage(image, clonedImage);
+ IndexedImageFrame quantized = this.CreateQuantizedImageAndUpdateBitDepth(image, clonedImage);
stream.Write(PngConstants.HeaderBytes);
@@ -171,6 +198,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
where TPixel : unmanaged, IPixel =>
image.ProcessPixelRows(accessor =>
{
+ // TODO: We should be able to speed this up with SIMD and masking.
Rgba32 rgba32 = default;
Rgba32 transparent = Color.Transparent;
for (int y = 0; y < accessor.Height; y++)
@@ -189,27 +217,28 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
});
///
- /// Creates the quantized image and sets calculates and sets the bit depth.
+ /// Creates the quantized image and calculates and sets the bit depth.
///
/// The type of the pixel.
/// The image to quantize.
/// Cloned image with transparent pixels are changed to black.
/// The quantized image.
- private IndexedImageFrame CreateQuantizedImage(Image image, Image clonedImage)
+ private IndexedImageFrame CreateQuantizedImageAndUpdateBitDepth(
+ Image image,
+ Image clonedImage)
where TPixel : unmanaged, IPixel
{
IndexedImageFrame quantized;
- if (this.options.TransparentColorMode == PngTransparentColorMode.Clear)
+ if (this.encoder.TransparentColorMode == PngTransparentColorMode.Clear)
{
- quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, clonedImage);
- this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, quantized);
+ quantized = CreateQuantizedFrame(this.encoder, this.colorType, this.bitDepth, clonedImage);
}
else
{
- quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image);
- this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, quantized);
+ quantized = CreateQuantizedFrame(this.encoder, this.colorType, this.bitDepth, image);
}
+ this.bitDepth = CalculateBitDepth(this.colorType, this.bitDepth, quantized);
return quantized;
}
@@ -223,23 +252,21 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
Span rawScanlineSpan = this.currentScanline.GetSpan();
ref byte rawScanlineSpanRef = ref MemoryMarshal.GetReference(rawScanlineSpan);
- if (this.options.ColorType == PngColorType.Grayscale)
+ if (this.colorType == PngColorType.Grayscale)
{
if (this.use16Bit)
{
// 16 bit grayscale
- using (IMemoryOwner luminanceBuffer = this.memoryAllocator.Allocate(rowSpan.Length))
- {
- Span luminanceSpan = luminanceBuffer.GetSpan();
- ref L16 luminanceRef = ref MemoryMarshal.GetReference(luminanceSpan);
- PixelOperations.Instance.ToL16(this.configuration, rowSpan, luminanceSpan);
+ using IMemoryOwner luminanceBuffer = this.memoryAllocator.Allocate(rowSpan.Length);
+ Span luminanceSpan = luminanceBuffer.GetSpan();
+ ref L16 luminanceRef = ref MemoryMarshal.GetReference(luminanceSpan);
+ PixelOperations.Instance.ToL16(this.configuration, rowSpan, luminanceSpan);
- // Can't map directly to byte array as it's big-endian.
- for (int x = 0, o = 0; x < luminanceSpan.Length; x++, o += 2)
- {
- L16 luminance = Unsafe.Add(ref luminanceRef, x);
- BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), luminance.PackedValue);
- }
+ // Can't map directly to byte array as it's big-endian.
+ for (int x = 0, o = 0; x < luminanceSpan.Length; x++, o += 2)
+ {
+ L16 luminance = Unsafe.Add(ref luminanceRef, x);
+ BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), luminance.PackedValue);
}
}
else if (this.bitDepth == 8)
@@ -382,7 +409,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
private void CollectPixelBytes(ReadOnlySpan rowSpan, IndexedImageFrame quantized, int row)
where TPixel : unmanaged, IPixel
{
- switch (this.options.ColorType)
+ switch (this.colorType)
{
case PngColorType.Palette:
@@ -413,7 +440,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
/// Used for attempting optimized filtering.
private void FilterPixelBytes(ref Span filter, ref Span attempt)
{
- switch (this.options.FilterMethod)
+ switch (this.filterMethod)
{
case PngFilterMethod.None:
NoneFilter.Encode(this.currentScanline.GetSpan(), filter);
@@ -495,7 +522,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
{
// Palette images don't compress well with adaptive filtering.
// Nor do images comprising a single row.
- if (this.options.ColorType == PngColorType.Palette || this.height == 1 || this.bitDepth < 8)
+ if (this.colorType == PngColorType.Palette || this.height == 1 || this.bitDepth < 8)
{
NoneFilter.Encode(this.currentScanline.GetSpan(), filter);
return;
@@ -543,10 +570,10 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
width: this.width,
height: this.height,
bitDepth: this.bitDepth,
- colorType: this.options.ColorType.Value,
+ colorType: this.colorType,
compressionMethod: 0, // None
filterMethod: 0,
- interlaceMethod: this.options.InterlaceMethod.Value);
+ interlaceMethod: this.interlaceMode);
header.WriteTo(this.chunkDataBuffer);
@@ -593,7 +620,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
byte alpha = rgba.A;
Unsafe.Add(ref colorTableRef, i) = rgba.Rgb;
- if (alpha > this.options.Threshold)
+ if (alpha > this.encoder.Threshold)
{
alpha = byte.MaxValue;
}
@@ -619,7 +646,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
/// The image metadata.
private void WritePhysicalChunk(Stream stream, ImageMetadata meta)
{
- if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludePhysicalChunk) == PngChunkFilter.ExcludePhysicalChunk)
+ if ((this.chunkFilter & PngChunkFilter.ExcludePhysicalChunk) == PngChunkFilter.ExcludePhysicalChunk)
{
return;
}
@@ -636,7 +663,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
/// The image metadata.
private void WriteExifChunk(Stream stream, ImageMetadata meta)
{
- if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeExifChunk) == PngChunkFilter.ExcludeExifChunk)
+ if ((this.chunkFilter & PngChunkFilter.ExcludeExifChunk) == PngChunkFilter.ExcludeExifChunk)
{
return;
}
@@ -658,7 +685,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
private void WriteXmpChunk(Stream stream, ImageMetadata meta)
{
const int iTxtHeaderSize = 5;
- if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeTextChunks) == PngChunkFilter.ExcludeTextChunks)
+ if ((this.chunkFilter & PngChunkFilter.ExcludeTextChunks) == PngChunkFilter.ExcludeTextChunks)
{
return;
}
@@ -731,7 +758,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
/// The image metadata.
private void WriteTextChunks(Stream stream, PngMetadata meta)
{
- if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeTextChunks) == PngChunkFilter.ExcludeTextChunks)
+ if ((this.chunkFilter & PngChunkFilter.ExcludeTextChunks) == PngChunkFilter.ExcludeTextChunks)
{
return;
}
@@ -754,7 +781,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
{
// Write iTXt chunk.
byte[] keywordBytes = PngConstants.Encoding.GetBytes(textData.Keyword);
- byte[] textBytes = textData.Value.Length > this.options.TextCompressionThreshold
+ byte[] textBytes = textData.Value.Length > this.encoder.TextCompressionThreshold
? this.GetZlibCompressedBytes(PngConstants.TranslatedEncoding.GetBytes(textData.Value))
: PngConstants.TranslatedEncoding.GetBytes(textData.Value);
@@ -768,7 +795,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
keywordBytes.CopyTo(outputBytes);
int bytesWritten = keywordBytes.Length;
outputBytes[bytesWritten++] = 0;
- if (textData.Value.Length > this.options.TextCompressionThreshold)
+ if (textData.Value.Length > this.encoder.TextCompressionThreshold)
{
// Indicate that the text is compressed.
outputBytes[bytesWritten++] = 1;
@@ -788,7 +815,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
textBytes.CopyTo(outputBytes[bytesWritten..]);
this.WriteChunk(stream, PngChunkType.InternationalText, outputBytes);
}
- else if (textData.Value.Length > this.options.TextCompressionThreshold)
+ else if (textData.Value.Length > this.encoder.TextCompressionThreshold)
{
// Write zTXt chunk.
byte[] compressedData = this.GetZlibCompressedBytes(PngConstants.Encoding.GetBytes(textData.Value));
@@ -827,7 +854,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
private byte[] GetZlibCompressedBytes(byte[] dataBytes)
{
using MemoryStream memoryStream = new();
- using (ZlibDeflateStream deflateStream = new(this.memoryAllocator, memoryStream, this.options.CompressionLevel))
+ using (ZlibDeflateStream deflateStream = new(this.memoryAllocator, memoryStream, this.encoder.CompressionLevel))
{
deflateStream.Write(dataBytes);
}
@@ -842,15 +869,15 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
/// The containing image data.
private void WriteGammaChunk(Stream stream)
{
- if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeGammaChunk) == PngChunkFilter.ExcludeGammaChunk)
+ if ((this.chunkFilter & PngChunkFilter.ExcludeGammaChunk) == PngChunkFilter.ExcludeGammaChunk)
{
return;
}
- if (this.options.Gamma > 0)
+ if (this.gamma > 0)
{
// 4-byte unsigned integer of gamma * 100,000.
- uint gammaValue = (uint)(this.options.Gamma * 100_000F);
+ uint gammaValue = (uint)(this.gamma * 100_000F);
BinaryPrimitives.WriteUInt32BigEndian(this.chunkDataBuffer.AsSpan(0, 4), gammaValue);
@@ -924,9 +951,9 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
using (MemoryStream memoryStream = new())
{
- using (ZlibDeflateStream deflateStream = new(this.memoryAllocator, memoryStream, this.options.CompressionLevel))
+ using (ZlibDeflateStream deflateStream = new(this.memoryAllocator, memoryStream, this.encoder.CompressionLevel))
{
- if (this.options.InterlaceMethod == PngInterlaceMode.Adam7)
+ if (this.interlaceMode == PngInterlaceMode.Adam7)
{
if (quantized != null)
{
@@ -1192,4 +1219,196 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
ref IMemoryOwner current = ref this.currentScanline;
RuntimeUtility.Swap(ref prev, ref current);
}
+
+ ///
+ /// Adjusts the options based upon the given metadata.
+ ///
+ /// The type of pixel format.
+ /// The encoder with options.
+ /// The PNG metadata.
+ /// if set to true [use16 bit].
+ /// The bytes per pixel.
+ private void DeduceOptions(
+ PngEncoder encoder,
+ PngMetadata pngMetadata,
+ out bool use16Bit,
+ out int bytesPerPixel)
+ where TPixel : unmanaged, IPixel
+ {
+ // Always take the encoder options over the metadata values.
+ this.gamma = encoder.Gamma ?? pngMetadata.Gamma;
+
+ // Use options, then check metadata, if nothing set there then we suggest
+ // a sensible default based upon the pixel format.
+ this.colorType = encoder.ColorType ?? pngMetadata.ColorType ?? SuggestColorType();
+ if (!encoder.FilterMethod.HasValue)
+ {
+ // Specification recommends default filter method None for paletted images and Paeth for others.
+ if (this.colorType == PngColorType.Palette)
+ {
+ this.filterMethod = PngFilterMethod.None;
+ }
+ else
+ {
+ this.filterMethod = PngFilterMethod.Paeth;
+ }
+ }
+
+ // Ensure bit depth and color type are a supported combination.
+ // Bit8 is the only bit depth supported by all color types.
+ byte bits = (byte)(encoder.BitDepth ?? pngMetadata.BitDepth ?? SuggestBitDepth());
+ byte[] validBitDepths = PngConstants.ColorTypes[this.colorType];
+ if (Array.IndexOf(validBitDepths, bits) == -1)
+ {
+ bits = (byte)PngBitDepth.Bit8;
+ }
+
+ this.bitDepth = bits;
+ use16Bit = bits == (byte)PngBitDepth.Bit16;
+ bytesPerPixel = CalculateBytesPerPixel(this.colorType, use16Bit);
+
+ this.interlaceMode = (encoder.InterlaceMethod ?? pngMetadata.InterlaceMethod).Value;
+ this.chunkFilter = encoder.SkipMetadata ? PngChunkFilter.ExcludeAll : encoder.ChunkFilter ?? PngChunkFilter.None;
+ }
+
+ ///
+ /// Creates the quantized frame.
+ ///
+ /// The type of the pixel.
+ /// The png encoder.
+ /// The color type.
+ /// The bits per component.
+ /// The image.
+ private static IndexedImageFrame CreateQuantizedFrame(
+ IQuantizingEncoderOptions encoder,
+ PngColorType colorType,
+ byte bitDepth,
+ Image image)
+ where TPixel : unmanaged, IPixel
+ {
+ if (colorType != PngColorType.Palette)
+ {
+ return null;
+ }
+
+ // Use the metadata to determine what quantization depth to use if no quantizer has been set.
+ IQuantizer quantizer = encoder.Quantizer
+ ?? new WuQuantizer(new QuantizerOptions { MaxColors = ColorNumerics.GetColorCountForBitDepth(bitDepth) });
+
+ // Create quantized frame returning the palette and set the bit depth.
+ using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(image.GetConfiguration());
+
+ frameQuantizer.BuildPalette(encoder.GlobalPixelSamplingStrategy, image);
+ return frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
+ }
+
+ ///
+ /// Calculates the bit depth value.
+ ///
+ /// The type of the pixel.
+ /// The color type.
+ /// The bits per component.
+ /// The quantized frame.
+ /// Bit depth is not supported or not valid.
+ private static byte CalculateBitDepth(
+ PngColorType colorType,
+ byte bitDepth,
+ IndexedImageFrame quantizedFrame)
+ where TPixel : unmanaged, IPixel
+ {
+ if (colorType == PngColorType.Palette)
+ {
+ byte quantizedBits = (byte)Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(quantizedFrame.Palette.Length), 1, 8);
+ byte bits = Math.Max(bitDepth, quantizedBits);
+
+ // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk
+ // We check again for the bit depth as the bit depth of the color palette from a given quantizer might not
+ // be within the acceptable range.
+ if (bits == 3)
+ {
+ bits = 4;
+ }
+ else if (bits is >= 5 and <= 7)
+ {
+ bits = 8;
+ }
+
+ bitDepth = bits;
+ }
+
+ if (Array.IndexOf(PngConstants.ColorTypes[colorType], bitDepth) == -1)
+ {
+ throw new NotSupportedException("Bit depth is not supported or not valid.");
+ }
+
+ return bitDepth;
+ }
+
+ ///
+ /// Calculates the correct number of bytes per pixel for the given color type.
+ ///
+ /// The color type.
+ /// Whether to use 16 bits per component.
+ /// Bytes per pixel.
+ private static int CalculateBytesPerPixel(PngColorType? pngColorType, bool use16Bit)
+ => pngColorType switch
+ {
+ PngColorType.Grayscale => use16Bit ? 2 : 1,
+ PngColorType.GrayscaleWithAlpha => use16Bit ? 4 : 2,
+ PngColorType.Palette => 1,
+ PngColorType.Rgb => use16Bit ? 6 : 3,
+
+ // PngColorType.RgbWithAlpha
+ _ => use16Bit ? 8 : 4,
+ };
+
+ ///
+ /// Returns a suggested for the given
+ /// This is not exhaustive but covers many common pixel formats.
+ ///
+ /// The type of pixel format.
+ private static PngColorType SuggestColorType()
+ where TPixel : unmanaged, IPixel
+ => typeof(TPixel) switch
+ {
+ Type t when t == typeof(A8) => PngColorType.GrayscaleWithAlpha,
+ Type t when t == typeof(Argb32) => PngColorType.RgbWithAlpha,
+ Type t when t == typeof(Bgr24) => PngColorType.Rgb,
+ Type t when t == typeof(Bgra32) => PngColorType.RgbWithAlpha,
+ Type t when t == typeof(L8) => PngColorType.Grayscale,
+ Type t when t == typeof(L16) => PngColorType.Grayscale,
+ Type t when t == typeof(La16) => PngColorType.GrayscaleWithAlpha,
+ Type t when t == typeof(La32) => PngColorType.GrayscaleWithAlpha,
+ Type t when t == typeof(Rgb24) => PngColorType.Rgb,
+ Type t when t == typeof(Rgba32) => PngColorType.RgbWithAlpha,
+ Type t when t == typeof(Rgb48) => PngColorType.Rgb,
+ Type t when t == typeof(Rgba64) => PngColorType.RgbWithAlpha,
+ Type t when t == typeof(RgbaVector) => PngColorType.RgbWithAlpha,
+ _ => PngColorType.RgbWithAlpha
+ };
+
+ ///
+ /// Returns a suggested for the given
+ /// This is not exhaustive but covers many common pixel formats.
+ ///
+ /// The type of pixel format.
+ private static PngBitDepth SuggestBitDepth()
+ where TPixel : unmanaged, IPixel
+ => typeof(TPixel) switch
+ {
+ Type t when t == typeof(A8) => PngBitDepth.Bit8,
+ Type t when t == typeof(Argb32) => PngBitDepth.Bit8,
+ Type t when t == typeof(Bgr24) => PngBitDepth.Bit8,
+ Type t when t == typeof(Bgra32) => PngBitDepth.Bit8,
+ Type t when t == typeof(L8) => PngBitDepth.Bit8,
+ Type t when t == typeof(L16) => PngBitDepth.Bit16,
+ Type t when t == typeof(La16) => PngBitDepth.Bit8,
+ Type t when t == typeof(La32) => PngBitDepth.Bit16,
+ Type t when t == typeof(Rgb24) => PngBitDepth.Bit8,
+ Type t when t == typeof(Rgba32) => PngBitDepth.Bit8,
+ Type t when t == typeof(Rgb48) => PngBitDepth.Bit16,
+ Type t when t == typeof(Rgba64) => PngBitDepth.Bit16,
+ Type t when t == typeof(RgbaVector) => PngBitDepth.Bit16,
+ _ => PngBitDepth.Bit8
+ };
}
diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs
deleted file mode 100644
index 7fe27b78d7..0000000000
--- a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using SixLabors.ImageSharp.Processing.Processors.Quantization;
-
-namespace SixLabors.ImageSharp.Formats.Png;
-
-///
-/// The options structure for the .
-///
-internal class PngEncoderOptions : IPngEncoderOptions
-{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The source.
- public PngEncoderOptions(IPngEncoderOptions source)
- {
- this.BitDepth = source.BitDepth;
- this.ColorType = source.ColorType;
- this.FilterMethod = source.FilterMethod;
- this.CompressionLevel = source.CompressionLevel;
- this.TextCompressionThreshold = source.TextCompressionThreshold;
- this.Gamma = source.Gamma;
- this.Quantizer = source.Quantizer;
- this.Threshold = source.Threshold;
- this.InterlaceMethod = source.InterlaceMethod;
- this.ChunkFilter = source.ChunkFilter;
- this.IgnoreMetadata = source.IgnoreMetadata;
- this.TransparentColorMode = source.TransparentColorMode;
- }
-
- ///
- public PngBitDepth? BitDepth { get; set; }
-
- ///
- public PngColorType? ColorType { get; set; }
-
- ///
- public PngFilterMethod? FilterMethod { get; set; }
-
- ///
- public PngCompressionLevel CompressionLevel { get; } = PngCompressionLevel.DefaultCompression;
-
- ///
- public int TextCompressionThreshold { get; }
-
- ///
- public float? Gamma { get; set; }
-
- ///
- public IQuantizer Quantizer { get; set; }
-
- ///
- public byte Threshold { get; }
-
- ///
- public PngInterlaceMode? InterlaceMethod { get; set; }
-
- ///
- public PngChunkFilter? ChunkFilter { get; set; }
-
- ///
- public bool IgnoreMetadata { get; set; }
-
- ///
- public PngTransparentColorMode TransparentColorMode { get; set; }
-}
diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs b/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs
deleted file mode 100644
index e1ee61c378..0000000000
--- a/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs
+++ /dev/null
@@ -1,214 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing.Processors.Quantization;
-
-namespace SixLabors.ImageSharp.Formats.Png;
-
-///
-/// The helper methods for the PNG encoder options.
-///
-internal static class PngEncoderOptionsHelpers
-{
- ///
- /// Adjusts the options based upon the given metadata.
- ///
- /// The options.
- /// The PNG metadata.
- /// if set to true [use16 bit].
- /// The bytes per pixel.
- public static void AdjustOptions(
- PngEncoderOptions options,
- PngMetadata pngMetadata,
- out bool use16Bit,
- out int bytesPerPixel)
- where TPixel : unmanaged, IPixel
- {
- // Always take the encoder options over the metadata values.
- options.Gamma ??= pngMetadata.Gamma;
-
- // Use options, then check metadata, if nothing set there then we suggest
- // a sensible default based upon the pixel format.
- options.ColorType ??= pngMetadata.ColorType ?? SuggestColorType();
- options.BitDepth ??= pngMetadata.BitDepth ?? SuggestBitDepth();
- if (!options.FilterMethod.HasValue)
- {
- // Specification recommends default filter method None for paletted images and Paeth for others.
- if (options.ColorType == PngColorType.Palette)
- {
- options.FilterMethod = PngFilterMethod.None;
- }
- else
- {
- options.FilterMethod = PngFilterMethod.Paeth;
- }
- }
-
- // Ensure bit depth and color type are a supported combination.
- // Bit8 is the only bit depth supported by all color types.
- byte bits = (byte)options.BitDepth;
- byte[] validBitDepths = PngConstants.ColorTypes[options.ColorType.Value];
- if (Array.IndexOf(validBitDepths, bits) == -1)
- {
- options.BitDepth = PngBitDepth.Bit8;
- }
-
- options.InterlaceMethod ??= pngMetadata.InterlaceMethod;
-
- use16Bit = options.BitDepth == PngBitDepth.Bit16;
- bytesPerPixel = CalculateBytesPerPixel(options.ColorType, use16Bit);
-
- if (options.IgnoreMetadata)
- {
- options.ChunkFilter = PngChunkFilter.ExcludeAll;
- }
- }
-
- ///
- /// Creates the quantized frame.
- ///
- /// The type of the pixel.
- /// The options.
- /// The image.
- public static IndexedImageFrame CreateQuantizedFrame(
- PngEncoderOptions options,
- Image image)
- where TPixel : unmanaged, IPixel
- {
- if (options.ColorType != PngColorType.Palette)
- {
- return null;
- }
-
- // Use the metadata to determine what quantization depth to use if no quantizer has been set.
- if (options.Quantizer is null)
- {
- byte bits = (byte)options.BitDepth;
- var maxColors = ColorNumerics.GetColorCountForBitDepth(bits);
- options.Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = maxColors });
- }
-
- // Create quantized frame returning the palette and set the bit depth.
- using (IQuantizer frameQuantizer = options.Quantizer.CreatePixelSpecificQuantizer(image.GetConfiguration()))
- {
- ImageFrame frame = image.Frames.RootFrame;
- return frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds());
- }
- }
-
- ///
- /// Calculates the bit depth value.
- ///
- /// The type of the pixel.
- /// The options.
- /// The quantized frame.
- public static byte CalculateBitDepth(
- PngEncoderOptions options,
- IndexedImageFrame quantizedFrame)
- where TPixel : unmanaged, IPixel
- {
- byte bitDepth;
- if (options.ColorType == PngColorType.Palette)
- {
- byte quantizedBits = (byte)Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(quantizedFrame.Palette.Length), 1, 8);
- byte bits = Math.Max((byte)options.BitDepth, quantizedBits);
-
- // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk
- // We check again for the bit depth as the bit depth of the color palette from a given quantizer might not
- // be within the acceptable range.
- if (bits == 3)
- {
- bits = 4;
- }
- else if (bits >= 5 && bits <= 7)
- {
- bits = 8;
- }
-
- bitDepth = bits;
- }
- else
- {
- bitDepth = (byte)options.BitDepth;
- }
-
- if (Array.IndexOf(PngConstants.ColorTypes[options.ColorType.Value], bitDepth) == -1)
- {
- throw new NotSupportedException("Bit depth is not supported or not valid.");
- }
-
- return bitDepth;
- }
-
- ///
- /// Calculates the correct number of bytes per pixel for the given color type.
- ///
- /// Bytes per pixel.
- private static int CalculateBytesPerPixel(PngColorType? pngColorType, bool use16Bit)
- {
- return pngColorType switch
- {
- PngColorType.Grayscale => use16Bit ? 2 : 1,
- PngColorType.GrayscaleWithAlpha => use16Bit ? 4 : 2,
- PngColorType.Palette => 1,
- PngColorType.Rgb => use16Bit ? 6 : 3,
-
- // PngColorType.RgbWithAlpha
- _ => use16Bit ? 8 : 4,
- };
- }
-
- ///
- /// Returns a suggested for the given
- /// This is not exhaustive but covers many common pixel formats.
- ///
- private static PngColorType SuggestColorType()
- where TPixel : unmanaged, IPixel
- {
- return typeof(TPixel) switch
- {
- Type t when t == typeof(A8) => PngColorType.GrayscaleWithAlpha,
- Type t when t == typeof(Argb32) => PngColorType.RgbWithAlpha,
- Type t when t == typeof(Bgr24) => PngColorType.Rgb,
- Type t when t == typeof(Bgra32) => PngColorType.RgbWithAlpha,
- Type t when t == typeof(L8) => PngColorType.Grayscale,
- Type t when t == typeof(L16) => PngColorType.Grayscale,
- Type t when t == typeof(La16) => PngColorType.GrayscaleWithAlpha,
- Type t when t == typeof(La32) => PngColorType.GrayscaleWithAlpha,
- Type t when t == typeof(Rgb24) => PngColorType.Rgb,
- Type t when t == typeof(Rgba32) => PngColorType.RgbWithAlpha,
- Type t when t == typeof(Rgb48) => PngColorType.Rgb,
- Type t when t == typeof(Rgba64) => PngColorType.RgbWithAlpha,
- Type t when t == typeof(RgbaVector) => PngColorType.RgbWithAlpha,
- _ => PngColorType.RgbWithAlpha
- };
- }
-
- ///
- /// Returns a suggested for the given
- /// This is not exhaustive but covers many common pixel formats.
- ///
- private static PngBitDepth SuggestBitDepth()
- where TPixel : unmanaged, IPixel
- {
- return typeof(TPixel) switch
- {
- Type t when t == typeof(A8) => PngBitDepth.Bit8,
- Type t when t == typeof(Argb32) => PngBitDepth.Bit8,
- Type t when t == typeof(Bgr24) => PngBitDepth.Bit8,
- Type t when t == typeof(Bgra32) => PngBitDepth.Bit8,
- Type t when t == typeof(L8) => PngBitDepth.Bit8,
- Type t when t == typeof(L16) => PngBitDepth.Bit16,
- Type t when t == typeof(La16) => PngBitDepth.Bit8,
- Type t when t == typeof(La32) => PngBitDepth.Bit16,
- Type t when t == typeof(Rgb24) => PngBitDepth.Bit8,
- Type t when t == typeof(Rgba32) => PngBitDepth.Bit8,
- Type t when t == typeof(Rgb48) => PngBitDepth.Bit16,
- Type t when t == typeof(Rgba64) => PngBitDepth.Bit16,
- Type t when t == typeof(RgbaVector) => PngBitDepth.Bit16,
- _ => PngBitDepth.Bit8
- };
- }
-}
diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs
index 1129c4e27d..0dcb961f84 100644
--- a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs
+++ b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs
@@ -11,42 +11,40 @@ namespace SixLabors.ImageSharp.Tests.Drawing;
[GroupOutput("Drawing")]
public class DrawImageTests
{
- public static readonly TheoryData BlendingModes = new TheoryData
- {
- PixelColorBlendingMode.Normal,
- PixelColorBlendingMode.Multiply,
- PixelColorBlendingMode.Add,
- PixelColorBlendingMode.Subtract,
- PixelColorBlendingMode.Screen,
- PixelColorBlendingMode.Darken,
- PixelColorBlendingMode.Lighten,
- PixelColorBlendingMode.Overlay,
- PixelColorBlendingMode.HardLight,
- };
+ public static readonly TheoryData BlendingModes = new()
+ {
+ PixelColorBlendingMode.Normal,
+ PixelColorBlendingMode.Multiply,
+ PixelColorBlendingMode.Add,
+ PixelColorBlendingMode.Subtract,
+ PixelColorBlendingMode.Screen,
+ PixelColorBlendingMode.Darken,
+ PixelColorBlendingMode.Lighten,
+ PixelColorBlendingMode.Overlay,
+ PixelColorBlendingMode.HardLight,
+ };
[Theory]
[WithFile(TestImages.Png.Rainbow, nameof(BlendingModes), PixelTypes.Rgba32)]
public void ImageBlendingMatchesSvgSpecExamples(TestImageProvider provider, PixelColorBlendingMode mode)
where TPixel : unmanaged, IPixel
{
- using (Image background = provider.GetImage())
- using (var source = Image.Load(TestFile.Create(TestImages.Png.Ducky).Bytes))
- {
- background.Mutate(x => x.DrawImage(source, mode, 1F));
- background.DebugSave(
- provider,
- new { mode = mode },
- appendPixelTypeToFileName: false,
- appendSourceFileOrDescription: false);
-
- var comparer = ImageComparer.TolerantPercentage(0.01F);
- background.CompareToReferenceOutput(
- comparer,
- provider,
- new { mode = mode },
- appendPixelTypeToFileName: false,
- appendSourceFileOrDescription: false);
- }
+ using Image background = provider.GetImage();
+ using Image source = Image.Load(TestFile.Create(TestImages.Png.Ducky).Bytes);
+ background.Mutate(x => x.DrawImage(source, mode, 1F));
+ background.DebugSave(
+ provider,
+ new { mode },
+ appendPixelTypeToFileName: false,
+ appendSourceFileOrDescription: false);
+
+ ImageComparer comparer = ImageComparer.TolerantPercentage(0.01F);
+ background.CompareToReferenceOutput(
+ comparer,
+ provider,
+ new { mode },
+ appendPixelTypeToFileName: false,
+ appendSourceFileOrDescription: false);
}
[Theory]
@@ -68,28 +66,29 @@ public class DrawImageTests
float opacity)
where TPixel : unmanaged, IPixel
{
- using (Image image = provider.GetImage())
- using (var blend = Image.Load(TestFile.Create(brushImage).Bytes))
+ using Image image = provider.GetImage();
+ using Image blend = Image.Load(TestFile.Create(brushImage).Bytes);
+ Size size = new(image.Width * 3 / 4, image.Height * 3 / 4);
+ Point position = new(image.Width / 8, image.Height / 8);
+ blend.Mutate(x => x.Resize(size.Width, size.Height, KnownResamplers.Bicubic));
+ image.Mutate(x => x.DrawImage(blend, position, mode, opacity));
+ FormattableString testInfo = $"{Path.GetFileNameWithoutExtension(brushImage)}-{mode}-{opacity}";
+
+ PngEncoder encoder;
+ if (provider.PixelType == PixelTypes.Rgba64)
{
- var size = new Size(image.Width * 3 / 4, image.Height * 3 / 4);
- var position = new Point(image.Width / 8, image.Height / 8);
- blend.Mutate(x => x.Resize(size.Width, size.Height, KnownResamplers.Bicubic));
- image.Mutate(x => x.DrawImage(blend, position, mode, opacity));
- FormattableString testInfo = $"{System.IO.Path.GetFileNameWithoutExtension(brushImage)}-{mode}-{opacity}";
-
- var encoder = new PngEncoder();
-
- if (provider.PixelType == PixelTypes.Rgba64)
- {
- encoder.BitDepth = PngBitDepth.Bit16;
- }
-
- image.DebugSave(provider, testInfo, encoder: encoder);
- image.CompareToReferenceOutput(
- ImageComparer.TolerantPercentage(0.01f),
- provider,
- testInfo);
+ encoder = new() { BitDepth = PngBitDepth.Bit16 };
}
+ else
+ {
+ encoder = new();
+ }
+
+ image.DebugSave(provider, testInfo, encoder: encoder);
+ image.CompareToReferenceOutput(
+ ImageComparer.TolerantPercentage(0.01f),
+ provider,
+ testInfo);
}
[Theory]
@@ -99,19 +98,17 @@ public class DrawImageTests
{
byte[] brushData = TestFile.Create(TestImages.Png.Ducky).Bytes;
- using (Image image = provider.GetImage())
- using (Image brushImage = provider.PixelType == PixelTypes.Rgba32
- ? (Image)Image.Load(brushData)
- : Image.Load(brushData))
- {
- image.Mutate(c => c.DrawImage(brushImage, 0.5f));
-
- image.DebugSave(provider, appendSourceFileOrDescription: false);
- image.CompareToReferenceOutput(
- ImageComparer.TolerantPercentage(0.01f),
- provider,
- appendSourceFileOrDescription: false);
- }
+ using Image image = provider.GetImage();
+ using Image brushImage = provider.PixelType == PixelTypes.Rgba32
+ ? Image.Load(brushData)
+ : Image.Load(brushData);
+ image.Mutate(c => c.DrawImage(brushImage, 0.5f));
+
+ image.DebugSave(provider, appendSourceFileOrDescription: false);
+ image.CompareToReferenceOutput(
+ ImageComparer.TolerantPercentage(0.01f),
+ provider,
+ appendSourceFileOrDescription: false);
}
[Theory]
@@ -121,26 +118,24 @@ public class DrawImageTests
[WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, -25, -30)]
public void WorksWithDifferentLocations(TestImageProvider provider, int x, int y)
{
- using (Image background = provider.GetImage())
- using (var overlay = new Image(50, 50))
- {
- Assert.True(overlay.DangerousTryGetSinglePixelMemory(out Memory overlayMem));
- overlayMem.Span.Fill(Color.Black);
-
- background.Mutate(c => c.DrawImage(overlay, new Point(x, y), PixelColorBlendingMode.Normal, 1F));
-
- background.DebugSave(
- provider,
- testOutputDetails: $"{x}_{y}",
- appendPixelTypeToFileName: false,
- appendSourceFileOrDescription: false);
-
- background.CompareToReferenceOutput(
- provider,
- testOutputDetails: $"{x}_{y}",
- appendPixelTypeToFileName: false,
- appendSourceFileOrDescription: false);
- }
+ using Image background = provider.GetImage();
+ using Image overlay = new(50, 50);
+ Assert.True(overlay.DangerousTryGetSinglePixelMemory(out Memory overlayMem));
+ overlayMem.Span.Fill(Color.Black);
+
+ background.Mutate(c => c.DrawImage(overlay, new Point(x, y), PixelColorBlendingMode.Normal, 1F));
+
+ background.DebugSave(
+ provider,
+ testOutputDetails: $"{x}_{y}",
+ appendPixelTypeToFileName: false,
+ appendSourceFileOrDescription: false);
+
+ background.CompareToReferenceOutput(
+ provider,
+ testOutputDetails: $"{x}_{y}",
+ appendPixelTypeToFileName: false,
+ appendSourceFileOrDescription: false);
}
[Theory]
@@ -148,29 +143,27 @@ public class DrawImageTests
public void DrawTransformed(TestImageProvider provider)
where TPixel : unmanaged, IPixel
{
- using (Image image = provider.GetImage())
- using (var blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes))
- {
- AffineTransformBuilder builder = new AffineTransformBuilder()
- .AppendRotationDegrees(45F)
- .AppendScale(new SizeF(.25F, .25F))
- .AppendTranslation(new PointF(10, 10));
-
- // Apply a background color so we can see the translation.
- blend.Mutate(x => x.Transform(builder));
- blend.Mutate(x => x.BackgroundColor(Color.HotPink));
-
- // Lets center the matrix so we can tell whether any cut-off issues we may have belong to the drawing processor
- var position = new Point((image.Width - blend.Width) / 2, (image.Height - blend.Height) / 2);
- image.Mutate(x => x.DrawImage(blend, position, .75F));
-
- image.DebugSave(provider, appendSourceFileOrDescription: false, appendPixelTypeToFileName: false);
- image.CompareToReferenceOutput(
- ImageComparer.TolerantPercentage(0.002f),
- provider,
- appendSourceFileOrDescription: false,
- appendPixelTypeToFileName: false);
- }
+ using Image image = provider.GetImage();
+ using Image blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes);
+ AffineTransformBuilder builder = new AffineTransformBuilder()
+ .AppendRotationDegrees(45F)
+ .AppendScale(new SizeF(.25F, .25F))
+ .AppendTranslation(new PointF(10, 10));
+
+ // Apply a background color so we can see the translation.
+ blend.Mutate(x => x.Transform(builder));
+ blend.Mutate(x => x.BackgroundColor(Color.HotPink));
+
+ // Lets center the matrix so we can tell whether any cut-off issues we may have belong to the drawing processor
+ Point position = new((image.Width - blend.Width) / 2, (image.Height - blend.Height) / 2);
+ image.Mutate(x => x.DrawImage(blend, position, .75F));
+
+ image.DebugSave(provider, appendSourceFileOrDescription: false, appendPixelTypeToFileName: false);
+ image.CompareToReferenceOutput(
+ ImageComparer.TolerantPercentage(0.002f),
+ provider,
+ appendSourceFileOrDescription: false,
+ appendPixelTypeToFileName: false);
}
[Theory]
@@ -180,17 +173,15 @@ public class DrawImageTests
[WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, -30, 130)]
public void NonOverlappingImageThrows(TestImageProvider provider, int x, int y)
{
- using (Image background = provider.GetImage())
- using (var overlay = new Image(Configuration.Default, 10, 10, Color.Black))
- {
- ImageProcessingException ex = Assert.Throws(Test);
+ using Image background = provider.GetImage();
+ using Image overlay = new(Configuration.Default, 10, 10, Color.Black);
+ ImageProcessingException ex = Assert.Throws(Test);
- Assert.Contains("does not overlap", ex.ToString());
+ Assert.Contains("does not overlap", ex.ToString());
- void Test()
- {
- background.Mutate(context => context.DrawImage(overlay, new Point(x, y), new GraphicsOptions()));
- }
+ void Test()
+ {
+ background.Mutate(context => context.DrawImage(overlay, new Point(x, y), new GraphicsOptions()));
}
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
index d5365e8f73..83b8a88695 100644
--- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
@@ -46,7 +46,7 @@ public class GifEncoderTests
using (Image image = provider.GetImage())
{
- var encoder = new GifEncoder
+ GifEncoder encoder = new()
{
// Use the palette quantizer without dithering to ensure results
// are consistent
@@ -59,59 +59,45 @@ public class GifEncoderTests
// Compare encoded result
string path = provider.Utility.GetTestOutputFileName("gif", null, true);
- using (var encoded = Image.Load(path))
- {
- encoded.CompareToReferenceOutput(ValidatorComparer, provider, null, "gif");
- }
+ using Image encoded = Image.Load(path);
+ encoded.CompareToReferenceOutput(ValidatorComparer, provider, null, "gif");
}
[Theory]
[MemberData(nameof(RatioFiles))]
public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
- var options = new GifEncoder();
-
- 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))
- {
- ImageMetadata meta = output.Metadata;
- Assert.Equal(xResolution, meta.HorizontalResolution);
- Assert.Equal(yResolution, meta.VerticalResolution);
- Assert.Equal(resolutionUnit, meta.ResolutionUnits);
- }
- }
- }
+ GifEncoder options = new();
+
+ 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);
+ ImageMetadata meta = output.Metadata;
+ Assert.Equal(xResolution, meta.HorizontalResolution);
+ Assert.Equal(yResolution, meta.VerticalResolution);
+ Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
[Fact]
public void Encode_IgnoreMetadataIsFalse_CommentsAreWritten()
{
- var options = new GifEncoder();
+ GifEncoder options = new();
- var testFile = TestFile.Create(TestImages.Gif.Rings);
+ TestFile testFile = TestFile.Create(TestImages.Gif.Rings);
- using (Image input = testFile.CreateRgba32Image())
- {
- using (var memStream = new MemoryStream())
- {
- input.Save(memStream, options);
-
- memStream.Position = 0;
- using (var output = Image.Load(memStream))
- {
- GifMetadata metadata = output.Metadata.GetGifMetadata();
- Assert.Equal(1, metadata.Comments.Count);
- Assert.Equal("ImageSharp", metadata.Comments[0]);
- }
- }
- }
+ using Image input = testFile.CreateRgba32Image();
+ using MemoryStream memStream = new();
+ input.Save(memStream, options);
+
+ memStream.Position = 0;
+ using Image output = Image.Load(memStream);
+ GifMetadata metadata = output.Metadata.GetGifMetadata();
+ Assert.Equal(1, metadata.Comments.Count);
+ Assert.Equal("ImageSharp", metadata.Comments[0]);
}
[Theory]
@@ -119,25 +105,28 @@ public class GifEncoderTests
public void EncodeGlobalPaletteReturnsSmallerFile(TestImageProvider provider)
where TPixel : unmanaged, IPixel
{
- using (Image image = provider.GetImage())
+ using Image image = provider.GetImage();
+ GifEncoder encoder = new()
{
- var encoder = new GifEncoder
- {
- ColorTableMode = GifColorTableMode.Global,
- Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null })
- };
+ ColorTableMode = GifColorTableMode.Global,
+ Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null })
+ };
- // Always save as we need to compare the encoded output.
- provider.Utility.SaveTestOutputFile(image, "gif", encoder, "global");
+ // Always save as we need to compare the encoded output.
+ provider.Utility.SaveTestOutputFile(image, "gif", encoder, "global");
- encoder.ColorTableMode = GifColorTableMode.Local;
- provider.Utility.SaveTestOutputFile(image, "gif", encoder, "local");
+ encoder = new()
+ {
+ ColorTableMode = GifColorTableMode.Local,
+ Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }),
+ };
- var fileInfoGlobal = new FileInfo(provider.Utility.GetTestOutputFileName("gif", "global"));
- var fileInfoLocal = new FileInfo(provider.Utility.GetTestOutputFileName("gif", "local"));
+ provider.Utility.SaveTestOutputFile(image, "gif", encoder, "local");
- Assert.True(fileInfoGlobal.Length < fileInfoLocal.Length);
- }
+ FileInfo fileInfoGlobal = new(provider.Utility.GetTestOutputFileName("gif", "global"));
+ FileInfo fileInfoLocal = new(provider.Utility.GetTestOutputFileName("gif", "local"));
+
+ Assert.True(fileInfoGlobal.Length < fileInfoLocal.Length);
}
[Theory]
@@ -152,7 +141,7 @@ public class GifEncoderTests
{
using Image image = provider.GetImage();
- var encoder = new GifEncoder()
+ GifEncoder encoder = new()
{
ColorTableMode = GifColorTableMode.Global,
GlobalPixelSamplingStrategy = new DefaultPixelSamplingStrategy(maxPixels, scanRatio)
@@ -166,8 +155,7 @@ public class GifEncoderTests
appendPixelTypeToFileName: false);
// TODO: For proper regression testing of gifs, use a multi-frame reference output, or find a working reference decoder.
- // IImageDecoder referenceDecoder = TestEnvironment.Ge
- // ReferenceDecoder(testOutputFile);
+ // IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(testOutputFile);
// using var encoded = Image.Load(testOutputFile, referenceDecoder);
// ValidatorComparer.VerifySimilarity(image, encoded);
}
@@ -175,44 +163,42 @@ public class GifEncoderTests
[Fact]
public void NonMutatingEncodePreservesPaletteCount()
{
- using (var inStream = new MemoryStream(TestFile.Create(TestImages.Gif.Leo).Bytes))
- using (var outStream = new MemoryStream())
+ using MemoryStream inStream = new(TestFile.Create(TestImages.Gif.Leo).Bytes);
+ using MemoryStream outStream = new();
+ inStream.Position = 0;
+
+ Image image = Image.Load(inStream);
+ GifMetadata metaData = image.Metadata.GetGifMetadata();
+ GifFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetGifMetadata();
+ GifColorTableMode colorMode = metaData.ColorTableMode;
+ GifEncoder encoder = new()
{
- inStream.Position = 0;
-
- var image = Image.Load(inStream);
- GifMetadata metaData = image.Metadata.GetGifMetadata();
- GifFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetGifMetadata();
- GifColorTableMode colorMode = metaData.ColorTableMode;
- var encoder = new GifEncoder
- {
- ColorTableMode = colorMode,
- Quantizer = new OctreeQuantizer(new QuantizerOptions { MaxColors = frameMetadata.ColorTableLength })
- };
+ ColorTableMode = colorMode,
+ Quantizer = new OctreeQuantizer(new QuantizerOptions { MaxColors = frameMetadata.ColorTableLength })
+ };
- image.Save(outStream, encoder);
- outStream.Position = 0;
+ image.Save(outStream, encoder);
+ outStream.Position = 0;
- outStream.Position = 0;
- var clone = Image.Load(outStream);
+ outStream.Position = 0;
+ Image clone = Image.Load(outStream);
- GifMetadata cloneMetadata = clone.Metadata.GetGifMetadata();
- Assert.Equal(metaData.ColorTableMode, cloneMetadata.ColorTableMode);
+ GifMetadata cloneMetadata = clone.Metadata.GetGifMetadata();
+ Assert.Equal(metaData.ColorTableMode, cloneMetadata.ColorTableMode);
- // Gifiddle and Cyotek GifInfo say this image has 64 colors.
- Assert.Equal(64, frameMetadata.ColorTableLength);
+ // Gifiddle and Cyotek GifInfo say this image has 64 colors.
+ Assert.Equal(64, frameMetadata.ColorTableLength);
- for (int i = 0; i < image.Frames.Count; i++)
- {
- GifFrameMetadata ifm = image.Frames[i].Metadata.GetGifMetadata();
- GifFrameMetadata cifm = clone.Frames[i].Metadata.GetGifMetadata();
-
- Assert.Equal(ifm.ColorTableLength, cifm.ColorTableLength);
- Assert.Equal(ifm.FrameDelay, cifm.FrameDelay);
- }
+ for (int i = 0; i < image.Frames.Count; i++)
+ {
+ GifFrameMetadata ifm = image.Frames[i].Metadata.GetGifMetadata();
+ GifFrameMetadata cifm = clone.Frames[i].Metadata.GetGifMetadata();
- image.Dispose();
- clone.Dispose();
+ Assert.Equal(ifm.ColorTableLength, cifm.ColorTableLength);
+ Assert.Equal(ifm.FrameDelay, cifm.FrameDelay);
}
+
+ image.Dispose();
+ clone.Dispose();
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs
index 5a9b1df2a7..044da21938 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs
@@ -15,9 +15,9 @@ public partial class PngEncoderTests
public void HeaderChunk_ComesFirst()
{
// arrange
- var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
+ TestFile testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using Image input = testFile.CreateRgba32Image();
- using var memStream = new MemoryStream();
+ using MemoryStream memStream = new();
// act
input.Save(memStream, PngEncoder);
@@ -25,8 +25,8 @@ public partial class PngEncoderTests
// assert
memStream.Position = 0;
Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header.
- BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
- var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
+ BinaryPrimitives.ReadInt32BigEndian(bytesSpan[..4]);
+ PngChunkType type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
Assert.Equal(PngChunkType.Header, type);
}
@@ -34,9 +34,9 @@ public partial class PngEncoderTests
public void EndChunk_IsLast()
{
// arrange
- var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
+ TestFile testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using Image input = testFile.CreateRgba32Image();
- using var memStream = new MemoryStream();
+ using MemoryStream memStream = new();
// act
input.Save(memStream, PngEncoder);
@@ -47,15 +47,15 @@ public partial class PngEncoderTests
bool endChunkFound = false;
while (bytesSpan.Length > 0)
{
- int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
- var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
+ int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan[..4]);
+ PngChunkType type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
Assert.False(endChunkFound);
if (type == PngChunkType.End)
{
endChunkFound = true;
}
- bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
+ bytesSpan = bytesSpan[(4 + 4 + length + 4)..];
}
}
@@ -68,10 +68,10 @@ public partial class PngEncoderTests
public void Chunk_ComesBeforePlteAndIDat(object chunkTypeObj)
{
// arrange
- var chunkType = (PngChunkType)chunkTypeObj;
- var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
+ PngChunkType chunkType = (PngChunkType)chunkTypeObj;
+ TestFile testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using Image input = testFile.CreateRgba32Image();
- using var memStream = new MemoryStream();
+ using MemoryStream memStream = new();
// act
input.Save(memStream, PngEncoder);
@@ -83,8 +83,8 @@ public partial class PngEncoderTests
bool dataFound = false;
while (bytesSpan.Length > 0)
{
- int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
- var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
+ int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan[..4]);
+ PngChunkType type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
if (chunkType == type)
{
Assert.False(palFound || dataFound, $"{chunkType} chunk should come before data and palette chunk");
@@ -100,7 +100,7 @@ public partial class PngEncoderTests
break;
}
- bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
+ bytesSpan = bytesSpan[(4 + 4 + length + 4)..];
}
}
@@ -110,10 +110,10 @@ public partial class PngEncoderTests
public void Chunk_ComesBeforeIDat(object chunkTypeObj)
{
// arrange
- var chunkType = (PngChunkType)chunkTypeObj;
- var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
+ PngChunkType chunkType = (PngChunkType)chunkTypeObj;
+ TestFile testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using Image input = testFile.CreateRgba32Image();
- using var memStream = new MemoryStream();
+ using MemoryStream memStream = new();
// act
input.Save(memStream, PngEncoder);
@@ -124,8 +124,8 @@ public partial class PngEncoderTests
bool dataFound = false;
while (bytesSpan.Length > 0)
{
- int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
- var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
+ int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan[..4]);
+ PngChunkType type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
if (chunkType == type)
{
Assert.False(dataFound, $"{chunkType} chunk should come before data chunk");
@@ -136,7 +136,7 @@ public partial class PngEncoderTests
dataFound = true;
}
- bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
+ bytesSpan = bytesSpan[(4 + 4 + length + 4)..];
}
}
@@ -144,18 +144,18 @@ public partial class PngEncoderTests
public void IgnoreMetadata_WillExcludeAllAncillaryChunks()
{
// arrange
- var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
+ TestFile testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using Image input = testFile.CreateRgba32Image();
- using var memStream = new MemoryStream();
- var encoder = new PngEncoder() { IgnoreMetadata = true, TextCompressionThreshold = 8 };
- var expectedChunkTypes = new Dictionary()
+ using MemoryStream memStream = new();
+ PngEncoder encoder = new() { SkipMetadata = true, TextCompressionThreshold = 8 };
+ Dictionary expectedChunkTypes = new()
{
{ PngChunkType.Header, false },
{ PngChunkType.Palette, false },
{ PngChunkType.Data, false },
{ PngChunkType.End, false }
};
- var excludedChunkTypes = new List()
+ List excludedChunkTypes = new()
{
PngChunkType.Gamma,
PngChunkType.Exif,
@@ -174,15 +174,15 @@ public partial class PngEncoderTests
Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header.
while (bytesSpan.Length > 0)
{
- int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
- var chunkType = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
+ int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan[..4]);
+ PngChunkType chunkType = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
Assert.False(excludedChunkTypes.Contains(chunkType), $"{chunkType} chunk should have been excluded");
if (expectedChunkTypes.ContainsKey(chunkType))
{
expectedChunkTypes[chunkType] = true;
}
- bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
+ bytesSpan = bytesSpan[(4 + 4 + length + 4)..];
}
// all expected chunk types should have been seen at least once.
@@ -201,12 +201,12 @@ public partial class PngEncoderTests
public void ExcludeFilter_Works(object filterObj)
{
// arrange
- var chunkFilter = (PngChunkFilter)filterObj;
- var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
+ PngChunkFilter chunkFilter = (PngChunkFilter)filterObj;
+ TestFile testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using Image input = testFile.CreateRgba32Image();
- using var memStream = new MemoryStream();
- var encoder = new PngEncoder() { ChunkFilter = chunkFilter, TextCompressionThreshold = 8 };
- var expectedChunkTypes = new Dictionary()
+ using MemoryStream memStream = new();
+ PngEncoder encoder = new() { ChunkFilter = chunkFilter, TextCompressionThreshold = 8 };
+ Dictionary expectedChunkTypes = new()
{
{ PngChunkType.Header, false },
{ PngChunkType.Gamma, false },
@@ -219,7 +219,7 @@ public partial class PngEncoderTests
{ PngChunkType.Data, false },
{ PngChunkType.End, false }
};
- var excludedChunkTypes = new List();
+ List excludedChunkTypes = new();
switch (chunkFilter)
{
case PngChunkFilter.ExcludeGammaChunk:
@@ -267,15 +267,15 @@ public partial class PngEncoderTests
Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header.
while (bytesSpan.Length > 0)
{
- int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
- var chunkType = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
+ int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan[..4]);
+ PngChunkType chunkType = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
Assert.False(excludedChunkTypes.Contains(chunkType), $"{chunkType} chunk should have been excluded");
if (expectedChunkTypes.ContainsKey(chunkType))
{
expectedChunkTypes[chunkType] = true;
}
- bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
+ bytesSpan = bytesSpan[(4 + 4 + length + 4)..];
}
// all expected chunk types should have been seen at least once.
@@ -289,11 +289,11 @@ public partial class PngEncoderTests
public void ExcludeFilter_WithNone_DoesNotExcludeChunks()
{
// arrange
- var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
+ TestFile testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using Image input = testFile.CreateRgba32Image();
- using var memStream = new MemoryStream();
- var encoder = new PngEncoder() { ChunkFilter = PngChunkFilter.None, TextCompressionThreshold = 8 };
- var expectedChunkTypes = new List()
+ using MemoryStream memStream = new();
+ PngEncoder encoder = new() { ChunkFilter = PngChunkFilter.None, TextCompressionThreshold = 8 };
+ List expectedChunkTypes = new()
{
PngChunkType.Header,
PngChunkType.Gamma,
@@ -314,11 +314,11 @@ public partial class PngEncoderTests
Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header.
while (bytesSpan.Length > 0)
{
- int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
- var chunkType = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
+ int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan[..4]);
+ PngChunkType chunkType = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
Assert.True(expectedChunkTypes.Contains(chunkType), $"{chunkType} chunk should have been present");
- bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
+ bytesSpan = bytesSpan[(4 + 4 + length + 4)..];
}
}
}
diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ImageSharpPngEncoderWithDefaultConfiguration.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ImageSharpPngEncoderWithDefaultConfiguration.cs
index 6ba73cd279..1290fdea3a 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ImageSharpPngEncoderWithDefaultConfiguration.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ImageSharpPngEncoderWithDefaultConfiguration.cs
@@ -4,8 +4,6 @@
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Memory;
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
@@ -13,57 +11,20 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
/// A Png encoder that uses the ImageSharp core encoder but the default configuration.
/// This allows encoding under environments with restricted memory.
///
-public sealed class ImageSharpPngEncoderWithDefaultConfiguration : IImageEncoder, IPngEncoderOptions
+public sealed class ImageSharpPngEncoderWithDefaultConfiguration : PngEncoder
{
- ///
- public PngBitDepth? BitDepth { get; set; }
-
- ///
- public PngColorType? ColorType { get; set; }
-
- ///
- public PngFilterMethod? FilterMethod { get; set; }
-
- ///
- public PngCompressionLevel CompressionLevel { get; set; } = PngCompressionLevel.DefaultCompression;
-
- ///
- public int TextCompressionThreshold { get; set; } = 1024;
-
- ///
- public float? Gamma { get; set; }
-
- ///
- public IQuantizer Quantizer { get; set; }
-
- ///
- public byte Threshold { get; set; } = byte.MaxValue;
-
- ///
- public PngInterlaceMode? InterlaceMethod { get; set; }
-
- ///
- public PngChunkFilter? ChunkFilter { get; set; }
-
- ///
- public bool IgnoreMetadata { get; set; }
-
- ///
- public PngTransparentColorMode TransparentColorMode { get; set; }
-
///
/// Encodes the image to the specified stream from the .
///
/// The pixel format.
/// The to encode from.
/// The to encode the image data to.
- public void Encode(Image image, Stream stream)
- where TPixel : unmanaged, IPixel
+ public override void Encode(Image image, Stream stream)
{
Configuration configuration = Configuration.Default;
MemoryAllocator allocator = configuration.MemoryAllocator;
- using var encoder = new PngEncoderCore(allocator, configuration, new PngEncoderOptions(this));
+ using PngEncoderCore encoder = new(allocator, configuration, this);
encoder.Encode(image, stream);
}
@@ -75,8 +36,7 @@ public sealed class ImageSharpPngEncoderWithDefaultConfiguration : IImageEncoder
/// The to encode the image data to.
/// The token to monitor for cancellation requests.
/// A representing the asynchronous operation.
- public async Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel
+ public override async Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken)
{
Configuration configuration = Configuration.Default;
MemoryAllocator allocator = configuration.MemoryAllocator;
@@ -84,7 +44,7 @@ public sealed class ImageSharpPngEncoderWithDefaultConfiguration : IImageEncoder
// The introduction of a local variable that refers to an object the implements
// IDisposable means you must use async/await, where the compiler generates the
// state machine and a continuation.
- using var encoder = new PngEncoderCore(allocator, configuration, new PngEncoderOptions(this));
+ using PngEncoderCore encoder = new(allocator, configuration, this);
await encoder.EncodeAsync(image, stream, cancellationToken).ConfigureAwait(false);
}
}