Browse Source

Simplify API

af/qhack03
James Jackson-South 6 years ago
parent
commit
e8807771c8
  1. 2
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  2. 4
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  3. 30
      src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs
  4. 21
      src/ImageSharp/Processing/Processors/Dithering/IDither.cs
  5. 39
      src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs
  6. 77
      src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs
  7. 78
      src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs
  8. 7
      src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs
  9. 34
      src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs
  10. 6
      src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs
  11. 11
      src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs

2
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

@ -360,7 +360,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
for (int y = image.Height - 1; y >= 0; y--) for (int y = image.Height - 1; y >= 0; y--)
{ {
ReadOnlySpan<byte> pixelSpan = quantized.GetRowSpan(y); ReadOnlySpan<byte> pixelSpan = quantized.GetPixelRowSpan(y);
stream.Write(pixelSpan); stream.Write(pixelSpan);
for (int i = 0; i < this.padding; i++) for (int i = 0; i < this.padding; i++)

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

@ -380,7 +380,7 @@ namespace SixLabors.ImageSharp.Formats.Png
if (this.bitDepth < 8) if (this.bitDepth < 8)
{ {
PngEncoderHelpers.ScaleDownFrom8BitArray(quantized.GetRowSpan(row), this.currentScanline.GetSpan(), this.bitDepth); PngEncoderHelpers.ScaleDownFrom8BitArray(quantized.GetPixelRowSpan(row), this.currentScanline.GetSpan(), this.bitDepth);
} }
else else
{ {
@ -987,7 +987,7 @@ namespace SixLabors.ImageSharp.Formats.Png
row += Adam7.RowIncrement[pass]) row += Adam7.RowIncrement[pass])
{ {
// collect data // collect data
ReadOnlySpan<byte> srcRow = quantized.GetRowSpan(row); ReadOnlySpan<byte> srcRow = quantized.GetPixelRowSpan(row);
for (int col = startCol, i = 0; for (int col = startCol, i = 0;
col < width; col < width;
col += Adam7.ColumnIncrement[pass]) col += Adam7.ColumnIncrement[pass])

30
src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs

@ -89,29 +89,26 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void ApplyQuantizationDither<TFrameQuantizer, TPixel>( public void ApplyQuantizationDither<TFrameQuantizer, TPixel>(
ref TFrameQuantizer quantizer, ref TFrameQuantizer quantizer,
ReadOnlyMemory<TPixel> palette,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
Memory<byte> output, QuantizedFrame<TPixel> destination,
Rectangle bounds) Rectangle bounds)
where TFrameQuantizer : struct, IFrameQuantizer<TPixel> where TFrameQuantizer : struct, IFrameQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
Span<byte> outputSpan = output.Span; ReadOnlySpan<TPixel> paletteSpan = destination.Palette.Span;
ReadOnlySpan<TPixel> paletteSpan = palette.Span;
int width = bounds.Width;
int offsetY = bounds.Top; int offsetY = bounds.Top;
int offsetX = bounds.Left; int offsetX = bounds.Left;
float scale = quantizer.Options.DitherScale; float scale = quantizer.Options.DitherScale;
for (int y = bounds.Top; y < bounds.Bottom; y++) for (int y = bounds.Top; y < bounds.Bottom; y++)
{ {
Span<TPixel> row = source.GetPixelRowSpan(y); Span<TPixel> sourceRow = source.GetPixelRowSpan(y);
int rowStart = (y - offsetY) * width; Span<byte> destinationRow = destination.GetPixelRowSpan(y - offsetY);
for (int x = bounds.Left; x < bounds.Right; x++) for (int x = bounds.Left; x < bounds.Right; x++)
{ {
TPixel sourcePixel = row[x]; TPixel sourcePixel = sourceRow[x];
outputSpan[rowStart + x - offsetX] = quantizer.GetQuantizedColor(sourcePixel, paletteSpan, out TPixel transformed); destinationRow[x - offsetX] = quantizer.GetQuantizedColor(sourcePixel, paletteSpan, out TPixel transformed);
this.Dither(source, bounds, sourcePixel, transformed, x, y, scale); this.Dither(source, bounds, sourcePixel, transformed, x, y, scale);
} }
} }
@ -119,23 +116,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void ApplyPaletteDither<TPixel>( public void ApplyPaletteDither<TPaletteDitherImageProcessor, TPixel>(
Configuration configuration, in TPaletteDitherImageProcessor processor,
ReadOnlyMemory<TPixel> palette,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
Rectangle bounds, Rectangle bounds)
float scale) where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
var pixelMap = new EuclideanPixelMap<TPixel>(palette); float scale = processor.DitherScale;
ReadOnlySpan<TPixel> palette = processor.Palette.Span;
for (int y = bounds.Top; y < bounds.Bottom; y++) for (int y = bounds.Top; y < bounds.Bottom; y++)
{ {
Span<TPixel> row = source.GetPixelRowSpan(y); Span<TPixel> row = source.GetPixelRowSpan(y);
for (int x = bounds.Left; x < bounds.Right; x++) for (int x = bounds.Left; x < bounds.Right; x++)
{ {
TPixel sourcePixel = row[x]; TPixel sourcePixel = row[x];
pixelMap.GetClosestColor(sourcePixel, out TPixel transformed); TPixel transformed = processor.GetPaletteColor(sourcePixel, palette);
this.Dither(source, bounds, sourcePixel, transformed, x, y, scale); this.Dither(source, bounds, sourcePixel, transformed, x, y, scale);
row[x] = transformed; row[x] = transformed;
} }

21
src/ImageSharp/Processing/Processors/Dithering/IDither.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;
@ -19,15 +18,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// <typeparam name="TFrameQuantizer">The type of frame quantizer.</typeparam> /// <typeparam name="TFrameQuantizer">The type of frame quantizer.</typeparam>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="quantizer">The frame quantizer.</param> /// <param name="quantizer">The frame quantizer.</param>
/// <param name="palette">The quantized palette.</param>
/// <param name="source">The source image.</param> /// <param name="source">The source image.</param>
/// <param name="output">The output target</param> /// <param name="destination">The destination quantized frame.</param>
/// <param name="bounds">The region of interest bounds.</param> /// <param name="bounds">The region of interest bounds.</param>
void ApplyQuantizationDither<TFrameQuantizer, TPixel>( void ApplyQuantizationDither<TFrameQuantizer, TPixel>(
ref TFrameQuantizer quantizer, ref TFrameQuantizer quantizer,
ReadOnlyMemory<TPixel> palette,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
Memory<byte> output, QuantizedFrame<TPixel> destination,
Rectangle bounds) Rectangle bounds)
where TFrameQuantizer : struct, IFrameQuantizer<TPixel> where TFrameQuantizer : struct, IFrameQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel>; where TPixel : struct, IPixel<TPixel>;
@ -36,18 +33,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// Transforms the image frame applying a dither matrix. /// Transforms the image frame applying a dither matrix.
/// This method should be treated as destructive, altering the input pixels. /// This method should be treated as destructive, altering the input pixels.
/// </summary> /// </summary>
/// <typeparam name="TPaletteDitherImageProcessor">The type of palette dithering processor.</typeparam>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The configuration.</param> /// <param name="processor">The palette dithering processor.</param>
/// <param name="palette">The quantized palette.</param>
/// <param name="source">The source image.</param> /// <param name="source">The source image.</param>
/// <param name="bounds">The region of interest bounds.</param> /// <param name="bounds">The region of interest bounds.</param>
/// <param name="scale">The dithering scale used to adjust the amount of dither. Range 0..1.</param> void ApplyPaletteDither<TPaletteDitherImageProcessor, TPixel>(
void ApplyPaletteDither<TPixel>( in TPaletteDitherImageProcessor processor,
Configuration configuration,
ReadOnlyMemory<TPixel> palette,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
Rectangle bounds, Rectangle bounds)
float scale) where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>; where TPixel : struct, IPixel<TPixel>;
} }
} }

39
src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs

@ -0,0 +1,39 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
/// <summary>
/// Implements an algorithm to alter the pixels of an image via palette dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public interface IPaletteDitherImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Gets the configration instance to use when performing operations.
/// </summary>
public Configuration Configuration { get; }
/// <summary>
/// Gets the dithering palette.
/// </summary>
ReadOnlyMemory<TPixel> Palette { get; }
/// <summary>
/// Gets the dithering scale used to adjust the amount of dither. Range 0..1.
/// </summary>
float DitherScale { get; }
/// <summary>
/// Returns the color from the dithering palette corresponding to the given color.
/// </summary>
/// <param name="color">The color to match.</param>
/// <param name="palette">The output color palette.</param>
/// <returns>The <typeparamref name="TPixel"/> match.</returns>
TPixel GetPaletteColor(TPixel color, ReadOnlySpan<TPixel> palette);
}
}

77
src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs

@ -105,9 +105,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void ApplyQuantizationDither<TFrameQuantizer, TPixel>( public void ApplyQuantizationDither<TFrameQuantizer, TPixel>(
ref TFrameQuantizer quantizer, ref TFrameQuantizer quantizer,
ReadOnlyMemory<TPixel> palette,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
Memory<byte> output, QuantizedFrame<TPixel> destination,
Rectangle bounds) Rectangle bounds)
where TFrameQuantizer : struct, IFrameQuantizer<TPixel> where TFrameQuantizer : struct, IFrameQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
@ -116,10 +115,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
ref quantizer, ref quantizer,
in Unsafe.AsRef(this), in Unsafe.AsRef(this),
source, source,
output, destination,
bounds, bounds);
palette,
ImageMaths.GetBitsNeededForColorDepth(palette.Span.Length));
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
quantizer.Configuration, quantizer.Configuration,
@ -129,24 +126,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void ApplyPaletteDither<TPixel>( public void ApplyPaletteDither<TPaletteDitherImageProcessor, TPixel>(
Configuration configuration, in TPaletteDitherImageProcessor processor,
ReadOnlyMemory<TPixel> palette,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
Rectangle bounds, Rectangle bounds)
float scale) where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
var ditherOperation = new PaletteDitherRowIntervalOperation<TPixel>( var ditherOperation = new PaletteDitherRowIntervalOperation<TPaletteDitherImageProcessor, TPixel>(
in processor,
in Unsafe.AsRef(this), in Unsafe.AsRef(this),
source, source,
bounds, bounds);
palette,
scale,
ImageMaths.GetBitsNeededForColorDepth(palette.Span.Length));
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
configuration, processor.Configuration,
bounds, bounds,
in ditherOperation); in ditherOperation);
} }
@ -207,7 +201,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
private readonly TFrameQuantizer quantizer; private readonly TFrameQuantizer quantizer;
private readonly OrderedDither dither; private readonly OrderedDither dither;
private readonly ImageFrame<TPixel> source; private readonly ImageFrame<TPixel> source;
private readonly Memory<byte> output; private readonly QuantizedFrame<TPixel> destination;
private readonly Rectangle bounds; private readonly Rectangle bounds;
private readonly ReadOnlyMemory<TPixel> palette; private readonly ReadOnlyMemory<TPixel> palette;
private readonly int bitDepth; private readonly int bitDepth;
@ -217,84 +211,79 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
ref TFrameQuantizer quantizer, ref TFrameQuantizer quantizer,
in OrderedDither dither, in OrderedDither dither,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
Memory<byte> output, QuantizedFrame<TPixel> destination,
Rectangle bounds, Rectangle bounds)
ReadOnlyMemory<TPixel> palette,
int bitDepth)
{ {
this.quantizer = quantizer; this.quantizer = quantizer;
this.dither = dither; this.dither = dither;
this.source = source; this.source = source;
this.output = output; this.destination = destination;
this.bounds = bounds; this.bounds = bounds;
this.palette = palette; this.palette = destination.Palette;
this.bitDepth = bitDepth; this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(destination.Palette.Span.Length);
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows) public void Invoke(in RowInterval rows)
{ {
ReadOnlySpan<TPixel> paletteSpan = this.palette.Span; ReadOnlySpan<TPixel> paletteSpan = this.palette.Span;
Span<byte> outputSpan = this.output.Span;
int width = this.bounds.Width;
int offsetY = this.bounds.Top; int offsetY = this.bounds.Top;
int offsetX = this.bounds.Left; int offsetX = this.bounds.Left;
float scale = this.quantizer.Options.DitherScale; float scale = this.quantizer.Options.DitherScale;
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
{ {
Span<TPixel> row = this.source.GetPixelRowSpan(y); Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y);
int rowStart = (y - offsetY) * width; Span<byte> destinationRow = this.destination.GetPixelRowSpan(y - offsetY);
// TODO: This can be a bulk operation.
for (int x = this.bounds.Left; x < this.bounds.Right; x++) for (int x = this.bounds.Left; x < this.bounds.Right; x++)
{ {
TPixel dithered = this.dither.Dither(row[x], x, y, this.bitDepth, scale); TPixel dithered = this.dither.Dither(sourceRow[x], x, y, this.bitDepth, scale);
outputSpan[rowStart + x - offsetX] = this.quantizer.GetQuantizedColor(dithered, paletteSpan, out TPixel _); destinationRow[x - offsetX] = this.quantizer.GetQuantizedColor(dithered, paletteSpan, out TPixel _);
} }
} }
} }
} }
private readonly struct PaletteDitherRowIntervalOperation<TPixel> : IRowIntervalOperation private readonly struct PaletteDitherRowIntervalOperation<TPaletteDitherImageProcessor, TPixel> : IRowIntervalOperation
where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private readonly TPaletteDitherImageProcessor processor;
private readonly OrderedDither dither; private readonly OrderedDither dither;
private readonly ImageFrame<TPixel> source; private readonly ImageFrame<TPixel> source;
private readonly Rectangle bounds; private readonly Rectangle bounds;
private readonly EuclideanPixelMap<TPixel> pixelMap;
private readonly float scale; private readonly float scale;
private readonly int bitDepth; private readonly int bitDepth;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public PaletteDitherRowIntervalOperation( public PaletteDitherRowIntervalOperation(
in TPaletteDitherImageProcessor processor,
in OrderedDither dither, in OrderedDither dither,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
Rectangle bounds, Rectangle bounds)
ReadOnlyMemory<TPixel> palette,
float scale,
int bitDepth)
{ {
this.processor = processor;
this.dither = dither; this.dither = dither;
this.source = source; this.source = source;
this.bounds = bounds; this.bounds = bounds;
this.pixelMap = new EuclideanPixelMap<TPixel>(palette); this.scale = processor.DitherScale;
this.scale = scale; this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(processor.Palette.Span.Length);
this.bitDepth = bitDepth;
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows) public void Invoke(in RowInterval rows)
{ {
ReadOnlySpan<TPixel> paletteSpan = this.processor.Palette.Span;
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
{ {
Span<TPixel> row = this.source.GetPixelRowSpan(y); Span<TPixel> row = this.source.GetPixelRowSpan(y);
for (int x = this.bounds.Left; x < this.bounds.Right; x++) for (int x = this.bounds.Left; x < this.bounds.Right; x++)
{ {
TPixel dithered = this.dither.Dither(row[x], x, y, this.bitDepth, this.scale); ref TPixel sourcePixel = ref row[x];
this.pixelMap.GetClosestColor(dithered, out TPixel transformed); TPixel dithered = this.dither.Dither(sourcePixel, x, y, this.bitDepth, this.scale);
row[x] = transformed; sourcePixel = this.processor.GetPaletteColor(dithered, paletteSpan);
} }
} }
} }

78
src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs

@ -3,7 +3,9 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Processing.Processors.Dithering namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{ {
@ -14,11 +16,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
internal sealed class PaletteDitherProcessor<TPixel> : ImageProcessor<TPixel> internal sealed class PaletteDitherProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private readonly int paletteLength; private readonly DitherProcessor ditherProcessor;
private readonly IDither dither; private readonly IDither dither;
private readonly float ditherScale; private IMemoryOwner<TPixel> paletteMemory;
private readonly ReadOnlyMemory<Color> sourcePalette;
private IMemoryOwner<TPixel> palette;
private bool isDisposed; private bool isDisposed;
/// <summary> /// <summary>
@ -31,37 +31,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
public PaletteDitherProcessor(Configuration configuration, PaletteDitherProcessor definition, Image<TPixel> source, Rectangle sourceRectangle) public PaletteDitherProcessor(Configuration configuration, PaletteDitherProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle) : base(configuration, source, sourceRectangle)
{ {
this.paletteLength = definition.Palette.Span.Length;
this.dither = definition.Dither; this.dither = definition.Dither;
this.ditherScale = definition.DitherScale;
this.sourcePalette = definition.Palette;
}
/// <inheritdoc/> ReadOnlySpan<Color> sourcePalette = definition.Palette.Span;
protected override void OnFrameApply(ImageFrame<TPixel> source) this.paletteMemory = this.Configuration.MemoryAllocator.Allocate<TPixel>(sourcePalette.Length);
{ Color.ToPixel(this.Configuration, sourcePalette, this.paletteMemory.Memory.Span);
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
this.dither.ApplyPaletteDither( this.ditherProcessor = new DitherProcessor(
this.Configuration, this.Configuration,
this.palette.Memory, this.paletteMemory.Memory,
source, definition.DitherScale);
interest,
this.ditherScale);
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void BeforeFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
// Lazy init palettes: var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
if (this.palette is null) this.dither.ApplyPaletteDither(in this.ditherProcessor, source, interest);
{
this.palette = this.Configuration.MemoryAllocator.Allocate<TPixel>(this.paletteLength);
ReadOnlySpan<Color> sourcePalette = this.sourcePalette.Span;
Color.ToPixel(this.Configuration, sourcePalette, this.palette.Memory.Span);
}
base.BeforeFrameApply(source);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -74,13 +60,47 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
if (disposing) if (disposing)
{ {
this.palette?.Dispose(); this.paletteMemory?.Dispose();
} }
this.palette = null; this.paletteMemory = null;
this.isDisposed = true; this.isDisposed = true;
base.Dispose(disposing); base.Dispose(disposing);
} }
/// <summary>
/// Used to allow inlining of calls to
/// <see cref="IPaletteDitherImageProcessor{TPixel}.GetPaletteColor(TPixel, ReadOnlySpan{TPixel})"/>.
/// </summary>
private readonly struct DitherProcessor : IPaletteDitherImageProcessor<TPixel>
{
private readonly EuclideanPixelMap<TPixel> pixelMap;
[MethodImpl(InliningOptions.ShortMethod)]
public DitherProcessor(
Configuration configuration,
ReadOnlyMemory<TPixel> palette,
float ditherScale)
{
this.Configuration = configuration;
this.pixelMap = new EuclideanPixelMap<TPixel>(palette);
this.Palette = palette;
this.DitherScale = ditherScale;
}
public Configuration Configuration { get; }
public ReadOnlyMemory<TPixel> Palette { get; }
public float DitherScale { get; }
[MethodImpl(InliningOptions.ShortMethod)]
public TPixel GetPaletteColor(TPixel color, ReadOnlySpan<TPixel> palette)
{
this.pixelMap.GetClosestColor(color, out TPixel match);
return match;
}
}
} }
} }

7
src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs

@ -24,6 +24,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Initializes a new instance of the <see cref="EuclideanPixelMap{TPixel}"/> struct. /// Initializes a new instance of the <see cref="EuclideanPixelMap{TPixel}"/> struct.
/// </summary> /// </summary>
/// <param name="palette">The color palette to map from.</param> /// <param name="palette">The color palette to map from.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public EuclideanPixelMap(ReadOnlyMemory<TPixel> palette) public EuclideanPixelMap(ReadOnlyMemory<TPixel> palette)
{ {
Guard.MustBeGreaterThan(palette.Length, 0, nameof(palette)); Guard.MustBeGreaterThan(palette.Length, 0, nameof(palette));
@ -40,7 +41,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
} }
/// <inheritdoc/> /// <inheritdoc/>
public ReadOnlyMemory<TPixel> Palette { get; } public ReadOnlyMemory<TPixel> Palette
{
[MethodImpl(InliningOptions.ShortMethod)]
get;
}
/// <inheritdoc/> /// <inheritdoc/>
public override bool Equals(object obj) public override bool Equals(object obj)

34
src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs

@ -41,18 +41,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
MemoryAllocator memoryAllocator = quantizer.Configuration.MemoryAllocator; MemoryAllocator memoryAllocator = quantizer.Configuration.MemoryAllocator;
var quantizedFrame = new QuantizedFrame<TPixel>(memoryAllocator, interest.Width, interest.Height, palette); var quantizedFrame = new QuantizedFrame<TPixel>(memoryAllocator, interest.Width, interest.Height, palette);
Memory<byte> output = quantizedFrame.GetWritablePixelMemory();
if (quantizer.Options.Dither is null) if (quantizer.Options.Dither is null)
{ {
SecondPass(ref quantizer, source, interest, output, palette); SecondPass(ref quantizer, source, quantizedFrame, 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, interest, output, palette); SecondPass(ref quantizer, clone, quantizedFrame, interest);
} }
} }
@ -63,9 +62,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private static void SecondPass<TFrameQuantizer, TPixel>( private static void SecondPass<TFrameQuantizer, TPixel>(
ref TFrameQuantizer quantizer, ref TFrameQuantizer quantizer,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
Rectangle bounds, QuantizedFrame<TPixel> destination,
Memory<byte> output, Rectangle bounds)
ReadOnlyMemory<TPixel> palette)
where TFrameQuantizer : struct, IFrameQuantizer<TPixel> where TFrameQuantizer : struct, IFrameQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
@ -73,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (dither is null) if (dither is null)
{ {
var operation = new RowIntervalOperation<TFrameQuantizer, TPixel>(quantizer, source, output, bounds, palette); var operation = new RowIntervalOperation<TFrameQuantizer, TPixel>(quantizer, source, destination, bounds);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
quantizer.Configuration, quantizer.Configuration,
bounds, bounds,
@ -82,7 +80,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
return; return;
} }
dither.ApplyQuantizationDither(ref quantizer, palette, source, output, bounds); dither.ApplyQuantizationDither(ref quantizer, source, destination, bounds);
} }
private readonly struct RowIntervalOperation<TFrameQuantizer, TPixel> : IRowIntervalOperation private readonly struct RowIntervalOperation<TFrameQuantizer, TPixel> : IRowIntervalOperation
@ -91,7 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{ {
private readonly TFrameQuantizer quantizer; private readonly TFrameQuantizer quantizer;
private readonly ImageFrame<TPixel> source; private readonly ImageFrame<TPixel> source;
private readonly Memory<byte> output; private readonly QuantizedFrame<TPixel> destination;
private readonly Rectangle bounds; private readonly Rectangle bounds;
private readonly ReadOnlyMemory<TPixel> palette; private readonly ReadOnlyMemory<TPixel> palette;
@ -99,35 +97,31 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public RowIntervalOperation( public RowIntervalOperation(
in TFrameQuantizer quantizer, in TFrameQuantizer quantizer,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
Memory<byte> output, QuantizedFrame<TPixel> destination,
Rectangle bounds, Rectangle bounds)
ReadOnlyMemory<TPixel> palette)
{ {
this.quantizer = quantizer; this.quantizer = quantizer;
this.source = source; this.source = source;
this.output = output; this.destination = destination;
this.bounds = bounds; this.bounds = bounds;
this.palette = palette; this.palette = destination.Palette;
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows) public void Invoke(in RowInterval rows)
{ {
ReadOnlySpan<TPixel> paletteSpan = this.palette.Span; ReadOnlySpan<TPixel> paletteSpan = this.palette.Span;
Span<byte> outputSpan = this.output.Span;
int width = this.bounds.Width;
int offsetY = this.bounds.Top; int offsetY = this.bounds.Top;
int offsetX = this.bounds.Left; int offsetX = this.bounds.Left;
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
{ {
Span<TPixel> row = this.source.GetPixelRowSpan(y); Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y);
int rowStart = (y - offsetY) * width; Span<byte> destinationRow = this.destination.GetPixelRowSpan(y - offsetY);
// TODO: This can be a bulk operation.
for (int x = this.bounds.Left; x < this.bounds.Right; x++) for (int x = this.bounds.Left; x < this.bounds.Right; x++)
{ {
outputSpan[rowStart + x - offsetX] = this.quantizer.GetQuantizedColor(row[x], paletteSpan, out TPixel _); destinationRow[x - offsetX] = this.quantizer.GetQuantizedColor(sourceRow[x], paletteSpan, out TPixel _);
} }
} }
} }

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

@ -31,9 +31,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <returns> /// <returns>
/// A <see cref="QuantizedFrame{TPixel}"/> representing a quantized version of the source frame pixels. /// A <see cref="QuantizedFrame{TPixel}"/> representing a quantized version of the source frame pixels.
/// </returns> /// </returns>
QuantizedFrame<TPixel> QuantizeFrame( QuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds);
ImageFrame<TPixel> source,
Rectangle bounds);
/// <summary> /// <summary>
/// Builds the quantized palette from the given image frame and bounds. /// Builds the quantized palette from the given image frame and bounds.
@ -44,7 +42,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
ReadOnlyMemory<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds); ReadOnlyMemory<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds);
/// <summary> /// <summary>
/// Returns the index and color from the quantized palette corresponding to the give to the given color. /// Returns the index and color from the quantized palette corresponding to the given color.
/// </summary> /// </summary>
/// <param name="color">The color to match.</param> /// <param name="color">The color to match.</param>
/// <param name="palette">The output color palette.</param> /// <param name="palette">The output color palette.</param>

11
src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs

@ -57,16 +57,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
/// <returns>The <see cref="Span{T}"/></returns> /// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public ReadOnlySpan<byte> GetPixelSpan() => this.pixels.GetSpan(); public Span<byte> GetPixelSpan() => this.pixels.GetSpan();
/// <summary> /// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory /// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the the first pixel on that row. /// at row <paramref name="rowIndex"/> beginning from the the first pixel on that row.
/// </summary> /// </summary>
/// <param name="rowIndex">The row.</param> /// <param name="rowIndex">The row.</param>
/// <returns>The pixel row as a <see cref="ReadOnlySpan{T}"/>.</returns> /// <returns>The pixel row as a <see cref="Span{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public ReadOnlySpan<byte> GetRowSpan(int rowIndex) public Span<byte> GetPixelRowSpan(int rowIndex)
=> this.GetPixelSpan().Slice(rowIndex * this.Width, this.Width); => this.GetPixelSpan().Slice(rowIndex * this.Width, this.Width);
/// <inheritdoc/> /// <inheritdoc/>
@ -82,10 +82,5 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.pixels = null; this.pixels = null;
this.Palette = null; this.Palette = null;
} }
/// <summary>
/// Get the non-readonly memory of pixel data so <see cref="IFrameQuantizer{TPixel}"/> can fill it.
/// </summary>
internal Memory<byte> GetWritablePixelMemory() => this.pixels.Memory;
} }
} }

Loading…
Cancel
Save