diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs
index 9c285e21de..0cf28c6bb2 100644
--- a/src/ImageSharp/Advanced/AotCompilerTools.cs
+++ b/src/ImageSharp/Advanced/AotCompilerTools.cs
@@ -57,6 +57,9 @@ internal static class AotCompilerTools
/// If you are getting the above error, you need to call this method, which will pre-seed the AoT compiler with the
/// necessary methods to complete the SaveAsGif call. That's it, otherwise you should NEVER need this method!!!
///
+ ///
+ /// This method is used for AOT code generation only. Do not call it at runtime.
+ ///
[Preserve]
private static void SeedPixelFormats()
{
@@ -487,8 +490,10 @@ private static void AotCompileQuantizer()
private static void AotCompilePixelSamplingStrategys()
where TPixel : unmanaged, IPixel
{
- default(DefaultPixelSamplingStrategy).EnumeratePixelRegions(default);
- default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions(default);
+ default(DefaultPixelSamplingStrategy).EnumeratePixelRegions(default(Image));
+ default(DefaultPixelSamplingStrategy).EnumeratePixelRegions(default(ImageFrame));
+ default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions(default(Image));
+ default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions(default(ImageFrame));
}
///
@@ -513,13 +518,13 @@ private static void AotCompileDither()
where TPixel : unmanaged, IPixel
where TDither : struct, IDither
{
- var octree = default(OctreeQuantizer);
+ OctreeQuantizer octree = default;
default(TDither).ApplyQuantizationDither, TPixel>(ref octree, default, default, default);
- var palette = default(PaletteQuantizer);
+ PaletteQuantizer palette = default;
default(TDither).ApplyQuantizationDither, TPixel>(ref palette, default, default, default);
- var wu = default(WuQuantizer);
+ WuQuantizer wu = default;
default(TDither).ApplyQuantizationDither, TPixel>(ref wu, default, default, default);
default(TDither).ApplyPaletteDither.DitherProcessor, TPixel>(default, default, default);
}
diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs
index a410a862b5..9e20da170a 100644
--- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs
@@ -2,48 +2,38 @@
// 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.Bmp;
///
/// Image encoder for writing an image to a stream as a Windows bitmap.
///
-public sealed class BmpEncoder : IImageEncoder, IBmpEncoderOptions
+public sealed class BmpEncoder : QuantizingImageEncoder
{
///
- /// Gets or sets the number of bits per pixel.
+ /// Gets the number of bits per pixel.
///
- public BmpBitsPerPixel? BitsPerPixel { get; set; }
+ public BmpBitsPerPixel? BitsPerPixel { get; init; }
///
- /// Gets or sets a value indicating whether the encoder should support transparency.
+ /// Gets a value indicating whether the encoder should support transparency.
/// Note: Transparency support only works together with 32 bits per pixel. This option will
/// change the default behavior of the encoder of writing a bitmap version 3 info header with no compression.
/// Instead a bitmap version 4 info header will be written with the BITFIELDS compression.
///
- public bool SupportTransparency { get; set; }
-
- ///
- /// Gets or sets the quantizer for reducing the color count for 8-Bit images.
- /// Defaults to Wu Quantizer.
- ///
- public IQuantizer Quantizer { get; set; }
+ public bool SupportTransparency { get; init; }
///
- public void Encode(Image image, Stream stream)
- where TPixel : unmanaged, IPixel
+ public override void Encode(Image image, Stream stream)
{
- var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator());
+ BmpEncoderCore encoder = new(this, image.GetMemoryAllocator());
encoder.Encode(image, stream);
}
///
- public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel
+ public override Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken)
{
- var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator());
+ BmpEncoderCore encoder = new(this, image.GetMemoryAllocator());
return encoder.EncodeAsync(image, stream, cancellationToken);
}
}
diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
index 471e741826..a4e1f8eef9 100644
--- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
@@ -9,7 +9,6 @@
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Bmp;
@@ -92,17 +91,23 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
///
private readonly IQuantizer quantizer;
+ ///
+ /// The pixel sampling strategy for quantization.
+ ///
+ private readonly IPixelSamplingStrategy pixelSamplingStrategy;
+
///
/// Initializes a new instance of the class.
///
- /// The encoder options.
+ /// The encoder with options.
/// The memory manager.
- public BmpEncoderCore(IBmpEncoderOptions options, MemoryAllocator memoryAllocator)
+ public BmpEncoderCore(BmpEncoder encoder, MemoryAllocator memoryAllocator)
{
this.memoryAllocator = memoryAllocator;
- this.bitsPerPixel = options.BitsPerPixel;
- this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
- this.infoHeaderType = options.SupportTransparency ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion3;
+ this.bitsPerPixel = encoder.BitsPerPixel;
+ this.quantizer = encoder.Quantizer;
+ this.pixelSamplingStrategy = encoder.PixelSamplingStrategy;
+ this.infoHeaderType = encoder.SupportTransparency ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion3;
}
///
@@ -159,7 +164,7 @@ public void Encode(Image image, Stream stream, CancellationToken
WriteBitmapFileHeader(stream, infoHeaderSize, colorPaletteSize, iccProfileSize, infoHeader, buffer);
this.WriteBitmapInfoHeader(stream, infoHeader, buffer, infoHeaderSize);
- this.WriteImage(stream, image.Frames.RootFrame);
+ this.WriteImage(stream, image);
WriteColorProfile(stream, iccProfileData, buffer);
stream.Flush();
@@ -311,10 +316,10 @@ private void WriteBitmapInfoHeader(Stream stream, BmpInfoHeader infoHeader, Span
///
/// The containing pixel data.
///
- private void WriteImage(Stream stream, ImageFrame image)
+ private void WriteImage(Stream stream, Image image)
where TPixel : unmanaged, IPixel
{
- Buffer2D pixels = image.PixelBuffer;
+ Buffer2D pixels = image.Frames.RootFrame.PixelBuffer;
switch (this.bitsPerPixel)
{
case BmpBitsPerPixel.Pixel32:
@@ -433,8 +438,8 @@ private void Write16BitPixelData(Stream stream, Buffer2D pixels)
///
/// The type of the pixel.
/// The to write to.
- /// The containing pixel data.
- private void Write8BitPixelData(Stream stream, ImageFrame image)
+ /// The containing pixel data.
+ private void Write8BitPixelData(Stream stream, Image image)
where TPixel : unmanaged, IPixel
{
bool isL8 = typeof(TPixel) == typeof(L8);
@@ -456,13 +461,15 @@ private void Write8BitPixelData(Stream stream, ImageFrame image)
///
/// The type of the pixel.
/// The to write to.
- /// The containing pixel data.
+ /// The containing pixel data.
/// A byte span of size 1024 for the color palette.
- private void Write8BitColor(Stream stream, ImageFrame image, Span colorPalette)
+ private void Write8BitColor(Stream stream, Image image, Span colorPalette)
where TPixel : unmanaged, IPixel
{
using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration);
- using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
+
+ frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);
+ using IndexedImageFrame quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
ReadOnlySpan quantizedColorPalette = quantized.Palette.Span;
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
@@ -486,7 +493,7 @@ private void Write8BitColor(Stream stream, ImageFrame image, Spa
/// The to write to.
/// The containing pixel data.
/// A byte span of size 1024 for the color palette.
- private void Write8BitPixelData(Stream stream, ImageFrame image, Span colorPalette)
+ private void Write8BitPixelData(Stream stream, Image image, Span colorPalette)
where TPixel : unmanaged, IPixel
{
// Create a color palette with 256 different gray values.
@@ -503,7 +510,7 @@ private void Write8BitPixelData(Stream stream, ImageFrame image,
}
stream.Write(colorPalette);
- Buffer2D imageBuffer = image.PixelBuffer;
+ Buffer2D imageBuffer = image.GetRootFramePixelBuffer();
for (int y = image.Height - 1; y >= 0; y--)
{
ReadOnlySpan inputPixelRow = imageBuffer.DangerousGetRowSpan(y);
@@ -523,14 +530,17 @@ private void Write8BitPixelData(Stream stream, ImageFrame image,
/// The type of the pixel.
/// The to write to.
/// The containing pixel data.
- private void Write4BitPixelData(Stream stream, ImageFrame image)
+ private void Write4BitPixelData(Stream stream, Image image)
where TPixel : unmanaged, IPixel
{
using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration, new QuantizerOptions()
{
MaxColors = 16
});
- using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
+
+ frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);
+
+ using IndexedImageFrame quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.Allocate(ColorPaletteSize4Bit, AllocationOptions.Clean);
Span colorPalette = colorPaletteBuffer.GetSpan();
@@ -567,14 +577,17 @@ private void Write4BitPixelData(Stream stream, ImageFrame image)
/// The type of the pixel.
/// The to write to.
/// The containing pixel data.
- private void Write2BitPixelData(Stream stream, ImageFrame image)
+ private void Write2BitPixelData(Stream stream, Image image)
where TPixel : unmanaged, IPixel
{
using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration, new QuantizerOptions()
{
MaxColors = 4
});
- using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
+
+ frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);
+
+ using IndexedImageFrame quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.Allocate(ColorPaletteSize2Bit, AllocationOptions.Clean);
Span colorPalette = colorPaletteBuffer.GetSpan();
@@ -620,14 +633,17 @@ private void Write2BitPixelData(Stream stream, ImageFrame image)
/// The type of the pixel.
/// The to write to.
/// The containing pixel data.
- private void Write1BitPixelData(Stream stream, ImageFrame image)
+ private void Write1BitPixelData(Stream stream, Image image)
where TPixel : unmanaged, IPixel
{
using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration, new QuantizerOptions()
{
MaxColors = 2
});
- using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
+
+ frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);
+
+ using IndexedImageFrame quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.Allocate(ColorPaletteSize1Bit, AllocationOptions.Clean);
Span colorPalette = colorPaletteBuffer.GetSpan();
diff --git a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
deleted file mode 100644
index c2ce99ec71..0000000000
--- a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using SixLabors.ImageSharp.Processing.Processors.Quantization;
-
-namespace SixLabors.ImageSharp.Formats.Bmp;
-
-///
-/// Configuration options for use during bmp encoding.
-///
-internal interface IBmpEncoderOptions
-{
- ///
- /// Gets the number of bits per pixel.
- ///
- BmpBitsPerPixel? BitsPerPixel { get; }
-
- ///
- /// Gets a value indicating whether the encoder should support transparency.
- /// Note: Transparency support only works together with 32 bits per pixel. This option will
- /// change the default behavior of the encoder of writing a bitmap version 3 info header with no compression.
- /// Instead a bitmap version 4 info header will be written with the BITFIELDS compression.
- ///
- bool SupportTransparency { get; }
-
- ///
- /// Gets the quantizer for reducing the color count for 8-Bit, 4-Bit, and 1-Bit images.
- ///
- IQuantizer Quantizer { get; }
-}
diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs
index b6441db102..351554eb07 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoder.cs
+++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs
@@ -2,47 +2,30 @@
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing;
-using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Gif;
///
/// Image encoder for writing image data to a stream in gif format.
///
-public sealed class GifEncoder : IImageEncoder, IGifEncoderOptions
+public sealed class GifEncoder : QuantizingImageEncoder
{
///
- /// Gets or sets the quantizer for reducing the color count.
- /// Defaults to the
+ /// Gets the color table mode: Global or local.
///
- public IQuantizer Quantizer { get; set; } = KnownQuantizers.Octree;
-
- ///
- /// Gets or sets the color table mode: Global or local.
- ///
- public GifColorTableMode? ColorTableMode { get; set; }
-
- ///
- /// Gets or sets the used for quantization
- /// when building a global color table in case of .
- ///
- public IPixelSamplingStrategy GlobalPixelSamplingStrategy { get; set; } = new DefaultPixelSamplingStrategy();
+ public GifColorTableMode? ColorTableMode { get; init; }
///
- public void Encode(Image image, Stream stream)
- where TPixel : unmanaged, IPixel
+ public override void Encode(Image image, Stream stream)
{
- var encoder = new GifEncoderCore(image.GetConfiguration(), this);
+ GifEncoderCore encoder = new(image.GetConfiguration(), this);
encoder.Encode(image, stream);
}
///
- public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel
+ public override Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken)
{
- var encoder = new GifEncoderCore(image.GetConfiguration(), this);
+ GifEncoderCore encoder = new(image.GetConfiguration(), this);
return encoder.EncodeAsync(image, stream, cancellationToken);
}
}
diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
index cfd4ba36a6..14d20cf909 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
@@ -33,6 +33,11 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
///
private readonly byte[] buffer = new byte[20];
+ ///
+ /// Whether to skip metadata during encode.
+ ///
+ private readonly bool skipMetadata;
+
///
/// The quantizer used to generate the color palette.
///
@@ -57,14 +62,15 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
/// Initializes a new instance of the class.
///
/// The configuration which allows altering default behaviour or extending the library.
- /// The options for the encoder.
- public GifEncoderCore(Configuration configuration, IGifEncoderOptions options)
+ /// The encoder with options.
+ public GifEncoderCore(Configuration configuration, GifEncoder encoder)
{
this.configuration = configuration;
this.memoryAllocator = configuration.MemoryAllocator;
- this.quantizer = options.Quantizer;
- this.colorTableMode = options.ColorTableMode;
- this.pixelSamplingStrategy = options.GlobalPixelSamplingStrategy;
+ this.skipMetadata = encoder.SkipMetadata;
+ this.quantizer = encoder.Quantizer;
+ this.colorTableMode = encoder.ColorTableMode;
+ this.pixelSamplingStrategy = encoder.PixelSamplingStrategy;
}
///
@@ -97,7 +103,8 @@ public void Encode(Image image, Stream stream, CancellationToken
}
else
{
- quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image.Frames.RootFrame, image.Bounds());
+ frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image.Frames.RootFrame);
+ quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
}
}
@@ -116,12 +123,15 @@ public void Encode(Image image, Stream stream, CancellationToken
this.WriteColorTable(quantized, stream);
}
- // Write the comments.
- this.WriteComments(gifMetadata, stream);
+ if (!this.skipMetadata)
+ {
+ // Write the comments.
+ this.WriteComments(gifMetadata, stream);
- // Write application extensions.
- XmpProfile xmpProfile = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile;
- this.WriteApplicationExtensions(stream, image.Frames.Count, gifMetadata.RepeatCount, xmpProfile);
+ // Write application extensions.
+ XmpProfile xmpProfile = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile;
+ this.WriteApplicationExtensions(stream, image.Frames.Count, gifMetadata.RepeatCount, xmpProfile);
+ }
if (useGlobalTable)
{
diff --git a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs
deleted file mode 100644
index 1ed68a9177..0000000000
--- a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using SixLabors.ImageSharp.Processing.Processors.Quantization;
-
-namespace SixLabors.ImageSharp.Formats.Gif;
-
-///
-/// The configuration options used for encoding gifs.
-///
-internal interface IGifEncoderOptions
-{
- ///
- /// Gets the quantizer used to generate the color palette.
- ///
- IQuantizer Quantizer { get; }
-
- ///
- /// Gets the color table mode: Global or local.
- ///
- GifColorTableMode? ColorTableMode { get; }
-
- ///
- /// Gets the used for quantization when building a global color table.
- ///
- IPixelSamplingStrategy GlobalPixelSamplingStrategy { get; }
-}
diff --git a/src/ImageSharp/Formats/ImageDecoderUtilities.cs b/src/ImageSharp/Formats/ImageDecoderUtilities.cs
index 42f15cf976..c05e0d83cb 100644
--- a/src/ImageSharp/Formats/ImageDecoderUtilities.cs
+++ b/src/ImageSharp/Formats/ImageDecoderUtilities.cs
@@ -58,7 +58,7 @@ internal static IImageInfo Identify(
Stream stream,
CancellationToken cancellationToken)
{
- using var bufferedReadStream = new BufferedReadStream(configuration, stream);
+ using BufferedReadStream bufferedReadStream = new(configuration, stream);
try
{
@@ -86,7 +86,7 @@ internal static Image Decode(
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
- using var bufferedReadStream = new BufferedReadStream(configuration, stream);
+ using BufferedReadStream bufferedReadStream = new(configuration, stream);
try
{
diff --git a/src/ImageSharp/Formats/ImageEncoder.cs b/src/ImageSharp/Formats/ImageEncoder.cs
new file mode 100644
index 0000000000..a0c087e646
--- /dev/null
+++ b/src/ImageSharp/Formats/ImageEncoder.cs
@@ -0,0 +1,43 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+using SixLabors.ImageSharp.Processing.Processors.Quantization;
+
+namespace SixLabors.ImageSharp.Formats;
+
+///
+/// The base class for all image encoders.
+///
+public abstract class ImageEncoder : IImageEncoder
+{
+ ///
+ /// Gets a value indicating whether to ignore decoded metadata when encoding.
+ ///
+ public bool SkipMetadata { get; init; }
+
+ ///
+ public abstract void Encode(Image image, Stream stream)
+ where TPixel : unmanaged, IPixel;
+
+ ///
+ public abstract Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken)
+ where TPixel : unmanaged, IPixel;
+}
+
+///
+/// The base class for all image encoders that allow color palette generation via quantization.
+///
+public abstract class QuantizingImageEncoder : ImageEncoder
+{
+ ///
+ /// Gets the quantizer used to generate the color palette.
+ ///
+ public IQuantizer Quantizer { get; init; } = KnownQuantizers.Octree;
+
+ ///
+ /// Gets the used for quantization when building color palettes.
+ ///
+ public IPixelSamplingStrategy PixelSamplingStrategy { get; init; } = new DefaultPixelSamplingStrategy();
+}
diff --git a/src/ImageSharp/Formats/ImageEncoderUtilities.cs b/src/ImageSharp/Formats/ImageEncoderUtilities.cs
index 94f74ea253..665431c952 100644
--- a/src/ImageSharp/Formats/ImageEncoderUtilities.cs
+++ b/src/ImageSharp/Formats/ImageEncoderUtilities.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Advanced;
@@ -22,11 +22,11 @@ public static async Task EncodeAsync(
}
else
{
- using var ms = new MemoryStream();
+ using MemoryStream ms = new();
await DoEncodeAsync(ms);
ms.Position = 0;
await ms.CopyToAsync(stream, configuration.StreamProcessingBufferSize, cancellationToken)
- .ConfigureAwait(false);
+ .ConfigureAwait(false);
}
Task DoEncodeAsync(Stream innerStream)
diff --git a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs
deleted file mode 100644
index b7e6d5d614..0000000000
--- a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-namespace SixLabors.ImageSharp.Formats.Jpeg;
-
-///
-/// Encoder for writing the data image to a stream in jpeg format.
-///
-internal interface IJpegEncoderOptions
-{
- ///
- /// Gets or sets the quality, that will be used to encode the image. Quality
- /// index must be between 0 and 100 (compression from max to min).
- /// Defaults to 75.
- ///
- public int? Quality { get; set; }
-
- ///
- /// Gets or sets the component encoding mode.
- ///
- ///
- /// Interleaved encoding mode encodes all color components in a single scan.
- /// Non-interleaved encoding mode encodes each color component in a separate scan.
- ///
- public bool? Interleaved { get; set; }
-
- ///
- /// Gets or sets jpeg color for encoding.
- ///
- public JpegEncodingColor? ColorType { get; set; }
-}
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
index 28d095eedc..2ddd829f87 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
@@ -1,25 +1,28 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
-using SixLabors.ImageSharp.PixelFormats;
-
namespace SixLabors.ImageSharp.Formats.Jpeg;
///
/// Encoder for writing the data image to a stream in jpeg format.
///
-public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions
+public sealed class JpegEncoder : ImageEncoder
{
///
/// Backing field for .
///
private int? quality;
- ///
+ ///
+ /// Gets the quality, that will be used to encode the image. Quality
+ /// index must be between 1 and 100 (compression from max to min).
+ /// Defaults to 75.
+ ///
+ /// Quality factor must be in [1..100] range.
public int? Quality
{
get => this.quality;
- set
+ init
{
if (value is < 1 or > 100)
{
@@ -30,37 +33,31 @@ public int? Quality
}
}
- ///
- public bool? Interleaved { get; set; }
-
- ///
- public JpegEncodingColor? ColorType { get; set; }
+ ///
+ /// Gets the component encoding mode.
+ ///
+ ///
+ /// Interleaved encoding mode encodes all color components in a single scan.
+ /// Non-interleaved encoding mode encodes each color component in a separate scan.
+ ///
+ public bool? Interleaved { get; init; }
///
- /// Encodes the image to the specified stream from the .
+ /// Gets the jpeg color for encoding.
///
- /// 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 JpegEncodingColor? ColorType { get; init; }
+
+ ///
+ public override void Encode(Image image, Stream stream)
{
- var encoder = new JpegEncoderCore(this);
+ JpegEncoderCore encoder = new(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 Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel
+ ///
+ public override Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken)
{
- var encoder = new JpegEncoderCore(this);
+ JpegEncoderCore encoder = new(this);
return encoder.EncodeAsync(image, stream, cancellationToken);
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
index 83c2e27e91..e915b74bc3 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
@@ -29,7 +29,7 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
///
private readonly byte[] buffer = new byte[20];
- private readonly IJpegEncoderOptions options;
+ private readonly JpegEncoder encoder;
///
/// The output stream. All attempted writes after the first error become no-ops.
@@ -39,9 +39,9 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
///
/// Initializes a new instance of the class.
///
- /// The options.
- public JpegEncoderCore(IJpegEncoderOptions options)
- => this.options = options;
+ /// The parent encoder.
+ public JpegEncoderCore(JpegEncoder encoder)
+ => this.encoder = encoder;
public Block8x8F[] QuantizationTables { get; } = new Block8x8F[4];
@@ -71,8 +71,8 @@ public void Encode(Image image, Stream stream, CancellationToken
JpegMetadata jpegMetadata = metadata.GetJpegMetadata();
JpegFrameConfig frameConfig = this.GetFrameConfig(jpegMetadata);
- bool interleaved = this.options.Interleaved ?? jpegMetadata.Interleaved ?? true;
- using var frame = new JpegFrame(image, frameConfig, interleaved);
+ bool interleaved = this.encoder.Interleaved ?? jpegMetadata.Interleaved ?? true;
+ using JpegFrame frame = new(image, frameConfig, interleaved);
// Write the Start Of Image marker.
this.WriteStartOfImage();
@@ -96,14 +96,14 @@ public void Encode(Image image, Stream stream, CancellationToken
this.WriteStartOfFrame(image.Width, image.Height, frameConfig);
// Write the Huffman tables.
- var scanEncoder = new HuffmanScanEncoder(frame.BlocksPerMcu, stream);
+ HuffmanScanEncoder scanEncoder = new(frame.BlocksPerMcu, stream);
this.WriteDefineHuffmanTables(frameConfig.HuffmanTables, scanEncoder);
// Write the quantization tables.
- this.WriteDefineQuantizationTables(frameConfig.QuantizationTables, this.options.Quality, jpegMetadata);
+ this.WriteDefineQuantizationTables(frameConfig.QuantizationTables, this.encoder.Quality, jpegMetadata);
// Write scans with actual pixel data
- using var spectralConverter = new SpectralConverter(frame, image, this.QuantizationTables);
+ using SpectralConverter spectralConverter = new(frame, image, this.QuantizationTables);
this.WriteHuffmanScans(frame, frameConfig, spectralConverter, scanEncoder, cancellationToken);
// Write the End Of Image marker.
@@ -172,6 +172,9 @@ private void WriteJfifApplicationHeader(ImageMetadata meta)
///
/// Writes the Define Huffman Table marker and tables.
///
+ /// The table configuration.
+ /// The scan encoder.
+ /// is .
private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs, HuffmanScanEncoder scanEncoder)
{
if (tableConfigs is null)
@@ -203,6 +206,7 @@ private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs, Huf
///
/// Writes the APP14 marker to indicate the image is in RGB color space.
///
+ /// The color transform byte.
private void WriteApp14Marker(byte colorTransform)
{
this.WriteMarkerHeader(JpegConstants.Markers.APP14, 2 + Components.Decoder.AdobeMarker.Length);
@@ -498,6 +502,9 @@ private void WriteProfiles(ImageMetadata metadata)
///
/// Writes the Start Of Frame (Baseline) marker.
///
+ /// The frame width.
+ /// The frame height.
+ /// The frame configuration.
private void WriteStartOfFrame(int width, int height, JpegFrameConfig frame)
{
JpegComponentConfig[] components = frame.Components;
@@ -536,6 +543,7 @@ private void WriteStartOfFrame(int width, int height, JpegFrameConfig frame)
///
/// Writes the StartOfScan marker.
///
+ /// The collecction of component configuration items.
private void WriteStartOfScan(Span components)
{
// Write the SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes:
@@ -588,7 +596,18 @@ private void WriteEndOfImageMarker()
///
/// Writes scans for given config.
///
- private void WriteHuffmanScans(JpegFrame frame, JpegFrameConfig frameConfig, SpectralConverter spectralConverter, HuffmanScanEncoder encoder, CancellationToken cancellationToken)
+ /// The type of pixel format.
+ /// The current frame.
+ /// The frame configuration.
+ /// The spectral converter.
+ /// The scan encoder.
+ /// The cancellation token.
+ private void WriteHuffmanScans(
+ JpegFrame frame,
+ JpegFrameConfig frameConfig,
+ SpectralConverter spectralConverter,
+ HuffmanScanEncoder encoder,
+ CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
if (frame.Components.Length == 1)
@@ -696,7 +715,7 @@ private void WriteDefineQuantizationTables(JpegQuantizationTableConfig[] configs
private JpegFrameConfig GetFrameConfig(JpegMetadata metadata)
{
- JpegEncodingColor color = this.options.ColorType ?? metadata.ColorType ?? JpegEncodingColor.YCbCrRatio420;
+ JpegEncodingColor color = this.encoder.ColorType ?? metadata.ColorType ?? JpegEncodingColor.YCbCrRatio420;
JpegFrameConfig frameConfig = Array.Find(
FrameConfigs,
cfg => cfg.EncodingColor == color);
diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoder.cs b/src/ImageSharp/Formats/Pbm/PbmEncoder.cs
index e0e93cc11e..6b25347c04 100644
--- a/src/ImageSharp/Formats/Pbm/PbmEncoder.cs
+++ b/src/ImageSharp/Formats/Pbm/PbmEncoder.cs
@@ -2,7 +2,6 @@
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Pbm;
@@ -30,36 +29,34 @@ namespace SixLabors.ImageSharp.Formats.Pbm;
///
/// The specification of these images is found at .
///
-public sealed class PbmEncoder : IImageEncoder, IPbmEncoderOptions
+public sealed class PbmEncoder : ImageEncoder
{
///
- /// Gets or sets the Encoding of the pixels.
+ /// Gets the encoding of the pixels.
///
- public PbmEncoding? Encoding { get; set; }
+ public PbmEncoding? Encoding { get; init; }
///
- /// Gets or sets the Color type of the resulting image.
+ /// Gets the Color type of the resulting image.
///
- public PbmColorType? ColorType { get; set; }
+ public PbmColorType? ColorType { get; init; }
///
- /// Gets or sets the data type of the pixels components.
+ /// Gets the Data Type of the pixel components.
///
- public PbmComponentType? ComponentType { get; set; }
+ public PbmComponentType? ComponentType { get; init; }
///
- public void Encode(Image image, Stream stream)
- where TPixel : unmanaged, IPixel
+ public override void Encode(Image image, Stream stream)
{
- var encoder = new PbmEncoderCore(image.GetConfiguration(), this);
+ PbmEncoderCore encoder = new(image.GetConfiguration(), this);
encoder.Encode(image, stream);
}
///
- public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel
+ public override Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken)
{
- var encoder = new PbmEncoderCore(image.GetConfiguration(), this);
+ PbmEncoderCore encoder = new(image.GetConfiguration(), this);
return encoder.EncodeAsync(image, stream, cancellationToken);
}
}
diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs
index c7d8d183c2..7c7860c58e 100644
--- a/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs
+++ b/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs
@@ -22,9 +22,9 @@ internal sealed class PbmEncoderCore : IImageEncoderInternals
private Configuration configuration;
///
- /// The encoder options.
+ /// The encoder with options.
///
- private readonly IPbmEncoderOptions options;
+ private readonly PbmEncoder encoder;
///
/// The encoding for the pixels.
@@ -45,11 +45,11 @@ internal sealed class PbmEncoderCore : IImageEncoderInternals
/// Initializes a new instance of the class.
///
/// The configuration.
- /// The encoder options.
- public PbmEncoderCore(Configuration configuration, IPbmEncoderOptions options)
+ /// The encoder with options.
+ public PbmEncoderCore(Configuration configuration, PbmEncoder encoder)
{
this.configuration = configuration;
- this.options = options;
+ this.encoder = encoder;
}
///
@@ -65,7 +65,7 @@ public void Encode(Image image, Stream stream, CancellationToken
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
- this.DeduceOptions(image);
+ this.SanitizeAndSetEncoderOptions(image);
byte signature = this.DeduceSignature();
this.WriteHeader(stream, signature, image.Size());
@@ -75,16 +75,16 @@ public void Encode(Image image, Stream stream, CancellationToken
stream.Flush();
}
- private void DeduceOptions(Image image)
+ private void SanitizeAndSetEncoderOptions(Image image)
where TPixel : unmanaged, IPixel
{
this.configuration = image.GetConfiguration();
PbmMetadata metadata = image.Metadata.GetPbmMetadata();
- this.encoding = this.options.Encoding ?? metadata.Encoding;
- this.colorType = this.options.ColorType ?? metadata.ColorType;
+ this.encoding = this.encoder.Encoding ?? metadata.Encoding;
+ this.colorType = this.encoder.ColorType ?? metadata.ColorType;
if (this.colorType != PbmColorType.BlackAndWhite)
{
- this.componentType = this.options.ComponentType ?? metadata.ComponentType;
+ this.componentType = this.encoder.ComponentType ?? metadata.ComponentType;
}
else
{
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..610ea69240 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -5,6 +5,7 @@
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.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 @@ public void Encode(Image image, Stream stream, CancellationToken
ImageMetadata metadata = image.Metadata;
PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance);
- PngEncoderOptionsHelpers.AdjustOptions(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel);
+ this.SanitizeAndSetEncoderOptions(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 @@ private static void ClearTransparentPixels(Image image)
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 @@ private static void ClearTransparentPixels(Image image)
});
///
- /// 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 @@ private void CollectGrayscaleBytes(ReadOnlySpan rowSpan)
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 @@ private void CollectTPixelBytes(ReadOnlySpan rowSpan)
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 @@ private void CollectPixelBytes(ReadOnlySpan rowSpan, IndexedImag
/// 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 @@ private void ApplyOptimalFilteredScanline(ref Span filter, ref Span
{
// 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 @@ private void WriteHeaderChunk(Stream stream)
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 @@ private void WritePaletteChunk(Stream stream, IndexedImageFrame
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 @@ private void WritePaletteChunk(Stream stream, IndexedImageFrame
/// 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 @@ private void WritePhysicalChunk(Stream stream, ImageMetadata meta)
/// 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 @@ private void WriteExifChunk(Stream stream, ImageMetadata meta)
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 @@ private void WriteColorProfileChunk(Stream stream, ImageMetadata metaData)
/// 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 @@ private void WriteTextChunks(Stream stream, PngMetadata meta)
{
// 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 @@ private void WriteTextChunks(Stream stream, PngMetadata meta)
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 @@ private void WriteTextChunks(Stream stream, PngMetadata meta)
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 @@ private void WriteTextChunks(Stream stream, PngMetadata meta)
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 @@ private byte[] GetZlibCompressedBytes(byte[] dataBytes)
/// 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 @@ private void WriteDataChunks(Image pixels, IndexedImageFrame 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 SanitizeAndSetEncoderOptions(
+ 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(
+ QuantizingImageEncoder 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.PixelSamplingStrategy, 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) < 0)
+ {
+ 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/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs b/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs
deleted file mode 100644
index a42feb7f35..0000000000
--- a/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-namespace SixLabors.ImageSharp.Formats.Tga;
-
-///
-/// Configuration options for use during tga encoding.
-///
-internal interface ITgaEncoderOptions
-{
- ///
- /// Gets the number of bits per pixel.
- ///
- TgaBitsPerPixel? BitsPerPixel { get; }
-
- ///
- /// Gets a value indicating whether run length compression should be used.
- ///
- TgaCompression Compression { get; }
-}
diff --git a/src/ImageSharp/Formats/Tga/TgaEncoder.cs b/src/ImageSharp/Formats/Tga/TgaEncoder.cs
index f437e80a3f..e0a3932355 100644
--- a/src/ImageSharp/Formats/Tga/TgaEncoder.cs
+++ b/src/ImageSharp/Formats/Tga/TgaEncoder.cs
@@ -2,38 +2,35 @@
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tga;
///
/// Image encoder for writing an image to a stream as a targa truevision image.
///
-public sealed class TgaEncoder : IImageEncoder, ITgaEncoderOptions
+public sealed class TgaEncoder : ImageEncoder
{
///
- /// Gets or sets the number of bits per pixel.
+ /// Gets the number of bits per pixel.
///
- public TgaBitsPerPixel? BitsPerPixel { get; set; }
+ public TgaBitsPerPixel? BitsPerPixel { get; init; }
///
- /// Gets or sets a value indicating whether no compression or run length compression should be used.
+ /// Gets a value indicating whether no compression or run length compression should be used.
///
- public TgaCompression Compression { get; set; } = TgaCompression.RunLength;
+ public TgaCompression Compression { get; init; } = TgaCompression.RunLength;
///
- public void Encode(Image image, Stream stream)
- where TPixel : unmanaged, IPixel
+ public override void Encode(Image image, Stream stream)
{
- var encoder = new TgaEncoderCore(this, image.GetMemoryAllocator());
+ TgaEncoderCore encoder = new(this, image.GetMemoryAllocator());
encoder.Encode(image, stream);
}
///
- public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel
+ public override Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken)
{
- var encoder = new TgaEncoderCore(this, image.GetMemoryAllocator());
+ TgaEncoderCore encoder = new(this, image.GetMemoryAllocator());
return encoder.EncodeAsync(image, stream, cancellationToken);
}
}
diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
index 016806db03..0a3eb74aed 100644
--- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
+++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
@@ -45,13 +45,13 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
///
/// Initializes a new instance of the class.
///
- /// The encoder options.
+ /// The encoder with options.
/// The memory manager.
- public TgaEncoderCore(ITgaEncoderOptions options, MemoryAllocator memoryAllocator)
+ public TgaEncoderCore(TgaEncoder encoder, MemoryAllocator memoryAllocator)
{
this.memoryAllocator = memoryAllocator;
- this.bitsPerPixel = options.BitsPerPixel;
- this.compression = options.Compression;
+ this.bitsPerPixel = encoder.BitsPerPixel;
+ this.compression = encoder.Compression;
}
///
@@ -105,7 +105,9 @@ public void Encode(Image image, Stream stream, CancellationToken
cMapLength: 0,
cMapDepth: 0,
xOffset: 0,
- yOffset: this.compression is TgaCompression.RunLength ? (short)image.Height : (short)0, // When run length encoding is used, the origin should be top left instead of the default bottom left.
+
+ // When run length encoding is used, the origin should be top left instead of the default bottom left.
+ yOffset: this.compression is TgaCompression.RunLength ? (short)image.Height : (short)0,
width: (short)image.Width,
height: (short)image.Height,
pixelDepth: (byte)this.bitsPerPixel.Value,
diff --git a/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs b/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
deleted file mode 100644
index 1e74e630ce..0000000000
--- a/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using SixLabors.ImageSharp.Compression.Zlib;
-using SixLabors.ImageSharp.Formats.Tiff.Constants;
-using SixLabors.ImageSharp.Processing.Processors.Quantization;
-
-namespace SixLabors.ImageSharp.Formats.Tiff;
-
-///
-/// Encapsulates the options for the .
-///
-internal interface ITiffEncoderOptions
-{
- ///
- /// Gets the number of bits per pixel.
- ///
- TiffBitsPerPixel? BitsPerPixel { get; }
-
- ///
- /// Gets the compression type to use.
- ///
- TiffCompression? Compression { get; }
-
- ///
- /// Gets the compression level 1-9 for the deflate compression mode.
- /// Defaults to .
- ///
- DeflateCompressionLevel? CompressionLevel { get; }
-
- ///
- /// Gets the PhotometricInterpretation to use. Possible options are RGB, RGB with a color palette, gray or BiColor.
- /// If no PhotometricInterpretation is specified or it is unsupported by the encoder, RGB will be used.
- ///
- TiffPhotometricInterpretation? PhotometricInterpretation { get; }
-
- ///
- /// Gets a value indicating which horizontal prediction to use. This can improve the compression ratio with deflate or lzw compression.
- ///
- TiffPredictor? HorizontalPredictor { get; }
-
- ///
- /// Gets the quantizer for creating a color palette image.
- ///
- IQuantizer Quantizer { get; }
-}
diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs
index 3b5d347722..e7bb08cdc3 100644
--- a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs
@@ -4,47 +4,52 @@
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Tiff;
///
/// Encoder for writing the data image to a stream in TIFF format.
///
-public class TiffEncoder : IImageEncoder, ITiffEncoderOptions
+public class TiffEncoder : QuantizingImageEncoder
{
- ///
- public TiffBitsPerPixel? BitsPerPixel { get; set; }
-
- ///
- public TiffCompression? Compression { get; set; }
-
- ///
- public DeflateCompressionLevel? CompressionLevel { get; set; }
-
- ///
- public TiffPhotometricInterpretation? PhotometricInterpretation { get; set; }
-
- ///
- public TiffPredictor? HorizontalPredictor { get; set; }
-
- ///
- public IQuantizer Quantizer { get; set; }
-
- ///
- public void Encode(Image image, Stream stream)
- where TPixel : unmanaged, IPixel
+ ///
+ /// Gets the number of bits per pixel.
+ ///
+ public TiffBitsPerPixel? BitsPerPixel { get; init; }
+
+ ///
+ /// Gets the compression type to use.
+ ///
+ public TiffCompression? Compression { get; init; }
+
+ ///
+ /// Gets the compression level 1-9 for the deflate compression mode.
+ /// Defaults to .
+ ///
+ public DeflateCompressionLevel? CompressionLevel { get; init; }
+
+ ///
+ /// Gets the PhotometricInterpretation to use. Possible options are RGB, RGB with a color palette, gray or BiColor.
+ /// If no PhotometricInterpretation is specified or it is unsupported by the encoder, RGB will be used.
+ ///
+ public TiffPhotometricInterpretation? PhotometricInterpretation { get; init; }
+
+ ///
+ /// Gets a value indicating which horizontal prediction to use. This can improve the compression ratio with deflate or lzw compression.
+ ///
+ public TiffPredictor? HorizontalPredictor { get; init; }
+
+ ///
+ public override void Encode(Image image, Stream stream)
{
- var encode = new TiffEncoderCore(this, image.GetMemoryAllocator());
+ TiffEncoderCore encode = new(this, image.GetMemoryAllocator());
encode.Encode(image, stream);
}
///
- public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel
+ public override Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken)
{
- var encoder = new TiffEncoderCore(this, image.GetMemoryAllocator());
+ TiffEncoderCore encoder = new(this, image.GetMemoryAllocator());
return encoder.EncodeAsync(image, stream, cancellationToken);
}
}
diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
index 9b50b958c8..2013377ed6 100644
--- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
@@ -10,7 +10,6 @@
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Tiff;
@@ -40,10 +39,15 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
private Configuration configuration;
///
- /// The quantizer for creating color palette image.
+ /// The quantizer for creating color palette images.
///
private readonly IQuantizer quantizer;
+ ///
+ /// The pixel sampling strategy for quantization.
+ ///
+ private readonly IPixelSamplingStrategy pixelSamplingStrategy;
+
///
/// Sets the deflate compression level.
///
@@ -69,6 +73,11 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
///
private const TiffPhotometricInterpretation DefaultPhotometricInterpretation = TiffPhotometricInterpretation.Rgb;
+ ///
+ /// Whether to skip metadata during encoding.
+ ///
+ private readonly bool skipMetadata;
+
private readonly List<(long, uint)> frameMarkers = new();
///
@@ -76,15 +85,17 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
///
/// The options for the encoder.
/// The memory allocator.
- public TiffEncoderCore(ITiffEncoderOptions options, MemoryAllocator memoryAllocator)
+ public TiffEncoderCore(TiffEncoder options, MemoryAllocator memoryAllocator)
{
this.memoryAllocator = memoryAllocator;
this.PhotometricInterpretation = options.PhotometricInterpretation;
- this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
+ this.quantizer = options.Quantizer;
+ this.pixelSamplingStrategy = options.PixelSamplingStrategy;
this.BitsPerPixel = options.BitsPerPixel;
this.HorizontalPredictor = options.HorizontalPredictor;
this.CompressionType = options.Compression;
this.compressionLevel = options.CompressionLevel ?? DeflateCompressionLevel.DefaultCompression;
+ this.skipMetadata = options.SkipMetadata;
}
///
@@ -215,6 +226,7 @@ private long WriteFrame(
this.PhotometricInterpretation,
frame,
this.quantizer,
+ this.pixelSamplingStrategy,
this.memoryAllocator,
this.configuration,
entriesCollector,
@@ -226,7 +238,7 @@ private long WriteFrame(
if (image != null)
{
- entriesCollector.ProcessMetadata(image);
+ entriesCollector.ProcessMetadata(image, this.skipMetadata);
}
entriesCollector.ProcessFrameInfo(frame, imageMetadata);
@@ -331,7 +343,12 @@ private long WriteIfd(TiffStreamWriter writer, List entries)
return nextIfdMarker;
}
- private void SanitizeAndSetEncoderOptions(TiffBitsPerPixel? bitsPerPixel, int inputBitsPerPixel, TiffPhotometricInterpretation? photometricInterpretation, TiffCompression compression, TiffPredictor predictor)
+ private void SanitizeAndSetEncoderOptions(
+ TiffBitsPerPixel? bitsPerPixel,
+ int inputBitsPerPixel,
+ TiffPhotometricInterpretation? photometricInterpretation,
+ TiffCompression compression,
+ TiffPredictor predictor)
{
// BitsPerPixel should be the primary source of truth for the encoder options.
if (bitsPerPixel.HasValue)
diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs
index e158900cdc..64b300cd60 100644
--- a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs
@@ -15,8 +15,8 @@ internal class TiffEncoderEntriesCollector
public List Entries { get; } = new List();
- public void ProcessMetadata(Image image)
- => new MetadataProcessor(this).Process(image);
+ public void ProcessMetadata(Image image, bool skipMetadata)
+ => new MetadataProcessor(this).Process(image, skipMetadata);
public void ProcessFrameInfo(ImageFrame frame, ImageMetadata imageMetadata)
=> new FrameInfoProcessor(this).Process(frame, imageMetadata);
@@ -41,7 +41,7 @@ public void AddOrReplace(IExifValue entry)
private abstract class BaseProcessor
{
- public BaseProcessor(TiffEncoderEntriesCollector collector) => this.Collector = collector;
+ protected BaseProcessor(TiffEncoderEntriesCollector collector) => this.Collector = collector;
protected TiffEncoderEntriesCollector Collector { get; }
}
@@ -53,14 +53,18 @@ public MetadataProcessor(TiffEncoderEntriesCollector collector)
{
}
- public void Process(Image image)
+ public void Process(Image image, bool skipMetadata)
{
ImageFrame rootFrame = image.Frames.RootFrame;
- ExifProfile rootFrameExifProfile = rootFrame.Metadata.ExifProfile ?? new ExifProfile();
+ ExifProfile rootFrameExifProfile = rootFrame.Metadata.ExifProfile;
XmpProfile rootFrameXmpProfile = rootFrame.Metadata.XmpProfile;
- this.ProcessProfiles(image.Metadata, rootFrameExifProfile, rootFrameXmpProfile);
- this.ProcessMetadata(rootFrameExifProfile);
+ this.ProcessProfiles(image.Metadata, skipMetadata, rootFrameExifProfile, rootFrameXmpProfile);
+
+ if (!skipMetadata)
+ {
+ this.ProcessMetadata(rootFrameExifProfile ?? new ExifProfile());
+ }
if (!this.Collector.Entries.Exists(t => t.Tag == ExifTag.Software))
{
@@ -72,39 +76,35 @@ public void Process(Image image)
}
private static bool IsPureMetadata(ExifTag tag)
- {
- switch ((ExifTagValue)(ushort)tag)
+ => (ExifTagValue)(ushort)tag switch
{
- case ExifTagValue.DocumentName:
- case ExifTagValue.ImageDescription:
- case ExifTagValue.Make:
- case ExifTagValue.Model:
- case ExifTagValue.Software:
- case ExifTagValue.DateTime:
- case ExifTagValue.Artist:
- case ExifTagValue.HostComputer:
- case ExifTagValue.TargetPrinter:
- case ExifTagValue.XMP:
- case ExifTagValue.Rating:
- case ExifTagValue.RatingPercent:
- case ExifTagValue.ImageID:
- case ExifTagValue.Copyright:
- case ExifTagValue.MDLabName:
- case ExifTagValue.MDSampleInfo:
- case ExifTagValue.MDPrepDate:
- case ExifTagValue.MDPrepTime:
- case ExifTagValue.MDFileUnits:
- case ExifTagValue.SEMInfo:
- case ExifTagValue.XPTitle:
- case ExifTagValue.XPComment:
- case ExifTagValue.XPAuthor:
- case ExifTagValue.XPKeywords:
- case ExifTagValue.XPSubject:
- return true;
- default:
- return false;
- }
- }
+ ExifTagValue.DocumentName or
+ ExifTagValue.ImageDescription or
+ ExifTagValue.Make or
+ ExifTagValue.Model or
+ ExifTagValue.Software or
+ ExifTagValue.DateTime or
+ ExifTagValue.Artist or
+ ExifTagValue.HostComputer or
+ ExifTagValue.TargetPrinter or
+ ExifTagValue.XMP or
+ ExifTagValue.Rating or
+ ExifTagValue.RatingPercent or
+ ExifTagValue.ImageID or
+ ExifTagValue.Copyright or
+ ExifTagValue.MDLabName or
+ ExifTagValue.MDSampleInfo or
+ ExifTagValue.MDPrepDate or
+ ExifTagValue.MDPrepTime or
+ ExifTagValue.MDFileUnits or
+ ExifTagValue.SEMInfo or
+ ExifTagValue.XPTitle or
+ ExifTagValue.XPComment or
+ ExifTagValue.XPAuthor or
+ ExifTagValue.XPKeywords or
+ ExifTagValue.XPSubject => true,
+ _ => false,
+ };
private void ProcessMetadata(ExifProfile exifProfile)
{
@@ -149,9 +149,9 @@ private void ProcessMetadata(ExifProfile exifProfile)
}
}
- private void ProcessProfiles(ImageMetadata imageMetadata, ExifProfile exifProfile, XmpProfile xmpProfile)
+ private void ProcessProfiles(ImageMetadata imageMetadata, bool skipMetadata, ExifProfile exifProfile, XmpProfile xmpProfile)
{
- if (exifProfile != null && exifProfile.Parts != ExifParts.None)
+ if (!skipMetadata && (exifProfile != null && exifProfile.Parts != ExifParts.None))
{
foreach (IExifValue entry in exifProfile.Values)
{
@@ -167,13 +167,13 @@ private void ProcessProfiles(ImageMetadata imageMetadata, ExifProfile exifProfil
}
else
{
- exifProfile.RemoveValue(ExifTag.SubIFDOffset);
+ exifProfile?.RemoveValue(ExifTag.SubIFDOffset);
}
- if (imageMetadata.IptcProfile != null)
+ if (!skipMetadata && imageMetadata.IptcProfile != null)
{
imageMetadata.IptcProfile.UpdateData();
- var iptc = new ExifByteArray(ExifTagValue.IPTC, ExifDataType.Byte)
+ ExifByteArray iptc = new(ExifTagValue.IPTC, ExifDataType.Byte)
{
Value = imageMetadata.IptcProfile.Data
};
@@ -182,12 +182,12 @@ private void ProcessProfiles(ImageMetadata imageMetadata, ExifProfile exifProfil
}
else
{
- exifProfile.RemoveValue(ExifTag.IPTC);
+ exifProfile?.RemoveValue(ExifTag.IPTC);
}
if (imageMetadata.IccProfile != null)
{
- var icc = new ExifByteArray(ExifTagValue.IccProfile, ExifDataType.Undefined)
+ ExifByteArray icc = new(ExifTagValue.IccProfile, ExifDataType.Undefined)
{
Value = imageMetadata.IccProfile.ToByteArray()
};
@@ -196,12 +196,12 @@ private void ProcessProfiles(ImageMetadata imageMetadata, ExifProfile exifProfil
}
else
{
- exifProfile.RemoveValue(ExifTag.IccProfile);
+ exifProfile?.RemoveValue(ExifTag.IccProfile);
}
- if (xmpProfile != null)
+ if (!skipMetadata && xmpProfile != null)
{
- var xmp = new ExifByteArray(ExifTagValue.XMP, ExifDataType.Byte)
+ ExifByteArray xmp = new(ExifTagValue.XMP, ExifDataType.Byte)
{
Value = xmpProfile.Data
};
@@ -210,7 +210,7 @@ private void ProcessProfiles(ImageMetadata imageMetadata, ExifProfile exifProfil
}
else
{
- exifProfile.RemoveValue(ExifTag.XMP);
+ exifProfile?.RemoveValue(ExifTag.XMP);
}
}
}
@@ -273,29 +273,29 @@ public ImageFormatProcessor(TiffEncoderEntriesCollector collector)
public void Process(TiffEncoderCore encoder)
{
- var planarConfig = new ExifShort(ExifTagValue.PlanarConfiguration)
+ ExifShort planarConfig = new(ExifTagValue.PlanarConfiguration)
{
Value = (ushort)TiffPlanarConfiguration.Chunky
};
- var samplesPerPixel = new ExifLong(ExifTagValue.SamplesPerPixel)
+ ExifLong samplesPerPixel = new(ExifTagValue.SamplesPerPixel)
{
Value = GetSamplesPerPixel(encoder)
};
ushort[] bitsPerSampleValue = GetBitsPerSampleValue(encoder);
- var bitPerSample = new ExifShortArray(ExifTagValue.BitsPerSample)
+ ExifShortArray bitPerSample = new(ExifTagValue.BitsPerSample)
{
Value = bitsPerSampleValue
};
ushort compressionType = GetCompressionType(encoder);
- var compression = new ExifShort(ExifTagValue.Compression)
+ ExifShort compression = new(ExifTagValue.Compression)
{
Value = compressionType
};
- var photometricInterpretation = new ExifShort(ExifTagValue.PhotometricInterpretation)
+ ExifShort photometricInterpretation = new(ExifTagValue.PhotometricInterpretation)
{
Value = (ushort)encoder.PhotometricInterpretation
};
@@ -306,32 +306,25 @@ public void Process(TiffEncoderCore encoder)
this.Collector.AddOrReplace(compression);
this.Collector.AddOrReplace(photometricInterpretation);
- if (encoder.HorizontalPredictor == TiffPredictor.Horizontal)
+ if (encoder.HorizontalPredictor == TiffPredictor.Horizontal &&
+ (encoder.PhotometricInterpretation is TiffPhotometricInterpretation.Rgb or
+ TiffPhotometricInterpretation.PaletteColor or
+ TiffPhotometricInterpretation.BlackIsZero))
{
- if (encoder.PhotometricInterpretation == TiffPhotometricInterpretation.Rgb ||
- encoder.PhotometricInterpretation == TiffPhotometricInterpretation.PaletteColor ||
- encoder.PhotometricInterpretation == TiffPhotometricInterpretation.BlackIsZero)
- {
- var predictor = new ExifShort(ExifTagValue.Predictor) { Value = (ushort)TiffPredictor.Horizontal };
+ ExifShort predictor = new(ExifTagValue.Predictor) { Value = (ushort)TiffPredictor.Horizontal };
- this.Collector.AddOrReplace(predictor);
- }
+ this.Collector.AddOrReplace(predictor);
}
}
private static uint GetSamplesPerPixel(TiffEncoderCore encoder)
- {
- switch (encoder.PhotometricInterpretation)
+ => encoder.PhotometricInterpretation switch
{
- case TiffPhotometricInterpretation.PaletteColor:
- case TiffPhotometricInterpretation.BlackIsZero:
- case TiffPhotometricInterpretation.WhiteIsZero:
- return 1;
- case TiffPhotometricInterpretation.Rgb:
- default:
- return 3;
- }
- }
+ TiffPhotometricInterpretation.PaletteColor or
+ TiffPhotometricInterpretation.BlackIsZero or
+ TiffPhotometricInterpretation.WhiteIsZero => 1,
+ _ => 3,
+ };
private static ushort[] GetBitsPerSampleValue(TiffEncoderCore encoder)
{
@@ -342,10 +335,8 @@ private static ushort[] GetBitsPerSampleValue(TiffEncoderCore encoder)
{
return TiffConstants.BitsPerSample4Bit.ToArray();
}
- else
- {
- return TiffConstants.BitsPerSample8Bit.ToArray();
- }
+
+ return TiffConstants.BitsPerSample8Bit.ToArray();
case TiffPhotometricInterpretation.Rgb:
return TiffConstants.BitsPerSampleRgb8Bit.ToArray();
@@ -382,9 +373,9 @@ private static ushort GetCompressionType(TiffEncoderCore encoder)
// PackBits is allowed for all modes.
return (ushort)TiffCompression.PackBits;
case TiffCompression.Lzw:
- if (encoder.PhotometricInterpretation == TiffPhotometricInterpretation.Rgb ||
- encoder.PhotometricInterpretation == TiffPhotometricInterpretation.PaletteColor ||
- encoder.PhotometricInterpretation == TiffPhotometricInterpretation.BlackIsZero)
+ if (encoder.PhotometricInterpretation is TiffPhotometricInterpretation.Rgb or
+ TiffPhotometricInterpretation.PaletteColor or
+ TiffPhotometricInterpretation.BlackIsZero)
{
return (ushort)TiffCompression.Lzw;
}
diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs
index bb13137cef..a52d49a353 100644
--- a/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs
+++ b/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs
@@ -14,6 +14,7 @@ public static TiffBaseColorWriter Create(
TiffPhotometricInterpretation? photometricInterpretation,
ImageFrame image,
IQuantizer quantizer,
+ IPixelSamplingStrategy pixelSamplingStrategy,
MemoryAllocator memoryAllocator,
Configuration configuration,
TiffEncoderEntriesCollector entriesCollector,
@@ -23,7 +24,7 @@ public static TiffBaseColorWriter Create(
switch (photometricInterpretation)
{
case TiffPhotometricInterpretation.PaletteColor:
- return new TiffPaletteWriter(image, quantizer, memoryAllocator, configuration, entriesCollector, bitsPerPixel);
+ return new TiffPaletteWriter(image, quantizer, pixelSamplingStrategy, memoryAllocator, configuration, entriesCollector, bitsPerPixel);
case TiffPhotometricInterpretation.BlackIsZero:
case TiffPhotometricInterpretation.WhiteIsZero:
if (bitsPerPixel == 1)
diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs
index f8810d65ac..87118d6f76 100644
--- a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs
+++ b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs
@@ -17,19 +17,21 @@ internal sealed class TiffPaletteWriter : TiffBaseColorWriter
private readonly int maxColors;
private readonly int colorPaletteSize;
private readonly int colorPaletteBytes;
- private readonly IndexedImageFrame quantizedImage;
+ private readonly IndexedImageFrame quantizedFrame;
private IMemoryOwner indexedPixelsBuffer;
public TiffPaletteWriter(
- ImageFrame image,
+ ImageFrame frame,
IQuantizer quantizer,
+ IPixelSamplingStrategy pixelSamplingStrategy,
MemoryAllocator memoryAllocator,
Configuration configuration,
TiffEncoderEntriesCollector entriesCollector,
int bitsPerPixel)
- : base(image, memoryAllocator, configuration, entriesCollector)
+ : base(frame, memoryAllocator, configuration, entriesCollector)
{
DebugGuard.NotNull(quantizer, nameof(quantizer));
+ DebugGuard.NotNull(quantizer, nameof(pixelSamplingStrategy));
DebugGuard.NotNull(configuration, nameof(configuration));
DebugGuard.NotNull(entriesCollector, nameof(entriesCollector));
DebugGuard.MustBeBetweenOrEqualTo(bitsPerPixel, 4, 8, nameof(bitsPerPixel));
@@ -38,11 +40,15 @@ public TiffPaletteWriter(
this.maxColors = this.BitsPerPixel == 4 ? 16 : 256;
this.colorPaletteSize = this.maxColors * 3;
this.colorPaletteBytes = this.colorPaletteSize * 2;
- using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.Configuration, new QuantizerOptions()
- {
- MaxColors = this.maxColors
- });
- this.quantizedImage = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
+ using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(
+ this.Configuration,
+ new QuantizerOptions()
+ {
+ MaxColors = this.maxColors
+ });
+
+ frameQuantizer.BuildPalette(pixelSamplingStrategy, frame);
+ this.quantizedFrame = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
this.AddColorMapTag();
}
@@ -66,7 +72,7 @@ protected override void EncodeStrip(int y, int height, TiffBaseCompressor compre
int lastRow = y + height;
for (int row = y; row < lastRow; row++)
{
- ReadOnlySpan indexedPixelRow = this.quantizedImage.DangerousGetRowSpan(row);
+ ReadOnlySpan indexedPixelRow = this.quantizedFrame.DangerousGetRowSpan(row);
int idxPixels = 0;
for (int x = 0; x < halfWidth; x++)
{
@@ -93,7 +99,7 @@ protected override void EncodeStrip(int y, int height, TiffBaseCompressor compre
int indexedPixelsRowIdx = 0;
for (int row = y; row < lastRow; row++)
{
- ReadOnlySpan indexedPixelRow = this.quantizedImage.DangerousGetRowSpan(row);
+ ReadOnlySpan indexedPixelRow = this.quantizedFrame.DangerousGetRowSpan(row);
indexedPixelRow.CopyTo(indexedPixels.Slice(indexedPixelsRowIdx * width, width));
indexedPixelsRowIdx++;
}
@@ -105,7 +111,7 @@ protected override void EncodeStrip(int y, int height, TiffBaseCompressor compre
///
protected override void Dispose(bool disposing)
{
- this.quantizedImage?.Dispose();
+ this.quantizedFrame?.Dispose();
this.indexedPixelsBuffer?.Dispose();
}
@@ -114,7 +120,7 @@ private void AddColorMapTag()
using IMemoryOwner colorPaletteBuffer = this.MemoryAllocator.Allocate(this.colorPaletteBytes);
Span colorPalette = colorPaletteBuffer.GetSpan();
- ReadOnlySpan quantizedColors = this.quantizedImage.Palette.Span;
+ ReadOnlySpan quantizedColors = this.quantizedFrame.Palette.Span;
int quantizedColorBytes = quantizedColors.Length * 3 * 2;
// In the ColorMap, black is represented by 0, 0, 0 and white is represented by 65535, 65535, 65535.
@@ -126,7 +132,7 @@ private void AddColorMapTag()
// In a TIFF ColorMap, all the Red values come first, followed by the Green values,
// then the Blue values. Convert the quantized palette to this format.
- var palette = new ushort[this.colorPaletteSize];
+ ushort[] palette = new ushort[this.colorPaletteSize];
int paletteIdx = 0;
for (int i = 0; i < quantizedColors.Length; i++)
{
@@ -147,7 +153,7 @@ private void AddColorMapTag()
palette[paletteIdx++] = quantizedColorRgb48[i].B;
}
- var colorMap = new ExifShortArray(ExifTagValue.ColorMap)
+ ExifShortArray colorMap = new(ExifTagValue.ColorMap)
{
Value = palette
};
diff --git a/src/ImageSharp/Formats/Webp/AlphaEncoder.cs b/src/ImageSharp/Formats/Webp/AlphaEncoder.cs
index 1e60235e26..fc5580a4a7 100644
--- a/src/ImageSharp/Formats/Webp/AlphaEncoder.cs
+++ b/src/ImageSharp/Formats/Webp/AlphaEncoder.cs
@@ -24,10 +24,11 @@ internal class AlphaEncoder : IDisposable
/// The to encode from.
/// The global configuration.
/// The memory manager.
+ /// Whether to skip metadata encoding.
/// Indicates, if the data should be compressed with the lossless webp compression.
/// The size in bytes of the alpha data.
/// The encoded alpha data.
- public IMemoryOwner EncodeAlpha(Image image, Configuration configuration, MemoryAllocator memoryAllocator, bool compress, out int size)
+ public IMemoryOwner EncodeAlpha(Image image, Configuration configuration, MemoryAllocator memoryAllocator, bool skipMetadata, bool compress, out int size)
where TPixel : unmanaged, IPixel
{
int width = image.Width;
@@ -36,14 +37,15 @@ public IMemoryOwner EncodeAlpha(Image image, Configuration
if (compress)
{
- WebpEncodingMethod effort = WebpEncodingMethod.Default;
- int quality = 8 * (int)effort;
- using var lossLessEncoder = new Vp8LEncoder(
+ const WebpEncodingMethod effort = WebpEncodingMethod.Default;
+ const int quality = 8 * (int)effort;
+ using Vp8LEncoder lossLessEncoder = new(
memoryAllocator,
configuration,
width,
height,
quality,
+ skipMetadata,
effort,
WebpTransparentColorMode.Preserve,
false,
@@ -75,7 +77,7 @@ private static Image DispatchAlphaToGreen(Image image, S
{
int width = image.Width;
int height = image.Height;
- var alphaAsImage = new Image(width, height);
+ Image alphaAsImage = new(width, height);
for (int y = 0; y < height; y++)
{
diff --git a/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs b/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs
deleted file mode 100644
index bc316d08c7..0000000000
--- a/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-namespace SixLabors.ImageSharp.Formats.Webp;
-
-///
-/// Configuration options for use during webp encoding.
-///
-internal interface IWebpEncoderOptions
-{
- ///
- /// Gets the webp file format used. Either lossless or lossy.
- /// Defaults to lossy.
- ///
- WebpFileFormatType? FileFormat { get; }
-
- ///
- /// Gets the compression quality. Between 0 and 100.
- /// For lossy, 0 gives the smallest size and 100 the largest. For lossless,
- /// this parameter is the amount of effort put into the compression: 0 is the fastest but gives larger
- /// files compared to the slowest, but best, 100.
- /// Defaults to 75.
- ///
- int Quality { get; }
-
- ///
- /// Gets the encoding method to use. Its a quality/speed trade-off (0=fast, 6=slower-better).
- /// Defaults to 4.
- ///
- WebpEncodingMethod Method { get; }
-
- ///
- /// Gets a value indicating whether the alpha plane should be compressed with Webp lossless format.
- /// Defaults to true.
- ///
- bool UseAlphaCompression { get; }
-
- ///
- /// Gets the number of entropy-analysis passes (in [1..10]).
- /// Defaults to 1.
- ///
- int EntropyPasses { get; }
-
- ///
- /// Gets the amplitude of the spatial noise shaping. Spatial noise shaping (or sns for short) refers to a general collection of built-in algorithms
- /// used to decide which area of the picture should use relatively less bits, and where else to better transfer these bits.
- /// The possible range goes from 0 (algorithm is off) to 100 (the maximal effect).
- /// Defaults to 50.
- ///
- int SpatialNoiseShaping { get; }
-
- ///
- /// Gets the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering).
- /// A value of 0 will turn off any filtering. Higher value will increase the strength of the filtering process applied after decoding the picture.
- /// The higher the value the smoother the picture will appear.
- /// Typical values are usually in the range of 20 to 50.
- /// Defaults to 60.
- ///
- int FilterStrength { get; }
-
- ///
- /// Gets a value indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible
- /// RGB information for better compression.
- /// The default value is Clear.
- ///
- WebpTransparentColorMode TransparentColorMode { get; }
-
- ///
- /// Gets a value indicating whether near lossless mode should be used.
- /// This option adjusts pixel values to help compressibility, but has minimal impact on the visual quality.
- ///
- bool NearLossless { get; }
-
- ///
- /// Gets the quality of near-lossless image preprocessing. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default).
- /// The typical value is around 60. Note that lossy with -q 100 can at times yield better results.
- ///
- int NearLosslessQuality { get; }
-}
diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
index 3917c863b9..7f1f4f4e2f 100644
--- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
@@ -8,6 +8,8 @@
using SixLabors.ImageSharp.Formats.Webp.BitWriter;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
+using SixLabors.ImageSharp.Metadata.Profiles.Exif;
+using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Webp.Lossless;
@@ -67,6 +69,11 @@ internal class Vp8LEncoder : IDisposable
///
private readonly WebpTransparentColorMode transparentColorMode;
+ ///
+ /// Whether to skip metadata during encoding.
+ ///
+ private readonly bool skipMetadata;
+
///
/// Indicating whether near lossless mode should be used.
///
@@ -91,6 +98,7 @@ internal class Vp8LEncoder : IDisposable
/// The width of the input image.
/// The height of the input image.
/// The encoding quality.
+ /// Whether to skip metadata encoding.
/// Quality/speed trade-off (0=fast, 6=slower-better).
/// Flag indicating whether to preserve the exact RGB values under transparent area.
/// Otherwise, discard this invisible RGB information for better compression.
@@ -102,6 +110,7 @@ public Vp8LEncoder(
int width,
int height,
int quality,
+ bool skipMetadata,
WebpEncodingMethod method,
WebpTransparentColorMode transparentColorMode,
bool nearLossless,
@@ -113,6 +122,7 @@ public Vp8LEncoder(
this.memoryAllocator = memoryAllocator;
this.configuration = configuration;
this.quality = Numerics.Clamp(quality, 0, 100);
+ this.skipMetadata = skipMetadata;
this.method = method;
this.transparentColorMode = transparentColorMode;
this.nearLossless = nearLossless;
@@ -239,6 +249,9 @@ public void Encode(Image image, Stream stream)
ImageMetadata metadata = image.Metadata;
metadata.SyncProfiles();
+ ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
+ XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile;
+
// Convert image pixels to bgra array.
bool hasAlpha = this.ConvertPixelsToBgra(image, width, height);
@@ -252,7 +265,7 @@ public void Encode(Image image, Stream stream)
this.EncodeStream(image);
// Write bytes from the bitwriter buffer to the stream.
- this.bitWriter.WriteEncodedImageToStream(stream, metadata.ExifProfile, metadata.XmpProfile, metadata.IccProfile, (uint)width, (uint)height, hasAlpha);
+ this.bitWriter.WriteEncodedImageToStream(stream, exifProfile, xmpProfile, metadata.IccProfile, (uint)width, (uint)height, hasAlpha);
}
///
diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
index b15ccc052b..309e4175a0 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
@@ -6,6 +6,8 @@
using SixLabors.ImageSharp.Formats.Webp.BitWriter;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
+using SixLabors.ImageSharp.Metadata.Profiles.Exif;
+using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Webp.Lossy;
@@ -55,6 +57,11 @@ internal class Vp8Encoder : IDisposable
///
private Vp8BitWriter bitWriter;
+ ///
+ /// Whether to skip metadata during encoding.
+ ///
+ private readonly bool skipMetadata;
+
private readonly Vp8RdLevel rdOptLevel;
private int maxI4HeaderBits;
@@ -94,6 +101,7 @@ internal class Vp8Encoder : IDisposable
/// The width of the input image.
/// The height of the input image.
/// The encoding quality.
+ /// Whether to skip metadata encoding.
/// Quality/speed trade-off (0=fast, 6=slower-better).
/// Number of entropy-analysis passes (in [1..10]).
/// The filter the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering).
@@ -105,6 +113,7 @@ public Vp8Encoder(
int width,
int height,
int quality,
+ bool skipMetadata,
WebpEncodingMethod method,
int entropyPasses,
int filterStrength,
@@ -116,6 +125,7 @@ public Vp8Encoder(
this.Width = width;
this.Height = height;
this.quality = Numerics.Clamp(quality, 0, 100);
+ this.skipMetadata = skipMetadata;
this.method = method;
this.entropyPasses = Numerics.Clamp(entropyPasses, 1, 10);
this.filterStrength = Numerics.Clamp(filterStrength, 0, 100);
@@ -342,7 +352,7 @@ public void Encode(Image image, Stream stream)
if (hasAlpha)
{
// TODO: This can potentially run in an separate task.
- IMemoryOwner encodedAlphaData = alphaEncoder.EncodeAlpha(image, this.configuration, this.memoryAllocator, this.alphaCompression, out alphaDataSize);
+ IMemoryOwner encodedAlphaData = alphaEncoder.EncodeAlpha(image, this.configuration, this.memoryAllocator, this.skipMetadata, this.alphaCompression, out alphaDataSize);
alphaData = encodedAlphaData.GetSpan();
if (alphaDataSize < pixelCount)
{
@@ -384,10 +394,14 @@ public void Encode(Image image, Stream stream)
// Write bytes from the bitwriter buffer to the stream.
ImageMetadata metadata = image.Metadata;
metadata.SyncProfiles();
+
+ ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
+ XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile;
+
this.bitWriter.WriteEncodedImageToStream(
stream,
- metadata.ExifProfile,
- metadata.XmpProfile,
+ exifProfile,
+ xmpProfile,
metadata.IccProfile,
(uint)width,
(uint)height,
diff --git a/src/ImageSharp/Formats/Webp/WebpEncoder.cs b/src/ImageSharp/Formats/Webp/WebpEncoder.cs
index b6a45555d8..359128254f 100644
--- a/src/ImageSharp/Formats/Webp/WebpEncoder.cs
+++ b/src/ImageSharp/Formats/Webp/WebpEncoder.cs
@@ -2,58 +2,94 @@
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Webp;
///
/// Image encoder for writing an image to a stream in the Webp format.
///
-public sealed class WebpEncoder : IImageEncoder, IWebpEncoderOptions
+public sealed class WebpEncoder : ImageEncoder
{
- ///
- public WebpFileFormatType? FileFormat { get; set; }
+ ///
+ /// Gets the webp file format used. Either lossless or lossy.
+ /// Defaults to lossy.
+ ///
+ public WebpFileFormatType? FileFormat { get; init; }
- ///
- public int Quality { get; set; } = 75;
+ ///
+ /// Gets the compression quality. Between 0 and 100.
+ /// For lossy, 0 gives the smallest size and 100 the largest. For lossless,
+ /// this parameter is the amount of effort put into the compression: 0 is the fastest but gives larger
+ /// files compared to the slowest, but best, 100.
+ /// Defaults to 75.
+ ///
+ public int Quality { get; init; } = 75;
- ///
- public WebpEncodingMethod Method { get; set; } = WebpEncodingMethod.Default;
+ ///
+ /// Gets the encoding method to use. Its a quality/speed trade-off (0=fast, 6=slower-better).
+ /// Defaults to 4.
+ ///
+ public WebpEncodingMethod Method { get; init; } = WebpEncodingMethod.Default;
- ///
- public bool UseAlphaCompression { get; set; } = true;
+ ///
+ /// Gets a value indicating whether the alpha plane should be compressed with Webp lossless format.
+ /// Defaults to true.
+ ///
+ public bool UseAlphaCompression { get; init; } = true;
- ///
- public int EntropyPasses { get; set; } = 1;
+ ///
+ /// Gets the number of entropy-analysis passes (in [1..10]).
+ /// Defaults to 1.
+ ///
+ public int EntropyPasses { get; init; } = 1;
- ///
- public int SpatialNoiseShaping { get; set; } = 50;
+ ///
+ /// Gets the amplitude of the spatial noise shaping. Spatial noise shaping (or sns for short) refers to a general collection of built-in algorithms
+ /// used to decide which area of the picture should use relatively less bits, and where else to better transfer these bits.
+ /// The possible range goes from 0 (algorithm is off) to 100 (the maximal effect).
+ /// Defaults to 50.
+ ///
+ public int SpatialNoiseShaping { get; init; } = 50;
- ///
- public int FilterStrength { get; set; } = 60;
+ ///
+ /// Gets the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering).
+ /// A value of 0 will turn off any filtering. Higher value will increase the strength of the filtering process applied after decoding the picture.
+ /// The higher the value the smoother the picture will appear.
+ /// Typical values are usually in the range of 20 to 50.
+ /// Defaults to 60.
+ ///
+ public int FilterStrength { get; init; } = 60;
- ///
- public WebpTransparentColorMode TransparentColorMode { get; set; } = WebpTransparentColorMode.Clear;
+ ///
+ /// Gets a value indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible
+ /// RGB information for better compression.
+ /// The default value is Clear.
+ ///
+ public WebpTransparentColorMode TransparentColorMode { get; init; } = WebpTransparentColorMode.Clear;
- ///
- public bool NearLossless { get; set; }
+ ///
+ /// Gets a value indicating whether near lossless mode should be used.
+ /// This option adjusts pixel values to help compressibility, but has minimal impact on the visual quality.
+ ///
+ public bool NearLossless { get; init; }
- ///
- public int NearLosslessQuality { get; set; } = 100;
+ ///
+ /// Gets the quality of near-lossless image preprocessing. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default).
+ /// The typical value is around 60. Note that lossy with -q 100 can at times yield better results.
+ ///
+ public int NearLosslessQuality { get; init; } = 100;
///
- public void Encode(Image image, Stream stream)
- where TPixel : unmanaged, IPixel
+ public override void Encode(Image image, Stream stream)
{
- var encoder = new WebpEncoderCore(this, image.GetMemoryAllocator());
+ WebpEncoderCore encoder = new(this, image.GetMemoryAllocator());
encoder.Encode(image, stream);
}
///
- public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel
+ public override Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken)
{
- var encoder = new WebpEncoderCore(this, image.GetMemoryAllocator());
+ WebpEncoderCore encoder = new(this, image.GetMemoryAllocator());
return encoder.EncodeAsync(image, stream, cancellationToken);
}
}
diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
index f9ceaf3098..e8ee316d88 100644
--- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
+++ b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
@@ -56,6 +56,11 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals
///
private readonly WebpTransparentColorMode transparentColorMode;
+ ///
+ /// Whether to skip metadata during encoding.
+ ///
+ private readonly bool skipMetadata;
+
///
/// Indicating whether near lossless mode should be used.
///
@@ -80,21 +85,22 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals
///
/// Initializes a new instance of the class.
///
- /// The encoder options.
+ /// The encoder with options.
/// The memory manager.
- public WebpEncoderCore(IWebpEncoderOptions options, MemoryAllocator memoryAllocator)
+ public WebpEncoderCore(WebpEncoder encoder, MemoryAllocator memoryAllocator)
{
this.memoryAllocator = memoryAllocator;
- this.alphaCompression = options.UseAlphaCompression;
- this.fileFormat = options.FileFormat;
- this.quality = options.Quality;
- this.method = options.Method;
- this.entropyPasses = options.EntropyPasses;
- this.spatialNoiseShaping = options.SpatialNoiseShaping;
- this.filterStrength = options.FilterStrength;
- this.transparentColorMode = options.TransparentColorMode;
- this.nearLossless = options.NearLossless;
- this.nearLosslessQuality = options.NearLosslessQuality;
+ this.alphaCompression = encoder.UseAlphaCompression;
+ this.fileFormat = encoder.FileFormat;
+ this.quality = encoder.Quality;
+ this.method = encoder.Method;
+ this.entropyPasses = encoder.EntropyPasses;
+ this.spatialNoiseShaping = encoder.SpatialNoiseShaping;
+ this.filterStrength = encoder.FilterStrength;
+ this.transparentColorMode = encoder.TransparentColorMode;
+ this.skipMetadata = encoder.SkipMetadata;
+ this.nearLossless = encoder.NearLossless;
+ this.nearLosslessQuality = encoder.NearLosslessQuality;
}
///
@@ -124,12 +130,13 @@ public void Encode(Image image, Stream stream, CancellationToken
if (lossless)
{
- using var enc = new Vp8LEncoder(
+ using Vp8LEncoder enc = new(
this.memoryAllocator,
this.configuration,
image.Width,
image.Height,
this.quality,
+ this.skipMetadata,
this.method,
this.transparentColorMode,
this.nearLossless,
@@ -138,12 +145,13 @@ public void Encode(Image image, Stream stream, CancellationToken
}
else
{
- using var enc = new Vp8Encoder(
+ using Vp8Encoder enc = new(
this.memoryAllocator,
this.configuration,
image.Width,
image.Height,
this.quality,
+ this.skipMetadata,
this.method,
this.entropyPasses,
this.filterStrength,
diff --git a/src/ImageSharp/ImageExtensions.Internal.cs b/src/ImageSharp/ImageExtensions.Internal.cs
index 76c9b85224..6ec95a564a 100644
--- a/src/ImageSharp/ImageExtensions.Internal.cs
+++ b/src/ImageSharp/ImageExtensions.Internal.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Memory;
@@ -12,9 +12,9 @@ namespace SixLabors.ImageSharp;
public static partial class ImageExtensions
{
///
- /// Locks the image providing access to the pixels.
+ /// Provides access to the image pixels.
///
- /// It is imperative that the accessor is correctly disposed off after use.
+ /// It is imperative that the accessor is correctly disposed of after use.
///
///
/// The type of the pixel.
@@ -24,7 +24,5 @@ public static partial class ImageExtensions
///
internal static Buffer2D GetRootFramePixelBuffer(this Image image)
where TPixel : unmanaged, IPixel
- {
- return image.Frames.RootFrame.PixelBuffer;
- }
+ => image.Frames.RootFrame.PixelBuffer;
}
diff --git a/src/ImageSharp/ImageFrameCollection.cs b/src/ImageSharp/ImageFrameCollection.cs
index 1632134260..cc2b430ff1 100644
--- a/src/ImageSharp/ImageFrameCollection.cs
+++ b/src/ImageSharp/ImageFrameCollection.cs
@@ -180,7 +180,7 @@ public void Dispose()
}
///
- public IEnumerator GetEnumerator()
+ IEnumerator IEnumerable.GetEnumerator()
{
this.EnsureNotDisposed();
@@ -188,7 +188,7 @@ public IEnumerator GetEnumerator()
}
///
- IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator();
///
/// Throws if the image frame is disposed.
@@ -208,7 +208,7 @@ protected void EnsureNotDisposed()
protected abstract void Dispose(bool disposing);
///
- /// Implements .
+ /// Implements .
///
/// The enumerator.
protected abstract IEnumerator NonGenericGetEnumerator();
diff --git a/src/ImageSharp/ImageFrameCollection{TPixel}.cs b/src/ImageSharp/ImageFrameCollection{TPixel}.cs
index 60b5c6d6ad..faa83b59e2 100644
--- a/src/ImageSharp/ImageFrameCollection{TPixel}.cs
+++ b/src/ImageSharp/ImageFrameCollection{TPixel}.cs
@@ -168,7 +168,7 @@ public ImageFrame AddFrame(ReadOnlySpan source)
{
this.EnsureNotDisposed();
- var frame = ImageFrame.LoadPixelData(
+ ImageFrame frame = ImageFrame.LoadPixelData(
this.parent.GetConfiguration(),
source,
this.RootFrame.Width,
@@ -298,7 +298,7 @@ public override void MoveFrame(int sourceIndex, int destinationIndex)
{
this.EnsureNotDisposed();
- var frame = new ImageFrame(
+ ImageFrame frame = new(
this.parent.GetConfiguration(),
this.RootFrame.Width,
this.RootFrame.Height);
@@ -364,7 +364,7 @@ protected override ImageFrame NonGenericCreateFrame(Color backgroundColor) =>
///
public ImageFrame CreateFrame(TPixel backgroundColor)
{
- var frame = new ImageFrame(
+ ImageFrame frame = new(
this.parent.GetConfiguration(),
this.RootFrame.Width,
this.RootFrame.Height,
@@ -374,10 +374,15 @@ public ImageFrame CreateFrame(TPixel backgroundColor)
}
///
- IEnumerator> IEnumerable>.GetEnumerator() => this.frames.GetEnumerator();
+ public IEnumerator> GetEnumerator()
+ {
+ this.EnsureNotDisposed();
+
+ return this.frames.GetEnumerator();
+ }
///
- IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this.frames).GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
private void ValidateFrame(ImageFrame frame)
{
@@ -408,7 +413,7 @@ protected override void Dispose(bool disposing)
private ImageFrame CopyNonCompatibleFrame(ImageFrame source)
{
- var result = new ImageFrame(
+ ImageFrame result = new(
this.parent.GetConfiguration(),
source.Size(),
source.Metadata.DeepClone());
diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs
index 814843013e..4dab82a024 100644
--- a/src/ImageSharp/Image{TPixel}.cs
+++ b/src/ImageSharp/Image{TPixel}.cs
@@ -80,9 +80,7 @@ public Image(int width, int height)
/// The images metadata.
internal Image(Configuration configuration, int width, int height, ImageMetadata metadata)
: base(configuration, PixelTypeInfo.Create(), metadata, width, height)
- {
- this.frames = new ImageFrameCollection(this, width, height, default(TPixel));
- }
+ => this.frames = new ImageFrameCollection(this, width, height, default(TPixel));
///
/// Initializes a new instance of the class
@@ -115,9 +113,7 @@ internal Image(
int height,
ImageMetadata metadata)
: base(configuration, PixelTypeInfo.Create(), metadata, width, height)
- {
- this.frames = new ImageFrameCollection(this, width, height, memoryGroup);
- }
+ => this.frames = new ImageFrameCollection(this, width, height, memoryGroup);
///
/// Initializes a new instance of the class
@@ -135,9 +131,7 @@ internal Image(
TPixel backgroundColor,
ImageMetadata metadata)
: base(configuration, PixelTypeInfo.Create(), metadata, width, height)
- {
- this.frames = new ImageFrameCollection(this, width, height, backgroundColor);
- }
+ => this.frames = new ImageFrameCollection(this, width, height, backgroundColor);
///
/// Initializes a new instance of the class
@@ -148,9 +142,7 @@ internal Image(
/// The frames that will be owned by this image instance.
internal Image(Configuration configuration, ImageMetadata metadata, IEnumerable> frames)
: base(configuration, PixelTypeInfo.Create(), metadata, ValidateFramesAndGetSize(frames))
- {
- this.frames = new ImageFrameCollection(this, frames);
- }
+ => this.frames = new ImageFrameCollection(this, frames);
///
protected override ImageFrameCollection NonGenericFrameCollection => this.Frames;
@@ -181,7 +173,7 @@ internal Image(Configuration configuration, ImageMetadata metadata, IEnumerable<
/// Thrown when the provided (x,y) coordinates are outside the image boundary.
public TPixel this[int x, int y]
{
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
this.EnsureNotDisposed();
@@ -190,7 +182,7 @@ internal Image(Configuration configuration, ImageMetadata metadata, IEnumerable<
return this.PixelSourceUnsafe.PixelBuffer.GetElementUnsafe(x, y);
}
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
this.EnsureNotDisposed();
@@ -212,7 +204,7 @@ public void ProcessPixelRows(PixelAccessorAction processPixels)
try
{
- var accessor = new PixelAccessor(buffer);
+ PixelAccessor accessor = new(buffer);
processPixels(accessor);
}
finally
@@ -243,8 +235,8 @@ public void ProcessPixelRows(
try
{
- var accessor1 = new PixelAccessor(buffer1);
- var accessor2 = new PixelAccessor(buffer2);
+ PixelAccessor accessor1 = new(buffer1);
+ PixelAccessor accessor2 = new(buffer2);
processPixels(accessor1, accessor2);
}
finally
@@ -283,9 +275,9 @@ public void ProcessPixelRows(
try
{
- var accessor1 = new PixelAccessor(buffer1);
- var accessor2 = new PixelAccessor(buffer2);
- var accessor3 = new PixelAccessor(buffer3);
+ PixelAccessor accessor1 = new(buffer1);
+ PixelAccessor accessor2 = new(buffer2);
+ PixelAccessor accessor3 = new(buffer3);
processPixels(accessor1, accessor2, accessor3);
}
finally
@@ -348,7 +340,7 @@ public Image Clone(Configuration configuration)
{
this.EnsureNotDisposed();
- var clonedFrames = new ImageFrame[this.frames.Count];
+ ImageFrame[] clonedFrames = new ImageFrame[this.frames.Count];
for (int i = 0; i < clonedFrames.Length; i++)
{
clonedFrames[i] = this.frames[i].Clone(configuration);
@@ -367,7 +359,7 @@ public override Image CloneAs(Configuration configuration)
{
this.EnsureNotDisposed();
- var clonedFrames = new ImageFrame[this.frames.Count];
+ ImageFrame[] clonedFrames = new ImageFrame[this.frames.Count];
for (int i = 0; i < clonedFrames.Length; i++)
{
clonedFrames[i] = this.frames[i].CloneAs(configuration);
@@ -444,7 +436,7 @@ private static Size ValidateFramesAndGetSize(IEnumerable> fra
return rootSize;
}
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void VerifyCoords(int x, int y)
{
if ((uint)x >= (uint)this.Width)
@@ -458,9 +450,6 @@ private void VerifyCoords(int x, int y)
}
}
- [MethodImpl(InliningOptions.ColdPath)]
private static void ThrowArgumentOutOfRangeException(string paramName)
- {
- throw new ArgumentOutOfRangeException(paramName);
- }
+ => throw new ArgumentOutOfRangeException(paramName);
}
diff --git a/src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs b/src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs
index 9da1d98e3b..d5e7a99677 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs
@@ -79,14 +79,15 @@ public IEnumerable> EnumeratePixelRegions(Image GetRow(int pos)
}
}
}
+
+ ///
+ public IEnumerable> EnumeratePixelRegions(ImageFrame frame)
+ where TPixel : unmanaged, IPixel
+ {
+ long maximumPixels = Math.Min(this.MaximumPixels, (long)frame.Width * frame.Height);
+ long maxNumberOfRows = maximumPixels / frame.Width;
+ long totalNumberOfRows = frame.Height;
+
+ if (totalNumberOfRows <= maxNumberOfRows)
+ {
+ yield return frame.PixelBuffer.GetRegion();
+ }
+ else
+ {
+ double r = maxNumberOfRows / (double)totalNumberOfRows;
+
+ // Use a rough approximation to make sure we don't leave out large contiguous regions:
+ if (maxNumberOfRows > 200)
+ {
+ r = Math.Round(r, 2);
+ }
+ else
+ {
+ r = Math.Round(r, 1);
+ }
+
+ r = Math.Max(this.MinimumScanRatio, r); // always visit the minimum defined portion of the image.
+
+ Rational ratio = new(r);
+
+ int denom = (int)ratio.Denominator;
+ int num = (int)ratio.Numerator;
+ DebugGuard.MustBeGreaterThan(denom, 0, "Denominator must be greater than zero.");
+
+ for (int pos = 0; pos < totalNumberOfRows; pos++)
+ {
+ int subPos = (int)((uint)pos % (uint)denom);
+ if (subPos < num)
+ {
+ yield return GetRow(pos);
+ }
+ }
+
+ Buffer2DRegion GetRow(int pos)
+ {
+ int y = pos % frame.Height;
+ return frame.PixelBuffer.GetRegion(0, y, frame.Width, 1);
+ }
+ }
+ }
}
diff --git a/src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs b/src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs
index 580227c2d7..150f785b38 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs
@@ -20,4 +20,11 @@ public IEnumerable> EnumeratePixelRegions(Image
+ public IEnumerable> EnumeratePixelRegions(ImageFrame frame)
+ where TPixel : unmanaged, IPixel
+ {
+ yield return frame.PixelBuffer.GetRegion();
+ }
}
diff --git a/src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs b/src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs
index ab118b55d4..55d56679ed 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs
@@ -7,16 +7,25 @@
namespace SixLabors.ImageSharp.Processing.Processors.Quantization;
///
-/// Provides an abstraction to enumerate pixel regions within a multi-framed .
+/// Provides an abstraction to enumerate pixel regions for sampling within .
///
public interface IPixelSamplingStrategy
{
///
- /// Enumerates pixel regions within the image as .
+ /// Enumerates pixel regions for all frames within the image as .
///
/// The image.
/// The pixel type.
/// An enumeration of pixel regions.
IEnumerable> EnumeratePixelRegions(Image image)
where TPixel : unmanaged, IPixel;
+
+ ///
+ /// Enumerates pixel regions within a single image frame as .
+ ///
+ /// The image frame.
+ /// The pixel type.
+ /// An enumeration of pixel regions.
+ IEnumerable> EnumeratePixelRegions(ImageFrame frame)
+ where TPixel : unmanaged, IPixel;
}
diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs
index 04e8124037..167cf91282 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs
@@ -50,7 +50,7 @@ public static IndexedImageFrame BuildPaletteAndQuantizeFrame(
Guard.NotNull(quantizer, nameof(quantizer));
Guard.NotNull(source, nameof(source));
- var interest = Rectangle.Intersect(source.Bounds(), bounds);
+ Rectangle interest = Rectangle.Intersect(source.Bounds(), bounds);
Buffer2DRegion region = source.PixelBuffer.GetRegion(interest);
// Collect the palette. Required before the second pass runs.
@@ -77,9 +77,9 @@ public static IndexedImageFrame QuantizeFrame(
where TPixel : unmanaged, IPixel
{
Guard.NotNull(source, nameof(source));
- var interest = Rectangle.Intersect(source.Bounds(), bounds);
+ Rectangle interest = Rectangle.Intersect(source.Bounds(), bounds);
- var destination = new IndexedImageFrame(
+ IndexedImageFrame destination = new(
quantizer.Configuration,
interest.Width,
interest.Height,
@@ -99,13 +99,39 @@ public static IndexedImageFrame QuantizeFrame(
return destination;
}
- internal static void BuildPalette(
+ ///
+ /// Adds colors to the quantized palette from the given pixel regions.
+ ///
+ /// The pixel format.
+ /// The pixel specific quantizer.
+ /// The pixel sampling strategy.
+ /// The source image to sample from.
+ public static void BuildPalette(
+ this IQuantizer quantizer,
+ IPixelSamplingStrategy pixelSamplingStrategy,
+ Image source)
+ where TPixel : unmanaged, IPixel
+ {
+ foreach (Buffer2DRegion region in pixelSamplingStrategy.EnumeratePixelRegions(source))
+ {
+ quantizer.AddPaletteColors(region);
+ }
+ }
+
+ ///
+ /// Adds colors to the quantized palette from the given pixel regions.
+ ///
+ /// The pixel format.
+ /// The pixel specific quantizer.
+ /// The pixel sampling strategy.
+ /// The source image frame to sample from.
+ public static void BuildPalette(
this IQuantizer quantizer,
IPixelSamplingStrategy pixelSamplingStrategy,
- Image image)
+ ImageFrame source)
where TPixel : unmanaged, IPixel
{
- foreach (Buffer2DRegion region in pixelSamplingStrategy.EnumeratePixelRegions(image))
+ foreach (Buffer2DRegion region in pixelSamplingStrategy.EnumeratePixelRegions(source))
{
quantizer.AddPaletteColors(region);
}
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 void WorksWithDifferentConfigurations(
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 void DrawImageOfDifferentPixelType(TestImageProvider prov
{
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 void DrawImageOfDifferentPixelType(TestImageProvider prov
[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