mirror of
https://github.com/AdAstra-LD/DS-Pokemon-Rom-Editor.git
synced 2026-05-14 15:59:43 -05:00
914 lines
35 KiB
C#
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
|
|
}
|
|
}
|