Skip to content

Commit b404826

Browse files
Merge pull request #2269 from SixLabors/js/encoder-normalization
Normalize and cleanup encoders
2 parents 451a713 + 864735f commit b404826

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1430
-1610
lines changed

src/ImageSharp/Advanced/AotCompilerTools.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ internal static class AotCompilerTools
5757
/// If you are getting the above error, you need to call this method, which will pre-seed the AoT compiler with the
5858
/// necessary methods to complete the SaveAsGif call. That's it, otherwise you should NEVER need this method!!!
5959
/// </remarks>
60+
/// <exception cref="InvalidOperationException">
61+
/// This method is used for AOT code generation only. Do not call it at runtime.
62+
/// </exception>
6063
[Preserve]
6164
private static void SeedPixelFormats()
6265
{
@@ -487,8 +490,10 @@ private static void AotCompileQuantizer<TPixel, TQuantizer>()
487490
private static void AotCompilePixelSamplingStrategys<TPixel>()
488491
where TPixel : unmanaged, IPixel<TPixel>
489492
{
490-
default(DefaultPixelSamplingStrategy).EnumeratePixelRegions<TPixel>(default);
491-
default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions<TPixel>(default);
493+
default(DefaultPixelSamplingStrategy).EnumeratePixelRegions(default(Image<TPixel>));
494+
default(DefaultPixelSamplingStrategy).EnumeratePixelRegions(default(ImageFrame<TPixel>));
495+
default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions(default(Image<TPixel>));
496+
default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions(default(ImageFrame<TPixel>));
492497
}
493498

494499
/// <summary>
@@ -513,13 +518,13 @@ private static void AotCompileDither<TPixel, TDither>()
513518
where TPixel : unmanaged, IPixel<TPixel>
514519
where TDither : struct, IDither
515520
{
516-
var octree = default(OctreeQuantizer<TPixel>);
521+
OctreeQuantizer<TPixel> octree = default;
517522
default(TDither).ApplyQuantizationDither<OctreeQuantizer<TPixel>, TPixel>(ref octree, default, default, default);
518523

519-
var palette = default(PaletteQuantizer<TPixel>);
524+
PaletteQuantizer<TPixel> palette = default;
520525
default(TDither).ApplyQuantizationDither<PaletteQuantizer<TPixel>, TPixel>(ref palette, default, default, default);
521526

522-
var wu = default(WuQuantizer<TPixel>);
527+
WuQuantizer<TPixel> wu = default;
523528
default(TDither).ApplyQuantizationDither<WuQuantizer<TPixel>, TPixel>(ref wu, default, default, default);
524529
default(TDither).ApplyPaletteDither<PaletteDitherProcessor<TPixel>.DitherProcessor, TPixel>(default, default, default);
525530
}

src/ImageSharp/Formats/Bmp/BmpEncoder.cs

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,38 @@
22
// Licensed under the Six Labors Split License.
33

44
using SixLabors.ImageSharp.Advanced;
5-
using SixLabors.ImageSharp.PixelFormats;
6-
using SixLabors.ImageSharp.Processing.Processors.Quantization;
75

86
namespace SixLabors.ImageSharp.Formats.Bmp;
97

108
/// <summary>
119
/// Image encoder for writing an image to a stream as a Windows bitmap.
1210
/// </summary>
13-
public sealed class BmpEncoder : IImageEncoder, IBmpEncoderOptions
11+
public sealed class BmpEncoder : QuantizingImageEncoder
1412
{
1513
/// <summary>
16-
/// Gets or sets the number of bits per pixel.
14+
/// Gets the number of bits per pixel.
1715
/// </summary>
18-
public BmpBitsPerPixel? BitsPerPixel { get; set; }
16+
public BmpBitsPerPixel? BitsPerPixel { get; init; }
1917

2018
/// <summary>
21-
/// Gets or sets a value indicating whether the encoder should support transparency.
19+
/// Gets a value indicating whether the encoder should support transparency.
2220
/// Note: Transparency support only works together with 32 bits per pixel. This option will
2321
/// change the default behavior of the encoder of writing a bitmap version 3 info header with no compression.
2422
/// Instead a bitmap version 4 info header will be written with the BITFIELDS compression.
2523
/// </summary>
26-
public bool SupportTransparency { get; set; }
27-
28-
/// <summary>
29-
/// Gets or sets the quantizer for reducing the color count for 8-Bit images.
30-
/// Defaults to Wu Quantizer.
31-
/// </summary>
32-
public IQuantizer Quantizer { get; set; }
24+
public bool SupportTransparency { get; init; }
3325

3426
/// <inheritdoc/>
35-
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
36-
where TPixel : unmanaged, IPixel<TPixel>
27+
public override void Encode<TPixel>(Image<TPixel> image, Stream stream)
3728
{
38-
var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator());
29+
BmpEncoderCore encoder = new(this, image.GetMemoryAllocator());
3930
encoder.Encode(image, stream);
4031
}
4132

4233
/// <inheritdoc/>
43-
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
44-
where TPixel : unmanaged, IPixel<TPixel>
34+
public override Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
4535
{
46-
var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator());
36+
BmpEncoderCore encoder = new(this, image.GetMemoryAllocator());
4737
return encoder.EncodeAsync(image, stream, cancellationToken);
4838
}
4939
}

src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
using SixLabors.ImageSharp.Memory;
1010
using SixLabors.ImageSharp.Metadata;
1111
using SixLabors.ImageSharp.PixelFormats;
12-
using SixLabors.ImageSharp.Processing;
1312
using SixLabors.ImageSharp.Processing.Processors.Quantization;
1413

1514
namespace SixLabors.ImageSharp.Formats.Bmp;
@@ -92,17 +91,23 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
9291
/// </summary>
9392
private readonly IQuantizer quantizer;
9493

94+
/// <summary>
95+
/// The pixel sampling strategy for quantization.
96+
/// </summary>
97+
private readonly IPixelSamplingStrategy pixelSamplingStrategy;
98+
9599
/// <summary>
96100
/// Initializes a new instance of the <see cref="BmpEncoderCore"/> class.
97101
/// </summary>
98-
/// <param name="options">The encoder options.</param>
102+
/// <param name="encoder">The encoder with options.</param>
99103
/// <param name="memoryAllocator">The memory manager.</param>
100-
public BmpEncoderCore(IBmpEncoderOptions options, MemoryAllocator memoryAllocator)
104+
public BmpEncoderCore(BmpEncoder encoder, MemoryAllocator memoryAllocator)
101105
{
102106
this.memoryAllocator = memoryAllocator;
103-
this.bitsPerPixel = options.BitsPerPixel;
104-
this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
105-
this.infoHeaderType = options.SupportTransparency ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion3;
107+
this.bitsPerPixel = encoder.BitsPerPixel;
108+
this.quantizer = encoder.Quantizer;
109+
this.pixelSamplingStrategy = encoder.PixelSamplingStrategy;
110+
this.infoHeaderType = encoder.SupportTransparency ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion3;
106111
}
107112

108113
/// <summary>
@@ -159,7 +164,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
159164

160165
WriteBitmapFileHeader(stream, infoHeaderSize, colorPaletteSize, iccProfileSize, infoHeader, buffer);
161166
this.WriteBitmapInfoHeader(stream, infoHeader, buffer, infoHeaderSize);
162-
this.WriteImage(stream, image.Frames.RootFrame);
167+
this.WriteImage(stream, image);
163168
WriteColorProfile(stream, iccProfileData, buffer);
164169

165170
stream.Flush();
@@ -311,10 +316,10 @@ private void WriteBitmapInfoHeader(Stream stream, BmpInfoHeader infoHeader, Span
311316
/// <param name="image">
312317
/// The <see cref="ImageFrame{TPixel}"/> containing pixel data.
313318
/// </param>
314-
private void WriteImage<TPixel>(Stream stream, ImageFrame<TPixel> image)
319+
private void WriteImage<TPixel>(Stream stream, Image<TPixel> image)
315320
where TPixel : unmanaged, IPixel<TPixel>
316321
{
317-
Buffer2D<TPixel> pixels = image.PixelBuffer;
322+
Buffer2D<TPixel> pixels = image.Frames.RootFrame.PixelBuffer;
318323
switch (this.bitsPerPixel)
319324
{
320325
case BmpBitsPerPixel.Pixel32:
@@ -433,8 +438,8 @@ private void Write16BitPixelData<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
433438
/// </summary>
434439
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
435440
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
436-
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
437-
private void Write8BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
441+
/// <param name="image"> The <see cref="Image{TPixel}"/> containing pixel data.</param>
442+
private void Write8BitPixelData<TPixel>(Stream stream, Image<TPixel> image)
438443
where TPixel : unmanaged, IPixel<TPixel>
439444
{
440445
bool isL8 = typeof(TPixel) == typeof(L8);
@@ -456,13 +461,15 @@ private void Write8BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
456461
/// </summary>
457462
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
458463
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
459-
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
464+
/// <param name="image"> The <see cref="Image{TPixel}"/> containing pixel data.</param>
460465
/// <param name="colorPalette">A byte span of size 1024 for the color palette.</param>
461-
private void Write8BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette)
466+
private void Write8BitColor<TPixel>(Stream stream, Image<TPixel> image, Span<byte> colorPalette)
462467
where TPixel : unmanaged, IPixel<TPixel>
463468
{
464469
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration);
465-
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
470+
471+
frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);
472+
using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
466473

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

505512
stream.Write(colorPalette);
506-
Buffer2D<TPixel> imageBuffer = image.PixelBuffer;
513+
Buffer2D<TPixel> imageBuffer = image.GetRootFramePixelBuffer();
507514
for (int y = image.Height - 1; y >= 0; y--)
508515
{
509516
ReadOnlySpan<TPixel> inputPixelRow = imageBuffer.DangerousGetRowSpan(y);
@@ -523,14 +530,17 @@ private void Write8BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image,
523530
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
524531
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
525532
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
526-
private void Write4BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
533+
private void Write4BitPixelData<TPixel>(Stream stream, Image<TPixel> image)
527534
where TPixel : unmanaged, IPixel<TPixel>
528535
{
529536
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
530537
{
531538
MaxColors = 16
532539
});
533-
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
540+
541+
frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);
542+
543+
using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
534544
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize4Bit, AllocationOptions.Clean);
535545

536546
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
@@ -567,14 +577,17 @@ private void Write4BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
567577
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
568578
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
569579
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
570-
private void Write2BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
580+
private void Write2BitPixelData<TPixel>(Stream stream, Image<TPixel> image)
571581
where TPixel : unmanaged, IPixel<TPixel>
572582
{
573583
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
574584
{
575585
MaxColors = 4
576586
});
577-
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
587+
588+
frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);
589+
590+
using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
578591
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize2Bit, AllocationOptions.Clean);
579592

580593
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
@@ -620,14 +633,17 @@ private void Write2BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
620633
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
621634
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
622635
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
623-
private void Write1BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
636+
private void Write1BitPixelData<TPixel>(Stream stream, Image<TPixel> image)
624637
where TPixel : unmanaged, IPixel<TPixel>
625638
{
626639
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
627640
{
628641
MaxColors = 2
629642
});
630-
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
643+
644+
frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);
645+
646+
using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
631647
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize1Bit, AllocationOptions.Clean);
632648

633649
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();

src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs

Lines changed: 0 additions & 30 deletions
This file was deleted.

src/ImageSharp/Formats/Gif/GifEncoder.cs

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,30 @@
22
// Licensed under the Six Labors Split License.
33

44
using SixLabors.ImageSharp.Advanced;
5-
using SixLabors.ImageSharp.PixelFormats;
6-
using SixLabors.ImageSharp.Processing;
7-
using SixLabors.ImageSharp.Processing.Processors.Quantization;
85

96
namespace SixLabors.ImageSharp.Formats.Gif;
107

118
/// <summary>
129
/// Image encoder for writing image data to a stream in gif format.
1310
/// </summary>
14-
public sealed class GifEncoder : IImageEncoder, IGifEncoderOptions
11+
public sealed class GifEncoder : QuantizingImageEncoder
1512
{
1613
/// <summary>
17-
/// Gets or sets the quantizer for reducing the color count.
18-
/// Defaults to the <see cref="OctreeQuantizer"/>
14+
/// Gets the color table mode: Global or local.
1915
/// </summary>
20-
public IQuantizer Quantizer { get; set; } = KnownQuantizers.Octree;
21-
22-
/// <summary>
23-
/// Gets or sets the color table mode: Global or local.
24-
/// </summary>
25-
public GifColorTableMode? ColorTableMode { get; set; }
26-
27-
/// <summary>
28-
/// Gets or sets the <see cref="IPixelSamplingStrategy"/> used for quantization
29-
/// when building a global color table in case of <see cref="GifColorTableMode.Global"/>.
30-
/// </summary>
31-
public IPixelSamplingStrategy GlobalPixelSamplingStrategy { get; set; } = new DefaultPixelSamplingStrategy();
16+
public GifColorTableMode? ColorTableMode { get; init; }
3217

3318
/// <inheritdoc/>
34-
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
35-
where TPixel : unmanaged, IPixel<TPixel>
19+
public override void Encode<TPixel>(Image<TPixel> image, Stream stream)
3620
{
37-
var encoder = new GifEncoderCore(image.GetConfiguration(), this);
21+
GifEncoderCore encoder = new(image.GetConfiguration(), this);
3822
encoder.Encode(image, stream);
3923
}
4024

4125
/// <inheritdoc/>
42-
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
43-
where TPixel : unmanaged, IPixel<TPixel>
26+
public override Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
4427
{
45-
var encoder = new GifEncoderCore(image.GetConfiguration(), this);
28+
GifEncoderCore encoder = new(image.GetConfiguration(), this);
4629
return encoder.EncodeAsync(image, stream, cancellationToken);
4730
}
4831
}

0 commit comments

Comments
 (0)