Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e238cc6
Define base classes and options
JimBobSquarePants Oct 13, 2022
52bac6f
Migrate Gif encoder
JimBobSquarePants Oct 13, 2022
a824703
Migrate Bmp encoder
JimBobSquarePants Oct 13, 2022
e58f292
Merge branch 'main' into js/encoder-normalization
JimBobSquarePants Oct 15, 2022
ca51d41
BmpEncoder uses Octree as default
JimBobSquarePants Oct 15, 2022
073cefd
Fix frame enumeration and clean up
JimBobSquarePants Oct 16, 2022
715b0ba
Use sampling strategy in BmpEncoder
JimBobSquarePants Oct 16, 2022
6485dde
Convert jpeg encoder.
JimBobSquarePants Oct 16, 2022
8c90f78
Convert PbmEncoder. Make options immutable
JimBobSquarePants Oct 16, 2022
4499df7
Migrate PngEncoder and fix tests
JimBobSquarePants Oct 16, 2022
b88b181
Migrate TgaEncoder
JimBobSquarePants Oct 16, 2022
f0b935e
TgaEncoder options should be immutable.
JimBobSquarePants Oct 16, 2022
1ac9de5
Convert TiffEncoder, Use sampling strategy for local palette building
JimBobSquarePants Oct 16, 2022
2fc9719
Convert WebpEncoder
JimBobSquarePants Oct 16, 2022
37d4ed2
Allow skipping Tiff metadata
JimBobSquarePants Oct 16, 2022
ebdf5c8
Merge branch 'main' into js/encoder-normalization
JimBobSquarePants Oct 24, 2022
6745cae
Update src/ImageSharp/Formats/Png/PngEncoderCore.cs
JimBobSquarePants Oct 24, 2022
45a7b8b
Update src/ImageSharp/Processing/Processors/Quantization/DefaultPixel…
JimBobSquarePants Oct 24, 2022
30a245a
Build fixes and feedback
JimBobSquarePants Oct 24, 2022
48b3abe
Remove IEncoderOptions
JimBobSquarePants Oct 27, 2022
307a3ba
Use DebugGuard
JimBobSquarePants Oct 27, 2022
af7eadd
Update PixelSamplingStrategyTests.cs
JimBobSquarePants Oct 27, 2022
e5df749
Fix sampling tests
JimBobSquarePants Oct 27, 2022
830d3e2
Use DebugGuard
antonfirsov Oct 27, 2022
864735f
Move check outside of the loop
JimBobSquarePants Oct 28, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 AotCompileQuantizer<TPixel, TQuantizer>()
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 AotCompileDither<TPixel, TDither>()
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());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's easy to overlook important semantic changes if there are so many style refactorings in the same PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but every time I open a file just now there's a heap of warnings from the new rules. I'm adopting the boy scout rule.

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