Skip to content

Commit

Permalink
Merge pull request #2269 from SixLabors/js/encoder-normalization
Browse files Browse the repository at this point in the history
Normalize and cleanup encoders
  • Loading branch information
JimBobSquarePants committed Oct 28, 2022
2 parents 451a713 + 864735f commit b404826
Show file tree
Hide file tree
Showing 51 changed files with 1,430 additions and 1,610 deletions.
15 changes: 10 additions & 5 deletions src/ImageSharp/Advanced/AotCompilerTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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!!!
/// </remarks>
/// <exception cref="InvalidOperationException">
/// This method is used for AOT code generation only. Do not call it at runtime.
/// </exception>
[Preserve]
private static void SeedPixelFormats()
{
Expand Down Expand Up @@ -487,8 +490,10 @@ private static void AotCompileQuantizers<TPixel>()
private static void AotCompilePixelSamplingStrategys<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
default(DefaultPixelSamplingStrategy).EnumeratePixelRegions<TPixel>(default);
default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions<TPixel>(default);
default(DefaultPixelSamplingStrategy).EnumeratePixelRegions(default(Image<TPixel>));
default(DefaultPixelSamplingStrategy).EnumeratePixelRegions(default(ImageFrame<TPixel>));
default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions(default(Image<TPixel>));
default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions(default(ImageFrame<TPixel>));
}

/// <summary>
Expand All @@ -513,13 +518,13 @@ private static void AotCompileDithers<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
where TDither : struct, IDither
{
var octree = default(OctreeQuantizer<TPixel>);
OctreeQuantizer<TPixel> octree = default;
default(TDither).ApplyQuantizationDither<OctreeQuantizer<TPixel>, TPixel>(ref octree, default, default, default);

var palette = default(PaletteQuantizer<TPixel>);
PaletteQuantizer<TPixel> palette = default;
default(TDither).ApplyQuantizationDither<PaletteQuantizer<TPixel>, TPixel>(ref palette, default, default, default);

var wu = default(WuQuantizer<TPixel>);
WuQuantizer<TPixel> wu = default;
default(TDither).ApplyQuantizationDither<WuQuantizer<TPixel>, TPixel>(ref wu, default, default, default);
default(TDither).ApplyPaletteDither<PaletteDitherProcessor<TPixel>.DitherProcessor, TPixel>(default, default, default);
}
Expand Down
28 changes: 9 additions & 19 deletions src/ImageSharp/Formats/Bmp/BmpEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <summary>
/// Image encoder for writing an image to a stream as a Windows bitmap.
/// </summary>
public sealed class BmpEncoder : IImageEncoder, IBmpEncoderOptions
public sealed class BmpEncoder : QuantizingImageEncoder
{
/// <summary>
/// Gets or sets the number of bits per pixel.
/// Gets the number of bits per pixel.
/// </summary>
public BmpBitsPerPixel? BitsPerPixel { get; set; }
public BmpBitsPerPixel? BitsPerPixel { get; init; }

/// <summary>
/// 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.
/// </summary>
public bool SupportTransparency { get; set; }

/// <summary>
/// Gets or sets the quantizer for reducing the color count for 8-Bit images.
/// Defaults to Wu Quantizer.
/// </summary>
public IQuantizer Quantizer { get; set; }
public bool SupportTransparency { get; init; }

/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
public override void Encode<TPixel>(Image<TPixel> image, Stream stream)
{
var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator());
BmpEncoderCore encoder = new(this, image.GetMemoryAllocator());
encoder.Encode(image, stream);
}

/// <inheritdoc/>
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
public override Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator());
BmpEncoderCore encoder = new(this, image.GetMemoryAllocator());
return encoder.EncodeAsync(image, stream, cancellationToken);
}
}
60 changes: 38 additions & 22 deletions src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -92,17 +91,23 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// </summary>
private readonly IQuantizer quantizer;

/// <summary>
/// The pixel sampling strategy for quantization.
/// </summary>
private readonly IPixelSamplingStrategy pixelSamplingStrategy;

/// <summary>
/// Initializes a new instance of the <see cref="BmpEncoderCore"/> class.
/// </summary>
/// <param name="options">The encoder options.</param>
/// <param name="encoder">The encoder with options.</param>
/// <param name="memoryAllocator">The memory manager.</param>
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;
}

/// <summary>
Expand Down Expand Up @@ -159,7 +164,7 @@ public void Encode<TPixel>(Image<TPixel> 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();
Expand Down Expand Up @@ -311,10 +316,10 @@ private void WriteBitmapInfoHeader(Stream stream, BmpInfoHeader infoHeader, Span
/// <param name="image">
/// The <see cref="ImageFrame{TPixel}"/> containing pixel data.
/// </param>
private void WriteImage<TPixel>(Stream stream, ImageFrame<TPixel> image)
private void WriteImage<TPixel>(Stream stream, Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2D<TPixel> pixels = image.PixelBuffer;
Buffer2D<TPixel> pixels = image.Frames.RootFrame.PixelBuffer;
switch (this.bitsPerPixel)
{
case BmpBitsPerPixel.Pixel32:
Expand Down Expand Up @@ -433,8 +438,8 @@ private void Write16BitPixelData<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write8BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
/// <param name="image"> The <see cref="Image{TPixel}"/> containing pixel data.</param>
private void Write8BitPixelData<TPixel>(Stream stream, Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
bool isL8 = typeof(TPixel) == typeof(L8);
Expand All @@ -456,13 +461,15 @@ private void Write8BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
/// <param name="image"> The <see cref="Image{TPixel}"/> containing pixel data.</param>
/// <param name="colorPalette">A byte span of size 1024 for the color palette.</param>
private void Write8BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette)
private void Write8BitColor<TPixel>(Stream stream, Image<TPixel> image, Span<byte> colorPalette)
where TPixel : unmanaged, IPixel<TPixel>
{
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration);
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());

frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);
using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());

ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span;
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
Expand All @@ -486,7 +493,7 @@ private void Write8BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image, Spa
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
/// <param name="colorPalette">A byte span of size 1024 for the color palette.</param>
private void Write8BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette)
private void Write8BitPixelData<TPixel>(Stream stream, Image<TPixel> image, Span<byte> colorPalette)
where TPixel : unmanaged, IPixel<TPixel>
{
// Create a color palette with 256 different gray values.
Expand All @@ -503,7 +510,7 @@ private void Write8BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image,
}

stream.Write(colorPalette);
Buffer2D<TPixel> imageBuffer = image.PixelBuffer;
Buffer2D<TPixel> imageBuffer = image.GetRootFramePixelBuffer();
for (int y = image.Height - 1; y >= 0; y--)
{
ReadOnlySpan<TPixel> inputPixelRow = imageBuffer.DangerousGetRowSpan(y);
Expand All @@ -523,14 +530,17 @@ private void Write8BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image,
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write4BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
private void Write4BitPixelData<TPixel>(Stream stream, Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
{
MaxColors = 16
});
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());

frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);

using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize4Bit, AllocationOptions.Clean);

Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
Expand Down Expand Up @@ -567,14 +577,17 @@ private void Write4BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write2BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
private void Write2BitPixelData<TPixel>(Stream stream, Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
{
MaxColors = 4
});
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());

frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);

using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize2Bit, AllocationOptions.Clean);

Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
Expand Down Expand Up @@ -620,14 +633,17 @@ private void Write2BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write1BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
private void Write1BitPixelData<TPixel>(Stream stream, Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
{
MaxColors = 2
});
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());

frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);

using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize1Bit, AllocationOptions.Clean);

Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
Expand Down
30 changes: 0 additions & 30 deletions src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs

This file was deleted.

31 changes: 7 additions & 24 deletions src/ImageSharp/Formats/Gif/GifEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <summary>
/// Image encoder for writing image data to a stream in gif format.
/// </summary>
public sealed class GifEncoder : IImageEncoder, IGifEncoderOptions
public sealed class GifEncoder : QuantizingImageEncoder
{
/// <summary>
/// Gets or sets the quantizer for reducing the color count.
/// Defaults to the <see cref="OctreeQuantizer"/>
/// Gets the color table mode: Global or local.
/// </summary>
public IQuantizer Quantizer { get; set; } = KnownQuantizers.Octree;

/// <summary>
/// Gets or sets the color table mode: Global or local.
/// </summary>
public GifColorTableMode? ColorTableMode { get; set; }

/// <summary>
/// Gets or sets the <see cref="IPixelSamplingStrategy"/> used for quantization
/// when building a global color table in case of <see cref="GifColorTableMode.Global"/>.
/// </summary>
public IPixelSamplingStrategy GlobalPixelSamplingStrategy { get; set; } = new DefaultPixelSamplingStrategy();
public GifColorTableMode? ColorTableMode { get; init; }

/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
public override void Encode<TPixel>(Image<TPixel> image, Stream stream)
{
var encoder = new GifEncoderCore(image.GetConfiguration(), this);
GifEncoderCore encoder = new(image.GetConfiguration(), this);
encoder.Encode(image, stream);
}

/// <inheritdoc/>
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
public override Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
var encoder = new GifEncoderCore(image.GetConfiguration(), this);
GifEncoderCore encoder = new(image.GetConfiguration(), this);
return encoder.EncodeAsync(image, stream, cancellationToken);
}
}
Loading

0 comments on commit b404826

Please sign in to comment.