Browse Source

Simplify API and clone options.

pull/2894/head
James Jackson-South 1 year ago
parent
commit
16b4eb6c81
  1. 4
      src/ImageSharp/Formats/AlphaAwareImageEncoder.cs
  2. 39
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  3. 13
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  4. 2
      src/ImageSharp/Formats/TransparentColorMode.cs
  5. 29
      src/ImageSharp/Processing/Processors/Quantization/IQuantizer{TPixel}.cs
  6. 6
      src/ImageSharp/Processing/Processors/Quantization/IQuantizingPixelRowDelegate{TPixel}.cs
  7. 20
      src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs
  8. 14
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs
  9. 3
      src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs
  10. 90
      src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs
  11. 20
      src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs

4
src/ImageSharp/Formats/AlphaAwareImageEncoder.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats; namespace SixLabors.ImageSharp.Formats;
/// <summary> /// <summary>
@ -10,6 +12,8 @@ public abstract class AlphaAwareImageEncoder : ImageEncoder
{ {
/// <summary> /// <summary>
/// Gets or initializes the mode that determines how transparent pixels are handled during encoding. /// Gets or initializes the mode that determines how transparent pixels are handled during encoding.
/// This overrides any other settings that may affect the encoding of transparent pixels
/// including those passed via <see cref="QuantizerOptions"/>.
/// </summary> /// </summary>
public TransparentColorMode TransparentColorMode { get; init; } public TransparentColorMode TransparentColorMode { get; init; }
} }

39
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -150,10 +150,7 @@ internal sealed class GifEncoderCore
TransparentColorMode mode = this.transparentColorMode; TransparentColorMode mode = this.transparentColorMode;
QuantizerOptions options = this.quantizer.Options.DeepClone(o => o.TransparentColorMode = mode); QuantizerOptions options = this.quantizer.Options.DeepClone(o => o.TransparentColorMode = mode);
// Quantize the first frame. Checking to see whether we can clear the transparent pixels // Quantize the first frame.
// to allow for a smaller color palette and encoded result.
// TODO: What should we use as the background color here?
Color background = Color.Transparent;
using (IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, options)) using (IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, options))
{ {
IPixelSamplingStrategy strategy = this.pixelSamplingStrategy; IPixelSamplingStrategy strategy = this.pixelSamplingStrategy;
@ -163,12 +160,12 @@ internal sealed class GifEncoderCore
{ {
if (useGlobalTable) if (useGlobalTable)
{ {
frameQuantizer.BuildPalette(mode, strategy, image); frameQuantizer.BuildPalette(strategy, image);
quantized = frameQuantizer.QuantizeFrame(encodingFrame, image.Bounds); quantized = frameQuantizer.QuantizeFrame(encodingFrame, image.Bounds);
} }
else else
{ {
frameQuantizer.BuildPalette(mode, strategy, encodingFrame); frameQuantizer.BuildPalette(strategy, encodingFrame);
quantized = frameQuantizer.QuantizeFrame(encodingFrame, encodingFrame.Bounds); quantized = frameQuantizer.QuantizeFrame(encodingFrame, encodingFrame.Bounds);
} }
} }
@ -176,13 +173,14 @@ internal sealed class GifEncoderCore
{ {
quantized = this.QuantizeAdditionalFrameAndUpdateMetadata( quantized = this.QuantizeAdditionalFrameAndUpdateMetadata(
encodingFrame, encodingFrame,
options,
encodingFrame.Bounds, encodingFrame.Bounds,
frameMetadata, frameMetadata,
true, true,
default, default,
false, false,
frameMetadata.HasTransparency ? frameMetadata.TransparencyIndex : -1, frameMetadata.HasTransparency ? frameMetadata.TransparencyIndex : -1,
background); Color.Transparent);
} }
} }
@ -197,6 +195,7 @@ internal sealed class GifEncoderCore
frameMetadata.TransparencyIndex = ClampIndex(derivedTransparencyIndex); frameMetadata.TransparencyIndex = ClampIndex(derivedTransparencyIndex);
} }
// TODO: We should be checking the metadata here also I think?
if (!TryGetBackgroundIndex(quantized, this.backgroundColor, out byte backgroundIndex)) if (!TryGetBackgroundIndex(quantized, this.backgroundColor, out byte backgroundIndex))
{ {
backgroundIndex = derivedTransparencyIndex >= 0 backgroundIndex = derivedTransparencyIndex >= 0
@ -235,6 +234,7 @@ internal sealed class GifEncoderCore
this.EncodeAdditionalFrames( this.EncodeAdditionalFrames(
stream, stream,
image, image,
options,
globalPalette, globalPalette,
derivedTransparencyIndex, derivedTransparencyIndex,
frameMetadata.DisposalMode, frameMetadata.DisposalMode,
@ -264,6 +264,7 @@ internal sealed class GifEncoderCore
private void EncodeAdditionalFrames<TPixel>( private void EncodeAdditionalFrames<TPixel>(
Stream stream, Stream stream,
Image<TPixel> image, Image<TPixel> image,
QuantizerOptions options,
ReadOnlyMemory<TPixel> globalPalette, ReadOnlyMemory<TPixel> globalPalette,
int globalTransparencyIndex, int globalTransparencyIndex,
FrameDisposalMode previousDisposalMode, FrameDisposalMode previousDisposalMode,
@ -301,7 +302,7 @@ internal sealed class GifEncoderCore
// The palette quantizer can reuse the same global pixel map across multiple frames since the palette is unchanging. // The palette quantizer can reuse the same global pixel map across multiple frames since the palette is unchanging.
// This allows a reduction of memory usage across multi-frame gifs using a global palette // This allows a reduction of memory usage across multi-frame gifs using a global palette
// and also allows use to reuse the cache from previous runs. // and also allows use to reuse the cache from previous runs.
globalPaletteQuantizer = new(this.configuration, this.quantizer!.Options, globalPalette); globalPaletteQuantizer = new(this.configuration, options, globalPalette);
hasGlobalPaletteQuantizer = true; hasGlobalPaletteQuantizer = true;
} }
@ -311,6 +312,7 @@ internal sealed class GifEncoderCore
currentFrame, currentFrame,
nextFrame, nextFrame,
encodingFrame, encodingFrame,
options,
useLocal, useLocal,
gifMetadata, gifMetadata,
globalPaletteQuantizer, globalPaletteQuantizer,
@ -361,6 +363,7 @@ internal sealed class GifEncoderCore
ImageFrame<TPixel> currentFrame, ImageFrame<TPixel> currentFrame,
ImageFrame<TPixel>? nextFrame, ImageFrame<TPixel>? nextFrame,
ImageFrame<TPixel> encodingFrame, ImageFrame<TPixel> encodingFrame,
QuantizerOptions options,
bool useLocal, bool useLocal,
GifFrameMetadata metadata, GifFrameMetadata metadata,
PaletteQuantizer<TPixel> globalPaletteQuantizer, PaletteQuantizer<TPixel> globalPaletteQuantizer,
@ -392,6 +395,7 @@ internal sealed class GifEncoderCore
using IndexedImageFrame<TPixel> quantized = this.QuantizeAdditionalFrameAndUpdateMetadata( using IndexedImageFrame<TPixel> quantized = this.QuantizeAdditionalFrameAndUpdateMetadata(
encodingFrame, encodingFrame,
options,
bounds, bounds,
metadata, metadata,
useLocal, useLocal,
@ -416,6 +420,7 @@ internal sealed class GifEncoderCore
private IndexedImageFrame<TPixel> QuantizeAdditionalFrameAndUpdateMetadata<TPixel>( private IndexedImageFrame<TPixel> QuantizeAdditionalFrameAndUpdateMetadata<TPixel>(
ImageFrame<TPixel> encodingFrame, ImageFrame<TPixel> encodingFrame,
QuantizerOptions options,
Rectangle bounds, Rectangle bounds,
GifFrameMetadata metadata, GifFrameMetadata metadata,
bool useLocal, bool useLocal,
@ -446,7 +451,12 @@ internal sealed class GifEncoderCore
transparencyIndex = palette.Length; transparencyIndex = palette.Length;
metadata.TransparencyIndex = ClampIndex(transparencyIndex); metadata.TransparencyIndex = ClampIndex(transparencyIndex);
PaletteQuantizer quantizer = new(palette, new() { Dither = null }, transparencyIndex, transparentColor); QuantizerOptions paletteOptions = options.DeepClone(o =>
{
o.MaxColors = palette.Length;
o.Dither = null;
});
PaletteQuantizer quantizer = new(palette, paletteOptions, transparencyIndex, transparentColor);
using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, quantizer.Options); using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, quantizer.Options);
quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, bounds); quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, bounds);
} }
@ -454,7 +464,7 @@ internal sealed class GifEncoderCore
{ {
// We must quantize the frame to generate a local color table. // We must quantize the frame to generate a local color table.
IQuantizer quantizer = this.hasQuantizer ? this.quantizer! : FallbackQuantizer; IQuantizer quantizer = this.hasQuantizer ? this.quantizer! : FallbackQuantizer;
using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, quantizer.Options); using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, options);
quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, bounds); quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, bounds);
// The transparency index derived by the quantizer will differ from the index // The transparency index derived by the quantizer will differ from the index
@ -466,7 +476,12 @@ internal sealed class GifEncoderCore
else else
{ {
// Just use the local palette. // Just use the local palette.
PaletteQuantizer quantizer = new(palette, new() { Dither = null }, transparencyIndex, transparentColor); QuantizerOptions paletteOptions = options.DeepClone(o =>
{
o.MaxColors = palette.Length;
o.Dither = null;
});
PaletteQuantizer quantizer = new(palette, paletteOptions, transparencyIndex, transparentColor);
using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, quantizer.Options); using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, quantizer.Options);
quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, bounds); quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, bounds);
} }
@ -475,7 +490,7 @@ internal sealed class GifEncoderCore
{ {
// We must quantize the frame to generate a local color table. // We must quantize the frame to generate a local color table.
IQuantizer quantizer = this.hasQuantizer ? this.quantizer! : FallbackQuantizer; IQuantizer quantizer = this.hasQuantizer ? this.quantizer! : FallbackQuantizer;
using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, quantizer.Options); using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, options);
quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, bounds); quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, bounds);
// The transparency index derived by the quantizer might differ from the index // The transparency index derived by the quantizer might differ from the index

13
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -1574,13 +1574,21 @@ internal sealed class PngEncoderCore : IDisposable
{ {
// We can use the color data from the decoded metadata here. // We can use the color data from the decoded metadata here.
// We avoid dithering by default to preserve the original colors. // We avoid dithering by default to preserve the original colors.
this.quantizer = new PaletteQuantizer(metadata.ColorTable.Value, new() { Dither = null }); QuantizerOptions options = new() { Dither = null, TransparentColorMode = encoder.TransparentColorMode };
this.quantizer = new PaletteQuantizer(metadata.ColorTable.Value, options);
} }
else else
{ {
// Don't use the default transparency threshold for quantization as PNG can handle multiple transparent colors. // Don't use the default transparency threshold for quantization as PNG can handle multiple transparent colors.
// We choose a value that is close to zero so that edge cases causes by lower bit depths for the alpha channel are handled correctly. // We choose a value that is close to zero so that edge cases causes by lower bit depths for the alpha channel are handled correctly.
this.quantizer = new WuQuantizer(new QuantizerOptions { TransparencyThreshold = 0, MaxColors = ColorNumerics.GetColorCountForBitDepth(bitDepth) }); QuantizerOptions options = new()
{
TransparencyThreshold = 0,
MaxColors = ColorNumerics.GetColorCountForBitDepth(bitDepth),
TransparentColorMode = encoder.TransparentColorMode
};
this.quantizer = new WuQuantizer(options);
} }
} }
@ -1604,7 +1612,6 @@ internal sealed class PngEncoderCore : IDisposable
} }
frameQuantizer.BuildPalette( frameQuantizer.BuildPalette(
encoder.TransparentColorMode,
encoder.PixelSamplingStrategy, encoder.PixelSamplingStrategy,
image); image);

2
src/ImageSharp/Formats/TransparentColorMode.cs

@ -4,7 +4,7 @@
namespace SixLabors.ImageSharp.Formats; namespace SixLabors.ImageSharp.Formats;
/// <summary> /// <summary>
/// Specifies how transparent pixels should be handled during encoding and quantization. /// Specifies how pixels with transparent alpha components should be handled during encoding and quantization.
/// </summary> /// </summary>
public enum TransparentColorMode public enum TransparentColorMode
{ {

29
src/ImageSharp/Processing/Processors/Quantization/IQuantizer{TPixel}.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -36,37 +35,13 @@ public interface IQuantizer<TPixel> : IDisposable
/// Adds colors to the quantized palette from the given pixel source. /// Adds colors to the quantized palette from the given pixel source.
/// </summary> /// </summary>
/// <param name="pixelRegion">The <see cref="Buffer2DRegion{T}"/> of source pixels to register.</param> /// <param name="pixelRegion">The <see cref="Buffer2DRegion{T}"/> of source pixels to register.</param>
public void AddPaletteColors(in Buffer2DRegion<TPixel> pixelRegion) public void AddPaletteColors(in Buffer2DRegion<TPixel> pixelRegion);
=> this.AddPaletteColors(pixelRegion, TransparentColorMode.Preserve);
/// <summary>
/// Adds colors to the quantized palette from the given pixel source.
/// </summary>
/// <param name="pixelRegion">The <see cref="Buffer2DRegion{T}"/> of source pixels to register.</param>
/// <param name="mode">The <see cref="TransparentColorMode"/> to use when adding colors to the palette.</param>
public void AddPaletteColors(in Buffer2DRegion<TPixel> pixelRegion, TransparentColorMode mode);
/// <summary>
/// Quantizes an image frame and return the resulting output pixels.
/// </summary>
/// <param name="source">The source image frame to quantize.</param>
/// <param name="bounds">The bounds within the frame to quantize.</param>
/// <returns>
/// A <see cref="IndexedImageFrame{TPixel}"/> representing a quantized version of the source frame pixels.
/// </returns>
/// <remarks>
/// Only executes the second (quantization) step. The palette has to be built by calling <see cref="AddPaletteColors(in Buffer2DRegion{TPixel})"/>.
/// To run both steps, use <see cref="QuantizerUtilities.BuildPaletteAndQuantizeFrame{TPixel}(IQuantizer{TPixel}, ImageFrame{TPixel}, Rectangle)"/>.
/// </remarks>
public IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds)
=> this.QuantizeFrame(source, bounds, TransparentColorMode.Preserve);
/// <summary> /// <summary>
/// Quantizes an image frame and return the resulting output pixels. /// Quantizes an image frame and return the resulting output pixels.
/// </summary> /// </summary>
/// <param name="source">The source image frame to quantize.</param> /// <param name="source">The source image frame to quantize.</param>
/// <param name="bounds">The bounds within the frame to quantize.</param> /// <param name="bounds">The bounds within the frame to quantize.</param>
/// <param name="mode">The <see cref="TransparentColorMode"/> to use when quantizing the frame.</param>
/// <returns> /// <returns>
/// A <see cref="IndexedImageFrame{TPixel}"/> representing a quantized version of the source frame pixels. /// A <see cref="IndexedImageFrame{TPixel}"/> representing a quantized version of the source frame pixels.
/// </returns> /// </returns>
@ -74,7 +49,7 @@ public interface IQuantizer<TPixel> : IDisposable
/// Only executes the second (quantization) step. The palette has to be built by calling <see cref="AddPaletteColors(in Buffer2DRegion{TPixel})"/>. /// Only executes the second (quantization) step. The palette has to be built by calling <see cref="AddPaletteColors(in Buffer2DRegion{TPixel})"/>.
/// To run both steps, use <see cref="QuantizerUtilities.BuildPaletteAndQuantizeFrame{TPixel}(IQuantizer{TPixel}, ImageFrame{TPixel}, Rectangle)"/>. /// To run both steps, use <see cref="QuantizerUtilities.BuildPaletteAndQuantizeFrame{TPixel}(IQuantizer{TPixel}, ImageFrame{TPixel}, Rectangle)"/>.
/// </remarks> /// </remarks>
public IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds, TransparentColorMode mode); public IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds);
/// <summary> /// <summary>
/// Returns the index and color from the quantized palette corresponding to the given color. /// Returns the index and color from the quantized palette corresponding to the given color.

6
src/ImageSharp/Processing/Processors/Quantization/IQuantizingPixelRowDelegate{TPixel}.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Processing.Processors.Quantization;
@ -13,11 +12,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization;
internal interface IQuantizingPixelRowDelegate<TPixel> internal interface IQuantizingPixelRowDelegate<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
/// <summary>
/// Gets the transparent color mode to use when adding colors to the palette.
/// </summary>
public TransparentColorMode TransparentColorMode { get; }
/// <summary> /// <summary>
/// Processes a row of pixels for quantization. /// Processes a row of pixels for quantization.
/// </summary> /// </summary>

20
src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs

@ -6,7 +6,6 @@ using System.Diagnostics.CodeAnalysis;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -75,9 +74,9 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
} }
/// <inheritdoc/> /// <inheritdoc/>
public readonly void AddPaletteColors(in Buffer2DRegion<TPixel> pixelRegion, TransparentColorMode mode) public readonly void AddPaletteColors(in Buffer2DRegion<TPixel> pixelRegion)
{ {
PixelRowDelegate pixelRowDelegate = new(this.octree, mode); PixelRowDelegate pixelRowDelegate = new(this.octree);
QuantizerUtilities.AddPaletteColors<OctreeQuantizer<TPixel>, TPixel, Rgba32, PixelRowDelegate>( QuantizerUtilities.AddPaletteColors<OctreeQuantizer<TPixel>, TPixel, Rgba32, PixelRowDelegate>(
ref Unsafe.AsRef(in this), ref Unsafe.AsRef(in this),
in pixelRegion, in pixelRegion,
@ -103,12 +102,7 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public readonly IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds) public readonly IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds)
=> this.QuantizeFrame(source, bounds, TransparentColorMode.Preserve); => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(in this), source, bounds);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public readonly IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds, TransparentColorMode mode)
=> QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(in this), source, bounds, mode);
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
@ -146,13 +140,7 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
{ {
private readonly Octree octree; private readonly Octree octree;
public PixelRowDelegate(Octree octree, TransparentColorMode mode) public PixelRowDelegate(Octree octree) => this.octree = octree;
{
this.octree = octree;
this.TransparentColorMode = mode;
}
public TransparentColorMode TransparentColorMode { get; }
public void Invoke(ReadOnlySpan<Rgba32> row, int rowIndex) => this.octree.AddColors(row); public void Invoke(ReadOnlySpan<Rgba32> row, int rowIndex) => this.octree.AddColors(row);
} }

14
src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs

@ -3,7 +3,6 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -76,29 +75,18 @@ internal struct PaletteQuantizer<TPixel> : IQuantizer<TPixel>
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public readonly void AddPaletteColors(in Buffer2DRegion<TPixel> pixelRegion) public readonly void AddPaletteColors(in Buffer2DRegion<TPixel> pixelRegion)
=> this.AddPaletteColors(in pixelRegion, TransparentColorMode.Preserve);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public readonly void AddPaletteColors(in Buffer2DRegion<TPixel> pixelRegion, TransparentColorMode mode)
{ {
} }
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public readonly IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds) public readonly IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds)
=> this.QuantizeFrame(source, bounds, TransparentColorMode.Preserve); => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(in this), source, bounds);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public readonly IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds, TransparentColorMode mode)
=> QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(in this), source, bounds, mode);
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public readonly byte GetQuantizedColor(TPixel color, out TPixel match) public readonly byte GetQuantizedColor(TPixel color, out TPixel match)
{ {
// TODO: We need to use thesholding here.
if (this.transparencyIndex >= 0 && color.Equals(this.transparentColor)) if (this.transparencyIndex >= 0 && color.Equals(this.transparentColor))
{ {
match = this.transparentColor; match = this.transparentColor;

3
src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs

@ -81,7 +81,8 @@ public class QuantizerOptions : IDeepCloneable<QuantizerOptions>
} }
/// <summary> /// <summary>
/// Gets or sets the transparent color mode used for handling transparent colors. /// Gets or sets the transparent color mode used for handling transparent colors
/// when not using thresholding.
/// Defaults to <see cref="TransparentColorMode.Preserve"/>. /// Defaults to <see cref="TransparentColorMode.Preserve"/>.
/// </summary> /// </summary>
public TransparentColorMode TransparentColorMode { get; set; } = TransparentColorMode.Preserve; public TransparentColorMode TransparentColorMode { get; set; } = TransparentColorMode.Preserve;

90
src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs

@ -18,7 +18,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization;
/// </summary> /// </summary>
public static class QuantizerUtilities public static class QuantizerUtilities
{ {
internal static QuantizerOptions DeepClone(this QuantizerOptions options, Action<QuantizerOptions>? mutate) /// <summary>
/// Performs a deep clone the <see cref="QuantizerOptions"/> instance and optionally mutates the clone.
/// </summary>
/// <param name="options">The <see cref="QuantizerOptions"/> instance to clone.</param>
/// <param name="mutate">An optional delegate to mutate the cloned instance.</param>
/// <returns>The cloned <see cref="QuantizerOptions"/> instance.</returns>
public static QuantizerOptions DeepClone(this QuantizerOptions options, Action<QuantizerOptions>? mutate)
{ {
QuantizerOptions clone = options.DeepClone(); QuantizerOptions clone = options.DeepClone();
mutate?.Invoke(clone); mutate?.Invoke(clone);
@ -170,29 +176,6 @@ public static class QuantizerUtilities
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
Rectangle bounds) Rectangle bounds)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> BuildPaletteAndQuantizeFrame(
quantizer,
source,
bounds,
TransparentColorMode.Preserve);
/// <summary>
/// Execute both steps of the quantization.
/// </summary>
/// <param name="quantizer">The pixel specific quantizer.</param>
/// <param name="source">The source image frame to quantize.</param>
/// <param name="bounds">The bounds within the frame to quantize.</param>
/// <param name="mode">The transparent color mode.</param>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <returns>
/// A <see cref="IndexedImageFrame{TPixel}"/> representing a quantized version of the source frame pixels.
/// </returns>
public static IndexedImageFrame<TPixel> BuildPaletteAndQuantizeFrame<TPixel>(
this IQuantizer<TPixel> quantizer,
ImageFrame<TPixel> source,
Rectangle bounds,
TransparentColorMode mode)
where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(quantizer, nameof(quantizer)); Guard.NotNull(quantizer, nameof(quantizer));
Guard.NotNull(source, nameof(source)); Guard.NotNull(source, nameof(source));
@ -200,7 +183,7 @@ public static class QuantizerUtilities
Rectangle interest = Rectangle.Intersect(source.Bounds, bounds); Rectangle interest = Rectangle.Intersect(source.Bounds, bounds);
Buffer2DRegion<TPixel> region = source.PixelBuffer.GetRegion(interest); Buffer2DRegion<TPixel> region = source.PixelBuffer.GetRegion(interest);
quantizer.AddPaletteColors(in region, mode); quantizer.AddPaletteColors(in region);
return quantizer.QuantizeFrame(source, bounds); return quantizer.QuantizeFrame(source, bounds);
} }
@ -212,15 +195,13 @@ public static class QuantizerUtilities
/// <param name="quantizer">The pixel specific quantizer.</param> /// <param name="quantizer">The pixel specific quantizer.</param>
/// <param name="source">The source image frame to quantize.</param> /// <param name="source">The source image frame to quantize.</param>
/// <param name="bounds">The bounds within the frame to quantize.</param> /// <param name="bounds">The bounds within the frame to quantize.</param>
/// <param name="mode">The transparent color mode.</param>
/// <returns> /// <returns>
/// A <see cref="IndexedImageFrame{TPixel}"/> representing a quantized version of the source frame pixels. /// A <see cref="IndexedImageFrame{TPixel}"/> representing a quantized version of the source frame pixels.
/// </returns> /// </returns>
public static IndexedImageFrame<TPixel> QuantizeFrame<TFrameQuantizer, TPixel>( public static IndexedImageFrame<TPixel> QuantizeFrame<TFrameQuantizer, TPixel>(
ref TFrameQuantizer quantizer, ref TFrameQuantizer quantizer,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
Rectangle bounds, Rectangle bounds)
TransparentColorMode mode)
where TFrameQuantizer : struct, IQuantizer<TPixel> where TFrameQuantizer : struct, IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
@ -235,13 +216,13 @@ public static class QuantizerUtilities
if (quantizer.Options.Dither is null) if (quantizer.Options.Dither is null)
{ {
SecondPass(ref quantizer, source, destination, interest, mode); SecondPass(ref quantizer, source, destination, interest);
} }
else else
{ {
// We clone the image as we don't want to alter the original via error diffusion based dithering. // We clone the image as we don't want to alter the original via error diffusion based dithering.
using ImageFrame<TPixel> clone = source.Clone(); using ImageFrame<TPixel> clone = source.Clone();
SecondPass(ref quantizer, clone, destination, interest, mode); SecondPass(ref quantizer, clone, destination, interest);
} }
return destination; return destination;
@ -259,29 +240,10 @@ public static class QuantizerUtilities
IPixelSamplingStrategy pixelSamplingStrategy, IPixelSamplingStrategy pixelSamplingStrategy,
Image<TPixel> source) Image<TPixel> source)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> quantizer.BuildPalette(
TransparentColorMode.Preserve,
pixelSamplingStrategy,
source);
/// <summary>
/// Adds colors to the quantized palette from the given pixel regions.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="quantizer">The pixel specific quantizer.</param>
/// <param name="mode">The transparent color mode.</param>
/// <param name="pixelSamplingStrategy">The pixel sampling strategy.</param>
/// <param name="source">The source image to sample from.</param>
public static void BuildPalette<TPixel>(
this IQuantizer<TPixel> quantizer,
TransparentColorMode mode,
IPixelSamplingStrategy pixelSamplingStrategy,
Image<TPixel> source)
where TPixel : unmanaged, IPixel<TPixel>
{ {
foreach (Buffer2DRegion<TPixel> region in pixelSamplingStrategy.EnumeratePixelRegions(source)) foreach (Buffer2DRegion<TPixel> region in pixelSamplingStrategy.EnumeratePixelRegions(source))
{ {
quantizer.AddPaletteColors(in region, mode); quantizer.AddPaletteColors(in region);
} }
} }
@ -297,29 +259,10 @@ public static class QuantizerUtilities
IPixelSamplingStrategy pixelSamplingStrategy, IPixelSamplingStrategy pixelSamplingStrategy,
ImageFrame<TPixel> source) ImageFrame<TPixel> source)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> quantizer.BuildPalette(
TransparentColorMode.Preserve,
pixelSamplingStrategy,
source);
/// <summary>
/// Adds colors to the quantized palette from the given pixel regions.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="quantizer">The pixel specific quantizer.</param>
/// <param name="mode">The transparent color mode.</param>
/// <param name="pixelSamplingStrategy">The pixel sampling strategy.</param>
/// <param name="source">The source image frame to sample from.</param>
public static void BuildPalette<TPixel>(
this IQuantizer<TPixel> quantizer,
TransparentColorMode mode,
IPixelSamplingStrategy pixelSamplingStrategy,
ImageFrame<TPixel> source)
where TPixel : unmanaged, IPixel<TPixel>
{ {
foreach (Buffer2DRegion<TPixel> region in pixelSamplingStrategy.EnumeratePixelRegions(source)) foreach (Buffer2DRegion<TPixel> region in pixelSamplingStrategy.EnumeratePixelRegions(source))
{ {
quantizer.AddPaletteColors(in region, mode); quantizer.AddPaletteColors(in region);
} }
} }
@ -340,7 +283,7 @@ public static class QuantizerUtilities
Span<TPixel2> delegateRow = delegateRowOwner.Memory.Span; Span<TPixel2> delegateRow = delegateRowOwner.Memory.Span;
bool replaceByThreshold = ShouldReplacePixelsByAlphaThreshold<TPixel>(threshold); bool replaceByThreshold = ShouldReplacePixelsByAlphaThreshold<TPixel>(threshold);
bool replaceTransparent = EncodingUtilities.ShouldReplaceTransparentPixels<TPixel>(rowDelegate.TransparentColorMode); bool replaceTransparent = EncodingUtilities.ShouldReplaceTransparentPixels<TPixel>(mode);
if (replaceByThreshold || replaceTransparent) if (replaceByThreshold || replaceTransparent)
{ {
@ -389,13 +332,14 @@ public static class QuantizerUtilities
ref TFrameQuantizer quantizer, ref TFrameQuantizer quantizer,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
IndexedImageFrame<TPixel> destination, IndexedImageFrame<TPixel> destination,
Rectangle bounds, Rectangle bounds)
TransparentColorMode mode)
where TFrameQuantizer : struct, IQuantizer<TPixel> where TFrameQuantizer : struct, IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
float threshold = quantizer.Options.TransparencyThreshold; float threshold = quantizer.Options.TransparencyThreshold;
bool replaceByThreshold = ShouldReplacePixelsByAlphaThreshold<TPixel>(threshold); bool replaceByThreshold = ShouldReplacePixelsByAlphaThreshold<TPixel>(threshold);
TransparentColorMode mode = quantizer.Options.TransparentColorMode;
bool replaceTransparent = EncodingUtilities.ShouldReplaceTransparentPixels<TPixel>(mode); bool replaceTransparent = EncodingUtilities.ShouldReplaceTransparentPixels<TPixel>(mode);
IDither? dither = quantizer.Options.Dither; IDither? dither = quantizer.Options.Dither;

20
src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs

@ -6,7 +6,6 @@ using System.Diagnostics.CodeAnalysis;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -107,9 +106,9 @@ internal struct WuQuantizer<TPixel> : IQuantizer<TPixel>
} }
/// <inheritdoc/> /// <inheritdoc/>
public readonly void AddPaletteColors(in Buffer2DRegion<TPixel> pixelRegion, TransparentColorMode mode) public readonly void AddPaletteColors(in Buffer2DRegion<TPixel> pixelRegion)
{ {
PixelRowDelegate pixelRowDelegate = new(ref Unsafe.AsRef(in this), mode); PixelRowDelegate pixelRowDelegate = new(ref Unsafe.AsRef(in this));
QuantizerUtilities.AddPaletteColors<WuQuantizer<TPixel>, TPixel, Rgba32, PixelRowDelegate>( QuantizerUtilities.AddPaletteColors<WuQuantizer<TPixel>, TPixel, Rgba32, PixelRowDelegate>(
ref Unsafe.AsRef(in this), ref Unsafe.AsRef(in this),
in pixelRegion, in pixelRegion,
@ -162,12 +161,7 @@ internal struct WuQuantizer<TPixel> : IQuantizer<TPixel>
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public readonly IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds) public readonly IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds)
=> this.QuantizeFrame(source, bounds, TransparentColorMode.Preserve); => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(in this), source, bounds);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public readonly IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds, TransparentColorMode mode)
=> QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(in this), source, bounds, mode);
/// <inheritdoc/> /// <inheritdoc/>
public readonly byte GetQuantizedColor(TPixel color, out TPixel match) public readonly byte GetQuantizedColor(TPixel color, out TPixel match)
@ -891,13 +885,7 @@ internal struct WuQuantizer<TPixel> : IQuantizer<TPixel>
{ {
private readonly WuQuantizer<TPixel> quantizer; private readonly WuQuantizer<TPixel> quantizer;
public PixelRowDelegate(ref WuQuantizer<TPixel> quantizer, TransparentColorMode mode) public PixelRowDelegate(ref WuQuantizer<TPixel> quantizer) => this.quantizer = quantizer;
{
this.quantizer = quantizer;
this.TransparentColorMode = mode;
}
public TransparentColorMode TransparentColorMode { get; }
public void Invoke(ReadOnlySpan<Rgba32> row, int rowIndex) => this.quantizer.Build3DHistogram(row); public void Invoke(ReadOnlySpan<Rgba32> row, int rowIndex) => this.quantizer.Build3DHistogram(row);
} }

Loading…
Cancel
Save