// ----------------------------------------------------------------------- // // // 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 . // // // ----------------------------------------------------------------------- namespace Ekona.Images { using System; using System.Collections.Generic; using System.Drawing; using System.Globalization; using System.Text; using Mathematics; /// /// Fast texture compressor for Nintendo DS format /// 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 /// /// The compress 4x4 block. /// /// /// The block of 4x4 colors. /// /// /// The mask of 4x4 pixels (1 - visible, 0 - not visible). /// /// /// Four compressed colors. /// /// /// The color0. /// /// /// The color1. /// /// /// Texels. /// /// /// The approximation error. /// 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); } /// /// The compress ARGB32 format image to 4x4 blocks. /// /// The bgra data. /// The width. /// The height. /// The boundary approx flag (if flag = 1 then pixels inside of the 4x4 block will be ignored). /// Interpolated palettes using only flag (if flag = 0 then palettes each 4x4 block can be contain from 2 to 4 colors). /// /// The output palette. /// WARNING: If color pairs number (palette size / 4) > 0x3FFF then output data has error color indexes. /// /// /// The compressed data. /// 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 colorQuarts = new List(); 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 colorPairs = new List(); 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 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 } }