using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
namespace TEAMModelOS.SDK.PngQuant
{
public abstract class PngQuantizerBase
{
private const int MaxColor = 256;
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;
///
/// 處理Base64圖片,支持不同格式圖片統一轉換為8位元PNG
///
///
///
///
///
public Image QuantizeImage(Stream base64, int width = 0, int height = 0, int alphaThreshold = 10, int alphaFader = 70)
{
Bitmap oldbmp = new Bitmap(base64);
Bitmap newbmp = null;
try
{
int neww = width;
int newh = height;
//先處理縮圖計算,指定宽,高按比例
if (width == 0 || height == 0)
{
neww = oldbmp.Width > 1024 ? 1024 : oldbmp.Width;
newh = Convert.ToInt32(oldbmp.Height * neww / oldbmp.Width);
}
//縮圖時自動保存為Format32bppArgb無須另外處理
newbmp = new Bitmap(oldbmp, new Size(neww, newh));
return QuantizeImage(newbmp, alphaThreshold, alphaFader);
//TODO 判斷圖片是否非32位元和8位元,處理轉換,這個不准,已改為取字節判斷
//var d = Image.GetPixelFormatSize(oldbmp.PixelFormat);
//TODO 此段碼為轉換32位元,但上面縮圖保存時以自帶,後面研究能否解決8位元保存問題
// newbmp = new Bitmap(neww, newh, PixelFormat.Format8bppIndexed);
// using (var gr = Graphics.FromImage(newbmp))
// gr.DrawImage(oldbmp, new Rectangle(0, 0, oldbmp.Width, oldbmp.Height));
// return QuantizeImage(newbmp, alphaThreshold, alphaFader);
}
catch
{
return null;
}
finally
{
oldbmp.Dispose();
newbmp.Dispose();
}
}
private 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
{
targetData = result.LockBits(Rectangle.FromLTRB(0, 0, result.Width, result.Height), 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 targetSize = targetByteLength * result.Height;
var targetOffset = 0;
var targetBuffer = new byte[targetSize];
var targetValue = new byte[targetByteCount];
var pixelIndex = 0;
for (var y = 0; y < result.Height; y++)
{
var targetIndex = 0;
for (var x = 0; x < result.Width; x++)
{
var targetIndexOffset = targetIndex >> 3;
targetValue[0] = (byte)(palette.PixelIndex[pixelIndex] == -1 ? palette.Colors.Count - 1 : palette.PixelIndex[pixelIndex]);
pixelIndex++;
for (var valueIndex = 0; valueIndex < targetByteCount; valueIndex++)
targetBuffer[targetOffset + valueIndex + targetIndexOffset] = targetValue[valueIndex];
targetIndex += targetBitDepth;
}
targetOffset += targetByteLength;
}
Marshal.Copy(targetBuffer, 0, targetData.Scan0, targetSize);
}
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 offset = 0;
var buffer = new Byte[byteLength * sourceImage.Height];
var value = new Byte[byteCount];
Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);
for (int y = 0; y < bitmapHeight; y++)
{
var index = 0;
for (int x = 0; x < bitmapWidth; x++)
{
var indexOffset = index >> 3;
for (var valueIndex = 0; valueIndex < byteCount; valueIndex++)
value[valueIndex] = buffer[offset + valueIndex + indexOffset];
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.Weights[indexAlpha, indexRed, indexGreen, indexBlue]++;
colorData.MomentsRed[indexAlpha, indexRed, indexGreen, indexBlue] += value[Red];
colorData.MomentsGreen[indexAlpha, indexRed, indexGreen, indexBlue] += value[Green];
colorData.MomentsBlue[indexAlpha, indexRed, indexGreen, indexBlue] += value[Blue];
colorData.MomentsAlpha[indexAlpha, indexRed, indexGreen, indexBlue] += value[Alpha];
colorData.Moments[indexAlpha, indexRed, indexGreen, indexBlue] += (value[Alpha] * value[Alpha]) +
(value[Red] * value[Red]) +
(value[Green] * value[Green]) +
(value[Blue] * value[Blue]);
}
colorData.AddPixel(
new Pixel(value[Alpha], value[Red], value[Green], value[Blue]),
BitConverter.ToInt32(new[] { indexAlpha, indexRed, indexGreen, indexBlue }, 0));
index += bitDepth;
}
offset += byteLength;
}
}
finally
{
sourceImage.UnlockBits(data);
}
return colorData;
}
private static ColorData CalculateMoments(ColorData data)
{
for (var alphaIndex = 1; alphaIndex <= MaxSideIndex; ++alphaIndex)
{
var xarea = new long[SideSize, SideSize, SideSize];
var xareaAlpha = new long[SideSize, SideSize, SideSize];
var xareaRed = new long[SideSize, SideSize, SideSize];
var xareaGreen = new long[SideSize, SideSize, SideSize];
var xareaBlue = new long[SideSize, SideSize, SideSize];
var xarea2 = new float[SideSize, SideSize, SideSize];
for (var redIndex = 1; redIndex <= MaxSideIndex; ++redIndex)
{
var area = new long[SideSize];
var areaAlpha = new long[SideSize];
var areaRed = new long[SideSize];
var areaGreen = new long[SideSize];
var areaBlue = new long[SideSize];
var area2 = new float[SideSize];
for (var greenIndex = 1; greenIndex <= MaxSideIndex; ++greenIndex)
{
long line = 0;
long lineAlpha = 0;
long lineRed = 0;
long lineGreen = 0;
long lineBlue = 0;
var line2 = 0.0f;
for (var blueIndex = 1; blueIndex <= MaxSideIndex; ++blueIndex)
{
line += data.Weights[alphaIndex, redIndex, greenIndex, blueIndex];
lineAlpha += data.MomentsAlpha[alphaIndex, redIndex, greenIndex, blueIndex];
lineRed += data.MomentsRed[alphaIndex, redIndex, greenIndex, blueIndex];
lineGreen += data.MomentsGreen[alphaIndex, redIndex, greenIndex, blueIndex];
lineBlue += data.MomentsBlue[alphaIndex, redIndex, greenIndex, blueIndex];
line2 += data.Moments[alphaIndex, redIndex, greenIndex, blueIndex];
area[blueIndex] += line;
areaAlpha[blueIndex] += lineAlpha;
areaRed[blueIndex] += lineRed;
areaGreen[blueIndex] += lineGreen;
areaBlue[blueIndex] += lineBlue;
area2[blueIndex] += line2;
xarea[redIndex, greenIndex, blueIndex] = xarea[redIndex - 1, greenIndex, blueIndex] + area[blueIndex];
xareaAlpha[redIndex, greenIndex, blueIndex] = xareaAlpha[redIndex - 1, greenIndex, blueIndex] + areaAlpha[blueIndex];
xareaRed[redIndex, greenIndex, blueIndex] = xareaRed[redIndex - 1, greenIndex, blueIndex] + areaRed[blueIndex];
xareaGreen[redIndex, greenIndex, blueIndex] = xareaGreen[redIndex - 1, greenIndex, blueIndex] + areaGreen[blueIndex];
xareaBlue[redIndex, greenIndex, blueIndex] = xareaBlue[redIndex - 1, greenIndex, blueIndex] + areaBlue[blueIndex];
xarea2[redIndex, greenIndex, blueIndex] = xarea2[redIndex - 1, greenIndex, blueIndex] + area2[blueIndex];
data.Weights[alphaIndex, redIndex, greenIndex, blueIndex] = data.Weights[alphaIndex - 1, redIndex, greenIndex, blueIndex] + xarea[redIndex, greenIndex, blueIndex];
data.MomentsAlpha[alphaIndex, redIndex, greenIndex, blueIndex] = data.MomentsAlpha[alphaIndex - 1, redIndex, greenIndex, blueIndex] + xareaAlpha[redIndex, greenIndex, blueIndex];
data.MomentsRed[alphaIndex, redIndex, greenIndex, blueIndex] = data.MomentsRed[alphaIndex - 1, redIndex, greenIndex, blueIndex] + xareaRed[redIndex, greenIndex, blueIndex];
data.MomentsGreen[alphaIndex, redIndex, greenIndex, blueIndex] = data.MomentsGreen[alphaIndex - 1, redIndex, greenIndex, blueIndex] + xareaGreen[redIndex, greenIndex, blueIndex];
data.MomentsBlue[alphaIndex, redIndex, greenIndex, blueIndex] = data.MomentsBlue[alphaIndex - 1, redIndex, greenIndex, blueIndex] + xareaBlue[redIndex, greenIndex, blueIndex];
data.Moments[alphaIndex, redIndex, greenIndex, blueIndex] = data.Moments[alphaIndex - 1, redIndex, greenIndex, blueIndex] + xarea2[redIndex, greenIndex, blueIndex];
}
}
}
}
return data;
}
private static long Top(Box cube, int direction, int position, long[,,,] 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 0;
}
}
private static long Bottom(Box cube, int direction, long[,,,] 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 0;
}
}
private static CubeCut Maximize(ColorData data, Box cube, int direction, byte first, byte last, long wholeAlpha, long wholeRed, long wholeGreen, long wholeBlue, long wholeWeight)
{
var bottomAlpha = Bottom(cube, direction, data.MomentsAlpha);
var bottomRed = Bottom(cube, direction, data.MomentsRed);
var bottomGreen = Bottom(cube, direction, data.MomentsGreen);
var bottomBlue = Bottom(cube, direction, data.MomentsBlue);
var bottomWeight = Bottom(cube, direction, data.Weights);
var result = 0.0f;
byte? cutPoint = null;
for (var position = first; position < last; ++position)
{
var halfAlpha = bottomAlpha + Top(cube, direction, position, data.MomentsAlpha);
var halfRed = bottomRed + Top(cube, direction, position, data.MomentsRed);
var halfGreen = bottomGreen + Top(cube, direction, position, data.MomentsGreen);
var halfBlue = bottomBlue + Top(cube, direction, position, data.MomentsBlue);
var halfWeight = bottomWeight + Top(cube, direction, position, data.Weights);
if (halfWeight == 0) continue;
var halfDistance = halfAlpha * halfAlpha + halfRed * halfRed + halfGreen * halfGreen + halfBlue * halfBlue;
var temp = halfDistance / halfWeight;
halfAlpha = wholeAlpha - halfAlpha;
halfRed = wholeRed - halfRed;
halfGreen = wholeGreen - halfGreen;
halfBlue = wholeBlue - halfBlue;
halfWeight = wholeWeight - halfWeight;
if (halfWeight != 0)
{
halfDistance = halfAlpha * halfAlpha + halfRed * halfRed + halfGreen * halfGreen + halfBlue * halfBlue;
temp += halfDistance / halfWeight;
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 wholeAlpha = Volume(first, data.MomentsAlpha);
var wholeRed = Volume(first, data.MomentsRed);
var wholeGreen = Volume(first, data.MomentsGreen);
var wholeBlue = Volume(first, data.MomentsBlue);
var wholeWeight = Volume(first, data.Weights);
var maxAlpha = Maximize(data, first, Alpha, (byte)(first.AlphaMinimum + 1), first.AlphaMaximum, wholeAlpha, wholeRed, wholeGreen, wholeBlue, wholeWeight);
var maxRed = Maximize(data, first, Red, (byte)(first.RedMinimum + 1), first.RedMaximum, wholeAlpha, wholeRed, wholeGreen, wholeBlue, wholeWeight);
var maxGreen = Maximize(data, first, Green, (byte)(first.GreenMinimum + 1), first.GreenMaximum, wholeAlpha, wholeRed, wholeGreen, wholeBlue, wholeWeight);
var maxBlue = Maximize(data, first, Blue, (byte)(first.BlueMinimum + 1), first.BlueMaximum, wholeAlpha, wholeRed, wholeGreen, wholeBlue, wholeWeight);
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)
{
float volumeAlpha = Volume(cube, data.MomentsAlpha);
float volumeRed = Volume(cube, data.MomentsRed);
float volumeGreen = Volume(cube, data.MomentsGreen);
float volumeBlue = Volume(cube, data.MomentsBlue);
float volumeMoment = VolumeFloat(cube, data.Moments);
float volumeWeight = Volume(cube, data.Weights);
float distance = volumeAlpha * volumeAlpha + volumeRed * volumeRed + volumeGreen * volumeGreen + volumeBlue * volumeBlue;
var result = volumeMoment - distance / volumeWeight;
return double.IsNaN(result) ? 0.0f : result;
}
private static long Volume(Box cube, long[,,,] 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 IEnumerable 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).ToList();
}
protected LookupData BuildLookups(IEnumerable cubes, ColorData data)
{
LookupData lookups = new LookupData(SideSize);
int lookupsCount = lookups.Lookups.Count;
foreach (var cube in cubes)
{
for (var alphaIndex = (byte)(cube.AlphaMinimum + 1); alphaIndex <= cube.AlphaMaximum; ++alphaIndex)
{
for (var redIndex = (byte)(cube.RedMinimum + 1); redIndex <= cube.RedMaximum; ++redIndex)
{
for (var greenIndex = (byte)(cube.GreenMinimum + 1); greenIndex <= cube.GreenMaximum; ++greenIndex)
{
for (var blueIndex = (byte)(cube.BlueMinimum + 1); blueIndex <= cube.BlueMaximum; ++blueIndex)
{
lookups.Tags[alphaIndex, redIndex, greenIndex, blueIndex] = lookupsCount;
}
}
}
}
var weight = Volume(cube, data.Weights);
if (weight <= 0) continue;
var lookup = new Lookup
{
Alpha = (int)(Volume(cube, data.MomentsAlpha) / weight),
Red = (int)(Volume(cube, data.MomentsRed) / weight),
Green = (int)(Volume(cube, data.MomentsGreen) / weight),
Blue = (int)(Volume(cube, data.MomentsBlue) / weight)
};
lookups.Lookups.Add(lookup);
}
return lookups;
}
protected abstract QuantizedPalette GetQuantizedPalette(int colorCount, ColorData data, IEnumerable cubes, int alphaThreshold);
}
}