DS-Pokemon-Rom-Editor/Ekona/Images/NitroTextureCompressor.cs
AdAstra-LD 73ca3d06a9 Added Ekona and NImages file support
More progress with the Trainer Editor
2021-07-21 23:04:53 +02:00

914 lines
35 KiB
C#

// -----------------------------------------------------------------------
// <copyright file="NitroTextureCompressor.cs" company="NII">
//
// Copyright (C) 2016 MetLob
// Used NVidia optimization methods of end-points
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// </copyright>
// -----------------------------------------------------------------------
namespace Ekona.Images
{
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.Text;
using Mathematics;
/// <summary>
/// Fast texture compressor for Nintendo DS format
/// </summary>
public class NitroTextureCompressor
{
#region Math
private static double Clamp(double a)
{
if (a > 255) a = 255;
if (a < 0) a = 0;
return a;
}
#endregion
#region Error
private static double ColorLengthSquared(Vector3 v)
{
return Math.Truncate(v.X * v.X + v.Y * v.Y + v.Z * v.Z);
}
private static double Error(Vector3[] inputPoints, int[] mask, Vector3 a, Vector3 b, bool hasAlpha)
{
Vector3[] dxtFourPointsTemp = new Vector3[4];
dxtFourPointsTemp[0] = a;
dxtFourPointsTemp[1] = b;
// Calculate inside DXT colors
if (!hasAlpha)
{
dxtFourPointsTemp[2] = (5 * dxtFourPointsTemp[0] + 3* dxtFourPointsTemp[1]) / 8;
dxtFourPointsTemp[3] = (5 * dxtFourPointsTemp[1] + 3* dxtFourPointsTemp[0]) / 8;
}
else
{
dxtFourPointsTemp[2] = (dxtFourPointsTemp[0] + dxtFourPointsTemp[1]) / 2;
dxtFourPointsTemp[3] = new Vector3(double.MaxValue, double.MaxValue, double.MaxValue);
}
double error = 0;
for (int i = 0; i < inputPoints.Length; i++)
{
if (mask[i] > 0)
{
double dist0 = ColorLengthSquared(inputPoints[i] - dxtFourPointsTemp[0]);
double dist1 = ColorLengthSquared(inputPoints[i] - dxtFourPointsTemp[1]);
double dist2 = ColorLengthSquared(inputPoints[i] - dxtFourPointsTemp[2]);
double dist3 = ColorLengthSquared(inputPoints[i] - dxtFourPointsTemp[3]);
if (dist0 < dist2 && dist0 < dist3)
{
error += dist0;
}
else if (dist1 < dist2 && dist1 < dist3)
{
error += dist1;
}
else error += Math.Min(dist2, dist3);
}
}
return error;
}
#endregion
#region Colors manipulations
private static uint ColorSquaredDistance(Color c1, Color c2)
{
int r = c1.R - c2.R;
int g = c1.G - c2.G;
int b = c1.B - c2.B;
return (uint)(r * r + g * g + b * b);
}
private static Color VectorToColor(Vector3 v)
{
return Color.FromArgb(255,
(byte)Clamp(v.X /* 255 / 31*/),
(byte)Clamp(v.Y /* 255 / 63*/),
(byte)Clamp(v.Z /* 255 / 31*/));
}
private static Vector3 ColorToVector(Color c)
{
return new Vector3(c.R, c.G, c.B);
}
private static Color SumColors(Color a, Color b, int wa, int wb)
{
return Color.FromArgb(
(a.R * wa + b.R * wb) / (wa + wb),
(a.G * wa + b.G * wb) / (wa + wb),
(a.B * wa + b.B * wb) / (wa + wb));
}
#endregion
#region Compressed block processing
private static Color CalcSecondBoundColor(Color c, Color boundColor)
{
int r = 2 * c.R - boundColor.R;
int g = 2 * c.G - boundColor.G;
int b = 2 * c.B - boundColor.B;
if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) return c;
return Color.FromArgb(255, r, g, b);
}
private static bool IsCompressedBlock(Color[] colors, bool[] uniqueFlags, int count, int aIndex, int bIndex, out Color[] dxtBoundColors, ref bool hasAlpha)
{
Color[] uniqueColors = new Color[count];
Vector3[] inputPoints = new Vector3[count];
if (count > 0)
{
uniqueColors[0] = colors[aIndex];
uniqueColors[count - 1] = colors[bIndex];
for (int i = 0, j = 1; i < colors.Length && j < count - 1; i++)
if (uniqueFlags[i] && i != aIndex && i != bIndex) uniqueColors[j++] = colors[i];
for (int i = 0; i < count; i++)
inputPoints[i] = new Vector3(uniqueColors[i].R / 8, uniqueColors[i].G / 8, uniqueColors[i].B / 8);
}
dxtBoundColors = new Color[2];
if (count == 0)
{
#region Transparent block
dxtBoundColors[0] = Color.Black;
dxtBoundColors[1] = dxtBoundColors[0];
hasAlpha = true;
#endregion
}
else if (count == 1)
{
#region Single color
int r = (byte)(inputPoints[0].X * 255 / 31);
int g = (byte)(inputPoints[0].Y * 255 / 31);
int b = (byte)(inputPoints[0].Z * 255 / 31);
if (uniqueColors[0].R == r && uniqueColors[0].G == g && uniqueColors[0].B == b)
{
dxtBoundColors[0] = uniqueColors[0];
dxtBoundColors[1] = dxtBoundColors[0];
hasAlpha = true;
}
else
{
Vector3[] dxtPoints = new Vector3[2];
dxtPoints[0] = Vector3.Zero;
dxtPoints[1] = 2 * new Vector3(uniqueColors[0].R, uniqueColors[0].G, uniqueColors[0].B);
if (dxtPoints[1].X > 255)
{
dxtPoints[0].X = dxtPoints[1].X - 255;
dxtPoints[1].X -= dxtPoints[0].X;
}
if (dxtPoints[1].Y > 255)
{
dxtPoints[0].Y = dxtPoints[1].Y - 255;
dxtPoints[1].Y -= dxtPoints[0].Y;
}
if (dxtPoints[1].Z > 255)
{
dxtPoints[0].Z = dxtPoints[1].Z - 255;
dxtPoints[1].Z -= dxtPoints[0].Z;
}
dxtBoundColors[0] = Color.FromArgb(255, (byte)dxtPoints[0].X, (byte)dxtPoints[0].Y, (byte)dxtPoints[0].Z);
dxtBoundColors[1] = Color.FromArgb(255, (byte)dxtPoints[1].X, (byte)dxtPoints[1].Y, (byte)dxtPoints[1].Z);
hasAlpha = true;
}
#endregion
}
else if (count == 2)
{
#region Two colors block
byte r0 = (byte)(inputPoints[0].X * 255 / 31);
byte g0 = (byte)(inputPoints[0].Y * 255 / 31);
byte b0 = (byte)(inputPoints[0].Z * 255 / 31);
byte r1 = (byte)(inputPoints[1].X * 255 / 31);
byte g1 = (byte)(inputPoints[1].Y * 255 / 31);
byte b1 = (byte)(inputPoints[1].Z * 255 / 31);
bool color0 = uniqueColors[0].R == r0 && uniqueColors[0].G == g0 && uniqueColors[0].B == b0;
bool color1 = uniqueColors[1].R == r1 && uniqueColors[1].G == g1 && uniqueColors[1].B == b1;
if (color0 && color1)
{
dxtBoundColors[0] = uniqueColors[0];
dxtBoundColors[1] = uniqueColors[1];
hasAlpha = true;
}
else
{
if (color0)
{
dxtBoundColors[0] = uniqueColors[0];
dxtBoundColors[1] = CalcSecondBoundColor(uniqueColors[1], uniqueColors[0]);
hasAlpha = true;
}
else if (color1 || hasAlpha)
{
dxtBoundColors[0] = CalcSecondBoundColor(uniqueColors[0], uniqueColors[1]);
dxtBoundColors[1] = uniqueColors[1];
hasAlpha = true;
}
else
{
dxtBoundColors[0] = CalcSecondBoundColor(uniqueColors[0], uniqueColors[1]);
dxtBoundColors[1] = CalcSecondBoundColor(uniqueColors[1], uniqueColors[0]);
if (dxtBoundColors[0] == uniqueColors[0] || dxtBoundColors[1] == uniqueColors[1])
hasAlpha = true;
}
}
#endregion
}
else if (count == 3)
{
#region Three colors block
Color middle = SumColors(uniqueColors[0], uniqueColors[2], 1, 1);
if (middle.R == uniqueColors[1].R && middle.G == uniqueColors[1].G && middle.B == uniqueColors[1].B)
{
byte r0 = (byte)(inputPoints[0].X * 255 / 31);
byte g0 = (byte)(inputPoints[0].Y * 255 / 31);
byte b0 = (byte)(inputPoints[0].Z * 255 / 31);
byte r2 = (byte)(inputPoints[2].X * 255 / 31);
byte g2 = (byte)(inputPoints[2].Y * 255 / 31);
byte b2 = (byte)(inputPoints[2].Z * 255 / 31);
bool color0 = uniqueColors[0].R == r0 && uniqueColors[0].G == g0 && uniqueColors[0].B == b0;
bool color2 = uniqueColors[2].R == r2 && uniqueColors[2].G == g2 && uniqueColors[2].B == b2;
if ((color0 && color2) || hasAlpha)
{
dxtBoundColors[0] = uniqueColors[0];
dxtBoundColors[1] = uniqueColors[2];
hasAlpha = true;
}
else if (color0)
{
dxtBoundColors[0] = uniqueColors[0];
dxtBoundColors[1] = CalcSecondBoundColor(uniqueColors[2], uniqueColors[1]);
hasAlpha = (dxtBoundColors[1] == uniqueColors[2]);
}
else
{
dxtBoundColors[0] = CalcSecondBoundColor(uniqueColors[0], uniqueColors[1]);
dxtBoundColors[1] = uniqueColors[2];
hasAlpha = (dxtBoundColors[0] == uniqueColors[0]);
}
}
else if (!hasAlpha)
{
Color m1 = SumColors(uniqueColors[0], uniqueColors[2], 5, 3);
Color m2 = SumColors(uniqueColors[0], uniqueColors[2], 3, 5);
if ((uniqueColors[1].R == m1.R && uniqueColors[1].G == m1.G && uniqueColors[1].B == m1.B)
|| (uniqueColors[1].R == m2.R && uniqueColors[1].G == m2.G && uniqueColors[1].B == m2.B))
{
dxtBoundColors[0] = uniqueColors[0];
dxtBoundColors[1] = uniqueColors[2];
}
else return false;
}
else return false;
#endregion
}
else if (count == 4 && !hasAlpha)
{
#region Four colors block
Color m1 = SumColors(uniqueColors[0], uniqueColors[3], 5, 3);
Color m2 = SumColors(uniqueColors[0], uniqueColors[3], 3, 5);
if ((uniqueColors[1].R == m1.R && uniqueColors[1].G == m1.G && uniqueColors[1].B == m1.B
&& uniqueColors[2].R == m2.R && uniqueColors[2].G == m2.G && uniqueColors[2].B == m2.B)
|| (uniqueColors[2].R == m1.R && uniqueColors[2].G == m1.G && uniqueColors[2].B == m1.B
&& uniqueColors[1].R == m2.R && uniqueColors[1].G == m2.G && uniqueColors[1].B == m2.B))
{
dxtBoundColors[0] = uniqueColors[0];
dxtBoundColors[1] = uniqueColors[3];
}
else return false;
#endregion
}
else return false;
return true;
}
#endregion
#region NVidia optimization
static void OptimizeEndPoints3(Vector3[] block, ref Vector3[] dxtPoints, ref uint indices)
{
float alpha2_sum = 0.0f;
float beta2_sum = 0.0f;
float alphabeta_sum = 0.0f;
Vector3 alphax_sum = Vector3.Zero;
Vector3 betax_sum = Vector3.Zero;
for (int i = 0; i < 16; ++i)
{
uint bits = indices >> (2 * i);
float beta = (float)(bits & 1);
if ((bits & 2) > 0) beta = 0.5f;
float alpha = 1.0f - beta;
alpha2_sum += alpha * alpha;
beta2_sum += beta * beta;
alphabeta_sum += alpha * beta;
alphax_sum += alpha * block[i];
betax_sum += beta * block[i];
}
float denom = alpha2_sum * beta2_sum - alphabeta_sum * alphabeta_sum;
if (NvMath.Equal(denom, 0.0f)) return;
float factor = 1.0f / denom;
Vector3 a = (alphax_sum * beta2_sum - betax_sum * alphabeta_sum) * factor;
Vector3 b = (betax_sum * alpha2_sum - alphax_sum * alphabeta_sum) * factor;
a = Vector3.Clamp(a, 0, 255);
b = Vector3.Clamp(b, 0, 255);
//UInt16 color0 = roundAndExpand(ref a);
//UInt16 color1 = roundAndExpand(ref b);
//if (color0 < color1)
//{
// NvMath.swap(ref a, ref b);
// NvMath.swap(ref color0, ref color1);
//}
//indices = computeIndices3(block, a, b);
dxtPoints[0] = b;
dxtPoints[1] = a;
}
static void OptimizeEndPoints4(Vector3[] block, ref Vector3[] dxtPoints, ref uint indices)
{
float alpha2_sum = 0.0f;
float beta2_sum = 0.0f;
float alphabeta_sum = 0.0f;
Vector3 alphax_sum = Vector3.Zero;
Vector3 betax_sum = Vector3.Zero;
for (int i = 0; i < 16; ++i)
{
uint bits = indices >> (2 * i);
float beta = (float)(bits & 1);
if ((bits & 2) > 0) beta = (1 + beta) / 3.0f;
float alpha = 1.0f - beta;
alpha2_sum += alpha * alpha;
beta2_sum += beta * beta;
alphabeta_sum += alpha * beta;
alphax_sum += alpha * block[i];
betax_sum += beta * block[i];
}
float denom = alpha2_sum * beta2_sum - alphabeta_sum * alphabeta_sum;
if (NvMath.Equal(denom, 0.0f)) return;
float factor = 1.0f / denom;
Vector3 a = (alphax_sum * beta2_sum - betax_sum * alphabeta_sum) * factor;
Vector3 b = (betax_sum * alpha2_sum - alphax_sum * alphabeta_sum) * factor;
a = Vector3.Clamp(a, 0, 255);
b = Vector3.Clamp(b, 0, 255);
//UInt16 color0 = roundAndExpand(ref a);
//UInt16 color1 = roundAndExpand(ref b);
//if (color0 < color1)
//{
// NvMath.swap(ref a, ref b);
// NvMath.swap(ref color0, ref color1);
//}
//indices = computeIndices4(block, a, b);
dxtPoints[0] = a;
dxtPoints[1] = b;
}
static uint ComputeIndices3(Vector3[] block, Vector3 maxColor, Vector3 minColor)
{
Vector3[] palette = new Vector3[4];
palette[0] = minColor;
palette[1] = maxColor;
palette[2] = (palette[0] + palette[1]) * 0.5f;
uint indices = 0;
for (int i = 0; i < 16; i++)
{
double d0 = ColorDistance(palette[0], block[i]);
double d1 = ColorDistance(palette[1], block[i]);
double d2 = ColorDistance(palette[2], block[i]);
uint index;
if (d0 < d1 && d0 < d2) index = 0;
else if (d1 < d2) index = 1;
else index = 2;
indices |= index << (2 * i);
}
return indices;
}
static uint ComputeIndices4(Vector3[] block, Vector3 maxColor, Vector3 minColor)
{
Vector3[] palette = new Vector3[4];
palette[0] = maxColor;
palette[1] = minColor;
palette[2] = Vector3.Lerp(palette[0], palette[1], 3.0f / 8.0f);
palette[3] = Vector3.Lerp(palette[0], palette[1], 5.0f / 8.0f);
uint indices = 0;
for (int i = 0; i < 16; i++)
{
double d0 = ColorDistance(palette[0], block[i]);
double d1 = ColorDistance(palette[1], block[i]);
double d2 = ColorDistance(palette[2], block[i]);
double d3 = ColorDistance(palette[3], block[i]);
uint b0 = d0 > d3 ? (uint)1 : 0;
uint b1 = d1 > d2 ? (uint)1 : 0;
uint b2 = d0 > d2 ? (uint)1 : 0;
uint b3 = d1 > d3 ? (uint)1 : 0;
uint b4 = d2 > d3 ? (uint)1 : 0;
uint x0 = b1 & b2;
uint x1 = b0 & b3;
uint x2 = b0 & b4;
indices |= (x2 | ((x0 | x1) << 1)) << (2 * i);
}
return indices;
}
static double ColorDistance(Vector3 c0, Vector3 c1)
{
return (c0 - c1).LengthSquared();
}
// Takes a normalized color in [0, 255] range and returns
static ushort RoundAndExpand(ref Vector3 v)
{
uint r = (uint)Math.Floor(NvMath.Clamp(v.X * (31.0f / 255.0f), 0.0f, 31.0f));
uint g = (uint)Math.Floor(NvMath.Clamp(v.Y * (31.0f / 255.0f), 0.0f, 31.0f));
uint b = (uint)Math.Floor(NvMath.Clamp(v.Z * (31.0f / 255.0f), 0.0f, 31.0f));
float r0 = (float)(((r + 0) << 3) | ((r + 0) >> 2));
float r1 = (float)(((r + 1) << 3) | ((r + 1) >> 2));
if (Math.Abs(v.X - r1) < Math.Abs(v.X - r0)) r = Math.Min(r + 1, 31U);
float g0 = (float)(((g + 0) << 3) | ((g + 0) >> 2));
float g1 = (float)(((g + 1) << 3) | ((g + 1) >> 2));
if (Math.Abs(v.Y - g1) < Math.Abs(v.Y - g0)) g = Math.Min(g + 1, 31U);
float b0 = (float)(((b + 0) << 3) | ((b + 0) >> 2));
float b1 = (float)(((b + 1) << 3) | ((b + 1) >> 2));
if (Math.Abs(v.Z - b1) < Math.Abs(v.Z - b0)) b = Math.Min(b + 1, 31U);
ushort w = (ushort)((b << 10) | (g << 5) | r);
r = (r << 3) | (r >> 2);
g = (g << 3) | (g >> 2);
b = (b << 3) | (b >> 2);
v = new Vector3((float)r, (float)g, (float)b);
return w;
}
#endregion
#region Compress
/// <summary>
/// The compress 4x4 block.
/// </summary>
/// <param name="block">
/// The block of 4x4 colors.
/// </param>
/// <param name="mask">
/// The mask of 4x4 pixels (1 - visible, 0 - not visible).
/// </param>
/// <param name="compressColors">
/// Four compressed colors.
/// </param>
/// <param name="c0">
/// The color0.
/// </param>
/// <param name="c1">
/// The color1.
/// </param>
/// <param name="texels">
/// Texels.
/// </param>
/// <returns>
/// The approximation error<see cref="double"/>.
/// </returns>
public static double CompressBlock(Color[] block, int[] mask, out Color[] compressColors, out ushort c0, out ushort c1, out uint texels)
{
#region Init
bool hasAlpha = false;
double bestError = 0;
Vector3[] inputPoints = new Vector3[block.Length];
bool[] uniqueFlags = new bool[block.Length];
int[] lowIds = new int[block.Length];
int[] highIds = new int[block.Length];
int count = 0;
int countWithmask = 0;
double maxDistance = -1;
double maxDistanceWithMask = -1;
for (int i = 0; i < block.Length; i++)
{
inputPoints[i] = ColorToVector(block[i]);
if (block[i].A >= 8)
{
bool dubled = false;
bool dubledWithMask = false;
for (int j = i - 1; j >= 0 && (!dubled || !dubledWithMask); --j)
{
if (block[j].A >= 8)
{
double dist = (inputPoints[i] - inputPoints[j]).LengthSquared();
if (uniqueFlags[j])
{
dubled |= dist < 1e-6;
if (dist > maxDistance)
{
maxDistance = dist;
lowIds[0] = j;
highIds[0] = i;
}
}
if (mask[j] > 0 && mask[i] > 0)
{
dubledWithMask |= dist < 1e-6;
if (dist > maxDistanceWithMask)
{
maxDistanceWithMask = dist;
lowIds[2] = j;
highIds[2] = i;
}
}
}
}
if (!dubled)
{
count++;
uniqueFlags[i] = true;
if (maxDistance < 0) lowIds[0] = i;
}
if (!dubledWithMask && mask[i] > 0)
{
countWithmask++;
if (maxDistanceWithMask < 0) lowIds[2] = i;
}
}
else hasAlpha = true;
}
#endregion
#region Compress
bool compressed = false;
bool hasAlpha0 = hasAlpha;
Color[] dxtBoundColors = null;
if (count <= 4)
compressed = IsCompressedBlock(block, uniqueFlags, count, lowIds[0], highIds[0], out dxtBoundColors, ref hasAlpha);
Vector3[] dxtBoundPoints = new Vector3[2];
if (!compressed)
{
hasAlpha = hasAlpha0;
if (countWithmask == 0)
{
bestError = 0;
dxtBoundPoints[0] = inputPoints[lowIds[0]];
dxtBoundPoints[1] = inputPoints[highIds[0]];
hasAlpha = true;
}
else if (countWithmask == 1)
{
bestError = Math.Max(0, maxDistance);
uint dist0 = ColorSquaredDistance(block[lowIds[2]], block[lowIds[0]]);
uint dist1 = ColorSquaredDistance(block[lowIds[2]], block[highIds[0]]);
if (dist0 > dist1)
dxtBoundPoints[1] = inputPoints[lowIds[0]];
else
dxtBoundPoints[1] = inputPoints[highIds[0]];
dxtBoundPoints[0] = inputPoints[lowIds[2]];
hasAlpha = true;
}
else
{
Vector3 maxColor = inputPoints[lowIds[2]];
Vector3 minColor = inputPoints[highIds[2]];
UInt16 color0 = RoundAndExpand(ref maxColor);
UInt16 color1 = RoundAndExpand(ref minColor);
hasAlpha |= color0 == color1;
if (color0 < color1)
{
NvMath.Swap(ref maxColor, ref minColor);
NvMath.Swap(ref color0, ref color1);
}
dxtBoundPoints[0] = maxColor;
dxtBoundPoints[1] = minColor;
if (!hasAlpha)
{
uint indices = ComputeIndices4(inputPoints, maxColor, minColor);
OptimizeEndPoints4(inputPoints, ref dxtBoundPoints, ref indices);
}
//else
//{
// uint indices = computeIndices3(inputPoints, maxColor, minColor);
// optimizeEndPoints3(inputPoints, ref dxtBoundPoints, ref indices);
//}
}
}
else
{
for (int i = 0; i < 2; i++) dxtBoundPoints[i] = ColorToVector(dxtBoundColors[i]);
}
#endregion
#region Finilize
c0 = RoundAndExpand(ref dxtBoundPoints[0]);
c1 = RoundAndExpand(ref dxtBoundPoints[1]);
if (c0 == c1) hasAlpha = true;
if (c0 > c1 == hasAlpha)
{
NvMath.Swap(ref c0, ref c1);
NvMath.Swap(ref dxtBoundPoints[0], ref dxtBoundPoints[1]);
}
if (c0 > c1) NvMath.Swap(ref dxtBoundPoints[0], ref dxtBoundPoints[1]);
compressColors = new Color[4];
for (int i = 0; i < 2; i++) compressColors[i] = VectorToColor(dxtBoundPoints[i]);
if (hasAlpha)
{
compressColors[2] = SumColors(compressColors[0], compressColors[1], 1, 1);
compressColors[3] = Color.FromArgb(0, 0, 0, 0);
}
else
{
compressColors[2] = SumColors(compressColors[0], compressColors[1], 5, 3);
compressColors[3] = SumColors(compressColors[0], compressColors[1], 3, 5);
}
bestError = 0;
texels = 0;
for (int i = 0; i < 16; i++)
{
byte ci;
if (block[i].A >= 8)
{
double dist0 = ColorSquaredDistance(block[i], compressColors[0]);
double dist1 = ColorSquaredDistance(block[i], compressColors[1]);
double dist2 = ColorSquaredDistance(block[i], compressColors[2]);
double dist3 = ColorSquaredDistance(block[i], compressColors[3]);
if (dist0 < dist2)
{
ci = 0;
bestError += dist0;
}
else if (dist1 < dist2 && dist1 < dist3)
{
ci = 1;
bestError += dist1;
}
else if (dist2 < dist3)
{
ci = 2;
bestError += dist2;
}
else
{
ci = 3;
bestError += dist3;
}
}
else ci = 3;
texels |= (uint)(ci << (2 * i));
}
#endregion
return Math.Sqrt(bestError / 16);
}
/// <summary>
/// The compress ARGB32 format image to 4x4 blocks.
/// </summary>
/// <param name="bgra">The bgra data.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="boundaryApprox">The boundary approx flag (if flag = 1 then pixels inside of the 4x4 block will be ignored).</param>
/// <param name="onlyInterpolatedPalettes">Interpolated palettes using only flag (if flag = 0 then palettes each 4x4 block can be contain from 2 to 4 colors).</param>
/// <param name="palette">
/// The output palette.
/// WARNING: If color pairs number (palette size / 4) > 0x3FFF then output data has error color indexes.
/// </param>
/// <returns>
/// The compressed data<see cref="byte[]"/>.
/// </returns>
public static byte[] Compress(byte[] bgra, uint width, uint height, bool boundaryApprox, bool onlyInterpolatedPalettes, out byte[] palette)
{
uint wt = width / 4;
uint texelsCount = width * height / 16;
byte[] result = new byte[texelsCount * 6];
List<ulong> colorQuarts = new List<ulong>();
ulong[] colorPairsOrQuarts = new ulong[texelsCount];
byte[] palTypes = new byte[texelsCount];
int[] colorPairIndexes = new int[texelsCount];
for (uint y = 0; y < height; y += 4)
{
for (uint x = 0; x < width; x += 4)
{
Color[] block = new Color[16];
for (int j = 0; j < 4; j++)
{
for (int i = 0; i < 4; i++)
{
uint pixelIndex = (uint)((y + i) * width + (x + j));
int pixelBlockIndex = i * 4 + j;
block[pixelBlockIndex] = Color.FromArgb(
bgra[4 * pixelIndex + 3],
bgra[4 * pixelIndex + 2],
bgra[4 * pixelIndex + 1],
bgra[4 * pixelIndex + 0]);
}
}
int[] mask;
if (boundaryApprox)
{
mask = new[] {
1, 1, 1, 1,
1, 0, 0, 1,
1, 0, 0, 1,
1, 1, 1, 1
};
}
else
{
mask = new[] {
1, 1, 1, 1,
1, 1, 1, 1,
1, 1, 1, 1,
1, 1, 1, 1
};
}
// try
{
Color[] compressColors;
uint texels = 0;
ushort c0, c1;
double err = CompressBlock(block, mask, out compressColors, out c0, out c1, out texels);
uint texelIndex = y / 4 * wt + x / 4;
uint resIndex = texelIndex * 4;
result[resIndex + 0] = (byte)((texels >> 0) & 0xFF);
result[resIndex + 1] = (byte)((texels >> 8) & 0xFF);
result[resIndex + 2] = (byte)((texels >> 16) & 0xFF);
result[resIndex + 3] = (byte)((texels >> 24) & 0xFF);
palTypes[texelIndex] = (c0 <= c1) ? (byte)1 : (byte)3;
colorPairsOrQuarts[texelIndex] = (c0 <= c1) ? (uint)((c1 << 16) | c0) : (uint)((c0 << 16) | c1);
int colorIndex = -1;
if (!onlyInterpolatedPalettes && err >= 16)
{
Vector3[] cV = { Vector3.Zero, Vector3.Zero, Vector3.Zero, Vector3.Zero };
int[] cN = { 0, 0, 0, 0};
for (int i = 0; i < 16; i++)
{
uint ci = (texels >> (2 * i)) & 0x3;
cV[ci] += ColorToVector(block[i]);
cN[ci]++;
}
if (cN[2] != 0 || cN[3] != 0)
{
palTypes[texelIndex] -= 1;
ushort[] c = new ushort[4];
for (int ci = 0; ci < 4; ci++)
{
if (cN[ci] > 0) cV[ci] /= cN[ci];
c[ci] = RoundAndExpand(ref cV[ci]);
}
colorPairsOrQuarts[texelIndex] = ((ulong)((c[3] << 16) | c[2]) << 32) | (ulong)((c[1] << 16) | c[0]);
colorIndex = 2 * colorQuarts.IndexOf(colorPairsOrQuarts[texelIndex]);
if (colorIndex < 0)
{
colorIndex = 2 * colorQuarts.Count;
colorQuarts.Add(colorPairsOrQuarts[texelIndex]);
}
}
}
colorPairIndexes[texelIndex] = colorIndex;
}
// catch (Exception e)
// {
// throw e;
// }
}
}
// Generates pairs of colors. First writing pairs from quartets.
List<uint> colorPairs = new List<uint>();
for (int i = 0; i < colorQuarts.Count; i++)
{
UInt32 pair1 = (uint)(colorQuarts[i] & 0xFFFFFFFF);
UInt32 pair2 = (uint)((colorQuarts[i] >> 32) & 0xFFFFFFFF);
colorPairs.Add(pair1);
colorPairs.Add(pair2);
}
// Extend pairs list and writing <palette index data> to result.
for (uint i = 0; i < texelsCount; i++)
{
int colorIndex = colorPairIndexes[i];
if (palTypes[i] % 2 != 0)
{
UInt32 colorPair = (uint)colorPairsOrQuarts[i];
colorIndex = colorPairs.IndexOf(colorPair);
if (colorIndex < 0)
{
colorIndex = colorPairs.Count;
colorPairs.Add(colorPair);
}
}
if (colorIndex > 0x3FFF) colorIndex = 0;
ushort palInfo = (ushort)((colorIndex & 0x3FFF) | (palTypes[i] << 14));
uint resIndex = texelsCount * 4 + i * 2;
result[resIndex + 0] = (byte)((palInfo >> 0) & 0xFF);
result[resIndex + 1] = (byte)((palInfo >> 8) & 0xFF);
}
// Convert color pais to output palette data.
palette = new byte[colorPairs.Count * 4];
for (int i = 0; i < colorPairs.Count; i++)
{
byte[] colorsData = BitConverter.GetBytes(colorPairs[i]);
Array.Copy(colorsData, 0, palette, 4 * i, 4);
}
return result;
}
#endregion
}
}