mirror of
https://github.com/sampletext32/ParkanPlayground.git
synced 2026-08-15 02:57:49 +04:00
fpopov thanks
This commit is contained in:
@@ -6,11 +6,14 @@ public static class Extensions
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
0x22B8 => 32,
|
||||
0x115C => 16,
|
||||
0x0 => 8,
|
||||
0x235 => 16,
|
||||
0x22C => 16,
|
||||
0x115C => 16,
|
||||
0x58 => 16,
|
||||
0x378 => 32,
|
||||
0 => 32
|
||||
0x22B8 => 32,
|
||||
_ => throw new InvalidOperationException($"Unsupported Texm format {format}")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+90
-75
@@ -35,44 +35,31 @@ public record TexmHeader(
|
||||
/// <param name="Items">Элементы</param>
|
||||
public record PageHeader(string Page, int Count, List<PageItem> Items);
|
||||
|
||||
/// <summary>Элемент PAGE-секции TEXM.</summary>
|
||||
/// <param name="X">X-координата в атласе.</param>
|
||||
/// <param name="Width">Ширина элемента в атласе.</param>
|
||||
/// <param name="Y">Y-координата в атласе.</param>
|
||||
/// <param name="Height">Высота элемента в атласе.</param>
|
||||
public record PageItem(short X, short Width, short Y, short Height);
|
||||
|
||||
public class TexmFile
|
||||
/// <summary>TEXM файл.</summary>
|
||||
/// <param name="FileName">Исходное имя файла текстуры TEXM.</param>
|
||||
/// <param name="Header">Заголовок файла (length = 32).</param>
|
||||
/// <param name="MipmapBytes">Байты mipmap уровней.</param>
|
||||
/// <param name="Pages">PAGE-секция атласа, если присутствует.</param>
|
||||
/// <param name="IsIndexed">Признак indexed texture с lookup таблицей на 1024 байта.</param>
|
||||
/// <param name="LookupColors">Lookup таблица цветов: 256 цветов * 4 байта.</param>
|
||||
public record class TexmFile(
|
||||
string FileName,
|
||||
TexmHeader Header,
|
||||
List<byte[]> MipmapBytes,
|
||||
PageHeader? Pages,
|
||||
bool IsIndexed,
|
||||
byte[] LookupColors)
|
||||
{
|
||||
/// <summary>
|
||||
/// Исходное имя файла текстуры TEXM
|
||||
/// </summary>
|
||||
public string FileName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Заголовок файла, всегда 32 байта
|
||||
/// </summary>
|
||||
public TexmHeader Header { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Если в одной текстуре есть несколько MipMap уровней, тут будет несколько отдельных текстур
|
||||
/// </summary>
|
||||
public List<byte[]> MipmapBytes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Если текстура - это атлас, то здесь будет информация о координатах в атласе
|
||||
/// </summary>
|
||||
public PageHeader? Pages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// В некоторых случаях, текстура может быть закодирована как lookup таблица на 1024 байта (256 цветов),
|
||||
/// тогда сначала идёт 1024 байта lookup таблицы, а далее сами мипмапы, по 1 байту (каждый байт - индекс в lookup таблице)
|
||||
/// </summary>
|
||||
public bool IsIndexed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Lookup таблица цветов (каждый цвет закодирован как 4 байта (ARGB))
|
||||
/// </summary>
|
||||
public byte[] LookupColors { get; set; }
|
||||
|
||||
public async Task WriteToFolder(string folder)
|
||||
public Task WriteToFolder(string folder)
|
||||
{
|
||||
if (Directory.Exists(folder))
|
||||
if (!Directory.Exists(folder))
|
||||
{
|
||||
Directory.CreateDirectory(folder);
|
||||
}
|
||||
@@ -84,8 +71,8 @@ public class TexmFile
|
||||
{
|
||||
for (var i = 0; i < Header.MipmapCount; i++)
|
||||
{
|
||||
var mipWidth = Header.Width / (int) Math.Pow(2, i);
|
||||
var mipHeight = Header.Height / (int) Math.Pow(2, i);
|
||||
var mipWidth = Math.Max(1, Header.Width >> i);
|
||||
var mipHeight = Math.Max(1, Header.Height >> i);
|
||||
var reinterpretedPixels = ReinterpretIndexedMipmap(MipmapBytes[i], LookupColors);
|
||||
|
||||
var image = Image.LoadPixelData<Rgba32>(reinterpretedPixels, mipWidth, mipHeight);
|
||||
@@ -93,13 +80,13 @@ public class TexmFile
|
||||
image.SaveAsPng(Path.Combine(outputDir, Path.GetFileName(FileName)) + $"_{mipWidth}x{mipHeight}_indexed.png");
|
||||
}
|
||||
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
for (var i = 0; i < Header.MipmapCount; i++)
|
||||
{
|
||||
var mipWidth = Header.Width / (int) Math.Pow(2, i);
|
||||
var mipHeight = Header.Height / (int) Math.Pow(2, i);
|
||||
var mipWidth = Math.Max(1, Header.Width >> i);
|
||||
var mipHeight = Math.Max(1, Header.Height >> i);
|
||||
|
||||
var reinterpretedPixels = ReinterpretMipmapBytesAsRgba32(
|
||||
MipmapBytes[i],
|
||||
@@ -112,14 +99,16 @@ public class TexmFile
|
||||
|
||||
image.SaveAsPng(Path.Combine(outputDir, Path.GetFileName(FileName)) + $"_{Header.Format}_{mipWidth}x{mipHeight}.png");
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public byte[] GetRgba32BytesFromMipmap(int index, out int mipWidth, out int mipHeight)
|
||||
{
|
||||
var mipmapBytes = MipmapBytes[index];
|
||||
|
||||
mipWidth = Header.Width / (int) Math.Pow(2, index);
|
||||
mipHeight = Header.Height / (int) Math.Pow(2, index);
|
||||
mipWidth = Math.Max(1, Header.Width >> index);
|
||||
mipHeight = Math.Max(1, Header.Height >> index);
|
||||
|
||||
if (IsIndexed)
|
||||
{
|
||||
@@ -151,7 +140,7 @@ public class TexmFile
|
||||
result[i * 4 + 0] = r;
|
||||
result[i * 4 + 1] = g;
|
||||
result[i * 4 + 2] = b;
|
||||
result[i * 4 + 3] = 255;
|
||||
result[i * 4 + 3] = a;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -165,6 +154,8 @@ public class TexmFile
|
||||
888 => ReinterpretAs888(bytes, mipWidth, mipHeight),
|
||||
4444 => ReinterpretAs4444(bytes, mipWidth, mipHeight),
|
||||
565 => ReinterpretAs565(bytes, mipWidth, mipHeight),
|
||||
556 => ReinterpretAs556(bytes, mipWidth, mipHeight),
|
||||
88 => ReinterpretAs88(bytes, mipWidth, mipHeight),
|
||||
_ => throw new InvalidOperationException($"Invalid format {format}")
|
||||
};
|
||||
|
||||
@@ -178,22 +169,38 @@ public class TexmFile
|
||||
var result = new byte[bytes.Length * 2];
|
||||
for (var i = 0; i < span.Length; i += 2)
|
||||
{
|
||||
var rawPixel = span.Slice(i, 2);
|
||||
var rawPixel = BinaryPrimitives.ReadUInt16LittleEndian(span.Slice(i, 2));
|
||||
|
||||
// swap endianess
|
||||
(rawPixel[0], rawPixel[1]) = (rawPixel[1], rawPixel[0]);
|
||||
var r = (byte)(((rawPixel >> 11) & 0b11111) * 255 / 31);
|
||||
var g = (byte)(((rawPixel >> 5) & 0b111111) * 255 / 63);
|
||||
var b = (byte)((rawPixel & 0b11111) * 255 / 31);
|
||||
|
||||
var r = (byte)(((rawPixel[0] >> 3) & 0b11111) * 255 / 31);
|
||||
var g = (byte)((((rawPixel[0] & 0b111) << 3) | ((rawPixel[1] >> 5) & 0b111)) * 255 / 63);
|
||||
var b = (byte)((rawPixel[1] & 0b11111) * 255 / 31);
|
||||
|
||||
result[i / 2 * 4 + 0] = (byte)(0xff - r);
|
||||
result[i / 2 * 4 + 1] = (byte)(0xff - g);
|
||||
result[i / 2 * 4 + 2] = (byte)(0xff - b);
|
||||
result[i / 2 * 4 + 0] = r;
|
||||
result[i / 2 * 4 + 1] = g;
|
||||
result[i / 2 * 4 + 2] = b;
|
||||
result[i / 2 * 4 + 3] = 0xff;
|
||||
}
|
||||
|
||||
// swap endianess back
|
||||
(rawPixel[0], rawPixel[1]) = (rawPixel[1], rawPixel[0]);
|
||||
return result;
|
||||
}
|
||||
|
||||
private byte[] ReinterpretAs556(byte[] bytes, int mipWidth, int mipHeight)
|
||||
{
|
||||
var span = bytes.AsSpan();
|
||||
|
||||
var result = new byte[bytes.Length * 2];
|
||||
for (var i = 0; i < span.Length; i += 2)
|
||||
{
|
||||
var rawPixel = BinaryPrimitives.ReadUInt16LittleEndian(span.Slice(i, 2));
|
||||
|
||||
var r = (byte)(((rawPixel >> 11) & 0b11111) * 255 / 31);
|
||||
var g = (byte)(((rawPixel >> 6) & 0b11111) * 255 / 31);
|
||||
var b = (byte)((rawPixel & 0b111111) * 255 / 63);
|
||||
|
||||
result[i / 2 * 4 + 0] = r;
|
||||
result[i / 2 * 4 + 1] = g;
|
||||
result[i / 2 * 4 + 2] = b;
|
||||
result[i / 2 * 4 + 3] = 0xff;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -206,23 +213,36 @@ public class TexmFile
|
||||
var result = new byte[bytes.Length * 2];
|
||||
for (var i = 0; i < span.Length; i += 2)
|
||||
{
|
||||
var rawPixel = span.Slice(i, 2);
|
||||
var rawPixel = BinaryPrimitives.ReadUInt16LittleEndian(span.Slice(i, 2));
|
||||
|
||||
// swap endianess
|
||||
(rawPixel[0], rawPixel[1]) = (rawPixel[1], rawPixel[0]);
|
||||
|
||||
var a = (byte)(((rawPixel[0] >> 4) & 0b1111) * 17);
|
||||
var b = (byte)(((rawPixel[0] >> 0) & 0b1111) * 17);
|
||||
var g = (byte)(((rawPixel[1] >> 4) & 0b1111) * 17);
|
||||
var r = (byte)(((rawPixel[1] >> 0) & 0b1111) * 17);
|
||||
var a = (byte)(((rawPixel >> 12) & 0b1111) * 17);
|
||||
var r = (byte)(((rawPixel >> 8) & 0b1111) * 17);
|
||||
var g = (byte)(((rawPixel >> 4) & 0b1111) * 17);
|
||||
var b = (byte)((rawPixel & 0b1111) * 17);
|
||||
|
||||
result[i / 2 * 4 + 0] = r;
|
||||
result[i / 2 * 4 + 1] = g;
|
||||
result[i / 2 * 4 + 2] = b;
|
||||
result[i / 2 * 4 + 3] = a;
|
||||
|
||||
// swap endianess back
|
||||
(rawPixel[0], rawPixel[1]) = (rawPixel[1], rawPixel[0]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private byte[] ReinterpretAs88(byte[] bytes, int mipWidth, int mipHeight)
|
||||
{
|
||||
var span = bytes.AsSpan();
|
||||
|
||||
var result = new byte[bytes.Length * 2];
|
||||
for (var i = 0; i < span.Length; i += 2)
|
||||
{
|
||||
var l = span[i];
|
||||
var a = span[i + 1];
|
||||
|
||||
result[i / 2 * 4 + 0] = l;
|
||||
result[i / 2 * 4 + 1] = l;
|
||||
result[i / 2 * 4 + 2] = l;
|
||||
result[i / 2 * 4 + 3] = a;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -237,14 +257,9 @@ public class TexmFile
|
||||
{
|
||||
var rawPixel = span.Slice(i, 4);
|
||||
|
||||
var r = rawPixel[0];
|
||||
var g = rawPixel[1];
|
||||
var b = rawPixel[2];
|
||||
var w = rawPixel[3];
|
||||
|
||||
result[i + 0] = r;
|
||||
result[i + 1] = g;
|
||||
result[i + 2] = b;
|
||||
result[i + 0] = rawPixel[0];
|
||||
result[i + 1] = rawPixel[1];
|
||||
result[i + 2] = rawPixel[2];
|
||||
result[i + 3] = 255;
|
||||
}
|
||||
|
||||
@@ -270,7 +285,7 @@ public class TexmFile
|
||||
result[i + 0] = r;
|
||||
result[i + 1] = g;
|
||||
result[i + 2] = b;
|
||||
result[i + 3] = a;
|
||||
result[i + 3] = 255;
|
||||
|
||||
// swap endianess back
|
||||
// (rawPixel[0], rawPixel[1], rawPixel[2], rawPixel[3]) = (rawPixel[3], rawPixel[2], rawPixel[1], rawPixel[0]);
|
||||
@@ -278,4 +293,4 @@ public class TexmFile
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+24
-37
@@ -20,18 +20,18 @@ public class TexmParser
|
||||
var widthBytes = headerBytes[4..8];
|
||||
var heightBytes = headerBytes[8..12];
|
||||
var mipmapCountBytes = headerBytes[12..16];
|
||||
var strideBytes = headerBytes[16..20];
|
||||
var magic1Bytes = headerBytes[20..24];
|
||||
var formatOptionFlagsBytes = headerBytes[24..28];
|
||||
var flags4Bytes = headerBytes[16..20];
|
||||
var flags5Bytes = headerBytes[20..24];
|
||||
var unk6Bytes = headerBytes[24..28];
|
||||
var formatBytes = headerBytes[28..32];
|
||||
|
||||
var texmAscii = Encoding.ASCII.GetString(texmHeader).Trim('\0');
|
||||
var width = BinaryPrimitives.ReadInt32LittleEndian(widthBytes);
|
||||
var height = BinaryPrimitives.ReadInt32LittleEndian(heightBytes);
|
||||
var mipmapCount = BinaryPrimitives.ReadInt32LittleEndian(mipmapCountBytes);
|
||||
var stride = BinaryPrimitives.ReadInt32LittleEndian(strideBytes);
|
||||
var magic1 = BinaryPrimitives.ReadInt32LittleEndian(magic1Bytes);
|
||||
var formatOptionFlags = BinaryPrimitives.ReadInt32LittleEndian(formatOptionFlagsBytes);
|
||||
var flags4 = BinaryPrimitives.ReadInt32LittleEndian(flags4Bytes);
|
||||
var flags5 = BinaryPrimitives.ReadInt32LittleEndian(flags5Bytes);
|
||||
var unk6 = BinaryPrimitives.ReadInt32LittleEndian(unk6Bytes);
|
||||
var format = BinaryPrimitives.ReadInt32LittleEndian(formatBytes);
|
||||
|
||||
if (texmAscii != "Texm")
|
||||
@@ -39,64 +39,56 @@ public class TexmParser
|
||||
return new TexmParseResult(null, "Файл не начинается с Texm");
|
||||
}
|
||||
|
||||
var textureFile = new TexmFile()
|
||||
{
|
||||
FileName = file
|
||||
};
|
||||
|
||||
var header = new TexmHeader(
|
||||
texmAscii,
|
||||
width,
|
||||
height,
|
||||
mipmapCount,
|
||||
stride,
|
||||
magic1,
|
||||
formatOptionFlags,
|
||||
flags4,
|
||||
flags5,
|
||||
unk6,
|
||||
format
|
||||
);
|
||||
|
||||
textureFile.Header = header;
|
||||
List<byte[]> mipmapBytesList;
|
||||
var isIndexed = false;
|
||||
byte[] lookupColors = [];
|
||||
|
||||
if (format == 0)
|
||||
{
|
||||
// если формат 0, то текстура использует lookup таблицу в первых 1024 байтах (256 разных цветов в формате ARGB 888)
|
||||
|
||||
var lookupColors = new byte[1024];
|
||||
lookupColors = new byte[1024];
|
||||
stream.ReadExactly(lookupColors, 0, lookupColors.Length);
|
||||
|
||||
textureFile.LookupColors = lookupColors;
|
||||
|
||||
var mipmapBytesList = ReadMipmapsAsIndexes(
|
||||
mipmapBytesList = ReadMipmapsAsIndexes(
|
||||
stream,
|
||||
mipmapCount,
|
||||
width,
|
||||
height
|
||||
);
|
||||
|
||||
textureFile.MipmapBytes = mipmapBytesList;
|
||||
textureFile.IsIndexed = true;
|
||||
isIndexed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var mipmapBytesList = ReadMipmaps(
|
||||
mipmapBytesList = ReadMipmaps(
|
||||
stream,
|
||||
format.AsStride(),
|
||||
mipmapCount,
|
||||
width,
|
||||
height
|
||||
);
|
||||
|
||||
textureFile.MipmapBytes = mipmapBytesList;
|
||||
}
|
||||
|
||||
PageHeader? pages = null;
|
||||
if (stream.Position < stream.Length)
|
||||
{
|
||||
// has PAGE data
|
||||
var pageHeader = ReadPage(stream);
|
||||
|
||||
textureFile.Pages = pageHeader;
|
||||
pages = ReadPage(stream);
|
||||
}
|
||||
|
||||
var textureFile = new TexmFile(file, header, mipmapBytesList, pages, isIndexed, lookupColors);
|
||||
return new TexmParseResult(textureFile);
|
||||
}
|
||||
|
||||
@@ -142,17 +134,12 @@ public class TexmParser
|
||||
|
||||
private static List<byte[]> ReadMipmaps(Stream stream, int stride, int mipmapCount, int topWidth, int topHeight)
|
||||
{
|
||||
if (stride == 0)
|
||||
{
|
||||
stride = 16;
|
||||
}
|
||||
|
||||
List<int> mipmapByteLengths = [];
|
||||
|
||||
for (int i = 0; i < mipmapCount; i++)
|
||||
{
|
||||
var mipWidth = topWidth / (int) Math.Pow(2, i);
|
||||
var mipHeight = topHeight / (int) Math.Pow(2, i);
|
||||
var mipWidth = Math.Max(1, topWidth >> i);
|
||||
var mipHeight = Math.Max(1, topHeight >> i);
|
||||
|
||||
var imageByteLength = mipWidth * mipHeight * (stride / 8);
|
||||
mipmapByteLengths.Add(imageByteLength);
|
||||
@@ -178,8 +165,8 @@ public class TexmParser
|
||||
|
||||
for (int i = 0; i < mipmapCount; i++)
|
||||
{
|
||||
var mipWidth = topWidth / (int) Math.Pow(2, i);
|
||||
var mipHeight = topHeight / (int) Math.Pow(2, i);
|
||||
var mipWidth = Math.Max(1, topWidth >> i);
|
||||
var mipHeight = Math.Max(1, topHeight >> i);
|
||||
|
||||
var imageByteLength = mipWidth * mipHeight;
|
||||
mipmapByteLengths.Add(imageByteLength);
|
||||
@@ -198,4 +185,4 @@ public class TexmParser
|
||||
|
||||
return mipmapBytesList;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user