|
|
|
@ -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()); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -236,50 +183,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization |
|
|
|
} |
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
protected override void SecondPass(ImageFrame<TPixel> source, Span<byte> output, ReadOnlySpan<TPixel> palette, int width, int height) |
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
protected override byte GetQuantizedColor(TPixel color, out TPixel match) |
|
|
|
{ |
|
|
|
// Load up the values for the first pixel. We can use these to speed up the second
|
|
|
|
// pass of the algorithm by avoiding transforming rows of identical color.
|
|
|
|
TPixel sourcePixel = source[0, 0]; |
|
|
|
TPixel previousPixel = sourcePixel; |
|
|
|
byte pixelValue = this.QuantizePixel(ref sourcePixel); |
|
|
|
TPixel transformedPixel = palette[pixelValue]; |
|
|
|
|
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
if (!this.DoDither) |
|
|
|
{ |
|
|
|
Span<TPixel> row = source.GetPixelRowSpan(y); |
|
|
|
|
|
|
|
// And loop through each column
|
|
|
|
for (int x = 0; x < width; x++) |
|
|
|
{ |
|
|
|
// Get the pixel.
|
|
|
|
sourcePixel = row[x]; |
|
|
|
|
|
|
|
// Check if this is the same as the last pixel. If so use that value
|
|
|
|
// rather than calculating it again. This is an inexpensive optimization.
|
|
|
|
if (!previousPixel.Equals(sourcePixel)) |
|
|
|
{ |
|
|
|
// Quantize the pixel
|
|
|
|
pixelValue = this.QuantizePixel(ref sourcePixel); |
|
|
|
|
|
|
|
// And setup the previous pointer
|
|
|
|
previousPixel = sourcePixel; |
|
|
|
|
|
|
|
if (this.Dither) |
|
|
|
{ |
|
|
|
transformedPixel = palette[pixelValue]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (this.Dither) |
|
|
|
{ |
|
|
|
// Apply the dithering matrix. We have to reapply the value now as the original has changed.
|
|
|
|
this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, 0, width, height); |
|
|
|
} |
|
|
|
|
|
|
|
output[(y * source.Width) + x] = pixelValue; |
|
|
|
} |
|
|
|
// Expected order r->g->b->a
|
|
|
|
Rgba32 rgba = default; |
|
|
|
color.ToRgba32(ref rgba); |
|
|
|
|
|
|
|
int r = rgba.R >> (8 - IndexBits); |
|
|
|
int g = rgba.G >> (8 - IndexBits); |
|
|
|
int b = rgba.B >> (8 - IndexBits); |
|
|
|
int a = rgba.A >> (8 - IndexAlphaBits); |
|
|
|
|
|
|
|
ReadOnlySpan<byte> tagSpan = this.tag.GetSpan(); |
|
|
|
var index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; |
|
|
|
match = this.GetPalette().Span[index]; |
|
|
|
return index; |
|
|
|
} |
|
|
|
|
|
|
|
return base.GetQuantizedColor(color, out match); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -290,7 +214,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 +231,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 +258,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 +319,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 +382,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 +417,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 +462,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 +499,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 +549,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 +590,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 +598,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 +606,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 +614,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 +672,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 |
|
|
|
{ |
|
|
|
@ -886,35 +701,92 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Process the pixel in the second pass of the algorithm
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="pixel">The pixel to quantize</param>
|
|
|
|
/// <returns>
|
|
|
|
/// The quantized value
|
|
|
|
/// </returns>
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
private byte QuantizePixel(ref TPixel pixel) |
|
|
|
private struct Moment |
|
|
|
{ |
|
|
|
if (this.Dither) |
|
|
|
/// <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) |
|
|
|
{ |
|
|
|
// The colors have changed so we need to use Euclidean distance calculation to
|
|
|
|
// find the closest value.
|
|
|
|
return this.GetClosestPixel(ref pixel); |
|
|
|
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; |
|
|
|
} |
|
|
|
|
|
|
|
// Expected order r->g->b->a
|
|
|
|
Rgba32 rgba = default; |
|
|
|
pixel.ToRgba32(ref rgba); |
|
|
|
[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; |
|
|
|
} |
|
|
|
|
|
|
|
int r = rgba.R >> (8 - IndexBits); |
|
|
|
int g = rgba.G >> (8 - IndexBits); |
|
|
|
int b = rgba.B >> (8 - IndexBits); |
|
|
|
int a = rgba.A >> (8 - IndexAlphaBits); |
|
|
|
[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; |
|
|
|
} |
|
|
|
|
|
|
|
Span<byte> tagSpan = this.tag.GetSpan(); |
|
|
|
[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; |
|
|
|
} |
|
|
|
|
|
|
|
return tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; |
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
public readonly Vector4 Normalize() |
|
|
|
=> new Vector4(this.R, this.G, this.B, this.A) / this.Weight / 255F; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -968,10 +840,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 +857,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); |
|
|
|
|