mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Former-commit-id: 3c6b59e30ffdbbc752829bd2f76675b4a18815ed Former-commit-id: acfc7974999bfee275e867b5289724e697164e0caf/merge-core
23 changed files with 1161 additions and 43 deletions
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:73f4d8e08292df487a457be0765cb7994f2d91600b1824170a82e8f20f0a6d0b |
|||
size 5089 |
|||
oid sha256:33ceb5405f4f51976646815e3a90bcdbd7d5fcc8773db595001e4ca83a22fc24 |
|||
size 1738 |
|||
|
|||
@ -0,0 +1,24 @@ |
|||
|
|||
|
|||
namespace ImageProcessor.Common.Extensions |
|||
{ |
|||
using System.Drawing; |
|||
using System.Drawing.Imaging; |
|||
|
|||
public static class ImageExtensions |
|||
{ |
|||
public static Image ChangePixelFormat(this Image image, PixelFormat format) |
|||
{ |
|||
Bitmap clone = new Bitmap(image.Width, image.Height, format); |
|||
clone.SetResolution(image.HorizontalResolution, image.VerticalResolution); |
|||
|
|||
using (Graphics graphics = Graphics.FromImage(clone)) |
|||
{ |
|||
graphics.DrawImage(image, new Rectangle(0, 0, clone.Width, clone.Height)); |
|||
} |
|||
|
|||
image = new Bitmap(clone); |
|||
return image; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
namespace nQuant |
|||
{ |
|||
public struct Box |
|||
{ |
|||
public byte AlphaMinimum; |
|||
public byte AlphaMaximum; |
|||
public byte RedMinimum; |
|||
public byte RedMaximum; |
|||
public byte GreenMinimum; |
|||
public byte GreenMaximum; |
|||
public byte BlueMinimum; |
|||
public byte BlueMaximum; |
|||
public int Size; |
|||
} |
|||
} |
|||
@ -0,0 +1,92 @@ |
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
// <copyright file="ColorData.cs" company="James South">
|
|||
// Copyright (c) James South.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace nQuant |
|||
{ |
|||
/// <summary>
|
|||
/// The color data.
|
|||
/// </summary>
|
|||
public class ColorData |
|||
{ |
|||
/// <summary>
|
|||
/// The pixels.
|
|||
/// </summary>
|
|||
private readonly Pixel[] pixels; |
|||
|
|||
/// <summary>
|
|||
/// The pixels count.
|
|||
/// </summary>
|
|||
private readonly int pixelsCount; |
|||
|
|||
/// <summary>
|
|||
/// The pixel filling counter.
|
|||
/// </summary>
|
|||
private int pixelFillingCounter; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ColorData"/> class.
|
|||
/// </summary>
|
|||
/// <param name="dataGranularity">
|
|||
/// The data granularity.
|
|||
/// </param>
|
|||
/// <param name="bitmapWidth">
|
|||
/// The bitmap width.
|
|||
/// </param>
|
|||
/// <param name="bitmapHeight">
|
|||
/// The bitmap height.
|
|||
/// </param>
|
|||
public ColorData(int dataGranularity, int bitmapWidth, int bitmapHeight) |
|||
{ |
|||
dataGranularity++; |
|||
|
|||
this.Moments = new ColorMoment[dataGranularity, dataGranularity, dataGranularity, dataGranularity]; |
|||
this.pixelsCount = bitmapWidth * bitmapHeight; |
|||
this.pixels = new Pixel[this.pixelsCount]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the moments.
|
|||
/// </summary>
|
|||
public ColorMoment[, , ,] Moments { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the pixels.
|
|||
/// </summary>
|
|||
public Pixel[] Pixels |
|||
{ |
|||
get |
|||
{ |
|||
return this.pixels; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the pixels count.
|
|||
/// </summary>
|
|||
public int PixelsCount |
|||
{ |
|||
get |
|||
{ |
|||
return this.pixelsCount; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The add pixel.
|
|||
/// </summary>
|
|||
/// <param name="pixel">
|
|||
/// The pixel.
|
|||
/// </param>
|
|||
/// <param name="quantizedPixel">
|
|||
/// The quantized pixel.
|
|||
/// </param>
|
|||
public void AddPixel(Pixel pixel, Pixel quantizedPixel) |
|||
{ |
|||
this.pixels[this.pixelFillingCounter++] = pixel; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,73 @@ |
|||
|
|||
namespace nQuant |
|||
{ |
|||
public struct ColorMoment |
|||
{ |
|||
public long Alpha; |
|||
public long Red; |
|||
public long Green; |
|||
public long Blue; |
|||
public int Weight; |
|||
public float Moment; |
|||
|
|||
public static ColorMoment operator +(ColorMoment c1, ColorMoment c2) |
|||
{ |
|||
c1.Alpha += c2.Alpha; |
|||
c1.Red += c2.Red; |
|||
c1.Green += c2.Green; |
|||
c1.Blue += c2.Blue; |
|||
c1.Weight += c2.Weight; |
|||
c1.Moment += c2.Moment; |
|||
return c1; |
|||
} |
|||
|
|||
public static ColorMoment operator -(ColorMoment c1, ColorMoment c2) |
|||
{ |
|||
c1.Alpha -= c2.Alpha; |
|||
c1.Red -= c2.Red; |
|||
c1.Green -= c2.Green; |
|||
c1.Blue -= c2.Blue; |
|||
c1.Weight -= c2.Weight; |
|||
c1.Moment -= c2.Moment; |
|||
return c1; |
|||
} |
|||
|
|||
public static ColorMoment operator -(ColorMoment c1) |
|||
{ |
|||
c1.Alpha = -c1.Alpha; |
|||
c1.Red = -c1.Red; |
|||
c1.Green = -c1.Green; |
|||
c1.Blue = -c1.Blue; |
|||
c1.Weight = -c1.Weight; |
|||
c1.Moment = -c1.Moment; |
|||
return c1; |
|||
} |
|||
|
|||
public static ColorMoment operator +(ColorMoment m, Pixel p) |
|||
{ |
|||
m.Alpha += p.Alpha; |
|||
m.Red += p.Red; |
|||
m.Green += p.Green; |
|||
m.Blue += p.Blue; |
|||
m.Weight++; |
|||
m.Moment += p.Distance(); |
|||
return m; |
|||
} |
|||
|
|||
public long Distance() |
|||
{ |
|||
return (Alpha * Alpha) + (Red * Red) + (Green * Green) + (Blue * Blue); |
|||
} |
|||
|
|||
public long WeightedDistance() |
|||
{ |
|||
return Distance() / Weight; |
|||
} |
|||
|
|||
public float Variance() |
|||
{ |
|||
var result = Moment - ((float)Distance() / Weight); |
|||
return float.IsNaN(result) ? 0.0f : result; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
namespace nQuant |
|||
{ |
|||
internal struct CubeCut |
|||
{ |
|||
public readonly byte? Position; |
|||
public readonly float Value; |
|||
|
|||
public CubeCut(byte? cutPoint, float result) |
|||
{ |
|||
Position = cutPoint; |
|||
Value = result; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System.Drawing; |
|||
|
|||
namespace nQuant |
|||
{ |
|||
public interface IWuQuantizer |
|||
{ |
|||
Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader); |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
namespace nQuant |
|||
{ |
|||
public class Lookup |
|||
{ |
|||
public int Alpha; |
|||
public int Red; |
|||
public int Green; |
|||
public int Blue; |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
namespace nQuant |
|||
{ |
|||
using System.Runtime.InteropServices; |
|||
|
|||
[StructLayout(LayoutKind.Explicit)] |
|||
public struct Pixel |
|||
{ |
|||
public Pixel(byte alpha, byte red, byte green, byte blue) |
|||
: this() |
|||
{ |
|||
Alpha = alpha; |
|||
Red = red; |
|||
Green = green; |
|||
Blue = blue; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Pixel"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="argb">
|
|||
/// The combined color components.
|
|||
/// </param>
|
|||
public Pixel(int argb) |
|||
: this() |
|||
{ |
|||
this.Argb = argb; |
|||
} |
|||
|
|||
public long Distance() |
|||
{ |
|||
return (Alpha * Alpha) + (Red * Red) + (Green * Green) + (Blue * Blue); |
|||
} |
|||
|
|||
[FieldOffsetAttribute(3)] |
|||
public byte Alpha; |
|||
[FieldOffsetAttribute(2)] |
|||
public byte Red; |
|||
[FieldOffsetAttribute(1)] |
|||
public byte Green; |
|||
[FieldOffsetAttribute(0)] |
|||
public byte Blue; |
|||
[FieldOffset(0)] |
|||
public int Argb; |
|||
|
|||
public override string ToString() |
|||
{ |
|||
return string.Format("Alpha:{0} Red:{1} Green:{2} Blue:{3}", Alpha, Red, Green, Blue); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
|
|||
namespace nQuant |
|||
{ |
|||
public class QuantizationException : ApplicationException |
|||
{ |
|||
public QuantizationException(string message) : base(message) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System.Collections.Generic; |
|||
using System.Drawing; |
|||
|
|||
namespace nQuant |
|||
{ |
|||
public class QuantizedPalette |
|||
{ |
|||
public QuantizedPalette(int size) |
|||
{ |
|||
Colors = new List<Color>(); |
|||
PixelIndex = new byte[size]; |
|||
} |
|||
public IList<Color> Colors { get; private set; } |
|||
|
|||
public byte[] PixelIndex { get; private set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Drawing; |
|||
|
|||
namespace nQuant |
|||
{ |
|||
public class WuQuantizer : WuQuantizerBase, IWuQuantizer |
|||
{ |
|||
protected override QuantizedPalette GetQuantizedPalette(int colorCount, ColorData data, Box[] cubes, int alphaThreshold) |
|||
{ |
|||
int pixelsCount = data.Pixels.Length; |
|||
Lookup[] lookups = BuildLookups(cubes, data); |
|||
|
|||
var alphas = new int[colorCount + 1]; |
|||
var reds = new int[colorCount + 1]; |
|||
var greens = new int[colorCount + 1]; |
|||
var blues = new int[colorCount + 1]; |
|||
var sums = new int[colorCount + 1]; |
|||
var palette = new QuantizedPalette(pixelsCount); |
|||
|
|||
IList<Pixel> pixels = data.Pixels; |
|||
|
|||
Dictionary<int, byte> cachedMaches = new Dictionary<int, byte>(); |
|||
|
|||
for (int pixelIndex = 0; pixelIndex < pixelsCount; pixelIndex++) |
|||
{ |
|||
Pixel pixel = pixels[pixelIndex]; |
|||
|
|||
if (pixel.Alpha > alphaThreshold) |
|||
{ |
|||
byte bestMatch; |
|||
int argb = pixel.Argb; |
|||
|
|||
if (!cachedMaches.TryGetValue(argb, out bestMatch)) |
|||
{ |
|||
int bestDistance = int.MaxValue; |
|||
|
|||
for (int lookupIndex = 0; lookupIndex < lookups.Length; lookupIndex++) |
|||
{ |
|||
Lookup lookup = lookups[lookupIndex]; |
|||
var deltaAlpha = pixel.Alpha - lookup.Alpha; |
|||
var deltaRed = pixel.Red - lookup.Red; |
|||
var deltaGreen = pixel.Green - lookup.Green; |
|||
var deltaBlue = pixel.Blue - lookup.Blue; |
|||
|
|||
int distance = deltaAlpha * deltaAlpha + deltaRed * deltaRed + deltaGreen * deltaGreen + deltaBlue * deltaBlue; |
|||
|
|||
if (distance >= bestDistance) |
|||
continue; |
|||
|
|||
bestDistance = distance; |
|||
bestMatch = (byte)lookupIndex; |
|||
} |
|||
|
|||
cachedMaches[argb] = bestMatch; |
|||
} |
|||
|
|||
alphas[bestMatch] += pixel.Alpha; |
|||
reds[bestMatch] += pixel.Red; |
|||
greens[bestMatch] += pixel.Green; |
|||
blues[bestMatch] += pixel.Blue; |
|||
sums[bestMatch]++; |
|||
|
|||
palette.PixelIndex[pixelIndex] = bestMatch; |
|||
} |
|||
else |
|||
{ |
|||
palette.PixelIndex[pixelIndex] = AlphaColor; |
|||
} |
|||
} |
|||
|
|||
for (var paletteIndex = 0; paletteIndex < colorCount; paletteIndex++) |
|||
{ |
|||
if (sums[paletteIndex] > 0) |
|||
{ |
|||
alphas[paletteIndex] /= sums[paletteIndex]; |
|||
reds[paletteIndex] /= sums[paletteIndex]; |
|||
greens[paletteIndex] /= sums[paletteIndex]; |
|||
blues[paletteIndex] /= sums[paletteIndex]; |
|||
} |
|||
|
|||
var color = Color.FromArgb(alphas[paletteIndex], reds[paletteIndex], greens[paletteIndex], blues[paletteIndex]); |
|||
palette.Colors.Add(color); |
|||
} |
|||
|
|||
palette.Colors.Add(Color.FromArgb(0, 0, 0, 0)); |
|||
|
|||
return palette; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,506 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Drawing; |
|||
using System.Drawing.Imaging; |
|||
using System.Linq; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace nQuant |
|||
{ |
|||
public abstract class WuQuantizerBase |
|||
{ |
|||
private const int MaxColor = 256; |
|||
protected const byte AlphaColor = 255; |
|||
protected const int Alpha = 3; |
|||
protected const int Red = 2; |
|||
protected const int Green = 1; |
|||
protected const int Blue = 0; |
|||
private const int SideSize = 33; |
|||
private const int MaxSideIndex = 32; |
|||
|
|||
public Image QuantizeImage(Bitmap image) |
|||
{ |
|||
return QuantizeImage(image, 10, 70); |
|||
} |
|||
|
|||
public Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader) |
|||
{ |
|||
var colorCount = MaxColor; |
|||
var data = BuildHistogram(image, alphaThreshold, alphaFader); |
|||
data = CalculateMoments(data); |
|||
var cubes = SplitData(ref colorCount, data); |
|||
var palette = GetQuantizedPalette(colorCount, data, cubes, alphaThreshold); |
|||
return ProcessImagePixels(image, palette); |
|||
} |
|||
|
|||
private static Bitmap ProcessImagePixels(Image sourceImage, QuantizedPalette palette) |
|||
{ |
|||
var result = new Bitmap(sourceImage.Width, sourceImage.Height, PixelFormat.Format8bppIndexed); |
|||
var newPalette = result.Palette; |
|||
for (var index = 0; index < palette.Colors.Count; index++) |
|||
newPalette.Entries[index] = palette.Colors[index]; |
|||
result.Palette = newPalette; |
|||
|
|||
BitmapData targetData = null; |
|||
try |
|||
{ |
|||
var resultHeight = result.Height; |
|||
var resultWidth = result.Width; |
|||
targetData = result.LockBits(Rectangle.FromLTRB(0, 0, resultWidth, resultHeight), ImageLockMode.WriteOnly, result.PixelFormat); |
|||
const byte targetBitDepth = 8; |
|||
var targetByteLength = targetData.Stride < 0 ? -targetData.Stride : targetData.Stride; |
|||
var targetByteCount = Math.Max(1, targetBitDepth >> 3); |
|||
var targetBuffer = new byte[targetByteLength]; |
|||
var targetValue = new byte[targetByteCount]; |
|||
var pixelIndex = 0; |
|||
|
|||
|
|||
|
|||
for (var y = 0; y < resultHeight; y++) |
|||
{ |
|||
var targetIndex = 0; |
|||
for (var x = 0; x < resultWidth; x++) |
|||
{ |
|||
var targetIndexOffset = targetIndex >> 3; |
|||
targetValue[0] = |
|||
(byte) |
|||
(palette.PixelIndex[pixelIndex] == AlphaColor |
|||
? palette.Colors.Count - 1 |
|||
: palette.PixelIndex[pixelIndex]); |
|||
pixelIndex++; |
|||
|
|||
for (var valueIndex = 0; valueIndex < targetByteCount; valueIndex++) |
|||
{ |
|||
targetBuffer[valueIndex + targetIndexOffset] = targetValue[valueIndex]; |
|||
} |
|||
|
|||
targetIndex += targetBitDepth; |
|||
} |
|||
|
|||
Marshal.Copy(targetBuffer, 0, targetData.Scan0 + (targetByteLength * y), targetByteLength); |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
if (targetData != null) |
|||
{ |
|||
result.UnlockBits(targetData); |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private static ColorData BuildHistogram(Bitmap sourceImage, int alphaThreshold, int alphaFader) |
|||
{ |
|||
int bitmapWidth = sourceImage.Width; |
|||
int bitmapHeight = sourceImage.Height; |
|||
|
|||
BitmapData data = sourceImage.LockBits( |
|||
Rectangle.FromLTRB(0, 0, bitmapWidth, bitmapHeight), |
|||
ImageLockMode.ReadOnly, |
|||
sourceImage.PixelFormat); |
|||
ColorData colorData = new ColorData(MaxSideIndex, bitmapWidth, bitmapHeight); |
|||
|
|||
try |
|||
{ |
|||
var bitDepth = Image.GetPixelFormatSize(sourceImage.PixelFormat); |
|||
if (bitDepth != 32) |
|||
throw new QuantizationException(string.Format("Thie image you are attempting to quantize does not contain a 32 bit ARGB palette. This image has a bit depth of {0} with {1} colors.", bitDepth, sourceImage.Palette.Entries.Length)); |
|||
var byteLength = data.Stride < 0 ? -data.Stride : data.Stride; |
|||
var byteCount = Math.Max(1, bitDepth >> 3); |
|||
var buffer = new Byte[byteLength]; |
|||
|
|||
var value = new Byte[byteCount]; |
|||
|
|||
for (int y = 0; y < bitmapHeight; y++) |
|||
{ |
|||
Marshal.Copy(data.Scan0 + (byteLength * y), buffer, 0, buffer.Length); |
|||
|
|||
var index = 0; |
|||
for (int x = 0; x < bitmapWidth; x++) |
|||
{ |
|||
var indexOffset = index >> 3; |
|||
|
|||
for (var valueIndex = 0; valueIndex < byteCount; valueIndex++) |
|||
{ |
|||
value[valueIndex] = buffer[valueIndex + indexOffset]; |
|||
} |
|||
|
|||
Pixel pixelValue = new Pixel(value[Alpha], value[Red], value[Green], value[Blue]); |
|||
|
|||
var indexAlpha = (byte)((value[Alpha] >> 3) + 1); |
|||
var indexRed = (byte)((value[Red] >> 3) + 1); |
|||
var indexGreen = (byte)((value[Green] >> 3) + 1); |
|||
var indexBlue = (byte)((value[Blue] >> 3) + 1); |
|||
|
|||
if (value[Alpha] > alphaThreshold) |
|||
{ |
|||
if (value[Alpha] < 255) |
|||
{ |
|||
var alpha = value[Alpha] + (value[Alpha] % alphaFader); |
|||
value[Alpha] = (byte)(alpha > 255 ? 255 : alpha); |
|||
indexAlpha = (byte)((value[Alpha] >> 3) + 1); |
|||
} |
|||
|
|||
colorData.Moments[indexAlpha, indexRed, indexGreen, indexBlue] += pixelValue; |
|||
} |
|||
|
|||
colorData.AddPixel( |
|||
pixelValue, |
|||
new Pixel(indexAlpha, indexRed, indexGreen, indexBlue)); |
|||
index += bitDepth; |
|||
} |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
sourceImage.UnlockBits(data); |
|||
} |
|||
return colorData; |
|||
} |
|||
|
|||
private static ColorData CalculateMoments(ColorData data) |
|||
{ |
|||
var xarea = new ColorMoment[SideSize, SideSize]; |
|||
var xPreviousArea = new ColorMoment[SideSize, SideSize]; |
|||
var area = new ColorMoment[SideSize]; |
|||
for (var alphaIndex = 1; alphaIndex <= MaxSideIndex; ++alphaIndex) |
|||
{ |
|||
for (var redIndex = 1; redIndex <= MaxSideIndex; ++redIndex) |
|||
{ |
|||
Array.Clear(area, 0, area.Length); |
|||
for (var greenIndex = 1; greenIndex <= MaxSideIndex; ++greenIndex) |
|||
{ |
|||
ColorMoment line = new ColorMoment(); |
|||
for (var blueIndex = 1; blueIndex <= MaxSideIndex; ++blueIndex) |
|||
{ |
|||
line += data.Moments[alphaIndex, redIndex, greenIndex, blueIndex]; |
|||
area[blueIndex] += line; |
|||
|
|||
xarea[greenIndex, blueIndex] = xPreviousArea[greenIndex, blueIndex] + area[blueIndex]; |
|||
data.Moments[alphaIndex, redIndex, greenIndex, blueIndex] = data.Moments[alphaIndex - 1, redIndex, greenIndex, blueIndex] + xarea[greenIndex, blueIndex]; |
|||
} |
|||
} |
|||
|
|||
var temp = xarea; |
|||
xarea = xPreviousArea; |
|||
xPreviousArea = temp; |
|||
} |
|||
} |
|||
return data; |
|||
} |
|||
|
|||
private static ColorMoment Top(Box cube, int direction, int position, ColorMoment[, , ,] moment) |
|||
{ |
|||
switch (direction) |
|||
{ |
|||
case Alpha: |
|||
return (moment[position, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] - |
|||
moment[position, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] - |
|||
moment[position, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] + |
|||
moment[position, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) - |
|||
(moment[position, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] - |
|||
moment[position, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] - |
|||
moment[position, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] + |
|||
moment[position, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]); |
|||
|
|||
case Red: |
|||
return (moment[cube.AlphaMaximum, position, cube.GreenMaximum, cube.BlueMaximum] - |
|||
moment[cube.AlphaMaximum, position, cube.GreenMinimum, cube.BlueMaximum] - |
|||
moment[cube.AlphaMinimum, position, cube.GreenMaximum, cube.BlueMaximum] + |
|||
moment[cube.AlphaMinimum, position, cube.GreenMinimum, cube.BlueMaximum]) - |
|||
(moment[cube.AlphaMaximum, position, cube.GreenMaximum, cube.BlueMinimum] - |
|||
moment[cube.AlphaMaximum, position, cube.GreenMinimum, cube.BlueMinimum] - |
|||
moment[cube.AlphaMinimum, position, cube.GreenMaximum, cube.BlueMinimum] + |
|||
moment[cube.AlphaMinimum, position, cube.GreenMinimum, cube.BlueMinimum]); |
|||
|
|||
case Green: |
|||
return (moment[cube.AlphaMaximum, cube.RedMaximum, position, cube.BlueMaximum] - |
|||
moment[cube.AlphaMaximum, cube.RedMinimum, position, cube.BlueMaximum] - |
|||
moment[cube.AlphaMinimum, cube.RedMaximum, position, cube.BlueMaximum] + |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, position, cube.BlueMaximum]) - |
|||
(moment[cube.AlphaMaximum, cube.RedMaximum, position, cube.BlueMinimum] - |
|||
moment[cube.AlphaMaximum, cube.RedMinimum, position, cube.BlueMinimum] - |
|||
moment[cube.AlphaMinimum, cube.RedMaximum, position, cube.BlueMinimum] + |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, position, cube.BlueMinimum]); |
|||
|
|||
case Blue: |
|||
return (moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, position] - |
|||
moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, position] - |
|||
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, position] + |
|||
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, position]) - |
|||
(moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, position] - |
|||
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, position] - |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, position] + |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, position]); |
|||
|
|||
default: |
|||
return new ColorMoment(); |
|||
} |
|||
} |
|||
|
|||
private static ColorMoment Bottom(Box cube, int direction, ColorMoment[, , ,] moment) |
|||
{ |
|||
switch (direction) |
|||
{ |
|||
case Alpha: |
|||
return (-moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] + |
|||
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] + |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] - |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) - |
|||
(-moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] + |
|||
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] + |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] - |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]); |
|||
|
|||
case Red: |
|||
return (-moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] + |
|||
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] + |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] - |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) - |
|||
(-moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] + |
|||
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum] + |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] - |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]); |
|||
|
|||
case Green: |
|||
return (-moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] + |
|||
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] + |
|||
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] - |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) - |
|||
(-moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] + |
|||
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum] + |
|||
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] - |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]); |
|||
|
|||
case Blue: |
|||
return (-moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] + |
|||
moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] + |
|||
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] - |
|||
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]) - |
|||
(-moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] + |
|||
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] + |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] - |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]); |
|||
|
|||
default: |
|||
return new ColorMoment(); |
|||
} |
|||
} |
|||
|
|||
private static CubeCut Maximize(ColorData data, Box cube, int direction, byte first, byte last, ColorMoment whole) |
|||
{ |
|||
var bottom = Bottom(cube, direction, data.Moments); |
|||
float result = 0.0f; |
|||
byte? cutPoint = null; |
|||
|
|||
for (byte position = first; position < last; ++position) |
|||
{ |
|||
var half = bottom + Top(cube, direction, position, data.Moments); |
|||
if (half.Weight == 0) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
long temp = half.WeightedDistance(); |
|||
half = whole - half; |
|||
if (half.Weight != 0) |
|||
{ |
|||
temp += half.WeightedDistance(); |
|||
|
|||
if (temp > result) |
|||
{ |
|||
result = temp; |
|||
cutPoint = position; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return new CubeCut(cutPoint, result); |
|||
} |
|||
|
|||
private bool Cut(ColorData data, ref Box first, ref Box second) |
|||
{ |
|||
int direction; |
|||
var whole = Volume(first, data.Moments); |
|||
var maxAlpha = Maximize(data, first, Alpha, (byte)(first.AlphaMinimum + 1), first.AlphaMaximum, whole); |
|||
var maxRed = Maximize(data, first, Red, (byte)(first.RedMinimum + 1), first.RedMaximum, whole); |
|||
var maxGreen = Maximize(data, first, Green, (byte)(first.GreenMinimum + 1), first.GreenMaximum, whole); |
|||
var maxBlue = Maximize(data, first, Blue, (byte)(first.BlueMinimum + 1), first.BlueMaximum, whole); |
|||
|
|||
if ((maxAlpha.Value >= maxRed.Value) && (maxAlpha.Value >= maxGreen.Value) && (maxAlpha.Value >= maxBlue.Value)) |
|||
{ |
|||
direction = Alpha; |
|||
if (maxAlpha.Position == null) return false; |
|||
} |
|||
else if ((maxRed.Value >= maxAlpha.Value) && (maxRed.Value >= maxGreen.Value) && (maxRed.Value >= maxBlue.Value)) |
|||
direction = Red; |
|||
else |
|||
{ |
|||
if ((maxGreen.Value >= maxAlpha.Value) && (maxGreen.Value >= maxRed.Value) && (maxGreen.Value >= maxBlue.Value)) |
|||
direction = Green; |
|||
else |
|||
direction = Blue; |
|||
} |
|||
|
|||
second.AlphaMaximum = first.AlphaMaximum; |
|||
second.RedMaximum = first.RedMaximum; |
|||
second.GreenMaximum = first.GreenMaximum; |
|||
second.BlueMaximum = first.BlueMaximum; |
|||
|
|||
switch (direction) |
|||
{ |
|||
case Alpha: |
|||
second.AlphaMinimum = first.AlphaMaximum = (byte)maxAlpha.Position; |
|||
second.RedMinimum = first.RedMinimum; |
|||
second.GreenMinimum = first.GreenMinimum; |
|||
second.BlueMinimum = first.BlueMinimum; |
|||
break; |
|||
|
|||
case Red: |
|||
second.RedMinimum = first.RedMaximum = (byte)maxRed.Position; |
|||
second.AlphaMinimum = first.AlphaMinimum; |
|||
second.GreenMinimum = first.GreenMinimum; |
|||
second.BlueMinimum = first.BlueMinimum; |
|||
break; |
|||
|
|||
case Green: |
|||
second.GreenMinimum = first.GreenMaximum = (byte)maxGreen.Position; |
|||
second.AlphaMinimum = first.AlphaMinimum; |
|||
second.RedMinimum = first.RedMinimum; |
|||
second.BlueMinimum = first.BlueMinimum; |
|||
break; |
|||
|
|||
case Blue: |
|||
second.BlueMinimum = first.BlueMaximum = (byte)maxBlue.Position; |
|||
second.AlphaMinimum = first.AlphaMinimum; |
|||
second.RedMinimum = first.RedMinimum; |
|||
second.GreenMinimum = first.GreenMinimum; |
|||
break; |
|||
} |
|||
|
|||
first.Size = (first.AlphaMaximum - first.AlphaMinimum) * (first.RedMaximum - first.RedMinimum) * (first.GreenMaximum - first.GreenMinimum) * (first.BlueMaximum - first.BlueMinimum); |
|||
second.Size = (second.AlphaMaximum - second.AlphaMinimum) * (second.RedMaximum - second.RedMinimum) * (second.GreenMaximum - second.GreenMinimum) * (second.BlueMaximum - second.BlueMinimum); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
private static float CalculateVariance(ColorData data, Box cube) |
|||
{ |
|||
ColorMoment volume = Volume(cube, data.Moments); |
|||
return volume.Variance(); |
|||
} |
|||
|
|||
private static ColorMoment Volume(Box cube, ColorMoment[, , ,] moment) |
|||
{ |
|||
return (moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] - |
|||
moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] - |
|||
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] + |
|||
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] - |
|||
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] + |
|||
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] + |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] - |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) - |
|||
|
|||
(moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] - |
|||
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] - |
|||
moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] + |
|||
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] - |
|||
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] + |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] + |
|||
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum] - |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]); |
|||
} |
|||
|
|||
private static float VolumeFloat(Box cube, float[, , ,] moment) |
|||
{ |
|||
return (moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] - |
|||
moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] - |
|||
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] + |
|||
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] - |
|||
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] + |
|||
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] + |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] - |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) - |
|||
|
|||
(moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] - |
|||
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] - |
|||
moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] + |
|||
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] - |
|||
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] + |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] + |
|||
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum] - |
|||
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]); |
|||
} |
|||
|
|||
private Box[] SplitData(ref int colorCount, ColorData data) |
|||
{ |
|||
--colorCount; |
|||
var next = 0; |
|||
var volumeVariance = new float[MaxColor]; |
|||
var cubes = new Box[MaxColor]; |
|||
cubes[0].AlphaMaximum = MaxSideIndex; |
|||
cubes[0].RedMaximum = MaxSideIndex; |
|||
cubes[0].GreenMaximum = MaxSideIndex; |
|||
cubes[0].BlueMaximum = MaxSideIndex; |
|||
for (var cubeIndex = 1; cubeIndex < colorCount; ++cubeIndex) |
|||
{ |
|||
if (Cut(data, ref cubes[next], ref cubes[cubeIndex])) |
|||
{ |
|||
volumeVariance[next] = cubes[next].Size > 1 ? CalculateVariance(data, cubes[next]) : 0.0f; |
|||
volumeVariance[cubeIndex] = cubes[cubeIndex].Size > 1 ? CalculateVariance(data, cubes[cubeIndex]) : 0.0f; |
|||
} |
|||
else |
|||
{ |
|||
volumeVariance[next] = 0.0f; |
|||
cubeIndex--; |
|||
} |
|||
|
|||
next = 0; |
|||
var temp = volumeVariance[0]; |
|||
|
|||
for (var index = 1; index <= cubeIndex; ++index) |
|||
{ |
|||
if (volumeVariance[index] <= temp) continue; |
|||
temp = volumeVariance[index]; |
|||
next = index; |
|||
} |
|||
|
|||
if (temp > 0.0) continue; |
|||
colorCount = cubeIndex + 1; |
|||
break; |
|||
} |
|||
return cubes.Take(colorCount).ToArray(); |
|||
} |
|||
|
|||
protected Lookup[] BuildLookups(Box[] cubes, ColorData data) |
|||
{ |
|||
List<Lookup> lookups = new List<Lookup>(cubes.Length); |
|||
|
|||
foreach (var cube in cubes) |
|||
{ |
|||
var volume = Volume(cube, data.Moments); |
|||
|
|||
if (volume.Weight <= 0) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
var lookup = new Lookup |
|||
{ |
|||
Alpha = (int)(volume.Alpha / volume.Weight), |
|||
Red = (int)(volume.Red / volume.Weight), |
|||
Green = (int)(volume.Green / volume.Weight), |
|||
Blue = (int)(volume.Blue / volume.Weight) |
|||
}; |
|||
|
|||
lookups.Add(lookup); |
|||
} |
|||
|
|||
return lookups.ToArray(); |
|||
} |
|||
|
|||
protected abstract QuantizedPalette GetQuantizedPalette(int colorCount, ColorData data, Box[] cubes, int alphaThreshold); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue