Browse Source

Smarter dithering.

pull/637/head
James Jackson-South 8 years ago
parent
commit
07064e90c4
  1. 9
      src/ImageSharp/Processing/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs
  2. 7
      src/ImageSharp/Processing/Dithering/Processors/ErrorDiffusionPaletteProcessor.cs
  3. 7
      src/ImageSharp/Processing/Dithering/Processors/OrderedDitherPaletteProcessor.cs
  4. 18
      src/ImageSharp/Processing/Dithering/Processors/PaletteDitherProcessorBase.cs
  5. 16
      src/ImageSharp/Processing/Quantization/FrameQuantizers/OctreeFrameQuantizer{TPixel}.cs
  6. 2
      src/ImageSharp/Processing/Quantization/FrameQuantizers/PaletteFrameQuantizer{TPixel}.cs
  7. 3
      tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs

9
src/ImageSharp/Processing/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs

@ -75,15 +75,14 @@ namespace SixLabors.ImageSharp.Processing.Dithering.ErrorDiffusion
{
image[x, y] = transformed;
// Calculate the error
Vector4 error = source.ToVector4() - transformed.ToVector4();
// No error? Break out as there's nothing to pass.
if (error.Equals(Vector4.Zero))
// Equal? Break out as there's nothing to pass.
if (source.Equals(transformed))
{
return;
}
// Calculate the error
Vector4 error = source.ToVector4() - transformed.ToVector4();
this.DoDither(image, x, y, minX, minY, maxX, maxY, error);
}

7
src/ImageSharp/Processing/Dithering/Processors/ErrorDiffusionPaletteProcessor.cs

@ -97,6 +97,13 @@ namespace SixLabors.ImageSharp.Processing.Dithering.Processors
if (!previousPixel.Equals(sourcePixel))
{
pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette);
// No error to spread, exact match.
if (sourcePixel.Equals(pair.First))
{
continue;
}
sourcePixel.ToRgba32(ref rgba);
luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B);

7
src/ImageSharp/Processing/Dithering/Processors/OrderedDitherPaletteProcessor.cs

@ -78,6 +78,13 @@ namespace SixLabors.ImageSharp.Processing.Dithering.Processors
if (!previousPixel.Equals(sourcePixel))
{
pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette);
// No error to spread, exact match.
if (sourcePixel.Equals(pair.First))
{
continue;
}
sourcePixel.ToRgba32(ref rgba);
luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B);

18
src/ImageSharp/Processing/Dithering/Processors/PaletteDitherProcessorBase.cs

@ -37,11 +37,17 @@ namespace SixLabors.ImageSharp.Processing.Dithering.Processors
protected PixelPair<TPixel> GetClosestPixelPair(ref TPixel pixel, TPixel[] colorPalette)
{
// Check if the color is in the lookup table
if (this.cache.ContainsKey(pixel))
if (this.cache.TryGetValue(pixel, out PixelPair<TPixel> value))
{
return this.cache[pixel];
return value;
}
return this.GetClosestPixelPairSlow(ref pixel, colorPalette);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private PixelPair<TPixel> GetClosestPixelPairSlow(ref TPixel pixel, TPixel[] colorPalette)
{
// Not found - loop through the palette and find the nearest match.
float leastDistance = float.MaxValue;
float secondLeastDistance = float.MaxValue;
@ -51,19 +57,19 @@ namespace SixLabors.ImageSharp.Processing.Dithering.Processors
TPixel secondClosest = default;
for (int index = 0; index < colorPalette.Length; index++)
{
TPixel temp = colorPalette[index];
float distance = Vector4.DistanceSquared(vector, temp.ToVector4());
ref TPixel candidate = ref colorPalette[index];
float distance = Vector4.DistanceSquared(vector, candidate.ToVector4());
if (distance < leastDistance)
{
leastDistance = distance;
secondClosest = closest;
closest = temp;
closest = candidate;
}
else if (distance < secondLeastDistance)
{
secondLeastDistance = distance;
secondClosest = temp;
secondClosest = candidate;
}
}

16
src/ImageSharp/Processing/Quantization/FrameQuantizers/OctreeFrameQuantizer{TPixel}.cs

@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
: base(quantizer, false)
{
this.colors = (byte)quantizer.MaxColors;
this.octree = new Octree(this.GetBitsNeededForColorDepth(this.colors));
this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.colors).Clamp(1, 8));
}
/// <inheritdoc/>
@ -189,20 +189,6 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
return (byte)this.octree.GetPaletteIndex(ref pixel, ref rgba);
}
/// <summary>
/// Returns how many bits are required to store the specified number of colors.
/// Performs a Log2() on the value.
/// </summary>
/// <param name="colorCount">The number of colors.</param>
/// <returns>
/// The <see cref="int"/>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetBitsNeededForColorDepth(int colorCount)
{
return (int)Math.Ceiling(Math.Log(colorCount, 2));
}
/// <summary>
/// Class which does the actual quantization
/// </summary>

2
src/ImageSharp/Processing/Quantization/FrameQuantizers/PaletteFrameQuantizer{TPixel}.cs

@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
public PaletteFrameQuantizer(PaletteQuantizer quantizer, TPixel[] colors)
: base(quantizer, true)
{
Guard.MustBeLessThanOrEqualTo(256, colors.Length, "Maximum color count must be 256.");
Guard.MustBeBetweenOrEqualTo(colors.Length, 1, 255, nameof(colors));
this.colors = colors;
}

3
tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs

@ -83,8 +83,7 @@ namespace SixLabors.ImageSharp.Tests
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(c => c.Quantize(quantizer));
image.DebugSave(provider, new PngEncoder() { ColorType = PngColorType.Palette }, testOutputDetails: quantizerName);
image.DebugSave(provider, new PngEncoder() { ColorType = PngColorType.Palette, Quantizer = quantizer }, testOutputDetails: quantizerName);
}
provider.Configuration.MemoryAllocator.ReleaseRetainedResources();

Loading…
Cancel
Save