Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix boxed quantization and update refs #2582

Merged
merged 1 commit into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public OctreeQuantizer(Configuration configuration, QuantizerOptions options)
public QuantizerOptions Options { get; }

/// <inheritdoc/>
public ReadOnlyMemory<TPixel> Palette
public readonly ReadOnlyMemory<TPixel> Palette
{
get
{
Expand All @@ -72,16 +72,14 @@ public ReadOnlyMemory<TPixel> Palette
/// <inheritdoc/>
public void AddPaletteColors(Buffer2DRegion<TPixel> pixelRegion)
{
Rectangle bounds = pixelRegion.Rectangle;
Buffer2D<TPixel> source = pixelRegion.Buffer;
using (IMemoryOwner<Rgba32> buffer = this.Configuration.MemoryAllocator.Allocate<Rgba32>(bounds.Width))
using (IMemoryOwner<Rgba32> buffer = this.Configuration.MemoryAllocator.Allocate<Rgba32>(pixelRegion.Width))
{
Span<Rgba32> bufferSpan = buffer.GetSpan();

// Loop through each row
for (int y = bounds.Top; y < bounds.Bottom; y++)
for (int y = 0; y < pixelRegion.Height; y++)
{
Span<TPixel> row = source.DangerousGetRowSpan(y).Slice(bounds.Left, bounds.Width);
Span<TPixel> row = pixelRegion.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToRgba32(this.Configuration, row, bufferSpan);

for (int x = 0; x < bufferSpan.Length; x++)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public QuantizeProcessor(Configuration configuration, IQuantizer quantizer, Imag
/// <inheritdoc />
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
var interest = Rectangle.Intersect(source.Bounds(), this.SourceRectangle);
Rectangle interest = Rectangle.Intersect(source.Bounds(), this.SourceRectangle);

Configuration configuration = this.Configuration;
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(configuration);
Expand All @@ -43,14 +43,14 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)
int offsetX = interest.Left;
Buffer2D<TPixel> sourceBuffer = source.PixelBuffer;

for (int y = interest.Y; y < interest.Height; y++)
for (int y = 0; y < quantized.Height; y++)
{
Span<TPixel> row = sourceBuffer.DangerousGetRowSpan(y);
ReadOnlySpan<byte> quantizedRow = quantized.DangerousGetRowSpan(y - offsetY);
ReadOnlySpan<byte> quantizedRow = quantized.DangerousGetRowSpan(y);
Span<TPixel> row = sourceBuffer.DangerousGetRowSpan(y + offsetY);

for (int x = interest.Left; x < interest.Right; x++)
for (int x = 0; x < quantized.Width; x++)
{
row[x] = paletteSpan[quantizedRow[x - offsetX]];
row[x + offsetX] = paletteSpan[quantizedRow[x]];
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,14 @@ public static void CheckPaletteState<TPixel>(in ReadOnlyMemory<TPixel> palette)
int offsetY = bounds.Top;
int offsetX = bounds.Left;

for (int y = bounds.Y; y < bounds.Height; y++)
for (int y = 0; y < destination.Height; y++)
{
Span<TPixel> sourceRow = sourceBuffer.DangerousGetRowSpan(y);
Span<byte> destinationRow = destination.GetWritablePixelRowSpanUnsafe(y - offsetY);
Span<TPixel> sourceRow = sourceBuffer.DangerousGetRowSpan(y + offsetY);
Span<byte> destinationRow = destination.GetWritablePixelRowSpanUnsafe(y);

for (int x = bounds.Left; x < bounds.Right; x++)
for (int x = 0; x < destination.Width; x++)
{
destinationRow[x - offsetX] = Unsafe.AsRef(quantizer).GetQuantizedColor(sourceRow[x], out TPixel _);
destinationRow[x] = Unsafe.AsRef(quantizer).GetQuantizedColor(sourceRow[x + offsetX], out TPixel _);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,11 @@ public WuQuantizer(Configuration configuration, QuantizerOptions options)
/// <inheritdoc/>
public void AddPaletteColors(Buffer2DRegion<TPixel> pixelRegion)
{
Rectangle bounds = pixelRegion.Rectangle;
Buffer2D<TPixel> source = pixelRegion.Buffer;

this.Build3DHistogram(source, bounds);
// TODO: Something is destroying the existing palette when adding new colors.
// When the QuantizingImageEncoder.PixelSamplingStrategy is DefaultPixelSamplingStrategy
// this leads to performance issues + the palette is not preserved.
// https://github.com/SixLabors/ImageSharp/issues/2498
this.Build3DHistogram(pixelRegion);
this.Get3DMoments(this.memoryAllocator);
this.BuildCube();

Expand Down Expand Up @@ -360,19 +361,18 @@ private static Moment Top(ref Box cube, int direction, int position, ReadOnlySpa
/// <summary>
/// Builds a 3-D color histogram of <c>counts, r/g/b, c^2</c>.
/// </summary>
/// <param name="source">The source data.</param>
/// <param name="bounds">The bounds within the source image to quantize.</param>
private readonly void Build3DHistogram(Buffer2D<TPixel> source, Rectangle bounds)
/// <param name="source">The source pixel data.</param>
private readonly void Build3DHistogram(Buffer2DRegion<TPixel> source)
{
Span<Moment> momentSpan = this.momentsOwner.GetSpan();

// Build up the 3-D color histogram
using IMemoryOwner<Rgba32> buffer = this.memoryAllocator.Allocate<Rgba32>(bounds.Width);
using IMemoryOwner<Rgba32> buffer = this.memoryAllocator.Allocate<Rgba32>(source.Width);
Span<Rgba32> bufferSpan = buffer.GetSpan();

for (int y = bounds.Top; y < bounds.Bottom; y++)
for (int y = 0; y < source.Height; y++)
{
Span<TPixel> row = source.DangerousGetRowSpan(y).Slice(bounds.Left, bounds.Width);
Span<TPixel> row = source.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToRgba32(this.Configuration, row, bufferSpan);

for (int x = 0; x < bufferSpan.Length; x++)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,74 +12,66 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization;
[Trait("Category", "Processors")]
public class QuantizerTests
{
/// <summary>
/// Something is causing tests to fail on NETFX in CI.
/// Could be a JIT error as everything runs well and is identical to .NET Core output.
/// Not worth investigating for now.
/// <see href="https://github.com/SixLabors/ImageSharp/pull/1114/checks?check_run_id=448891164#step:11:631"/>
/// </summary>
private static readonly bool SkipAllQuantizerTests = TestEnvironment.IsFramework;

public static readonly string[] CommonTestImages =
{
TestImages.Png.CalliphoraPartial,
TestImages.Png.Bike
};

private static readonly QuantizerOptions NoDitherOptions = new QuantizerOptions { Dither = null };
private static readonly QuantizerOptions DiffuserDitherOptions = new QuantizerOptions { Dither = KnownDitherings.FloydSteinberg };
private static readonly QuantizerOptions OrderedDitherOptions = new QuantizerOptions { Dither = KnownDitherings.Bayer8x8 };
private static readonly QuantizerOptions NoDitherOptions = new() { Dither = null };
private static readonly QuantizerOptions DiffuserDitherOptions = new() { Dither = KnownDitherings.FloydSteinberg };
private static readonly QuantizerOptions OrderedDitherOptions = new() { Dither = KnownDitherings.Bayer8x8 };

private static readonly QuantizerOptions Diffuser0_ScaleDitherOptions = new QuantizerOptions
private static readonly QuantizerOptions Diffuser0_ScaleDitherOptions = new()
{
Dither = KnownDitherings.FloydSteinberg,
DitherScale = 0F
};

private static readonly QuantizerOptions Diffuser0_25_ScaleDitherOptions = new QuantizerOptions
private static readonly QuantizerOptions Diffuser0_25_ScaleDitherOptions = new()
{
Dither = KnownDitherings.FloydSteinberg,
DitherScale = .25F
};

private static readonly QuantizerOptions Diffuser0_5_ScaleDitherOptions = new QuantizerOptions
private static readonly QuantizerOptions Diffuser0_5_ScaleDitherOptions = new()
{
Dither = KnownDitherings.FloydSteinberg,
DitherScale = .5F
};

private static readonly QuantizerOptions Diffuser0_75_ScaleDitherOptions = new QuantizerOptions
private static readonly QuantizerOptions Diffuser0_75_ScaleDitherOptions = new()
{
Dither = KnownDitherings.FloydSteinberg,
DitherScale = .75F
};

private static readonly QuantizerOptions Ordered0_ScaleDitherOptions = new QuantizerOptions
private static readonly QuantizerOptions Ordered0_ScaleDitherOptions = new()
{
Dither = KnownDitherings.Bayer8x8,
DitherScale = 0F
};

private static readonly QuantizerOptions Ordered0_25_ScaleDitherOptions = new QuantizerOptions
private static readonly QuantizerOptions Ordered0_25_ScaleDitherOptions = new()
{
Dither = KnownDitherings.Bayer8x8,
DitherScale = .25F
};

private static readonly QuantizerOptions Ordered0_5_ScaleDitherOptions = new QuantizerOptions
private static readonly QuantizerOptions Ordered0_5_ScaleDitherOptions = new()
{
Dither = KnownDitherings.Bayer8x8,
DitherScale = .5F
};

private static readonly QuantizerOptions Ordered0_75_ScaleDitherOptions = new QuantizerOptions
private static readonly QuantizerOptions Ordered0_75_ScaleDitherOptions = new()
{
Dither = KnownDitherings.Bayer8x8,
DitherScale = .75F
};

public static readonly TheoryData<IQuantizer> Quantizers
= new TheoryData<IQuantizer>
= new()
{
// Known uses error diffusion by default.
KnownQuantizers.Octree,
Expand All @@ -97,7 +89,7 @@ public class QuantizerTests
};

public static readonly TheoryData<IQuantizer> DitherScaleQuantizers
= new TheoryData<IQuantizer>
= new()
{
new OctreeQuantizer(Diffuser0_ScaleDitherOptions),
new WebSafePaletteQuantizer(Diffuser0_ScaleDitherOptions),
Expand Down Expand Up @@ -151,7 +143,7 @@ public class QuantizerTests
};

public static readonly TheoryData<IDither> DefaultInstanceDitherers
= new TheoryData<IDither>
= new()
{
default(ErrorDither),
default(OrderedDither)
Expand All @@ -164,11 +156,6 @@ public class QuantizerTests
public void ApplyQuantizationInBox<TPixel>(TestImageProvider<TPixel> provider, IQuantizer quantizer)
where TPixel : unmanaged, IPixel<TPixel>
{
if (SkipAllQuantizerTests)
{
return;
}

string quantizerName = quantizer.GetType().Name;
string ditherName = quantizer.Options.Dither?.GetType()?.Name ?? "NoDither";
string testOutputDetails = $"{quantizerName}_{ditherName}";
Expand All @@ -185,11 +172,6 @@ public void ApplyQuantizationInBox<TPixel>(TestImageProvider<TPixel> provider, I
public void ApplyQuantization<TPixel>(TestImageProvider<TPixel> provider, IQuantizer quantizer)
where TPixel : unmanaged, IPixel<TPixel>
{
if (SkipAllQuantizerTests)
{
return;
}

string quantizerName = quantizer.GetType().Name;
string ditherName = quantizer.Options.Dither?.GetType()?.Name ?? "NoDither";
string testOutputDetails = $"{quantizerName}_{ditherName}";
Expand All @@ -206,11 +188,6 @@ public void ApplyQuantization<TPixel>(TestImageProvider<TPixel> provider, IQuant
public void ApplyQuantizationWithDitheringScale<TPixel>(TestImageProvider<TPixel> provider, IQuantizer quantizer)
where TPixel : unmanaged, IPixel<TPixel>
{
if (SkipAllQuantizerTests)
{
return;
}

string quantizerName = quantizer.GetType().Name;
string ditherName = quantizer.Options.Dither.GetType().Name;
float ditherScale = quantizer.Options.DitherScale;
Expand All @@ -229,8 +206,8 @@ public void ShouldThrowForDefaultDitherInstance(IDither dither)
{
void Command()
{
using var image = new Image<Rgba32>(10, 10);
var quantizer = new WebSafePaletteQuantizer();
using Image<Rgba32> image = new(10, 10);
WebSafePaletteQuantizer quantizer = new();
quantizer.Options.Dither = dither;
image.Mutate(x => x.Quantize(quantizer));
}
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.