mirror of https://github.com/SixLabors/ImageSharp
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
144 lines
5.6 KiB
144 lines
5.6 KiB
// Copyright (c) Six Labors and contributors.
|
|
// Licensed under the Apache License, Version 2.0.
|
|
|
|
using System;
|
|
using System.Buffers;
|
|
using System.Collections.Generic;
|
|
using System.Numerics;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
using SixLabors.ImageSharp.PixelFormats;
|
|
using SixLabors.Memory;
|
|
using SixLabors.Primitives;
|
|
|
|
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
|
|
{
|
|
/// <summary>
|
|
/// The base class for dither and diffusion processors that consume a palette.
|
|
/// </summary>
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
internal abstract class PaletteDitherProcessor<TPixel> : ImageProcessor<TPixel>
|
|
where TPixel : struct, IPixel<TPixel>
|
|
{
|
|
private readonly Dictionary<TPixel, PixelPair<TPixel>> cache = new Dictionary<TPixel, PixelPair<TPixel>>();
|
|
private IMemoryOwner<TPixel> palette;
|
|
private IMemoryOwner<Vector4> paletteVector;
|
|
private bool palleteVectorMapped;
|
|
private bool isDisposed;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PaletteDitherProcessor{TPixel}"/> class.
|
|
/// </summary>
|
|
/// <param name="definition">The <see cref="PaletteDitherProcessor"/> defining the processor parameters.</param>
|
|
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
|
|
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
|
|
protected PaletteDitherProcessor(PaletteDitherProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
|
|
: base(source, sourceRectangle)
|
|
{
|
|
this.Definition = definition;
|
|
this.palette = this.Configuration.MemoryAllocator.Allocate<TPixel>(definition.Palette.Length);
|
|
this.paletteVector = this.Configuration.MemoryAllocator.Allocate<Vector4>(definition.Palette.Length);
|
|
}
|
|
|
|
protected PaletteDitherProcessor Definition { get; }
|
|
|
|
/// <inheritdoc/>
|
|
protected override void BeforeFrameApply(ImageFrame<TPixel> source)
|
|
{
|
|
// Lazy init palettes:
|
|
if (!this.palleteVectorMapped)
|
|
{
|
|
ReadOnlySpan<Color> sourcePalette = this.Definition.Palette.Span;
|
|
Color.ToPixel(this.Configuration, sourcePalette, this.palette.Memory.Span);
|
|
|
|
PixelOperations<TPixel>.Instance.ToVector4(
|
|
this.Configuration,
|
|
this.palette.Memory.Span,
|
|
this.paletteVector.Memory.Span,
|
|
PixelConversionModifiers.Scale);
|
|
}
|
|
|
|
this.palleteVectorMapped = true;
|
|
|
|
base.BeforeFrameApply(source);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (this.isDisposed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (disposing)
|
|
{
|
|
this.palette?.Dispose();
|
|
this.paletteVector?.Dispose();
|
|
}
|
|
|
|
this.palette = null;
|
|
this.paletteVector = null;
|
|
|
|
this.isDisposed = true;
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the two closest colors from the palette calculated via Euclidean distance in the Rgba space.
|
|
/// </summary>
|
|
/// <param name="pixel">The source color to match.</param>
|
|
/// <returns>The <see cref="PixelPair{TPixel}"/>.</returns>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
protected PixelPair<TPixel> GetClosestPixelPair(ref TPixel pixel)
|
|
{
|
|
// Check if the color is in the lookup table
|
|
if (this.cache.TryGetValue(pixel, out PixelPair<TPixel> value))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
return this.GetClosestPixelPairSlow(ref pixel);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
private PixelPair<TPixel> GetClosestPixelPairSlow(ref TPixel pixel)
|
|
{
|
|
// Not found - loop through the palette and find the nearest match.
|
|
float leastDistance = float.MaxValue;
|
|
float secondLeastDistance = float.MaxValue;
|
|
var vector = pixel.ToVector4();
|
|
|
|
TPixel closest = default;
|
|
TPixel secondClosest = default;
|
|
Span<TPixel> paletteSpan = this.palette.Memory.Span;
|
|
ref TPixel paletteSpanBase = ref MemoryMarshal.GetReference(paletteSpan);
|
|
Span<Vector4> paletteVectorSpan = this.paletteVector.Memory.Span;
|
|
ref Vector4 paletteVectorSpanBase = ref MemoryMarshal.GetReference(paletteVectorSpan);
|
|
|
|
for (int index = 0; index < paletteVectorSpan.Length; index++)
|
|
{
|
|
ref Vector4 candidate = ref Unsafe.Add(ref paletteVectorSpanBase, index);
|
|
float distance = Vector4.DistanceSquared(vector, candidate);
|
|
|
|
if (distance < leastDistance)
|
|
{
|
|
leastDistance = distance;
|
|
secondClosest = closest;
|
|
closest = Unsafe.Add(ref paletteSpanBase, index);
|
|
}
|
|
else if (distance < secondLeastDistance)
|
|
{
|
|
secondLeastDistance = distance;
|
|
secondClosest = Unsafe.Add(ref paletteSpanBase, index);
|
|
}
|
|
}
|
|
|
|
// Pop it into the cache for next time
|
|
var pair = new PixelPair<TPixel>(closest, secondClosest);
|
|
this.cache.Add(pixel, pair);
|
|
|
|
return pair;
|
|
}
|
|
}
|
|
}
|
|
|