Browse Source

Merge pull request #1112 from SixLabors/js/wu-color-moment

Use Moment struct in WuQuantizer.
af/octree-no-pixelmap
James Jackson-South 6 years ago
committed by GitHub
parent
commit
4b22fcb16c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 651
      src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs

651
src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs

@ -10,8 +10,6 @@ using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
// TODO: Isn't an AOS ("array of structures") layout more efficient & more readable than SOA ("structure of arrays") for this particular use case?
// (T, R, G, B, A, M2) could be grouped together! Investigate a ColorMoment struct.
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
/// <summary>
@ -69,34 +67,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount;
/// <summary>
/// Moment of <c>P(c)</c>.
/// Color moments.
/// </summary>
private IMemoryOwner<long> vwt;
/// <summary>
/// Moment of <c>r*P(c)</c>.
/// </summary>
private IMemoryOwner<long> vmr;
/// <summary>
/// Moment of <c>g*P(c)</c>.
/// </summary>
private IMemoryOwner<long> vmg;
/// <summary>
/// Moment of <c>b*P(c)</c>.
/// </summary>
private IMemoryOwner<long> vmb;
/// <summary>
/// Moment of <c>a*P(c)</c>.
/// </summary>
private IMemoryOwner<long> vma;
/// <summary>
/// Moment of <c>c^2*P(c)</c>.
/// </summary>
private IMemoryOwner<double> m2;
private IMemoryOwner<Moment> moments;
/// <summary>
/// Color space tag.
@ -148,15 +121,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
: base(configuration, quantizer, false)
{
this.memoryAllocator = this.Configuration.MemoryAllocator;
this.vwt = this.memoryAllocator.Allocate<long>(TableLength, AllocationOptions.Clean);
this.vmr = this.memoryAllocator.Allocate<long>(TableLength, AllocationOptions.Clean);
this.vmg = this.memoryAllocator.Allocate<long>(TableLength, AllocationOptions.Clean);
this.vmb = this.memoryAllocator.Allocate<long>(TableLength, AllocationOptions.Clean);
this.vma = this.memoryAllocator.Allocate<long>(TableLength, AllocationOptions.Clean);
this.m2 = this.memoryAllocator.Allocate<double>(TableLength, AllocationOptions.Clean);
this.moments = this.memoryAllocator.Allocate<Moment>(TableLength, AllocationOptions.Clean);
this.tag = this.memoryAllocator.Allocate<byte>(TableLength, AllocationOptions.Clean);
this.colors = maxColors;
}
@ -170,21 +136,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (disposing)
{
this.vwt?.Dispose();
this.vmr?.Dispose();
this.vmg?.Dispose();
this.vmb?.Dispose();
this.vma?.Dispose();
this.m2?.Dispose();
this.moments?.Dispose();
this.tag?.Dispose();
}
this.vwt = null;
this.vmr = null;
this.vmg = null;
this.vmb = null;
this.vma = null;
this.m2 = null;
this.moments = null;
this.tag = null;
this.isDisposed = true;
@ -199,27 +155,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (this.palette is null)
{
this.palette = new TPixel[this.colors];
Span<long> vwtSpan = this.vwt.GetSpan();
Span<long> vmrSpan = this.vmr.GetSpan();
Span<long> vmgSpan = this.vmg.GetSpan();
Span<long> vmbSpan = this.vmb.GetSpan();
Span<long> vmaSpan = this.vma.GetSpan();
ReadOnlySpan<Moment> momentsSpan = this.moments.GetSpan();
for (int k = 0; k < this.colors; k++)
{
this.Mark(ref this.colorCube[k], (byte)k);
float weight = Volume(ref this.colorCube[k], vwtSpan);
Moment moment = Volume(ref this.colorCube[k], momentsSpan);
if (MathF.Abs(weight) > Constants.Epsilon)
if (moment.Weight > 0)
{
float r = Volume(ref this.colorCube[k], vmrSpan);
float g = Volume(ref this.colorCube[k], vmgSpan);
float b = Volume(ref this.colorCube[k], vmbSpan);
float a = Volume(ref this.colorCube[k], vmaSpan);
ref TPixel color = ref this.palette[k];
color.FromScaledVector4(new Vector4(r, g, b, a) / weight / 255F);
color.FromScaledVector4(moment.Normalize());
}
}
}
@ -247,7 +194,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
for (int y = 0; y < height; y++)
{
Span<TPixel> row = source.GetPixelRowSpan(y);
ReadOnlySpan<TPixel> row = source.GetPixelRowSpan(y);
// And loop through each column
for (int x = 0; x < width; x++)
@ -290,7 +237,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <param name="b">The blue value.</param>
/// <param name="a">The alpha value.</param>
/// <returns>The index.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
private static int GetPaletteIndex(int r, int g, int b, int a)
{
return (r << ((IndexBits * 2) + IndexAlphaBits))
@ -307,26 +254,26 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Computes sum over a box of any given statistic.
/// </summary>
/// <param name="cube">The cube.</param>
/// <param name="moment">The moment.</param>
/// <param name="moments">The moment.</param>
/// <returns>The result.</returns>
private static float Volume(ref Box cube, Span<long> moment)
private static Moment Volume(ref Box cube, ReadOnlySpan<Moment> moments)
{
return moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)]
- moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)]
- moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)]
+ moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)]
- moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)]
+ moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)]
+ moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)]
- moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)]
- moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)]
+ moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)]
+ moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)]
- moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)]
+ moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)]
- moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)]
- moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)]
+ moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)];
return moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)]
- moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)]
- moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)]
+ moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)]
- moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)]
+ moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)]
+ moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)]
- moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)]
- moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)]
- moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)]
- moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)]
- moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)];
}
/// <summary>
@ -334,55 +281,55 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary>
/// <param name="cube">The cube.</param>
/// <param name="direction">The direction.</param>
/// <param name="moment">The moment.</param>
/// <param name="moments">The moment.</param>
/// <returns>The result.</returns>
private static long Bottom(ref Box cube, int direction, Span<long> moment)
private static Moment Bottom(ref Box cube, int direction, ReadOnlySpan<Moment> moments)
{
switch (direction)
{
// Red
case 3:
return -moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)]
+ moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)]
+ moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)]
- moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)]
+ moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)]
- moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)]
- moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)]
+ moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)];
return -moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)]
- moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)]
- moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)]
- moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)];
// Green
case 2:
return -moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)]
+ moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)]
+ moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)]
- moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)]
+ moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)]
- moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)]
- moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)]
+ moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)];
return -moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)]
+ moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)]
+ moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)]
- moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)]
- moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)]
- moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)];
// Blue
case 1:
return -moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)]
+ moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)]
+ moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)]
- moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)]
+ moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)]
- moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)]
- moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)]
+ moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)];
return -moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)]
+ moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)]
+ moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)]
- moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)]
- moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)]
- moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)];
// Alpha
case 0:
return -moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)]
+ moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)]
+ moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)]
- moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)]
+ moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)]
- moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)]
- moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)]
+ moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)];
return -moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)]
+ moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)]
+ moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)]
- moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)]
- moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)]
- moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)];
default:
throw new ArgumentOutOfRangeException(nameof(direction));
@ -395,55 +342,55 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <param name="cube">The cube.</param>
/// <param name="direction">The direction.</param>
/// <param name="position">The position.</param>
/// <param name="moment">The moment.</param>
/// <param name="moments">The moment.</param>
/// <returns>The result.</returns>
private static long Top(ref Box cube, int direction, int position, Span<long> moment)
private static Moment Top(ref Box cube, int direction, int position, ReadOnlySpan<Moment> moments)
{
switch (direction)
{
// Red
case 3:
return moment[GetPaletteIndex(position, cube.GMax, cube.BMax, cube.AMax)]
- moment[GetPaletteIndex(position, cube.GMax, cube.BMax, cube.AMin)]
- moment[GetPaletteIndex(position, cube.GMax, cube.BMin, cube.AMax)]
+ moment[GetPaletteIndex(position, cube.GMax, cube.BMin, cube.AMin)]
- moment[GetPaletteIndex(position, cube.GMin, cube.BMax, cube.AMax)]
+ moment[GetPaletteIndex(position, cube.GMin, cube.BMax, cube.AMin)]
+ moment[GetPaletteIndex(position, cube.GMin, cube.BMin, cube.AMax)]
- moment[GetPaletteIndex(position, cube.GMin, cube.BMin, cube.AMin)];
return moments[GetPaletteIndex(position, cube.GMax, cube.BMax, cube.AMax)]
- moments[GetPaletteIndex(position, cube.GMax, cube.BMax, cube.AMin)]
- moments[GetPaletteIndex(position, cube.GMax, cube.BMin, cube.AMax)]
+ moments[GetPaletteIndex(position, cube.GMax, cube.BMin, cube.AMin)]
- moments[GetPaletteIndex(position, cube.GMin, cube.BMax, cube.AMax)]
+ moments[GetPaletteIndex(position, cube.GMin, cube.BMax, cube.AMin)]
+ moments[GetPaletteIndex(position, cube.GMin, cube.BMin, cube.AMax)]
- moments[GetPaletteIndex(position, cube.GMin, cube.BMin, cube.AMin)];
// Green
case 2:
return moment[GetPaletteIndex(cube.RMax, position, cube.BMax, cube.AMax)]
- moment[GetPaletteIndex(cube.RMax, position, cube.BMax, cube.AMin)]
- moment[GetPaletteIndex(cube.RMax, position, cube.BMin, cube.AMax)]
+ moment[GetPaletteIndex(cube.RMax, position, cube.BMin, cube.AMin)]
- moment[GetPaletteIndex(cube.RMin, position, cube.BMax, cube.AMax)]
+ moment[GetPaletteIndex(cube.RMin, position, cube.BMax, cube.AMin)]
+ moment[GetPaletteIndex(cube.RMin, position, cube.BMin, cube.AMax)]
- moment[GetPaletteIndex(cube.RMin, position, cube.BMin, cube.AMin)];
return moments[GetPaletteIndex(cube.RMax, position, cube.BMax, cube.AMax)]
- moments[GetPaletteIndex(cube.RMax, position, cube.BMax, cube.AMin)]
- moments[GetPaletteIndex(cube.RMax, position, cube.BMin, cube.AMax)]
+ moments[GetPaletteIndex(cube.RMax, position, cube.BMin, cube.AMin)]
- moments[GetPaletteIndex(cube.RMin, position, cube.BMax, cube.AMax)]
+ moments[GetPaletteIndex(cube.RMin, position, cube.BMax, cube.AMin)]
+ moments[GetPaletteIndex(cube.RMin, position, cube.BMin, cube.AMax)]
- moments[GetPaletteIndex(cube.RMin, position, cube.BMin, cube.AMin)];
// Blue
case 1:
return moment[GetPaletteIndex(cube.RMax, cube.GMax, position, cube.AMax)]
- moment[GetPaletteIndex(cube.RMax, cube.GMax, position, cube.AMin)]
- moment[GetPaletteIndex(cube.RMax, cube.GMin, position, cube.AMax)]
+ moment[GetPaletteIndex(cube.RMax, cube.GMin, position, cube.AMin)]
- moment[GetPaletteIndex(cube.RMin, cube.GMax, position, cube.AMax)]
+ moment[GetPaletteIndex(cube.RMin, cube.GMax, position, cube.AMin)]
+ moment[GetPaletteIndex(cube.RMin, cube.GMin, position, cube.AMax)]
- moment[GetPaletteIndex(cube.RMin, cube.GMin, position, cube.AMin)];
return moments[GetPaletteIndex(cube.RMax, cube.GMax, position, cube.AMax)]
- moments[GetPaletteIndex(cube.RMax, cube.GMax, position, cube.AMin)]
- moments[GetPaletteIndex(cube.RMax, cube.GMin, position, cube.AMax)]
+ moments[GetPaletteIndex(cube.RMax, cube.GMin, position, cube.AMin)]
- moments[GetPaletteIndex(cube.RMin, cube.GMax, position, cube.AMax)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMax, position, cube.AMin)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMin, position, cube.AMax)]
- moments[GetPaletteIndex(cube.RMin, cube.GMin, position, cube.AMin)];
// Alpha
case 0:
return moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, position)]
- moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, position)]
- moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, position)]
+ moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, position)]
- moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, position)]
+ moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, position)]
+ moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, position)]
- moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, position)];
return moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, position)]
- moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, position)]
- moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, position)]
+ moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, position)]
- moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, position)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, position)]
+ moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, position)]
- moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, position)];
default:
throw new ArgumentOutOfRangeException(nameof(direction));
@ -458,45 +405,31 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <param name="height">The height in pixels of the image.</param>
private void Build3DHistogram(ImageFrame<TPixel> source, int width, int height)
{
Span<long> vwtSpan = this.vwt.GetSpan();
Span<long> vmrSpan = this.vmr.GetSpan();
Span<long> vmgSpan = this.vmg.GetSpan();
Span<long> vmbSpan = this.vmb.GetSpan();
Span<long> vmaSpan = this.vma.GetSpan();
Span<double> m2Span = this.m2.GetSpan();
Span<Moment> momentSpan = this.moments.GetSpan();
// Build up the 3-D color histogram
// Loop through each row
using (IMemoryOwner<Rgba32> rgbaBuffer = this.memoryAllocator.Allocate<Rgba32>(source.Width))
{
for (int y = 0; y < height; y++)
{
Span<TPixel> row = source.GetPixelRowSpan(y);
Span<Rgba32> rgbaSpan = rgbaBuffer.GetSpan();
PixelOperations<TPixel>.Instance.ToRgba32(source.GetConfiguration(), row, rgbaSpan);
ref Rgba32 scanBaseRef = ref MemoryMarshal.GetReference(rgbaSpan);
// And loop through each column
for (int x = 0; x < width; x++)
{
ref Rgba32 rgba = ref Unsafe.Add(ref scanBaseRef, x);
using IMemoryOwner<Rgba32> rgbaBuffer = this.memoryAllocator.Allocate<Rgba32>(source.Width);
Span<Rgba32> rgbaSpan = rgbaBuffer.GetSpan();
ref Rgba32 scanBaseRef = ref MemoryMarshal.GetReference(rgbaSpan);
int r = rgba.R >> (8 - IndexBits);
int g = rgba.G >> (8 - IndexBits);
int b = rgba.B >> (8 - IndexBits);
int a = rgba.A >> (8 - IndexAlphaBits);
for (int y = 0; y < height; y++)
{
Span<TPixel> row = source.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToRgba32(source.GetConfiguration(), row, rgbaSpan);
int index = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1);
// And loop through each column
for (int x = 0; x < width; x++)
{
ref Rgba32 rgba = ref Unsafe.Add(ref scanBaseRef, x);
vwtSpan[index]++;
vmrSpan[index] += rgba.R;
vmgSpan[index] += rgba.G;
vmbSpan[index] += rgba.B;
vmaSpan[index] += rgba.A;
int r = (rgba.R >> (8 - IndexBits)) + 1;
int g = (rgba.G >> (8 - IndexBits)) + 1;
int b = (rgba.B >> (8 - IndexBits)) + 1;
int a = (rgba.A >> (8 - IndexAlphaBits)) + 1;
var vector = new Vector4(rgba.R, rgba.G, rgba.B, rgba.A);
m2Span[index] += Vector4.Dot(vector, vector);
}
int index = GetPaletteIndex(r, g, b, a);
momentSpan[index] += rgba;
}
}
}
@ -507,103 +440,38 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <param name="memoryAllocator">The memory allocator used for allocating buffers.</param>
private void Get3DMoments(MemoryAllocator memoryAllocator)
{
Span<long> vwtSpan = this.vwt.GetSpan();
Span<long> vmrSpan = this.vmr.GetSpan();
Span<long> vmgSpan = this.vmg.GetSpan();
Span<long> vmbSpan = this.vmb.GetSpan();
Span<long> vmaSpan = this.vma.GetSpan();
Span<double> m2Span = this.m2.GetSpan();
using (IMemoryOwner<long> volume = memoryAllocator.Allocate<long>(IndexCount * IndexAlphaCount))
using (IMemoryOwner<long> volumeR = memoryAllocator.Allocate<long>(IndexCount * IndexAlphaCount))
using (IMemoryOwner<long> volumeG = memoryAllocator.Allocate<long>(IndexCount * IndexAlphaCount))
using (IMemoryOwner<long> volumeB = memoryAllocator.Allocate<long>(IndexCount * IndexAlphaCount))
using (IMemoryOwner<long> volumeA = memoryAllocator.Allocate<long>(IndexCount * IndexAlphaCount))
using (IMemoryOwner<double> volume2 = memoryAllocator.Allocate<double>(IndexCount * IndexAlphaCount))
using (IMemoryOwner<long> area = memoryAllocator.Allocate<long>(IndexAlphaCount))
using (IMemoryOwner<long> areaR = memoryAllocator.Allocate<long>(IndexAlphaCount))
using (IMemoryOwner<long> areaG = memoryAllocator.Allocate<long>(IndexAlphaCount))
using (IMemoryOwner<long> areaB = memoryAllocator.Allocate<long>(IndexAlphaCount))
using (IMemoryOwner<long> areaA = memoryAllocator.Allocate<long>(IndexAlphaCount))
using (IMemoryOwner<double> area2 = memoryAllocator.Allocate<double>(IndexAlphaCount))
using IMemoryOwner<Moment> volume = memoryAllocator.Allocate<Moment>(IndexCount * IndexAlphaCount);
using IMemoryOwner<Moment> area = memoryAllocator.Allocate<Moment>(IndexAlphaCount);
Span<Moment> momentSpan = this.moments.GetSpan();
Span<Moment> volumeSpan = volume.GetSpan();
Span<Moment> areaSpan = area.GetSpan();
int baseIndex = GetPaletteIndex(1, 0, 0, 0);
for (int r = 1; r < IndexCount; r++)
{
Span<long> volumeSpan = volume.GetSpan();
Span<long> volumeRSpan = volumeR.GetSpan();
Span<long> volumeGSpan = volumeG.GetSpan();
Span<long> volumeBSpan = volumeB.GetSpan();
Span<long> volumeASpan = volumeA.GetSpan();
Span<double> volume2Span = volume2.GetSpan();
Span<long> areaSpan = area.GetSpan();
Span<long> areaRSpan = areaR.GetSpan();
Span<long> areaGSpan = areaG.GetSpan();
Span<long> areaBSpan = areaB.GetSpan();
Span<long> areaASpan = areaA.GetSpan();
Span<double> area2Span = area2.GetSpan();
for (int r = 1; r < IndexCount; r++)
volumeSpan.Clear();
for (int g = 1; g < IndexCount; g++)
{
volume.Clear();
volumeR.Clear();
volumeG.Clear();
volumeB.Clear();
volumeA.Clear();
volume2.Clear();
for (int g = 1; g < IndexCount; g++)
areaSpan.Clear();
for (int b = 1; b < IndexCount; b++)
{
area.Clear();
areaR.Clear();
areaG.Clear();
areaB.Clear();
areaA.Clear();
area2.Clear();
for (int b = 1; b < IndexCount; b++)
Moment line = default;
for (int a = 1; a < IndexAlphaCount; a++)
{
long line = 0;
long lineR = 0;
long lineG = 0;
long lineB = 0;
long lineA = 0;
double line2 = 0;
for (int a = 1; a < IndexAlphaCount; a++)
{
int ind1 = GetPaletteIndex(r, g, b, a);
line += vwtSpan[ind1];
lineR += vmrSpan[ind1];
lineG += vmgSpan[ind1];
lineB += vmbSpan[ind1];
lineA += vmaSpan[ind1];
line2 += m2Span[ind1];
areaSpan[a] += line;
areaRSpan[a] += lineR;
areaGSpan[a] += lineG;
areaBSpan[a] += lineB;
areaASpan[a] += lineA;
area2Span[a] += line2;
int inv = (b * IndexAlphaCount) + a;
volumeSpan[inv] += areaSpan[a];
volumeRSpan[inv] += areaRSpan[a];
volumeGSpan[inv] += areaGSpan[a];
volumeBSpan[inv] += areaBSpan[a];
volumeASpan[inv] += areaASpan[a];
volume2Span[inv] += area2Span[a];
int ind2 = ind1 - GetPaletteIndex(1, 0, 0, 0);
vwtSpan[ind1] = vwtSpan[ind2] + volumeSpan[inv];
vmrSpan[ind1] = vmrSpan[ind2] + volumeRSpan[inv];
vmgSpan[ind1] = vmgSpan[ind2] + volumeGSpan[inv];
vmbSpan[ind1] = vmbSpan[ind2] + volumeBSpan[inv];
vmaSpan[ind1] = vmaSpan[ind2] + volumeASpan[inv];
m2Span[ind1] = m2Span[ind2] + volume2Span[inv];
}
int ind1 = GetPaletteIndex(r, g, b, a);
line += momentSpan[ind1];
areaSpan[a] += line;
int inv = (b * IndexAlphaCount) + a;
volumeSpan[inv] += areaSpan[a];
int ind2 = ind1 - baseIndex;
momentSpan[ind1] = momentSpan[ind2] + volumeSpan[inv];
}
}
}
@ -617,33 +485,29 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <returns>The <see cref="float"/>.</returns>
private double Variance(ref Box cube)
{
float dr = Volume(ref cube, this.vmr.GetSpan());
float dg = Volume(ref cube, this.vmg.GetSpan());
float db = Volume(ref cube, this.vmb.GetSpan());
float da = Volume(ref cube, this.vma.GetSpan());
Span<double> m2Span = this.m2.GetSpan();
double moment =
m2Span[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)]
- m2Span[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)]
- m2Span[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)]
+ m2Span[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)]
- m2Span[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)]
+ m2Span[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)]
+ m2Span[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)]
- m2Span[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)]
- m2Span[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)]
+ m2Span[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)]
+ m2Span[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)]
- m2Span[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)]
+ m2Span[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)]
- m2Span[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)]
- m2Span[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)]
+ m2Span[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)];
var vector = new Vector4(dr, dg, db, da);
return moment - (Vector4.Dot(vector, vector) / Volume(ref cube, this.vwt.GetSpan()));
ReadOnlySpan<Moment> momentSpan = this.moments.GetSpan();
Moment volume = Volume(ref cube, momentSpan);
Moment variance =
momentSpan[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)]
- momentSpan[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)]
- momentSpan[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)]
+ momentSpan[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)]
- momentSpan[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)]
+ momentSpan[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)]
+ momentSpan[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)]
- momentSpan[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)]
- momentSpan[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)]
+ momentSpan[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)]
+ momentSpan[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)]
- momentSpan[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)]
+ momentSpan[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)]
- momentSpan[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)]
- momentSpan[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)]
+ momentSpan[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)];
var vector = new Vector4(volume.R, volume.G, volume.B, volume.A);
return variance.Moment2 - (Vector4.Dot(vector, vector) / volume.Weight);
}
/// <summary>
@ -658,60 +522,37 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <param name="first">The first position.</param>
/// <param name="last">The last position.</param>
/// <param name="cut">The cutting point.</param>
/// <param name="wholeR">The whole red.</param>
/// <param name="wholeG">The whole green.</param>
/// <param name="wholeB">The whole blue.</param>
/// <param name="wholeA">The whole alpha.</param>
/// <param name="wholeW">The whole weight.</param>
/// <param name="whole">The whole moment.</param>
/// <returns>The <see cref="float"/>.</returns>
private float Maximize(ref Box cube, int direction, int first, int last, out int cut, float wholeR, float wholeG, float wholeB, float wholeA, float wholeW)
private float Maximize(ref Box cube, int direction, int first, int last, out int cut, Moment whole)
{
Span<long> vwtSpan = this.vwt.GetSpan();
Span<long> vmrSpan = this.vmr.GetSpan();
Span<long> vmgSpan = this.vmg.GetSpan();
Span<long> vmbSpan = this.vmb.GetSpan();
Span<long> vmaSpan = this.vma.GetSpan();
long baseR = Bottom(ref cube, direction, vmrSpan);
long baseG = Bottom(ref cube, direction, vmgSpan);
long baseB = Bottom(ref cube, direction, vmbSpan);
long baseA = Bottom(ref cube, direction, vmaSpan);
long baseW = Bottom(ref cube, direction, vwtSpan);
ReadOnlySpan<Moment> momentSpan = this.moments.GetSpan();
Moment bottom = Bottom(ref cube, direction, momentSpan);
float max = 0F;
cut = -1;
for (int i = first; i < last; i++)
{
float halfR = baseR + Top(ref cube, direction, i, vmrSpan);
float halfG = baseG + Top(ref cube, direction, i, vmgSpan);
float halfB = baseB + Top(ref cube, direction, i, vmbSpan);
float halfA = baseA + Top(ref cube, direction, i, vmaSpan);
float halfW = baseW + Top(ref cube, direction, i, vwtSpan);
Moment half = bottom + Top(ref cube, direction, i, momentSpan);
if (MathF.Abs(halfW) < Constants.Epsilon)
if (half.Weight == 0)
{
continue;
}
var vector = new Vector4(halfR, halfG, halfB, halfA);
float temp = Vector4.Dot(vector, vector) / halfW;
var vector = new Vector4(half.R, half.G, half.B, half.A);
float temp = Vector4.Dot(vector, vector) / half.Weight;
halfW = wholeW - halfW;
half = whole - half;
if (MathF.Abs(halfW) < Constants.Epsilon)
if (half.Weight == 0)
{
continue;
}
halfR = wholeR - halfR;
halfG = wholeG - halfG;
halfB = wholeB - halfB;
halfA = wholeA - halfA;
vector = new Vector4(halfR, halfG, halfB, halfA);
temp += Vector4.Dot(vector, vector) / halfW;
vector = new Vector4(half.R, half.G, half.B, half.A);
temp += Vector4.Dot(vector, vector) / half.Weight;
if (temp > max)
{
@ -731,33 +572,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <returns>Returns a value indicating whether the box has been split.</returns>
private bool Cut(ref Box set1, ref Box set2)
{
float wholeR = Volume(ref set1, this.vmr.GetSpan());
float wholeG = Volume(ref set1, this.vmg.GetSpan());
float wholeB = Volume(ref set1, this.vmb.GetSpan());
float wholeA = Volume(ref set1, this.vma.GetSpan());
float wholeW = Volume(ref set1, this.vwt.GetSpan());
ReadOnlySpan<Moment> momentSpan = this.moments.GetSpan();
Moment whole = Volume(ref set1, momentSpan);
float maxr = this.Maximize(ref set1, 3, set1.RMin + 1, set1.RMax, out int cutr, wholeR, wholeG, wholeB, wholeA, wholeW);
float maxg = this.Maximize(ref set1, 2, set1.GMin + 1, set1.GMax, out int cutg, wholeR, wholeG, wholeB, wholeA, wholeW);
float maxb = this.Maximize(ref set1, 1, set1.BMin + 1, set1.BMax, out int cutb, wholeR, wholeG, wholeB, wholeA, wholeW);
float maxa = this.Maximize(ref set1, 0, set1.AMin + 1, set1.AMax, out int cuta, wholeR, wholeG, wholeB, wholeA, wholeW);
float maxR = this.Maximize(ref set1, 3, set1.RMin + 1, set1.RMax, out int cutR, whole);
float maxG = this.Maximize(ref set1, 2, set1.GMin + 1, set1.GMax, out int cutG, whole);
float maxB = this.Maximize(ref set1, 1, set1.BMin + 1, set1.BMax, out int cutB, whole);
float maxA = this.Maximize(ref set1, 0, set1.AMin + 1, set1.AMax, out int cutA, whole);
int dir;
if ((maxr >= maxg) && (maxr >= maxb) && (maxr >= maxa))
if ((maxR >= maxG) && (maxR >= maxB) && (maxR >= maxA))
{
dir = 3;
if (cutr < 0)
if (cutR < 0)
{
return false;
}
}
else if ((maxg >= maxr) && (maxg >= maxb) && (maxg >= maxa))
else if ((maxG >= maxR) && (maxG >= maxB) && (maxG >= maxA))
{
dir = 2;
}
else if ((maxb >= maxr) && (maxb >= maxg) && (maxb >= maxa))
else if ((maxB >= maxR) && (maxB >= maxG) && (maxB >= maxA))
{
dir = 1;
}
@ -775,7 +613,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
// Red
case 3:
set2.RMin = set1.RMax = cutr;
set2.RMin = set1.RMax = cutR;
set2.GMin = set1.GMin;
set2.BMin = set1.BMin;
set2.AMin = set1.AMin;
@ -783,7 +621,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
// Green
case 2:
set2.GMin = set1.GMax = cutg;
set2.GMin = set1.GMax = cutG;
set2.RMin = set1.RMin;
set2.BMin = set1.BMin;
set2.AMin = set1.AMin;
@ -791,7 +629,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
// Blue
case 1:
set2.BMin = set1.BMax = cutb;
set2.BMin = set1.BMax = cutB;
set2.RMin = set1.RMin;
set2.GMin = set1.GMin;
set2.AMin = set1.AMin;
@ -799,7 +637,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
// Alpha
case 0:
set2.AMin = set1.AMax = cuta;
set2.AMin = set1.AMax = cutA;
set2.RMin = set1.RMin;
set2.GMin = set1.GMin;
set2.BMin = set1.BMin;
@ -857,8 +695,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
ref Box currentCube = ref this.colorCube[i];
if (this.Cut(ref nextCube, ref currentCube))
{
vv[next] = nextCube.Volume > 1 ? this.Variance(ref nextCube) : 0F;
vv[i] = currentCube.Volume > 1 ? this.Variance(ref currentCube) : 0F;
vv[next] = nextCube.Volume > 1 ? this.Variance(ref nextCube) : 0D;
vv[i] = currentCube.Volume > 1 ? this.Variance(ref currentCube) : 0D;
}
else
{
@ -893,7 +731,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <returns>
/// The quantized value
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
private byte QuantizePixel(ref TPixel pixel)
{
if (this.Dither)
@ -912,11 +750,98 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
int b = rgba.B >> (8 - IndexBits);
int a = rgba.A >> (8 - IndexAlphaBits);
Span<byte> tagSpan = this.tag.GetSpan();
ReadOnlySpan<byte> tagSpan = this.tag.GetSpan();
return tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)];
}
private struct Moment
{
/// <summary>
/// Moment of <c>r*P(c)</c>.
/// </summary>
public long R;
/// <summary>
/// Moment of <c>g*P(c)</c>.
/// </summary>
public long G;
/// <summary>
/// Moment of <c>b*P(c)</c>.
/// </summary>
public long B;
/// <summary>
/// Moment of <c>a*P(c)</c>.
/// </summary>
public long A;
/// <summary>
/// Moment of <c>P(c)</c>.
/// </summary>
public long Weight;
/// <summary>
/// Moment of <c>c^2*P(c)</c>.
/// </summary>
public double Moment2;
[MethodImpl(InliningOptions.ShortMethod)]
public static Moment operator +(Moment x, Moment y)
{
x.R += y.R;
x.G += y.G;
x.B += y.B;
x.A += y.A;
x.Weight += y.Weight;
x.Moment2 += y.Moment2;
return x;
}
[MethodImpl(InliningOptions.ShortMethod)]
public static Moment operator -(Moment x, Moment y)
{
x.R -= y.R;
x.G -= y.G;
x.B -= y.B;
x.A -= y.A;
x.Weight -= y.Weight;
x.Moment2 -= y.Moment2;
return x;
}
[MethodImpl(InliningOptions.ShortMethod)]
public static Moment operator -(Moment x)
{
x.R = -x.R;
x.G = -x.G;
x.B = -x.B;
x.A = -x.A;
x.Weight = -x.Weight;
x.Moment2 = -x.Moment2;
return x;
}
[MethodImpl(InliningOptions.ShortMethod)]
public static Moment operator +(Moment x, Rgba32 y)
{
x.R += y.R;
x.G += y.G;
x.B += y.B;
x.A += y.A;
x.Weight++;
var vector = new Vector4(y.R, y.G, y.B, y.A);
x.Moment2 += Vector4.Dot(vector, vector);
return x;
}
[MethodImpl(InliningOptions.ShortMethod)]
public readonly Vector4 Normalize()
=> new Vector4(this.R, this.G, this.B, this.A) / this.Weight / 255F;
}
/// <summary>
/// Represents a box color cube.
/// </summary>
@ -968,10 +893,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public int Volume;
/// <inheritdoc/>
public override bool Equals(object obj) => obj is Box box && this.Equals(box);
public readonly override bool Equals(object obj)
=> obj is Box box
&& this.Equals(box);
/// <inheritdoc/>
public bool Equals(Box other) =>
public readonly bool Equals(Box other) =>
this.RMin == other.RMin
&& this.RMax == other.RMax
&& this.GMin == other.GMin
@ -983,7 +910,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
&& this.Volume == other.Volume;
/// <inheritdoc/>
public override int GetHashCode()
public readonly override int GetHashCode()
{
HashCode hash = default;
hash.Add(this.RMin);

Loading…
Cancel
Save