Browse Source

Smarter dithering.

af/merge-core
James Jackson-South 8 years ago
parent
commit
1682f3bf70
  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; image[x, y] = transformed;
// Calculate the error // Equal? Break out as there's nothing to pass.
Vector4 error = source.ToVector4() - transformed.ToVector4(); if (source.Equals(transformed))
// No error? Break out as there's nothing to pass.
if (error.Equals(Vector4.Zero))
{ {
return; return;
} }
// Calculate the error
Vector4 error = source.ToVector4() - transformed.ToVector4();
this.DoDither(image, x, y, minX, minY, maxX, maxY, error); 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)) if (!previousPixel.Equals(sourcePixel))
{ {
pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette); pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette);
// No error to spread, exact match.
if (sourcePixel.Equals(pair.First))
{
continue;
}
sourcePixel.ToRgba32(ref rgba); sourcePixel.ToRgba32(ref rgba);
luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B); 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)) if (!previousPixel.Equals(sourcePixel))
{ {
pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette); pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette);
// No error to spread, exact match.
if (sourcePixel.Equals(pair.First))
{
continue;
}
sourcePixel.ToRgba32(ref rgba); sourcePixel.ToRgba32(ref rgba);
luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B); 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) protected PixelPair<TPixel> GetClosestPixelPair(ref TPixel pixel, TPixel[] colorPalette)
{ {
// Check if the color is in the lookup table // 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. // Not found - loop through the palette and find the nearest match.
float leastDistance = float.MaxValue; float leastDistance = float.MaxValue;
float secondLeastDistance = float.MaxValue; float secondLeastDistance = float.MaxValue;
@ -51,19 +57,19 @@ namespace SixLabors.ImageSharp.Processing.Dithering.Processors
TPixel secondClosest = default; TPixel secondClosest = default;
for (int index = 0; index < colorPalette.Length; index++) for (int index = 0; index < colorPalette.Length; index++)
{ {
TPixel temp = colorPalette[index]; ref TPixel candidate = ref colorPalette[index];
float distance = Vector4.DistanceSquared(vector, temp.ToVector4()); float distance = Vector4.DistanceSquared(vector, candidate.ToVector4());
if (distance < leastDistance) if (distance < leastDistance)
{ {
leastDistance = distance; leastDistance = distance;
secondClosest = closest; secondClosest = closest;
closest = temp; closest = candidate;
} }
else if (distance < secondLeastDistance) else if (distance < secondLeastDistance)
{ {
secondLeastDistance = distance; 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) : base(quantizer, false)
{ {
this.colors = (byte)quantizer.MaxColors; 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/> /// <inheritdoc/>
@ -189,20 +189,6 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
return (byte)this.octree.GetPaletteIndex(ref pixel, ref rgba); 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> /// <summary>
/// Class which does the actual quantization /// Class which does the actual quantization
/// </summary> /// </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) public PaletteFrameQuantizer(PaletteQuantizer quantizer, TPixel[] colors)
: base(quantizer, true) : 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; this.colors = colors;
} }

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

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

Loading…
Cancel
Save