📷 A modern, cross-platform, 2D Graphics library for .NET
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.
 
 

134 lines
4.8 KiB

// <copyright file="PaletteQuantizer.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Quantizers
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// Encapsulates methods to create a quantized image based upon the given palette.
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// </summary>
public class PaletteQuantizer : Quantizer
{
/// <summary>
/// A lookup table for colors
/// </summary>
private readonly ConcurrentDictionary<string, byte> colorMap = new ConcurrentDictionary<string, byte>();
/// <summary>
/// List of all colors in the palette
/// </summary>
private Bgra32[] colors;
/// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer"/> class.
/// </summary>
/// <param name="palette">
/// The color palette. If none is given this will default to the web safe colors defined
/// in the CSS Color Module Level 4.
/// </param>
public PaletteQuantizer(Color[] palette = null)
: base(true)
{
if (palette == null)
{
List<Bgra32> safe = ColorConstants.WebSafeColors.Select(c => (Bgra32)c).ToList();
safe.Insert(0, Bgra32.Empty);
this.colors = safe.ToArray();
}
else
{
this.colors = palette.Select(c => (Bgra32)c).ToArray();
}
}
/// <inheritdoc/>
public override QuantizedImage Quantize(ImageBase image, int maxColors)
{
Array.Resize(ref this.colors, maxColors.Clamp(1, 256));
return base.Quantize(image, maxColors);
}
/// <inheritdoc/>
protected override byte QuantizePixel(Bgra32 pixel)
{
byte colorIndex = 0;
string colorHash = pixel.ToString();
// Check if the color is in the lookup table
if (this.colorMap.ContainsKey(colorHash))
{
colorIndex = this.colorMap[colorHash];
}
else
{
// Not found - loop through the palette and find the nearest match.
// Firstly check the alpha value - if less than the threshold, lookup the transparent color
if (!(pixel.A > this.Threshold))
{
// Transparent. Lookup the first color with an alpha value of 0
for (int index = 0; index < this.colors.Length; index++)
{
if (this.colors[index].A == 0)
{
colorIndex = (byte)index;
this.TransparentIndex = colorIndex;
break;
}
}
}
else
{
// Not transparent...
int leastDistance = int.MaxValue;
int red = pixel.R;
int green = pixel.G;
int blue = pixel.B;
// Loop through the entire palette, looking for the closest color match
for (int index = 0; index < this.colors.Length; index++)
{
Bgra32 paletteColor = this.colors[index];
int redDistance = paletteColor.R - red;
int greenDistance = paletteColor.G - green;
int blueDistance = paletteColor.B - blue;
int distance = (redDistance * redDistance) +
(greenDistance * greenDistance) +
(blueDistance * blueDistance);
if (distance < leastDistance)
{
colorIndex = (byte)index;
leastDistance = distance;
// And if it's an exact match, exit the loop
if (distance == 0)
{
break;
}
}
}
}
// Now I have the color, pop it into the cache for next time
this.colorMap.TryAdd(colorHash, colorIndex);
}
return colorIndex;
}
/// <inheritdoc/>
protected override List<Bgra32> GetPalette()
{
return this.colors.ToList();
}
}
}